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?
This commit is contained in:
parent
9c2d18d5a2
commit
9b78bb8a78
5 changed files with 464 additions and 513 deletions
|
@ -23,8 +23,8 @@
|
|||
.lazy = true,
|
||||
},
|
||||
.aio = .{
|
||||
.url = "git+https://github.com/Cloudef/zig-aio#be8e2b374bf223202090e282447fa4581029c2eb",
|
||||
.hash = "122012a11b37a350395a32fdb514e57ff54a0f9d8d4ce09498b6c45ffb7211232920",
|
||||
.url = "git+https://github.com/Cloudef/zig-aio#407bb416136b61087cec2c561fa4b4103a44c5b1",
|
||||
.hash = "12202405ca6dd40f314dba6472983fcbb388118ab7446d75065b1efb982d03f515d2",
|
||||
.lazy = true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -41,7 +41,7 @@ fn audioTask(allocator: std.mem.Allocator) !void {
|
|||
|
||||
const sound = blk: {
|
||||
var tpool: coro.ThreadPool = .{};
|
||||
try tpool.start(allocator, 1);
|
||||
try tpool.start(allocator, .{});
|
||||
defer tpool.deinit();
|
||||
break :blk try tpool.yieldForCompletition(downloadTask, .{ allocator, "https://keroserene.net/lol/roll.s16" });
|
||||
};
|
||||
|
|
270
src/Loop.zig
270
src/Loop.zig
|
@ -8,6 +8,7 @@ const Parser = @import("Parser.zig");
|
|||
const Queue = @import("queue.zig").Queue;
|
||||
const Tty = @import("main.zig").Tty;
|
||||
const Vaxis = @import("Vaxis.zig");
|
||||
const log = std.log.scoped(.loop);
|
||||
|
||||
pub fn Loop(comptime T: type) type {
|
||||
return struct {
|
||||
|
@ -15,8 +16,6 @@ pub fn Loop(comptime T: type) type {
|
|||
|
||||
const Event = T;
|
||||
|
||||
const log = std.log.scoped(.loop);
|
||||
|
||||
tty: *Tty,
|
||||
vaxis: *Vaxis,
|
||||
|
||||
|
@ -110,38 +109,7 @@ pub fn Loop(comptime T: type) type {
|
|||
.windows => {
|
||||
while (!self.should_quit) {
|
||||
const event = try self.tty.nextEvent();
|
||||
switch (event) {
|
||||
.winsize => |ws| {
|
||||
if (@hasField(Event, "winsize")) {
|
||||
self.postEvent(.{ .winsize = ws });
|
||||
}
|
||||
},
|
||||
.key_press => |key| {
|
||||
if (@hasField(Event, "key_press")) {
|
||||
// HACK: yuck. there has to be a better way
|
||||
var mut_key = key;
|
||||
if (key.text) |text| {
|
||||
mut_key.text = cache.put(text);
|
||||
}
|
||||
self.postEvent(.{ .key_press = mut_key });
|
||||
}
|
||||
},
|
||||
.key_release => |*key| {
|
||||
if (@hasField(Event, "key_release")) {
|
||||
// HACK: yuck. there has to be a better way
|
||||
var mut_key = key;
|
||||
if (key.text) |text| {
|
||||
mut_key.text = cache.put(text);
|
||||
}
|
||||
self.postEvent(.{ .key_release = mut_key });
|
||||
}
|
||||
},
|
||||
.cap_da1 => {
|
||||
std.Thread.Futex.wake(&self.vaxis.query_futex, 10);
|
||||
},
|
||||
.mouse => {}, // Unsupported currently
|
||||
else => {},
|
||||
}
|
||||
try handleEventGeneric(self, self.vaxis, &cache, Event, event, null);
|
||||
}
|
||||
},
|
||||
else => {
|
||||
|
@ -178,102 +146,7 @@ pub fn Loop(comptime T: type) type {
|
|||
seq_start += result.n;
|
||||
|
||||
const event = result.event orelse continue;
|
||||
switch (event) {
|
||||
.key_press => |key| {
|
||||
if (@hasField(Event, "key_press")) {
|
||||
// HACK: yuck. there has to be a better way
|
||||
var mut_key = key;
|
||||
if (key.text) |text| {
|
||||
mut_key.text = cache.put(text);
|
||||
}
|
||||
self.postEvent(.{ .key_press = mut_key });
|
||||
}
|
||||
},
|
||||
.key_release => |*key| {
|
||||
if (@hasField(Event, "key_release")) {
|
||||
// HACK: yuck. there has to be a better way
|
||||
var mut_key = key;
|
||||
if (key.text) |text| {
|
||||
mut_key.text = cache.put(text);
|
||||
}
|
||||
self.postEvent(.{ .key_release = mut_key });
|
||||
}
|
||||
},
|
||||
.mouse => |mouse| {
|
||||
if (@hasField(Event, "mouse")) {
|
||||
self.postEvent(.{ .mouse = self.vaxis.translateMouse(mouse) });
|
||||
}
|
||||
},
|
||||
.focus_in => {
|
||||
if (@hasField(Event, "focus_in")) {
|
||||
self.postEvent(.focus_in);
|
||||
}
|
||||
},
|
||||
.focus_out => {
|
||||
if (@hasField(Event, "focus_out")) {
|
||||
self.postEvent(.focus_out);
|
||||
}
|
||||
},
|
||||
.paste_start => {
|
||||
if (@hasField(Event, "paste_start")) {
|
||||
self.postEvent(.paste_start);
|
||||
}
|
||||
},
|
||||
.paste_end => {
|
||||
if (@hasField(Event, "paste_end")) {
|
||||
self.postEvent(.paste_end);
|
||||
}
|
||||
},
|
||||
.paste => |text| {
|
||||
if (@hasField(Event, "paste")) {
|
||||
self.postEvent(.{ .paste = text });
|
||||
} else {
|
||||
if (paste_allocator) |_|
|
||||
paste_allocator.?.free(text);
|
||||
}
|
||||
},
|
||||
.color_report => |report| {
|
||||
if (@hasField(Event, "color_report")) {
|
||||
self.postEvent(.{ .color_report = report });
|
||||
}
|
||||
},
|
||||
.color_scheme => |scheme| {
|
||||
if (@hasField(Event, "color_scheme")) {
|
||||
self.postEvent(.{ .color_scheme = scheme });
|
||||
}
|
||||
},
|
||||
.cap_kitty_keyboard => {
|
||||
log.info("kitty keyboard capability detected", .{});
|
||||
self.vaxis.caps.kitty_keyboard = true;
|
||||
},
|
||||
.cap_kitty_graphics => {
|
||||
if (!self.vaxis.caps.kitty_graphics) {
|
||||
log.info("kitty graphics capability detected", .{});
|
||||
self.vaxis.caps.kitty_graphics = true;
|
||||
}
|
||||
},
|
||||
.cap_rgb => {
|
||||
log.info("rgb capability detected", .{});
|
||||
self.vaxis.caps.rgb = true;
|
||||
},
|
||||
.cap_unicode => {
|
||||
log.info("unicode capability detected", .{});
|
||||
self.vaxis.caps.unicode = .unicode;
|
||||
self.vaxis.screen.width_method = .unicode;
|
||||
},
|
||||
.cap_sgr_pixels => {
|
||||
log.info("pixel mouse capability detected", .{});
|
||||
self.vaxis.caps.sgr_pixels = true;
|
||||
},
|
||||
.cap_color_scheme_updates => {
|
||||
log.info("color_scheme_updates capability detected", .{});
|
||||
self.vaxis.caps.color_scheme_updates = true;
|
||||
},
|
||||
.cap_da1 => {
|
||||
std.Thread.Futex.wake(&self.vaxis.query_futex, 10);
|
||||
},
|
||||
.winsize => unreachable, // handled elsewhere for posix
|
||||
}
|
||||
try handleEventGeneric(self, self.vaxis, &cache, Event, event, paste_allocator);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -281,3 +154,140 @@ pub fn Loop(comptime T: type) type {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn handleEventGeneric(self: anytype, vx: *Vaxis, cache: *GraphemeCache, Event: type, event: anytype, paste_allocator: ?std.mem.Allocator) !void {
|
||||
switch (builtin.os.tag) {
|
||||
.windows => {
|
||||
switch (event) {
|
||||
.winsize => |ws| {
|
||||
if (@hasField(Event, "winsize")) {
|
||||
return self.postEvent(.{ .winsize = ws });
|
||||
}
|
||||
},
|
||||
.key_press => |key| {
|
||||
if (@hasField(Event, "key_press")) {
|
||||
// HACK: yuck. there has to be a better way
|
||||
var mut_key = key;
|
||||
if (key.text) |text| {
|
||||
mut_key.text = cache.put(text);
|
||||
}
|
||||
return self.postEvent(.{ .key_press = mut_key });
|
||||
}
|
||||
},
|
||||
.key_release => |*key| {
|
||||
if (@hasField(Event, "key_release")) {
|
||||
// HACK: yuck. there has to be a better way
|
||||
var mut_key = key;
|
||||
if (key.text) |text| {
|
||||
mut_key.text = cache.put(text);
|
||||
}
|
||||
return self.postEvent(.{ .key_release = mut_key });
|
||||
}
|
||||
},
|
||||
.cap_da1 => {
|
||||
std.Thread.Futex.wake(&vx.query_futex, 10);
|
||||
},
|
||||
.mouse => {}, // Unsupported currently
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
else => {
|
||||
switch (event) {
|
||||
.key_press => |key| {
|
||||
if (@hasField(Event, "key_press")) {
|
||||
// HACK: yuck. there has to be a better way
|
||||
var mut_key = key;
|
||||
if (key.text) |text| {
|
||||
mut_key.text = cache.put(text);
|
||||
}
|
||||
return self.postEvent(.{ .key_press = mut_key });
|
||||
}
|
||||
},
|
||||
.key_release => |*key| {
|
||||
if (@hasField(Event, "key_release")) {
|
||||
// HACK: yuck. there has to be a better way
|
||||
var mut_key = key;
|
||||
if (key.text) |text| {
|
||||
mut_key.text = cache.put(text);
|
||||
}
|
||||
return self.postEvent(.{ .key_release = mut_key });
|
||||
}
|
||||
},
|
||||
.mouse => |mouse| {
|
||||
if (@hasField(Event, "mouse")) {
|
||||
return self.postEvent(.{ .mouse = vx.translateMouse(mouse) });
|
||||
}
|
||||
},
|
||||
.focus_in => {
|
||||
if (@hasField(Event, "focus_in")) {
|
||||
return self.postEvent(.focus_in);
|
||||
}
|
||||
},
|
||||
.focus_out => {
|
||||
if (@hasField(Event, "focus_out")) {
|
||||
return self.postEvent(.focus_out);
|
||||
}
|
||||
},
|
||||
.paste_start => {
|
||||
if (@hasField(Event, "paste_start")) {
|
||||
return self.postEvent(.paste_start);
|
||||
}
|
||||
},
|
||||
.paste_end => {
|
||||
if (@hasField(Event, "paste_end")) {
|
||||
return self.postEvent(.paste_end);
|
||||
}
|
||||
},
|
||||
.paste => |text| {
|
||||
if (@hasField(Event, "paste")) {
|
||||
return self.postEvent(.{ .paste = text });
|
||||
} else {
|
||||
if (paste_allocator) |_|
|
||||
paste_allocator.?.free(text);
|
||||
}
|
||||
},
|
||||
.color_report => |report| {
|
||||
if (@hasField(Event, "color_report")) {
|
||||
return self.postEvent(.{ .color_report = report });
|
||||
}
|
||||
},
|
||||
.color_scheme => |scheme| {
|
||||
if (@hasField(Event, "color_scheme")) {
|
||||
return self.postEvent(.{ .color_scheme = scheme });
|
||||
}
|
||||
},
|
||||
.cap_kitty_keyboard => {
|
||||
log.info("kitty keyboard capability detected", .{});
|
||||
vx.caps.kitty_keyboard = true;
|
||||
},
|
||||
.cap_kitty_graphics => {
|
||||
if (!vx.caps.kitty_graphics) {
|
||||
log.info("kitty graphics capability detected", .{});
|
||||
vx.caps.kitty_graphics = true;
|
||||
}
|
||||
},
|
||||
.cap_rgb => {
|
||||
log.info("rgb capability detected", .{});
|
||||
vx.caps.rgb = true;
|
||||
},
|
||||
.cap_unicode => {
|
||||
log.info("unicode capability detected", .{});
|
||||
vx.caps.unicode = .unicode;
|
||||
vx.screen.width_method = .unicode;
|
||||
},
|
||||
.cap_sgr_pixels => {
|
||||
log.info("pixel mouse capability detected", .{});
|
||||
vx.caps.sgr_pixels = true;
|
||||
},
|
||||
.cap_color_scheme_updates => {
|
||||
log.info("color_scheme_updates capability detected", .{});
|
||||
vx.caps.color_scheme_updates = true;
|
||||
},
|
||||
.cap_da1 => {
|
||||
std.Thread.Futex.wake(&vx.query_futex, 10);
|
||||
},
|
||||
.winsize => unreachable, // handled elsewhere for posix
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
146
src/aio.zig
146
src/aio.zig
|
@ -3,14 +3,9 @@ const std = @import("std");
|
|||
const aio = @import("aio");
|
||||
const coro = @import("coro");
|
||||
const vaxis = @import("main.zig");
|
||||
const handleEventGeneric = @import("Loop.zig").handleEventGeneric;
|
||||
const log = std.log.scoped(.vaxis_aio);
|
||||
|
||||
comptime {
|
||||
if (builtin.target.os.tag == .windows) {
|
||||
@compileError("Windows is not supported right now");
|
||||
}
|
||||
}
|
||||
|
||||
const Yield = enum { no_state, took_event };
|
||||
|
||||
/// zig-aio based event loop
|
||||
|
@ -52,9 +47,11 @@ pub fn Loop(comptime T: type) type {
|
|||
|
||||
// keep on stack
|
||||
var ctx: Context = .{ .loop = self, .tty = tty };
|
||||
if (@hasField(Event, "winsize")) {
|
||||
const handler: vaxis.Tty.SignalHandler = .{ .context = &ctx, .callback = Context.cb };
|
||||
try vaxis.Tty.notifyWinsize(handler);
|
||||
if (builtin.target.os.tag != .windows) {
|
||||
if (@hasField(Event, "winsize")) {
|
||||
const handler: vaxis.Tty.SignalHandler = .{ .context = &ctx, .callback = Context.cb };
|
||||
try vaxis.Tty.notifyWinsize(handler);
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
|
@ -74,7 +71,32 @@ pub fn Loop(comptime T: type) type {
|
|||
};
|
||||
}
|
||||
|
||||
fn ttyReaderInner(self: *@This(), vx: *vaxis.Vaxis, tty: *vaxis.Tty, paste_allocator: ?std.mem.Allocator) !void {
|
||||
fn windowsReadEvent(tty: *vaxis.Tty) !vaxis.Event {
|
||||
var state: vaxis.Tty.EventState = .{};
|
||||
while (true) {
|
||||
var bytes_read: usize = 0;
|
||||
var input_record: vaxis.Tty.INPUT_RECORD = undefined;
|
||||
try coro.io.single(aio.ReadTty{
|
||||
.tty = .{ .handle = tty.stdin },
|
||||
.buffer = std.mem.asBytes(&input_record),
|
||||
.out_read = &bytes_read,
|
||||
});
|
||||
|
||||
if (try tty.eventFromRecord(&input_record, &state)) |ev| {
|
||||
return ev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ttyReaderWindows(self: *@This(), vx: *vaxis.Vaxis, tty: *vaxis.Tty) !void {
|
||||
var cache: vaxis.GraphemeCache = .{};
|
||||
while (true) {
|
||||
const event = try windowsReadEvent(tty);
|
||||
try handleEventGeneric(self, vx, &cache, Event, event, null);
|
||||
}
|
||||
}
|
||||
|
||||
fn ttyReaderPosix(self: *@This(), vx: *vaxis.Vaxis, tty: *vaxis.Tty, paste_allocator: ?std.mem.Allocator) !void {
|
||||
// initialize a grapheme cache
|
||||
var cache: vaxis.GraphemeCache = .{};
|
||||
|
||||
|
@ -93,7 +115,7 @@ pub fn Loop(comptime T: type) type {
|
|||
var buf: [4096]u8 = undefined;
|
||||
var n: usize = undefined;
|
||||
var read_start: usize = 0;
|
||||
try coro.io.single(aio.Read{ .file = file, .buffer = buf[read_start..], .out_read = &n });
|
||||
try coro.io.single(aio.ReadTty{ .tty = file, .buffer = buf[read_start..], .out_read = &n });
|
||||
var seq_start: usize = 0;
|
||||
while (seq_start < n) {
|
||||
const result = try parser.parse(buf[seq_start..n], paste_allocator);
|
||||
|
@ -111,108 +133,16 @@ pub fn Loop(comptime T: type) type {
|
|||
seq_start += result.n;
|
||||
|
||||
const event = result.event orelse continue;
|
||||
switch (event) {
|
||||
.key_press => |key| {
|
||||
if (@hasField(Event, "key_press")) {
|
||||
// HACK: yuck. there has to be a better way
|
||||
var mut_key = key;
|
||||
if (key.text) |text| {
|
||||
mut_key.text = cache.put(text);
|
||||
}
|
||||
try self.postEvent(.{ .key_press = mut_key });
|
||||
}
|
||||
},
|
||||
.key_release => |*key| {
|
||||
if (@hasField(Event, "key_release")) {
|
||||
// HACK: yuck. there has to be a better way
|
||||
var mut_key = key;
|
||||
if (key.text) |text| {
|
||||
mut_key.text = cache.put(text);
|
||||
}
|
||||
try self.postEvent(.{ .key_release = mut_key });
|
||||
}
|
||||
},
|
||||
.mouse => |mouse| {
|
||||
if (@hasField(Event, "mouse")) {
|
||||
try self.postEvent(.{ .mouse = vx.translateMouse(mouse) });
|
||||
}
|
||||
},
|
||||
.focus_in => {
|
||||
if (@hasField(Event, "focus_in")) {
|
||||
try self.postEvent(.focus_in);
|
||||
}
|
||||
},
|
||||
.focus_out => {
|
||||
if (@hasField(Event, "focus_out")) {
|
||||
try self.postEvent(.focus_out);
|
||||
}
|
||||
},
|
||||
.paste_start => {
|
||||
if (@hasField(Event, "paste_start")) {
|
||||
try self.postEvent(.paste_start);
|
||||
}
|
||||
},
|
||||
.paste_end => {
|
||||
if (@hasField(Event, "paste_end")) {
|
||||
try self.postEvent(.paste_end);
|
||||
}
|
||||
},
|
||||
.paste => |text| {
|
||||
if (@hasField(Event, "paste")) {
|
||||
try self.postEvent(.{ .paste = text });
|
||||
} else {
|
||||
if (paste_allocator) |_|
|
||||
paste_allocator.?.free(text);
|
||||
}
|
||||
},
|
||||
.color_report => |report| {
|
||||
if (@hasField(Event, "color_report")) {
|
||||
try self.postEvent(.{ .color_report = report });
|
||||
}
|
||||
},
|
||||
.color_scheme => |scheme| {
|
||||
if (@hasField(Event, "color_scheme")) {
|
||||
try self.postEvent(.{ .color_scheme = scheme });
|
||||
}
|
||||
},
|
||||
.cap_kitty_keyboard => {
|
||||
log.info("kitty keyboard capability detected", .{});
|
||||
vx.caps.kitty_keyboard = true;
|
||||
},
|
||||
.cap_kitty_graphics => {
|
||||
if (!vx.caps.kitty_graphics) {
|
||||
log.info("kitty graphics capability detected", .{});
|
||||
vx.caps.kitty_graphics = true;
|
||||
}
|
||||
},
|
||||
.cap_rgb => {
|
||||
log.info("rgb capability detected", .{});
|
||||
vx.caps.rgb = true;
|
||||
},
|
||||
.cap_unicode => {
|
||||
log.info("unicode capability detected", .{});
|
||||
vx.caps.unicode = .unicode;
|
||||
vx.screen.width_method = .unicode;
|
||||
},
|
||||
.cap_sgr_pixels => {
|
||||
log.info("pixel mouse capability detected", .{});
|
||||
vx.caps.sgr_pixels = true;
|
||||
},
|
||||
.cap_color_scheme_updates => {
|
||||
log.info("color_scheme_updates capability detected", .{});
|
||||
vx.caps.color_scheme_updates = true;
|
||||
},
|
||||
.cap_da1 => {
|
||||
std.Thread.Futex.wake(&vx.query_futex, 10);
|
||||
},
|
||||
.winsize => unreachable, // handled elsewhere for posix
|
||||
}
|
||||
try handleEventGeneric(self, vx, &cache, Event, event, paste_allocator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ttyReaderTask(self: *@This(), vx: *vaxis.Vaxis, tty: *vaxis.Tty, paste_allocator: ?std.mem.Allocator) void {
|
||||
self.ttyReaderInner(vx, tty, paste_allocator) catch |err| {
|
||||
return switch (builtin.target.os.tag) {
|
||||
.windows => self.ttyReaderWindows(vx, tty),
|
||||
else => self.ttyReaderPosix(vx, tty, paste_allocator),
|
||||
} catch |err| {
|
||||
if (err != error.Canceled) log.err("ttyReader: {}", .{err});
|
||||
self.fatal = true;
|
||||
};
|
||||
|
|
|
@ -119,288 +119,299 @@ pub fn bufferedWriter(self: *const Tty) std.io.BufferedWriter(4096, std.io.AnyWr
|
|||
|
||||
pub fn nextEvent(self: *Tty) !Event {
|
||||
// We use a loop so we can ignore certain events
|
||||
var ansi_buf: [128]u8 = undefined;
|
||||
var ansi_idx: usize = 0;
|
||||
var escape_st: bool = false;
|
||||
var state: EventState = .{};
|
||||
while (true) {
|
||||
var event_count: u32 = 0;
|
||||
var input_record: INPUT_RECORD = undefined;
|
||||
if (ReadConsoleInputW(self.stdin, &input_record, 1, &event_count) == 0)
|
||||
return windows.unexpectedError(windows.kernel32.GetLastError());
|
||||
|
||||
switch (input_record.EventType) {
|
||||
0x0001 => { // Key event
|
||||
const event = input_record.Event.KeyEvent;
|
||||
|
||||
const base_layout: u21 = switch (event.wVirtualKeyCode) {
|
||||
0x00 => { // delivered when we get an escape sequence
|
||||
ansi_buf[ansi_idx] = event.uChar.AsciiChar;
|
||||
ansi_idx += 1;
|
||||
if (ansi_idx <= 2) {
|
||||
continue;
|
||||
}
|
||||
switch (ansi_buf[1]) {
|
||||
'[' => { // CSI, read until 0x40 to 0xFF
|
||||
switch (event.uChar.AsciiChar) {
|
||||
0x40...0xFF => {
|
||||
return .cap_da1;
|
||||
},
|
||||
else => continue,
|
||||
}
|
||||
},
|
||||
']' => { // OSC, read until ESC \ or BEL
|
||||
switch (event.uChar.AsciiChar) {
|
||||
0x07 => {
|
||||
return .cap_da1;
|
||||
},
|
||||
0x1B => {
|
||||
escape_st = true;
|
||||
continue;
|
||||
},
|
||||
'\\' => {
|
||||
if (escape_st) {
|
||||
return .cap_da1;
|
||||
}
|
||||
continue;
|
||||
},
|
||||
else => continue,
|
||||
}
|
||||
},
|
||||
else => continue,
|
||||
}
|
||||
},
|
||||
0x08 => Key.backspace,
|
||||
0x09 => Key.tab,
|
||||
0x0D => Key.enter,
|
||||
0x13 => Key.pause,
|
||||
0x14 => Key.caps_lock,
|
||||
0x1B => Key.escape,
|
||||
0x20 => Key.space,
|
||||
0x21 => Key.page_up,
|
||||
0x22 => Key.page_down,
|
||||
0x23 => Key.end,
|
||||
0x24 => Key.home,
|
||||
0x25 => Key.left,
|
||||
0x26 => Key.up,
|
||||
0x27 => Key.right,
|
||||
0x28 => Key.down,
|
||||
0x2c => Key.print_screen,
|
||||
0x2d => Key.insert,
|
||||
0x2e => Key.delete,
|
||||
0x30...0x39 => |k| k,
|
||||
0x41...0x5a => |k| k + 0x20, // translate to lowercase
|
||||
0x5b => Key.left_meta,
|
||||
0x5c => Key.right_meta,
|
||||
0x60 => Key.kp_0,
|
||||
0x61 => Key.kp_1,
|
||||
0x62 => Key.kp_2,
|
||||
0x63 => Key.kp_3,
|
||||
0x64 => Key.kp_4,
|
||||
0x65 => Key.kp_5,
|
||||
0x66 => Key.kp_6,
|
||||
0x67 => Key.kp_7,
|
||||
0x68 => Key.kp_8,
|
||||
0x69 => Key.kp_9,
|
||||
0x6a => Key.kp_multiply,
|
||||
0x6b => Key.kp_add,
|
||||
0x6c => Key.kp_separator,
|
||||
0x6d => Key.kp_subtract,
|
||||
0x6e => Key.kp_decimal,
|
||||
0x6f => Key.kp_divide,
|
||||
0x70 => Key.f1,
|
||||
0x71 => Key.f2,
|
||||
0x72 => Key.f3,
|
||||
0x73 => Key.f4,
|
||||
0x74 => Key.f5,
|
||||
0x75 => Key.f6,
|
||||
0x76 => Key.f8,
|
||||
0x77 => Key.f8,
|
||||
0x78 => Key.f9,
|
||||
0x79 => Key.f10,
|
||||
0x7a => Key.f11,
|
||||
0x7b => Key.f12,
|
||||
0x7c => Key.f13,
|
||||
0x7d => Key.f14,
|
||||
0x7e => Key.f15,
|
||||
0x7f => Key.f16,
|
||||
0x80 => Key.f17,
|
||||
0x81 => Key.f18,
|
||||
0x82 => Key.f19,
|
||||
0x83 => Key.f20,
|
||||
0x84 => Key.f21,
|
||||
0x85 => Key.f22,
|
||||
0x86 => Key.f23,
|
||||
0x87 => Key.f24,
|
||||
0x90 => Key.num_lock,
|
||||
0x91 => Key.scroll_lock,
|
||||
0xa0 => Key.left_shift,
|
||||
0xa1 => Key.right_shift,
|
||||
0xa2 => Key.left_control,
|
||||
0xa3 => Key.right_control,
|
||||
0xa4 => Key.left_alt,
|
||||
0xa5 => Key.right_alt,
|
||||
0xad => Key.mute_volume,
|
||||
0xae => Key.lower_volume,
|
||||
0xaf => Key.raise_volume,
|
||||
0xb0 => Key.media_track_next,
|
||||
0xb1 => Key.media_track_previous,
|
||||
0xb2 => Key.media_stop,
|
||||
0xb3 => Key.media_play_pause,
|
||||
0xba => ';',
|
||||
0xbb => '+',
|
||||
0xbc => ',',
|
||||
0xbd => '-',
|
||||
0xbe => '.',
|
||||
0xbf => '/',
|
||||
0xc0 => '`',
|
||||
0xdb => '[',
|
||||
0xdc => '\\',
|
||||
0xdd => ']',
|
||||
0xde => '\'',
|
||||
else => continue,
|
||||
};
|
||||
|
||||
var codepoint: u21 = base_layout;
|
||||
var text: ?[]const u8 = null;
|
||||
switch (event.uChar.UnicodeChar) {
|
||||
0x00...0x1F => {},
|
||||
else => |cp| {
|
||||
codepoint = cp;
|
||||
const n = try std.unicode.utf8Encode(cp, &self.buf);
|
||||
text = self.buf[0..n];
|
||||
},
|
||||
}
|
||||
|
||||
const key: Key = .{
|
||||
.codepoint = codepoint,
|
||||
.base_layout_codepoint = base_layout,
|
||||
.mods = translateMods(event.dwControlKeyState),
|
||||
.text = text,
|
||||
};
|
||||
|
||||
switch (event.bKeyDown) {
|
||||
0 => return .{ .key_release = key },
|
||||
else => return .{ .key_press = key },
|
||||
}
|
||||
},
|
||||
0x0002 => { // Mouse event
|
||||
// see https://learn.microsoft.com/en-us/windows/console/mouse-event-record-str
|
||||
|
||||
const event = input_record.Event.MouseEvent;
|
||||
|
||||
// High word of dwButtonState represents mouse wheel. Positive is wheel_up, negative
|
||||
// is wheel_down
|
||||
// Low word represents button state
|
||||
const mouse_wheel_direction: i16 = blk: {
|
||||
const wheelu32: u32 = event.dwButtonState >> 16;
|
||||
const wheelu16: u16 = @truncate(wheelu32);
|
||||
break :blk @bitCast(wheelu16);
|
||||
};
|
||||
|
||||
const buttons: u16 = @truncate(event.dwButtonState);
|
||||
// save the current state when we are done
|
||||
defer self.last_mouse_button_press = buttons;
|
||||
const button_xor = self.last_mouse_button_press ^ buttons;
|
||||
|
||||
var event_type: Mouse.Type = .press;
|
||||
const btn: Mouse.Button = switch (button_xor) {
|
||||
0x0000 => blk: {
|
||||
// Check wheel event
|
||||
if (event.dwEventFlags & 0x0004 > 0) {
|
||||
if (mouse_wheel_direction > 0)
|
||||
break :blk .wheel_up
|
||||
else
|
||||
break :blk .wheel_down;
|
||||
}
|
||||
|
||||
// If we have no change but one of the buttons is still pressed we have a
|
||||
// drag event. Find out which button is held down
|
||||
if (buttons > 0 and event.dwEventFlags & 0x0001 > 0) {
|
||||
event_type = .drag;
|
||||
if (buttons & 0x0001 > 0) break :blk .left;
|
||||
if (buttons & 0x0002 > 0) break :blk .right;
|
||||
if (buttons & 0x0004 > 0) break :blk .middle;
|
||||
if (buttons & 0x0008 > 0) break :blk .button_8;
|
||||
if (buttons & 0x0010 > 0) break :blk .button_9;
|
||||
}
|
||||
|
||||
if (event.dwEventFlags & 0x0001 > 0) event_type = .motion;
|
||||
break :blk .none;
|
||||
},
|
||||
0x0001 => blk: {
|
||||
if (buttons & 0x0001 == 0) event_type = .release;
|
||||
break :blk .left;
|
||||
},
|
||||
0x0002 => blk: {
|
||||
if (buttons & 0x0002 == 0) event_type = .release;
|
||||
break :blk .right;
|
||||
},
|
||||
0x0004 => blk: {
|
||||
if (buttons & 0x0004 == 0) event_type = .release;
|
||||
break :blk .middle;
|
||||
},
|
||||
0x0008 => blk: {
|
||||
if (buttons & 0x0008 == 0) event_type = .release;
|
||||
break :blk .button_8;
|
||||
},
|
||||
0x0010 => blk: {
|
||||
if (buttons & 0x0010 == 0) event_type = .release;
|
||||
break :blk .button_9;
|
||||
},
|
||||
else => {
|
||||
std.log.warn("unknown mouse event: {}", .{event});
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
const shift: u32 = 0x0010;
|
||||
const alt: u32 = 0x0001 | 0x0002;
|
||||
const ctrl: u32 = 0x0004 | 0x0008;
|
||||
const mods: Mouse.Modifiers = .{
|
||||
.shift = event.dwControlKeyState & shift > 0,
|
||||
.alt = event.dwControlKeyState & alt > 0,
|
||||
.ctrl = event.dwControlKeyState & ctrl > 0,
|
||||
};
|
||||
|
||||
const mouse: Mouse = .{
|
||||
.col = @as(u16, @bitCast(event.dwMousePosition.X)), // Windows reports with 0 index
|
||||
.row = @as(u16, @bitCast(event.dwMousePosition.Y)), // Windows reports with 0 index
|
||||
.mods = mods,
|
||||
.type = event_type,
|
||||
.button = btn,
|
||||
};
|
||||
return .{ .mouse = mouse };
|
||||
},
|
||||
0x0004 => { // Screen resize events
|
||||
// NOTE: Even though the event comes with a size, it may not be accurate. We ask for
|
||||
// the size directly when we get this event
|
||||
var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
|
||||
if (windows.kernel32.GetConsoleScreenBufferInfo(self.stdout, &console_info) == 0) {
|
||||
return windows.unexpectedError(windows.kernel32.GetLastError());
|
||||
}
|
||||
const window_rect = console_info.srWindow;
|
||||
const width = window_rect.Right - window_rect.Left + 1;
|
||||
const height = window_rect.Bottom - window_rect.Top + 1;
|
||||
return .{
|
||||
.winsize = .{
|
||||
.cols = @intCast(width),
|
||||
.rows = @intCast(height),
|
||||
.x_pixel = 0,
|
||||
.y_pixel = 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
0x0010 => { // Focus events
|
||||
switch (input_record.Event.FocusEvent.bSetFocus) {
|
||||
0 => return .focus_out,
|
||||
else => return .focus_in,
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
if (try self.eventFromRecord(&input_record, &state)) |ev| {
|
||||
return ev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const EventState = struct {
|
||||
ansi_buf: [128]u8 = undefined,
|
||||
ansi_idx: usize = 0,
|
||||
escape_st: bool = false,
|
||||
};
|
||||
|
||||
pub fn eventFromRecord(self: *Tty, record: *const INPUT_RECORD, state: *EventState) !?Event {
|
||||
switch (record.EventType) {
|
||||
0x0001 => { // Key event
|
||||
const event = record.Event.KeyEvent;
|
||||
|
||||
const base_layout: u21 = switch (event.wVirtualKeyCode) {
|
||||
0x00 => { // delivered when we get an escape sequence
|
||||
state.ansi_buf[state.ansi_idx] = event.uChar.AsciiChar;
|
||||
state.ansi_idx += 1;
|
||||
if (state.ansi_idx <= 2) {
|
||||
return null;
|
||||
}
|
||||
switch (state.ansi_buf[1]) {
|
||||
'[' => { // CSI, read until 0x40 to 0xFF
|
||||
switch (event.uChar.AsciiChar) {
|
||||
0x40...0xFF => {
|
||||
return .cap_da1;
|
||||
},
|
||||
else => return null,
|
||||
}
|
||||
},
|
||||
']' => { // OSC, read until ESC \ or BEL
|
||||
switch (event.uChar.AsciiChar) {
|
||||
0x07 => {
|
||||
return .cap_da1;
|
||||
},
|
||||
0x1B => {
|
||||
state.escape_st = true;
|
||||
return null;
|
||||
},
|
||||
'\\' => {
|
||||
if (state.escape_st) {
|
||||
return .cap_da1;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
else => return null,
|
||||
}
|
||||
},
|
||||
else => return null,
|
||||
}
|
||||
},
|
||||
0x08 => Key.backspace,
|
||||
0x09 => Key.tab,
|
||||
0x0D => Key.enter,
|
||||
0x13 => Key.pause,
|
||||
0x14 => Key.caps_lock,
|
||||
0x1B => Key.escape,
|
||||
0x20 => Key.space,
|
||||
0x21 => Key.page_up,
|
||||
0x22 => Key.page_down,
|
||||
0x23 => Key.end,
|
||||
0x24 => Key.home,
|
||||
0x25 => Key.left,
|
||||
0x26 => Key.up,
|
||||
0x27 => Key.right,
|
||||
0x28 => Key.down,
|
||||
0x2c => Key.print_screen,
|
||||
0x2d => Key.insert,
|
||||
0x2e => Key.delete,
|
||||
0x30...0x39 => |k| k,
|
||||
0x41...0x5a => |k| k + 0x20, // translate to lowercase
|
||||
0x5b => Key.left_meta,
|
||||
0x5c => Key.right_meta,
|
||||
0x60 => Key.kp_0,
|
||||
0x61 => Key.kp_1,
|
||||
0x62 => Key.kp_2,
|
||||
0x63 => Key.kp_3,
|
||||
0x64 => Key.kp_4,
|
||||
0x65 => Key.kp_5,
|
||||
0x66 => Key.kp_6,
|
||||
0x67 => Key.kp_7,
|
||||
0x68 => Key.kp_8,
|
||||
0x69 => Key.kp_9,
|
||||
0x6a => Key.kp_multiply,
|
||||
0x6b => Key.kp_add,
|
||||
0x6c => Key.kp_separator,
|
||||
0x6d => Key.kp_subtract,
|
||||
0x6e => Key.kp_decimal,
|
||||
0x6f => Key.kp_divide,
|
||||
0x70 => Key.f1,
|
||||
0x71 => Key.f2,
|
||||
0x72 => Key.f3,
|
||||
0x73 => Key.f4,
|
||||
0x74 => Key.f5,
|
||||
0x75 => Key.f6,
|
||||
0x76 => Key.f8,
|
||||
0x77 => Key.f8,
|
||||
0x78 => Key.f9,
|
||||
0x79 => Key.f10,
|
||||
0x7a => Key.f11,
|
||||
0x7b => Key.f12,
|
||||
0x7c => Key.f13,
|
||||
0x7d => Key.f14,
|
||||
0x7e => Key.f15,
|
||||
0x7f => Key.f16,
|
||||
0x80 => Key.f17,
|
||||
0x81 => Key.f18,
|
||||
0x82 => Key.f19,
|
||||
0x83 => Key.f20,
|
||||
0x84 => Key.f21,
|
||||
0x85 => Key.f22,
|
||||
0x86 => Key.f23,
|
||||
0x87 => Key.f24,
|
||||
0x90 => Key.num_lock,
|
||||
0x91 => Key.scroll_lock,
|
||||
0xa0 => Key.left_shift,
|
||||
0xa1 => Key.right_shift,
|
||||
0xa2 => Key.left_control,
|
||||
0xa3 => Key.right_control,
|
||||
0xa4 => Key.left_alt,
|
||||
0xa5 => Key.right_alt,
|
||||
0xad => Key.mute_volume,
|
||||
0xae => Key.lower_volume,
|
||||
0xaf => Key.raise_volume,
|
||||
0xb0 => Key.media_track_next,
|
||||
0xb1 => Key.media_track_previous,
|
||||
0xb2 => Key.media_stop,
|
||||
0xb3 => Key.media_play_pause,
|
||||
0xba => ';',
|
||||
0xbb => '+',
|
||||
0xbc => ',',
|
||||
0xbd => '-',
|
||||
0xbe => '.',
|
||||
0xbf => '/',
|
||||
0xc0 => '`',
|
||||
0xdb => '[',
|
||||
0xdc => '\\',
|
||||
0xdd => ']',
|
||||
0xde => '\'',
|
||||
else => return null,
|
||||
};
|
||||
|
||||
var codepoint: u21 = base_layout;
|
||||
var text: ?[]const u8 = null;
|
||||
switch (event.uChar.UnicodeChar) {
|
||||
0x00...0x1F => {},
|
||||
else => |cp| {
|
||||
codepoint = cp;
|
||||
const n = try std.unicode.utf8Encode(cp, &self.buf);
|
||||
text = self.buf[0..n];
|
||||
},
|
||||
}
|
||||
|
||||
const key: Key = .{
|
||||
.codepoint = codepoint,
|
||||
.base_layout_codepoint = base_layout,
|
||||
.mods = translateMods(event.dwControlKeyState),
|
||||
.text = text,
|
||||
};
|
||||
|
||||
switch (event.bKeyDown) {
|
||||
0 => return .{ .key_release = key },
|
||||
else => return .{ .key_press = key },
|
||||
}
|
||||
},
|
||||
0x0002 => { // Mouse event
|
||||
// see https://learn.microsoft.com/en-us/windows/console/mouse-event-record-str
|
||||
|
||||
const event = record.Event.MouseEvent;
|
||||
|
||||
// High word of dwButtonState represents mouse wheel. Positive is wheel_up, negative
|
||||
// is wheel_down
|
||||
// Low word represents button state
|
||||
const mouse_wheel_direction: i16 = blk: {
|
||||
const wheelu32: u32 = event.dwButtonState >> 16;
|
||||
const wheelu16: u16 = @truncate(wheelu32);
|
||||
break :blk @bitCast(wheelu16);
|
||||
};
|
||||
|
||||
const buttons: u16 = @truncate(event.dwButtonState);
|
||||
// save the current state when we are done
|
||||
defer self.last_mouse_button_press = buttons;
|
||||
const button_xor = self.last_mouse_button_press ^ buttons;
|
||||
|
||||
var event_type: Mouse.Type = .press;
|
||||
const btn: Mouse.Button = switch (button_xor) {
|
||||
0x0000 => blk: {
|
||||
// Check wheel event
|
||||
if (event.dwEventFlags & 0x0004 > 0) {
|
||||
if (mouse_wheel_direction > 0)
|
||||
break :blk .wheel_up
|
||||
else
|
||||
break :blk .wheel_down;
|
||||
}
|
||||
|
||||
// If we have no change but one of the buttons is still pressed we have a
|
||||
// drag event. Find out which button is held down
|
||||
if (buttons > 0 and event.dwEventFlags & 0x0001 > 0) {
|
||||
event_type = .drag;
|
||||
if (buttons & 0x0001 > 0) break :blk .left;
|
||||
if (buttons & 0x0002 > 0) break :blk .right;
|
||||
if (buttons & 0x0004 > 0) break :blk .middle;
|
||||
if (buttons & 0x0008 > 0) break :blk .button_8;
|
||||
if (buttons & 0x0010 > 0) break :blk .button_9;
|
||||
}
|
||||
|
||||
if (event.dwEventFlags & 0x0001 > 0) event_type = .motion;
|
||||
break :blk .none;
|
||||
},
|
||||
0x0001 => blk: {
|
||||
if (buttons & 0x0001 == 0) event_type = .release;
|
||||
break :blk .left;
|
||||
},
|
||||
0x0002 => blk: {
|
||||
if (buttons & 0x0002 == 0) event_type = .release;
|
||||
break :blk .right;
|
||||
},
|
||||
0x0004 => blk: {
|
||||
if (buttons & 0x0004 == 0) event_type = .release;
|
||||
break :blk .middle;
|
||||
},
|
||||
0x0008 => blk: {
|
||||
if (buttons & 0x0008 == 0) event_type = .release;
|
||||
break :blk .button_8;
|
||||
},
|
||||
0x0010 => blk: {
|
||||
if (buttons & 0x0010 == 0) event_type = .release;
|
||||
break :blk .button_9;
|
||||
},
|
||||
else => {
|
||||
std.log.warn("unknown mouse event: {}", .{event});
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
const shift: u32 = 0x0010;
|
||||
const alt: u32 = 0x0001 | 0x0002;
|
||||
const ctrl: u32 = 0x0004 | 0x0008;
|
||||
const mods: Mouse.Modifiers = .{
|
||||
.shift = event.dwControlKeyState & shift > 0,
|
||||
.alt = event.dwControlKeyState & alt > 0,
|
||||
.ctrl = event.dwControlKeyState & ctrl > 0,
|
||||
};
|
||||
|
||||
const mouse: Mouse = .{
|
||||
.col = @as(u16, @bitCast(event.dwMousePosition.X)), // Windows reports with 0 index
|
||||
.row = @as(u16, @bitCast(event.dwMousePosition.Y)), // Windows reports with 0 index
|
||||
.mods = mods,
|
||||
.type = event_type,
|
||||
.button = btn,
|
||||
};
|
||||
return .{ .mouse = mouse };
|
||||
},
|
||||
0x0004 => { // Screen resize events
|
||||
// NOTE: Even though the event comes with a size, it may not be accurate. We ask for
|
||||
// the size directly when we get this event
|
||||
var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
|
||||
if (windows.kernel32.GetConsoleScreenBufferInfo(self.stdout, &console_info) == 0) {
|
||||
return windows.unexpectedError(windows.kernel32.GetLastError());
|
||||
}
|
||||
const window_rect = console_info.srWindow;
|
||||
const width = window_rect.Right - window_rect.Left + 1;
|
||||
const height = window_rect.Bottom - window_rect.Top + 1;
|
||||
return .{
|
||||
.winsize = .{
|
||||
.cols = @intCast(width),
|
||||
.rows = @intCast(height),
|
||||
.x_pixel = 0,
|
||||
.y_pixel = 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
0x0010 => { // Focus events
|
||||
switch (record.Event.FocusEvent.bSetFocus) {
|
||||
0 => return .focus_out,
|
||||
else => return .focus_in,
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn translateMods(mods: u32) Key.Modifiers {
|
||||
const left_alt: u32 = 0x0002;
|
||||
const right_alt: u32 = 0x0001;
|
||||
|
|
Loading…
Reference in a new issue