After some fiddling, while we can't intercept keyboard (and shouldn't to reliably detect a quit), it's actually possible to listen to some distributed notifications in the screensaver that tells us when the user gets logged in, and use that to call our code to stop animations/sound, as there's a sizable lag between the moment we are effectively killed and the moment the user gets back to desktop.
I did find some events in this answer here : Monitoring Screensaver Events in OSX and while it's a bit dated those events are still fired and can be intercepted inside the screensaver despite the sandboxing. I did found a few new ones though that may be interesting for other purposes.
Anyway, here's what's working for me :
class AerialView: ScreenSaverView {
...
func setNotifications {
DistributedNotificationCenter.default.addObserver(self, selector: #selector(AerialView.willStop(_:)), name: Notification.Name("com.apple.screensaver.willstop"), object: nil)
}
@objc func willStop(_ aNotification: Notification) {
NSLog("############ willStop")
// Put your stop audio/animation code here
}
}
(you will have to call setNotifications yourself or put that code somewhere else).
In practice, there are multiple events that are fired one after the other when the screensaver exits:
com.apple.screensaver.willstop
com.apple.screensaver.didstop
com.apple.screenIsUnlocked
com.apple.screenLockUIIsShown
Here's some logging of those events
2021-04-27 14:52:00.564 : ############ screenLockUIIsShown
2021-04-27 14:52:01.873 : ############ screenIsUnlocked
2021-04-27 14:52:01.873 : ############ willStop
2021-04-27 14:52:01.879 : ############ didStop
The first event was when I pressed a key, about 1.5s later the watch unlock unlocked the screen and you can see the 3 events being fired in succession.
The actual termination of the screensaver only happened at 14:52:07 in that case.
For completeness, some new events to detect when the login UI is up and dismissed (could be useful for some other things). This is in 11.4 and I have no idea if those events are fired in previous versions of macOS. In firing order :
com.apple.shieldWindowRaised
com.apple.screenIsLocked
com.apple.screenLockUIIsShown
com.apple.screenLockUIIsHidden
com.apple.shieldWindowLowered
com.apple.screenIsUnlocked