mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
termio: poll termios for changes
This commit is contained in:
25
src/pty.zig
25
src/pty.zig
@ -21,6 +21,20 @@ pub const Pty = switch (builtin.os.tag) {
|
||||
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.
|
||||
//
|
||||
// TODO: This should be removed. This is only temporary until we have
|
||||
@ -119,6 +133,17 @@ const PosixPty = struct {
|
||||
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.
|
||||
pub fn getSize(self: Pty) !winsize {
|
||||
var ws: winsize = undefined;
|
||||
|
@ -21,12 +21,16 @@ const terminal = @import("../terminal/main.zig");
|
||||
const termio = @import("../termio.zig");
|
||||
const Command = @import("../Command.zig");
|
||||
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 windows = internal_os.windows;
|
||||
|
||||
const log = std.log.scoped(.io_exec);
|
||||
|
||||
/// The termios poll rate in milliseconds.
|
||||
const TERMIOS_POLL_MS = 500;
|
||||
|
||||
/// The subprocess state for our exec backend.
|
||||
subprocess: Subprocess,
|
||||
|
||||
@ -114,6 +118,12 @@ pub fn threadEnter(
|
||||
var process = try xev.Process.init(pid);
|
||||
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
|
||||
const read_thread = try std.Thread.spawn(
|
||||
.{},
|
||||
@ -131,7 +141,8 @@ pub fn threadEnter(
|
||||
.process = process,
|
||||
.read_thread = read_thread,
|
||||
.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,
|
||||
} };
|
||||
|
||||
// Start our process watcher
|
||||
@ -142,6 +153,20 @@ pub fn threadEnter(
|
||||
td,
|
||||
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 {
|
||||
@ -359,6 +384,62 @@ fn processExit(
|
||||
return .disarm;
|
||||
}
|
||||
|
||||
fn termiosTimer(
|
||||
td_: ?*termio.Termio.ThreadData,
|
||||
_: *xev.Loop,
|
||||
_: *xev.Completion,
|
||||
r: xev.Timer.RunError!void,
|
||||
) xev.CallbackAction {
|
||||
// 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 .{};
|
||||
};
|
||||
|
||||
log.warn("termios mode={}", .{mode});
|
||||
|
||||
// Repeat the timer
|
||||
exec.termios_timer.run(
|
||||
td.loop,
|
||||
&exec.termios_timer_c,
|
||||
TERMIOS_POLL_MS,
|
||||
termio.Termio.ThreadData,
|
||||
td,
|
||||
termiosTimer,
|
||||
);
|
||||
|
||||
return .disarm;
|
||||
}
|
||||
|
||||
pub fn queueWrite(
|
||||
self: *Exec,
|
||||
alloc: Allocator,
|
||||
@ -498,7 +579,11 @@ pub const ThreadData = struct {
|
||||
/// Reader thread state
|
||||
read_thread: std.Thread,
|
||||
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 = .{},
|
||||
|
||||
pub fn deinit(self: *ThreadData, alloc: Allocator) void {
|
||||
posix.close(self.read_thread_pipe);
|
||||
@ -514,6 +599,9 @@ pub const ThreadData = struct {
|
||||
|
||||
// Stop our write stream
|
||||
self.write_stream.deinit();
|
||||
|
||||
// Stop our termios timer
|
||||
self.termios_timer.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user