diff --git a/src/logic/ATShortcut.swift b/src/logic/ATShortcut.swift index 40ada127..00983f39 100644 --- a/src/logic/ATShortcut.swift +++ b/src/logic/ATShortcut.swift @@ -57,7 +57,7 @@ class ATShortcut { if triggerPhase == .down && (!App.app.appIsBeingUsed || index == nil || index == App.app.shortcutIndex) { return true } - if triggerPhase == .up && App.app.appIsBeingUsed && (index == nil || index == App.app.shortcutIndex) && Preferences.shortcutStyle[App.app.shortcutIndex] == .focusOnRelease { + if triggerPhase == .up && App.app.appIsBeingUsed && (index == nil || index == App.app.shortcutIndex) && !App.app.forceDoNothingOnRelease && Preferences.shortcutStyle[App.app.shortcutIndex] == .focusOnRelease { return true } } @@ -83,7 +83,7 @@ class ATShortcut { // Another issue is events being dropped by macOS, which we never receive // Knowing this, we handle these edge-cases by double checking if holdShortcut is UP, when any shortcut state is UP // If it is, then we trigger the holdShortcut action - if App.app.appIsBeingUsed && Preferences.shortcutStyle[App.app.shortcutIndex] == .focusOnRelease { + if App.app.appIsBeingUsed && !App.app.forceDoNothingOnRelease && Preferences.shortcutStyle[App.app.shortcutIndex] == .focusOnRelease { if let currentHoldShortcut = ControlsTab.shortcuts[Preferences.indexToName("holdShortcut", App.app.shortcutIndex)], id == currentHoldShortcut.id { let currentModifiers = cocoaToCarbonFlags(ModifierFlags.current) diff --git a/src/logic/events/CliEvents.swift b/src/logic/events/CliEvents.swift index cc897772..bc4e77d6 100644 --- a/src/logic/events/CliEvents.swift +++ b/src/logic/events/CliEvents.swift @@ -38,28 +38,56 @@ class CliServer { static func executeCommandAndSendReponse(_ rawValue: String) -> Codable { var output: Codable = "" DispatchQueue.main.sync { - if rawValue == "--list" { - output = JsonOutput(windows: Windows.list - .filter { !$0.isWindowlessApp } - .map { JsonWindow(id: $0.cgWindowId, title: $0.title) } - ) - return - } - if rawValue.hasPrefix("--focus=") { - if let id = CGWindowID(rawValue.dropFirst("--focus=".count)), - let window = (Windows.list.first { $0.cgWindowId == id }) { - window.focus() - output = noOutput - return - } - } - output = error + output = executeCommandAndSendReponse_(rawValue) } return output } + + private static func executeCommandAndSendReponse_(_ rawValue: String) -> Codable { + if rawValue == "--list" { + return JsonWindowList(windows: Windows.list + .filter { !$0.isWindowlessApp } + .map { JsonWindow(id: $0.cgWindowId, title: $0.title) } + ) + } + if rawValue == "--detailed-list" { + return JsonWindowFullList(windows: Windows.list + .filter { !$0.isWindowlessApp } + .map { + JsonWindowFull( + id: $0.cgWindowId, + title: $0.title, + appName: $0.application.localizedName, + appBundleId: $0.application.bundleIdentifier, + spaceIndexes: $0.spaceIndexes, + lastFocusOrder: $0.lastFocusOrder, + creationOrder: $0.creationOrder, + isTabbed: $0.isTabbed, + isHidden: $0.isHidden, + isFullscreen: $0.isFullscreen, + isMinimized: $0.isMinimized, + isOnAllSpaces: $0.isOnAllSpaces, + position: $0.position, + size: $0.size + ) + } + ) + } + if rawValue.hasPrefix("--focus="), + let id = CGWindowID(rawValue.dropFirst("--focus=".count)), let window = (Windows.list.first { $0.cgWindowId == id }) { + window.focus() + return noOutput + } + if rawValue.hasPrefix("--show="), + let shortcutIndex = Int(rawValue.dropFirst("--show=".count)), (0...3).contains(shortcutIndex) { + App.app.showUi(shortcutIndex) + return noOutput + } + return error + } } -struct JsonOutput: Codable { +struct JsonWindowList: Codable { var windows: [JsonWindow] } @@ -68,11 +96,33 @@ struct JsonWindow: Codable { var title: String } +struct JsonWindowFullList: Codable { + var windows: [JsonWindowFull] +} + +struct JsonWindowFull: Codable { + var id: CGWindowID? + var title: String + // -- additional properties + var appName: String? + var appBundleId: String? + var spaceIndexes: [SpaceIndex] + var lastFocusOrder: Int + var creationOrder: Int + var isTabbed: Bool + var isHidden: Bool + var isFullscreen: Bool + var isMinimized: Bool + var isOnAllSpaces: Bool + var position: CGPoint? + var size: CGSize? +} + class CliClient { static func detectCommand() -> String? { let args = CommandLine.arguments if args.count == 2 && !args[1].starts(with: "--logs=") { - if args[1] == "--list" || args[1].hasPrefix("--focus=") { + if args[1] == "--list" || args[1] == "--detailed-list" || args[1].hasPrefix("--focus=") || args[1].hasPrefix("--show=") { return args[1] } } diff --git a/src/logic/events/TrackpadEvents.swift b/src/logic/events/TrackpadEvents.swift index 1fa9325a..ea05e9e1 100644 --- a/src/logic/events/TrackpadEvents.swift +++ b/src/logic/events/TrackpadEvents.swift @@ -86,7 +86,7 @@ class GestureDetector { static func checkForFingersUp(_ touches: Set, _ requiredFingers: Int) -> Bool { if App.app.appIsBeingUsed && touches.count < requiredFingers && App.app.shortcutIndex == Preferences.gestureIndex - && Preferences.shortcutStyle[App.app.shortcutIndex] == .focusOnRelease { + && !App.app.forceDoNothingOnRelease && Preferences.shortcutStyle[App.app.shortcutIndex] == .focusOnRelease { DispatchQueue.main.async { App.app.focusTarget() } return true } @@ -140,7 +140,7 @@ class TriggerSwipeDetector { let horizontal = Preferences.nextWindowGesture.isHorizontal() if (updateSwipeStillPossible(horizontal ? absY : absX) && (horizontal ? absX : absY) >= MIN_SWIPE_DISTANCE) { reset() - DispatchQueue.main.async { App.app.showUiOrCycleSelection(Preferences.gestureIndex) } + DispatchQueue.main.async { App.app.showUiOrCycleSelection(Preferences.gestureIndex, false) } return true } return false diff --git a/src/ui/App.swift b/src/ui/App.swift index 27e323c6..ea9d5804 100644 --- a/src/ui/App.swift +++ b/src/ui/App.swift @@ -23,6 +23,7 @@ class App: AppCenterApplication { var permissionsWindow: PermissionsWindow! var appIsBeingUsed = false var shortcutIndex = 0 + var forceDoNothingOnRelease = false private var feedbackWindow: FeedbackWindow! private var isFirstSummon = true private var isVeryFirstSummon = true @@ -73,6 +74,7 @@ class App: AppCenterApplication { guard appIsBeingUsed else { return } // already hidden appIsBeingUsed = false isFirstSummon = true + forceDoNothingOnRelease = false MouseEvents.toggle(false) hideThumbnailPanelWithoutChangingKeyWindow() if !keepPreview { @@ -156,8 +158,12 @@ class App: AppCenterApplication { } } - @objc func showUi() { - showUiOrCycleSelection(0) + func showUi(_ shortcutIndex: Int) { + showUiOrCycleSelection(shortcutIndex, true) + } + + @objc func showUiFromShortcut0() { + showUi(0) } @objc func showAboutTab() { @@ -226,7 +232,8 @@ class App: AppCenterApplication { Applications.refreshBadgesAsync() } - func showUiOrCycleSelection(_ shortcutIndex: Int) { + func showUiOrCycleSelection(_ shortcutIndex: Int, _ forceDoNothingOnRelease_: Bool) { + forceDoNothingOnRelease = forceDoNothingOnRelease_ Logger.debug(shortcutIndex, self.shortcutIndex, isFirstSummon) App.app.appIsBeingUsed = true if isFirstSummon || shortcutIndex != self.shortcutIndex { diff --git a/src/ui/Menubar.swift b/src/ui/Menubar.swift index 0cda03b1..a2aee815 100644 --- a/src/ui/Menubar.swift +++ b/src/ui/Menubar.swift @@ -19,7 +19,7 @@ class Menubar { menu.addItem(NSMenuItem.separator()) menu.addItem( withTitle: NSLocalizedString("Show", comment: "Menubar option"), - action: #selector(App.app.showUi), + action: #selector(App.app.showUiFromShortcut0), keyEquivalent: "") menu.addItem( withTitle: NSLocalizedString("Preferences…", comment: "Menubar option"), @@ -67,7 +67,7 @@ class Menubar { @objc static func statusItemOnClick() { // NSApp.currentEvent == nil if the icon is "clicked" through VoiceOver if let type = NSApp.currentEvent?.type, type != .leftMouseDown { - App.app.showUi() + App.app.showUiFromShortcut0() } else { statusItem.popUpMenu(Menubar.menu) } diff --git a/src/ui/preferences-window/tabs/controls/ControlsTab.swift b/src/ui/preferences-window/tabs/controls/ControlsTab.swift index 63099315..2c597ef4 100644 --- a/src/ui/preferences-window/tabs/controls/ControlsTab.swift +++ b/src/ui/preferences-window/tabs/controls/ControlsTab.swift @@ -9,9 +9,9 @@ class ControlsTab { "holdShortcut2": { App.app.focusTarget() }, "holdShortcut3": { App.app.focusTarget() }, "focusWindowShortcut": { App.app.focusTarget() }, - "nextWindowShortcut": { App.app.showUiOrCycleSelection(0) }, - "nextWindowShortcut2": { App.app.showUiOrCycleSelection(1) }, - "nextWindowShortcut3": { App.app.showUiOrCycleSelection(2) }, + "nextWindowShortcut": { App.app.showUiOrCycleSelection(0, false) }, + "nextWindowShortcut2": { App.app.showUiOrCycleSelection(1, false) }, + "nextWindowShortcut3": { App.app.showUiOrCycleSelection(2, false) }, "previousWindowShortcut": { App.app.previousWindowShortcutWithRepeatingKey() }, "→": { App.app.cycleSelection(.right) }, "←": { App.app.cycleSelection(.left) }, diff --git a/unit-tests/Mocks.swift b/unit-tests/Mocks.swift index ec58dfeb..eb05f608 100644 --- a/unit-tests/Mocks.swift +++ b/unit-tests/Mocks.swift @@ -4,6 +4,7 @@ class App { class AppMock { var appIsBeingUsed = false var shortcutIndex = 0 + var forceDoNothingOnRelease = false } static let app = AppMock() }