fix: use blocking tty io with libvaxis
This resolves WouldBlock issues with libvaxis and tty issues with macos.
This commit is contained in:
parent
f0d3db298d
commit
ced45d50e4
2 changed files with 106 additions and 33 deletions
|
@ -25,6 +25,7 @@ vx: vaxis.Vaxis,
|
||||||
|
|
||||||
no_alternate: bool,
|
no_alternate: bool,
|
||||||
event_buffer: std.ArrayList(u8),
|
event_buffer: std.ArrayList(u8),
|
||||||
|
input_buffer: std.ArrayList(u8),
|
||||||
|
|
||||||
bracketed_paste: bool = false,
|
bracketed_paste: bool = false,
|
||||||
bracketed_paste_buffer: std.ArrayList(u8),
|
bracketed_paste_buffer: std.ArrayList(u8),
|
||||||
|
@ -54,6 +55,7 @@ pub fn init(a: std.mem.Allocator, handler_ctx: *anyopaque, no_alternate: bool) !
|
||||||
.vx = try vaxis.init(a, opts),
|
.vx = try vaxis.init(a, opts),
|
||||||
.no_alternate = no_alternate,
|
.no_alternate = no_alternate,
|
||||||
.event_buffer = std.ArrayList(u8).init(a),
|
.event_buffer = std.ArrayList(u8).init(a),
|
||||||
|
.input_buffer = std.ArrayList(u8).init(a),
|
||||||
.bracketed_paste_buffer = std.ArrayList(u8).init(a),
|
.bracketed_paste_buffer = std.ArrayList(u8).init(a),
|
||||||
.handler_ctx = handler_ctx,
|
.handler_ctx = handler_ctx,
|
||||||
.logger = log.logger(log_name),
|
.logger = log.logger(log_name),
|
||||||
|
@ -63,6 +65,7 @@ pub fn init(a: std.mem.Allocator, handler_ctx: *anyopaque, no_alternate: bool) !
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
self.vx.deinit(self.a);
|
self.vx.deinit(self.a);
|
||||||
self.bracketed_paste_buffer.deinit();
|
self.bracketed_paste_buffer.deinit();
|
||||||
|
self.input_buffer.deinit();
|
||||||
self.event_buffer.deinit();
|
self.event_buffer.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +73,7 @@ pub fn run(self: *Self) !void {
|
||||||
if (self.vx.tty == null) self.vx.tty = try vaxis.Tty.init();
|
if (self.vx.tty == null) self.vx.tty = try vaxis.Tty.init();
|
||||||
if (!self.no_alternate) try self.vx.enterAltScreen();
|
if (!self.no_alternate) try self.vx.enterAltScreen();
|
||||||
try self.vx.queryTerminal();
|
try self.vx.queryTerminal();
|
||||||
const ws = try vaxis.Tty.getWinsize(self.input_fd());
|
const ws = try vaxis.Tty.getWinsize(self.input_fd_blocking());
|
||||||
try self.vx.resize(self.a, ws);
|
try self.vx.resize(self.a, ws);
|
||||||
self.vx.queueRefresh();
|
self.vx.queueRefresh();
|
||||||
try self.vx.setMouseMode(true);
|
try self.vx.setMouseMode(true);
|
||||||
|
@ -82,7 +85,7 @@ pub fn render(self: *Self) !void {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refresh(self: *Self) !void {
|
pub fn refresh(self: *Self) !void {
|
||||||
const ws = try vaxis.Tty.getWinsize(self.input_fd());
|
const ws = try vaxis.Tty.getWinsize(self.input_fd_blocking());
|
||||||
try self.vx.resize(self.a, ws);
|
try self.vx.resize(self.a, ws);
|
||||||
self.vx.queueRefresh();
|
self.vx.queueRefresh();
|
||||||
}
|
}
|
||||||
|
@ -102,7 +105,7 @@ pub fn stdplane(self: *Self) Plane {
|
||||||
return plane;
|
return plane;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn input_fd(self: Self) i32 {
|
pub fn input_fd_blocking(self: Self) i32 {
|
||||||
return self.vx.tty.?.fd;
|
return self.vx.tty.?.fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,19 +113,27 @@ pub fn leave_alternate_screen(self: *Self) void {
|
||||||
self.vx.exitAltScreen() catch {};
|
self.vx.exitAltScreen() catch {};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_input(self: *Self) !void {
|
pub fn process_input(self: *Self, input_: []const u8) !void {
|
||||||
var parser: vaxis.Parser = .{
|
var parser: vaxis.Parser = .{
|
||||||
.grapheme_data = &self.vx.screen.unicode.grapheme_data,
|
.grapheme_data = &self.vx.screen.unicode.grapheme_data,
|
||||||
};
|
};
|
||||||
var buf: [1024]u8 = undefined;
|
try self.input_buffer.appendSlice(input_);
|
||||||
var start: usize = 0;
|
var buf = self.input_buffer.items;
|
||||||
const n = std.posix.read(self.input_fd(), &buf) catch |e| switch (e) {
|
defer {
|
||||||
error.WouldBlock => return,
|
if (buf.len == 0) {
|
||||||
else => return e,
|
self.input_buffer.clearRetainingCapacity();
|
||||||
};
|
} else {
|
||||||
while (start < n) {
|
const rest = self.a.alloc(u8, buf.len) catch |e| std.debug.panic("{any}", .{e});
|
||||||
const result = try parser.parse(buf[start..n]);
|
@memcpy(rest, buf);
|
||||||
start += result.n;
|
self.input_buffer.deinit();
|
||||||
|
self.input_buffer = std.ArrayList(u8).fromOwnedSlice(self.a, rest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (buf.len > 0) {
|
||||||
|
const result = try parser.parse(buf);
|
||||||
|
if (result.n == 0)
|
||||||
|
return;
|
||||||
|
buf = buf[result.n..];
|
||||||
const event = result.event orelse continue;
|
const event = result.event orelse continue;
|
||||||
switch (event) {
|
switch (event) {
|
||||||
.key_press => |key_| {
|
.key_press => |key_| {
|
||||||
|
@ -176,16 +187,17 @@ pub fn process_input(self: *Self) !void {
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
})),
|
})),
|
||||||
.drag => f(self.handler_ctx, @intCast(mouse.row), @intCast(mouse.col), try self.fmtmsg(.{
|
.drag => if (self.dispatch_mouse_drag) |f_|
|
||||||
"D",
|
f_(self.handler_ctx, @intCast(mouse.row), @intCast(mouse.col), true, try self.fmtmsg(.{
|
||||||
event_type.PRESS,
|
"D",
|
||||||
@intFromEnum(mouse.button),
|
event_type.PRESS,
|
||||||
input.utils.button_id_string(@intFromEnum(mouse.button)),
|
@intFromEnum(mouse.button),
|
||||||
mouse.col,
|
input.utils.button_id_string(@intFromEnum(mouse.button)),
|
||||||
mouse.row,
|
mouse.col,
|
||||||
0,
|
mouse.row,
|
||||||
0,
|
0,
|
||||||
})),
|
0,
|
||||||
|
})),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
.focus_in => {
|
.focus_in => {
|
||||||
|
|
|
@ -51,6 +51,7 @@ init_timer: ?tp.timeout,
|
||||||
sigwinch_signal: ?tp.signal = null,
|
sigwinch_signal: ?tp.signal = null,
|
||||||
no_sleep: bool = false,
|
no_sleep: bool = false,
|
||||||
final_exit: []const u8 = "normal",
|
final_exit: []const u8 = "normal",
|
||||||
|
input_reader: ?*InputReader = null,
|
||||||
|
|
||||||
const idle_frames = 1;
|
const idle_frames = 1;
|
||||||
|
|
||||||
|
@ -115,12 +116,16 @@ fn init(a: Allocator) !*Self {
|
||||||
defer instance_ = null;
|
defer instance_ = null;
|
||||||
|
|
||||||
try self.rdr.run();
|
try self.rdr.run();
|
||||||
self.fd_stdin = try tp.file_descriptor.init("stdin", self.rdr.input_fd());
|
if (comptime @hasDecl(renderer, "input_fd"))
|
||||||
// self.fd_stdin = try tp.file_descriptor.init("stdin", std.os.STDIN_FILENO);
|
self.fd_stdin = try tp.file_descriptor.init("stdin", self.rdr.input_fd());
|
||||||
const n = self.rdr.stdplane();
|
const n = self.rdr.stdplane();
|
||||||
|
|
||||||
try frame_clock.start();
|
try frame_clock.start();
|
||||||
try self.fd_stdin.wait_read();
|
if (comptime @hasDecl(renderer, "input_fd")) {
|
||||||
|
try self.fd_stdin.wait_read();
|
||||||
|
} else {
|
||||||
|
self.input_reader = try InputReader.create(a, self.rdr.input_fd_blocking());
|
||||||
|
}
|
||||||
|
|
||||||
self.rdr.handler_ctx = self;
|
self.rdr.handler_ctx = self;
|
||||||
self.rdr.dispatch_input = dispatch_input;
|
self.rdr.dispatch_input = dispatch_input;
|
||||||
|
@ -156,8 +161,10 @@ fn deinit(self: *Self) void {
|
||||||
self.frame_clock.deinit();
|
self.frame_clock.deinit();
|
||||||
self.rdr.stop();
|
self.rdr.stop();
|
||||||
self.rdr.deinit();
|
self.rdr.deinit();
|
||||||
self.fd_stdin.deinit();
|
if (comptime @hasDecl(renderer, "input_fd"))
|
||||||
|
self.fd_stdin.deinit();
|
||||||
self.logger.deinit();
|
self.logger.deinit();
|
||||||
|
if (self.input_reader) |p| p.stop();
|
||||||
self.a.destroy(self);
|
self.a.destroy(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +179,9 @@ fn receive(self: *Self, from: tp.pid_ref, m: tp.message) tp.result {
|
||||||
instance_ = self;
|
instance_ = self;
|
||||||
defer instance_ = null;
|
defer instance_ = null;
|
||||||
errdefer self.deinit();
|
errdefer self.deinit();
|
||||||
errdefer self.fd_stdin.cancel() catch {};
|
|
||||||
|
errdefer if (comptime @hasDecl(renderer, "input_fd"))
|
||||||
|
self.fd_stdin.cancel() catch {};
|
||||||
errdefer self.rdr.leave_alternate_screen();
|
errdefer self.rdr.leave_alternate_screen();
|
||||||
self.receive_safe(from, m) catch |e| {
|
self.receive_safe(from, m) catch |e| {
|
||||||
if (std.mem.eql(u8, "normal", tp.error_text()))
|
if (std.mem.eql(u8, "normal", tp.error_text()))
|
||||||
|
@ -227,11 +236,22 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) tp.result {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.dispatch_input_fd(m) catch |e| b: {
|
if (comptime @hasDecl(renderer, "input_fd")) {
|
||||||
self.logger.err("input dispatch", e);
|
if (self.dispatch_input_fd(m) catch |e| b: {
|
||||||
break :b true;
|
self.logger.err("input dispatch", e);
|
||||||
})
|
break :b true;
|
||||||
return;
|
})
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
var input: []const u8 = undefined;
|
||||||
|
if (try m.match(.{ "process_input", tp.extract(&input) })) {
|
||||||
|
self.rdr.process_input(input) catch |e| return tp.exit_error(e);
|
||||||
|
try self.dispatch_flush_input_event();
|
||||||
|
if (self.unrendered_input_events_count > 0 and !self.frame_clock_running)
|
||||||
|
need_render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (try m.match(.{"render"})) {
|
if (try m.match(.{"render"})) {
|
||||||
if (!self.frame_clock_running)
|
if (!self.frame_clock_running)
|
||||||
|
@ -789,3 +809,44 @@ pub const fallbacks: []const FallBack = &[_]FallBack{
|
||||||
.{ .ts = "repeat", .tm = "keyword.control.flow" },
|
.{ .ts = "repeat", .tm = "keyword.control.flow" },
|
||||||
.{ .ts = "field", .tm = "variable" },
|
.{ .ts = "field", .tm = "variable" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const InputReader = struct {
|
||||||
|
a: std.mem.Allocator,
|
||||||
|
fd: std.posix.fd_t,
|
||||||
|
pid: tp.pid,
|
||||||
|
id: ?std.Thread.Id = null,
|
||||||
|
|
||||||
|
fn create(a: std.mem.Allocator, fd: std.posix.fd_t) error{Exit}!*InputReader {
|
||||||
|
const self = a.create(InputReader) catch |e| return tp.exit_error(e);
|
||||||
|
self.* = .{
|
||||||
|
.a = a,
|
||||||
|
.fd = fd,
|
||||||
|
.pid = tp.self_pid().clone(),
|
||||||
|
};
|
||||||
|
const pid = tp.spawn_link(self.a, self, InputReader.start, "tui.InputReader") catch |e| return tp.exit_error(e);
|
||||||
|
pid.deinit();
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(self: *InputReader) void {
|
||||||
|
self.pid.deinit();
|
||||||
|
self.a.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(self: *InputReader) tp.result {
|
||||||
|
defer self.deinit();
|
||||||
|
self.id = std.Thread.getCurrentId();
|
||||||
|
var buf: [4096]u8 = undefined;
|
||||||
|
while (true) {
|
||||||
|
const n = std.posix.read(self.fd, &buf) catch |e| return tp.exit_error(e);
|
||||||
|
if (n == 0)
|
||||||
|
return tp.exit_normal();
|
||||||
|
try self.pid.send(.{ "process_input", buf[0..n] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(self: *InputReader) void {
|
||||||
|
if (self.id) |id|
|
||||||
|
_ = std.os.linux.tkill(@intCast(id), std.os.linux.SIG.INT);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue