JohnCoates / Aerial

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

MacOS Sonoma Screen Saver Configuration plist #1332

Open xmddmx opened 9 months ago

xmddmx commented 9 months ago

This is a followup to: https://github.com/JohnCoates/Aerial/issues/1305#issuecomment-1677202879

Sonoma changes the location and format of the configuration files / plists for choosing which screen saver(s) are active on which screens.

Initial notes:

Here's a conmmand to fully decode it :

cd ~/Library/Application\ Support/com.apple.wallpaper/Store ; plutil -extract "AllSpacesAndDisplays.Idle.Content.Choices.0.Configuration" raw Index.plist | base64 --decode > temp.plist ; plutil -p temp.plist

Which gives results like this:

{
  "module" => {
    "relative" => "file:///Users/username/Library/Screen%20Savers/MyScreensaverDemo.saver"
  }
}
glouel commented 9 months ago

Hey @xmddmx !

Someone I know tried to mess with it with limited results. The problem is, even if you overwrite the Index.plist, there's something that rebuilds it at startup from "somewhere" (maybe one more sqllite database).

Feel free to poke around but from what I understand, it's idleassetsd that is responsible for overwriting this when it gets restarted.

For now I went the boring way, I use the "old" plist to detect if we are set as default (because they still update it for some reason), and if not I put a screen with instructions. It's really messy though!

One more thing, I tried looking up private frameworks and had limited success.

/System/Library/PrivateFrameworks/Wallpaper.framework/Versions/A/Wallpaper is the one I believe that contains all we need.

I managed to extract those with resymbol after extracting from the dylibs (what a mess). I've attached the file below

Wallpaper.txt

I found a few interesting references:

struct WallpaperFirstRunChooser {
    let _defaultWallpaperImage: So0C
    let _priorWallpaperImage: So0C
    let _initiallySelectedChoice: Choice
}

Which I believe is linked to the Sonoma first run thing that kicks us out if the user picks a new wallpaper, and

enum AgentXPCMessage {
...
    case updateDesktopWallpaperUserSettings: WallpaperUserSettings
    case updateScreenSaverWallpaperUserSettings: WallpaperUserSettings

I'm sure someone more knowledgeable may be able to make something out of these. If I'm guessing correctly, there's a way to make a XPC call to set savers, maybe?

xmddmx commented 8 months ago

I think I found a solution - WallpaperAgent is the key

Does not work:

  1. set the screensaver you want, and make a saved copy of the Index.plist
  2. set a different screensaver (such as 'Message')
  3. copy your saved plist on top of Index.plist
  4. Result: your screensaver is not selected.

Works:

  1. set the screensaver you want, and make a saved copy of the Index.plist
  2. set a different screensaver (such as 'Message')
  3. copy your saved plist on top of Index.plist
  4. kill the WallpaperAgent process
  5. Result: your screensaver is selected!

Code:


# first, go to System Preferences / Screen Saver and set a screensaver you want, then close the window
# make a backup copy of this plist:
cd ~/Library/Application\ Support/com.apple.wallpaper/Store
cp Index.plist MyPlist.plist
# go back to System Preferences / Screen Saver and set a different screensaver (such as 'Messages') then close the window
# now, replace the plist with your plist which should select your screensaver
cp MyPlist.plist Index.plist
# kill the Wallpaper agent, which seems to force re-loading of the plist datat
killall WallpaperAgent
# launch the screensaver 
# result: works!  your screensaver is selected.
glouel commented 8 months ago

That sounds great, but I have to ask, does it survive a reboot ? I think someone tried something fairly close and it didn't, at reboot (or maybe when wallpaperagent is launched again) it got overwritten ?

zeromhz commented 8 months ago

I tried exactly this and it doesn't survive a reboot unless you lock the plist file. The system will write out the values it has in memory over the values in that file. Locking the plist file caused some other issues, like the wallpaper resetting on reboot.

zeromhz commented 8 months ago

Actually let me correct myself there, I didn't try actually killing the Wallpaper Agent. It's possible that by doing that, you've found a way to avoid it writing out the value from memory.

xmddmx commented 8 months ago

Just tried it, and it survives a reboot:

# close System Settings
# edit ~/Library/Application\ Support/com.apple.wallpaper/Store/Index.plist
killall WallpaperAgent
# launched the screensaver using hot corner to test
# rebooted
# launched the screensaver using hot corner to test
zeromhz commented 8 months ago

Yeah, I can confirm, that this works. The drawback is that the wallpaper disappears for a second while it's happening. But if the app can kill WallpaperAgent without other consequences this may be acceptable.

glouel commented 8 months ago

Oh wow, this is seriously awesome 😭

I'll have a look at making this work tomorrow in Companion, but from here it should be straightforward ! I did have some bits of Swift code to decode the whole plist thing somewhere (it's a mess of nested plists) if you want, it might be somewhere in a repository, let me know !

Again, many thanks this is a great find!

xmddmx commented 8 months ago

I tried other kill signals:

killall -HUP WallpaperAgent
killall -INT WallpaperAgent

but all of them also cause the wallpaper to flash briefly, so no improvement.

zeromhz commented 8 months ago

i'll be surprised if the wallpaper flicker can be avoided other than with a cosmetic hack. it is reloading the wallpaper. this trick also works for changing the wallpaper.

glouel commented 8 months ago

Yeah I won't worry about this, it's absolutely great as is! I'll keep you posted on implementation tomorrow if no one else beats me to it.

Again, many thanks for sharing this.

glouel commented 8 months ago

@xmddmx This is my code for playing around with that plist (it was v2 at the time, needs changed to v1)

What we need on top of that is detecting user path (I 99,9999% think we need absolute paths), base64 encoding properly the saver path, and do it for all spaces/screens.

        let defaultFile = "/Users/guillaume/Library/Application Support/com.apple.wallpaper/Store/Index_v2.plist"

        let plistURL = URL(fileURLWithPath: defaultFile)
        let data = try? Data(contentsOf: plistURL)

        guard let plistDictionary = try? PropertyListSerialization.propertyList(from: data!, options: [], format: nil) as? [String: AnyObject] else {
            return
        }

        let allDisplays = plistDictionary["AllSpacesAndDisplays"] as! NSDictionary
        let idle = allDisplays["Idle"] as! NSDictionary
        let content = idle["Content"] as! NSDictionary
        let choices = content["Choices"] as! NSArray
        let choicesDict = choices[0] as! NSDictionary

        let encodedData = choicesDict["Configuration"] // This is our data
        //print(encodedData.debugDescription)

        guard let configurationPlist = try? PropertyListSerialization.propertyList(from: encodedData as! Data, options: [], format: nil) as? [String: AnyObject] else {
            return
        }

        print(configurationPlist)
zeromhz commented 8 months ago

important side note, it appears the killall WallpaperAgent approach does NOT update the classic plist setting, so it will need to be set this way AND set the classic way.