fix: copy_title_to_clipboard respects user-overridden title (#10694)

## Summary
- Fixes #10345 — `copy_title_to_clipboard` now copies the user-set
custom title instead of the raw terminal title
- Adds a new `copy_title` apprt action as [suggested by
@mitchellh](https://github.com/ghostty-org/ghostty/issues/10345#issuecomment-2601002974)
- Each platform (GTK + macOS) resolves the effective title (user
override → terminal title fallback) before copying to clipboard

## Changes
- **`src/apprt/action.zig`** — New `copy_title` void action
- **`include/ghostty.h`** — C ABI enum entry
- **`src/Surface.zig`** — Binding handler now dispatches apprt action
instead of inline logic
- **`src/apprt/gtk/class/surface.zig`** — `getEffectiveTitle()` helper
(returns `title_override orelse title`)
- **`src/apprt/gtk/class/application.zig`** — GTK action handler
- **`macos/.../Ghostty.App.swift`** — macOS handler using
`surfaceView.title` + `NSPasteboard`

*Note*: This PR was *AI* assisted.
This commit is contained in:
Mitchell Hashimoto
2026-02-16 11:10:17 -08:00
committed by GitHub
6 changed files with 60 additions and 14 deletions

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_to_clipboard,
/// 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_to_clipboard,
};
/// Sync with: ghostty_action_u

View File

@@ -674,6 +674,8 @@ pub const Application = extern struct {
.close_tab => return Action.closeTab(target, value),
.close_window => return Action.closeWindow(target),
.copy_title_to_clipboard => return Action.copyTitleToClipboard(target),
.config_change => try Action.configChange(
self,
target,
@@ -1921,6 +1923,13 @@ const Action = struct {
}
}
pub fn copyTitleToClipboard(target: apprt.Target) bool {
return switch (target) {
.app => false,
.surface => |v| v.rt_surface.gobj().copyTitleToClipboard(),
};
}
pub fn configChange(
self: *Application,
target: apprt.Target,

View File

@@ -1989,6 +1989,24 @@ 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;
}
/// Copies the effective title to the clipboard.
pub fn copyTitleToClipboard(self: *Self) bool {
const title = self.getEffectiveTitle() orelse return false;
if (title.len == 0) return false;
self.setClipboard(.standard, &.{.{
.mime = "text/plain",
.data = title,
}}, false);
return true;
}
/// 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`.