mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
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:
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 = .{},
|
||||
} = .{},
|
||||
|
@ -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.
|
||||
|
@ -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],
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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;
|
||||
|
Reference in New Issue
Block a user