mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
OSC handling, handle OSC change window title command
This commit is contained in:
1
TODO.md
1
TODO.md
@ -41,7 +41,6 @@ Mac:
|
|||||||
|
|
||||||
* Set menubar
|
* Set menubar
|
||||||
* Preferences window
|
* Preferences window
|
||||||
* Need to load and proper locale env vars
|
|
||||||
|
|
||||||
Major Features:
|
Major Features:
|
||||||
|
|
||||||
|
25
src/App.zig
25
src/App.zig
@ -106,10 +106,11 @@ fn drainMailbox(self: *App) !void {
|
|||||||
defer drain.deinit();
|
defer drain.deinit();
|
||||||
|
|
||||||
while (drain.next()) |message| {
|
while (drain.next()) |message| {
|
||||||
log.debug("mailbox message={}", .{message});
|
log.debug("mailbox message={s}", .{@tagName(message)});
|
||||||
switch (message) {
|
switch (message) {
|
||||||
.new_window => try self.newWindow(),
|
.new_window => try self.newWindow(),
|
||||||
.quit => try self.setQuit(),
|
.quit => try self.setQuit(),
|
||||||
|
.window_message => |msg| try self.windowMessage(msg.window, msg.message),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,6 +134,22 @@ fn setQuit(self: *App) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handle a window message
|
||||||
|
fn windowMessage(self: *App, win: *Window, msg: Window.Message) !void {
|
||||||
|
// We want to ensure our window is still active. Window messages
|
||||||
|
// are quite rare and we normally don't have many windows so we do
|
||||||
|
// a simple linear search here.
|
||||||
|
for (self.windows.items) |window| {
|
||||||
|
if (window == win) {
|
||||||
|
try win.handleMessage(msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Window was not found, it probably quit before we handled the message.
|
||||||
|
// Not a problem.
|
||||||
|
}
|
||||||
|
|
||||||
/// The message types that can be sent to the app thread.
|
/// The message types that can be sent to the app thread.
|
||||||
pub const Message = union(enum) {
|
pub const Message = union(enum) {
|
||||||
/// Create a new terminal window.
|
/// Create a new terminal window.
|
||||||
@ -140,4 +157,10 @@ pub const Message = union(enum) {
|
|||||||
|
|
||||||
/// Quit
|
/// Quit
|
||||||
quit: void,
|
quit: void,
|
||||||
|
|
||||||
|
/// A message for a specific window
|
||||||
|
window_message: struct {
|
||||||
|
window: *Window,
|
||||||
|
message: Window.Message,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,12 @@
|
|||||||
//! writing, we support exactly one window.
|
//! writing, we support exactly one window.
|
||||||
const Window = @This();
|
const Window = @This();
|
||||||
|
|
||||||
|
// TODO: eventually, I want to extract Window.zig into the "window" package
|
||||||
|
// so we can also have alternate implementations (i.e. not glfw).
|
||||||
|
const message = @import("window/message.zig");
|
||||||
|
pub const Mailbox = message.Mailbox;
|
||||||
|
pub const Message = message.Message;
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
@ -309,6 +315,7 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
|
|||||||
.renderer_state = &self.renderer_state,
|
.renderer_state = &self.renderer_state,
|
||||||
.renderer_wakeup = render_thread.wakeup,
|
.renderer_wakeup = render_thread.wakeup,
|
||||||
.renderer_mailbox = render_thread.mailbox,
|
.renderer_mailbox = render_thread.mailbox,
|
||||||
|
.window_mailbox = .{ .window = self, .app = app.mailbox },
|
||||||
});
|
});
|
||||||
errdefer io.deinit();
|
errdefer io.deinit();
|
||||||
|
|
||||||
@ -478,6 +485,20 @@ pub fn shouldClose(self: Window) bool {
|
|||||||
return self.window.shouldClose();
|
return self.window.shouldClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called from the app thread to handle mailbox messages to our specific
|
||||||
|
/// window.
|
||||||
|
pub fn handleMessage(self: *Window, msg: Message) !void {
|
||||||
|
switch (msg) {
|
||||||
|
.set_title => |*v| {
|
||||||
|
// The ptrCast just gets sliceTo to return the proper type.
|
||||||
|
// We know that our title should end in 0.
|
||||||
|
const slice = std.mem.sliceTo(@ptrCast([*:0]const u8, v), 0);
|
||||||
|
log.debug("changing title \"{s}\"", .{slice});
|
||||||
|
try self.window.setTitle(slice.ptr);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This queues a render operation with the renderer thread. The render
|
/// This queues a render operation with the renderer thread. The render
|
||||||
/// isn't guaranteed to happen immediately but it will happen as soon as
|
/// isn't guaranteed to happen immediately but it will happen as soon as
|
||||||
/// practical.
|
/// practical.
|
||||||
|
@ -37,7 +37,7 @@ pub fn BlockingQueue(
|
|||||||
// The type we use for queue size types. We can optimize this
|
// The type we use for queue size types. We can optimize this
|
||||||
// in the future to be the correct bit-size for our preallocated
|
// in the future to be the correct bit-size for our preallocated
|
||||||
// size for this queue.
|
// size for this queue.
|
||||||
const Size = u32;
|
pub const Size = u32;
|
||||||
|
|
||||||
// The bounds of this queue. We recast this to Size so we can do math.
|
// The bounds of this queue. We recast this to Size so we can do math.
|
||||||
const bounds = @intCast(Size, capacity);
|
const bounds = @intCast(Size, capacity);
|
||||||
|
@ -4,6 +4,7 @@ const Parser = @import("Parser.zig");
|
|||||||
const ansi = @import("ansi.zig");
|
const ansi = @import("ansi.zig");
|
||||||
const charsets = @import("charsets.zig");
|
const charsets = @import("charsets.zig");
|
||||||
const csi = @import("csi.zig");
|
const csi = @import("csi.zig");
|
||||||
|
const osc = @import("osc.zig");
|
||||||
const sgr = @import("sgr.zig");
|
const sgr = @import("sgr.zig");
|
||||||
const trace = @import("tracy").trace;
|
const trace = @import("tracy").trace;
|
||||||
|
|
||||||
@ -59,7 +60,7 @@ pub fn Stream(comptime Handler: type) type {
|
|||||||
.execute => |code| try self.execute(code),
|
.execute => |code| try self.execute(code),
|
||||||
.csi_dispatch => |csi_action| try self.csiDispatch(csi_action),
|
.csi_dispatch => |csi_action| try self.csiDispatch(csi_action),
|
||||||
.esc_dispatch => |esc| try self.escDispatch(esc),
|
.esc_dispatch => |esc| try self.escDispatch(esc),
|
||||||
.osc_dispatch => |cmd| log.warn("unhandled OSC: {}", .{cmd}),
|
.osc_dispatch => |cmd| try self.oscDispatch(cmd),
|
||||||
.dcs_hook => |dcs| log.warn("unhandled DCS hook: {}", .{dcs}),
|
.dcs_hook => |dcs| log.warn("unhandled DCS hook: {}", .{dcs}),
|
||||||
.dcs_put => |code| log.warn("unhandled DCS put: {}", .{code}),
|
.dcs_put => |code| log.warn("unhandled DCS put: {}", .{code}),
|
||||||
.dcs_unhook => log.warn("unhandled DCS unhook", .{}),
|
.dcs_unhook => log.warn("unhandled DCS unhook", .{}),
|
||||||
@ -455,6 +456,21 @@ pub fn Stream(comptime Handler: type) type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn oscDispatch(self: *Self, cmd: osc.Command) !void {
|
||||||
|
switch (cmd) {
|
||||||
|
.change_window_title => |title| {
|
||||||
|
if (@hasDecl(T, "changeWindowTitle")) {
|
||||||
|
try self.handler.changeWindowTitle(title);
|
||||||
|
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||||
|
},
|
||||||
|
|
||||||
|
else => if (@hasDecl(T, "oscUnimplemented"))
|
||||||
|
try self.handler.oscUnimplemented(cmd)
|
||||||
|
else
|
||||||
|
log.warn("unimplemented OSC command: {}", .{cmd}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn configureCharset(
|
fn configureCharset(
|
||||||
self: *Self,
|
self: *Self,
|
||||||
intermediates: []const u8,
|
intermediates: []const u8,
|
||||||
|
@ -7,6 +7,7 @@ const assert = std.debug.assert;
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const termio = @import("../termio.zig");
|
const termio = @import("../termio.zig");
|
||||||
const Command = @import("../Command.zig");
|
const Command = @import("../Command.zig");
|
||||||
|
const Window = @import("../Window.zig");
|
||||||
const Pty = @import("../Pty.zig");
|
const Pty = @import("../Pty.zig");
|
||||||
const SegmentedPool = @import("../segmented_pool.zig").SegmentedPool;
|
const SegmentedPool = @import("../segmented_pool.zig").SegmentedPool;
|
||||||
const terminal = @import("../terminal/main.zig");
|
const terminal = @import("../terminal/main.zig");
|
||||||
@ -52,6 +53,9 @@ renderer_wakeup: libuv.Async,
|
|||||||
/// The mailbox for notifying the renderer of things.
|
/// The mailbox for notifying the renderer of things.
|
||||||
renderer_mailbox: *renderer.Thread.Mailbox,
|
renderer_mailbox: *renderer.Thread.Mailbox,
|
||||||
|
|
||||||
|
/// The mailbox for communicating with the window.
|
||||||
|
window_mailbox: Window.Mailbox,
|
||||||
|
|
||||||
/// The cached grid size whenever a resize is called.
|
/// The cached grid size whenever a resize is called.
|
||||||
grid_size: renderer.GridSize,
|
grid_size: renderer.GridSize,
|
||||||
|
|
||||||
@ -117,6 +121,7 @@ pub fn init(alloc: Allocator, opts: termio.Options) !Exec {
|
|||||||
.renderer_state = opts.renderer_state,
|
.renderer_state = opts.renderer_state,
|
||||||
.renderer_wakeup = opts.renderer_wakeup,
|
.renderer_wakeup = opts.renderer_wakeup,
|
||||||
.renderer_mailbox = opts.renderer_mailbox,
|
.renderer_mailbox = opts.renderer_mailbox,
|
||||||
|
.window_mailbox = opts.window_mailbox,
|
||||||
.grid_size = opts.grid_size,
|
.grid_size = opts.grid_size,
|
||||||
.data = null,
|
.data = null,
|
||||||
};
|
};
|
||||||
@ -199,6 +204,7 @@ pub fn threadEnter(self: *Exec, loop: libuv.Loop) !ThreadData {
|
|||||||
.ev = ev_data_ptr,
|
.ev = ev_data_ptr,
|
||||||
.terminal = &self.terminal,
|
.terminal = &self.terminal,
|
||||||
.grid_size = &self.grid_size,
|
.grid_size = &self.grid_size,
|
||||||
|
.window_mailbox = self.window_mailbox,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -466,6 +472,7 @@ const StreamHandler = struct {
|
|||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
grid_size: *renderer.GridSize,
|
grid_size: *renderer.GridSize,
|
||||||
terminal: *terminal.Terminal,
|
terminal: *terminal.Terminal,
|
||||||
|
window_mailbox: Window.Mailbox,
|
||||||
|
|
||||||
inline fn queueRender(self: *StreamHandler) !void {
|
inline fn queueRender(self: *StreamHandler) !void {
|
||||||
try self.ev.queueRender();
|
try self.ev.queueRender();
|
||||||
@ -787,4 +794,22 @@ const StreamHandler = struct {
|
|||||||
) !void {
|
) !void {
|
||||||
self.terminal.fullReset();
|
self.terminal.fullReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
// OSC
|
||||||
|
|
||||||
|
pub fn changeWindowTitle(self: *StreamHandler, title: []const u8) !void {
|
||||||
|
var buf: [256]u8 = undefined;
|
||||||
|
if (title.len >= buf.len) {
|
||||||
|
log.warn("change title requested larger than our buffer size, ignoring", .{});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std.mem.copy(u8, &buf, title);
|
||||||
|
buf[title.len] = 0;
|
||||||
|
|
||||||
|
_ = self.window_mailbox.push(.{
|
||||||
|
.set_title = buf,
|
||||||
|
}, .{ .forever = {} });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
const libuv = @import("libuv");
|
const libuv = @import("libuv");
|
||||||
const renderer = @import("../renderer.zig");
|
const renderer = @import("../renderer.zig");
|
||||||
const Config = @import("../config.zig").Config;
|
const Config = @import("../config.zig").Config;
|
||||||
|
const Window = @import("../Window.zig");
|
||||||
|
|
||||||
/// The size of the terminal grid.
|
/// The size of the terminal grid.
|
||||||
grid_size: renderer.GridSize,
|
grid_size: renderer.GridSize,
|
||||||
@ -25,3 +26,6 @@ renderer_wakeup: libuv.Async,
|
|||||||
|
|
||||||
/// The mailbox for renderer messages.
|
/// The mailbox for renderer messages.
|
||||||
renderer_mailbox: *renderer.Thread.Mailbox,
|
renderer_mailbox: *renderer.Thread.Mailbox,
|
||||||
|
|
||||||
|
/// The mailbox for sending the window messages.
|
||||||
|
window_mailbox: Window.Mailbox,
|
||||||
|
35
src/window/message.zig
Normal file
35
src/window/message.zig
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
const App = @import("../App.zig");
|
||||||
|
const Window = @import("../Window.zig");
|
||||||
|
|
||||||
|
/// The message types that can be sent to a single window.
|
||||||
|
pub const Message = union(enum) {
|
||||||
|
/// Set the title of the window.
|
||||||
|
/// TODO: we should change this to a "WriteReq" style structure in
|
||||||
|
/// the termio message so that we can more efficiently send strings
|
||||||
|
/// of any length
|
||||||
|
set_title: [256]u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A window mailbox.
|
||||||
|
pub const Mailbox = struct {
|
||||||
|
window: *Window,
|
||||||
|
app: *App.Mailbox,
|
||||||
|
|
||||||
|
/// Send a message to the window.
|
||||||
|
pub fn push(self: Mailbox, msg: Message, timeout: App.Mailbox.Timeout) App.Mailbox.Size {
|
||||||
|
// Window message sending is actually implemented on the app
|
||||||
|
// thread, so we have to rewrap the message with our window
|
||||||
|
// pointer and send it to the app thread.
|
||||||
|
const result = self.app.push(.{
|
||||||
|
.window_message = .{
|
||||||
|
.window = self.window,
|
||||||
|
.message = msg,
|
||||||
|
},
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
// Wake up our app loop
|
||||||
|
self.window.app.wakeup();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
Reference in New Issue
Block a user