From 4c6d3f8ed2d53f5881ca525750650066c55c5c9f Mon Sep 17 00:00:00 2001 From: himura467 Date: Fri, 10 Oct 2025 11:01:41 +0900 Subject: [PATCH] macos: add `toggle_background_opacity` keybind action --- include/ghostty.h | 1 + .../QuickTerminalController.swift | 10 +++++++- .../Terminal/BaseTerminalController.swift | 19 +++++++++++++++ .../Terminal/TerminalController.swift | 8 +++++++ .../Window Styles/TerminalWindow.swift | 3 +++ macos/Sources/Ghostty/Ghostty.App.swift | 24 +++++++++++++++++++ src/Surface.zig | 6 +++++ src/apprt/action.zig | 4 ++++ src/input/Binding.zig | 11 +++++++++ src/input/command.zig | 6 +++++ 10 files changed, 91 insertions(+), 1 deletion(-) diff --git a/include/ghostty.h b/include/ghostty.h index b0395b89e..47db34e71 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -803,6 +803,7 @@ typedef enum { GHOSTTY_ACTION_TOGGLE_QUICK_TERMINAL, GHOSTTY_ACTION_TOGGLE_COMMAND_PALETTE, GHOSTTY_ACTION_TOGGLE_VISIBILITY, + GHOSTTY_ACTION_TOGGLE_BACKGROUND_OPACITY, GHOSTTY_ACTION_MOVE_TAB, GHOSTTY_ACTION_GOTO_TAB, GHOSTTY_ACTION_GOTO_SPLIT, diff --git a/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift b/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift index 4377b6510..d2db44d2d 100644 --- a/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift +++ b/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift @@ -313,6 +313,13 @@ class QuickTerminalController: BaseTerminalController { animateOut() } + override func toggleBackgroundOpacity() { + super.toggleBackgroundOpacity() + + // Sync the window appearance with the new opacity state + syncAppearance() + } + // MARK: Methods func toggle() { @@ -608,7 +615,8 @@ class QuickTerminalController: BaseTerminalController { guard window.isVisible else { return } // If we have window transparency then set it transparent. Otherwise set it opaque. - if (self.derivedConfig.backgroundOpacity < 1) { + // Also check if the user has overridden transparency to be fully opaque. + if !isBackgroundOpaque && self.derivedConfig.backgroundOpacity < 1 { window.isOpaque = false // This is weird, but we don't use ".clear" because this creates a look that diff --git a/macos/Sources/Features/Terminal/BaseTerminalController.swift b/macos/Sources/Features/Terminal/BaseTerminalController.swift index 98f1bcbf8..892bef555 100644 --- a/macos/Sources/Features/Terminal/BaseTerminalController.swift +++ b/macos/Sources/Features/Terminal/BaseTerminalController.swift @@ -78,6 +78,9 @@ class BaseTerminalController: NSWindowController, /// The configuration derived from the Ghostty config so we don't need to rely on references. private var derivedConfig: DerivedConfig + /// Track whether background is forced opaque (true) or using config transparency (false) + var isBackgroundOpaque: Bool = false + /// The cancellables related to our focused surface. private var focusedSurfaceCancellables: Set = [] @@ -812,6 +815,22 @@ class BaseTerminalController: NSWindowController, } } + // MARK: Background Opacity + + /// Toggle the background opacity between transparent and opaque states. + /// If the configured background-opacity is already opaque (>= 1), this resets + /// the override flag to false so that future config changes take effect. + /// Subclasses should override this to sync their appearance after toggling. + func toggleBackgroundOpacity() { + // If config is already opaque, just ensure override is disabled + if ghostty.config.backgroundOpacity >= 1 { + isBackgroundOpaque = false + } else { + // Otherwise toggle between transparent and opaque + isBackgroundOpaque.toggle() + } + } + // MARK: Fullscreen /// Toggle fullscreen for the given mode. diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index a980723ba..29b856cdb 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -176,6 +176,14 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr syncAppearance(focusedSurface.derivedConfig) } + override func toggleBackgroundOpacity() { + super.toggleBackgroundOpacity() + + // Sync the window appearance with the new opacity state + guard let focusedSurface else { return } + syncAppearance(focusedSurface.derivedConfig) + } + // MARK: Terminal Creation /// Returns all the available terminal controllers present in the app currently. diff --git a/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift index 0c0ac0646..730cdea65 100644 --- a/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift +++ b/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift @@ -469,7 +469,10 @@ class TerminalWindow: NSWindow { // Window transparency only takes effect if our window is not native fullscreen. // In native fullscreen we disable transparency/opacity because the background // becomes gray and widgets show through. + // Also check if the user has overridden transparency to be fully opaque. + let forceOpaque = terminalController?.isBackgroundOpaque ?? false if !styleMask.contains(.fullScreen) && + !forceOpaque && surfaceConfig.backgroundOpacity < 1 { isOpaque = false diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 2cd0a362a..4e9d039d4 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -573,6 +573,9 @@ extension Ghostty { case GHOSTTY_ACTION_TOGGLE_VISIBILITY: toggleVisibility(app, target: target) + case GHOSTTY_ACTION_TOGGLE_BACKGROUND_OPACITY: + toggleBackgroundOpacity(app, target: target) + case GHOSTTY_ACTION_KEY_SEQUENCE: keySequence(app, target: target, v: action.action.key_sequence) @@ -1375,6 +1378,27 @@ extension Ghostty { } } + private static func toggleBackgroundOpacity( + _ app: ghostty_app_t, + target: ghostty_target_s + ) { + switch (target.tag) { + case GHOSTTY_TARGET_APP: + Ghostty.logger.warning("toggle background opacity does nothing with an app target") + return + + case GHOSTTY_TARGET_SURFACE: + guard let surface = target.target.surface, + let surfaceView = self.surfaceView(from: surface), + let controller = surfaceView.window?.windowController as? BaseTerminalController else { return } + + controller.toggleBackgroundOpacity() + + default: + assertionFailure() + } + } + private static func toggleSecureInput( _ app: ghostty_app_t, target: ghostty_target_s, diff --git a/src/Surface.zig b/src/Surface.zig index 4786e0b86..d84e786f3 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -5518,6 +5518,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool {}, ), + .toggle_background_opacity => return try self.rt_app.performAction( + .{ .surface = self }, + .toggle_background_opacity, + {}, + ), + .show_on_screen_keyboard => return try self.rt_app.performAction( .{ .surface = self }, .show_on_screen_keyboard, diff --git a/src/apprt/action.zig b/src/apprt/action.zig index af1c22552..7b9e9d222 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -115,6 +115,9 @@ pub const Action = union(Key) { /// Toggle the visibility of all Ghostty terminal windows. toggle_visibility, + /// Toggle the window background opacity. This currently only works on macOS. + toggle_background_opacity, + /// Moves a tab by a relative offset. /// /// Adjusts the tab position based on `offset` (e.g., -1 for left, +1 @@ -335,6 +338,7 @@ pub const Action = union(Key) { toggle_quick_terminal, toggle_command_palette, toggle_visibility, + toggle_background_opacity, move_tab, goto_tab, goto_split, diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 31672bc1a..9f3ad8a2a 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -755,6 +755,16 @@ pub const Action = union(enum) { /// Only implemented on macOS. toggle_visibility, + /// Toggle the window background opacity between transparent and opaque. + /// + /// This does nothing when `background-opacity` is set to 1 or above. + /// + /// When `background-opacity` is less than 1, this action will either make + /// the window transparent or not depending on its current transparency state. + /// + /// Only implemented on macOS. + toggle_background_opacity, + /// Check for updates. /// /// Only implemented on macOS. @@ -1240,6 +1250,7 @@ pub const Action = union(enum) { .toggle_secure_input, .toggle_mouse_reporting, .toggle_command_palette, + .toggle_background_opacity, .show_on_screen_keyboard, .reset_window_size, .crash, diff --git a/src/input/command.zig b/src/input/command.zig index a377effa2..d5daafd7d 100644 --- a/src/input/command.zig +++ b/src/input/command.zig @@ -618,6 +618,12 @@ fn actionCommands(action: Action.Key) []const Command { .description = "Toggle whether mouse events are reported to terminal applications.", }}, + .toggle_background_opacity => comptime &.{.{ + .action = .toggle_background_opacity, + .title = "Toggle Background Opacity", + .description = "Toggle the window background between transparent and opaque.", + }}, + .check_for_updates => comptime &.{.{ .action = .check_for_updates, .title = "Check for Updates",