this repo has no description
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

loop: add an xev loop implementation

This loop adds an xev.File wrapper called TtyWatcher which delivers
events to the users callback. Note that this implementation does not
handle any of the writes. Writes are always safe in the main thread, so
we let users decide how they will schedule those (buffered writers, xev
writes, etc)

+317
+8
build.zig
··· 22 22 .optimize = optimize, 23 23 .target = target, 24 24 }); 25 + const xev_dep = b.dependency("libxev", .{ 26 + .optimize = optimize, 27 + .target = target, 28 + }); 25 29 26 30 // Module 27 31 const vaxis_mod = b.addModule("vaxis", .{ ··· 35 39 vaxis_mod.addImport("zigimg", zigimg_dep.module("zigimg")); 36 40 vaxis_mod.addImport("gap_buffer", gap_buffer_dep.module("gap_buffer")); 37 41 vaxis_mod.addImport("znvim", znvim_dep.module("znvim")); 42 + vaxis_mod.addImport("xev", xev_dep.module("xev")); 38 43 39 44 // Examples 40 45 const Example = enum { ··· 45 50 table, 46 51 text_input, 47 52 vaxis, 53 + xev, 48 54 }; 49 55 const example_option = b.option(Example, "example", "Example to run (default: text_input)") orelse .text_input; 50 56 const example_step = b.step("example", "Run example"); ··· 58 64 .optimize = optimize, 59 65 }); 60 66 example.root_module.addImport("vaxis", vaxis_mod); 67 + example.root_module.addImport("xev", xev_dep.module("xev")); 68 + 61 69 const example_run = b.addRunArtifact(example); 62 70 example_step.dependOn(&example_run.step); 63 71
+4
build.zig.zon
··· 19 19 .url = "git+https://codeberg.org/dude_the_builder/zg#16735685fcc3410de361ba3411788ad1fb4fe188", 20 20 .hash = "1220fe9ac5cdb41833d327a78745614e67d472469f8666567bd8cf9f5847a52b1c51", 21 21 }, 22 + .libxev = .{ 23 + .url = "git+https://github.com/mitchellh/libxev#a284cf851fe2f88f8947c01160c39ff216dacea1", 24 + .hash = "12205b8ea5495b812b9a8943535a7af8da556644d2c1599dc01e1a5ea7aaf59bb2c7", 25 + }, 22 26 }, 23 27 .paths = .{ 24 28 "LICENSE",
+67
examples/xev.zig
··· 1 + const std = @import("std"); 2 + const vaxis = @import("vaxis"); 3 + const xev = @import("xev"); 4 + const Cell = vaxis.Cell; 5 + 6 + const Event = union(enum) { 7 + key_press: vaxis.Key, 8 + winsize: vaxis.Winsize, 9 + }; 10 + 11 + pub const panic = vaxis.panic_handler; 12 + 13 + pub fn main() !void { 14 + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 15 + defer { 16 + const deinit_status = gpa.deinit(); 17 + //fail test; can't try in defer as defer is executed after we return 18 + if (deinit_status == .leak) { 19 + std.log.err("memory leak", .{}); 20 + } 21 + } 22 + var alloc = gpa.allocator(); 23 + 24 + var tty = try vaxis.Tty.init(); 25 + 26 + var vx = try vaxis.init(alloc, .{}); 27 + defer vx.deinit(alloc, tty.anyWriter()); 28 + 29 + var loop = try xev.Loop.init(.{}); 30 + defer loop.deinit(); 31 + 32 + var vx_loop: vaxis.xev.TtyWatcher(std.mem.Allocator) = undefined; 33 + try vx_loop.init(&tty, &vx, &loop, &alloc, callback); 34 + 35 + try vx.enterAltScreen(tty.anyWriter()); 36 + // send queries asynchronously 37 + try vx.queryTerminalSend(tty.anyWriter()); 38 + 39 + try loop.run(.until_done); 40 + @panic("AHHH"); 41 + } 42 + 43 + fn callback( 44 + ud: ?*std.mem.Allocator, 45 + loop: *xev.Loop, 46 + watcher: *vaxis.xev.TtyWatcher(std.mem.Allocator), 47 + event: vaxis.xev.Event, 48 + ) xev.CallbackAction { 49 + switch (event) { 50 + .key_press => |key| { 51 + if (key.matches('c', .{ .ctrl = true })) { 52 + loop.stop(); 53 + return .disarm; 54 + } 55 + }, 56 + .winsize => |ws| watcher.vx.resize(ud.?.*, watcher.tty.anyWriter(), ws) catch @panic("TODO"), 57 + else => {}, 58 + } 59 + std.log.debug("a", .{}); 60 + const win = watcher.vx.window(); 61 + win.clear(); 62 + watcher.vx.render(watcher.tty.anyWriter()) catch { 63 + std.log.err("couldn't render", .{}); 64 + return .disarm; 65 + }; 66 + return .rearm; 67 + }
+238
src/xev.zig
··· 1 + const std = @import("std"); 2 + const xev = @import("xev"); 3 + 4 + const Tty = @import("tty.zig").Tty; 5 + const Winsize = @import("tty.zig").Winsize; 6 + const Vaxis = @import("Vaxis.zig"); 7 + const Parser = @import("Parser.zig"); 8 + const Key = @import("Key.zig"); 9 + const Mouse = @import("Mouse.zig"); 10 + const Color = @import("Cell.zig").Color; 11 + 12 + const log = std.log.scoped(.xev); 13 + 14 + pub const Event = union(enum) { 15 + key_press: Key, 16 + key_release: Key, 17 + mouse: Mouse, 18 + focus_in, 19 + focus_out, 20 + paste_start, // bracketed paste start 21 + paste_end, // bracketed paste end 22 + paste: []const u8, // osc 52 paste, caller must free 23 + color_report: Color.Report, // osc 4, 10, 11, 12 response 24 + color_scheme: Color.Scheme, 25 + winsize: Winsize, 26 + }; 27 + 28 + pub fn TtyWatcher(comptime Userdata: type) type { 29 + return struct { 30 + const Self = @This(); 31 + 32 + file: xev.File, 33 + tty: *Tty, 34 + 35 + read_buf: [4096]u8, 36 + read_buf_start: usize, 37 + read_cmp: xev.Completion, 38 + 39 + winsize_wakeup: xev.Async, 40 + winsize_cmp: xev.Completion, 41 + 42 + callback: *const fn ( 43 + ud: ?*Userdata, 44 + loop: *xev.Loop, 45 + watcher: *Self, 46 + event: Event, 47 + ) xev.CallbackAction, 48 + 49 + ud: ?*Userdata, 50 + vx: *Vaxis, 51 + parser: Parser, 52 + 53 + pub fn init( 54 + self: *Self, 55 + tty: *Tty, 56 + vaxis: *Vaxis, 57 + loop: *xev.Loop, 58 + userdata: ?*Userdata, 59 + callback: *const fn ( 60 + ud: ?*Userdata, 61 + loop: *xev.Loop, 62 + watcher: *Self, 63 + event: Event, 64 + ) xev.CallbackAction, 65 + ) !void { 66 + self.* = .{ 67 + .tty = tty, 68 + .file = xev.File.initFd(tty.fd), 69 + .read_buf = undefined, 70 + .read_buf_start = 0, 71 + .read_cmp = .{}, 72 + 73 + .winsize_wakeup = try xev.Async.init(), 74 + .winsize_cmp = .{}, 75 + 76 + .callback = callback, 77 + .ud = userdata, 78 + .vx = vaxis, 79 + .parser = .{ .grapheme_data = &vaxis.unicode.grapheme_data }, 80 + }; 81 + 82 + self.file.read( 83 + loop, 84 + &self.read_cmp, 85 + .{ .slice = &self.read_buf }, 86 + Self, 87 + self, 88 + Self.ttyReadCallback, 89 + ); 90 + self.winsize_wakeup.wait( 91 + loop, 92 + &self.winsize_cmp, 93 + Self, 94 + self, 95 + winsizeCallback, 96 + ); 97 + const handler: Tty.SignalHandler = .{ 98 + .context = self, 99 + .callback = Self.signalCallback, 100 + }; 101 + try Tty.notifyWinsize(handler); 102 + const winsize = try Tty.getWinsize(self.tty.fd); 103 + _ = self.callback(self.ud, loop, self, .{ .winsize = winsize }); 104 + } 105 + 106 + fn signalCallback(ptr: *anyopaque) void { 107 + const self: *Self = @ptrCast(@alignCast(ptr)); 108 + self.winsize_wakeup.notify() catch @panic("TODO"); 109 + } 110 + 111 + fn ttyReadCallback( 112 + ud: ?*Self, 113 + loop: *xev.Loop, 114 + c: *xev.Completion, 115 + _: xev.File, 116 + buf: xev.ReadBuffer, 117 + r: xev.ReadError!usize, 118 + ) xev.CallbackAction { 119 + _ = c; // autofix 120 + const n = r catch @panic("TODO"); 121 + const self = ud orelse unreachable; 122 + 123 + // reset read start state 124 + self.read_buf_start = 0; 125 + 126 + var seq_start: usize = 0; 127 + parse_loop: while (seq_start < n) { 128 + const result = self.parser.parse(buf.slice[seq_start..n], null) catch @panic("TODO"); 129 + if (result.n == 0) { 130 + // copy the read to the beginning. We don't use memcpy because 131 + // this could be overlapping, and it's also rare 132 + const initial_start = seq_start; 133 + while (seq_start < n) : (seq_start += 1) { 134 + self.read_buf[seq_start - initial_start] = self.read_buf[seq_start]; 135 + } 136 + self.read_buf_start = seq_start - initial_start + 1; 137 + return .rearm; 138 + } 139 + seq_start += n; 140 + const event_inner = result.event orelse { 141 + std.log.warn("unknown event: {s}", .{self.read_buf[seq_start - n + 1 .. seq_start]}); 142 + continue :parse_loop; 143 + }; 144 + 145 + // Capture events we want to bubble up 146 + const event: ?Event = switch (event_inner) { 147 + .key_press => |key| .{ .key_press = key }, 148 + .key_release => |key| .{ .key_release = key }, 149 + .mouse => |mouse| .{ .mouse = mouse }, 150 + .focus_in => .focus_in, 151 + .focus_out => .focus_out, 152 + .paste_start => .paste_start, 153 + .paste_end => .paste_end, 154 + .paste => |paste| .{ .paste = paste }, 155 + .color_report => |report| .{ .color_report = report }, 156 + .color_scheme => |scheme| .{ .color_scheme = scheme }, 157 + 158 + // capability events which we handle below 159 + .cap_kitty_keyboard, 160 + .cap_kitty_graphics, 161 + .cap_rgb, 162 + .cap_unicode, 163 + .cap_sgr_pixels, 164 + .cap_color_scheme_updates, 165 + .cap_da1, 166 + => null, // handled below 167 + }; 168 + 169 + if (event) |ev| { 170 + const action = self.callback(self.ud, loop, self, ev); 171 + switch (action) { 172 + .disarm => return .disarm, 173 + else => continue :parse_loop, 174 + } 175 + } 176 + 177 + switch (event_inner) { 178 + .key_press, 179 + .key_release, 180 + .mouse, 181 + .focus_in, 182 + .focus_out, 183 + .paste_start, 184 + .paste_end, 185 + .paste, 186 + .color_report, 187 + .color_scheme, 188 + => unreachable, // handled above 189 + 190 + .cap_kitty_keyboard => { 191 + log.info("kitty keyboard capability detected", .{}); 192 + self.vx.caps.kitty_keyboard = true; 193 + }, 194 + .cap_kitty_graphics => { 195 + if (!self.vx.caps.kitty_graphics) { 196 + log.info("kitty graphics capability detected", .{}); 197 + self.vx.caps.kitty_graphics = true; 198 + } 199 + }, 200 + .cap_rgb => { 201 + log.info("rgb capability detected", .{}); 202 + self.vx.caps.rgb = true; 203 + }, 204 + .cap_unicode => { 205 + log.info("unicode capability detected", .{}); 206 + self.vx.caps.unicode = .unicode; 207 + self.vx.screen.width_method = .unicode; 208 + }, 209 + .cap_sgr_pixels => { 210 + log.info("pixel mouse capability detected", .{}); 211 + self.vx.caps.sgr_pixels = true; 212 + }, 213 + .cap_color_scheme_updates => { 214 + log.info("color_scheme_updates capability detected", .{}); 215 + self.vx.caps.color_scheme_updates = true; 216 + }, 217 + .cap_da1 => { 218 + self.vx.enableDetectedFeatures(self.tty.anyWriter()) catch {}; 219 + }, 220 + } 221 + } 222 + 223 + return .rearm; 224 + } 225 + 226 + fn winsizeCallback( 227 + ud: ?*Self, 228 + l: *xev.Loop, 229 + _: *xev.Completion, 230 + r: xev.Async.WaitError!void, 231 + ) xev.CallbackAction { 232 + _ = r catch @panic("TODO"); 233 + const self = ud orelse @panic("TODO"); 234 + const winsize = Tty.getWinsize(self.tty.fd) catch @panic("TODO"); 235 + return self.callback(self.ud, l, self, .{ .winsize = winsize }); 236 + } 237 + }; 238 + }