JohnCoates / Aerial

Apple TV Aerial Screensaver for Mac
MIT License
20.77k stars 1.05k forks source link

macOS Ventura: Crash after closing the Options/Settings Sheet #1250

Open xmddmx opened 2 years ago

xmddmx commented 2 years ago

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
glouel commented 2 years ago

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 😅

xmddmx commented 2 years ago

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.

glouel commented 2 years ago

For what it's worth, I don't use loadWindow on either Aerial nor my ScreenSaverMinimal sample.

xmddmx commented 2 years ago

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?

glouel commented 2 years ago

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 !

xmddmx commented 2 years ago

Interesting.

Here's what I'm doing:

xmddmx commented 2 years ago

Clearly, the best outcome would be if Apple:

But given their track record, I feel like exploring these workarounds is prudent in case these issues go un-fixed in Ventura...

glouel commented 2 years ago

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:

xmddmx commented 2 years ago
  • 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 ScreenSaverDefault wrapper 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.

glouel commented 2 years ago

@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].plist which 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 :

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 :

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.

glouel commented 2 years ago

@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 😅

xmddmx commented 2 years ago

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?

glouel commented 2 years ago

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

xmddmx commented 2 years ago

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!

xmddmx commented 2 years ago

Also, if your App and .Saver are Intel, you would need to replace legacyScreenSaver with legacyScreenSaver.x86-64 when running on a M1 system.

glouel commented 2 years ago

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.

glouel commented 2 years ago

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 !

xmddmx commented 2 years ago

Glad to be of help!

xmddmx commented 2 years ago

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

glouel commented 2 years ago

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 ;)