ohmu
·
2026-10-15
1#include "wallpaper.h"
2#include "common/settings.h"
3
4#import <Foundation/Foundation.h>
5#import <AppKit/AppKit.h>
6
7const char* detectFromPlist(FFstrbuf* result) {
8 // For Sonoma and later (macOS 14.0+)
9 // https://github.com/JohnCoates/Aerial/issues/1332
10 // Initialised because the check below reads it: the method is documented to assign it, but an
11 // uninitialised read decides between two sources of the wallpaper, so it is not worth relying on.
12 NSError* error = nil;
13 NSString* fileName = [NSString stringWithFormat:@"file://%s/Library/Application Support/com.apple.wallpaper/Store/Index.plist", instance.state.platform.homeDir.chars];
14 NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName]
15 error:&error];
16
17 if (error) {
18 return "Failed to read wallpaper plist file";
19 }
20
21 NSArray* choices = [dict valueForKeyPath:@"SystemDefault.Desktop.Content.Choices"];
22 if (choices.count > 0) {
23 NSDictionary* choice = choices[0];
24
25 NSArray* files = choice[@"Files"];
26 if ([files isKindOfClass:NSArray.class] && files.count > 0) {
27 NSDictionary* fileEntry = files[0];
28 // `relative` is read through a dictionary, which raises an unrecognized selector rather
29 // than returning nil when the entry is not one.
30 if ([fileEntry isKindOfClass:NSDictionary.class]) {
31 NSString* relative = fileEntry[@"relative"];
32 if ([relative isKindOfClass:NSString.class]) {
33 ffStrbufSetS(result, [NSURL URLWithString:relative].path.UTF8String);
34 }
35 }
36 }
37
38 if (result->length == 0) {
39 NSData* configData = choice[@"Configuration"];
40 if (configData && configData.length > 0) {
41 NSError* plistError = nil;
42 NSDictionary* configPlist = [NSPropertyListSerialization propertyListWithData:configData
43 options:NSPropertyListImmutable
44 format:NULL
45 error:&plistError];
46
47 if (!plistError && [configPlist isKindOfClass:NSDictionary.class]) {
48 NSDictionary* urlDict = configPlist[@"url"];
49 if ([urlDict isKindOfClass:NSDictionary.class]) {
50 NSString* relativeUrlString = urlDict[@"relative"];
51 if ([relativeUrlString isKindOfClass:[NSString class]]) {
52 NSURL* fileUrl = [NSURL URLWithString:relativeUrlString];
53 if (fileUrl.fileURL) {
54 ffStrbufSetS(result, fileUrl.path.UTF8String);
55 }
56 }
57 }
58 }
59 }
60 }
61
62 if (result->length == 0) {
63 NSString* provider = choice[@"Provider"];
64 if ([provider isKindOfClass:NSString.class]) {
65 NSString* builtinPrefix = @"com.apple.wallpaper.choice.";
66 if ([provider hasPrefix:builtinPrefix]) {
67 provider = [provider substringFromIndex:builtinPrefix.length];
68 }
69
70 // macOS internal wallpapers
71 if ([provider isEqualToString:@"aerials"]) { // Most builtin aerial wallpapers are private
72 ffStrbufSetStatic(result, "Built-in aerial photography");
73 } else if ([provider isEqualToString:@"default"]) {
74 ffStrbufSetStatic(result, "macOS Default Wallpaper");
75 } else {
76 ffStrbufSetF(result, "Built-in %s wallpaper", provider.UTF8String);
77 }
78 }
79 }
80 }
81 if (result->length == 0) {
82 return "Failed to detect wallpaper from plist";
83 }
84 return nullptr;
85}
86
87#ifdef FF_HAVE_SQLITE3
88const char* detectFromSQLite(FFstrbuf* result) {
89 // For Ventura
90 // https://stackoverflow.com/questions/301215/getting-desktop-background-on-mac
91 FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateCopy(&instance.state.platform.homeDir);
92 ffStrbufAppendS(&path, "Library/Application Support/Dock/desktoppicture.db");
93 if (ffSettingsGetSQLite3String(path.chars,
94 "SELECT value\n"
95 "FROM preferences\n"
96 "JOIN data ON preferences.data_id=data.ROWID\n"
97 "JOIN pictures ON preferences.picture_id=pictures.ROWID\n"
98 "JOIN displays ON pictures.display_id=displays.ROWID\n"
99 "JOIN spaces ON pictures.space_id=spaces.ROWID\n"
100 "WHERE display_id=1 AND space_id=1 AND key=1",
101 result)) {
102 return nullptr;
103 }
104 return "Failed to detect wallpaper from SQLite database";
105}
106#endif
107
108const char* detectFromNSWorkspace(FFstrbuf* result) {
109 // Reliable for user-picked static images.
110 NSScreen* mainScreen = NSScreen.mainScreen;
111 if (!mainScreen) {
112 return "Failed to detect wallpaper from NSWorkspace: No main screen found";
113 }
114
115 NSURL* url = [NSWorkspace.sharedWorkspace desktopImageURLForScreen:mainScreen];
116 if (url.fileURL) {
117 ffStrbufSetS(result, url.path.UTF8String);
118 return nullptr;
119 }
120
121 return "Failed to detect wallpaper from NSWorkspace";
122}
123
124const char* ffDetectWallpaper(FFstrbuf* result) {
125 const char* error;
126
127 if (@available(macOS 14.0, *)) {
128 error = detectFromPlist(result);
129 if (error) {
130 // The plist is the authoritative source on Sonoma and later, but when it can not be read
131 // at all, NSWorkspace can still resolve a user-picked static image.
132 error = detectFromNSWorkspace(result);
133 }
134 } else {
135#ifdef FF_HAVE_SQLITE3
136 error = detectFromSQLite(result);
137 if (error) {
138 // The database only holds the user-picked picture, so it goes missing on a machine that
139 // never changed its wallpaper. NSWorkspace answers for the system default there.
140 error = detectFromNSWorkspace(result);
141 }
142#else
143 error = detectFromNSWorkspace(result);
144#endif
145 }
146 return error;
147}