mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-06 07:38:21 +00:00
terminal: add set_window_title effect to stream handler
Previously the window_title action was silently ignored in the readonly stream handler. Add a set_window_title callback to the Effects struct so callers can be notified when a window title is set via OSC 2. Follows the same pattern as bell and write_pty where the callback is optional and defaults to null in readonly mode.
This commit is contained in:
@@ -40,12 +40,16 @@ pub const Handler = struct {
|
||||
/// during the lifetime of the call.
|
||||
write_pty: ?*const fn (*Handler, [:0]const u8) void,
|
||||
|
||||
/// Called when the window title is set via OSC 2.
|
||||
set_window_title: ?*const fn (*Handler, []const u8) void,
|
||||
|
||||
/// No effects means that the stream effectively becomes readonly
|
||||
/// that only affects pure terminal state and ignores all side
|
||||
/// effects beyond that.
|
||||
pub const readonly: Effects = .{
|
||||
.bell = null,
|
||||
.write_pty = null,
|
||||
.set_window_title = null,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -190,6 +194,7 @@ pub const Handler = struct {
|
||||
|
||||
// Effect-based handlers
|
||||
.bell => self.bell(),
|
||||
.window_title => self.setWindowTitle(value.title),
|
||||
.request_mode => self.requestMode(value.mode),
|
||||
.request_mode_unknown => self.requestModeUnknown(value.mode, value.ansi),
|
||||
|
||||
@@ -214,7 +219,6 @@ pub const Handler = struct {
|
||||
.device_attributes,
|
||||
.device_status,
|
||||
.kitty_keyboard_query,
|
||||
.window_title,
|
||||
.report_pwd,
|
||||
.show_desktop_notification,
|
||||
.progress_report,
|
||||
@@ -235,6 +239,11 @@ pub const Handler = struct {
|
||||
func(self, data);
|
||||
}
|
||||
|
||||
inline fn setWindowTitle(self: *Handler, title: []const u8) void {
|
||||
const func = self.effects.set_window_title orelse return;
|
||||
func(self, title);
|
||||
}
|
||||
|
||||
fn requestMode(self: *Handler, mode: modes.Mode) void {
|
||||
const report = self.terminal.modes.getReport(.fromMode(mode));
|
||||
self.sendModeReport(report);
|
||||
@@ -1163,3 +1172,72 @@ test "stream: CSI W with intermediate but no params" {
|
||||
|
||||
s.nextSlice("\x1b[?W");
|
||||
}
|
||||
|
||||
test "window_title effect is called" {
|
||||
var t: Terminal = try .init(testing.allocator, .{ .cols = 80, .rows = 24 });
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
const S = struct {
|
||||
var last_title: ?[]const u8 = null;
|
||||
fn setWindowTitle(handler: *Handler, title: []const u8) void {
|
||||
_ = handler;
|
||||
if (last_title) |old| testing.allocator.free(old);
|
||||
last_title = testing.allocator.dupe(u8, title) catch null;
|
||||
}
|
||||
};
|
||||
S.last_title = null;
|
||||
defer if (S.last_title) |t2| testing.allocator.free(t2);
|
||||
|
||||
var handler: Handler = .init(&t);
|
||||
handler.effects.set_window_title = &S.setWindowTitle;
|
||||
|
||||
var s: Stream = .initAlloc(testing.allocator, handler);
|
||||
defer s.deinit();
|
||||
|
||||
// Set window title via OSC 2
|
||||
s.nextSlice("\x1b]2;Hello World\x1b\\");
|
||||
try testing.expectEqualStrings("Hello World", S.last_title.?);
|
||||
}
|
||||
|
||||
test "window_title effect not called without callback" {
|
||||
var t: Terminal = try .init(testing.allocator, .{ .cols = 80, .rows = 24 });
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
var s: Stream = .initAlloc(testing.allocator, .init(&t));
|
||||
defer s.deinit();
|
||||
|
||||
// Should not crash when no callback is set
|
||||
s.nextSlice("\x1b]2;Hello World\x1b\\");
|
||||
|
||||
// Terminal should still be functional
|
||||
s.nextSlice("Test");
|
||||
const str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("Test", str);
|
||||
}
|
||||
|
||||
test "window_title effect with empty title" {
|
||||
var t: Terminal = try .init(testing.allocator, .{ .cols = 80, .rows = 24 });
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
const S = struct {
|
||||
var last_title: ?[]const u8 = null;
|
||||
fn setWindowTitle(handler: *Handler, title: []const u8) void {
|
||||
_ = handler;
|
||||
if (last_title) |old| testing.allocator.free(old);
|
||||
last_title = testing.allocator.dupe(u8, title) catch null;
|
||||
}
|
||||
};
|
||||
S.last_title = null;
|
||||
defer if (S.last_title) |t2| testing.allocator.free(t2);
|
||||
|
||||
var handler: Handler = .init(&t);
|
||||
handler.effects.set_window_title = &S.setWindowTitle;
|
||||
|
||||
var s: Stream = .initAlloc(testing.allocator, handler);
|
||||
defer s.deinit();
|
||||
|
||||
// Set empty window title
|
||||
s.nextSlice("\x1b]2;\x1b\\");
|
||||
try testing.expectEqualStrings("", S.last_title.?);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user