mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-18 05:20:29 +00:00
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:
@@ -904,6 +904,7 @@ typedef enum {
|
||||
GHOSTTY_ACTION_SEARCH_TOTAL,
|
||||
GHOSTTY_ACTION_SEARCH_SELECTED,
|
||||
GHOSTTY_ACTION_READONLY,
|
||||
GHOSTTY_ACTION_COPY_TITLE_TO_CLIPBOARD,
|
||||
} ghostty_action_tag_e;
|
||||
|
||||
typedef union {
|
||||
|
||||
@@ -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_TO_CLIPBOARD:
|
||||
return copyTitleToClipboard(app, target: target)
|
||||
default:
|
||||
Ghostty.logger.warning("unknown action action=\(action.tag.rawValue)")
|
||||
return false
|
||||
@@ -1506,6 +1508,25 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
private static func copyTitleToClipboard(
|
||||
_ 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,
|
||||
|
||||
@@ -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_to_clipboard,
|
||||
{},
|
||||
),
|
||||
|
||||
.paste_from_clipboard => return try self.startClipboardRequest(
|
||||
.standard,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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`.
|
||||
|
||||
Reference in New Issue
Block a user