mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 09:16:11 +03:00
Merge pull request #2262 from ghostty-org/pwinput
Detect password input and render lock glyph
This commit is contained in:
@ -1826,17 +1826,12 @@ 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();
|
||||||
|
|
||||||
// Update the focus state and notify the terminal about the focus event if
|
// Update the focus state and notify the terminal
|
||||||
// it is requesting it
|
|
||||||
{
|
{
|
||||||
self.renderer_state.mutex.lock();
|
self.renderer_state.mutex.lock();
|
||||||
self.io.terminal.flags.focused = focused;
|
self.io.terminal.flags.focused = focused;
|
||||||
const focus_event = self.io.terminal.modes.get(.focus_event);
|
|
||||||
self.renderer_state.mutex.unlock();
|
self.renderer_state.mutex.unlock();
|
||||||
|
self.io.queueMessage(.{ .focused = focused }, .unlocked);
|
||||||
if (focus_event) {
|
|
||||||
self.io.queueMessage(.{ .focused = focused }, .unlocked);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
30
src/pty.zig
30
src/pty.zig
@ -21,6 +21,20 @@ pub const Pty = switch (builtin.os.tag) {
|
|||||||
else => PosixPty,
|
else => PosixPty,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The modes of a pty. Not all of these modes are supported on
|
||||||
|
/// all platforms but all platforms share the same mode struct.
|
||||||
|
///
|
||||||
|
/// The default values of fields in this struct are set to the
|
||||||
|
/// most typical values for a pty. This makes it easier for cross-platform
|
||||||
|
/// code which doesn't support all of the modes to work correctly.
|
||||||
|
pub const Mode = packed struct {
|
||||||
|
/// ICANON on POSIX
|
||||||
|
canonical: bool = true,
|
||||||
|
|
||||||
|
/// ECHO on POSIX
|
||||||
|
echo: bool = true,
|
||||||
|
};
|
||||||
|
|
||||||
// A pty implementation that does nothing.
|
// A pty implementation that does nothing.
|
||||||
//
|
//
|
||||||
// TODO: This should be removed. This is only temporary until we have
|
// TODO: This should be removed. This is only temporary until we have
|
||||||
@ -41,6 +55,11 @@ const NullPty = struct {
|
|||||||
_ = self;
|
_ = self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getMode(self: Pty) error{GetModeFailed}!Mode {
|
||||||
|
_ = self;
|
||||||
|
return .{};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn setSize(self: *Pty, size: winsize) !void {
|
pub fn setSize(self: *Pty, size: winsize) !void {
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = size;
|
_ = size;
|
||||||
@ -119,6 +138,17 @@ const PosixPty = struct {
|
|||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getMode(self: Pty) error{GetModeFailed}!Mode {
|
||||||
|
var attrs: c.termios = undefined;
|
||||||
|
if (c.tcgetattr(self.master, &attrs) != 0)
|
||||||
|
return error.GetModeFailed;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.canonical = (attrs.c_lflag & c.ICANON) != 0,
|
||||||
|
.echo = (attrs.c_lflag & c.ECHO) != 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the size of the pty.
|
/// Return the size of the pty.
|
||||||
pub fn getSize(self: Pty) !winsize {
|
pub fn getSize(self: Pty) !winsize {
|
||||||
var ws: winsize = undefined;
|
var ws: winsize = undefined;
|
||||||
|
@ -947,11 +947,14 @@ pub fn updateFrame(
|
|||||||
errdefer screen_copy.deinit();
|
errdefer screen_copy.deinit();
|
||||||
|
|
||||||
// Whether to draw our cursor or not.
|
// Whether to draw our cursor or not.
|
||||||
const cursor_style = renderer.cursorStyle(
|
const cursor_style = if (state.terminal.flags.password_input)
|
||||||
state,
|
.lock
|
||||||
self.focused,
|
else
|
||||||
cursor_blink_visible,
|
renderer.cursorStyle(
|
||||||
);
|
state,
|
||||||
|
self.focused,
|
||||||
|
cursor_blink_visible,
|
||||||
|
);
|
||||||
|
|
||||||
// Get our preedit state
|
// Get our preedit state
|
||||||
const preedit: ?renderer.State.Preedit = preedit: {
|
const preedit: ?renderer.State.Preedit = preedit: {
|
||||||
@ -2652,24 +2655,52 @@ fn addCursor(
|
|||||||
break :alpha @intFromFloat(@ceil(alpha));
|
break :alpha @intFromFloat(@ceil(alpha));
|
||||||
};
|
};
|
||||||
|
|
||||||
const sprite: font.Sprite = switch (cursor_style) {
|
const render = switch (cursor_style) {
|
||||||
.block => .cursor_rect,
|
.block,
|
||||||
.block_hollow => .cursor_hollow_rect,
|
.block_hollow,
|
||||||
.bar => .cursor_bar,
|
.bar,
|
||||||
.underline => .underline,
|
.underline,
|
||||||
};
|
=> render: {
|
||||||
|
const sprite: font.Sprite = switch (cursor_style) {
|
||||||
|
.block => .cursor_rect,
|
||||||
|
.block_hollow => .cursor_hollow_rect,
|
||||||
|
.bar => .cursor_bar,
|
||||||
|
.underline => .underline,
|
||||||
|
.lock => unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
const render = self.font_grid.renderGlyph(
|
break :render self.font_grid.renderGlyph(
|
||||||
self.alloc,
|
self.alloc,
|
||||||
font.sprite_index,
|
font.sprite_index,
|
||||||
@intFromEnum(sprite),
|
@intFromEnum(sprite),
|
||||||
.{
|
.{
|
||||||
.cell_width = if (wide) 2 else 1,
|
.cell_width = if (wide) 2 else 1,
|
||||||
.grid_metrics = self.grid_metrics,
|
.grid_metrics = self.grid_metrics,
|
||||||
|
},
|
||||||
|
) catch |err| {
|
||||||
|
log.warn("error rendering cursor glyph err={}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
.lock => self.font_grid.renderCodepoint(
|
||||||
|
self.alloc,
|
||||||
|
0xF023, // lock symbol
|
||||||
|
.regular,
|
||||||
|
.text,
|
||||||
|
.{
|
||||||
|
.cell_width = if (wide) 2 else 1,
|
||||||
|
.grid_metrics = self.grid_metrics,
|
||||||
|
},
|
||||||
|
) catch |err| {
|
||||||
|
log.warn("error rendering cursor glyph err={}", .{err});
|
||||||
|
return;
|
||||||
|
} orelse {
|
||||||
|
// This should never happen because we embed nerd
|
||||||
|
// fonts so we just log and return instead of fallback.
|
||||||
|
log.warn("failed to find lock symbol for cursor codepoint=0xF023", .{});
|
||||||
|
return;
|
||||||
},
|
},
|
||||||
) catch |err| {
|
|
||||||
log.warn("error rendering cursor glyph err={}", .{err});
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.cells.setCursor(.{
|
self.cells.setCursor(.{
|
||||||
|
@ -767,11 +767,14 @@ pub fn updateFrame(
|
|||||||
errdefer screen_copy.deinit();
|
errdefer screen_copy.deinit();
|
||||||
|
|
||||||
// Whether to draw our cursor or not.
|
// Whether to draw our cursor or not.
|
||||||
const cursor_style = renderer.cursorStyle(
|
const cursor_style = if (state.terminal.flags.password_input)
|
||||||
state,
|
.lock
|
||||||
self.focused,
|
else
|
||||||
cursor_blink_visible,
|
renderer.cursorStyle(
|
||||||
);
|
state,
|
||||||
|
self.focused,
|
||||||
|
cursor_blink_visible,
|
||||||
|
);
|
||||||
|
|
||||||
// Get our preedit state
|
// Get our preedit state
|
||||||
const preedit: ?renderer.State.Preedit = preedit: {
|
const preedit: ?renderer.State.Preedit = preedit: {
|
||||||
@ -1537,24 +1540,52 @@ fn addCursor(
|
|||||||
break :alpha @intFromFloat(@ceil(alpha));
|
break :alpha @intFromFloat(@ceil(alpha));
|
||||||
};
|
};
|
||||||
|
|
||||||
const sprite: font.Sprite = switch (cursor_style) {
|
const render = switch (cursor_style) {
|
||||||
.block => .cursor_rect,
|
.block,
|
||||||
.block_hollow => .cursor_hollow_rect,
|
.block_hollow,
|
||||||
.bar => .cursor_bar,
|
.bar,
|
||||||
.underline => .underline,
|
.underline,
|
||||||
};
|
=> render: {
|
||||||
|
const sprite: font.Sprite = switch (cursor_style) {
|
||||||
|
.block => .cursor_rect,
|
||||||
|
.block_hollow => .cursor_hollow_rect,
|
||||||
|
.bar => .cursor_bar,
|
||||||
|
.underline => .underline,
|
||||||
|
.lock => unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
const render = self.font_grid.renderGlyph(
|
break :render self.font_grid.renderGlyph(
|
||||||
self.alloc,
|
self.alloc,
|
||||||
font.sprite_index,
|
font.sprite_index,
|
||||||
@intFromEnum(sprite),
|
@intFromEnum(sprite),
|
||||||
.{
|
.{
|
||||||
.cell_width = if (wide) 2 else 1,
|
.cell_width = if (wide) 2 else 1,
|
||||||
.grid_metrics = self.grid_metrics,
|
.grid_metrics = self.grid_metrics,
|
||||||
|
},
|
||||||
|
) catch |err| {
|
||||||
|
log.warn("error rendering cursor glyph err={}", .{err});
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
.lock => self.font_grid.renderCodepoint(
|
||||||
|
self.alloc,
|
||||||
|
0xF023, // lock symbol
|
||||||
|
.regular,
|
||||||
|
.text,
|
||||||
|
.{
|
||||||
|
.cell_width = if (wide) 2 else 1,
|
||||||
|
.grid_metrics = self.grid_metrics,
|
||||||
|
},
|
||||||
|
) catch |err| {
|
||||||
|
log.warn("error rendering cursor glyph err={}", .{err});
|
||||||
|
return null;
|
||||||
|
} orelse {
|
||||||
|
// This should never happen because we embed nerd
|
||||||
|
// fonts so we just log and return instead of fallback.
|
||||||
|
log.warn("failed to find lock symbol for cursor codepoint=0xF023", .{});
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
) catch |err| {
|
|
||||||
log.warn("error rendering cursor glyph err={}", .{err});
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try self.cells.append(self.alloc, .{
|
try self.cells.append(self.alloc, .{
|
||||||
|
@ -6,11 +6,15 @@ const State = @import("State.zig");
|
|||||||
/// This is a superset of terminal cursor styles since the renderer supports
|
/// This is a superset of terminal cursor styles since the renderer supports
|
||||||
/// some additional cursor states such as the hollow block.
|
/// some additional cursor states such as the hollow block.
|
||||||
pub const Style = enum {
|
pub const Style = enum {
|
||||||
|
// Typical cursor input styles
|
||||||
block,
|
block,
|
||||||
block_hollow,
|
block_hollow,
|
||||||
bar,
|
bar,
|
||||||
underline,
|
underline,
|
||||||
|
|
||||||
|
// Special cursor styles
|
||||||
|
lock,
|
||||||
|
|
||||||
/// Create a cursor style from the terminal style request.
|
/// Create a cursor style from the terminal style request.
|
||||||
pub fn fromTerminal(term: terminal.CursorStyle) ?Style {
|
pub fn fromTerminal(term: terminal.CursorStyle) ?Style {
|
||||||
return switch (term) {
|
return switch (term) {
|
||||||
|
@ -119,6 +119,11 @@ flags: packed struct {
|
|||||||
/// True if the window is focused.
|
/// True if the window is focused.
|
||||||
focused: bool = true,
|
focused: bool = true,
|
||||||
|
|
||||||
|
/// True if the terminal is in a password entry mode. This is set
|
||||||
|
/// to true based on termios state. This is set
|
||||||
|
/// to true based on termios state.
|
||||||
|
password_input: bool = false,
|
||||||
|
|
||||||
/// Dirty flags for the renderer.
|
/// Dirty flags for the renderer.
|
||||||
dirty: Dirty = .{},
|
dirty: Dirty = .{},
|
||||||
} = .{},
|
} = .{},
|
||||||
|
@ -21,12 +21,16 @@ const terminal = @import("../terminal/main.zig");
|
|||||||
const termio = @import("../termio.zig");
|
const termio = @import("../termio.zig");
|
||||||
const Command = @import("../Command.zig");
|
const Command = @import("../Command.zig");
|
||||||
const SegmentedPool = @import("../segmented_pool.zig").SegmentedPool;
|
const SegmentedPool = @import("../segmented_pool.zig").SegmentedPool;
|
||||||
const Pty = @import("../pty.zig").Pty;
|
const ptypkg = @import("../pty.zig");
|
||||||
|
const Pty = ptypkg.Pty;
|
||||||
const EnvMap = std.process.EnvMap;
|
const EnvMap = std.process.EnvMap;
|
||||||
const windows = internal_os.windows;
|
const windows = internal_os.windows;
|
||||||
|
|
||||||
const log = std.log.scoped(.io_exec);
|
const log = std.log.scoped(.io_exec);
|
||||||
|
|
||||||
|
/// The termios poll rate in milliseconds.
|
||||||
|
const TERMIOS_POLL_MS = 200;
|
||||||
|
|
||||||
/// The subprocess state for our exec backend.
|
/// The subprocess state for our exec backend.
|
||||||
subprocess: Subprocess,
|
subprocess: Subprocess,
|
||||||
|
|
||||||
@ -114,6 +118,12 @@ pub fn threadEnter(
|
|||||||
var process = try xev.Process.init(pid);
|
var process = try xev.Process.init(pid);
|
||||||
errdefer process.deinit();
|
errdefer process.deinit();
|
||||||
|
|
||||||
|
// Start our timer to read termios state changes. This is used
|
||||||
|
// to detect things such as when password input is being done
|
||||||
|
// so we can render the terminal in a different way.
|
||||||
|
var termios_timer = try xev.Timer.init();
|
||||||
|
errdefer termios_timer.deinit();
|
||||||
|
|
||||||
// Start our read thread
|
// Start our read thread
|
||||||
const read_thread = try std.Thread.spawn(
|
const read_thread = try std.Thread.spawn(
|
||||||
.{},
|
.{},
|
||||||
@ -131,7 +141,9 @@ pub fn threadEnter(
|
|||||||
.process = process,
|
.process = process,
|
||||||
.read_thread = read_thread,
|
.read_thread = read_thread,
|
||||||
.read_thread_pipe = pipe[1],
|
.read_thread_pipe = pipe[1],
|
||||||
.read_thread_fd = if (builtin.os.tag == .windows) pty_fds.read else {},
|
.read_thread_fd = pty_fds.read,
|
||||||
|
.termios_timer = termios_timer,
|
||||||
|
.renderer_wakeup = io.renderer_wakeup,
|
||||||
} };
|
} };
|
||||||
|
|
||||||
// Start our process watcher
|
// Start our process watcher
|
||||||
@ -142,6 +154,20 @@ pub fn threadEnter(
|
|||||||
td,
|
td,
|
||||||
processExit,
|
processExit,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Start our termios timer. We only support this on Windows.
|
||||||
|
// Fundamentally, we could support this on Windows so we're just
|
||||||
|
// waiting for someone to implement it.
|
||||||
|
if (comptime builtin.os.tag != .windows) {
|
||||||
|
termios_timer.run(
|
||||||
|
td.loop,
|
||||||
|
&td.backend.exec.termios_timer_c,
|
||||||
|
TERMIOS_POLL_MS,
|
||||||
|
termio.Termio.ThreadData,
|
||||||
|
td,
|
||||||
|
termiosTimer,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn threadExit(self: *Exec, td: *termio.Termio.ThreadData) void {
|
pub fn threadExit(self: *Exec, td: *termio.Termio.ThreadData) void {
|
||||||
@ -170,6 +196,32 @@ pub fn threadExit(self: *Exec, td: *termio.Termio.ThreadData) void {
|
|||||||
exec.read_thread.join();
|
exec.read_thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn focusGained(
|
||||||
|
self: *Exec,
|
||||||
|
td: *termio.Termio.ThreadData,
|
||||||
|
focused: bool,
|
||||||
|
) !void {
|
||||||
|
_ = self;
|
||||||
|
|
||||||
|
assert(td.backend == .exec);
|
||||||
|
const execdata = &td.backend.exec;
|
||||||
|
|
||||||
|
if (!focused) {
|
||||||
|
// Flag the timer to end on the next iteration. This is
|
||||||
|
// a lot cheaper than doing full timer cancellation.
|
||||||
|
execdata.termios_timer_running = false;
|
||||||
|
} else {
|
||||||
|
// If we're focused, we want to start our termios timer. We
|
||||||
|
// only do this if it isn't already running. We use the termios
|
||||||
|
// callback because that'll trigger an immediate state check AND
|
||||||
|
// start the timer.
|
||||||
|
if (execdata.termios_timer_c.state() != .active) {
|
||||||
|
execdata.termios_timer_running = true;
|
||||||
|
_ = termiosTimer(td, undefined, undefined, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn resize(
|
pub fn resize(
|
||||||
self: *Exec,
|
self: *Exec,
|
||||||
grid_size: renderer.GridSize,
|
grid_size: renderer.GridSize,
|
||||||
@ -359,6 +411,85 @@ fn processExit(
|
|||||||
return .disarm;
|
return .disarm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn termiosTimer(
|
||||||
|
td_: ?*termio.Termio.ThreadData,
|
||||||
|
_: *xev.Loop,
|
||||||
|
_: *xev.Completion,
|
||||||
|
r: xev.Timer.RunError!void,
|
||||||
|
) xev.CallbackAction {
|
||||||
|
// log.debug("termios timer fired", .{});
|
||||||
|
|
||||||
|
// This should never happen because we guard starting our
|
||||||
|
// timer on windows but we want this assertion to fire if
|
||||||
|
// we ever do start the timer on windows.
|
||||||
|
// TODO: support on windows
|
||||||
|
if (comptime builtin.os.tag == .windows) {
|
||||||
|
@panic("termios timer not implemented on Windows");
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = r catch |err| switch (err) {
|
||||||
|
// This is sent when our timer is canceled. That's fine.
|
||||||
|
error.Canceled => return .disarm,
|
||||||
|
|
||||||
|
else => {
|
||||||
|
log.warn("error in termios timer callback err={}", .{err});
|
||||||
|
@panic("crash in termios timer callback");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const td = td_.?;
|
||||||
|
assert(td.backend == .exec);
|
||||||
|
const exec = &td.backend.exec;
|
||||||
|
|
||||||
|
// This is kind of hacky but we rebuild a Pty struct to get the
|
||||||
|
// termios data.
|
||||||
|
const mode: ptypkg.Mode = (Pty{
|
||||||
|
.master = exec.read_thread_fd,
|
||||||
|
.slave = undefined,
|
||||||
|
}).getMode() catch |err| err: {
|
||||||
|
log.warn("error getting termios mode err={}", .{err});
|
||||||
|
|
||||||
|
// If we have an error we return the default mode values
|
||||||
|
// which are the likely values.
|
||||||
|
break :err .{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the mode changed, then we process it.
|
||||||
|
if (!std.meta.eql(mode, exec.termios_mode)) {
|
||||||
|
log.debug("termios change mode={}", .{mode});
|
||||||
|
exec.termios_mode = mode;
|
||||||
|
|
||||||
|
{
|
||||||
|
td.renderer_state.mutex.lock();
|
||||||
|
defer td.renderer_state.mutex.unlock();
|
||||||
|
const t = td.renderer_state.terminal;
|
||||||
|
|
||||||
|
// We assume we're in some sort of password input if we're
|
||||||
|
// in canonical mode and not echoing. This is a heuristic.
|
||||||
|
t.flags.password_input = mode.canonical and !mode.echo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify the renderer of our state change
|
||||||
|
exec.renderer_wakeup.notify() catch |err| {
|
||||||
|
log.warn("error notifying renderer err={}", .{err});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repeat the timer
|
||||||
|
if (exec.termios_timer_running) {
|
||||||
|
exec.termios_timer.run(
|
||||||
|
td.loop,
|
||||||
|
&exec.termios_timer_c,
|
||||||
|
TERMIOS_POLL_MS,
|
||||||
|
termio.Termio.ThreadData,
|
||||||
|
td,
|
||||||
|
termiosTimer,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .disarm;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn queueWrite(
|
pub fn queueWrite(
|
||||||
self: *Exec,
|
self: *Exec,
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
@ -498,7 +629,19 @@ pub const ThreadData = struct {
|
|||||||
/// Reader thread state
|
/// Reader thread state
|
||||||
read_thread: std.Thread,
|
read_thread: std.Thread,
|
||||||
read_thread_pipe: posix.fd_t,
|
read_thread_pipe: posix.fd_t,
|
||||||
read_thread_fd: if (builtin.os.tag == .windows) posix.fd_t else void,
|
read_thread_fd: posix.fd_t,
|
||||||
|
|
||||||
|
/// The timer to detect termios state changes.
|
||||||
|
termios_timer: xev.Timer,
|
||||||
|
termios_timer_c: xev.Completion = .{},
|
||||||
|
termios_timer_running: bool = true,
|
||||||
|
|
||||||
|
/// The last known termios mode. Used for change detection
|
||||||
|
/// to prevent unnecessary locking of expensive mutexes.
|
||||||
|
termios_mode: ptypkg.Mode = .{},
|
||||||
|
|
||||||
|
/// The handle to wake up the renderer.
|
||||||
|
renderer_wakeup: xev.Async,
|
||||||
|
|
||||||
pub fn deinit(self: *ThreadData, alloc: Allocator) void {
|
pub fn deinit(self: *ThreadData, alloc: Allocator) void {
|
||||||
posix.close(self.read_thread_pipe);
|
posix.close(self.read_thread_pipe);
|
||||||
@ -514,6 +657,9 @@ pub const ThreadData = struct {
|
|||||||
|
|
||||||
// Stop our write stream
|
// Stop our write stream
|
||||||
self.write_stream.deinit();
|
self.write_stream.deinit();
|
||||||
|
|
||||||
|
// Stop our termios timer
|
||||||
|
self.termios_timer.deinit();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -523,8 +523,18 @@ pub fn childExitedAbnormally(self: *Termio, exit_code: u32, runtime_ms: u64) !vo
|
|||||||
|
|
||||||
/// Called when focus is gained or lost (when focus events are enabled)
|
/// Called when focus is gained or lost (when focus events are enabled)
|
||||||
pub fn focusGained(self: *Termio, td: *ThreadData, focused: bool) !void {
|
pub fn focusGained(self: *Termio, td: *ThreadData, focused: bool) !void {
|
||||||
const seq = if (focused) "\x1b[I" else "\x1b[O";
|
self.renderer_state.mutex.lock();
|
||||||
try self.queueWrite(td, seq, false);
|
const focus_event = self.renderer_state.terminal.modes.get(.focus_event);
|
||||||
|
self.renderer_state.mutex.unlock();
|
||||||
|
|
||||||
|
// If we have focus events enabled, we send the focus event.
|
||||||
|
if (focus_event) {
|
||||||
|
const seq = if (focused) "\x1b[I" else "\x1b[O";
|
||||||
|
try self.queueWrite(td, seq, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We always notify our backend of focus changes.
|
||||||
|
try self.backend.focusGained(td, focused);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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
|
||||||
|
@ -62,6 +62,16 @@ pub const Backend = union(Kind) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn focusGained(
|
||||||
|
self: *Backend,
|
||||||
|
td: *termio.Termio.ThreadData,
|
||||||
|
focused: bool,
|
||||||
|
) !void {
|
||||||
|
switch (self.*) {
|
||||||
|
.exec => |*exec| try exec.focusGained(td, focused),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn resize(
|
pub fn resize(
|
||||||
self: *Backend,
|
self: *Backend,
|
||||||
grid_size: renderer.GridSize,
|
grid_size: renderer.GridSize,
|
||||||
|
Reference in New Issue
Block a user