From 50242b984b8332e2b913ea9e1c8dcf9d01d8be41 Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Tue, 30 Apr 2024 16:42:10 -0500 Subject: [PATCH] loop: fully clean up tty when stopping event loop Previously, stopping the read thread would not fully clean up the tty. This could result in issues such as #9 where the terminal state was borked upon return to shell or if temporarily exiting the TUI. Fixes: #9 --- examples/text_input.zig | 6 +++ src/Loop.zig | 14 ++++-- src/Tty.zig | 24 +++++++++ src/Vaxis.zig | 105 ++++++++++++++++------------------------ 4 files changed, 83 insertions(+), 66 deletions(-) diff --git a/examples/text_input.zig b/examples/text_input.zig index 6b33dba..f4a756d 100644 --- a/examples/text_input.zig +++ b/examples/text_input.zig @@ -82,6 +82,12 @@ pub fn main() !void { vx.queueRefresh(); } else if (key.matches('n', .{ .ctrl = true })) { try vx.notify("vaxis", "hello from vaxis"); + loop.stop(); + var child = std.process.Child.init(&.{"nvim"}, alloc); + _ = try child.spawnAndWait(); + try loop.run(); + try vx.enterAltScreen(); + vx.queueRefresh(); } else if (key.matches(vaxis.Key.enter, .{})) { text_input.clearAndFree(); } else { diff --git a/src/Loop.zig b/src/Loop.zig index 41d2285..5869abf 100644 --- a/src/Loop.zig +++ b/src/Loop.zig @@ -30,10 +30,16 @@ pub fn Loop(comptime T: type) type { /// stops reading from the tty and returns it to it's initial state pub fn stop(self: *Self) void { - if (self.vaxis.tty) |*tty| tty.stop(); - if (self.thread) |thread| { - thread.join(); - self.thread = null; + if (self.vaxis.tty) |*tty| { + // stop the read loop, then join the thread + tty.stop(); + if (self.thread) |thread| { + thread.join(); + self.thread = null; + } + // once thread is closed we can deinit the tty + tty.deinit(); + self.vaxis.tty = null; } } diff --git a/src/Tty.zig b/src/Tty.zig index 38e1781..b38e228 100644 --- a/src/Tty.zig +++ b/src/Tty.zig @@ -25,6 +25,15 @@ should_quit: bool = false, buffered_writer: BufferedWriter, +state: struct { + /// if we are in the alt screen + alt_screen: bool = false, + /// if we have entered kitty keyboard + kitty_keyboard: bool = false, + bracketed_paste: bool = false, + mouse: bool = false, +} = .{}, + /// initializes a Tty instance by opening /dev/tty and "making it raw" pub fn init() !Tty { // Open our tty @@ -42,6 +51,21 @@ pub fn init() !Tty { /// release resources associated with the Tty return it to its original state pub fn deinit(self: *Tty) void { + if (self.state.kitty_keyboard) { + _ = self.write(ctlseqs.csi_u_pop) catch {}; + } + if (self.state.mouse) { + _ = self.write(ctlseqs.mouse_reset) catch {}; + } + if (self.state.bracketed_paste) { + _ = self.write(ctlseqs.bp_reset) catch {}; + } + if (self.state.alt_screen) { + _ = self.write(ctlseqs.rmcup) catch {}; + } + // always show the cursor on exit + _ = self.write(ctlseqs.show_cursor) catch {}; + self.flush() catch {}; posix.tcsetattr(self.fd, .FLUSH, self.termios) catch |err| { log.err("couldn't restore terminal: {}", .{err}); }; diff --git a/src/Vaxis.zig b/src/Vaxis.zig index 56296f6..fb9b2c6 100644 --- a/src/Vaxis.zig +++ b/src/Vaxis.zig @@ -42,15 +42,6 @@ screen: Screen, /// the next render screen_last: InternalScreen = undefined, -state: struct { - /// if we are in the alt screen - alt_screen: bool = false, - /// if we have entered kitty keyboard - kitty_keyboard: bool = false, - bracketed_paste: bool = false, - mouse: bool = false, -} = .{}, - caps: Capabilities = .{}, /// if we should redraw the entire screen on the next render @@ -86,23 +77,7 @@ pub fn init(alloc: std.mem.Allocator, _: Options) !Vaxis { /// optional so applications can choose to not free resources when the /// application will be exiting anyways pub fn deinit(self: *Vaxis, alloc: ?std.mem.Allocator) void { - if (self.tty) |_| { - var tty = &self.tty.?; - if (self.state.kitty_keyboard) { - _ = tty.write(ctlseqs.csi_u_pop) catch {}; - } - if (self.state.mouse) { - _ = tty.write(ctlseqs.mouse_reset) catch {}; - } - if (self.state.bracketed_paste) { - _ = tty.write(ctlseqs.bp_reset) catch {}; - } - if (self.state.alt_screen) { - _ = tty.write(ctlseqs.rmcup) catch {}; - } - // always show the cursor on exit - _ = tty.write(ctlseqs.show_cursor) catch {}; - tty.flush() catch {}; + if (self.tty) |*tty| { tty.deinit(); } if (alloc) |a| { @@ -147,20 +122,22 @@ pub fn window(self: *Vaxis) Window { /// enter the alternate screen. The alternate screen will automatically /// be exited if calling deinit while in the alt screen pub fn enterAltScreen(self: *Vaxis) !void { - if (self.state.alt_screen) return; - var tty = self.tty orelse return; - _ = try tty.write(ctlseqs.smcup); - try tty.flush(); - self.state.alt_screen = true; + if (self.tty) |*tty| { + if (tty.state.alt_screen) return; + _ = try tty.write(ctlseqs.smcup); + try tty.flush(); + tty.state.alt_screen = true; + } } /// exit the alternate screen pub fn exitAltScreen(self: *Vaxis) !void { - if (!self.state.alt_screen) return; - var tty = self.tty orelse return; - _ = try tty.write(ctlseqs.rmcup); - try tty.flush(); - self.state.alt_screen = false; + if (self.tty) |*tty| { + if (!tty.state.alt_screen) return; + _ = try tty.write(ctlseqs.rmcup); + try tty.flush(); + tty.state.alt_screen = false; + } } /// write queries to the terminal to determine capabilities. Individual @@ -487,16 +464,18 @@ pub fn render(self: *Vaxis) !void { } fn enableKittyKeyboard(self: *Vaxis, flags: Key.KittyFlags) !void { - self.state.kitty_keyboard = true; - const flag_int: u5 = @bitCast(flags); - try std.fmt.format( - self.tty.?.buffered_writer.writer(), - ctlseqs.csi_u_push, - .{ - flag_int, - }, - ); - try self.tty.?.flush(); + if (self.tty) |*tty| { + const flag_int: u5 = @bitCast(flags); + try std.fmt.format( + tty.buffered_writer.writer(), + ctlseqs.csi_u_push, + .{ + flag_int, + }, + ); + try tty.flush(); + tty.state.kitty_keyboard = true; + } } /// send a system notification @@ -533,14 +512,15 @@ pub fn setTitle(self: *Vaxis, title: []const u8) !void { // beginning and end of a detected paste. All keystrokes between these // events were pasted pub fn setBracketedPaste(self: *Vaxis, enable: bool) !void { - if (self.tty == null) return; - self.state.bracketed_paste = enable; - const seq = if (enable) - ctlseqs.bp_set - else - ctlseqs.bp_reset; - _ = try self.tty.?.write(seq); - try self.tty.?.flush(); + if (self.tty) |*tty| { + const seq = if (enable) + ctlseqs.bp_set + else + ctlseqs.bp_reset; + _ = try tty.write(seq); + try tty.flush(); + tty.state.bracketed_paste = enable; + } } /// set the mouse shape @@ -550,14 +530,15 @@ pub fn setMouseShape(self: *Vaxis, shape: Shape) void { /// turn mouse reporting on or off pub fn setMouseMode(self: *Vaxis, enable: bool) !void { - var tty = self.tty orelse return; - self.state.mouse = enable; - if (enable) { - _ = try tty.write(ctlseqs.mouse_set); - try tty.flush(); - } else { - _ = try tty.write(ctlseqs.mouse_reset); - try tty.flush(); + if (self.tty) |*tty| { + tty.state.mouse = enable; + if (enable) { + _ = try tty.write(ctlseqs.mouse_set); + try tty.flush(); + } else { + _ = try tty.write(ctlseqs.mouse_reset); + try tty.flush(); + } } }