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.
This commit is contained in:
Gregory Anders
2024-08-31 19:40:19 -05:00
parent 42b799e9a0
commit df06697899
6 changed files with 21 additions and 3 deletions

View File

@ -1824,15 +1824,16 @@ pub fn focusCallback(self: *Surface, focused: bool) !void {
// Schedule render which also drains our mailbox // Schedule render which also drains our mailbox
try self.queueRender(); 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.renderer_state.mutex.lock();
self.io.terminal.flags.focused = focused;
const focus_event = self.io.terminal.modes.get(.focus_event); const focus_event = self.io.terminal.modes.get(.focus_event);
self.renderer_state.mutex.unlock(); self.renderer_state.mutex.unlock();
if (focus_event) { if (focus_event) {
const seq = if (focused) "\x1b[I" else "\x1b[O"; self.io.queueMessage(.{ .focused = focused }, .unlocked);
self.io.queueMessage(.{ .write_stable = seq }, .unlocked);
} }
} }
} }

View File

@ -116,6 +116,9 @@ flags: packed struct {
/// if the configuration allows it. /// if the configuration allows it.
mouse_shift_capture: enum(u2) { null, false, true } = .null, mouse_shift_capture: enum(u2) { null, false, true } = .null,
/// True if the window is focused.
focused: bool = true,
/// Dirty flags for the renderer. /// Dirty flags for the renderer.
dirty: Dirty = .{}, dirty: Dirty = .{},
} = .{}, } = .{},

View File

@ -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); 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 /// 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 /// call with pty data but it is also called by the read thread when using
/// an exec subprocess. /// an exec subprocess.

View File

@ -283,6 +283,7 @@ fn drainMailbox(
.start_synchronized_output => self.startSynchronizedOutput(cb), .start_synchronized_output => self.startSynchronizedOutput(cb),
.linefeed_mode => |v| self.flags.linefeed_mode = v, .linefeed_mode => |v| self.flags.linefeed_mode = v,
.child_exited_abnormally => |v| try io.childExitedAbnormally(v.exit_code, v.runtime_ms), .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( .write_small => |v| try io.queueWrite(
data, data,
v.data[0..v.len], v.data[0..v.len],

View File

@ -80,6 +80,9 @@ pub const Message = union(enum) {
runtime_ms: u64, runtime_ms: u64,
}, },
/// The surface gained or lost focus.
focused: bool,
/// Write where the data fits in the union. /// Write where the data fits in the union.
write_small: WriteReq.Small, write_small: WriteReq.Small,

View File

@ -637,6 +637,10 @@ pub const StreamHandler = struct {
.size_report = .mode_2048, .size_report = .mode_2048,
}), }),
.focus_event => if (enabled) self.messageWriter(.{
.focused = self.terminal.flags.focused,
}),
.mouse_event_x10 => { .mouse_event_x10 => {
if (enabled) { if (enabled) {
self.terminal.flags.mouse_event = .x10; self.terminal.flags.mouse_event = .x10;