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, thread: ?Thread = null,
quit_event: Thread.ResetEvent, quit_event: Thread.ResetEvent,
termios: ?posix.termios = null, termios: ?posix.termios = null,
winch_registered: bool = false,
pub const SignalHandler = struct { // global variable for the registered handler for WINCH
context: *anyopaque, var handler_ctx: *anyopaque = undefined;
callback: *const fn (context: *anyopaque) void, /// 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() = .{ pub const init: @This() = .{
.queue = .{}, .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) // post init event (as the very first element to be in the queue - event loop)
this.postEvent(.init); 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.quit_event.reset();
this.thread = try Thread.spawn(.{}, @This().run, .{this}); this.thread = try Thread.spawn(.{}, @This().run, .{this});
@@ -159,7 +176,7 @@ pub fn App(comptime E: type) type {
'Q' => input.F2, 'Q' => input.F2,
'R' => input.F3, 'R' => input.F3,
'S' => input.F4, 'S' => input.F4,
else => unreachable, // switch case prevents in this case form ever happening else => unreachable,
}, },
}; };
this.postEvent(.{ .key = key }); this.postEvent(.{ .key = key });
@@ -205,7 +222,7 @@ pub fn App(comptime E: type) type {
'I' => this.postEvent(.{ .focus = true }), 'I' => this.postEvent(.{ .focus = true }),
'O' => this.postEvent(.{ .focus = false }), 'O' => this.postEvent(.{ .focus = false }),
'M', 'm' => { 'M', 'm' => {
std.debug.assert(sequence.len >= 4); assert(sequence.len >= 4);
if (sequence[2] != '<') break; if (sequence[2] != '<') break;
const delim1 = mem.indexOfScalarPos(u8, sequence, 3, ';') orelse break; const delim1 = mem.indexOfScalarPos(u8, sequence, 3, ';') orelse break;
@@ -252,7 +269,7 @@ pub fn App(comptime E: type) type {
// Device Status Report // Device Status Report
// CSI Ps n // CSI Ps n
// CSI ? Ps n // CSI ? Ps n
std.debug.assert(sequence.len >= 3); assert(sequence.len >= 3);
}, },
't' => { 't' => {
// XTWINOPS // XTWINOPS
@@ -267,6 +284,7 @@ pub fn App(comptime E: type) type {
_ = width_char; _ = width_char;
_ = height_char; _ = height_char;
this.postEvent(.resize);
// this.postEvent(.{ .size = .{ // this.postEvent(.{ .size = .{
// .x = fmt.parseUnsigned(u16, width_char, 10) catch break, // .x = fmt.parseUnsigned(u16, width_char, 10) catch break,
// .y = fmt.parseUnsigned(u16, height_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 }, 0x0a, 0x0d => .{ .cp = input.Enter },
0x01...0x07, 0x0b...0x0c, 0x0e...0x1a => .{ .cp = b + 0x60, .mod = .{ .ctrl = true } }, 0x01...0x07, 0x0b...0x0c, 0x0e...0x1a => .{ .cp = b + 0x60, .mod = .{ .ctrl = true } },
0x1b => escape: { 0x1b => escape: {
std.debug.assert(read_bytes == 1); assert(read_bytes == 1);
break :escape .{ .cp = input.Escape }; break :escape .{ .cp = input.Escape };
}, },
0x7f => .{ .cp = input.Backspace }, 0x7f => .{ .cp = input.Backspace },
@@ -333,6 +351,7 @@ const mem = std.mem;
const fmt = std.fmt; const fmt = std.fmt;
const posix = std.posix; const posix = std.posix;
const Thread = std.Thread; const Thread = std.Thread;
const assert = std.debug.assert;
const code_point = @import("code_point"); const code_point = @import("code_point");
const event = @import("event.zig"); const event = @import("event.zig");
const input = @import("input.zig"); const input = @import("input.zig");

View File

@@ -8,6 +8,8 @@ pub const SystemEvent = union(enum) {
init, init,
/// Quit event to signify the end of the event loop (rendering should stop afterwards) /// Quit event to signify the end of the event loop (rendering should stop afterwards)
quit, quit,
/// Resize event to signify that the application should re-draw to resize
resize,
/// Error event to notify other containers about a recoverable error /// Error event to notify other containers about a recoverable error
err: struct { err: struct {
/// actual error /// actual error