fix: could sometimes freeze upon launch (closes #5079)

This commit is contained in:
lwouis committed 2025-12-06 12:59:29 +01:00
1 parent e0ae29d64a
commit ee27df7680
1 file changed
+67 -31
+67 -31
View File
@@ -1,4 +1,5 @@
import Cocoa import Cocoa
import ScreenCaptureKit.SCShareableContent
// macOS has some privacy restrictions. The user needs to grant certain permissions, app by app, in System Preferences > Security & Privacy // macOS has some privacy restrictions. The user needs to grant certain permissions, app by app, in System Preferences > Security & Privacy
class SystemPermissions { class SystemPermissions {
@@ -32,16 +33,6 @@ class SystemPermissions {
CFRunLoopAddTimer(BackgroundWork.systemPermissionsThread.runLoop, timerPermissionsToUpdatePermissionsWindow!, .commonModes) CFRunLoopAddTimer(BackgroundWork.systemPermissionsThread.runLoop, timerPermissionsToUpdatePermissionsWindow!, .commonModes)
} }
static func pollPermissionsRemovedWhileAltTabIsRunning() {
timerPermissionsRemovedWhileAltTabIsRunning = Timer(timeInterval: 5, repeats: true) { _ in
DispatchQueue.main.async {
checkPermissionsWhileAltTabIsRunning()
}
}
timerPermissionsRemovedWhileAltTabIsRunning!.tolerance = 1
CFRunLoopAddTimer(BackgroundWork.systemPermissionsThread.runLoop, timerPermissionsRemovedWhileAltTabIsRunning!, .commonModes)
}
@discardableResult @discardableResult
static func updateAccessibilityIsGranted() -> PermissionStatus { static func updateAccessibilityIsGranted() -> PermissionStatus {
accessibilityPermission = detectAccessibilityIsGranted() accessibilityPermission = detectAccessibilityIsGranted()
@@ -54,6 +45,16 @@ class SystemPermissions {
return screenRecordingPermission return screenRecordingPermission
} }
private static func pollPermissionsRemovedWhileAltTabIsRunning() {
timerPermissionsRemovedWhileAltTabIsRunning = Timer(timeInterval: 5, repeats: true) { _ in
DispatchQueue.main.async {
checkPermissionsWhileAltTabIsRunning()
}
}
timerPermissionsRemovedWhileAltTabIsRunning!.tolerance = 1
CFRunLoopAddTimer(BackgroundWork.systemPermissionsThread.runLoop, timerPermissionsRemovedWhileAltTabIsRunning!, .commonModes)
}
private static func detectAccessibilityIsGranted() -> PermissionStatus { private static func detectAccessibilityIsGranted() -> PermissionStatus {
if #available(macOS 10.9, *) { if #available(macOS 10.9, *) {
return AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeRetainedValue(): false] as CFDictionary) ? .granted : .notGranted return AXIsProcessTrustedWithOptions([kAXTrustedCheckOptionPrompt.takeRetainedValue(): false] as CFDictionary) ? .granted : .notGranted
@@ -123,31 +124,66 @@ class SystemPermissions {
// their return value is not updated during the app lifetime // their return value is not updated during the app lifetime
// note: shows the system prompt if there's no permission // note: shows the system prompt if there's no permission
private static func screenRecordingIsGrantedOnSomeDisplay() -> Bool { private static func screenRecordingIsGrantedOnSomeDisplay() -> Bool {
let mainDisplayID = CGMainDisplayID() if #available(macOS 12.3, *) {
if screenRecordingIsGrantedOnDisplay(mainDisplayID) { return checkWithSCShareableContent()
return true } else {
} let mainDisplayID = CGMainDisplayID()
// maybe the main screen can't produce a CGDisplayStream, but another screen can if checkWithCGDisplayStream(mainDisplayID) {
// a positive on any screen must mean that the permission is granted; we try on the other screens return true
for screen in NSScreen.screens { }
if let id = screen.number(), id != mainDisplayID { // maybe the main screen can't produce a CGDisplayStream, but another screen can
if screenRecordingIsGrantedOnDisplay(id) { // a positive on any screen must mean that the permission is granted; we try on the other screens
return true for screen in NSScreen.screens {
if let id = screen.number(), id != mainDisplayID {
if checkWithCGDisplayStream(id) {
return true
}
} }
} }
return false
} }
return false
} }
private static func screenRecordingIsGrantedOnDisplay(_ displayId: CGDirectDisplayID) -> Bool { @available(macOS 12.3, *)
return CGDisplayStream( private static func checkWithSCShareableContent() -> Bool {
dispatchQueueDisplay: displayId, return runWithTimeout { completion in
outputWidth: 1, SCShareableContent.getWithCompletionHandler { shareableContent, error in
outputHeight: 1, completion(error != nil ? false : (shareableContent != nil))
pixelFormat: Int32(kCVPixelFormatType_32BGRA), }
properties: nil, }
queue: .global(), }
handler: { _, _, _, _ in }
) != nil private static func checkWithCGDisplayStream(_ id: CGDirectDisplayID) -> Bool {
return runWithTimeout { completion in
// this initializer can actually block for a while
// it's undocumented but has been proven by spindumps shared by AltTab users
let displayStream = CGDisplayStream(
dispatchQueueDisplay: id,
outputWidth: 1,
outputHeight: 1,
pixelFormat: Int32(kCVPixelFormatType_32BGRA),
properties: nil,
queue: .global()
) { _, _, _, _ in }
completion(displayStream != nil)
}
}
private static func runWithTimeout(_ block: @escaping (@escaping (Bool) -> Void) -> Void) -> Bool{
let semaphore = DispatchSemaphore(value: 0)
var result = false
block { r in
result = r
semaphore.signal()
}
let timeoutResult = semaphore.wait(timeout: .now() + 1)
if timeoutResult == .timedOut {
return false
}
return result
}
private enum TimeoutError: Error {
case timedOut
} }
} }