const signals = @import("signals.zig"); const std = @import("std"); const terminal = @import("terminal.zig"); pub const Command = enum { Pause, Quit, Reset, }; pub const Poller = struct { stdin_fd: std.posix.fd_t, signals_fd: std.posix.fd_t, terminal: ?terminal.Terminal, pub fn init(allocator: std.mem.Allocator) !Poller { _ = allocator; const opt_terminal = blk: { break :blk terminal.Terminal.init() catch |err| { std.log.debug("failed to init terminal: {any}", .{err}); break :blk null; }; }; errdefer { if (opt_terminal) |terminal_| { terminal_.deinit(); } } const signals_file = try signals.open(); const signals_fd = signals_file.handle; return Poller{ .terminal = opt_terminal, .stdin_fd = std.Io.File.stdin().handle, .signals_fd = signals_fd, }; } pub fn interactive(self: *Poller) bool { return self.terminal != null; } pub fn deinit(self: *Poller) void { if (self.terminal) |terminal_| terminal_.deinit(); } pub fn pollTimeout(self: *Poller, nanoseconds: u64) !?Command { var fds = [2]std.posix.pollfd{ .{ .fd = self.stdin_fd, .events = std.posix.POLL.IN, .revents = 0 }, .{ .fd = self.signals_fd, .events = std.posix.POLL.IN, .revents = 0 }, }; const timeout_ms = @as(i32, @intCast(@divTrunc(nanoseconds, std.time.ns_per_ms))); const n = try std.posix.poll(&fds, timeout_ms); if (n == 0) return null; if (fds[0].revents & std.posix.POLL.IN != 0) { var buf: [1]u8 = undefined; const bytes_read = try std.posix.read(self.stdin_fd, &buf); if (bytes_read > 0) { switch (buf[0]) { 'p' => return Command.Pause, 'q' => return Command.Quit, 'r' => return Command.Reset, else => {}, } } } if (fds[1].revents & std.posix.POLL.IN != 0) { const signo = try signals.readSignal(self.signals_fd); switch (signo) { std.posix.SIG.USR1 => return Command.Pause, std.posix.SIG.USR2 => return Command.Reset, else => unreachable, } } return null; } };