Vorschau liest macOS' eigene Hintergrundverwaltung

NSWorkspace.desktopImageURL ist die alte Schnittstelle: sie meldet das letzte
Bild, das als Datei gesetzt wurde — auch dann noch, wenn es längst gelöscht
ist. Genau das war hier der Fall, der Pfad zeigt auf eine Datei, die es nicht
mehr gibt. Seit macOS 14 führt das System die Auswahl in
com.apple.wallpaper/Store/Index.plist, je Bildschirm und je Schreibtisch, und
Anbieter wie Fotos hinterlegen dort ihre Zwischenspeicher.

Die Datei ist doppelt verpackt: die Konfiguration je Bildschirm steckt als
eigene Property-Liste in einem Datenfeld der äußeren. Ohne den zweiten
Durchgang findet man gar nichts.

Gelesen wird nur der Eintrag dieses Bildschirms, nachgeschlagen über die
Display-Kennung. In der Datei stehen auch alte Bildschirme, der
Bildschirmschoner und alles, was einmal gesetzt war — das Neueste von allem zu
nehmen führt zuverlässig zu einem Bild, das mit dem Schreibtisch nichts zu tun
hat. Ausprobiert: dabei kam ein weißes Platzhalterbild aus einem Kurzbefehl
heraus. Fehlt der Eintrag oder die Datei, ist ein Systembild die ehrlichere
Antwort als ein Rateergebnis.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-08-14 00:08:44 +02:00
parent 152d36c904
commit c6f0a5a848
2 changed files with 163 additions and 3 deletions

View File

@@ -139,8 +139,9 @@ private struct TriggerPreview: View {
// Den Pfad auf dem Hauptthread bestimmen `NSScreen` und
// `NSWorkspace` gehören dorthin , das Bild daneben.
let own = Self.ownWallpaperURL()
let display = Self.mainDisplayUUID()
let loaded = await Task.detached(priority: .userInitiated) {
Wallpaper(image: WallpaperLoader.load(own: own))
Wallpaper(image: WallpaperLoader.load(own: own, display: display))
}.value
Self.cache = loaded
wallpaper = loaded.image
@@ -178,6 +179,14 @@ private struct TriggerPreview: View {
return NSWorkspace.shared.desktopImageURL(for: screen)
}
/// Die Kennung des Hauptbildschirms unter ihr steht sein Hintergrundbild
/// in macOS' Verwaltung.
@MainActor
private static func mainDisplayUUID() -> String? {
guard let uuid = CGDisplayCreateUUIDFromDisplayID(CGMainDisplayID()) else { return nil }
return CFUUIDCreateString(nil, uuid.takeRetainedValue()) as String?
}
}
@@ -190,8 +199,82 @@ private struct TriggerPreview: View {
/// Prüfung die App angehalten. Hier draußen gibt es die Bindung nicht.
enum WallpaperLoader {
static func load(own: URL?) -> NSImage? {
own.flatMap(thumbnail(of:)) ?? systemWallpaper()
static func load(own: URL?, display: String?) -> NSImage? {
// Der Reihe nach: was macOS als Pfad herausgibt, dann der Eintrag
// dieses Bildschirms in der Hintergrundverwaltung, dann ein Systembild.
if let own, let image = thumbnail(of: own) { return image }
if let indexed = indexedWallpaperURL(display: display),
let image = thumbnail(of: indexed) { return image }
return systemWallpaper()
}
/// Das Bild, das macOS für **diesen** Bildschirm verzeichnet hat.
///
/// `NSWorkspace.desktopImageURL` ist die alte Schnittstelle und meldet nur
/// das letzte Bild, das als Datei gesetzt wurde auch dann noch, wenn es
/// längst gelöscht ist. Seit macOS 14 führt das System die Auswahl
/// stattdessen in `com.apple.wallpaper/Store/Index.plist`, je Bildschirm und
/// je Schreibtisch.
///
/// Gelesen wird **nur** der Eintrag dieses Bildschirms. In der Datei stehen
/// auch alte Bildschirme, der Bildschirmschoner und alles, was einmal
/// gesetzt war; das Neueste davon zu nehmen führt zuverlässig zu einem
/// Bild, das mit dem Schreibtisch nichts zu tun hat. Fehlt der Eintrag oder
/// die Datei, ist ein Systembild die ehrlichere Antwort als ein Rateergebnis.
static func indexedWallpaperURL(display: String?) -> URL? {
let index = FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent("Library/Application Support/com.apple.wallpaper/Store/Index.plist")
guard let data = try? Data(contentsOf: index),
let root = try? PropertyListSerialization.propertyList(from: data,
options: [], format: nil)
as? [String: Any]
else { return nil }
// Der eigene Bildschirm zuerst, sonst die Vorgabe für alle.
let displays = root["Displays"] as? [String: Any]
let candidates = [display.flatMap { displays?[$0] }, root["SystemDefault"]]
for candidate in candidates.compactMap({ $0 }) {
guard let entry = candidate as? [String: Any],
let desktop = entry["Desktop"] else { continue }
var found: [URL] = []
collectImageFiles(desktop, into: &found)
if let url = found.first(where: { FileManager.default.fileExists(atPath: $0.path) }) {
return url
}
}
return nil
}
/// Sammelt alle Bilddateien aus der verschachtelten Liste.
///
/// Verschachtelt heißt hier wörtlich: die Konfiguration je Bildschirm steckt
/// als **eigene** Property-Liste in einem Datenfeld der äußeren. Ohne den
/// zweiten Durchgang findet man gar nichts.
static func collectImageFiles(_ node: Any, into found: inout [URL]) {
switch node {
case let dictionary as [String: Any]:
if dictionary["type"] as? String == "imageFile",
let url = dictionary["url"] as? [String: Any],
let text = url["relative"] as? String,
let parsed = URL(string: text), parsed.isFileURL {
found.append(parsed)
}
for value in dictionary.values { collectImageFiles(value, into: &found) }
case let array as [Any]:
for value in array { collectImageFiles(value, into: &found) }
case let data as Data:
guard data.starts(with: Array("bplist00".utf8)),
let inner = try? PropertyListSerialization.propertyList(from: data,
options: [], format: nil)
else { return }
collectImageFiles(inner, into: &found)
default:
break
}
}
/// Ein Standardhintergrund von macOS.