mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-06 07:38:21 +00:00
vt: add title_changed effect callback
Add GHOSTTY_TERMINAL_OPT_TITLE_CHANGED so C consumers are notified when the terminal title changes via OSC 0 or OSC 2 sequences. The callback has the same fire-and-forget shape as bell.
This commit is contained in:
@@ -201,6 +201,21 @@ typedef GhosttyString (*GhosttyTerminalEnquiryFn)(GhosttyTerminal terminal,
|
||||
typedef GhosttyString (*GhosttyTerminalXtversionFn)(GhosttyTerminal terminal,
|
||||
void* userdata);
|
||||
|
||||
/**
|
||||
* Callback function type for title_changed.
|
||||
*
|
||||
* Called when the terminal title changes via escape sequences
|
||||
* (e.g. OSC 0 or OSC 2). The new title can be queried from the
|
||||
* terminal after the callback returns.
|
||||
*
|
||||
* @param terminal The terminal handle
|
||||
* @param userdata The userdata pointer set via GHOSTTY_TERMINAL_OPT_USERDATA
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
typedef void (*GhosttyTerminalTitleChangedFn)(GhosttyTerminal terminal,
|
||||
void* userdata);
|
||||
|
||||
/**
|
||||
* Terminal option identifiers.
|
||||
*
|
||||
@@ -249,6 +264,15 @@ typedef enum {
|
||||
* Input type: GhosttyTerminalXtversionFn*
|
||||
*/
|
||||
GHOSTTY_TERMINAL_OPT_XTVERSION = 4,
|
||||
|
||||
/**
|
||||
* Callback invoked when the terminal title changes via escape
|
||||
* sequences (e.g. OSC 0 or OSC 2). Set to NULL to ignore title
|
||||
* change events.
|
||||
*
|
||||
* Input type: GhosttyTerminalTitleChangedFn*
|
||||
*/
|
||||
GHOSTTY_TERMINAL_OPT_TITLE_CHANGED = 5,
|
||||
} GhosttyTerminalOption;
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,6 +39,7 @@ const Effects = struct {
|
||||
bell: ?BellFn = null,
|
||||
enquiry: ?EnquiryFn = null,
|
||||
xtversion: ?XtversionFn = null,
|
||||
title_changed: ?TitleChangedFn = null,
|
||||
|
||||
/// C function pointer type for the write_pty callback.
|
||||
pub const WritePtyFn = *const fn (Terminal, ?*anyopaque, [*]const u8, usize) callconv(.c) void;
|
||||
@@ -57,6 +58,9 @@ const Effects = struct {
|
||||
/// (len=0) causes the default "libghostty" to be reported.
|
||||
pub const XtversionFn = *const fn (Terminal, ?*anyopaque) callconv(.c) lib.String;
|
||||
|
||||
/// C function pointer type for the title_changed callback.
|
||||
pub const TitleChangedFn = *const fn (Terminal, ?*anyopaque) callconv(.c) void;
|
||||
|
||||
fn writePtyTrampoline(handler: *Handler, data: [:0]const u8) void {
|
||||
const stream_ptr: *Stream = @fieldParentPtr("handler", handler);
|
||||
const wrapper: *TerminalWrapper = @fieldParentPtr("stream", stream_ptr);
|
||||
@@ -88,6 +92,13 @@ const Effects = struct {
|
||||
if (result.len == 0) return "";
|
||||
return result.ptr[0..result.len];
|
||||
}
|
||||
|
||||
fn titleChangedTrampoline(handler: *Handler) void {
|
||||
const stream_ptr: *Stream = @fieldParentPtr("handler", handler);
|
||||
const wrapper: *TerminalWrapper = @fieldParentPtr("stream", stream_ptr);
|
||||
const func = wrapper.effects.title_changed orelse return;
|
||||
func(@ptrCast(wrapper), wrapper.effects.userdata);
|
||||
}
|
||||
};
|
||||
|
||||
/// C: GhosttyTerminal
|
||||
@@ -151,6 +162,7 @@ fn new_(
|
||||
handler.effects.bell = &Effects.bellTrampoline;
|
||||
handler.effects.enquiry = &Effects.enquiryTrampoline;
|
||||
handler.effects.xtversion = &Effects.xtversionTrampoline;
|
||||
handler.effects.title_changed = &Effects.titleChangedTrampoline;
|
||||
|
||||
wrapper.* = .{
|
||||
.terminal = t,
|
||||
@@ -176,6 +188,7 @@ pub const Option = enum(c_int) {
|
||||
bell = 2,
|
||||
enquiry = 3,
|
||||
xtversion = 4,
|
||||
title_changed = 5,
|
||||
|
||||
/// Input type expected for setting the option.
|
||||
pub fn InType(comptime self: Option) type {
|
||||
@@ -185,6 +198,7 @@ pub const Option = enum(c_int) {
|
||||
.bell => ?Effects.BellFn,
|
||||
.enquiry => ?Effects.EnquiryFn,
|
||||
.xtversion => ?Effects.XtversionFn,
|
||||
.title_changed => ?Effects.TitleChangedFn,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -222,6 +236,7 @@ fn setTyped(
|
||||
.bell => wrapper.effects.bell = if (value) |v| v.* else null,
|
||||
.enquiry => wrapper.effects.enquiry = if (value) |v| v.* else null,
|
||||
.xtversion => wrapper.effects.xtversion = if (value) |v| v.* else null,
|
||||
.title_changed => wrapper.effects.title_changed = if (value) |v| v.* else null,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1198,6 +1213,64 @@ test "xtversion without callback reports default" {
|
||||
try testing.expectEqualStrings("\x1BP>|libghostty\x1B\\", S.last_data.?);
|
||||
}
|
||||
|
||||
test "set title_changed callback" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 0,
|
||||
},
|
||||
));
|
||||
defer free(t);
|
||||
|
||||
const S = struct {
|
||||
var title_count: usize = 0;
|
||||
var last_userdata: ?*anyopaque = null;
|
||||
|
||||
fn titleChanged(_: Terminal, ud: ?*anyopaque) callconv(.c) void {
|
||||
title_count += 1;
|
||||
last_userdata = ud;
|
||||
}
|
||||
};
|
||||
S.title_count = 0;
|
||||
S.last_userdata = null;
|
||||
|
||||
var sentinel: u8 = 77;
|
||||
const ud: ?*anyopaque = @ptrCast(&sentinel);
|
||||
set(t, .userdata, @ptrCast(&ud));
|
||||
const cb: ?Effects.TitleChangedFn = &S.titleChanged;
|
||||
set(t, .title_changed, @ptrCast(&cb));
|
||||
|
||||
// OSC 2 ; title ST — set window title
|
||||
vt_write(t, "\x1B]2;Hello\x1B\\", 10);
|
||||
try testing.expectEqual(@as(usize, 1), S.title_count);
|
||||
try testing.expectEqual(@as(?*anyopaque, @ptrCast(&sentinel)), S.last_userdata);
|
||||
|
||||
// Another title change
|
||||
vt_write(t, "\x1B]2;World\x1B\\", 10);
|
||||
try testing.expectEqual(@as(usize, 2), S.title_count);
|
||||
}
|
||||
|
||||
test "title_changed without callback is silent" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 0,
|
||||
},
|
||||
));
|
||||
defer free(t);
|
||||
|
||||
// OSC 2 without a callback should not crash
|
||||
vt_write(t, "\x1B]2;Hello\x1B\\", 10);
|
||||
}
|
||||
|
||||
test "grid_ref out of bounds" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
|
||||
Reference in New Issue
Block a user