diff --git a/macos/Sources/AppDelegate.swift b/macos/Sources/AppDelegate.swift index 4b0398767..44e11ef28 100644 --- a/macos/Sources/AppDelegate.swift +++ b/macos/Sources/AppDelegate.swift @@ -21,6 +21,7 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, UNUserNoti @IBOutlet private var menuSplitVertical: NSMenuItem? @IBOutlet private var menuClose: NSMenuItem? @IBOutlet private var menuCloseWindow: NSMenuItem? + @IBOutlet private var menuCloseAllWindows: NSMenuItem? @IBOutlet private var menuCopy: NSMenuItem? @IBOutlet private var menuPaste: NSMenuItem? @@ -218,6 +219,7 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, UNUserNoti syncMenuShortcut(action: "new_tab", menuItem: self.menuNewTab) syncMenuShortcut(action: "close_surface", menuItem: self.menuClose) syncMenuShortcut(action: "close_window", menuItem: self.menuCloseWindow) + syncMenuShortcut(action: "close_all_windows", menuItem: self.menuCloseAllWindows) syncMenuShortcut(action: "new_split:right", menuItem: self.menuSplitHorizontal) syncMenuShortcut(action: "new_split:down", menuItem: self.menuSplitVertical) @@ -360,6 +362,11 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, UNUserNoti NSApp.activate(ignoringOtherApps: true) } + @IBAction func closeAllWindows(_ sender: Any?) { + terminalManager.closeAllWindows() + AboutController.shared.hide() + } + @IBAction func showAbout(_ sender: Any?) { AboutController.shared.show() } diff --git a/macos/Sources/Features/About/AboutController.swift b/macos/Sources/Features/About/AboutController.swift index 2c6882eea..897e2918b 100644 --- a/macos/Sources/Features/About/AboutController.swift +++ b/macos/Sources/Features/About/AboutController.swift @@ -16,8 +16,11 @@ class AboutController: NSWindowController, NSWindowDelegate { // MARK: - Functions func show() { - guard let window = window else { return } - window.makeKeyAndOrderFront(nil) + window?.makeKeyAndOrderFront(nil) + } + + func hide() { + window?.close() } //MARK: - First Responder diff --git a/macos/Sources/Features/Terminal/TerminalManager.swift b/macos/Sources/Features/Terminal/TerminalManager.swift index c532285ef..24e742971 100644 --- a/macos/Sources/Features/Terminal/TerminalManager.swift +++ b/macos/Sources/Features/Terminal/TerminalManager.swift @@ -189,6 +189,52 @@ class TerminalManager { } } + /// Close all windows, asking for confirmation if necessary. + func closeAllWindows() { + var needsConfirm: Bool = false + for w in self.windows { + if (w.controller.surfaceTree?.needsConfirmQuit() ?? false) { + needsConfirm = true + break + } + } + + if (!needsConfirm) { + for w in self.windows { + w.controller.close() + } + + return + } + + // If we don't have a main window, we just close all windows because + // we have no window to show the modal on top of. I'm sure there's a way + // to do an app-level alert but I don't know how and this case should never + // really happen. + guard let alertWindow = mainWindow?.controller.window else { + for w in self.windows { + w.controller.close() + } + + return + } + + // If we need confirmation by any, show one confirmation for all windows + let alert = NSAlert() + alert.messageText = "Close All Windows?" + alert.informativeText = "All terminal sessions will be terminated." + alert.addButton(withTitle: "Close All Windows") + alert.addButton(withTitle: "Cancel") + alert.alertStyle = .warning + alert.beginSheetModal(for: alertWindow, completionHandler: { response in + if (response == .alertFirstButtonReturn) { + for w in self.windows { + w.controller.close() + } + } + }) + } + /// Relabels all the tabs with the proper keyboard shortcut. func relabelAllTabs() { for w in windows { diff --git a/macos/Sources/MainMenu.xib b/macos/Sources/MainMenu.xib index 69ee9fe38..1b6c88fa3 100644 --- a/macos/Sources/MainMenu.xib +++ b/macos/Sources/MainMenu.xib @@ -15,6 +15,7 @@ + @@ -134,6 +135,12 @@ + + + + + + diff --git a/src/Surface.zig b/src/Surface.zig index f79818738..cb781fe0a 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -2885,6 +2885,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool .close_window => try self.app.closeSurface(self), + .close_all_windows => { + if (@hasDecl(apprt.Surface, "closeAllWindows")) { + self.rt_surface.closeAllWindows(); + } else log.warn("runtime doesn't implement closeAllWindows", .{}); + }, + .quit => try self.app.setQuit(), } diff --git a/src/config/Config.zig b/src/config/Config.zig index 1275c8a82..5c4109223 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1183,6 +1183,11 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config { .{ .key = .w, .mods = .{ .super = true, .shift = true } }, .{ .close_window = {} }, ); + try result.keybind.set.put( + alloc, + .{ .key = .w, .mods = .{ .super = true, .shift = true, .alt = true } }, + .{ .close_all_windows = {} }, + ); try result.keybind.set.put( alloc, .{ .key = .t, .mods = .{ .super = true } }, diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 19bdcdc27..95449b60a 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -221,12 +221,18 @@ pub const Action = union(enum) { reload_config: void, /// Close the current "surface", whether that is a window, tab, split, - /// etc. This only closes ONE surface. + /// etc. This only closes ONE surface. This will trigger close confirmation + /// as configured. close_surface: void, /// Close the window, regardless of how many tabs or splits there may be. + /// This will trigger close confirmation as configured. close_window: void, + /// Close all windows. This will trigger close confirmation as configured. + /// This only works for macOS currently. + close_all_windows: void, + /// Toggle fullscreen mode of window. toggle_fullscreen: void,