From 5514bad47a03328986952a90b031e2d12e7a22fc Mon Sep 17 00:00:00 2001 From: Jordan Baird Date: Thu, 4 Jul 2024 16:13:41 -0600 Subject: [PATCH] Rework application frame getter --- Ice.xcodeproj/project.pbxproj | 24 ----- .../AccessibilityApplication.swift | 41 -------- Ice/Accessibility/AccessibilityError.swift | 20 ---- Ice/Accessibility/AccessibilityMenuBar.swift | 95 ------------------- .../AccessibilityMenuBarItem.swift | 52 ---------- Ice/Events/EventManager.swift | 2 +- Ice/MenuBar/MenuBarManager.swift | 52 +++++++--- .../MenuBarOverlayPanel.swift | 11 +-- 8 files changed, 45 insertions(+), 252 deletions(-) delete mode 100644 Ice/Accessibility/AccessibilityApplication.swift delete mode 100644 Ice/Accessibility/AccessibilityError.swift delete mode 100644 Ice/Accessibility/AccessibilityMenuBar.swift delete mode 100644 Ice/Accessibility/AccessibilityMenuBarItem.swift diff --git a/Ice.xcodeproj/project.pbxproj b/Ice.xcodeproj/project.pbxproj index 697596cb..f9a08f79 100644 --- a/Ice.xcodeproj/project.pbxproj +++ b/Ice.xcodeproj/project.pbxproj @@ -20,10 +20,6 @@ 1725FC6A2AED973800A59081 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1725FC692AED973800A59081 /* AppState.swift */; }; 172778482C1490EA00F6F687 /* EventTap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172778472C1490EA00F6F687 /* EventTap.swift */; }; 1727786E2C15181300F6F687 /* MenuBarItemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1727786D2C15181300F6F687 /* MenuBarItemManager.swift */; }; - 172D827A2BC1F29E0048DD88 /* AccessibilityMenuBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172D82792BC1F29E0048DD88 /* AccessibilityMenuBar.swift */; }; - 172D827C2BC1F2C10048DD88 /* AccessibilityError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172D827B2BC1F2C10048DD88 /* AccessibilityError.swift */; }; - 172D827E2BC1F2E70048DD88 /* AccessibilityMenuBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172D827D2BC1F2E70048DD88 /* AccessibilityMenuBarItem.swift */; }; - 172D82802BC1F3270048DD88 /* AccessibilityApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172D827F2BC1F3270048DD88 /* AccessibilityApplication.swift */; }; 1736F77C2ADBBF340073428E /* CustomGradientPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1736F77B2ADBBF340073428E /* CustomGradientPicker.swift */; }; 1736F7802ADBC02B0073428E /* CustomGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1736F77F2ADBC02B0073428E /* CustomGradient.swift */; }; 1737E3E52BA3C4AA009F0EFA /* HotkeyAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1737E3E42BA3C4AA009F0EFA /* HotkeyAction.swift */; }; @@ -148,10 +144,6 @@ 1726A3F82B3378B8008B09DD /* Acknowledgements.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Acknowledgements.rtf; sourceTree = ""; }; 172778472C1490EA00F6F687 /* EventTap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventTap.swift; sourceTree = ""; }; 1727786D2C15181300F6F687 /* MenuBarItemManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarItemManager.swift; sourceTree = ""; }; - 172D82792BC1F29E0048DD88 /* AccessibilityMenuBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityMenuBar.swift; sourceTree = ""; }; - 172D827B2BC1F2C10048DD88 /* AccessibilityError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityError.swift; sourceTree = ""; }; - 172D827D2BC1F2E70048DD88 /* AccessibilityMenuBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityMenuBarItem.swift; sourceTree = ""; }; - 172D827F2BC1F3270048DD88 /* AccessibilityApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityApplication.swift; sourceTree = ""; }; 1736F77B2ADBBF340073428E /* CustomGradientPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomGradientPicker.swift; sourceTree = ""; }; 1736F77F2ADBC02B0073428E /* CustomGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomGradient.swift; sourceTree = ""; }; 1737E3E42BA3C4AA009F0EFA /* HotkeyAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotkeyAction.swift; sourceTree = ""; }; @@ -338,17 +330,6 @@ name = Frameworks; sourceTree = ""; }; - 172D82782BC1F2950048DD88 /* Accessibility */ = { - isa = PBXGroup; - children = ( - 172D827B2BC1F2C10048DD88 /* AccessibilityError.swift */, - 172D82792BC1F29E0048DD88 /* AccessibilityMenuBar.swift */, - 172D827F2BC1F3270048DD88 /* AccessibilityApplication.swift */, - 172D827D2BC1F2E70048DD88 /* AccessibilityMenuBarItem.swift */, - ); - path = Accessibility; - sourceTree = ""; - }; 1736F7762ADBBEF10073428E /* CustomGradientPicker */ = { isa = PBXGroup; children = ( @@ -669,7 +650,6 @@ 7166832C2A767E6A006ABF84 /* Ice */ = { isa = PBXGroup; children = ( - 172D82782BC1F2950048DD88 /* Accessibility */, 17CDCACA2C2E6F3D000B1CFF /* Bridging */, 177386F62B0A656100448BBF /* ControlItem */, 17147FBE2B99513200C4A435 /* Events */, @@ -895,7 +875,6 @@ 71FEA2542A8D701B0048341A /* LocalEventMonitor.swift in Sources */, 7150A7AF2AA426C60045EA68 /* HotkeyRegistry.swift in Sources */, 7162406F2AA0A323003EC671 /* MenuBarSection.swift in Sources */, - 172D827A2BC1F29E0048DD88 /* AccessibilityMenuBar.swift in Sources */, 177386F52B0A654D00448BBF /* ControlItemImageSet.swift in Sources */, 17CDCACF2C2E6F77000B1CFF /* Private.swift in Sources */, 17BE3DE02C1A705E008B98EF /* IceBarPanel+hasKeyAppearance.swift in Sources */, @@ -942,7 +921,6 @@ 176B23F42ADB76A1008AE86B /* CustomColorPicker.swift in Sources */, 17CDCAD12C2E6FB0000B1CFF /* Bridging.swift in Sources */, 17540BD82B20C0DA00A0F965 /* NSBezierPath+drawShadow.swift in Sources */, - 172D827C2BC1F2C10048DD88 /* AccessibilityError.swift in Sources */, 177354842B1F9AF9001CF731 /* NSBezierPath+union.swift in Sources */, 172778482C1490EA00F6F687 /* EventTap.swift in Sources */, 17540BDC2B23BD5700A0F965 /* NSBezierPath+intersects.swift in Sources */, @@ -950,7 +928,6 @@ 179AC2FC2C1146EF0051E7B0 /* RemoveSidebarToggle.swift in Sources */, 17B7F32B2B264C1800CDCF49 /* MenuBarAppearanceManager.swift in Sources */, 17F9984D2C16CD8E00D75EC0 /* MenuBarItemsSettingsPane.swift in Sources */, - 172D827E2BC1F2E70048DD88 /* AccessibilityMenuBarItem.swift in Sources */, 17D1AC8D2B97BB5900726180 /* MenuBarItem.swift in Sources */, 173C248C2B8E821C0096F7A1 /* UpdatesSettingsPane.swift in Sources */, 171C6F9C2C0356BC0097A5C8 /* GeneralSettingsPane.swift in Sources */, @@ -1004,7 +981,6 @@ 175812542B80FBFA00D622DA /* MenuBarAppearanceEditorPanel.swift in Sources */, 17CDCAD42C2E8A04000B1CFF /* CGError+logString.swift in Sources */, 7166832E2A767E6A006ABF84 /* IceApp.swift in Sources */, - 172D82802BC1F3270048DD88 /* AccessibilityApplication.swift in Sources */, 17C3C5F72B75A36100B9648C /* NSScreen+displayID.swift in Sources */, 7133ED5E2A853FCF000A7E1B /* Constants.swift in Sources */, 17F9984B2C16CBC100D75EC0 /* LayoutBar.swift in Sources */, diff --git a/Ice/Accessibility/AccessibilityApplication.swift b/Ice/Accessibility/AccessibilityApplication.swift deleted file mode 100644 index b8e8d8ad..00000000 --- a/Ice/Accessibility/AccessibilityApplication.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// AccessibilityApplication.swift -// Ice -// - -import AXSwift -import Cocoa - -/// An accessibility representation of an application. -@MainActor -struct AccessibilityApplication { - let application: Application - - /// Creates an accessibility application from an application retrieved - /// from an accessibility framework. - init(application: Application) { - self.application = application - } - - /// Creates an accessibility application from a process identifier. - init(forProcessID processID: pid_t) throws { - guard let application = Application(forProcessID: processID) else { - throw AccessibilityError(message: "Could not create application") - } - self.init(application: application) - } - - /// Returns the menu bar associated with the application. - func menuBar() throws -> AccessibilityMenuBar { - do { - guard let uiElement: UIElement = try application.attribute(.menuBar) else { - throw AccessibilityError(message: "No menu bar for application") - } - return try AccessibilityMenuBar(uiElement: uiElement) - } catch let error as AccessibilityError { - throw error - } catch { - throw AccessibilityError(message: "Invalid menu bar for application", underlyingError: error) - } - } -} diff --git a/Ice/Accessibility/AccessibilityError.swift b/Ice/Accessibility/AccessibilityError.swift deleted file mode 100644 index 5f6636b7..00000000 --- a/Ice/Accessibility/AccessibilityError.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// AccessibilityError.swift -// Ice -// - -/// An error that provides more context about an accessibility failure. -struct AccessibilityError: Error, CustomStringConvertible { - /// A message associated with the error. - var message: String - /// An underlying error that was thrown by an accessibility framework, if any. - var underlyingError: (any Error)? - - var description: String { - var description = message - if let underlyingError { - description += " (\(underlyingError))" - } - return description - } -} diff --git a/Ice/Accessibility/AccessibilityMenuBar.swift b/Ice/Accessibility/AccessibilityMenuBar.swift deleted file mode 100644 index 9fa687a7..00000000 --- a/Ice/Accessibility/AccessibilityMenuBar.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// AccessibilityMenuBar.swift -// Ice -// - -import AXSwift -import Cocoa - -/// An accessibility representation of a menu bar. -@MainActor -struct AccessibilityMenuBar { - /// The underyling UI element. - let uiElement: UIElement - - /// Creates an accessibility menu bar from the given UI element. - /// - /// - Parameter uiElement: A UI element that represents a menu bar. - init(uiElement: UIElement) throws { - do { - guard try uiElement.role() == .menuBar else { - throw AccessibilityError(message: "Not a menu bar") - } - self.uiElement = uiElement - } catch let error as AccessibilityError { - throw error - } catch { - throw AccessibilityError(message: "Invalid menu bar", underlyingError: error) - } - } - - /// Creates an accessibility menu bar for the given display. - /// - /// - Parameter display: The display to get the menu bar for. - init(display: CGDirectDisplayID) throws { - do { - guard let menuBarWindow = WindowInfo.getMenuBarWindow(for: display) else { - throw AccessibilityError(message: "No menu bar window for display \(display)") - } - let position = menuBarWindow.frame.origin - guard let uiElement = try systemWideElement.elementAtPosition(Float(position.x), Float(position.y)) else { - throw AccessibilityError(message: "No menu bar at position \(position)") - } - try self.init(uiElement: uiElement) - } catch let error as AccessibilityError { - throw error - } catch { - throw AccessibilityError(message: "Invalid menu bar for display \(display)", underlyingError: error) - } - } - - /// Returns a Boolean value that indicates whether the given display - /// has a valid menu bar. - static func hasValidMenuBar(for display: CGDirectDisplayID) -> Bool { - guard let menuBarWindow = WindowInfo.getMenuBarWindow(for: display) else { - return false - } - let position = menuBarWindow.frame.origin - do { - let uiElement = try systemWideElement.elementAtPosition(Float(position.x), Float(position.y)) - return try uiElement?.role() == .menuBar - } catch { - return false - } - } - - /// Returns the menu bar's frame. - func frame() throws -> CGRect { - do { - guard let frame: CGRect = try uiElement.attribute(.frame) else { - throw AccessibilityError(message: "Missing frame") - } - return frame - } catch let error as AccessibilityError { - throw error - } catch { - throw AccessibilityError(message: "Invalid frame", underlyingError: error) - } - } - - /// Returns an array of the items in the menu bar. - func menuBarItems() throws -> [AccessibilityMenuBarItem] { - do { - guard let children: [UIElement] = try uiElement.arrayAttribute(.children) else { - throw AccessibilityError(message: "Missing menu bar items") - } - return try children.map { child in - try AccessibilityMenuBarItem(uiElement: child) - } - } catch let error as AccessibilityError { - throw error - } catch { - throw AccessibilityError(message: "Invalid menu bar items", underlyingError: error) - } - } -} diff --git a/Ice/Accessibility/AccessibilityMenuBarItem.swift b/Ice/Accessibility/AccessibilityMenuBarItem.swift deleted file mode 100644 index 49a0a9b6..00000000 --- a/Ice/Accessibility/AccessibilityMenuBarItem.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// AccessibilityMenuBarItem.swift -// Ice -// - -import AXSwift -import CoreGraphics - -/// An accessibility representation of an item in a menu bar. -@MainActor -struct AccessibilityMenuBarItem { - /// The underyling UI element. - let uiElement: UIElement - - /// A Boolean value that indicates whether the menu bar item is enabled. - var isEnabled: Bool { - (try? uiElement.attribute(.enabled)) == true - } - - /// Creates an accessibility menu bar item from the given UI element. - /// - /// - Parameter uiElement: A UI element that represents a menu bar item. - init(uiElement: UIElement) throws { - do { - guard let parent: UIElement = try uiElement.attribute(.parent) else { - throw AccessibilityError(message: "Missing parent") - } - guard try parent.role() == .menuBar else { - throw AccessibilityError(message: "Not a menu bar item") - } - self.uiElement = uiElement - } catch let error as AccessibilityError { - throw error - } catch { - throw AccessibilityError(message: "Invalid menu bar item", underlyingError: error) - } - } - - /// Returns the item's frame. - func frame() throws -> CGRect { - do { - guard let frame: CGRect = try uiElement.attribute(.frame) else { - throw AccessibilityError(message: "Missing frame") - } - return frame - } catch let error as AccessibilityError { - throw error - } catch { - throw AccessibilityError(message: "Invalid frame", underlyingError: error) - } - } -} diff --git a/Ice/Events/EventManager.swift b/Ice/Events/EventManager.swift index 354ec0fe..d39666f8 100644 --- a/Ice/Events/EventManager.swift +++ b/Ice/Events/EventManager.swift @@ -459,7 +459,7 @@ extension EventManager { let mouseLocation = MouseCursor.location(flipped: true), let screen = bestScreen, let appState, - let applicationMenuFrame = try? appState.menuBarManager.getApplicationMenuFrame(for: screen.displayID) + let applicationMenuFrame = appState.menuBarManager.getApplicationMenuFrame(for: screen.displayID) else { return false } diff --git a/Ice/MenuBar/MenuBarManager.swift b/Ice/MenuBar/MenuBarManager.swift index 1cde3098..91f6c4ae 100644 --- a/Ice/MenuBar/MenuBarManager.swift +++ b/Ice/MenuBar/MenuBarManager.swift @@ -3,6 +3,7 @@ // Ice // +import AXSwift import Combine import OSLog import SwiftUI @@ -199,7 +200,7 @@ final class MenuBarManager: ObservableObject { let displayID = screen.displayID // get the application menu frame for the display - guard let applicationMenuFrame = try? getApplicationMenuFrame(for: displayID) else { + guard let applicationMenuFrame = getApplicationMenuFrame(for: displayID) else { return } @@ -281,20 +282,49 @@ final class MenuBarManager: ObservableObject { } } + /// Returns a Boolean value that indicates whether the given display + /// has a valid menu bar. + func hasValidMenuBar(for display: CGDirectDisplayID) -> Bool { + guard let menuBarWindow = WindowInfo.getMenuBarWindow(for: display) else { + return false + } + let position = menuBarWindow.frame.origin + do { + let uiElement = try systemWideElement.elementAtPosition(Float(position.x), Float(position.y)) + return try uiElement?.role() == .menuBar + } catch { + return false + } + } + /// Returns the frame of the application menu for the given display. - func getApplicationMenuFrame(for display: CGDirectDisplayID) throws -> CGRect { - let menuBar = try AccessibilityMenuBar(display: display) - var menuBarFrame = try menuBar.frame() - menuBarFrame.origin = CGDisplayBounds(display).origin - menuBarFrame.size.width = try menuBar.menuBarItems().reduce(into: 0) { width, item in - if item.isEnabled { - try width += item.frame().width - } + func getApplicationMenuFrame(for display: CGDirectDisplayID) -> CGRect? { + let displayBounds = CGDisplayBounds(display) + guard + let menuBar = try? systemWideElement.elementAtPosition( + Float(displayBounds.origin.x), + Float(displayBounds.origin.y) + ), + (try? menuBar.role()) == .menuBar, + var menuBarFrame: CGRect = try? menuBar.attribute(.frame), + let items: [UIElement] = try? menuBar.arrayAttribute(.children) + else { + return nil } + menuBarFrame.origin = displayBounds.origin + menuBarFrame.size.width = items.lazy + .filter { item in + (try? item.attribute(.enabled)) == true + } + .reduce(into: 0) { width, item in + if let frame: CGRect = try? item.attribute(.frame) { + width += frame.width + } + } if menuBarFrame.width == .zero { - return menuBarFrame + return nil } - menuBarFrame.size.width += 15 // extra width to accomodate menu bar item padding + menuBarFrame.size.width += 15 // extra width to accomodate menu padding return menuBarFrame } diff --git a/Ice/MenuBarAppearance/MenuBarOverlayPanel.swift b/Ice/MenuBarAppearance/MenuBarOverlayPanel.swift index 9541b815..fb6187ac 100644 --- a/Ice/MenuBarAppearance/MenuBarOverlayPanel.swift +++ b/Ice/MenuBarAppearance/MenuBarOverlayPanel.swift @@ -171,7 +171,7 @@ class MenuBarOverlayPanel: NSPanel { while true { try Task.checkCancellation() guard - let latestFrame = try? appState.menuBarManager.getApplicationMenuFrame(for: displayID), + let latestFrame = appState.menuBarManager.getApplicationMenuFrame(for: displayID), latestFrame != self.applicationMenuFrame else { try await Task.sleep(for: .milliseconds(1)) @@ -278,7 +278,7 @@ class MenuBarOverlayPanel: NSPanel { return nil } let owningDisplay = owningScreen.displayID - guard AccessibilityMenuBar.hasValidMenuBar(for: owningDisplay) else { + guard appState.menuBarManager.hasValidMenuBar(for: owningDisplay) else { Logger.overlayPanel.notice("No valid menu bar found. \(actionMessage)") return nil } @@ -293,12 +293,7 @@ class MenuBarOverlayPanel: NSPanel { else { return } - do { - applicationMenuFrame = try menuBarManager.getApplicationMenuFrame(for: display) - } catch { - applicationMenuFrame = nil - throw error - } + applicationMenuFrame = menuBarManager.getApplicationMenuFrame(for: display) } /// Stores the area of the desktop wallpaper that is under the menu bar