widgets(terminal): handle exiting events
This commit is contained in:
parent
ef62de5541
commit
d991755fe2
3 changed files with 160 additions and 9 deletions
|
@ -3,6 +3,7 @@ const Command = @This();
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const Pty = @import("Pty.zig");
|
||||
const Terminal = @import("Terminal.zig");
|
||||
|
||||
const posix = std.posix;
|
||||
|
||||
|
@ -40,19 +41,45 @@ pub fn spawn(self: *Command, allocator: std.mem.Allocator) !void {
|
|||
try posix.dup2(self.pty.tty, std.posix.STDOUT_FILENO);
|
||||
try posix.dup2(self.pty.tty, std.posix.STDERR_FILENO);
|
||||
|
||||
// posix.close(self.pty.tty);
|
||||
// if (self.pty.pty > 2) posix.close(self.pty.pty);
|
||||
posix.close(self.pty.tty);
|
||||
if (self.pty.pty > 2) posix.close(self.pty.pty);
|
||||
|
||||
// exec
|
||||
const err = std.posix.execvpeZ(argv_buf.ptr[0].?, argv_buf.ptr, envp);
|
||||
_ = err catch {};
|
||||
@panic("a");
|
||||
// const EOT = "\x04";
|
||||
// _ = std.posix.write(self.pty.tty, EOT) catch {};
|
||||
}
|
||||
|
||||
var act = posix.Sigaction{
|
||||
.handler = .{ .handler = handleSigChild },
|
||||
.mask = switch (builtin.os.tag) {
|
||||
.macos => 0,
|
||||
.linux => posix.empty_sigset,
|
||||
else => @compileError("os not supported"),
|
||||
},
|
||||
.flags = 0,
|
||||
};
|
||||
try posix.sigaction(posix.SIG.CHLD, &act, null);
|
||||
|
||||
// we are the parent
|
||||
self.pid = @intCast(pid);
|
||||
return;
|
||||
}
|
||||
|
||||
fn handleSigChild(_: c_int) callconv(.C) void {
|
||||
std.log.err("sigchild", .{});
|
||||
const result = std.posix.waitpid(-1, 0);
|
||||
|
||||
Terminal.global_vt_mutex.lock();
|
||||
defer Terminal.global_vt_mutex.unlock();
|
||||
if (Terminal.global_vts) |vts| {
|
||||
var vt = vts.get(result.pid) orelse return;
|
||||
vt.event_queue.push(.exited);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kill(self: *Command) void {
|
||||
if (self.pid) |pid| {
|
||||
std.posix.kill(pid, std.posix.SIG.TERM) catch {};
|
||||
|
|
|
@ -29,6 +29,20 @@ pub const Cell = struct {
|
|||
self.wrapped = false;
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
pub fn copyFrom(self: *Cell, src: Cell) !void {
|
||||
self.char.clearRetainingCapacity();
|
||||
try self.char.appendSlice(src.char.items);
|
||||
self.style = src.style;
|
||||
self.uri.clearRetainingCapacity();
|
||||
try self.uri.appendSlice(src.uri.items);
|
||||
self.uri_id.clearRetainingCapacity();
|
||||
try self.uri_id.appendSlice(src.uri_id.items);
|
||||
self.width = src.width;
|
||||
self.wrapped = src.wrapped;
|
||||
|
||||
self.dirty = true;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Cursor = struct {
|
||||
|
@ -198,7 +212,8 @@ pub fn index(self: *Screen) !void {
|
|||
// Inside scrolling region *and* at bottom of screen, we scroll contents up and insert a
|
||||
// blank line
|
||||
// TODO: scrollback if scrolling region is entire visible screen
|
||||
@panic("TODO");
|
||||
try self.deleteLine(1);
|
||||
return;
|
||||
}
|
||||
self.cursor.row += 1;
|
||||
}
|
||||
|
@ -311,3 +326,55 @@ pub fn eraseRight(self: *Screen) void {
|
|||
self.buf[i].erase(self.cursor.style.bg);
|
||||
}
|
||||
}
|
||||
|
||||
/// delete n lines from te bottom of te scrolling region
|
||||
pub fn deleteLine(self: *Screen, n: usize) !void {
|
||||
if (n == 0) return;
|
||||
|
||||
// Don't delete if outside scroll region
|
||||
if (!self.withinScrollingRegion()) return;
|
||||
|
||||
self.cursor.pending_wrap = false;
|
||||
|
||||
// Number of rows from here to bottom of scroll region or n
|
||||
const cnt = @min(self.scrolling_region.bottom - self.cursor.row + 1, n);
|
||||
const stride = (self.width) * cnt;
|
||||
|
||||
var row: usize = self.scrolling_region.top;
|
||||
while (row <= self.scrolling_region.bottom) : (row += 1) {
|
||||
var col: usize = self.scrolling_region.left;
|
||||
while (col <= self.scrolling_region.right) : (col += 1) {
|
||||
const i = (row * self.width) + col;
|
||||
if (row + cnt > self.scrolling_region.bottom)
|
||||
self.buf[i].erase(self.cursor.style.bg)
|
||||
else
|
||||
try self.buf[i].copyFrom(self.buf[i + stride]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// insert n lines at the top of the scrolling region
|
||||
pub fn insertLine(self: *Screen, n: usize) !void {
|
||||
if (n == 0) return;
|
||||
|
||||
// Don't insert if outside scroll region
|
||||
if (!self.withinScrollingRegion()) return;
|
||||
|
||||
self.cursor.pending_wrap = false;
|
||||
|
||||
// Number of rows from here to top of scroll region or n
|
||||
const cnt = @min(self.cursor.row - self.scrolling_region.top + 1, n);
|
||||
const stride = (self.width) * cnt;
|
||||
|
||||
var row: usize = self.scrolling_region.bottom;
|
||||
while (row > self.scrolling_region.top) : (row -= 1) {
|
||||
var col: usize = self.scrolling_region.left;
|
||||
while (col <= self.scrolling_region.right) : (col += 1) {
|
||||
const i = (row * self.width) + col;
|
||||
if (row - cnt < self.scrolling_region.top)
|
||||
self.buf[i].erase(self.cursor.style.bg)
|
||||
else
|
||||
try self.buf[i].copyFrom(self.buf[i - stride]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,12 @@ const Winsize = vaxis.Winsize;
|
|||
const Screen = @import("Screen.zig");
|
||||
const DisplayWidth = @import("DisplayWidth");
|
||||
const Key = vaxis.Key;
|
||||
const Queue = vaxis.Queue(Event, 16);
|
||||
|
||||
pub const Event = union(enum) {
|
||||
exited,
|
||||
bell,
|
||||
};
|
||||
|
||||
const grapheme = @import("grapheme");
|
||||
|
||||
|
@ -34,6 +40,9 @@ pub const InputEvent = union(enum) {
|
|||
key_press: vaxis.Key,
|
||||
};
|
||||
|
||||
pub var global_vt_mutex: std.Thread.Mutex = .{};
|
||||
pub var global_vts: ?std.AutoHashMap(i32, *Terminal) = null;
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
scrollback_size: usize,
|
||||
|
||||
|
@ -58,9 +67,7 @@ should_quit: bool = false,
|
|||
|
||||
mode: Mode = .{},
|
||||
|
||||
pending_events: struct {
|
||||
bell: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
|
||||
} = .{},
|
||||
event_queue: Queue = .{},
|
||||
|
||||
/// initialize a Terminal. This sets the size of the underlying pty and allocates the sizes of the
|
||||
/// screen
|
||||
|
@ -93,6 +100,15 @@ pub fn init(
|
|||
/// release all resources of the Terminal
|
||||
pub fn deinit(self: *Terminal) void {
|
||||
self.should_quit = true;
|
||||
|
||||
pid: {
|
||||
global_vt_mutex.lock();
|
||||
defer global_vt_mutex.unlock();
|
||||
var vts = global_vts orelse break :pid;
|
||||
if (self.cmd.pid) |pid|
|
||||
_ = vts.remove(pid);
|
||||
if (vts.count() == 0) vts.deinit();
|
||||
}
|
||||
self.cmd.kill();
|
||||
if (self.thread) |thread| {
|
||||
// write an EOT into the tty to trigger a read on our thread
|
||||
|
@ -112,6 +128,17 @@ pub fn spawn(self: *Terminal) !void {
|
|||
self.back_screen = &self.back_screen_pri;
|
||||
|
||||
try self.cmd.spawn(self.allocator);
|
||||
|
||||
{
|
||||
// add to our global list
|
||||
global_vt_mutex.lock();
|
||||
defer global_vt_mutex.unlock();
|
||||
if (global_vts == null)
|
||||
global_vts = std.AutoHashMap(i32, *Terminal).init(self.allocator);
|
||||
if (self.cmd.pid) |pid|
|
||||
try global_vts.?.put(pid, self);
|
||||
}
|
||||
|
||||
self.thread = try std.Thread.spawn(.{}, Terminal.run, .{self});
|
||||
}
|
||||
|
||||
|
@ -160,6 +187,10 @@ pub fn draw(self: *Terminal, win: vaxis.Window) !void {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn tryEvent(self: *Terminal) ?Event {
|
||||
return self.event_queue.tryPop();
|
||||
}
|
||||
|
||||
pub fn update(self: *Terminal, event: InputEvent) !void {
|
||||
switch (event) {
|
||||
.key_press => |key| try self.encodeKey(key, true),
|
||||
|
@ -216,7 +247,7 @@ fn run(self: *Terminal) !void {
|
|||
}
|
||||
},
|
||||
.c0 => |b| try self.handleC0(b),
|
||||
.escape => |str| std.log.err("unhandled escape: {s}", .{str}),
|
||||
.escape => |_| {}, // std.log.err("unhandled escape: {s}", .{str}),
|
||||
.ss2 => |ss2| std.log.err("unhandled ss2: {c}", .{ss2}),
|
||||
.ss3 => |ss3| std.log.err("unhandled ss3: {c}", .{ss3}),
|
||||
.csi => |seq| {
|
||||
|
@ -250,7 +281,7 @@ fn run(self: *Terminal) !void {
|
|||
self.back_screen.width,
|
||||
);
|
||||
},
|
||||
'H' => { // CUP
|
||||
'H', 'f' => { // CUP
|
||||
var iter = seq.iterator(u16);
|
||||
const row = iter.next() orelse 1;
|
||||
const col = iter.next() orelse 1;
|
||||
|
@ -268,6 +299,16 @@ fn run(self: *Terminal) !void {
|
|||
else => continue,
|
||||
}
|
||||
},
|
||||
'L' => {
|
||||
var iter = seq.iterator(u16);
|
||||
const n = iter.next() orelse 1;
|
||||
try self.back_screen.insertLine(n);
|
||||
},
|
||||
'M' => {
|
||||
var iter = seq.iterator(u16);
|
||||
const n = iter.next() orelse 1;
|
||||
try self.back_screen.deleteLine(n);
|
||||
},
|
||||
'h', 'l' => {
|
||||
var iter = seq.iterator(u16);
|
||||
const mode = iter.next() orelse continue;
|
||||
|
@ -293,6 +334,22 @@ fn run(self: *Terminal) !void {
|
|||
}
|
||||
}
|
||||
},
|
||||
'r' => {
|
||||
if (seq.intermediate) |_| {
|
||||
// TODO: XTRESTORE
|
||||
continue;
|
||||
}
|
||||
if (seq.private_marker) |_| {
|
||||
// TODO: DECCARA
|
||||
continue;
|
||||
}
|
||||
// DECSTBM
|
||||
var iter = seq.iterator(u16);
|
||||
const top = iter.next() orelse 1;
|
||||
const bottom = iter.next() orelse self.back_screen.height;
|
||||
self.back_screen.scrolling_region.top = top - 1;
|
||||
self.back_screen.scrolling_region.bottom = bottom - 1;
|
||||
},
|
||||
else => std.log.err("unhandled CSI: {}", .{seq}),
|
||||
}
|
||||
},
|
||||
|
@ -307,7 +364,7 @@ inline fn handleC0(self: *Terminal, b: ansi.C0) !void {
|
|||
.NUL, .SOH, .STX => {},
|
||||
.EOT => {}, // we send EOT to quit the read thread
|
||||
.ENQ => {},
|
||||
.BEL => self.pending_events.bell.store(true, .unordered),
|
||||
.BEL => self.event_queue.push(.bell),
|
||||
.BS => self.back_screen.cursorLeft(1),
|
||||
.HT => {}, // TODO: HT
|
||||
.LF, .VT, .FF => try self.back_screen.index(),
|
||||
|
|
Loading…
Reference in a new issue