diff --git a/src/logic/Applications.swift b/src/logic/Applications.swift index 11027cfa..663feeaf 100644 --- a/src/logic/Applications.swift +++ b/src/logic/Applications.swift @@ -55,7 +55,7 @@ class Applications { for runningApp in runningApps { Applications.list.removeAll(where: { $0.runningApplication.isEqual(runningApp) }) Windows.list.enumerated().forEach { (index, window) in - if window.application.runningApplication.isEqual(runningApp) && index <= Windows.focusedWindowIndex { + if window.application.runningApplication.isEqual(runningApp) && index < Windows.focusedWindowIndex { windowsOnTheLeftOfFocusedWindow += 1 } } diff --git a/src/logic/Preferences.swift b/src/logic/Preferences.swift index 012c43d6..68c957a3 100644 --- a/src/logic/Preferences.swift +++ b/src/logic/Preferences.swift @@ -17,6 +17,7 @@ class Preferences { "previousWindowShortcut": "⇧⇥", "cancelShortcut": "⎋", "closeWindowShortcut": "W", + "quitAppShortcut": "Q", "arrowKeysEnabled": true, "mouseHoverEnabled": true, "showMinimizedWindows": true, @@ -53,6 +54,7 @@ class Preferences { static var previousWindowShortcut: String { defaults.string(forKey: "previousWindowShortcut")! } static var cancelShortcut: String { defaults.string(forKey: "cancelShortcut")! } static var closeWindowShortcut: String { defaults.string(forKey: "closeWindowShortcut")! } + static var quitAppShortcut: String { defaults.string(forKey: "quitAppShortcut")! } static var arrowKeysEnabled: Bool { defaults.bool(forKey: "arrowKeysEnabled") } static var mouseHoverEnabled: Bool { defaults.bool(forKey: "mouseHoverEnabled") } static var showMinimizedWindows: Bool { defaults.bool(forKey: "showMinimizedWindows") } diff --git a/src/logic/Window.swift b/src/logic/Window.swift index ef84d15d..b64e6ab1 100644 --- a/src/logic/Window.swift +++ b/src/logic/Window.swift @@ -70,6 +70,12 @@ class Window { } } + func quitApp() { + DispatchQueues.accessibilityCommands.async { [weak self] in + self?.application.runningApplication.terminate() + } + } + func focus() { // macOS bug: when switching to a System Preferences window in another space, it switches to that space, // but quickly switches back to another window in that space diff --git a/src/ui/App.swift b/src/ui/App.swift index 612ec17f..2e50f27f 100644 --- a/src/ui/App.swift +++ b/src/ui/App.swift @@ -100,6 +100,10 @@ class App: NSApplication, NSApplicationDelegate { Windows.focusedWindow()?.close() } + func quitSelectedApp() { + Windows.focusedWindow()?.quitApp() + } + func focusTarget() { debugPrint("focusTarget") focusSelectedWindow(Windows.focusedWindow()) diff --git a/src/ui/preferences-window/tabs/GeneralTab.swift b/src/ui/preferences-window/tabs/GeneralTab.swift index 124100ea..b81b0d83 100644 --- a/src/ui/preferences-window/tabs/GeneralTab.swift +++ b/src/ui/preferences-window/tabs/GeneralTab.swift @@ -18,6 +18,7 @@ class GeneralTab: NSViewController, PreferencePane { let previousWindowShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Select previous window", comment: ""), "previousWindowShortcut", Preferences.previousWindowShortcut, labelPosition: .right) let cancelShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Cancel and hide", comment: ""), "cancelShortcut", Preferences.cancelShortcut, labelPosition: .right) let closeWindowShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Close window", comment: ""), "closeWindowShortcut", Preferences.closeWindowShortcut, labelPosition: .right) + let quitAppShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Quit app", comment: ""), "quitAppShortcut", Preferences.quitAppShortcut, labelPosition: .right) let enableArrows = LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Arrow keys", comment: ""), "arrowKeysEnabled", extraAction: GeneralTab.arrowKeysEnabledCallback, labelPosition: .right) let enableMouse = LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Mouse hover", comment: ""), "mouseHoverEnabled", labelPosition: .right) let holdAndPress = StackView(holdShortcut) @@ -28,7 +29,7 @@ class GeneralTab: NSViewController, PreferencePane { let screensToShow = dropdown("screensToShow", ScreensToShowPreference.allCases) let showMinimizedWindows = StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Minimized", comment: ""), "showMinimizedWindows", labelPosition: .right)) let showHiddenWindows = StackView(LabelAndControl.makeLabelWithCheckbox(NSLocalizedString("Hidden", comment: ""), "showHiddenWindows", labelPosition: .right)) - let shortcuts = StackView([nextWindowShortcut, previousWindowShortcut, cancelShortcut, closeWindowShortcut].map { (view: [NSView]) in StackView(view) }, .vertical) + let shortcuts = StackView([nextWindowShortcut, previousWindowShortcut, cancelShortcut, closeWindowShortcut, quitAppShortcut].map { (view: [NSView]) in StackView(view) }, .vertical) let toShowDropdowns = StackView([appsToShow, spacesToShow, screensToShow], .vertical) let toShowCheckboxes = StackView([showMinimizedWindows, showHiddenWindows], .vertical) let toShowExplanations = LabelAndControl.makeLabel(NSLocalizedString("Show the following windows:", comment: "")) @@ -44,7 +45,8 @@ class GeneralTab: NSViewController, PreferencePane { [1, 2, 3].forEach { grid.row(at: $0).topPadding = GridView.interPadding } grid.fit() - GeneralTab.shortcutsDependentOnHoldShortcut.append(contentsOf: [enableArrows[0] as! NSControl] + [nextWindowShortcut, previousWindowShortcut, cancelShortcut, closeWindowShortcut].map { $0[0] as! NSControl }) + GeneralTab.shortcutsDependentOnHoldShortcut.append(contentsOf: [enableArrows[0] as! NSControl] + + [nextWindowShortcut, previousWindowShortcut, cancelShortcut, closeWindowShortcut, quitAppShortcut].map { $0[0] as! NSControl }) GeneralTab.arrowKeysEnabledCallback(enableArrows[0] as! NSControl) startAtLoginCallback(startAtLogin[1] as! NSControl) @@ -59,9 +61,10 @@ class GeneralTab: NSViewController, PreferencePane { private static func addShortcut(_ fn: @escaping () -> Void, _ type: KeyEventType, _ shortcut: Shortcut, _ controlId: String) { removeShortcutIfExists(controlId, type) // remove the previous shortcut shortcutActions[controlId] = ShortcutAction(shortcut: shortcut, actionHandler: { _ in - let shortcutsWithUiShown = ["previousWindowShortcut", "nextWindowShortcut", "closeWindowShortcut"].contains(controlId) - App.app.uiWorkShouldBeDone = shortcutsWithUiShown - if shortcutsWithUiShown { + let isShortcutInitiatingTheApp = ["previousWindowShortcut", "nextWindowShortcut"].contains(controlId) + let isShortcutWithUiShown = ["closeWindowShortcut", "quitAppShortcut"].contains(controlId) + App.app.uiWorkShouldBeDone = isShortcutInitiatingTheApp || isShortcutWithUiShown + if isShortcutInitiatingTheApp { App.app.appIsBeingUsed = true DispatchQueue.main.async { () -> () in fn() } } else if App.app.appIsBeingUsed { @@ -98,6 +101,8 @@ class GeneralTab: NSViewController, PreferencePane { return { App.app.hideUi() } } else if controlId == "closeWindowShortcut" { return { App.app.closeSelectedWindow() } + } else if controlId == "quitAppShortcut" { + return { App.app.quitSelectedApp() } } return {} }