Skip to content

Draw the desktop picture as the backdrop - #3

Merged
winebarrel merged 1 commit into
mainfrom
wallpaper-background
Aug 10, 2026
Merged

winebarrel merged 1 commit into
mainfrom
wallpaper-background

Conversation

@winebarrel

Copy link
Copy Markdown
Owner

The saver painted a flat grey and nothing else, which is not what "the usual
screen, with a cat on it" looks like. It now draws the display's own wallpaper,
on by default, with the grey slider as the fallback.

The sandbox turned out not to be in the way

.saver bundles are loaded into legacyScreenSaver.appex, so the worry was that
reading a picture would need an NSOpenPanel and a security-scoped bookmark.
Its entitlements say otherwise:

com.apple.security.assets.pictures.read-only                          true
com.apple.security.temporary-exception.files.absolute-path.read-only  "/"
com.apple.security.files.bookmarks.app-scope                          true
com.apple.security.files.user-selected.read-only                      true

A read-only exception for / covers a picture anywhere on disk — which matters,
since a wallpaper is quite often somewhere other than ~/Pictures. No bookmark
plumbing needed.

Wallpaper still returns nil on anything it cannot read (a video wallpaper has
no still frame to load) and the engine falls back to the grey, so the setting can
be left on regardless.

Laid out the way the desktop lays it out

NSWorkspace.desktopImageOptions(for:) reports the scaling mode, the clipping
flag and the fill colour, so Fill Screen, Fit to Screen, Stretch and Centre are
all honoured. The backdrop matches what was on screen a moment earlier rather
than approximating it.

Where it is read

The wallpaper is per display and only the view knows which display it landed on,
so the view supplies it and the engine stays free of NSScreen. It is read in
startAnimation, by which point the view is in a window, and only when the
setting asks for it — decoding a picture costs megabytes that a grey backdrop has
no use for.

Litter ink

It used to flip on the background slider alone, which says nothing once a picture
is in the way. The engine now renders the backdrop into a 16px-wide thumbnail and
averages its luma, so a letterboxed picture's fill colour counts for as much of
the answer as it does of the screen. Marks stay visible over a black screen and a
bright wallpaper alike.

Options

One new checkbox, Desktop picture, on by default. Background now reads as the
fallback grey.

Verified

  • Both schemes build and analyse; swiftlint --strict, swiftlint analyze --strict
    and swiftformat --lint are clean
  • Offscreen renders at the real display's aspect ratio (3440x1440), wallpaper on
    and off — the picture lands exactly as Fill Screen puts it on the desktop, and
    the ink flips to dark over a light wallpaper
  • The Options sheet layout with the new row

Still not verified on a real machine in System Settings.

Not this

This is the wallpaper picture, not the live desktop. A screen saver is a window
covering the screen at CGShieldingWindowLevel(), so nothing below it shows
through — windows, icons and the menu bar are not part of it, and no amount of
transparency changes that.

The saver painted a flat grey and nothing else, which is not what "the usual
screen with a cat on it" looks like. It now draws the display's own wallpaper,
on by default, with the grey slider as the fallback.

Reading the file turns out to need no ceremony: legacyScreenSaver.appex holds
com.apple.security.temporary-exception.files.absolute-path.read-only for `/`
(alongside com.apple.security.assets.pictures.read-only), so a picture anywhere
on disk opens without a security-scoped bookmark. `Wallpaper` still returns nil
on anything it cannot read — a video wallpaper has no still frame — and the
engine falls back to the grey.

NSWorkspace also reports how the desktop lays the picture out, so Fill Screen,
Fit to Screen, Stretch and Centre are all honoured, fill colour included, and
the backdrop matches what was on screen a moment earlier.

The wallpaper is per display and only the view knows which display it landed on,
so the view supplies it and the engine stays free of NSScreen. It is read in
startAnimation, by which point the view is in a window, and only when the
setting asks for it — decoding a picture costs megabytes a grey backdrop has no
use for.

The litter ink used to flip on the background slider alone. It now renders the
backdrop into a 16px thumbnail and averages it, so a letterboxed picture's fill
colour counts for as much of the answer as it does of the screen.
@winebarrel
winebarrel enabled auto-merge August 10, 2026 02:09
@winebarrel
winebarrel merged commit ab719bf into main Aug 10, 2026
3 checks passed
@winebarrel
winebarrel deleted the wallpaper-background branch August 10, 2026 02:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant