Pick the backdrop by path, seeded with the wallpaper - #4
Merged
Merged
Conversation
The Desktop picture backdrop shows the wrong picture when the wallpaper is a dynamic or video one. NSWorkspace.desktopImageURL(for:) predates WallpaperKit and only reports still pictures: with the com.apple.NeptuneOneExtension provider set, the wallpaper store holds no file at all and the API answers with a placeholder, /System/Library/CoreServices/DefaultDesktop.heic, and an empty options dictionary. The saver was drawing that placeholder faithfully. No public API reports those wallpapers, so there is nothing to fix on the reading side. The Desktop picture checkbox therefore becomes a Backdrop menu — Solid grey, Desktop picture, Custom picture… — and the last of those opens a file panel and keeps what you chose. Choosing that item always opens the panel, so one menu entry also serves to swap one picture for another; cancelling with nothing chosen puts the menu back. The picture is stored as a bookmark rather than a path, so renaming or moving it does not break the backdrop. Security-scoped where the host can make one — the preview app, being unsandboxed, cannot — and a plain bookmark otherwise; the read succeeds either way, since legacyScreenSaver.appex can read anywhere under `/`. The grey slider is now labelled Grey level, which is what it does, and no longer sits one row under Backdrop meaning something else.
The three-way Backdrop menu becomes a checkbox and a path field. There is one source for the backdrop now — a file — and the desktop's wallpaper is only where its value starts. The path is filled in once, on first run, from NSWorkspace.desktopImageURL(for:), and is an ordinary saved setting from then on: changing the wallpaper later leaves the saver alone. That removes the awkward middle case the menu had, where "Desktop picture" silently followed an API that cannot see dynamic or video wallpapers and answered with a placeholder. The placeholder can still turn up as the seeded value, but now it is sitting in a field, visible and correctable, rather than deciding the backdrop from behind the scenes. Storing a path rather than a bookmark drops the security-scoped bookmark machinery. A typed path is not a user-selected file in the sandbox's sense, but legacyScreenSaver.appex can read anywhere under `/`, so the read succeeds either way and there is nothing left for a bookmark to buy. The desktop's layout options still apply when the path happens to name the very picture the desktop is showing, which is the usual case; anything else is laid out as Fill Screen.
winebarrel
enabled auto-merge
August 10, 2026 03:00
winebarrel
added a commit
that referenced
this pull request
Aug 10, 2026
A picture chosen from Documents never appeared. The saver stored its path and
reopened it by path, and the kernel refused:
Sandbox: legacyScreenSaver deny(1) file-read-data
/Users/…/Documents/Wallpaper/…png
#4 dropped the security-scoped bookmark on the grounds that legacyScreenSaver.appex
holds a read-only exception for `/`, so a path could reach anything. It cannot.
Documents, Desktop and Downloads sit behind a consent macOS will never ask a
screen saver for, and the sandbox exception says nothing about them. The read is
refused outright, the image comes back nil, and the backdrop falls to black with
nothing said — which is how a setting that saved correctly still did nothing.
So the bookmark comes back. Choosing a file is what grants access to it; the
bookmark is how that grant survives into the run that draws it. A bare path still
serves everywhere else — a picture seeded from the wallpaper, or one typed into
the field — and stands in when there is no bookmark or it no longer resolves.
Typing a path clears the bookmark, which described a different file.
The file is read into Data inside the security scope rather than handed to
NSImage as a URL: NSImage may defer the read, and by then the scope is shut.
Left as it is: a wallpaper seeded into the field is not something the user picked,
so a wallpaper living in Documents still cannot be read.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The
Desktop picturebackdrop showed the wrong picture whenever the wallpaper wasa dynamic or video one. This replaces it with a checkbox and a path field.
Why the old one could not work
NSWorkspace.desktopImageURL(for:)predates WallpaperKit and only reports stillpictures. With the
com.apple.NeptuneOneExtensionprovider set — Sonoma's dynamicand video wallpapers — the wallpaper store holds no file at all:
and the API answers with a placeholder,
/System/Library/CoreServices/DefaultDesktop.heic, plus an empty optionsdictionary. The saver was drawing that placeholder faithfully. No public API
reports those wallpapers, so there is nothing to fix on the reading side.
What it does instead
There is one source for the backdrop now — a file — and the desktop's wallpaper is
only where its value starts. The path is filled in once, on first run, and is an
ordinary saved setting from then on: changing your wallpaper later leaves the saver
alone.
That removes the awkward middle case the old control had, where
Desktop picturesilently followed an API that could not see the wallpaper. The placeholder can
still turn up as the seeded value, but now it is sitting in a field, visible and
correctable, rather than deciding the backdrop from behind the scenes.
Unchecking the box, or emptying the field, leaves the grey.
Bookmarks dropped
Storing a path rather than a bookmark drops the security-scoped bookmark
machinery added in the previous change. A typed path is not a user-selected file
in the sandbox's sense, but
legacyScreenSaver.appexcan read anywhere under/,so the read succeeds either way and there is nothing left for a bookmark to buy.
Layout
The desktop's own scaling, clipping and fill colour still apply when the path
happens to name the very picture the desktop is showing, which is the usual case
given where the value comes from. Anything else is laid out as Fill Screen.
Verified
swiftlint --strict,swiftlint analyze --strictand
swiftformat --lintare cleanDefaultDesktop.heicplaceholder, which is the case this change exists for--picture, and for--greystays readable
The
Choose…panel needs a GUI to exercise, so it is unverified, as is the wholesaver in System Settings on a real machine.