1

I know this has asked before, but I have not yet found an answer that has actually helped me.

What I'm trying to do is write a launchd daemon in Swift, that will catch the shutdown notification and run a script, or do something else.

This is the latest iteration I've tried, but it doesn't work. The logging messages are of course to verify if it works. The current behavior is, the daemon launches succesfully and sets the observer (or at least notifies me that it does, although I'm not sure if I can check whether it was actually successful in doing so)

But it never catches the powerdown notification as it should.

Here's the Swift code:

EDIT: I have updated this code with a newer iteration, which now does not give any selector errors (and catches activate/hide notifications properly), but it still fails to catch the shutdown notification.

import AppKit
import os

public class ShutdownHandlerDelegate : NSObject, NSApplicationDelegate {
    static let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "LoggingTest")
    var shutdownObserver: NSObjectProtocol?
    @objc func handleShutdown() {
        ShutdownHandlerDelegate.logger.notice("System is about to shut down. This is us logging that for debug...")
        NSWorkspace.shared.notificationCenter.removeObserver(shutdownObserver!)
        shutdownObserver = nil
        // Perform your pre-shutdown tasks here
    }
    @objc func handleActivate(_ notification: Notification) {
        if let app = (notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication)?.bundleIdentifier {
            ShutdownHandlerDelegate.logger.notice("\(app, privacy: .public) will now be activated!")
            // Perform your pre-shutdown tasks here
        }
    }
    @objc func handleHide(_ notification: Notification) {
        if let app = (notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication)?.bundleIdentifier {
            ShutdownHandlerDelegate.logger.notice("\(app, privacy: .public) will now be hidden!")
            // Perform your pre-shutdown tasks here
        }
    }
    func setUpObservers() {
        self.shutdownObserver = NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.willPowerOffNotification, object: nil, queue: OperationQueue.main, using: {
            (_ note) in
            self.handleShutdown()
        })
//      NSWorkspace.shared.notificationCenter.addObserver(
//          self,
//          selector: #selector(handleShutdown(_:)),
//          name: NSWorkspace.willPowerOffNotification,
//          object: nil
//      )
        NSWorkspace.shared.notificationCenter.addObserver(
            self,
            selector: #selector(handleActivate(_:)),
            name: NSWorkspace.didActivateApplicationNotification,
            object: nil
        )
        NSWorkspace.shared.notificationCenter.addObserver(
            self,
            selector: #selector(handleHide(_:)),
            name: NSWorkspace.didHideApplicationNotification,
            object: nil
        )
    }
    override init() {
        super.init()
    }
    deinit {
        if let observer = shutdownObserver {
            NSWorkspace.shared.notificationCenter.removeObserver(observer)
        }
    }
}

@main
public class ShutdownHandler : NSObject {
    let _app : NSApplication?
    static let _delegate : ShutdownHandlerDelegate = ShutdownHandlerDelegate()
    static public func main() {
//      _app.delegate = ShutdownHandlerDelegate
        _delegate.setUpObservers()
        ShutdownHandlerDelegate.logger.notice("We're registering the shutdown observer. bundle-id is: \(Bundle.main.bundleIdentifier!, privacy: .public)")
        NSApplication.shared.run() // This keeps the app running & makes sure events are received
    }
    override init () {
        _app = NSApplication.shared
        _app!.delegate = ShutdownHandler._delegate
        super.init()
    }
}

And here's the launchd file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.lord-kamina.shutdownHandler</string>
    <key>ProgramArguments</key>
    <array>
    <string>/usr/local/bin/shutdown-daemon</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
</dict>
</plist>
6
  • A small update... I tried using other notifications, while manually running from terminal, to try and pinpoint where the issue is, and now I'm seeing these errors on the consiole: shutdown-daemon[16353:125785] -[NSApplication handleActivate:]: unrecognized selector sent to instance 0x7fa219a04f00 Commented Aug 21, 2023 at 15:52
  • Is handleActivate: a selector you're trying to send from some other part of your codebase? Commented Aug 21, 2023 at 16:33
  • Hell, sorry. I just copied the latest instance from the log. No, it's also there along with handleShutdown in a newer iteration. Will correct the code. Commented Aug 21, 2023 at 18:38
  • I see the handleActivate method now, but what's actually calling it? DO you have some other notification observer configured to send handleActivate: to your object? Commented Aug 21, 2023 at 19:44
  • The notification is sent to ShutdownHandler._app = NSApplication.shared. Where/when is ShutdownHandler instantiated? Do you set the app delegate? Did you try addObserver(forName:object:queue:using:)? Commented Aug 22, 2023 at 23:31

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.