fix: show android emulator window (closes #376)
This commit is contained in:
1 parent
fd0623a706
commit
bb8a5ceaa9
4 files changed
+37
-11
No files matched your search
@@ -45,23 +45,43 @@ extension AXUIElement {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isActualWindow(_ bundleIdentifier: String?, _ wid: CGWindowID, _ isOnNormalLevel: Bool, _ title: String?, _ subrole: String?, _ role: String?) -> Bool {
|
func isActualWindow(_ runningApp: NSRunningApplication, _ wid: CGWindowID, _ isOnNormalLevel: Bool, _ title: String?, _ subrole: String?, _ role: String?) -> Bool {
|
||||||
// Some non-windows have cgWindowId == 0 (e.g. windows of apps starting at login with the checkbox "Hidden" checked)
|
|
||||||
// Some non-windows have title: nil (e.g. some OS elements)
|
// Some non-windows have title: nil (e.g. some OS elements)
|
||||||
// Some non-windows have subrole: nil (e.g. some OS elements), "AXUnknown" (e.g. Bartender), "AXSystemDialog" (e.g. Intellij tooltips)
|
// Some non-windows have subrole: nil (e.g. some OS elements), "AXUnknown" (e.g. Bartender), "AXSystemDialog" (e.g. Intellij tooltips)
|
||||||
// Minimized windows or windows of a hidden app have subrole "AXDialog"
|
// Minimized windows or windows of a hidden app have subrole "AXDialog"
|
||||||
// Activity Monitor main window subrole is "AXDialog" for a brief moment at launch; it then becomes "AXStandardWindow"
|
// Activity Monitor main window subrole is "AXDialog" for a brief moment at launch; it then becomes "AXStandardWindow"
|
||||||
// CGWindowLevel == .normalWindow helps filter out iStats Pro and other top-level pop-overs
|
|
||||||
|
// Some non-windows have cgWindowId == 0 (e.g. windows of apps starting at login with the checkbox "Hidden" checked)
|
||||||
return wid != 0 &&
|
return wid != 0 &&
|
||||||
// don't show floating windows
|
// CGWindowLevel == .normalWindow helps filter out iStats Pro and other top-level pop-overs, and floating windows
|
||||||
isOnNormalLevel &&
|
isOnNormalLevel &&
|
||||||
(["AXStandardWindow", "AXDialog"].contains(subrole) ||
|
(["AXStandardWindow", "AXDialog"].contains(subrole) ||
|
||||||
|
steam(runningApp, title, role) ||
|
||||||
|
firefoxFullscreenVideo(runningApp, role) ||
|
||||||
|
androidEmulator(runningApp, title))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func steam(_ runningApp: NSRunningApplication, _ title: String?, _ role: String?) -> Bool {
|
||||||
// All Steam windows have subrole == AXUnknown
|
// All Steam windows have subrole == AXUnknown
|
||||||
// some dropdown menus are not desirable; they have title == "", or sometimes role == nil when switching between menus quickly
|
// some dropdown menus are not desirable; they have title == "", or sometimes role == nil when switching between menus quickly
|
||||||
(bundleIdentifier == "com.valvesoftware.steam" && title != "" && role != nil) ||
|
return runningApp.bundleIdentifier == "com.valvesoftware.steam" && title != "" && role != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func firefoxFullscreenVideo(_ runningApp: NSRunningApplication, _ role: String?) -> Bool {
|
||||||
// Firefox fullscreen video have subrole == AXUnknown if fullscreen'ed when the base window is not fullscreen
|
// Firefox fullscreen video have subrole == AXUnknown if fullscreen'ed when the base window is not fullscreen
|
||||||
(bundleIdentifier == "org.mozilla.firefox" && role == "AXWindow")
|
return runningApp.bundleIdentifier == "org.mozilla.firefox" && role == "AXWindow"
|
||||||
)
|
}
|
||||||
|
|
||||||
|
private func androidEmulator(_ runningApp: NSRunningApplication, _ title: String?) -> Bool {
|
||||||
|
// android emulator small vertical menu is a "window" with empty title; we exclude it
|
||||||
|
if title != "",
|
||||||
|
// NSRunningApplication provides no way to identify the emulator; we pattern match on its KERN_PROCARGS
|
||||||
|
runningApp.bundleIdentifier == nil,
|
||||||
|
let executablePath = Sysctl.run([CTL_KERN, KERN_PROCARGS, runningApp.processIdentifier]) {
|
||||||
|
// example path: ~/Library/Android/sdk/emulator/qemu/darwin-x86_64/qemu-system-x86_64
|
||||||
|
return executablePath.range(of: "qemu-system[^/]*$", options: .regularExpression, range: nil, locale: nil) != nil
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func isOnNormalLevel(_ wid: CGWindowID) -> Bool {
|
func isOnNormalLevel(_ wid: CGWindowID) -> Bool {
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ public struct Sysctl {
|
|||||||
return run(name, { $0.baseAddress?.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee } })
|
return run(name, { $0.baseAddress?.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee } })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func run(_ keys: [Int32]) -> String? {
|
||||||
|
return data(keys)?.withUnsafeBufferPointer() { dataPointer -> String? in
|
||||||
|
dataPointer.baseAddress.flatMap { String(validatingUTF8: $0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static func run<R>(_ name: String, _ fn: (UnsafeBufferPointer<Int8>) -> R?) -> R? {
|
private static func run<R>(_ name: String, _ fn: (UnsafeBufferPointer<Int8>) -> R?) -> R? {
|
||||||
return keys(name).flatMap { keys in data(keys)?.withUnsafeBufferPointer() { fn($0) } }
|
return keys(name).flatMap { keys in data(keys)?.withUnsafeBufferPointer() { fn($0) } }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class Application: NSObject {
|
|||||||
let subrole = try $0.subrole()
|
let subrole = try $0.subrole()
|
||||||
let role = try $0.role()
|
let role = try $0.role()
|
||||||
let isOnNormalLevel = $0.isOnNormalLevel(wid)
|
let isOnNormalLevel = $0.isOnNormalLevel(wid)
|
||||||
if $0.isActualWindow(self.runningApplication.bundleIdentifier, wid, isOnNormalLevel, title, subrole, role) {
|
if $0.isActualWindow(self.runningApplication, wid, isOnNormalLevel, title, subrole, role) {
|
||||||
return ($0, wid, title, try $0.isFullscreen(), try $0.isMinimized(), try $0.position())
|
return ($0, wid, title, try $0.isFullscreen(), try $0.isMinimized(), try $0.position())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func retryUntilTimeout(_ fn: @escaping () throws -> Void, _ startTime: DispatchT
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleEvent(_ type: String, _ element: AXUIElement) throws {
|
func handleEvent(_ type: String, _ element: AXUIElement) throws {
|
||||||
debugPrint("Accessibility event", type, element)
|
debugPrint("Accessibility event", type, try element.title())
|
||||||
// events are handled concurrently, thus we check that the app is still running
|
// events are handled concurrently, thus we check that the app is still running
|
||||||
if let pid = try element.pid() {
|
if let pid = try element.pid() {
|
||||||
switch type {
|
switch type {
|
||||||
@@ -102,7 +102,7 @@ private func windowCreated(_ element: AXUIElement, _ pid: pid_t) throws {
|
|||||||
// a window being un-minimized can trigger kAXWindowCreatedNotification
|
// a window being un-minimized can trigger kAXWindowCreatedNotification
|
||||||
if Windows.list.firstIndexThatMatches(element, wid) == nil,
|
if Windows.list.firstIndexThatMatches(element, wid) == nil,
|
||||||
let runningApp = NSRunningApplication(processIdentifier: pid),
|
let runningApp = NSRunningApplication(processIdentifier: pid),
|
||||||
element.isActualWindow(runningApp.bundleIdentifier, wid, isOnNormalLevel, axTitle, subrole, role),
|
element.isActualWindow(runningApp, wid, isOnNormalLevel, axTitle, subrole, role),
|
||||||
let app = (Applications.list.first { $0.runningApplication.processIdentifier == pid }) {
|
let app = (Applications.list.first { $0.runningApplication.processIdentifier == pid }) {
|
||||||
let window = Window(element, app, wid, axTitle, isFullscreen, isMinimized, position)
|
let window = Window(element, app, wid, axTitle, isFullscreen, isMinimized, position)
|
||||||
Windows.list.insertAndScaleRecycledPool(window, at: 0)
|
Windows.list.insertAndScaleRecycledPool(window, at: 0)
|
||||||
@@ -127,7 +127,7 @@ private func focusedWindowChanged(_ element: AXUIElement, _ pid: pid_t) throws {
|
|||||||
Windows.list.insertAndScaleRecycledPool(Windows.list.remove(at: existingIndex), at: 0)
|
Windows.list.insertAndScaleRecycledPool(Windows.list.remove(at: existingIndex), at: 0)
|
||||||
App.app.refreshOpenUi([Windows.list[0], Windows.list[existingIndex]])
|
App.app.refreshOpenUi([Windows.list[0], Windows.list[existingIndex]])
|
||||||
} else if let runningApp = NSRunningApplication(processIdentifier: pid),
|
} else if let runningApp = NSRunningApplication(processIdentifier: pid),
|
||||||
element.isActualWindow(runningApp.bundleIdentifier, wid, isOnNormalLevel, axTitle, subrole, role),
|
element.isActualWindow(runningApp, wid, isOnNormalLevel, axTitle, subrole, role),
|
||||||
let app = (Applications.list.first { $0.runningApplication.processIdentifier == pid }) {
|
let app = (Applications.list.first { $0.runningApplication.processIdentifier == pid }) {
|
||||||
Windows.list.insertAndScaleRecycledPool(Window(element, app, wid, axTitle, isFullscreen, isMinimized, position), at: 0)
|
Windows.list.insertAndScaleRecycledPool(Window(element, app, wid, axTitle, isFullscreen, isMinimized, position), at: 0)
|
||||||
App.app.refreshOpenUi([Windows.list[0]])
|
App.app.refreshOpenUi([Windows.list[0]])
|
||||||
|
|||||||
Reference in new issue
Block a user