From df0669789986ed549b51900b11d09e559cd9999f Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Sat, 31 Aug 2024 19:40:19 -0500 Subject: [PATCH] termio: send initial focus reports When the focus reporting mode (1004) is enabled, send the current focus state. This allows applications to track their own focus state without first having to wait for a focus event (or query it by sending a DECSET followed by a DECRST). Ghostty's focus state is stored only in the renderer, where the termio thread cannot access it. We duplicate the focus state tracking in the Terminal struct with the addition of a new (1-bit) flag. We duplicate the state because the renderer uses the focus state for its own purposes (in particular, the Metal renderer uses the focus state to manage its DisplayLink), and synchronizing access to the shared terminal state is more cumbersome than simply tracking the focus state in the renderer in addition to the terminal. --- src/Surface.zig | 7 ++++--- src/terminal/Terminal.zig | 3 +++ src/termio/Termio.zig | 6 ++++++ src/termio/Thread.zig | 1 + src/termio/message.zig | 3 +++ src/termio/stream_handler.zig | 4 ++++ 6 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index 737414594..c4c30f675 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -1824,15 +1824,16 @@ pub fn focusCallback(self: *Surface, focused: bool) !void { // Schedule render which also drains our mailbox try self.queueRender(); - // Notify the app about focus in/out if it is requesting it + // Update the focus state and notify the terminal about the focus event if + // it is requesting it { self.renderer_state.mutex.lock(); + self.io.terminal.flags.focused = focused; const focus_event = self.io.terminal.modes.get(.focus_event); self.renderer_state.mutex.unlock(); if (focus_event) { - const seq = if (focused) "\x1b[I" else "\x1b[O"; - self.io.queueMessage(.{ .write_stable = seq }, .unlocked); + self.io.queueMessage(.{ .focused = focused }, .unlocked); } } } diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 12a056664..0f15845dd 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -116,6 +116,9 @@ flags: packed struct { /// if the configuration allows it. mouse_shift_capture: enum(u2) { null, false, true } = .null, + /// True if the window is focused. + focused: bool = true, + /// Dirty flags for the renderer. dirty: Dirty = .{}, } = .{}, diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig index ae38eb043..f209748df 100644 --- a/src/termio/Termio.zig +++ b/src/termio/Termio.zig @@ -521,6 +521,12 @@ pub fn childExitedAbnormally(self: *Termio, exit_code: u32, runtime_ms: u64) !vo try self.backend.childExitedAbnormally(self.alloc, t, exit_code, runtime_ms); } +/// Called when focus is gained or lost (when focus events are enabled) +pub fn focusGained(self: *Termio, td: *ThreadData, focused: bool) !void { + const seq = if (focused) "\x1b[I" else "\x1b[O"; + try self.queueWrite(td, seq, false); +} + /// Process output from the pty. This is the manual API that users can /// call with pty data but it is also called by the read thread when using /// an exec subprocess. diff --git a/src/termio/Thread.zig b/src/termio/Thread.zig index a62e0b8de..4c75b3b9e 100644 --- a/src/termio/Thread.zig +++ b/src/termio/Thread.zig @@ -283,6 +283,7 @@ fn drainMailbox( .start_synchronized_output => self.startSynchronizedOutput(cb), .linefeed_mode => |v| self.flags.linefeed_mode = v, .child_exited_abnormally => |v| try io.childExitedAbnormally(v.exit_code, v.runtime_ms), + .focused => |v| try io.focusGained(data, v), .write_small => |v| try io.queueWrite( data, v.data[0..v.len], diff --git a/src/termio/message.zig b/src/termio/message.zig index 6bdb7a2da..79b920ad7 100644 --- a/src/termio/message.zig +++ b/src/termio/message.zig @@ -80,6 +80,9 @@ pub const Message = union(enum) { runtime_ms: u64, }, + /// The surface gained or lost focus. + focused: bool, + /// Write where the data fits in the union. write_small: WriteReq.Small, diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 93ee3e780..90a33e8b7 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -637,6 +637,10 @@ pub const StreamHandler = struct { .size_report = .mode_2048, }), + .focus_event => if (enabled) self.messageWriter(.{ + .focused = self.terminal.flags.focused, + }), + .mouse_event_x10 => { if (enabled) { self.terminal.flags.mouse_event = .x10;