feat(app): signal WINCH for .resize system event

This allows the application to automatically re-draw and resize if the
application receives the signal by the terminal emulator.
This commit is contained in:
2025-05-21 22:49:08 +02:00
parent ba25e6056c
commit e9a9c2b680
2 changed files with 29 additions and 8 deletions

View File

@@ -28,11 +28,17 @@ pub fn App(comptime E: type) type {
thread: ?Thread = null,
quit_event: Thread.ResetEvent,
termios: ?posix.termios = null,
winch_registered: bool = false,
pub const SignalHandler = struct {
context: *anyopaque,
callback: *const fn (context: *anyopaque) void,
};
// global variable for the registered handler for WINCH
var handler_ctx: *anyopaque = undefined;
/// registered WINCH handler to report resize events
fn handleWinch(_: c_int) callconv(.C) void {
const this: *@This() = @ptrCast(@alignCast(handler_ctx));
// NOTE this does not have to be done if in-band resize events are supported
// -> the signal might not work correctly when hosting the application over ssh!
this.postEvent(.resize);
}
pub const init: @This() = .{
.queue = .{},
@@ -45,6 +51,17 @@ pub fn App(comptime E: type) type {
// post init event (as the very first element to be in the queue - event loop)
this.postEvent(.init);
if (!this.winch_registered) {
handler_ctx = this;
var act = posix.Sigaction{
.handler = .{ .handler = handleWinch },
.mask = posix.empty_sigset,
.flags = 0,
};
posix.sigaction(posix.SIG.WINCH, &act, null);
this.winch_registered = true;
}
this.quit_event.reset();
this.thread = try Thread.spawn(.{}, @This().run, .{this});
@@ -159,7 +176,7 @@ pub fn App(comptime E: type) type {
'Q' => input.F2,
'R' => input.F3,
'S' => input.F4,
else => unreachable, // switch case prevents in this case form ever happening
else => unreachable,
},
};
this.postEvent(.{ .key = key });
@@ -205,7 +222,7 @@ pub fn App(comptime E: type) type {
'I' => this.postEvent(.{ .focus = true }),
'O' => this.postEvent(.{ .focus = false }),
'M', 'm' => {
std.debug.assert(sequence.len >= 4);
assert(sequence.len >= 4);
if (sequence[2] != '<') break;
const delim1 = mem.indexOfScalarPos(u8, sequence, 3, ';') orelse break;
@@ -252,7 +269,7 @@ pub fn App(comptime E: type) type {
// Device Status Report
// CSI Ps n
// CSI ? Ps n
std.debug.assert(sequence.len >= 3);
assert(sequence.len >= 3);
},
't' => {
// XTWINOPS
@@ -267,6 +284,7 @@ pub fn App(comptime E: type) type {
_ = width_char;
_ = height_char;
this.postEvent(.resize);
// this.postEvent(.{ .size = .{
// .x = fmt.parseUnsigned(u16, width_char, 10) catch break,
// .y = fmt.parseUnsigned(u16, height_char, 10) catch break,
@@ -298,7 +316,7 @@ pub fn App(comptime E: type) type {
0x0a, 0x0d => .{ .cp = input.Enter },
0x01...0x07, 0x0b...0x0c, 0x0e...0x1a => .{ .cp = b + 0x60, .mod = .{ .ctrl = true } },
0x1b => escape: {
std.debug.assert(read_bytes == 1);
assert(read_bytes == 1);
break :escape .{ .cp = input.Escape };
},
0x7f => .{ .cp = input.Backspace },
@@ -333,6 +351,7 @@ const mem = std.mem;
const fmt = std.fmt;
const posix = std.posix;
const Thread = std.Thread;
const assert = std.debug.assert;
const code_point = @import("code_point");
const event = @import("event.zig");
const input = @import("input.zig");