To learn about macOS services (daemons and agents), I have a sample GUI app which counts every 10 sec in the background and displays some UI.
In AppDelegate.swift
class AppDelegate: NSObject, NSApplicationDelegate {
static let TAG: String = "[BackgroundProcess]: "
static let sNotificationCenter: UNUserNotificationCenter = UNUserNotificationCenter.current()
// Uses ViewController to define and display UI
var mainWindow: NSWindow?
func applicationWillFinishLaunching(_ notification: Notification) {
NSLog(AppDelegate.TAG + "applicationWillFinishLaunching(_:)")
// Request user permission to display notification
AppDelegate.sNotificationCenter.requestAuthorization(options: [.alert, .sound, .badge]) { (granted, error) in
if(granted) {
NSLog(AppDelegate.TAG + "Permission granted!")
} else {
NSLog(AppDelegate.TAG + "Permission denied!")
}
}
let queue: DispatchQueue = DispatchQueue.global(qos: .background)
queue.async {
self.countEvery10Sec()
}
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
NSLog(AppDelegate.TAG + "applicationDidFinishLaunching(_:)")
// Sets the window variable and displays UI
CreateMainWindow()
}
// Other delegate methods
func countEvery10Sec () {
var numOfSeconds: Int = 0
var date: Date
while(true) {
// Sleep for 10 sec
Thread.sleep(forTimeInterval: 10)
numOfSeconds += 10
date = Date.now
NSLog(AppDelegate.TAG + "Time = " + date.description)
// When 1 min is up, display a notification
if (numOfSeconds % 60 == 0) {
NSLog(AppDelegate.TAG + "That's 60 sec. Displaying notification...")
DisplayNotification(date: date)
// Reset the count to start again
numOfSeconds = 0;
}
}
}
}
App is installed as an agent and started using launchctl. I got the following logs,
[BackgroundProcess]: applicationWillFinishLaunching(_:)
[BackgroundProcess]: applicationDidFinishLaunching(_:)
[BackgroundProcess]: CreateMainWindow()
[BackgroundProcess]: Permission granted!
[BackgroundProcess]: Time = 2023-02-09 06:28:12 +0000
[BackgroundProcess]: Time = 2023-02-09 06:28:22 +0000
[BackgroundProcess]: Time = 2023-02-09 06:28:32 +0000
[BackgroundProcess]: Time = 2023-02-09 06:28:42 +0000
[BackgroundProcess]: Time = 2023-02-09 06:28:52 +0000
[BackgroundProcess]: Time = 2023-02-09 06:29:02 +0000
[BackgroundProcess]: That's 60 sec. Displaying notification...
[BackgroundProcess]: Time = 2023-02-09 06:29:12 +0000
Permission granted as app was registered as an agent, which can interact with user. The GUI was visible and there was a notification every minute in the notification centre. This is as expected.
But when app is registered as a daemon and launched,
[BackgroundProcess]: applicationWillFinishLaunching(_:)
[BackgroundProcess]: Permission denied!
[BackgroundProcess]: applicationDidFinishLaunching(_:)
[BackgroundProcess]: CreateMainWindow()
[BackgroundProcess]: Time = 2023-02-09 06:13:39 +0000
[BackgroundProcess]: Time = 2023-02-09 06:13:49 +0000
[BackgroundProcess]: Time = 2023-02-09 06:13:59 +0000
[BackgroundProcess]: Time = 2023-02-09 06:14:09 +0000
[BackgroundProcess]: Time = 2023-02-09 06:14:19 +0000
[BackgroundProcess]: Time = 2023-02-09 06:14:29 +0000
[BackgroundProcess]: That's 60 sec. Displaying notification...
Permission denied, as a daemon should not interact with the user. And according to documentation,
A daemon cannot display any GUI; more specifically, it is not allowed to connect to the window server
That means, even if UI code gets executed, app wouldn't be able display UI. But in my example, the daemon showed GUI (same as that of an agent). But it wasn't able to display notification every minute.
Since daemons cannot connect to window server, they cannot display UI correct? But it was able to show the GUI. Does that mean app has to take care not to display any UI when registered as a daemon and the documentation is wrong?