fix: could sometimes freeze upon launch (closes #5079)
This commit is contained in:
1 parent
e0ae29d64a
commit
ee27df7680
1 file changed
+67
-31
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user