-
-
Notifications
You must be signed in to change notification settings - Fork 36
/
Wallpaper.swift
148 lines (124 loc) · 4.1 KB
/
Wallpaper.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import AppKit
import SQLite
// https://github.com/stephencelis/SQLite.swift/issues/1277
typealias Expression = SQLite.Expression
public enum Wallpaper {
public enum Screen {
case all
case main
case index(Int)
case nsScreens([NSScreen])
fileprivate var nsScreens: [NSScreen] {
switch self {
case .all:
return NSScreen.screens
case .main:
guard let mainScreen = NSScreen.main else {
return []
}
return [mainScreen]
case .index(let index):
guard let screen = NSScreen.screens[safe: index] else {
return []
}
return [screen]
case .nsScreens(let nsScreens):
return nsScreens
}
}
}
public enum Scale: String, CaseIterable {
case auto
case fill
case fit
case stretch
case center
}
/**
Works around macOS bug where it sometimes returns a directory instead of an image.
https://openradar.appspot.com/radar?id=4959084113559552
*/
private static func getFromDirectory(_ url: URL) throws -> URL {
let appSupportDirectory = try FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let dbURL = appSupportDirectory.appendingPathComponent("Dock/desktoppicture.db", isDirectory: false)
let table = Table("data")
let column = Expression<String>("value")
let rowID = Expression<Int64>("rowid")
let db = try Connection(dbURL.path)
let maxID = try db.scalar(table.select(rowID.max))!
let query = table.select(column).filter(rowID == maxID)
let image = try db.pluck(query)!.get(column)
return url.appendingPathComponent(image, isDirectory: false)
}
/**
Get the current wallpapers.
*/
public static func get(screen: Screen = .all) throws -> [URL] {
let wallpaperURLs = screen.nsScreens.compactMap { NSWorkspace.shared.desktopImageURL(for: $0) }
return try wallpaperURLs.map { $0.isDirectory ? try getFromDirectory($0) : $0 }
}
/**
Works around a macOS bug where if you set a wallpaper to the same path as the existing wallpaper but with different content, it doesn't update.
https://openradar.appspot.com/radar?id=6095446787227648
*/
private static func forceRefreshIfNeeded(_ image: URL, screen: Screen) throws {
var shouldSleep = false
let currentImages = try get(screen: screen)
for (index, nsScreen) in screen.nsScreens.enumerated() {
if image == currentImages[index] {
shouldSleep = true
try NSWorkspace.shared.setDesktopImageURL(URL(fileURLWithPath: ""), for: nsScreen, options: [:])
}
}
if shouldSleep {
// We need to sleep for a little bit, otherwise it doesn't take effect.
// It works with 0.3, but not with 0.2, so we're using 0.4 just to be sure.
sleep(for: 0.4)
}
}
/**
Set an image URL as wallpaper.
*/
public static func set(
_ image: URL,
screen: Screen = .all,
scale: Scale = .auto,
fillColor: NSColor? = nil
) throws {
var options = [NSWorkspace.DesktopImageOptionKey: Any]()
switch scale {
case .auto:
break
case .fill:
options[.imageScaling] = NSImageScaling.scaleProportionallyUpOrDown.rawValue
options[.allowClipping] = true
case .fit:
options[.imageScaling] = NSImageScaling.scaleProportionallyUpOrDown.rawValue
options[.allowClipping] = false
case .stretch:
options[.imageScaling] = NSImageScaling.scaleAxesIndependently.rawValue
options[.allowClipping] = true
case .center:
options[.imageScaling] = NSImageScaling.scaleNone.rawValue
options[.allowClipping] = false
}
options[.fillColor] = fillColor
try forceRefreshIfNeeded(image, screen: screen)
for nsScreen in screen.nsScreens {
try NSWorkspace.shared.setDesktopImageURL(image, for: nsScreen, options: options)
}
}
/**
Set a solid color as wallpaper.
*/
public static func set(_ solidColor: NSColor, screen: Screen = .all) throws {
let transparentImage = URL(fileURLWithPath: "/System/Library/PreferencePanes/DesktopScreenEffectsPref.prefPane/Contents/Resources/DesktopPictures.prefPane/Contents/Resources/Transparent.tiff")
try set(transparentImage, screen: screen, scale: .fit, fillColor: solidColor)
}
/**
Names of available screens.
*/
public static var screenNames: [String] {
NSScreen.screens.map(\.name)
}
}