fix: copy_title_to_clipboard now respects user-overridden title

When a user renames a surface via "Change Terminal Title" and then
uses copy_title_to_clipboard, the raw terminal title was copied
instead of the custom name.

This adds a new `copy_title` apprt action so each platform resolves
the effective title (user override or terminal-set) before copying
to clipboard.

Fixes #10345
This commit is contained in:
Priyanshu
2026-02-13 01:19:15 +05:30
parent 13e8d85a01
commit b4be13ed50
6 changed files with 57 additions and 14 deletions

View File

@@ -904,6 +904,7 @@ typedef enum {
GHOSTTY_ACTION_SEARCH_TOTAL,
GHOSTTY_ACTION_SEARCH_SELECTED,
GHOSTTY_ACTION_READONLY,
GHOSTTY_ACTION_COPY_TITLE,
} ghostty_action_tag_e;
typedef union {

View File

@@ -647,6 +647,8 @@ extension Ghostty {
case GHOSTTY_ACTION_SHOW_CHILD_EXITED:
Ghostty.logger.info("known but unimplemented action action=\(action.tag.rawValue)")
return false
case GHOSTTY_ACTION_COPY_TITLE:
return copyTitle(app, target: target)
default:
Ghostty.logger.warning("unknown action action=\(action.tag.rawValue)")
return false
@@ -1506,6 +1508,25 @@ extension Ghostty {
}
}
private static func copyTitle(
_ app: ghostty_app_t,
target: ghostty_target_s) -> Bool {
switch (target.tag) {
case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return false }
guard let surfaceView = self.surfaceView(from: surface) else { return false }
let title = surfaceView.title
if title.isEmpty { return false }
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.setString(title, forType: .string)
return true
default:
return false
}
}
private static func promptTitle(
_ app: ghostty_app_t,
target: ghostty_target_s,

View File

@@ -5398,20 +5398,11 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
return false;
},
.copy_title_to_clipboard => {
const title = self.rt_surface.getTitle() orelse return false;
if (title.len == 0) return false;
self.rt_surface.setClipboard(.standard, &.{.{
.mime = "text/plain",
.data = title,
}}, false) catch |err| {
log.err("error copying title to clipboard err={}", .{err});
return true;
};
return true;
},
.copy_title_to_clipboard => return try self.rt_app.performAction(
.{ .surface = self },
.copy_title,
{},
),
.paste_from_clipboard => return try self.startClipboardRequest(
.standard,

View File

@@ -330,6 +330,11 @@ pub const Action = union(Key) {
/// The readonly state of the surface has changed.
readonly: Readonly,
/// Copy the effective title of the surface to the clipboard.
/// The effective title is the user-overridden title if set,
/// otherwise the terminal-set title.
copy_title,
/// Sync with: ghostty_action_tag_e
pub const Key = enum(c_int) {
quit,
@@ -395,6 +400,7 @@ pub const Action = union(Key) {
search_total,
search_selected,
readonly,
copy_title,
};
/// Sync with: ghostty_action_u

View File

@@ -649,6 +649,8 @@ pub const Application = extern struct {
.close_tab => return Action.closeTab(target, value),
.close_window => return Action.closeWindow(target),
.copy_title => return Action.copyTitle(target),
.config_change => try Action.configChange(
self,
target,
@@ -1896,6 +1898,21 @@ const Action = struct {
}
}
pub fn copyTitle(target: apprt.Target) bool {
return switch (target) {
.app => false,
.surface => |v| surface: {
const title = v.rt_surface.surface.getEffectiveTitle() orelse break :surface false;
if (title.len == 0) break :surface false;
v.rt_surface.surface.setClipboard(.standard, &.{.{
.mime = "text/plain",
.data = title,
}}, false);
break :surface true;
},
};
}
pub fn configChange(
self: *Application,
target: apprt.Target,

View File

@@ -1982,6 +1982,13 @@ pub const Surface = extern struct {
return self.private().title;
}
/// Returns the effective title: the user-overridden title if set,
/// otherwise the terminal-set title.
pub fn getEffectiveTitle(self: *Self) ?[:0]const u8 {
const priv = self.private();
return priv.title_override orelse priv.title;
}
/// Set the title for this surface, copies the value. This should always
/// be the title as set by the terminal program, not any manually set
/// title. For manually set titles see `setTitleOverride`.