133 lines
3 KiB
Zig
133 lines
3 KiB
Zig
|
const std = @import("std");
|
||
|
const os = std.os;
|
||
|
const xev = @import("xev");
|
||
|
|
||
|
const log = std.log.scoped(.tty);
|
||
|
|
||
|
const Tty = @This();
|
||
|
|
||
|
/// the original state of the terminal, prior to calling makeRaw
|
||
|
termios: os.termios,
|
||
|
|
||
|
/// The file descriptor we are using for I/O
|
||
|
fd: os.fd_t,
|
||
|
|
||
|
/// Stream attached to our fd
|
||
|
stream: xev.Stream,
|
||
|
|
||
|
/// event loop
|
||
|
loop: xev.Loop,
|
||
|
|
||
|
read_buffer: [1024]u8 = undefined,
|
||
|
|
||
|
/// initializes a Tty instance by opening /dev/tty and "making it raw"
|
||
|
pub fn init() !Tty {
|
||
|
// Open our tty
|
||
|
const fd = try os.open("/dev/tty", os.system.O.RDWR, 0);
|
||
|
|
||
|
// Initialize the stream
|
||
|
const stream = xev.Stream.initFd(fd);
|
||
|
|
||
|
// Initialize event loop
|
||
|
const loop = try xev.Loop.init(.{});
|
||
|
|
||
|
// Set the termios of the tty
|
||
|
const termios = try makeRaw(fd);
|
||
|
|
||
|
return Tty{
|
||
|
.fd = fd,
|
||
|
.stream = stream,
|
||
|
.termios = termios,
|
||
|
.loop = loop,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/// release resources associated with the Tty return it to it's original state
|
||
|
pub fn deinit(self: *Tty) void {
|
||
|
os.tcsetattr(self.fd, .FLUSH, self.termios) catch |err| {
|
||
|
log.err("couldn't restore terminal: {}", .{err});
|
||
|
};
|
||
|
os.close(self.fd);
|
||
|
self.stream.deinit();
|
||
|
self.loop.deinit();
|
||
|
}
|
||
|
|
||
|
/// read input from the tty
|
||
|
pub fn run(self: *Tty) !void {
|
||
|
var c_stream: xev.Completion = undefined;
|
||
|
|
||
|
// Initialize our read event
|
||
|
self.stream.read(
|
||
|
&self.loop,
|
||
|
&c_stream,
|
||
|
.{ .slice = self.read_buffer[0..] },
|
||
|
Tty,
|
||
|
self,
|
||
|
readCallback,
|
||
|
);
|
||
|
|
||
|
try self.loop.run(.until_done);
|
||
|
}
|
||
|
|
||
|
fn readCallback(
|
||
|
ud: ?*Tty,
|
||
|
loop: *xev.Loop,
|
||
|
c: *xev.Completion,
|
||
|
stream: xev.Stream,
|
||
|
buf: xev.ReadBuffer,
|
||
|
r: xev.ReadError!usize,
|
||
|
) xev.CallbackAction {
|
||
|
_ = stream; // autofix
|
||
|
_ = c; // autofix
|
||
|
_ = loop; // autofix
|
||
|
const tty = ud.?;
|
||
|
_ = tty; // autofix
|
||
|
const n = r catch |err| {
|
||
|
// Log the error and shutdown
|
||
|
log.err("read error: {}", .{err});
|
||
|
return .disarm;
|
||
|
};
|
||
|
log.info("{s}\r", .{buf.slice[0..n]});
|
||
|
return .rearm;
|
||
|
}
|
||
|
|
||
|
/// makeRaw enters the raw state for the terminal.
|
||
|
pub fn makeRaw(fd: os.fd_t) !os.termios {
|
||
|
const state = try os.tcgetattr(fd);
|
||
|
var raw = state;
|
||
|
// see termios(3)
|
||
|
raw.iflag &= ~@as(
|
||
|
os.tcflag_t,
|
||
|
os.system.IGNBRK |
|
||
|
os.system.BRKINT |
|
||
|
os.system.PARMRK |
|
||
|
os.system.ISTRIP |
|
||
|
os.system.INLCR |
|
||
|
os.system.IGNCR |
|
||
|
os.system.ICRNL |
|
||
|
os.system.IXON,
|
||
|
);
|
||
|
raw.oflag &= ~@as(os.tcflag_t, os.system.OPOST);
|
||
|
raw.lflag &= ~@as(
|
||
|
os.tcflag_t,
|
||
|
os.system.ECHO |
|
||
|
os.system.ECHONL |
|
||
|
os.system.ICANON |
|
||
|
os.system.ISIG |
|
||
|
os.system.IEXTEN,
|
||
|
);
|
||
|
raw.cflag &= ~@as(
|
||
|
os.tcflag_t,
|
||
|
os.system.CSIZE |
|
||
|
os.system.PARENB,
|
||
|
);
|
||
|
raw.cflag |= @as(
|
||
|
os.tcflag_t,
|
||
|
os.system.CS8,
|
||
|
);
|
||
|
raw.cc[os.system.V.MIN] = 1;
|
||
|
raw.cc[os.system.V.TIME] = 0;
|
||
|
try os.tcsetattr(fd, .FLUSH, raw);
|
||
|
return state;
|
||
|
}
|