From c6d3d5f847a0b42350da56828adf7cc28f03ad45 Mon Sep 17 00:00:00 2001 From: Kushal Goyal Date: Tue, 17 Feb 2026 02:20:32 +0530 Subject: [PATCH 1/3] input: add prompt_window_title action Add a new keybinding action 'prompt_window_title' that allows users to rename the current window via a pop-up prompt, similar to the existing 'prompt_tab_title' and 'prompt_surface_title' actions. Changes: - Add 'window' variant to PromptTitle enum in apprt/action.zig - Add 'prompt_window_title' to the binding Action enum - Add handler in Surface.zig to dispatch the action - Add command palette entry ('Change Window Title...') - Add GHOSTTY_PROMPT_TITLE_WINDOW to C API header - Add macOS implementation (promptWindowTitle on BaseTerminalController) - Add GTK stub (returns false, not yet supported) Closes #10469 --- include/ghostty.h | 1 + .../Terminal/BaseTerminalController.swift | 31 +++++++++++++++++++ macos/Sources/Ghostty/Ghostty.Action.swift | 3 ++ macos/Sources/Ghostty/Ghostty.App.swift | 23 ++++++++++++++ src/Surface.zig | 6 ++++ src/apprt/action.zig | 3 +- src/apprt/gtk/class/application.zig | 4 +++ src/input/Binding.zig | 6 ++++ src/input/command.zig | 6 ++++ 9 files changed, 82 insertions(+), 1 deletion(-) diff --git a/include/ghostty.h b/include/ghostty.h index b32cc9856d1..471c4904a32 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -630,6 +630,7 @@ typedef struct { typedef enum { GHOSTTY_PROMPT_TITLE_SURFACE, GHOSTTY_PROMPT_TITLE_TAB, + GHOSTTY_PROMPT_TITLE_WINDOW, } ghostty_action_prompt_title_e; // apprt.action.Pwd.C diff --git a/macos/Sources/Features/Terminal/BaseTerminalController.swift b/macos/Sources/Features/Terminal/BaseTerminalController.swift index b739e9ed1c9..eb19f3d5167 100644 --- a/macos/Sources/Features/Terminal/BaseTerminalController.swift +++ b/macos/Sources/Features/Terminal/BaseTerminalController.swift @@ -378,6 +378,37 @@ class BaseTerminalController: NSWindowController, } } + /// Prompt the user to change the window title. + func promptWindowTitle() { + guard let window else { return } + + let alert = NSAlert() + alert.messageText = "Change Window Title" + alert.informativeText = "Leave blank to restore the default." + alert.alertStyle = .informational + + let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 250, height: 24)) + textField.stringValue = titleOverride ?? window.title + alert.accessoryView = textField + + alert.addButton(withTitle: "OK") + alert.addButton(withTitle: "Cancel") + + alert.window.initialFirstResponder = textField + + alert.beginSheetModal(for: window) { [weak self] response in + guard let self else { return } + guard response == .alertFirstButtonReturn else { return } + + let newTitle = textField.stringValue + if newTitle.isEmpty { + self.titleOverride = nil + } else { + self.titleOverride = newTitle + } + } + } + /// Close a surface from a view. func closeSurface( _ view: Ghostty.SurfaceView, diff --git a/macos/Sources/Ghostty/Ghostty.Action.swift b/macos/Sources/Ghostty/Ghostty.Action.swift index 91f1491dd16..9aaf7390855 100644 --- a/macos/Sources/Ghostty/Ghostty.Action.swift +++ b/macos/Sources/Ghostty/Ghostty.Action.swift @@ -131,11 +131,14 @@ extension Ghostty.Action { enum PromptTitle { case surface case tab + case window init(_ c: ghostty_action_prompt_title_e) { switch c { case GHOSTTY_PROMPT_TITLE_TAB: self = .tab + case GHOSTTY_PROMPT_TITLE_WINDOW: + self = .window default: self = .surface } diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index e3441257fdd..1db3ae05bc6 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -1572,6 +1572,29 @@ extension Ghostty { assertionFailure() return false } + + case .window: + switch (target.tag) { + case GHOSTTY_TARGET_APP: + guard let window = NSApp.mainWindow ?? NSApp.keyWindow, + let controller = window.windowController as? BaseTerminalController + else { return false } + controller.promptWindowTitle() + return true + + case GHOSTTY_TARGET_SURFACE: + guard let surface = target.target.surface else { return false } + guard let surfaceView = self.surfaceView(from: surface) else { return false } + guard let window = surfaceView.window, + let controller = window.windowController as? BaseTerminalController + else { return false } + controller.promptWindowTitle() + return true + + default: + assertionFailure() + return false + } } } diff --git a/src/Surface.zig b/src/Surface.zig index e5e7d284d10..35484cde1d9 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -5478,6 +5478,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool .tab, ), + .prompt_window_title => return try self.rt_app.performAction( + .{ .surface = self }, + .prompt_title, + .window, + ), + .clear_screen => { // This is a duplicate of some of the logic in termio.clearScreen // but we need to do this here so we can know the answer before diff --git a/src/apprt/action.zig b/src/apprt/action.zig index 1d9ef633c7d..7ca8656ecfe 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -575,10 +575,11 @@ pub const MouseVisibility = enum(c_int) { hidden, }; -/// Whether to prompt for the surface title or tab title. +/// Whether to prompt for the surface title, tab title, or window title. pub const PromptTitle = enum(c_int) { surface, tab, + window, }; pub const MouseOverLink = struct { diff --git a/src/apprt/gtk/class/application.zig b/src/apprt/gtk/class/application.zig index 5030236e53d..5b549a14cd2 100644 --- a/src/apprt/gtk/class/application.zig +++ b/src/apprt/gtk/class/application.zig @@ -2382,6 +2382,10 @@ const Action = struct { }, } }, + .window => { + // GTK does not yet support window title prompting + return false; + }, } } diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 57414d76482..0eb99d0511e 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -580,6 +580,11 @@ pub const Action = union(enum) { /// and persists across focus changes within the tab. prompt_tab_title, + /// Change the title of the current window via a pop-up prompt. The + /// title set via this prompt overrides any title set by the terminal + /// and persists across tab changes within the window. + prompt_window_title, + /// Create a new split in the specified direction. /// /// Valid arguments: @@ -1327,6 +1332,7 @@ pub const Action = union(enum) { .set_font_size, .prompt_surface_title, .prompt_tab_title, + .prompt_window_title, .clear_screen, .select_all, .scroll_to_top, diff --git a/src/input/command.zig b/src/input/command.zig index d6d2b024783..c6a8a2de817 100644 --- a/src/input/command.zig +++ b/src/input/command.zig @@ -450,6 +450,12 @@ fn actionCommands(action: Action.Key) []const Command { .description = "Prompt for a new title for the current tab.", }}, + .prompt_window_title => comptime &.{.{ + .action = .prompt_window_title, + .title = "Change Window Title...", + .description = "Prompt for a new title for the current window.", + }}, + .new_split => comptime &.{ .{ .action = .{ .new_split = .left }, From 18bce2051969bba448f9e201198b6a8b7afbdfeb Mon Sep 17 00:00:00 2001 From: Kushal Goyal Date: Tue, 17 Feb 2026 04:30:12 +0530 Subject: [PATCH 2/3] added unicode ellipsis has been added for terminal and tab titles also after the window one --- src/input/command.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/input/command.zig b/src/input/command.zig index c6a8a2de817..76154fe30d6 100644 --- a/src/input/command.zig +++ b/src/input/command.zig @@ -440,19 +440,19 @@ fn actionCommands(action: Action.Key) []const Command { .prompt_surface_title => comptime &.{.{ .action = .prompt_surface_title, - .title = "Change Terminal Title...", + .title = "Change Terminal Title\u{2026}", .description = "Prompt for a new title for the current terminal.", }}, .prompt_tab_title => comptime &.{.{ .action = .prompt_tab_title, - .title = "Change Tab Title...", + .title = "Change Tab Title\u{2026}", .description = "Prompt for a new title for the current tab.", }}, .prompt_window_title => comptime &.{.{ .action = .prompt_window_title, - .title = "Change Window Title...", + .title = "Change Window Title\u{2026}", .description = "Prompt for a new title for the current window.", }}, From cb90168635b6db7dc758e0ccab0410161d424cfe Mon Sep 17 00:00:00 2001 From: Kushal Goyal Date: Tue, 17 Feb 2026 04:40:49 +0530 Subject: [PATCH 3/3] added unicode ellipse literal instead of escapes --- src/input/command.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/input/command.zig b/src/input/command.zig index 76154fe30d6..66965ed63fc 100644 --- a/src/input/command.zig +++ b/src/input/command.zig @@ -440,19 +440,19 @@ fn actionCommands(action: Action.Key) []const Command { .prompt_surface_title => comptime &.{.{ .action = .prompt_surface_title, - .title = "Change Terminal Title\u{2026}", + .title = "Change Terminal Title…", .description = "Prompt for a new title for the current terminal.", }}, .prompt_tab_title => comptime &.{.{ .action = .prompt_tab_title, - .title = "Change Tab Title\u{2026}", + .title = "Change Tab Title…", .description = "Prompt for a new title for the current tab.", }}, .prompt_window_title => comptime &.{.{ .action = .prompt_window_title, - .title = "Change Window Title\u{2026}", + .title = "Change Window Title…", .description = "Prompt for a new title for the current window.", }},