-
Hello, firstly, thanks to your great work! Currently, I'm doing some POC for this idea base on your code. I've been able to run the program. But when I start to implement my ideas, such as listening to notification in helper, this program will report some errors, I refer to some documentation, but still no solution, I would like to ask you to see if there are any good ideas, I personally am an amateur in the development of Apple applications. I use import Foundation
import SecureXPC
NSLog("starting helper tool. PID \(getpid()). PPID \(getppid()).")
NSLog("version: \(try HelperToolInfoPropertyList.main().version.rawValue)")
// Command line arguments were provided, so process them
if CommandLine.arguments.count > 1 {
// Remove the first argument, which represents the name (typically the full path) of this helper tool
var arguments = CommandLine.arguments
_ = arguments.removeFirst()
NSLog("run with arguments: \(arguments)")
if let firstArgument = arguments.first {
if firstArgument == Uninstaller.commandLineArgument {
try Uninstaller.uninstallFromCommandLine(withArguments: arguments)
} else {
NSLog("argument not recognized: \(firstArgument)")
}
}
} else if getppid() == 1 { // Otherwise if started by launchd, start up XPC server
NSLog("parent is launchd, starting up XPC server")
// register notification
WorkspaceEvents.registerFrontAppChangeNote()
let server = try XPCServer.forThisBlessedHelperTool()
server.registerRoute(SharedConstants.allowedCommandRoute, handler: AllowedCommandRunner.run(message:))
server.registerRoute(SharedConstants.uninstallRoute, handler: Uninstaller.uninstallFromXPC)
server.registerRoute(SharedConstants.updateRoute, handler: Updater.updateHelperTool(atPath:))
server.setErrorHandler { error in
if case .connectionInvalid = error {
// Ignore invalidated connections as this happens whenever the client disconnects which is not a problem
} else {
NSLog("error: \(error)")
}
}
server.startAndBlock()
} else { // Otherwise started via command line without arguments, print out help info
print("Usage: \(try CodeInfo.currentCodeLocation().lastPathComponent) <command>")
print("\nCommands:")
print("\t\(Uninstaller.commandLineArgument)\tUnloads and deletes from disk this helper tool and configuration.")
} class WorkspaceEvents {
static func registerFrontAppChangeNote() {
NSLog("starting registerFrontAppChangeNote 7")
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(receiveFrontAppChangeNote(_:)), name: NSWorkspace.didActivateApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(receiveFrontAppLaunchNote(_:)), name: NSWorkspace.didLaunchApplicationNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(receiveFrontAppTerminateNote(_:)), name: NSWorkspace.didTerminateApplicationNotification, object: nil)
NSLog("end registerFrontAppChangeNote 7")
}
// We add apps when we receive a didActivateApplicationNotification notification, not when we receive an apps launched, because any app will have an apps launched notification.
// But we are only interested in apps that have windows. We think that since an app can be activated, it must have a window, and subscribing to its window event makes sense and is likely to work, even if it requires multiple retries to subscribe.
// I'm not very sure if there is an edge case, but so far testing down the line has not revealed it.
// When we receive the didActivateApplicationNotification notification, NSRunningApplication.isActive=true, even if the app is not the frontmost window anymore.
// If we go to add the application when we receive the message of apps launched, at this time NSRunningApplication.isActive may be false, and try axUiElement.windows() may also throw an exception.
// For those background applications, we don't receive notifications of didActivateApplicationNotification until they have their own window. For example, those menu bar applications.
@objc static func receiveFrontAppChangeNote(_ notification: Notification) {
NSLog("receiveFrontAppChangeNote")
if let application = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication {
NSLog("OS event \(notification.name.rawValue), \(application.bundleIdentifier), \(application.processIdentifier)")
}
}
@objc static func receiveFrontAppLaunchNote(_ notification: Notification) {
NSLog("receiveFrontAppLaunchNote")
if let application = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication {
NSLog("OS event \(notification.name.rawValue), \(application.bundleIdentifier), \(application.processIdentifier)")
}
}
@objc static func receiveFrontAppTerminateNote(_ notification: Notification) {
NSLog("receiveFrontAppTerminateNote")
if let application = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication {
NSLog("OS event \(notification.name.rawValue), \(application.bundleIdentifier), \(application.processIdentifier)")
}
}
} But when the daemon launched, I got some errors.
|
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 3 replies
-
I'm not entirely sure what you're looking to do, but perhaps you want a LaunchAgent? LaunchDaemons don't interact with macOS's window server upon which all GUIs are based. |
Beta Was this translation helpful? Give feedback.
NSWorkspace
wouldn't be expected to work from a LaunchDaemon.NSWorkspace
is part of AppKit which requires a GUI per-session bootstrap namespace. See Technical Note TN2083's Layered Frameworks section for more details.I'm not entirely sure what you're looking to do, but perhaps you want a LaunchAgent? LaunchDaemons don't interact with macOS's window server upon which all GUIs are based.