this repo has no description
13
fork

Configure Feed

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

aio: update to latest, windows, code reuse

Update to latest aio, which has minor changes such as the thread pool
argument and special aio.ReadTty operation.

In future the aio.ReadTty might have option to translate to vt escape
sequences, but for vaxis it will use the direct mode.

I was not really able to test the windows at all actually as wine did
not seem to play nice with any vaxis example, but it compiles and ...
runs?

authored by

Jari Vetoniemi and committed by
Tim Culverhouse
9b78bb8a 9c2d18d5

+452 -501
+2 -2
build.zig.zon
··· 23 23 .lazy = true, 24 24 }, 25 25 .aio = .{ 26 - .url = "git+https://github.com/Cloudef/zig-aio#be8e2b374bf223202090e282447fa4581029c2eb", 27 - .hash = "122012a11b37a350395a32fdb514e57ff54a0f9d8d4ce09498b6c45ffb7211232920", 26 + .url = "git+https://github.com/Cloudef/zig-aio#407bb416136b61087cec2c561fa4b4103a44c5b1", 27 + .hash = "12202405ca6dd40f314dba6472983fcbb388118ab7446d75065b1efb982d03f515d2", 28 28 .lazy = true, 29 29 }, 30 30 },
+1 -1
examples/aio.zig
··· 41 41 42 42 const sound = blk: { 43 43 var tpool: coro.ThreadPool = .{}; 44 - try tpool.start(allocator, 1); 44 + try tpool.start(allocator, .{}); 45 45 defer tpool.deinit(); 46 46 break :blk try tpool.yieldForCompletition(downloadTask, .{ allocator, "https://keroserene.net/lol/roll.s16" }); 47 47 };
+140 -130
src/Loop.zig
··· 8 8 const Queue = @import("queue.zig").Queue; 9 9 const Tty = @import("main.zig").Tty; 10 10 const Vaxis = @import("Vaxis.zig"); 11 + const log = std.log.scoped(.loop); 11 12 12 13 pub fn Loop(comptime T: type) type { 13 14 return struct { 14 15 const Self = @This(); 15 16 16 17 const Event = T; 17 - 18 - const log = std.log.scoped(.loop); 19 18 20 19 tty: *Tty, 21 20 vaxis: *Vaxis, ··· 110 109 .windows => { 111 110 while (!self.should_quit) { 112 111 const event = try self.tty.nextEvent(); 113 - switch (event) { 114 - .winsize => |ws| { 115 - if (@hasField(Event, "winsize")) { 116 - self.postEvent(.{ .winsize = ws }); 117 - } 118 - }, 119 - .key_press => |key| { 120 - if (@hasField(Event, "key_press")) { 121 - // HACK: yuck. there has to be a better way 122 - var mut_key = key; 123 - if (key.text) |text| { 124 - mut_key.text = cache.put(text); 125 - } 126 - self.postEvent(.{ .key_press = mut_key }); 127 - } 128 - }, 129 - .key_release => |*key| { 130 - if (@hasField(Event, "key_release")) { 131 - // HACK: yuck. there has to be a better way 132 - var mut_key = key; 133 - if (key.text) |text| { 134 - mut_key.text = cache.put(text); 135 - } 136 - self.postEvent(.{ .key_release = mut_key }); 137 - } 138 - }, 139 - .cap_da1 => { 140 - std.Thread.Futex.wake(&self.vaxis.query_futex, 10); 141 - }, 142 - .mouse => {}, // Unsupported currently 143 - else => {}, 144 - } 112 + try handleEventGeneric(self, self.vaxis, &cache, Event, event, null); 145 113 } 146 114 }, 147 115 else => { ··· 178 146 seq_start += result.n; 179 147 180 148 const event = result.event orelse continue; 181 - switch (event) { 182 - .key_press => |key| { 183 - if (@hasField(Event, "key_press")) { 184 - // HACK: yuck. there has to be a better way 185 - var mut_key = key; 186 - if (key.text) |text| { 187 - mut_key.text = cache.put(text); 188 - } 189 - self.postEvent(.{ .key_press = mut_key }); 190 - } 191 - }, 192 - .key_release => |*key| { 193 - if (@hasField(Event, "key_release")) { 194 - // HACK: yuck. there has to be a better way 195 - var mut_key = key; 196 - if (key.text) |text| { 197 - mut_key.text = cache.put(text); 198 - } 199 - self.postEvent(.{ .key_release = mut_key }); 200 - } 201 - }, 202 - .mouse => |mouse| { 203 - if (@hasField(Event, "mouse")) { 204 - self.postEvent(.{ .mouse = self.vaxis.translateMouse(mouse) }); 205 - } 206 - }, 207 - .focus_in => { 208 - if (@hasField(Event, "focus_in")) { 209 - self.postEvent(.focus_in); 210 - } 211 - }, 212 - .focus_out => { 213 - if (@hasField(Event, "focus_out")) { 214 - self.postEvent(.focus_out); 215 - } 216 - }, 217 - .paste_start => { 218 - if (@hasField(Event, "paste_start")) { 219 - self.postEvent(.paste_start); 220 - } 221 - }, 222 - .paste_end => { 223 - if (@hasField(Event, "paste_end")) { 224 - self.postEvent(.paste_end); 225 - } 226 - }, 227 - .paste => |text| { 228 - if (@hasField(Event, "paste")) { 229 - self.postEvent(.{ .paste = text }); 230 - } else { 231 - if (paste_allocator) |_| 232 - paste_allocator.?.free(text); 233 - } 234 - }, 235 - .color_report => |report| { 236 - if (@hasField(Event, "color_report")) { 237 - self.postEvent(.{ .color_report = report }); 238 - } 239 - }, 240 - .color_scheme => |scheme| { 241 - if (@hasField(Event, "color_scheme")) { 242 - self.postEvent(.{ .color_scheme = scheme }); 243 - } 244 - }, 245 - .cap_kitty_keyboard => { 246 - log.info("kitty keyboard capability detected", .{}); 247 - self.vaxis.caps.kitty_keyboard = true; 248 - }, 249 - .cap_kitty_graphics => { 250 - if (!self.vaxis.caps.kitty_graphics) { 251 - log.info("kitty graphics capability detected", .{}); 252 - self.vaxis.caps.kitty_graphics = true; 253 - } 254 - }, 255 - .cap_rgb => { 256 - log.info("rgb capability detected", .{}); 257 - self.vaxis.caps.rgb = true; 258 - }, 259 - .cap_unicode => { 260 - log.info("unicode capability detected", .{}); 261 - self.vaxis.caps.unicode = .unicode; 262 - self.vaxis.screen.width_method = .unicode; 263 - }, 264 - .cap_sgr_pixels => { 265 - log.info("pixel mouse capability detected", .{}); 266 - self.vaxis.caps.sgr_pixels = true; 267 - }, 268 - .cap_color_scheme_updates => { 269 - log.info("color_scheme_updates capability detected", .{}); 270 - self.vaxis.caps.color_scheme_updates = true; 271 - }, 272 - .cap_da1 => { 273 - std.Thread.Futex.wake(&self.vaxis.query_futex, 10); 274 - }, 275 - .winsize => unreachable, // handled elsewhere for posix 276 - } 149 + try handleEventGeneric(self, self.vaxis, &cache, Event, event, paste_allocator); 277 150 } 278 151 } 279 152 }, ··· 281 154 } 282 155 }; 283 156 } 157 + 158 + pub fn handleEventGeneric(self: anytype, vx: *Vaxis, cache: *GraphemeCache, Event: type, event: anytype, paste_allocator: ?std.mem.Allocator) !void { 159 + switch (builtin.os.tag) { 160 + .windows => { 161 + switch (event) { 162 + .winsize => |ws| { 163 + if (@hasField(Event, "winsize")) { 164 + return self.postEvent(.{ .winsize = ws }); 165 + } 166 + }, 167 + .key_press => |key| { 168 + if (@hasField(Event, "key_press")) { 169 + // HACK: yuck. there has to be a better way 170 + var mut_key = key; 171 + if (key.text) |text| { 172 + mut_key.text = cache.put(text); 173 + } 174 + return self.postEvent(.{ .key_press = mut_key }); 175 + } 176 + }, 177 + .key_release => |*key| { 178 + if (@hasField(Event, "key_release")) { 179 + // HACK: yuck. there has to be a better way 180 + var mut_key = key; 181 + if (key.text) |text| { 182 + mut_key.text = cache.put(text); 183 + } 184 + return self.postEvent(.{ .key_release = mut_key }); 185 + } 186 + }, 187 + .cap_da1 => { 188 + std.Thread.Futex.wake(&vx.query_futex, 10); 189 + }, 190 + .mouse => {}, // Unsupported currently 191 + else => {}, 192 + } 193 + }, 194 + else => { 195 + switch (event) { 196 + .key_press => |key| { 197 + if (@hasField(Event, "key_press")) { 198 + // HACK: yuck. there has to be a better way 199 + var mut_key = key; 200 + if (key.text) |text| { 201 + mut_key.text = cache.put(text); 202 + } 203 + return self.postEvent(.{ .key_press = mut_key }); 204 + } 205 + }, 206 + .key_release => |*key| { 207 + if (@hasField(Event, "key_release")) { 208 + // HACK: yuck. there has to be a better way 209 + var mut_key = key; 210 + if (key.text) |text| { 211 + mut_key.text = cache.put(text); 212 + } 213 + return self.postEvent(.{ .key_release = mut_key }); 214 + } 215 + }, 216 + .mouse => |mouse| { 217 + if (@hasField(Event, "mouse")) { 218 + return self.postEvent(.{ .mouse = vx.translateMouse(mouse) }); 219 + } 220 + }, 221 + .focus_in => { 222 + if (@hasField(Event, "focus_in")) { 223 + return self.postEvent(.focus_in); 224 + } 225 + }, 226 + .focus_out => { 227 + if (@hasField(Event, "focus_out")) { 228 + return self.postEvent(.focus_out); 229 + } 230 + }, 231 + .paste_start => { 232 + if (@hasField(Event, "paste_start")) { 233 + return self.postEvent(.paste_start); 234 + } 235 + }, 236 + .paste_end => { 237 + if (@hasField(Event, "paste_end")) { 238 + return self.postEvent(.paste_end); 239 + } 240 + }, 241 + .paste => |text| { 242 + if (@hasField(Event, "paste")) { 243 + return self.postEvent(.{ .paste = text }); 244 + } else { 245 + if (paste_allocator) |_| 246 + paste_allocator.?.free(text); 247 + } 248 + }, 249 + .color_report => |report| { 250 + if (@hasField(Event, "color_report")) { 251 + return self.postEvent(.{ .color_report = report }); 252 + } 253 + }, 254 + .color_scheme => |scheme| { 255 + if (@hasField(Event, "color_scheme")) { 256 + return self.postEvent(.{ .color_scheme = scheme }); 257 + } 258 + }, 259 + .cap_kitty_keyboard => { 260 + log.info("kitty keyboard capability detected", .{}); 261 + vx.caps.kitty_keyboard = true; 262 + }, 263 + .cap_kitty_graphics => { 264 + if (!vx.caps.kitty_graphics) { 265 + log.info("kitty graphics capability detected", .{}); 266 + vx.caps.kitty_graphics = true; 267 + } 268 + }, 269 + .cap_rgb => { 270 + log.info("rgb capability detected", .{}); 271 + vx.caps.rgb = true; 272 + }, 273 + .cap_unicode => { 274 + log.info("unicode capability detected", .{}); 275 + vx.caps.unicode = .unicode; 276 + vx.screen.width_method = .unicode; 277 + }, 278 + .cap_sgr_pixels => { 279 + log.info("pixel mouse capability detected", .{}); 280 + vx.caps.sgr_pixels = true; 281 + }, 282 + .cap_color_scheme_updates => { 283 + log.info("color_scheme_updates capability detected", .{}); 284 + vx.caps.color_scheme_updates = true; 285 + }, 286 + .cap_da1 => { 287 + std.Thread.Futex.wake(&vx.query_futex, 10); 288 + }, 289 + .winsize => unreachable, // handled elsewhere for posix 290 + } 291 + }, 292 + } 293 + }
+38 -108
src/aio.zig
··· 3 3 const aio = @import("aio"); 4 4 const coro = @import("coro"); 5 5 const vaxis = @import("main.zig"); 6 + const handleEventGeneric = @import("Loop.zig").handleEventGeneric; 6 7 const log = std.log.scoped(.vaxis_aio); 7 - 8 - comptime { 9 - if (builtin.target.os.tag == .windows) { 10 - @compileError("Windows is not supported right now"); 11 - } 12 - } 13 8 14 9 const Yield = enum { no_state, took_event }; 15 10 ··· 52 47 53 48 // keep on stack 54 49 var ctx: Context = .{ .loop = self, .tty = tty }; 55 - if (@hasField(Event, "winsize")) { 56 - const handler: vaxis.Tty.SignalHandler = .{ .context = &ctx, .callback = Context.cb }; 57 - try vaxis.Tty.notifyWinsize(handler); 50 + if (builtin.target.os.tag != .windows) { 51 + if (@hasField(Event, "winsize")) { 52 + const handler: vaxis.Tty.SignalHandler = .{ .context = &ctx, .callback = Context.cb }; 53 + try vaxis.Tty.notifyWinsize(handler); 54 + } 58 55 } 59 56 60 57 while (true) { ··· 74 71 }; 75 72 } 76 73 77 - fn ttyReaderInner(self: *@This(), vx: *vaxis.Vaxis, tty: *vaxis.Tty, paste_allocator: ?std.mem.Allocator) !void { 74 + fn windowsReadEvent(tty: *vaxis.Tty) !vaxis.Event { 75 + var state: vaxis.Tty.EventState = .{}; 76 + while (true) { 77 + var bytes_read: usize = 0; 78 + var input_record: vaxis.Tty.INPUT_RECORD = undefined; 79 + try coro.io.single(aio.ReadTty{ 80 + .tty = .{ .handle = tty.stdin }, 81 + .buffer = std.mem.asBytes(&input_record), 82 + .out_read = &bytes_read, 83 + }); 84 + 85 + if (try tty.eventFromRecord(&input_record, &state)) |ev| { 86 + return ev; 87 + } 88 + } 89 + } 90 + 91 + fn ttyReaderWindows(self: *@This(), vx: *vaxis.Vaxis, tty: *vaxis.Tty) !void { 92 + var cache: vaxis.GraphemeCache = .{}; 93 + while (true) { 94 + const event = try windowsReadEvent(tty); 95 + try handleEventGeneric(self, vx, &cache, Event, event, null); 96 + } 97 + } 98 + 99 + fn ttyReaderPosix(self: *@This(), vx: *vaxis.Vaxis, tty: *vaxis.Tty, paste_allocator: ?std.mem.Allocator) !void { 78 100 // initialize a grapheme cache 79 101 var cache: vaxis.GraphemeCache = .{}; 80 102 ··· 93 115 var buf: [4096]u8 = undefined; 94 116 var n: usize = undefined; 95 117 var read_start: usize = 0; 96 - try coro.io.single(aio.Read{ .file = file, .buffer = buf[read_start..], .out_read = &n }); 118 + try coro.io.single(aio.ReadTty{ .tty = file, .buffer = buf[read_start..], .out_read = &n }); 97 119 var seq_start: usize = 0; 98 120 while (seq_start < n) { 99 121 const result = try parser.parse(buf[seq_start..n], paste_allocator); ··· 111 133 seq_start += result.n; 112 134 113 135 const event = result.event orelse continue; 114 - switch (event) { 115 - .key_press => |key| { 116 - if (@hasField(Event, "key_press")) { 117 - // HACK: yuck. there has to be a better way 118 - var mut_key = key; 119 - if (key.text) |text| { 120 - mut_key.text = cache.put(text); 121 - } 122 - try self.postEvent(.{ .key_press = mut_key }); 123 - } 124 - }, 125 - .key_release => |*key| { 126 - if (@hasField(Event, "key_release")) { 127 - // HACK: yuck. there has to be a better way 128 - var mut_key = key; 129 - if (key.text) |text| { 130 - mut_key.text = cache.put(text); 131 - } 132 - try self.postEvent(.{ .key_release = mut_key }); 133 - } 134 - }, 135 - .mouse => |mouse| { 136 - if (@hasField(Event, "mouse")) { 137 - try self.postEvent(.{ .mouse = vx.translateMouse(mouse) }); 138 - } 139 - }, 140 - .focus_in => { 141 - if (@hasField(Event, "focus_in")) { 142 - try self.postEvent(.focus_in); 143 - } 144 - }, 145 - .focus_out => { 146 - if (@hasField(Event, "focus_out")) { 147 - try self.postEvent(.focus_out); 148 - } 149 - }, 150 - .paste_start => { 151 - if (@hasField(Event, "paste_start")) { 152 - try self.postEvent(.paste_start); 153 - } 154 - }, 155 - .paste_end => { 156 - if (@hasField(Event, "paste_end")) { 157 - try self.postEvent(.paste_end); 158 - } 159 - }, 160 - .paste => |text| { 161 - if (@hasField(Event, "paste")) { 162 - try self.postEvent(.{ .paste = text }); 163 - } else { 164 - if (paste_allocator) |_| 165 - paste_allocator.?.free(text); 166 - } 167 - }, 168 - .color_report => |report| { 169 - if (@hasField(Event, "color_report")) { 170 - try self.postEvent(.{ .color_report = report }); 171 - } 172 - }, 173 - .color_scheme => |scheme| { 174 - if (@hasField(Event, "color_scheme")) { 175 - try self.postEvent(.{ .color_scheme = scheme }); 176 - } 177 - }, 178 - .cap_kitty_keyboard => { 179 - log.info("kitty keyboard capability detected", .{}); 180 - vx.caps.kitty_keyboard = true; 181 - }, 182 - .cap_kitty_graphics => { 183 - if (!vx.caps.kitty_graphics) { 184 - log.info("kitty graphics capability detected", .{}); 185 - vx.caps.kitty_graphics = true; 186 - } 187 - }, 188 - .cap_rgb => { 189 - log.info("rgb capability detected", .{}); 190 - vx.caps.rgb = true; 191 - }, 192 - .cap_unicode => { 193 - log.info("unicode capability detected", .{}); 194 - vx.caps.unicode = .unicode; 195 - vx.screen.width_method = .unicode; 196 - }, 197 - .cap_sgr_pixels => { 198 - log.info("pixel mouse capability detected", .{}); 199 - vx.caps.sgr_pixels = true; 200 - }, 201 - .cap_color_scheme_updates => { 202 - log.info("color_scheme_updates capability detected", .{}); 203 - vx.caps.color_scheme_updates = true; 204 - }, 205 - .cap_da1 => { 206 - std.Thread.Futex.wake(&vx.query_futex, 10); 207 - }, 208 - .winsize => unreachable, // handled elsewhere for posix 209 - } 136 + try handleEventGeneric(self, vx, &cache, Event, event, paste_allocator); 210 137 } 211 138 } 212 139 } 213 140 214 141 fn ttyReaderTask(self: *@This(), vx: *vaxis.Vaxis, tty: *vaxis.Tty, paste_allocator: ?std.mem.Allocator) void { 215 - self.ttyReaderInner(vx, tty, paste_allocator) catch |err| { 142 + return switch (builtin.target.os.tag) { 143 + .windows => self.ttyReaderWindows(vx, tty), 144 + else => self.ttyReaderPosix(vx, tty, paste_allocator), 145 + } catch |err| { 216 146 if (err != error.Canceled) log.err("ttyReader: {}", .{err}); 217 147 self.fatal = true; 218 148 };
+271 -260
src/windows/Tty.zig
··· 119 119 120 120 pub fn nextEvent(self: *Tty) !Event { 121 121 // We use a loop so we can ignore certain events 122 - var ansi_buf: [128]u8 = undefined; 123 - var ansi_idx: usize = 0; 124 - var escape_st: bool = false; 122 + var state: EventState = .{}; 125 123 while (true) { 126 124 var event_count: u32 = 0; 127 125 var input_record: INPUT_RECORD = undefined; 128 126 if (ReadConsoleInputW(self.stdin, &input_record, 1, &event_count) == 0) 129 127 return windows.unexpectedError(windows.kernel32.GetLastError()); 130 128 131 - switch (input_record.EventType) { 132 - 0x0001 => { // Key event 133 - const event = input_record.Event.KeyEvent; 129 + if (try self.eventFromRecord(&input_record, &state)) |ev| { 130 + return ev; 131 + } 132 + } 133 + } 134 134 135 - const base_layout: u21 = switch (event.wVirtualKeyCode) { 136 - 0x00 => { // delivered when we get an escape sequence 137 - ansi_buf[ansi_idx] = event.uChar.AsciiChar; 138 - ansi_idx += 1; 139 - if (ansi_idx <= 2) { 140 - continue; 141 - } 142 - switch (ansi_buf[1]) { 143 - '[' => { // CSI, read until 0x40 to 0xFF 144 - switch (event.uChar.AsciiChar) { 145 - 0x40...0xFF => { 135 + pub const EventState = struct { 136 + ansi_buf: [128]u8 = undefined, 137 + ansi_idx: usize = 0, 138 + escape_st: bool = false, 139 + }; 140 + 141 + pub fn eventFromRecord(self: *Tty, record: *const INPUT_RECORD, state: *EventState) !?Event { 142 + switch (record.EventType) { 143 + 0x0001 => { // Key event 144 + const event = record.Event.KeyEvent; 145 + 146 + const base_layout: u21 = switch (event.wVirtualKeyCode) { 147 + 0x00 => { // delivered when we get an escape sequence 148 + state.ansi_buf[state.ansi_idx] = event.uChar.AsciiChar; 149 + state.ansi_idx += 1; 150 + if (state.ansi_idx <= 2) { 151 + return null; 152 + } 153 + switch (state.ansi_buf[1]) { 154 + '[' => { // CSI, read until 0x40 to 0xFF 155 + switch (event.uChar.AsciiChar) { 156 + 0x40...0xFF => { 157 + return .cap_da1; 158 + }, 159 + else => return null, 160 + } 161 + }, 162 + ']' => { // OSC, read until ESC \ or BEL 163 + switch (event.uChar.AsciiChar) { 164 + 0x07 => { 165 + return .cap_da1; 166 + }, 167 + 0x1B => { 168 + state.escape_st = true; 169 + return null; 170 + }, 171 + '\\' => { 172 + if (state.escape_st) { 146 173 return .cap_da1; 147 - }, 148 - else => continue, 149 - } 150 - }, 151 - ']' => { // OSC, read until ESC \ or BEL 152 - switch (event.uChar.AsciiChar) { 153 - 0x07 => { 154 - return .cap_da1; 155 - }, 156 - 0x1B => { 157 - escape_st = true; 158 - continue; 159 - }, 160 - '\\' => { 161 - if (escape_st) { 162 - return .cap_da1; 163 - } 164 - continue; 165 - }, 166 - else => continue, 167 - } 168 - }, 169 - else => continue, 170 - } 171 - }, 172 - 0x08 => Key.backspace, 173 - 0x09 => Key.tab, 174 - 0x0D => Key.enter, 175 - 0x13 => Key.pause, 176 - 0x14 => Key.caps_lock, 177 - 0x1B => Key.escape, 178 - 0x20 => Key.space, 179 - 0x21 => Key.page_up, 180 - 0x22 => Key.page_down, 181 - 0x23 => Key.end, 182 - 0x24 => Key.home, 183 - 0x25 => Key.left, 184 - 0x26 => Key.up, 185 - 0x27 => Key.right, 186 - 0x28 => Key.down, 187 - 0x2c => Key.print_screen, 188 - 0x2d => Key.insert, 189 - 0x2e => Key.delete, 190 - 0x30...0x39 => |k| k, 191 - 0x41...0x5a => |k| k + 0x20, // translate to lowercase 192 - 0x5b => Key.left_meta, 193 - 0x5c => Key.right_meta, 194 - 0x60 => Key.kp_0, 195 - 0x61 => Key.kp_1, 196 - 0x62 => Key.kp_2, 197 - 0x63 => Key.kp_3, 198 - 0x64 => Key.kp_4, 199 - 0x65 => Key.kp_5, 200 - 0x66 => Key.kp_6, 201 - 0x67 => Key.kp_7, 202 - 0x68 => Key.kp_8, 203 - 0x69 => Key.kp_9, 204 - 0x6a => Key.kp_multiply, 205 - 0x6b => Key.kp_add, 206 - 0x6c => Key.kp_separator, 207 - 0x6d => Key.kp_subtract, 208 - 0x6e => Key.kp_decimal, 209 - 0x6f => Key.kp_divide, 210 - 0x70 => Key.f1, 211 - 0x71 => Key.f2, 212 - 0x72 => Key.f3, 213 - 0x73 => Key.f4, 214 - 0x74 => Key.f5, 215 - 0x75 => Key.f6, 216 - 0x76 => Key.f8, 217 - 0x77 => Key.f8, 218 - 0x78 => Key.f9, 219 - 0x79 => Key.f10, 220 - 0x7a => Key.f11, 221 - 0x7b => Key.f12, 222 - 0x7c => Key.f13, 223 - 0x7d => Key.f14, 224 - 0x7e => Key.f15, 225 - 0x7f => Key.f16, 226 - 0x80 => Key.f17, 227 - 0x81 => Key.f18, 228 - 0x82 => Key.f19, 229 - 0x83 => Key.f20, 230 - 0x84 => Key.f21, 231 - 0x85 => Key.f22, 232 - 0x86 => Key.f23, 233 - 0x87 => Key.f24, 234 - 0x90 => Key.num_lock, 235 - 0x91 => Key.scroll_lock, 236 - 0xa0 => Key.left_shift, 237 - 0xa1 => Key.right_shift, 238 - 0xa2 => Key.left_control, 239 - 0xa3 => Key.right_control, 240 - 0xa4 => Key.left_alt, 241 - 0xa5 => Key.right_alt, 242 - 0xad => Key.mute_volume, 243 - 0xae => Key.lower_volume, 244 - 0xaf => Key.raise_volume, 245 - 0xb0 => Key.media_track_next, 246 - 0xb1 => Key.media_track_previous, 247 - 0xb2 => Key.media_stop, 248 - 0xb3 => Key.media_play_pause, 249 - 0xba => ';', 250 - 0xbb => '+', 251 - 0xbc => ',', 252 - 0xbd => '-', 253 - 0xbe => '.', 254 - 0xbf => '/', 255 - 0xc0 => '`', 256 - 0xdb => '[', 257 - 0xdc => '\\', 258 - 0xdd => ']', 259 - 0xde => '\'', 260 - else => continue, 261 - }; 174 + } 175 + return null; 176 + }, 177 + else => return null, 178 + } 179 + }, 180 + else => return null, 181 + } 182 + }, 183 + 0x08 => Key.backspace, 184 + 0x09 => Key.tab, 185 + 0x0D => Key.enter, 186 + 0x13 => Key.pause, 187 + 0x14 => Key.caps_lock, 188 + 0x1B => Key.escape, 189 + 0x20 => Key.space, 190 + 0x21 => Key.page_up, 191 + 0x22 => Key.page_down, 192 + 0x23 => Key.end, 193 + 0x24 => Key.home, 194 + 0x25 => Key.left, 195 + 0x26 => Key.up, 196 + 0x27 => Key.right, 197 + 0x28 => Key.down, 198 + 0x2c => Key.print_screen, 199 + 0x2d => Key.insert, 200 + 0x2e => Key.delete, 201 + 0x30...0x39 => |k| k, 202 + 0x41...0x5a => |k| k + 0x20, // translate to lowercase 203 + 0x5b => Key.left_meta, 204 + 0x5c => Key.right_meta, 205 + 0x60 => Key.kp_0, 206 + 0x61 => Key.kp_1, 207 + 0x62 => Key.kp_2, 208 + 0x63 => Key.kp_3, 209 + 0x64 => Key.kp_4, 210 + 0x65 => Key.kp_5, 211 + 0x66 => Key.kp_6, 212 + 0x67 => Key.kp_7, 213 + 0x68 => Key.kp_8, 214 + 0x69 => Key.kp_9, 215 + 0x6a => Key.kp_multiply, 216 + 0x6b => Key.kp_add, 217 + 0x6c => Key.kp_separator, 218 + 0x6d => Key.kp_subtract, 219 + 0x6e => Key.kp_decimal, 220 + 0x6f => Key.kp_divide, 221 + 0x70 => Key.f1, 222 + 0x71 => Key.f2, 223 + 0x72 => Key.f3, 224 + 0x73 => Key.f4, 225 + 0x74 => Key.f5, 226 + 0x75 => Key.f6, 227 + 0x76 => Key.f8, 228 + 0x77 => Key.f8, 229 + 0x78 => Key.f9, 230 + 0x79 => Key.f10, 231 + 0x7a => Key.f11, 232 + 0x7b => Key.f12, 233 + 0x7c => Key.f13, 234 + 0x7d => Key.f14, 235 + 0x7e => Key.f15, 236 + 0x7f => Key.f16, 237 + 0x80 => Key.f17, 238 + 0x81 => Key.f18, 239 + 0x82 => Key.f19, 240 + 0x83 => Key.f20, 241 + 0x84 => Key.f21, 242 + 0x85 => Key.f22, 243 + 0x86 => Key.f23, 244 + 0x87 => Key.f24, 245 + 0x90 => Key.num_lock, 246 + 0x91 => Key.scroll_lock, 247 + 0xa0 => Key.left_shift, 248 + 0xa1 => Key.right_shift, 249 + 0xa2 => Key.left_control, 250 + 0xa3 => Key.right_control, 251 + 0xa4 => Key.left_alt, 252 + 0xa5 => Key.right_alt, 253 + 0xad => Key.mute_volume, 254 + 0xae => Key.lower_volume, 255 + 0xaf => Key.raise_volume, 256 + 0xb0 => Key.media_track_next, 257 + 0xb1 => Key.media_track_previous, 258 + 0xb2 => Key.media_stop, 259 + 0xb3 => Key.media_play_pause, 260 + 0xba => ';', 261 + 0xbb => '+', 262 + 0xbc => ',', 263 + 0xbd => '-', 264 + 0xbe => '.', 265 + 0xbf => '/', 266 + 0xc0 => '`', 267 + 0xdb => '[', 268 + 0xdc => '\\', 269 + 0xdd => ']', 270 + 0xde => '\'', 271 + else => return null, 272 + }; 262 273 263 - var codepoint: u21 = base_layout; 264 - var text: ?[]const u8 = null; 265 - switch (event.uChar.UnicodeChar) { 266 - 0x00...0x1F => {}, 267 - else => |cp| { 268 - codepoint = cp; 269 - const n = try std.unicode.utf8Encode(cp, &self.buf); 270 - text = self.buf[0..n]; 271 - }, 272 - } 274 + var codepoint: u21 = base_layout; 275 + var text: ?[]const u8 = null; 276 + switch (event.uChar.UnicodeChar) { 277 + 0x00...0x1F => {}, 278 + else => |cp| { 279 + codepoint = cp; 280 + const n = try std.unicode.utf8Encode(cp, &self.buf); 281 + text = self.buf[0..n]; 282 + }, 283 + } 273 284 274 - const key: Key = .{ 275 - .codepoint = codepoint, 276 - .base_layout_codepoint = base_layout, 277 - .mods = translateMods(event.dwControlKeyState), 278 - .text = text, 279 - }; 285 + const key: Key = .{ 286 + .codepoint = codepoint, 287 + .base_layout_codepoint = base_layout, 288 + .mods = translateMods(event.dwControlKeyState), 289 + .text = text, 290 + }; 280 291 281 - switch (event.bKeyDown) { 282 - 0 => return .{ .key_release = key }, 283 - else => return .{ .key_press = key }, 284 - } 285 - }, 286 - 0x0002 => { // Mouse event 287 - // see https://learn.microsoft.com/en-us/windows/console/mouse-event-record-str 292 + switch (event.bKeyDown) { 293 + 0 => return .{ .key_release = key }, 294 + else => return .{ .key_press = key }, 295 + } 296 + }, 297 + 0x0002 => { // Mouse event 298 + // see https://learn.microsoft.com/en-us/windows/console/mouse-event-record-str 288 299 289 - const event = input_record.Event.MouseEvent; 300 + const event = record.Event.MouseEvent; 290 301 291 - // High word of dwButtonState represents mouse wheel. Positive is wheel_up, negative 292 - // is wheel_down 293 - // Low word represents button state 294 - const mouse_wheel_direction: i16 = blk: { 295 - const wheelu32: u32 = event.dwButtonState >> 16; 296 - const wheelu16: u16 = @truncate(wheelu32); 297 - break :blk @bitCast(wheelu16); 298 - }; 302 + // High word of dwButtonState represents mouse wheel. Positive is wheel_up, negative 303 + // is wheel_down 304 + // Low word represents button state 305 + const mouse_wheel_direction: i16 = blk: { 306 + const wheelu32: u32 = event.dwButtonState >> 16; 307 + const wheelu16: u16 = @truncate(wheelu32); 308 + break :blk @bitCast(wheelu16); 309 + }; 299 310 300 - const buttons: u16 = @truncate(event.dwButtonState); 301 - // save the current state when we are done 302 - defer self.last_mouse_button_press = buttons; 303 - const button_xor = self.last_mouse_button_press ^ buttons; 311 + const buttons: u16 = @truncate(event.dwButtonState); 312 + // save the current state when we are done 313 + defer self.last_mouse_button_press = buttons; 314 + const button_xor = self.last_mouse_button_press ^ buttons; 304 315 305 - var event_type: Mouse.Type = .press; 306 - const btn: Mouse.Button = switch (button_xor) { 307 - 0x0000 => blk: { 308 - // Check wheel event 309 - if (event.dwEventFlags & 0x0004 > 0) { 310 - if (mouse_wheel_direction > 0) 311 - break :blk .wheel_up 312 - else 313 - break :blk .wheel_down; 314 - } 316 + var event_type: Mouse.Type = .press; 317 + const btn: Mouse.Button = switch (button_xor) { 318 + 0x0000 => blk: { 319 + // Check wheel event 320 + if (event.dwEventFlags & 0x0004 > 0) { 321 + if (mouse_wheel_direction > 0) 322 + break :blk .wheel_up 323 + else 324 + break :blk .wheel_down; 325 + } 315 326 316 - // If we have no change but one of the buttons is still pressed we have a 317 - // drag event. Find out which button is held down 318 - if (buttons > 0 and event.dwEventFlags & 0x0001 > 0) { 319 - event_type = .drag; 320 - if (buttons & 0x0001 > 0) break :blk .left; 321 - if (buttons & 0x0002 > 0) break :blk .right; 322 - if (buttons & 0x0004 > 0) break :blk .middle; 323 - if (buttons & 0x0008 > 0) break :blk .button_8; 324 - if (buttons & 0x0010 > 0) break :blk .button_9; 325 - } 327 + // If we have no change but one of the buttons is still pressed we have a 328 + // drag event. Find out which button is held down 329 + if (buttons > 0 and event.dwEventFlags & 0x0001 > 0) { 330 + event_type = .drag; 331 + if (buttons & 0x0001 > 0) break :blk .left; 332 + if (buttons & 0x0002 > 0) break :blk .right; 333 + if (buttons & 0x0004 > 0) break :blk .middle; 334 + if (buttons & 0x0008 > 0) break :blk .button_8; 335 + if (buttons & 0x0010 > 0) break :blk .button_9; 336 + } 326 337 327 - if (event.dwEventFlags & 0x0001 > 0) event_type = .motion; 328 - break :blk .none; 329 - }, 330 - 0x0001 => blk: { 331 - if (buttons & 0x0001 == 0) event_type = .release; 332 - break :blk .left; 333 - }, 334 - 0x0002 => blk: { 335 - if (buttons & 0x0002 == 0) event_type = .release; 336 - break :blk .right; 337 - }, 338 - 0x0004 => blk: { 339 - if (buttons & 0x0004 == 0) event_type = .release; 340 - break :blk .middle; 341 - }, 342 - 0x0008 => blk: { 343 - if (buttons & 0x0008 == 0) event_type = .release; 344 - break :blk .button_8; 345 - }, 346 - 0x0010 => blk: { 347 - if (buttons & 0x0010 == 0) event_type = .release; 348 - break :blk .button_9; 349 - }, 350 - else => { 351 - std.log.warn("unknown mouse event: {}", .{event}); 352 - continue; 353 - }, 354 - }; 338 + if (event.dwEventFlags & 0x0001 > 0) event_type = .motion; 339 + break :blk .none; 340 + }, 341 + 0x0001 => blk: { 342 + if (buttons & 0x0001 == 0) event_type = .release; 343 + break :blk .left; 344 + }, 345 + 0x0002 => blk: { 346 + if (buttons & 0x0002 == 0) event_type = .release; 347 + break :blk .right; 348 + }, 349 + 0x0004 => blk: { 350 + if (buttons & 0x0004 == 0) event_type = .release; 351 + break :blk .middle; 352 + }, 353 + 0x0008 => blk: { 354 + if (buttons & 0x0008 == 0) event_type = .release; 355 + break :blk .button_8; 356 + }, 357 + 0x0010 => blk: { 358 + if (buttons & 0x0010 == 0) event_type = .release; 359 + break :blk .button_9; 360 + }, 361 + else => { 362 + std.log.warn("unknown mouse event: {}", .{event}); 363 + return null; 364 + }, 365 + }; 355 366 356 - const shift: u32 = 0x0010; 357 - const alt: u32 = 0x0001 | 0x0002; 358 - const ctrl: u32 = 0x0004 | 0x0008; 359 - const mods: Mouse.Modifiers = .{ 360 - .shift = event.dwControlKeyState & shift > 0, 361 - .alt = event.dwControlKeyState & alt > 0, 362 - .ctrl = event.dwControlKeyState & ctrl > 0, 363 - }; 367 + const shift: u32 = 0x0010; 368 + const alt: u32 = 0x0001 | 0x0002; 369 + const ctrl: u32 = 0x0004 | 0x0008; 370 + const mods: Mouse.Modifiers = .{ 371 + .shift = event.dwControlKeyState & shift > 0, 372 + .alt = event.dwControlKeyState & alt > 0, 373 + .ctrl = event.dwControlKeyState & ctrl > 0, 374 + }; 364 375 365 - const mouse: Mouse = .{ 366 - .col = @as(u16, @bitCast(event.dwMousePosition.X)), // Windows reports with 0 index 367 - .row = @as(u16, @bitCast(event.dwMousePosition.Y)), // Windows reports with 0 index 368 - .mods = mods, 369 - .type = event_type, 370 - .button = btn, 371 - }; 372 - return .{ .mouse = mouse }; 373 - }, 374 - 0x0004 => { // Screen resize events 375 - // NOTE: Even though the event comes with a size, it may not be accurate. We ask for 376 - // the size directly when we get this event 377 - var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; 378 - if (windows.kernel32.GetConsoleScreenBufferInfo(self.stdout, &console_info) == 0) { 379 - return windows.unexpectedError(windows.kernel32.GetLastError()); 380 - } 381 - const window_rect = console_info.srWindow; 382 - const width = window_rect.Right - window_rect.Left + 1; 383 - const height = window_rect.Bottom - window_rect.Top + 1; 384 - return .{ 385 - .winsize = .{ 386 - .cols = @intCast(width), 387 - .rows = @intCast(height), 388 - .x_pixel = 0, 389 - .y_pixel = 0, 390 - }, 391 - }; 392 - }, 393 - 0x0010 => { // Focus events 394 - switch (input_record.Event.FocusEvent.bSetFocus) { 395 - 0 => return .focus_out, 396 - else => return .focus_in, 397 - } 398 - }, 399 - else => {}, 400 - } 376 + const mouse: Mouse = .{ 377 + .col = @as(u16, @bitCast(event.dwMousePosition.X)), // Windows reports with 0 index 378 + .row = @as(u16, @bitCast(event.dwMousePosition.Y)), // Windows reports with 0 index 379 + .mods = mods, 380 + .type = event_type, 381 + .button = btn, 382 + }; 383 + return .{ .mouse = mouse }; 384 + }, 385 + 0x0004 => { // Screen resize events 386 + // NOTE: Even though the event comes with a size, it may not be accurate. We ask for 387 + // the size directly when we get this event 388 + var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; 389 + if (windows.kernel32.GetConsoleScreenBufferInfo(self.stdout, &console_info) == 0) { 390 + return windows.unexpectedError(windows.kernel32.GetLastError()); 391 + } 392 + const window_rect = console_info.srWindow; 393 + const width = window_rect.Right - window_rect.Left + 1; 394 + const height = window_rect.Bottom - window_rect.Top + 1; 395 + return .{ 396 + .winsize = .{ 397 + .cols = @intCast(width), 398 + .rows = @intCast(height), 399 + .x_pixel = 0, 400 + .y_pixel = 0, 401 + }, 402 + }; 403 + }, 404 + 0x0010 => { // Focus events 405 + switch (record.Event.FocusEvent.bSetFocus) { 406 + 0 => return .focus_out, 407 + else => return .focus_in, 408 + } 409 + }, 410 + else => {}, 401 411 } 412 + return null; 402 413 } 403 414 404 415 fn translateMods(mods: u32) Key.Modifiers {