fix: avoid macos bug where many permission dialogs pop (closes #5106)
This commit is contained in:
1 parent
7821d7c979
commit
5c4199ec74
3 files changed
+53
-5
No files matched your search
@@ -56,11 +56,12 @@ class WindowCaptureScreenshots {
|
||||
}
|
||||
|
||||
private static func oneTimeCapture(_ scWindow: SCWindow, _ source: RefreshCausedBy) {
|
||||
guard !App.app.isTerminating else { return }
|
||||
let config = SCStreamConfiguration.forWindow(scWindow, false)
|
||||
let filter = SCContentFilter(desktopIndependentWindow: scWindow)
|
||||
SCScreenshotManager.captureSampleBuffer(contentFilter: filter, configuration: config) { sampleBuffer, error in
|
||||
guard let sampleBuffer, error == nil else { Logger.error { "\(sampleBuffer == nil) \(error)" }; return }
|
||||
ActiveWindowCaptures.increment()
|
||||
SCScreenshotManager.captureImage(contentFilter: filter, configuration: config) { cgImage, error in
|
||||
ActiveWindowCaptures.decrement()
|
||||
guard let cgImage, error == nil else { Logger.error { "\(cgImage == nil) \(error)" }; return }
|
||||
guard source != .refreshOnlyThumbnailsAfterShowUi || App.app.appIsBeingUsed else { return }
|
||||
DispatchQueue.main.async {
|
||||
@@ -89,9 +90,12 @@ class WindowCaptureScreenshotsPrivateApi {
|
||||
}
|
||||
|
||||
private static func oneTimeCapture(_ wid: CGWindowID) -> CGImage? {
|
||||
guard !App.app.isTerminating else { return nil }
|
||||
// we use CGSHWCaptureWindowList because it can screenshot minimized windows, which CGWindowListCreateImage can't
|
||||
var windowId_ = wid
|
||||
ActiveWindowCaptures.increment()
|
||||
let list = CGSHWCaptureWindowList(CGS_CONNECTION, &windowId_, 1, [.ignoreGlobalClipShape, .bestResolution, .fullSize]).takeRetainedValue() as! [CGImage]
|
||||
ActiveWindowCaptures.decrement()
|
||||
return list.first
|
||||
}
|
||||
}
|
||||
@@ -282,3 +286,27 @@ extension CMSampleBuffer {
|
||||
return device.makeTexture(descriptor: desc, iosurface: surface, plane: 0)
|
||||
}
|
||||
}
|
||||
|
||||
class ActiveWindowCaptures {
|
||||
private static var count: Int = 0
|
||||
private static let semaphore = DispatchSemaphore(value: 1)
|
||||
|
||||
static func increment() {
|
||||
semaphore.wait()
|
||||
count += 1
|
||||
semaphore.signal()
|
||||
}
|
||||
|
||||
static func decrement() {
|
||||
semaphore.wait()
|
||||
count -= 1
|
||||
semaphore.signal()
|
||||
}
|
||||
|
||||
static func value() -> Int {
|
||||
semaphore.wait()
|
||||
let current = count
|
||||
semaphore.signal()
|
||||
return current
|
||||
}
|
||||
}
|
||||
+16
-3
@@ -5,9 +5,9 @@ if let command = CliClient.detectCommand() {
|
||||
CliClient.sendCommandAndProcessResponse(command)
|
||||
}
|
||||
|
||||
// - if the app is quit/force-quit from Activity Monitor, it will receive SIGTERM and applicationWillTerminate won't be called
|
||||
// - if the app crashes in swift code (e.g. unexpected nil object), SIGTRAP is sent
|
||||
// we intercept these signals, and do an emergency exit
|
||||
// - SIGTERM: if the app is quit/force-quit from Activity Monitor, it will receive SIGTERM and applicationWillTerminate won't be called
|
||||
// - SIGTRAP: if the app crashes in swift code (e.g. unexpected nil object), SIGTRAP is sent
|
||||
// - SIGKILL: if we stop the app using SIGKILL (e.g. stopping from IntelliJ, or from the terminal), there is no chance to intercept it
|
||||
[SIGTERM, SIGTRAP].forEach {
|
||||
signal($0) { s in
|
||||
emergencyExit("Exiting after receiving signal", s)
|
||||
@@ -34,5 +34,18 @@ fileprivate func emergencyExit(_ logs: Any?...) {
|
||||
setNativeCommandTabEnabled(true)
|
||||
print(logs)
|
||||
printStackTrace()
|
||||
makeSureAllCapturesAreFinished()
|
||||
exit(0)
|
||||
}
|
||||
|
||||
func makeSureAllCapturesAreFinished() {
|
||||
App.app.isTerminating = true
|
||||
let timeout = 5.0
|
||||
let startTime = DispatchTime.now()
|
||||
var elapsedTime = 0.0
|
||||
while ActiveWindowCaptures.value() > 0 && elapsedTime <= timeout {
|
||||
Logger.warning { "There are \(ActiveWindowCaptures.value()) screenshots in progress. We need to wait for them to avoid a bug where macOS shows permission dialogs to the user for no reason." }
|
||||
Thread.sleep(forTimeInterval: 0.1)
|
||||
elapsedTime = Double(DispatchTime.now().uptimeNanoseconds - startTime.uptimeNanoseconds) / 1_000_000_000
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ class App: AppCenterApplication {
|
||||
static let website = "https://alt-tab-macos.netlify.app"
|
||||
static let appIcon = CGImage.named("app.icns")
|
||||
static var app: App!
|
||||
var isTerminating = false
|
||||
var thumbnailsPanel: ThumbnailsPanel!
|
||||
var previewPanel: PreviewPanel!
|
||||
var preferencesWindow: PreferencesWindow!
|
||||
@@ -331,6 +332,12 @@ extension App: NSApplicationDelegate {
|
||||
// symbolic hotkeys state persist after the app is quit; we restore this shortcut before quitting
|
||||
setNativeCommandTabEnabled(true)
|
||||
}
|
||||
|
||||
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
|
||||
Logger.info { "" }
|
||||
makeSureAllCapturesAreFinished()
|
||||
return .terminateNow
|
||||
}
|
||||
}
|
||||
|
||||
enum RefreshCausedBy {
|
||||
|
||||
Reference in new issue
Block a user