OSC handling, handle OSC change window title command

This commit is contained in:
Mitchell Hashimoto
2022-11-14 10:46:40 -08:00
parent 56504a342f
commit 4ced2290b3
8 changed files with 127 additions and 4 deletions

View File

@ -41,7 +41,6 @@ Mac:
* Set menubar
* Preferences window
* Need to load and proper locale env vars
Major Features:

View File

@ -106,10 +106,11 @@ fn drainMailbox(self: *App) !void {
defer drain.deinit();
while (drain.next()) |message| {
log.debug("mailbox message={}", .{message});
log.debug("mailbox message={s}", .{@tagName(message)});
switch (message) {
.new_window => try self.newWindow(),
.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.
pub const Message = union(enum) {
/// Create a new terminal window.
@ -140,4 +157,10 @@ pub const Message = union(enum) {
/// Quit
quit: void,
/// A message for a specific window
window_message: struct {
window: *Window,
message: Window.Message,
},
};

View File

@ -5,6 +5,12 @@
//! writing, we support exactly one window.
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 builtin = @import("builtin");
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_wakeup = render_thread.wakeup,
.renderer_mailbox = render_thread.mailbox,
.window_mailbox = .{ .window = self, .app = app.mailbox },
});
errdefer io.deinit();
@ -478,6 +485,20 @@ pub fn shouldClose(self: Window) bool {
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
/// isn't guaranteed to happen immediately but it will happen as soon as
/// practical.

View File

@ -37,7 +37,7 @@ pub fn BlockingQueue(
// The type we use for queue size types. We can optimize this
// in the future to be the correct bit-size for our preallocated
// 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.
const bounds = @intCast(Size, capacity);

View File

@ -4,6 +4,7 @@ const Parser = @import("Parser.zig");
const ansi = @import("ansi.zig");
const charsets = @import("charsets.zig");
const csi = @import("csi.zig");
const osc = @import("osc.zig");
const sgr = @import("sgr.zig");
const trace = @import("tracy").trace;
@ -59,7 +60,7 @@ pub fn Stream(comptime Handler: type) type {
.execute => |code| try self.execute(code),
.csi_dispatch => |csi_action| try self.csiDispatch(csi_action),
.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_put => |code| log.warn("unhandled DCS put: {}", .{code}),
.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(
self: *Self,
intermediates: []const u8,

View File

@ -7,6 +7,7 @@ const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const termio = @import("../termio.zig");
const Command = @import("../Command.zig");
const Window = @import("../Window.zig");
const Pty = @import("../Pty.zig");
const SegmentedPool = @import("../segmented_pool.zig").SegmentedPool;
const terminal = @import("../terminal/main.zig");
@ -52,6 +53,9 @@ renderer_wakeup: libuv.Async,
/// The mailbox for notifying the renderer of things.
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.
grid_size: renderer.GridSize,
@ -117,6 +121,7 @@ pub fn init(alloc: Allocator, opts: termio.Options) !Exec {
.renderer_state = opts.renderer_state,
.renderer_wakeup = opts.renderer_wakeup,
.renderer_mailbox = opts.renderer_mailbox,
.window_mailbox = opts.window_mailbox,
.grid_size = opts.grid_size,
.data = null,
};
@ -199,6 +204,7 @@ pub fn threadEnter(self: *Exec, loop: libuv.Loop) !ThreadData {
.ev = ev_data_ptr,
.terminal = &self.terminal,
.grid_size = &self.grid_size,
.window_mailbox = self.window_mailbox,
},
},
};
@ -466,6 +472,7 @@ const StreamHandler = struct {
alloc: Allocator,
grid_size: *renderer.GridSize,
terminal: *terminal.Terminal,
window_mailbox: Window.Mailbox,
inline fn queueRender(self: *StreamHandler) !void {
try self.ev.queueRender();
@ -787,4 +794,22 @@ const StreamHandler = struct {
) !void {
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 = {} });
}
};

View File

@ -3,6 +3,7 @@
const libuv = @import("libuv");
const renderer = @import("../renderer.zig");
const Config = @import("../config.zig").Config;
const Window = @import("../Window.zig");
/// The size of the terminal grid.
grid_size: renderer.GridSize,
@ -25,3 +26,6 @@ renderer_wakeup: libuv.Async,
/// The mailbox for renderer messages.
renderer_mailbox: *renderer.Thread.Mailbox,
/// The mailbox for sending the window messages.
window_mailbox: Window.Mailbox,

35
src/window/message.zig Normal file
View 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;
}
};