From e9a9c2b6806168ba99c52ace57bf5d4325e71a66 Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Wed, 21 May 2025 22:49:08 +0200 Subject: [PATCH] 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. --- src/app.zig | 35 +++++++++++++++++++++++++++-------- src/event.zig | 2 ++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/app.zig b/src/app.zig index b559872..e521a60 100644 --- a/src/app.zig +++ b/src/app.zig @@ -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"); diff --git a/src/event.zig b/src/event.zig index 42caf43..05fd6dd 100644 --- a/src/event.zig +++ b/src/event.zig @@ -8,6 +8,8 @@ pub const SystemEvent = union(enum) { init, /// Quit event to signify the end of the event loop (rendering should stop afterwards) quit, + /// Resize event to signify that the application should re-draw to resize + resize, /// Error event to notify other containers about a recoverable error err: struct { /// actual error