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 std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const Pty = @import("Pty.zig");
|
const Pty = @import("Pty.zig");
|
||||||
|
const Terminal = @import("Terminal.zig");
|
||||||
|
|
||||||
const posix = std.posix;
|
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.STDOUT_FILENO);
|
||||||
try posix.dup2(self.pty.tty, std.posix.STDERR_FILENO);
|
try posix.dup2(self.pty.tty, std.posix.STDERR_FILENO);
|
||||||
|
|
||||||
// posix.close(self.pty.tty);
|
posix.close(self.pty.tty);
|
||||||
// if (self.pty.pty > 2) posix.close(self.pty.pty);
|
if (self.pty.pty > 2) posix.close(self.pty.pty);
|
||||||
|
|
||||||
// exec
|
// exec
|
||||||
const err = std.posix.execvpeZ(argv_buf.ptr[0].?, argv_buf.ptr, envp);
|
const err = std.posix.execvpeZ(argv_buf.ptr[0].?, argv_buf.ptr, envp);
|
||||||
_ = err catch {};
|
_ = 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
|
// we are the parent
|
||||||
self.pid = @intCast(pid);
|
self.pid = @intCast(pid);
|
||||||
return;
|
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 {
|
pub fn kill(self: *Command) void {
|
||||||
if (self.pid) |pid| {
|
if (self.pid) |pid| {
|
||||||
std.posix.kill(pid, std.posix.SIG.TERM) catch {};
|
std.posix.kill(pid, std.posix.SIG.TERM) catch {};
|
||||||
|
|
|
@ -29,6 +29,20 @@ pub const Cell = struct {
|
||||||
self.wrapped = false;
|
self.wrapped = false;
|
||||||
self.dirty = true;
|
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 {
|
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
|
// Inside scrolling region *and* at bottom of screen, we scroll contents up and insert a
|
||||||
// blank line
|
// blank line
|
||||||
// TODO: scrollback if scrolling region is entire visible screen
|
// TODO: scrollback if scrolling region is entire visible screen
|
||||||
@panic("TODO");
|
try self.deleteLine(1);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
self.cursor.row += 1;
|
self.cursor.row += 1;
|
||||||
}
|
}
|
||||||
|
@ -311,3 +326,55 @@ pub fn eraseRight(self: *Screen) void {
|
||||||
self.buf[i].erase(self.cursor.style.bg);
|
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 Screen = @import("Screen.zig");
|
||||||
const DisplayWidth = @import("DisplayWidth");
|
const DisplayWidth = @import("DisplayWidth");
|
||||||
const Key = vaxis.Key;
|
const Key = vaxis.Key;
|
||||||
|
const Queue = vaxis.Queue(Event, 16);
|
||||||
|
|
||||||
|
pub const Event = union(enum) {
|
||||||
|
exited,
|
||||||
|
bell,
|
||||||
|
};
|
||||||
|
|
||||||
const grapheme = @import("grapheme");
|
const grapheme = @import("grapheme");
|
||||||
|
|
||||||
|
@ -34,6 +40,9 @@ pub const InputEvent = union(enum) {
|
||||||
key_press: vaxis.Key,
|
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,
|
allocator: std.mem.Allocator,
|
||||||
scrollback_size: usize,
|
scrollback_size: usize,
|
||||||
|
|
||||||
|
@ -58,9 +67,7 @@ should_quit: bool = false,
|
||||||
|
|
||||||
mode: Mode = .{},
|
mode: Mode = .{},
|
||||||
|
|
||||||
pending_events: struct {
|
event_queue: Queue = .{},
|
||||||
bell: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
|
|
||||||
} = .{},
|
|
||||||
|
|
||||||
/// initialize a Terminal. This sets the size of the underlying pty and allocates the sizes of the
|
/// initialize a Terminal. This sets the size of the underlying pty and allocates the sizes of the
|
||||||
/// screen
|
/// screen
|
||||||
|
@ -93,6 +100,15 @@ pub fn init(
|
||||||
/// release all resources of the Terminal
|
/// release all resources of the Terminal
|
||||||
pub fn deinit(self: *Terminal) void {
|
pub fn deinit(self: *Terminal) void {
|
||||||
self.should_quit = true;
|
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();
|
self.cmd.kill();
|
||||||
if (self.thread) |thread| {
|
if (self.thread) |thread| {
|
||||||
// write an EOT into the tty to trigger a read on our 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;
|
self.back_screen = &self.back_screen_pri;
|
||||||
|
|
||||||
try self.cmd.spawn(self.allocator);
|
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});
|
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 {
|
pub fn update(self: *Terminal, event: InputEvent) !void {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
.key_press => |key| try self.encodeKey(key, true),
|
.key_press => |key| try self.encodeKey(key, true),
|
||||||
|
@ -216,7 +247,7 @@ fn run(self: *Terminal) !void {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.c0 => |b| try self.handleC0(b),
|
.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}),
|
.ss2 => |ss2| std.log.err("unhandled ss2: {c}", .{ss2}),
|
||||||
.ss3 => |ss3| std.log.err("unhandled ss3: {c}", .{ss3}),
|
.ss3 => |ss3| std.log.err("unhandled ss3: {c}", .{ss3}),
|
||||||
.csi => |seq| {
|
.csi => |seq| {
|
||||||
|
@ -250,7 +281,7 @@ fn run(self: *Terminal) !void {
|
||||||
self.back_screen.width,
|
self.back_screen.width,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
'H' => { // CUP
|
'H', 'f' => { // CUP
|
||||||
var iter = seq.iterator(u16);
|
var iter = seq.iterator(u16);
|
||||||
const row = iter.next() orelse 1;
|
const row = iter.next() orelse 1;
|
||||||
const col = iter.next() orelse 1;
|
const col = iter.next() orelse 1;
|
||||||
|
@ -268,6 +299,16 @@ fn run(self: *Terminal) !void {
|
||||||
else => continue,
|
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' => {
|
'h', 'l' => {
|
||||||
var iter = seq.iterator(u16);
|
var iter = seq.iterator(u16);
|
||||||
const mode = iter.next() orelse continue;
|
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}),
|
else => std.log.err("unhandled CSI: {}", .{seq}),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -307,7 +364,7 @@ inline fn handleC0(self: *Terminal, b: ansi.C0) !void {
|
||||||
.NUL, .SOH, .STX => {},
|
.NUL, .SOH, .STX => {},
|
||||||
.EOT => {}, // we send EOT to quit the read thread
|
.EOT => {}, // we send EOT to quit the read thread
|
||||||
.ENQ => {},
|
.ENQ => {},
|
||||||
.BEL => self.pending_events.bell.store(true, .unordered),
|
.BEL => self.event_queue.push(.bell),
|
||||||
.BS => self.back_screen.cursorLeft(1),
|
.BS => self.back_screen.cursorLeft(1),
|
||||||
.HT => {}, // TODO: HT
|
.HT => {}, // TODO: HT
|
||||||
.LF, .VT, .FF => try self.back_screen.index(),
|
.LF, .VT, .FF => try self.back_screen.index(),
|
||||||
|
|
Loading…
Reference in a new issue