-
-
Notifications
You must be signed in to change notification settings - Fork 330
/
SystemPermissions.swift
118 lines (108 loc) · 5 KB
/
SystemPermissions.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
import Cocoa
// macOS has some privacy restrictions. The user needs to grant certain permissions, app by app, in System Preferences > Security & Privacy
class SystemPermissions {
static var preStartupPermissionsPassed = false
static var flakyCounter = 0
static var timerPermissionsToUpdatePermissionsWindow: Timer?
static var timerPermissionsRemovedWhileAltTabIsRunning: Timer?
static func ensurePermissionsAreGranted(_ continueAppStartup: @escaping () -> Void) {
let startupBlock = {
pollPermissionsRemovedWhileAltTabIsRunning()
continueAppStartup()
}
if accessibilityIsGranted() != .notGranted && screenRecordingIsGranted() != .notGranted {
preStartupPermissionsPassed = true
startupBlock()
} else {
App.app.permissionsWindow.show(startupBlock)
}
}
static func pollPermissionsToUpdatePermissionsWindow(_ startupBlock: @escaping () -> Void) {
timerPermissionsToUpdatePermissionsWindow = Timer(timeInterval: 0.1, repeats: true) { _ in
DispatchQueue.main.async {
checkPermissionsToUpdatePermissionsWindow(startupBlock)
}
}
timerPermissionsToUpdatePermissionsWindow!.tolerance = 0.1
CFRunLoopAddTimer(BackgroundWork.systemPermissionsThread.runLoop, timerPermissionsToUpdatePermissionsWindow!, .defaultMode)
}
static func pollPermissionsRemovedWhileAltTabIsRunning() {
timerPermissionsRemovedWhileAltTabIsRunning = Timer(timeInterval: 5, repeats: true) { _ in
DispatchQueue.main.async {
checkPermissionsWhileAltTabIsRunning()
}
}
timerPermissionsRemovedWhileAltTabIsRunning!.tolerance = 1
CFRunLoopAddTimer(BackgroundWork.systemPermissionsThread.runLoop, timerPermissionsRemovedWhileAltTabIsRunning!, .defaultMode)
}
static func accessibilityIsGranted() -> PermissionStatus {
if #available(macOS 10.9, *) {
return AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeRetainedValue(): false] as CFDictionary) ? .granted : .notGranted
}
return .granted
}
static func screenRecordingIsGranted() -> PermissionStatus {
if #available(macOS 10.15, *) {
return screenRecordingIsGranted_() ? .granted :
(Preferences.screenRecordingPermissionSkipped ? .skipped : .notGranted)
}
return .granted
}
private static func checkPermissionsWhileAltTabIsRunning() {
let accessibility = accessibilityIsGranted()
let screenRecording = screenRecordingIsGranted()
logger.d(accessibility, screenRecording, preStartupPermissionsPassed)
Menubar.togglePermissionCallout(screenRecording == .skipped)
if accessibility == .notGranted {
App.app.restart()
}
if screenRecording == .notGranted {
// permission check may yield a false negative during wake-up
// we restart after 2 negative checks
if flakyCounter >= 2 {
App.app.restart()
} else {
flakyCounter += 1
}
} else {
flakyCounter = 0
}
}
private static func checkPermissionsToUpdatePermissionsWindow(_ startupBlock: @escaping () -> Void) {
let accessibility = accessibilityIsGranted()
let screenRecording = screenRecordingIsGranted()
logger.d(accessibility, screenRecording, preStartupPermissionsPassed)
Menubar.togglePermissionCallout(screenRecording == .skipped)
if accessibility != App.app.permissionsWindow?.accessibilityView?.permissionStatus {
App.app.permissionsWindow?.accessibilityView.updatePermissionStatus(accessibility)
}
if #available(macOS 10.15, *), screenRecording != App.app.permissionsWindow?.screenRecordingView?.permissionStatus {
App.app.permissionsWindow?.screenRecordingView?.updatePermissionStatus(screenRecording)
}
if !preStartupPermissionsPassed {
if accessibility != .notGranted && screenRecording != .notGranted {
preStartupPermissionsPassed = true
App.app.permissionsWindow?.close()
startupBlock()
}
} else {
if accessibility == .notGranted || screenRecording == .notGranted {
App.app.restart()
}
}
}
// workaround: public API CGPreflightScreenCaptureAccess and private API SLSRequestScreenCaptureAccess exist, but
// their return value is not updated during the app lifetime
// note: shows the system prompt if there's no permission
private static func screenRecordingIsGranted_() -> Bool {
return CGDisplayStream(
dispatchQueueDisplay: CGMainDisplayID(),
outputWidth: 1,
outputHeight: 1,
pixelFormat: Int32(kCVPixelFormatType_32BGRA),
properties: nil,
queue: .global(),
handler: { _, _, _, _ in }
) != nil
}
}