From 5f9caed280a60496f39d229c20993fe474bcd4d6 Mon Sep 17 00:00:00 2001 From: Louis Pontoise Date: Wed, 11 Nov 2020 23:12:10 +0900 Subject: [PATCH] fix: some windows would not be shown in fullscreen app (closes #688) --- src/api-wrappers/AXUIElement.swift | 10 +++++----- src/api-wrappers/PrivateApis.swift | 27 ++++++++++++++++----------- src/logic/Application.swift | 9 +++++++-- src/logic/Applications.swift | 1 + 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/api-wrappers/AXUIElement.swift b/src/api-wrappers/AXUIElement.swift index 17a1d308..959bb956 100644 --- a/src/api-wrappers/AXUIElement.swift +++ b/src/api-wrappers/AXUIElement.swift @@ -7,22 +7,22 @@ import ApplicationServices.HIServices.AXAttributeConstants import ApplicationServices.HIServices.AXActionConstants // if the window server is busy, it may not reply to AX calls. We retry right before the call times-out and returns a bogus value -func retryAxCallUntilTimeout(_ group: DispatchGroup? = nil, _ fn: @escaping () throws -> Void, _ startTime: DispatchTime = DispatchTime.now()) { +func retryAxCallUntilTimeout(_ group: DispatchGroup? = nil, _ timeoutInSeconds: Double = Double(AXUIElement.globalTimeoutInSeconds), _ fn: @escaping () throws -> Void, _ startTime: DispatchTime = DispatchTime.now()) { group?.enter() BackgroundWork.axCallsQueue.async { - retryAxCallUntilTimeout_(group, fn, startTime) + retryAxCallUntilTimeout_(group, timeoutInSeconds, fn, startTime) } } -func retryAxCallUntilTimeout_(_ group: DispatchGroup?, _ fn: @escaping () throws -> Void, _ startTime: DispatchTime = DispatchTime.now()) { +func retryAxCallUntilTimeout_(_ group: DispatchGroup?, _ timeoutInSeconds: Double, _ fn: @escaping () throws -> Void, _ startTime: DispatchTime = DispatchTime.now()) { do { try fn() group?.leave() } catch { let timePassedInSeconds = Double(DispatchTime.now().uptimeNanoseconds - startTime.uptimeNanoseconds) / 1_000_000_000 - if timePassedInSeconds < Double(AXUIElement.globalTimeoutInSeconds) { + if timePassedInSeconds < timeoutInSeconds { BackgroundWork.axCallsQueue.asyncAfter(deadline: .now() + .milliseconds(10)) { - retryAxCallUntilTimeout_(group, fn, startTime) + retryAxCallUntilTimeout_(group, timeoutInSeconds, fn, startTime) } } } diff --git a/src/api-wrappers/PrivateApis.swift b/src/api-wrappers/PrivateApis.swift index 2f3d42f5..f8ff45f8 100644 --- a/src/api-wrappers/PrivateApis.swift +++ b/src/api-wrappers/PrivateApis.swift @@ -146,11 +146,27 @@ func GetProcessInformation(_ psn: inout ProcessSerialNumber, _ info: inout Proce @_silgen_name("GetProcessForPID") @discardableResult func GetProcessForPID(_ pid: pid_t, _ psn: inout ProcessSerialNumber) -> OSStatus +enum CGSSpaceType: Int { + case user = 0 + case system = 2 + case fullscreen = 4 +} + +// get the CGSSpaceType for a given space. Maybe useful for fullscreen windows +// * macOS 10.10+ +@_silgen_name("CGSSpaceGetType") +func CGSSpaceGetType(_ cid: CGSConnectionID, _ sid: CGSSpaceID) -> CGSSpaceType + + // ------------------------------------------------------------ // below are some notes on some private APIs I experimented with // ------------------------------------------------------------ +// returns true if the display is animating +//@_silgen_name("CGSManagedDisplayIsAnimating") @discardableResult +//func CGSManagedDisplayIsAnimating(_ cid: CGSConnectionID, _ displayUuid: CFString) -> Bool + //@_silgen_name("CGSGetSymbolicHotKeyValue") //func CGSGetSymbolicHotKeyValue(_ hotKey: Int, _ options: inout UInt32, _ keyCode: inout UInt32, _ modifiers: inout UInt32) -> CGError // @@ -256,17 +272,6 @@ func GetProcessForPID(_ pid: pid_t, _ psn: inout ProcessSerialNumber) -> OSStatu //@_silgen_name("CGSGetWorkspaceWindowList") @discardableResult //func CGSGetWorkspaceWindowList(_ cid: CGSConnectionID, _ workspaceNumber: CGSSpaceID, _ count: Int, _ list: [Int], _ outCount: [Int]) -> OSStatus // -//enum CGSSpaceType { -// case user -// case fullscreen -// case system -// case unknown -//} -// -//// get the CGSSpaceType for a given space. Maybe useful for fullscreen windows -//// * macOS 10.10+ -//@_silgen_name("CGSSpaceGetType") -//func CGSSpaceGetType(_ connection: CGSConnectionID, _ space: CGSSpaceID) -> CGSSpaceType // //// assigns a process to all spaces. This creates weird behaviours where its windows are available from all spaces //// * macOS 10.10+ diff --git a/src/logic/Application.swift b/src/logic/Application.swift index 828fd0c0..3144232e 100644 --- a/src/logic/Application.swift +++ b/src/logic/Application.swift @@ -12,9 +12,10 @@ class Application: NSObject { var icon: NSImage? var dockLabel: String? var pid: pid_t { runningApplication.processIdentifier } + var wasLaunchedBeforeAltTab = false static func notifications(_ app: NSRunningApplication) -> [String] { - var n = [ + let n = [ kAXApplicationActivatedNotification, kAXMainWindowChangedNotification, kAXFocusedWindowChangedNotification, @@ -74,7 +75,7 @@ class Application: NSObject { func observeNewWindows(_ group: DispatchGroup? = nil) { if runningApplication.isFinishedLaunching && runningApplication.activationPolicy != .prohibited { - retryAxCallUntilTimeout(group) { [weak self] in + retryAxCallUntilTimeout(group, 5) { [weak self] in guard let self = self else { return } if let axWindows_ = try self.axUiElement!.windows(), axWindows_.count > 0 { // bug in macOS: sometimes the OS returns multiple duplicate windows (e.g. Mail.app starting at login) @@ -105,6 +106,10 @@ class Application: NSObject { let window = self.addWindowslessAppsIfNeeded() App.app.refreshOpenUi(window) } + // workaround: opening an app while the active app is fullscreen; we wait out the space transition animation + if group == nil && !self.wasLaunchedBeforeAltTab && CGSSpaceGetType(cgsMainConnectionId, Spaces.currentSpaceId) == .fullscreen { + throw AxError.runtimeError + } } } } diff --git a/src/logic/Applications.swift b/src/logic/Applications.swift index 52509090..b8da3d27 100644 --- a/src/logic/Applications.swift +++ b/src/logic/Applications.swift @@ -7,6 +7,7 @@ class Applications { static func observeNewWindowsBlocking() { let group = DispatchGroup() for app in list { + app.wasLaunchedBeforeAltTab = true guard app.runningApplication.isFinishedLaunching else { continue } app.observeNewWindows(group) }