macOS Ventura: Crash after closing the Options/Settings Sheet
M1 mac mini, macOS Ventura Beta 1 22a5226r two monitors (17" HDMI and 15" VGA)
I believe this is a macOS bug, as it's being seen with various screensavers such as https://github.com/soffes/Clock.saver and https://iscreensaver.com
Reported to Apple Feedback as FB10103112.
Seen both with ARM native and Intel x64 Rosetta emulated screensavers.
I'm trying to find a workaround using my own screensaver software, if I stumble across anything I will report back.
Example crash log:
Process: Screen Saver [3234]
Path: /System/Library/ExtensionKit/Extensions/Screen Saver.appex/Contents/MacOS/Screen Saver
Identifier: com.apple.ScreenSaver-Settings.extension
Version: 1.0 (1)
Build Info: DesktopScreenEffectsPref-283000000000000~10
Code Type: ARM-64 (Native)
Parent Process: launchd [1]
User ID: 501
Date/Time: 2022-06-08 07:46:13.7537 -0700
OS Version: macOS 13.0 (22A5266r)
Report Version: 12
Anonymous UUID: E1E1B825-F9F6-3DB0-A932-7E0490DDEA1D
Time Awake Since Boot: 2100 seconds
System Integrity Protection: enabled
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x0000000102699b98
Termination Reason: Namespace SIGNAL, Code 5 Trace/BPT trap: 5
Terminating Process: exc handler [3234]
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 Screen Saver 0x102699b98 0x102684000 + 88984
1 Screen Saver 0x1026999e8 0x102684000 + 88552
2 Screen Saver 0x10268d80c 0x102684000 + 38924
3 ScreenSaver 0x1c8a3dbd0 -[LegacyScreenSaverModule _requestDidCompleteNotification:] + 360
4 CoreFoundation 0x18a87c2d4 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 148
5 CoreFoundation 0x18a9199bc ___CFXRegistrationPost_block_invoke + 88
6 CoreFoundation 0x18a919904 _CFXRegistrationPost + 440
7 CoreFoundation 0x18a84c42c _CFXNotificationPost + 708
8 Foundation 0x18b72b778 -[NSNotificationCenter postNotificationName:object:userInfo:] + 88
9 ScreenSaver 0x1c8a34ed8 __58-[ScreenSaverExtensionModule initWithExtension:isPreview:]_block_invoke.28 + 288
10 ExtensionFoundation 0x1f4d964ac __93-[EXConcreteExtension _completeRequestReturningItems:forExtensionContextWithUUID:completion:]_block_invoke + 132
11 libdispatch.dylib 0x18a61daf4 _dispatch_call_block_and_release + 32
12 libdispatch.dylib 0x18a61f61c _dispatch_client_callout + 20
13 libdispatch.dylib 0x18a62ddd4 _dispatch_main_queue_drain + 928
14 libdispatch.dylib 0x18a62da24 _dispatch_main_queue_callback_4CF + 44
15 CoreFoundation 0x18a8c854c __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16
16 CoreFoundation 0x18a8863c4 __CFRunLoopRun + 2036
17 CoreFoundation 0x18a88547c CFRunLoopRunSpecific + 612
18 HIToolbox 0x193fbe634 RunCurrentEventLoopInMode + 292
19 HIToolbox 0x193fbe478 ReceiveNextEventCommon + 700
20 HIToolbox 0x193fbe1a4 _BlockUntilNextEventMatchingListInModeWithFilter + 72
21 AppKit 0x18dbabd78 _DPSNextEvent + 632
22 AppKit 0x18dbaaf08 -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 728
23 ViewBridge 0x19240c4d0 __75-[NSViewServiceApplication nextEventMatchingMask:untilDate:inMode:dequeue:]_block_invoke + 136
24 ViewBridge 0x19240c2f0 -[NSViewServiceApplication _withToxicEventMonitorPerform:] + 152
25 ViewBridge 0x1923fa304 -[NSViewServiceApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 168
26 AppKit 0x18dd54470 -[NSWindow(NSEventRouting) trackEventsMatchingMask:timeout:mode:handler:] + 216
27 ViewBridge 0x192460718 -[NSRemoteView _beginTrackingLoop:reply:] + 472
28 ViewBridge 0x19246394c -[NSRemoteViewMarshal beginTrackingLoop:reply:] + 84
29 CoreFoundation 0x18a869bc4 __invoking___ + 148
30 CoreFoundation 0x18a869a3c -[NSInvocation invoke] + 428
31 ViewBridge 0x1923fb534 __deferNSXPCInvocationOntoMainThread_block_invoke + 132
32 ViewBridge 0x1923f0814 __wrapBlockWithVoucher_block_invoke + 56
33 ViewBridge 0x19248147c kNotRunningOnAppKitCompatibleThread_block_invoke + 360
34 CoreFoundation 0x18a8871a4 __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 28
35 CoreFoundation 0x18a8870b8 __CFRunLoopDoBlocks + 368
36 CoreFoundation 0x18a886564 __CFRunLoopRun + 2452
37 CoreFoundation 0x18a88547c CFRunLoopRunSpecific + 612
38 HIToolbox 0x193fbe634 RunCurrentEventLoopInMode + 292
39 HIToolbox 0x193fbe478 ReceiveNextEventCommon + 700
40 HIToolbox 0x193fbe1a4 _BlockUntilNextEventMatchingListInModeWithFilter + 72
41 AppKit 0x18dbabd78 _DPSNextEvent + 632
42 AppKit 0x18dbaaf08 -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 728
43 ViewBridge 0x19240c4d0 __75-[NSViewServiceApplication nextEventMatchingMask:untilDate:inMode:dequeue:]_block_invoke + 136
44 ViewBridge 0x19240c2f0 -[NSViewServiceApplication _withToxicEventMonitorPerform:] + 152
45 ViewBridge 0x1923fa304 -[NSViewServiceApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 168
46 AppKit 0x18db9f2cc -[NSApplication run] + 464
47 AppKit 0x18db76838 NSApplicationMain + 880
48 libxpc.dylib 0x18a522840 _xpc_objc_main + 976
49 libxpc.dylib 0x18a5220ec xpc_main + 108
50 Foundation 0x18b79aeb8 -[NSXPCListener resume] + 312
51 ExtensionKit 0x1f4df5170 -[_EXRunningUIViewServiceExtension startWithArguments:count:] + 1148
52 ExtensionFoundation 0x1f4db0c4c EXExtensionMain + 232
53 Foundation 0x18b7f80c8 NSExtensionMain + 240
54 dyld 0x216b5fc10 start + 2368
Hey @xmddmx
I can confirm I also see this when I close Aerial's sheet.
As far as I understand the new System Preferences has been rewritten as a bunch of .appex, and it's the ScreenSaver.appex for System Preferences that crashes (which itself launches legacyScreenSaver.appex which then launches our plugins). I have the crash with Aerial too here but it's minimal, the panel goes blank + the crash log in console.
I think you did right filling the feedback, as I don't think there's anything we can do on our end.
Historically closing the sheet has been a mess as there are few solutions that work accross multiple macOS versions.
This is what I came up with a long time ago and has been working fine, it's possible that this triggers something :
window?.sheetParent?.endSheet(window!)
(taken from here, Aerial does the same but in a more convoluted way) : https://github.com/glouel/ScreenSaverMinimal/blob/master/ScreenSaverMinimal/ConfigureSheetController.swift
It's possible that using a simpler dismissal could make it not crash though I haven't tried yet. Feel free to reference this in the feedback if needed.
Edit : I also checked just in case but there's still no Swift screen saver template in Xcode 14 ! Maybe someday if we get the non legacy API 😅
I notice that some of the sample code calls .loadWindow() on the Preferences window controller.
Example:
https://github.com/soffes/Clock.saver/blob/main/Clock/Classes/MainView.swift#L24
Apple says
You should never directly invoke this method. Instead, access the window property so the windowDidLoad() and windowWillLoad() methods are invoked
See https://developer.apple.com/documentation/appkit/nswindowcontroller/1535137-loadwindow
I've played with not calling loadWindow() but it didn't seem to help.
For what it's worth, I don't use loadWindow on either Aerial nor my ScreenSaverMinimal sample.
Historically closing the sheet has been a mess as there are few solutions that work accross multiple macOS versions
Agreed. In earlier OS's you could do this:
override var hasConfigureSheet: Bool { return true }
override var configureSheet: NSWIndow? {
// launch a stand-alone app which hosts the configure sheet
configProcess = Process()
configProcess?.arguments = arguments
configProcess?.launchPath = "/path/to/helper/app"
configProcess?.terminationHandler = configTerminatedHandler
configProcess?.launch()
return nil
}
However, in my testsing, it's very fickle: macOS 10.11 through 10.13 : configureSheet=nil works OK macOS 10.14, 10.15, 11.x, and 12.x : configureSheet=nil crashes, so need to provide a dummy configureSheet macOS 13.0 : configureSheet crashes when not nil. Maybe try using nil again?
Since Aerial already has the helper app, I wonder if you should consider doing something similar - hosting the Preferences in a stand-alone app?
Hmm, I had no idea this was possible !
Is your helper somewhere in an app in /Applications ? Despite the sandboxing you can launch stuff easily and I do that a bit for various stuff, including launching some app bundled with companion (although that's not fully baked in right now) but it's mostly command line stuff.
I do it in reverse right now, Aerial Companion loads Aerial.saver and puts it in a window or triggers the configuresheet manually. My biggest issue is I want to sync settings between both and the sandbox makes it hard.
In your situation though, I'm unclear about how settings work ? Your app panel conigureSheet should have it's settings outside the sandbox ? Yet the screensaver proper will read from the legacyScreenSaver sandbox ? How do you work that around, or am I missing something ?
In any case thanks for the info a lot of food for thought !
Interesting.
Here's what I'm doing:
- the Helper app is inside the .saver bundle, located at : Example.saver/Contents/Helpers/Helper.app
- the helper app reads/writes data to/from
~/Library/Containers/com.apple.ScreenSaver.Engine.legacyScreenSaver/Data/Library/Preferences/com.example.plist(or)~/Library/Containers/com.apple.ScreenSaver.Engine.legacyScreenSaver.x86-64/Data/Library/Preferences/com.example.plistwhen running under Rosetta, since this is an Intel screensaver. - the helper app is not, itself, sandboxed, but seems to be fine working within the sandbox that legacyScreenSaver is running inside.
- I'm guessing that this woks because the helper.app is being launched from within the .saver when it's running sandboxed?
- I did run into one issue: my helper app uses WKWebView2, and that wasn't happy running inside this stack - I reverted to WebView and it worked fine. I think this bug was fixed in later macOS 12.x versions
- [Edit to add] Also, I believe that a regular app can write to the sandboxed plist data just fine, but the app running inside the sandbox can not break out of it (which makes sense). In other words, it should be possible for your Companion app to write data that the .saver can read when running sandboxed.
Clearly, the best outcome would be if Apple:
- fixed the bug
- gave us documentation / sample code showing how to do a Swift screensaver correctly
- published the Appex screensaver API
But given their track record, I feel like exploring these workarounds is prudent in case these issues go un-fixed in Ventura...
Clearly, the best outcome would be if Apple:
- fixed the bug
I'll keep an eye on this but considering it's beta 1 and a whole new System Preferences, I think the odds are higher than usual they'll fix this. If needed I'll also file another feedback, that does help sometimes. I haven't checked yet if Obj-C screensavers that are run through legacyScreenSaver also have the same issue. It could very well be a Swift thing (there was a lot of that in Catalina).
- gave us documentation / sample code showing how to do a Swift screensaver correctly
- published the Appex screensaver API
I keep hoping every year ! Surely, some day, they would. But even their latest screensavers (like the Hello one for iMac) are legacy plugins...
Regarding the other stuff:
- helper app not sandboxed : I can also confirm that even when you call an app outside the bundle, it runs unsandboxed (which completely undermines the sandbox, but hey...)
- prefs file in legacyScreenSaver : Yep, I believe that because your app is inside the .saver bundle, that's why you end up having your plist saved in the sandbox so that makes sense. It's still an interesting idea on how to do things and I'll think about it to see it it makes sense.
- Does WKWebView2 require some specific entitlement ? I think I tried using it at some point though that wasn't on Aerial I think, and had issues.
- Sharing prefs : Theoretically yes, the unsandboxed version of Aerial could definitely read/write the sandboxed file. It's unclear to me however how to approach this, since we use in screen savers the the
ScreenSaverDefaultwrapper thing, I may need to override that in some way, though I'm not sure yet how. I hope to investigate this soon.
- Does WKWebView2 require some specific entitlement ? I think I tried using it at some point though that wasn't on Aerial I think, and had issues.
Have not had any issues using it within the actual .saver, however we are only using it to read HTTP data via a localhost server on the same mac. I wouldn't be surprised if the sandbox might prevent other uses?
- Sharing prefs : Theoretically yes, the unsandboxed version of Aerial could definitely read/write the sandboxed file. It's unclear to me however how to approach this, since we use in screen savers the the
ScreenSaverDefaultwrapper thing, I may need to override that in some way, though I'm not sure yet how. I hope to investigate this soon.
I believe that ScreenSaverDefault stores data in ~/Library/Preferences/ByHost/com.example.[GUID String].plist (or the equivalent sandboxed location). I've never liked or understood the ByHost folder - I think it's something that was supposed to enable one person to be logged into the same account using multiple machines? (Wasn't that an idea of the NextStep era?) but I've always just found it confusing and clumsy. My screensaver just saves preferences using
CFPreferencesSetValue(key as CFString, val as CFString, self.bundleID as CFString, kCFPreferencesCurrentUser, kCFPreferencesAnyHost)
[...]
CFPreferencesAppSynchronize(self.bundleID as CFString)
which puts data in
~/Library/Preferences/[bundleID].plist
which I find a lot easier to use.
@xmddmx
First sorry for the late reply, missed your message the other day and had a busy week.
Have not had any issues using it within the actual .saver, however we are only using it to read HTTP data via a localhost server on the same mac. I wouldn't be surprised if the sandbox might prevent other uses?
This may be exactly that, I think one issue I had was regarding https being mandatory.
I believe that ScreenSaverDefault stores data in ~/Library/Preferences/ByHost/com.example.[GUID String].plist (or the equivalent sandboxed location). I've never liked or understood the ByHost folder - I think it's something that was supposed to enable one person to be logged into the same account using multiple machines? (Wasn't that an idea of the NextStep era?)
If I remember correctly, the ByHost folder is like a "host temporary" preferences, as in those only apply to this machine and could be recreated easily (things that must be tied to the machine like they have some unique MAC address or something). Which is not really great for a screensaver, but that's where it puts them sadly. Those settings are not always migrated in some scenarios (like machine upgrade), and if I remember correctly settings were lost when legacyScreenSaver.appex was introduced, and also in some other macOS transition (can't remember which). Just a pain.
but I've always just found it confusing and clumsy. My screensaver just saves preferences using
CFPreferencesSetValue(key as CFString, val as CFString, self.bundleID as CFString, kCFPreferencesCurrentUser, kCFPreferencesAnyHost) [...] CFPreferencesAppSynchronize(self.bundleID as CFString)which puts data in
~/Library/Preferences/[bundleID].plistwhich I find a lot easier to use.
That's... super super useful, thanks a whole lot. I'll have a look at implementing this as this would solve my biggest issue right now with the desktop/full screen mode of Companion. I had an idea it was probably possible but I was completely unfamiliar with those APIs ! Thanks again 👍
Now, regarding your original issue, I had a look with my ScreenSaverMinimal project and tried a few "other" things to dismiss the panel but none worked.
I also tried two other screensavers to see if I could reproduce just to be sure :
- Fliqlo, made with Swift
- ElectricSheep, Obj-C (or C, but definitely not swift !)
Both have the exact same issue, so if I had to guess, there's a callback from legacyScreenSaver.appex that seems to crash the new Screen Saver.appex. I'll file a radar with more details just in case.
Couple of other bugs I saw :
- Sometimes the settings panel preview keeps restarting the preview for no reason that I can see, throwing it in an unresponsive loop. This may be more of an Aerial specific issue than a general screen saver issue though (could be related to video playback/HDR)
- Double clicking a .saver file to install it no longer works
- The isPreview bug (a flag that tells you if you are in the tiny preview or fullscreen) is finally fixed ! It's been here since Catalina where it always returned false. Now it correctly returns true in that mode (and false in fullscreen) ! Progress ! (that bug was nagging me a lot)
- There's been no changes in entitlements to legacyScreenSaver.appex (so still can't use keyboard binding in the screensaver)
I put a list so I can track stuff in the wiki here : https://github.com/glouel/ScreenSaverMinimal/wiki/Issues-with-macOS-Ventura-betas
Hopefully things will get better in a couple of betas, I'll keep you posted if I find other stuff.
@xmddmx You probably noticed this but the original issue (crashing after closing) has been fixed in Ventura beta3.
Also, double clicking a .saver to install works again.
Last thing, I tried your methods to write and read preferences but I'm running into an issue when running in sandbox, the CFPreferences api will read and write inside the container. I'm wondering if you have a trick to read the specific unsandboxed file. Thanks !
Edit : I think it's stupid and I'm probably getting tired about this but I'm this close to calling defaults read/write in terminal and call it a day 😅
I agree, those 2 issues (crash after close the settings panel, double-click .saver file) seem fixed in Beta 3.
Regarding CFPreferences : I think I'm doing the opposite of what you are. In my setup, the screensaver itself reads/writes settings from within the sandbox container. I have an external app, which is not sandboxed, which can also read/write those same settings in the container.
It sounds like you are trying to go the opposite direction, where the .saver is trying to break out of the sandbox?
Thanks for taking the time to answer @xmddmx.
This is where I'm not getting it. If I call CFPreferences from outside the sandbox, I will read and write the one outside the sandbox. I don't see how to specify the sandbox path ?
CFPreferencesAppSynchronize("com.glouel.synctest" as CFString) <- should I pass the path here ???
Edit: I tried calling this in the unsandboxed app :
CFPreferencesAppSynchronize("~/Library/Containers/com.apple.ScreenSaver.Engine.legacyScreenSaver/Data/Library/Preferences/com.glouel.synctest" as CFString)
frustratingly, it still writes in ~/Library/Preferences
Perhaps using a relative path with "~" doesn't work? I had to go back and untangle my code, but here's what I'm doing to read from inside the Sandbox from an app Outside the sandbox:
bundleID = "/Users/username/Library/Containers/com.apple.ScreenSaver.Engine.legacyScreenSaver/Data/Library/Preferences/com.mycompany.myscreensaver"
key = "foobar"
value = CFPreferencesCopyValue(key, bundleID, kCFPreferencesCurrentUser, kCFPreferencesAnyHost)
This works for me. Writing a value is similar, just uses the CFPreferencesSetValue() method.
Where I learned that the "applicationID" can include the path too? Don't remember!
Also, if your App and .Saver are Intel, you would need to replace legacyScreenSaver with legacyScreenSaver.x86-64 when running on a M1 system.
Absolute path it is...
With an absolute path, it works, and I can succesfully read write from outside the container to it 🎉
Seriously I can't thank you enough for helping me on this, I've been stuck on this for about 6 months on and off, and this should finally solve it.
Thanks for your patience !
Edit: I do dual compile and my understanding is that it always hit the non .x86-64, even on Intel in that situation, and the x86-64 is a non factor. But I may have this wrong ! (I'm running a M1 based with dual compiled, but 99% sure this was the same with my previous x86 iMac dual compiled). To be clear, .x86-64 only matters if your app kicks in rosetta is my understanding.
FYI, I did further testing and the absolute path actually also work with the "modern" UserDefault API :
// Test 2
let bundleID2 = "/Users/guillaume/Library/Containers/com.apple.ScreenSaver.Engine.legacyScreenSaver/Data/Library/Preferences/com.glouel.synctest2"
let userDefaults = UserDefaults(suiteName: bundleID2)
userDefaults?.setValue(time, forKey: "lastRun")
userDefaults?.synchronize()
This actually works ! I'm working on cleaning it all up as I have a mix of stuff currently in preferences but this is looking very very promising. Again many thanks for sharing all that info !
Glad to be of help!
I'd be interested in pursuing some other quality-of-life issues with screensavers on Ventura. I've pretty much given up on the ability to capture Keyboard events on Catalina or higher, but I did have a pretty robust way of capturing mouse events, which allows the screensaver to provide on-screen controls / Heads-Up-Display (HUD). This seems broken in Ventura.
Are you interested/willing to collaborate? I don't have any github repos, so would you mind if I posted a new Issue here? Or would it perhaps be better posted on ScreenSaverMinimal ? https://github.com/AerialScreensaver/ScreenSaverMinimal/issues
Sure thing, if we can find ways to improve things, I'm all for it.
Either repo is fine to me, this one may have a tiny bit more visibility although I'm not sure it matters much. I'll pin the issue in any case.
(Code wise, it's definitely easier to test stuff on the other repo though !)
Edit : Aside on the preferences, onterestingly, you can't do anything in the ByHost directory. Even if you try to create a file in a ByHost directory, it's created one level down. So I'll take the opportunity to migrate everything from ByHost to regular prefs ;)