tty(windows): refactor and complete windows tty
Refactor the tty implementations. Complete the windows implementation
This commit is contained in:
parent
54def846bf
commit
e8670bd585
9 changed files with 785 additions and 345 deletions
291
src/Loop.zig
291
src/Loop.zig
|
@ -6,8 +6,7 @@ const grapheme = @import("grapheme");
|
||||||
const GraphemeCache = @import("GraphemeCache.zig");
|
const GraphemeCache = @import("GraphemeCache.zig");
|
||||||
const Parser = @import("Parser.zig");
|
const Parser = @import("Parser.zig");
|
||||||
const Queue = @import("queue.zig").Queue;
|
const Queue = @import("queue.zig").Queue;
|
||||||
const tty = @import("tty.zig");
|
const Tty = @import("main.zig").Tty;
|
||||||
const Tty = tty.Tty;
|
|
||||||
const Vaxis = @import("Vaxis.zig");
|
const Vaxis = @import("Vaxis.zig");
|
||||||
|
|
||||||
pub fn Loop(comptime T: type) type {
|
pub fn Loop(comptime T: type) type {
|
||||||
|
@ -29,7 +28,7 @@ pub fn Loop(comptime T: type) type {
|
||||||
/// a stable pointer to register signal callbacks with posix TTYs
|
/// a stable pointer to register signal callbacks with posix TTYs
|
||||||
pub fn init(self: *Self) !void {
|
pub fn init(self: *Self) !void {
|
||||||
switch (builtin.os.tag) {
|
switch (builtin.os.tag) {
|
||||||
.windows => @compileError("windows not supported"),
|
.windows => {},
|
||||||
else => {
|
else => {
|
||||||
const handler: Tty.SignalHandler = .{
|
const handler: Tty.SignalHandler = .{
|
||||||
.context = self,
|
.context = self,
|
||||||
|
@ -104,138 +103,180 @@ pub fn Loop(comptime T: type) type {
|
||||||
grapheme_data: *const grapheme.GraphemeData,
|
grapheme_data: *const grapheme.GraphemeData,
|
||||||
paste_allocator: ?std.mem.Allocator,
|
paste_allocator: ?std.mem.Allocator,
|
||||||
) !void {
|
) !void {
|
||||||
// get our initial winsize
|
|
||||||
const winsize = try Tty.getWinsize(self.tty.fd);
|
|
||||||
if (@hasField(Event, "winsize")) {
|
|
||||||
self.postEvent(.{ .winsize = winsize });
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize a grapheme cache
|
// initialize a grapheme cache
|
||||||
var cache: GraphemeCache = .{};
|
var cache: GraphemeCache = .{};
|
||||||
|
|
||||||
var parser: Parser = .{
|
switch (builtin.os.tag) {
|
||||||
.grapheme_data = grapheme_data,
|
.windows => {
|
||||||
};
|
while (!self.should_quit) {
|
||||||
|
const event = try self.tty.nextEvent();
|
||||||
// initialize the read buffer
|
switch (event) {
|
||||||
var buf: [1024]u8 = undefined;
|
.winsize => |ws| {
|
||||||
var read_start: usize = 0;
|
if (@hasField(Event, "winsize")) {
|
||||||
// read loop
|
self.postEvent(.{ .winsize = ws });
|
||||||
while (!self.should_quit) {
|
}
|
||||||
const n = try self.tty.read(buf[read_start..]);
|
},
|
||||||
var seq_start: usize = 0;
|
.key_press => |key| {
|
||||||
while (seq_start < n) {
|
if (@hasField(Event, "key_press")) {
|
||||||
const result = try parser.parse(buf[seq_start..n], paste_allocator);
|
// HACK: yuck. there has to be a better way
|
||||||
if (result.n == 0) {
|
var mut_key = key;
|
||||||
// copy the read to the beginning. We don't use memcpy because
|
if (key.text) |text| {
|
||||||
// this could be overlapping, and it's also rare
|
mut_key.text = cache.put(text);
|
||||||
const initial_start = seq_start;
|
}
|
||||||
while (seq_start < n) : (seq_start += 1) {
|
self.postEvent(.{ .key_press = mut_key });
|
||||||
buf[seq_start - initial_start] = buf[seq_start];
|
}
|
||||||
|
},
|
||||||
|
.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 => {},
|
||||||
}
|
}
|
||||||
read_start = seq_start - initial_start + 1;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
read_start = 0;
|
},
|
||||||
seq_start += result.n;
|
else => {
|
||||||
|
// get our initial winsize
|
||||||
|
const winsize = try Tty.getWinsize(self.tty.fd);
|
||||||
|
if (@hasField(Event, "winsize")) {
|
||||||
|
self.postEvent(.{ .winsize = winsize });
|
||||||
|
}
|
||||||
|
|
||||||
const event = result.event orelse continue;
|
var parser: Parser = .{
|
||||||
switch (event) {
|
.grapheme_data = grapheme_data,
|
||||||
.key_press => |key| {
|
};
|
||||||
if (@hasField(Event, "key_press")) {
|
|
||||||
// HACK: yuck. there has to be a better way
|
// initialize the read buffer
|
||||||
var mut_key = key;
|
var buf: [1024]u8 = undefined;
|
||||||
if (key.text) |text| {
|
var read_start: usize = 0;
|
||||||
mut_key.text = cache.put(text);
|
// read loop
|
||||||
|
while (!self.should_quit) {
|
||||||
|
const n = try self.tty.read(buf[read_start..]);
|
||||||
|
var seq_start: usize = 0;
|
||||||
|
while (seq_start < n) {
|
||||||
|
const result = try parser.parse(buf[seq_start..n], paste_allocator);
|
||||||
|
if (result.n == 0) {
|
||||||
|
// copy the read to the beginning. We don't use memcpy because
|
||||||
|
// this could be overlapping, and it's also rare
|
||||||
|
const initial_start = seq_start;
|
||||||
|
while (seq_start < n) : (seq_start += 1) {
|
||||||
|
buf[seq_start - initial_start] = buf[seq_start];
|
||||||
}
|
}
|
||||||
self.postEvent(.{ .key_press = mut_key });
|
read_start = seq_start - initial_start + 1;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
},
|
read_start = 0;
|
||||||
.key_release => |*key| {
|
seq_start += result.n;
|
||||||
if (@hasField(Event, "key_release")) {
|
|
||||||
// HACK: yuck. there has to be a better way
|
const event = result.event orelse continue;
|
||||||
var mut_key = key;
|
switch (event) {
|
||||||
if (key.text) |text| {
|
.key_press => |key| {
|
||||||
mut_key.text = cache.put(text);
|
if (@hasField(Event, "key_press")) {
|
||||||
}
|
// HACK: yuck. there has to be a better way
|
||||||
self.postEvent(.{ .key_release = mut_key });
|
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
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
.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);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@ const assert = std.debug.assert;
|
||||||
const Cell = @import("Cell.zig");
|
const Cell = @import("Cell.zig");
|
||||||
const Shape = @import("Mouse.zig").Shape;
|
const Shape = @import("Mouse.zig").Shape;
|
||||||
const Image = @import("Image.zig");
|
const Image = @import("Image.zig");
|
||||||
const Winsize = @import("tty.zig").Winsize;
|
const Winsize = @import("main.zig").Winsize;
|
||||||
const Unicode = @import("Unicode.zig");
|
const Unicode = @import("Unicode.zig");
|
||||||
const Method = @import("gwidth.zig").Method;
|
const Method = @import("gwidth.zig").Method;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
const atomic = std.atomic;
|
const atomic = std.atomic;
|
||||||
const base64Encoder = std.base64.standard.Encoder;
|
const base64Encoder = std.base64.standard.Encoder;
|
||||||
const zigimg = @import("zigimg");
|
const zigimg = @import("zigimg");
|
||||||
|
@ -17,7 +18,7 @@ const Hyperlink = Cell.Hyperlink;
|
||||||
const KittyFlags = Key.KittyFlags;
|
const KittyFlags = Key.KittyFlags;
|
||||||
const Shape = Mouse.Shape;
|
const Shape = Mouse.Shape;
|
||||||
const Style = Cell.Style;
|
const Style = Cell.Style;
|
||||||
const Winsize = @import("tty.zig").Winsize;
|
const Winsize = @import("main.zig").Winsize;
|
||||||
|
|
||||||
const ctlseqs = @import("ctlseqs.zig");
|
const ctlseqs = @import("ctlseqs.zig");
|
||||||
const gwidth = @import("gwidth.zig");
|
const gwidth = @import("gwidth.zig");
|
||||||
|
@ -253,29 +254,37 @@ pub fn queryTerminalSend(_: Vaxis, tty: AnyWriter) !void {
|
||||||
/// is only for use with a custom main loop. Call Vaxis.queryTerminal() if
|
/// is only for use with a custom main loop. Call Vaxis.queryTerminal() if
|
||||||
/// you are using Loop.run()
|
/// you are using Loop.run()
|
||||||
pub fn enableDetectedFeatures(self: *Vaxis, tty: AnyWriter) !void {
|
pub fn enableDetectedFeatures(self: *Vaxis, tty: AnyWriter) !void {
|
||||||
// Apply any environment variables
|
switch (builtin.os.tag) {
|
||||||
if (std.posix.getenv("ASCIINEMA_REC")) |_|
|
.windows => {
|
||||||
self.sgr = .legacy;
|
// No feature detection on windows. We just hard enable some knowns for ConPTY
|
||||||
if (std.posix.getenv("TERMUX_VERSION")) |_|
|
self.sgr = .legacy;
|
||||||
self.sgr = .legacy;
|
},
|
||||||
if (std.posix.getenv("VHS_RECORD")) |_| {
|
else => {
|
||||||
self.caps.unicode = .wcwidth;
|
// Apply any environment variables
|
||||||
self.caps.kitty_keyboard = false;
|
if (std.posix.getenv("ASCIINEMA_REC")) |_|
|
||||||
self.sgr = .legacy;
|
self.sgr = .legacy;
|
||||||
}
|
if (std.posix.getenv("TERMUX_VERSION")) |_|
|
||||||
if (std.posix.getenv("VAXIS_FORCE_LEGACY_SGR")) |_|
|
self.sgr = .legacy;
|
||||||
self.sgr = .legacy;
|
if (std.posix.getenv("VHS_RECORD")) |_| {
|
||||||
if (std.posix.getenv("VAXIS_FORCE_WCWIDTH")) |_|
|
self.caps.unicode = .wcwidth;
|
||||||
self.caps.unicode = .wcwidth;
|
self.caps.kitty_keyboard = false;
|
||||||
if (std.posix.getenv("VAXIS_FORCE_UNICODE")) |_|
|
self.sgr = .legacy;
|
||||||
self.caps.unicode = .unicode;
|
}
|
||||||
|
if (std.posix.getenv("VAXIS_FORCE_LEGACY_SGR")) |_|
|
||||||
|
self.sgr = .legacy;
|
||||||
|
if (std.posix.getenv("VAXIS_FORCE_WCWIDTH")) |_|
|
||||||
|
self.caps.unicode = .wcwidth;
|
||||||
|
if (std.posix.getenv("VAXIS_FORCE_UNICODE")) |_|
|
||||||
|
self.caps.unicode = .unicode;
|
||||||
|
|
||||||
// enable detected features
|
// enable detected features
|
||||||
if (self.caps.kitty_keyboard) {
|
if (self.caps.kitty_keyboard) {
|
||||||
try self.enableKittyKeyboard(tty, self.opts.kitty_keyboard_flags);
|
try self.enableKittyKeyboard(tty, self.opts.kitty_keyboard_flags);
|
||||||
}
|
}
|
||||||
if (self.caps.unicode == .unicode) {
|
if (self.caps.unicode == .unicode) {
|
||||||
try tty.writeAll(ctlseqs.unicode_set);
|
try tty.writeAll(ctlseqs.unicode_set);
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
pub const Key = @import("Key.zig");
|
pub const Key = @import("Key.zig");
|
||||||
pub const Mouse = @import("Mouse.zig");
|
pub const Mouse = @import("Mouse.zig");
|
||||||
pub const Color = @import("Cell.zig").Color;
|
pub const Color = @import("Cell.zig").Color;
|
||||||
|
pub const Winsize = @import("main.zig").Winsize;
|
||||||
|
|
||||||
/// The events that Vaxis emits internally
|
/// The events that Vaxis emits internally
|
||||||
pub const Event = union(enum) {
|
pub const Event = union(enum) {
|
||||||
|
@ -14,6 +15,7 @@ pub const Event = union(enum) {
|
||||||
paste: []const u8, // osc 52 paste, caller must free
|
paste: []const u8, // osc 52 paste, caller must free
|
||||||
color_report: Color.Report, // osc 4, 10, 11, 12 response
|
color_report: Color.Report, // osc 4, 10, 11, 12 response
|
||||||
color_scheme: Color.Scheme,
|
color_scheme: Color.Scheme,
|
||||||
|
winsize: Winsize,
|
||||||
|
|
||||||
// these are delivered as discovered terminal capabilities
|
// these are delivered as discovered terminal capabilities
|
||||||
cap_kitty_keyboard,
|
cap_kitty_keyboard,
|
||||||
|
|
18
src/main.zig
18
src/main.zig
|
@ -19,14 +19,24 @@ pub const Screen = @import("Screen.zig");
|
||||||
pub const AllocatingScreen = @import("InternalScreen.zig");
|
pub const AllocatingScreen = @import("InternalScreen.zig");
|
||||||
pub const Parser = @import("Parser.zig");
|
pub const Parser = @import("Parser.zig");
|
||||||
pub const Window = @import("Window.zig");
|
pub const Window = @import("Window.zig");
|
||||||
pub const tty = @import("tty.zig");
|
|
||||||
pub const Tty = tty.Tty;
|
|
||||||
pub const Winsize = tty.Winsize;
|
|
||||||
|
|
||||||
pub const widgets = @import("widgets.zig");
|
pub const widgets = @import("widgets.zig");
|
||||||
pub const gwidth = @import("gwidth.zig");
|
pub const gwidth = @import("gwidth.zig");
|
||||||
pub const ctlseqs = @import("ctlseqs.zig");
|
pub const ctlseqs = @import("ctlseqs.zig");
|
||||||
|
|
||||||
|
/// The target TTY implementation
|
||||||
|
pub const Tty = switch (builtin.os.tag) {
|
||||||
|
.windows => @import("windows/Tty.zig"),
|
||||||
|
else => @import("posix/Tty.zig"),
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The size of the terminal screen
|
||||||
|
pub const Winsize = struct {
|
||||||
|
rows: usize,
|
||||||
|
cols: usize,
|
||||||
|
x_pixel: usize,
|
||||||
|
y_pixel: usize,
|
||||||
|
};
|
||||||
|
|
||||||
/// Initialize a Vaxis application.
|
/// Initialize a Vaxis application.
|
||||||
pub fn init(alloc: std.mem.Allocator, opts: Vaxis.Options) !Vaxis {
|
pub fn init(alloc: std.mem.Allocator, opts: Vaxis.Options) !Vaxis {
|
||||||
return Vaxis.init(alloc, opts);
|
return Vaxis.init(alloc, opts);
|
||||||
|
|
177
src/posix/Tty.zig
Normal file
177
src/posix/Tty.zig
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
//! TTY implementation conforming to posix standards
|
||||||
|
const Posix = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const posix = std.posix;
|
||||||
|
const Winsize = @import("../main.zig").Winsize;
|
||||||
|
|
||||||
|
/// the original state of the terminal, prior to calling makeRaw
|
||||||
|
termios: posix.termios,
|
||||||
|
|
||||||
|
/// The file descriptor of the tty
|
||||||
|
fd: posix.fd_t,
|
||||||
|
|
||||||
|
pub const SignalHandler = struct {
|
||||||
|
context: *anyopaque,
|
||||||
|
callback: *const fn (context: *anyopaque) void,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// global signal handlers
|
||||||
|
var handlers: [8]SignalHandler = undefined;
|
||||||
|
var handler_mutex: std.Thread.Mutex = .{};
|
||||||
|
var handler_idx: usize = 0;
|
||||||
|
|
||||||
|
/// global tty instance, used in case of a panic. Not guaranteed to work if
|
||||||
|
/// for some reason there are multiple TTYs open under a single vaxis
|
||||||
|
/// compilation unit - but this is better than nothing
|
||||||
|
pub var global_tty: ?Posix = null;
|
||||||
|
|
||||||
|
/// initializes a Tty instance by opening /dev/tty and "making it raw". A
|
||||||
|
/// signal handler is installed for SIGWINCH. No callbacks are installed, be
|
||||||
|
/// sure to register a callback when initializing the event loop
|
||||||
|
pub fn init() !Posix {
|
||||||
|
// Open our tty
|
||||||
|
const fd = try posix.open("/dev/tty", .{ .ACCMODE = .RDWR }, 0);
|
||||||
|
|
||||||
|
// Set the termios of the tty
|
||||||
|
const termios = try makeRaw(fd);
|
||||||
|
|
||||||
|
var act = posix.Sigaction{
|
||||||
|
.handler = .{ .handler = Posix.handleWinch },
|
||||||
|
.mask = switch (builtin.os.tag) {
|
||||||
|
.macos => 0,
|
||||||
|
.linux => posix.empty_sigset,
|
||||||
|
else => @compileError("os not supported"),
|
||||||
|
},
|
||||||
|
.flags = 0,
|
||||||
|
};
|
||||||
|
try posix.sigaction(posix.SIG.WINCH, &act, null);
|
||||||
|
|
||||||
|
const self: Posix = .{
|
||||||
|
.fd = fd,
|
||||||
|
.termios = termios,
|
||||||
|
};
|
||||||
|
|
||||||
|
global_tty = self;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// release resources associated with the Tty return it to its original state
|
||||||
|
pub fn deinit(self: Posix) void {
|
||||||
|
posix.tcsetattr(self.fd, .FLUSH, self.termios) catch |err| {
|
||||||
|
std.log.err("couldn't restore terminal: {}", .{err});
|
||||||
|
};
|
||||||
|
if (builtin.os.tag != .macos) // closing /dev/tty may block indefinitely on macos
|
||||||
|
posix.close(self.fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write bytes to the tty
|
||||||
|
pub fn write(self: *const Posix, bytes: []const u8) !usize {
|
||||||
|
return posix.write(self.fd, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn opaqueWrite(ptr: *const anyopaque, bytes: []const u8) !usize {
|
||||||
|
const self: *const Posix = @ptrCast(@alignCast(ptr));
|
||||||
|
return posix.write(self.fd, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn anyWriter(self: *const Posix) std.io.AnyWriter {
|
||||||
|
return .{
|
||||||
|
.context = self,
|
||||||
|
.writeFn = Posix.opaqueWrite,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(self: *const Posix, buf: []u8) !usize {
|
||||||
|
return posix.read(self.fd, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn opaqueRead(ptr: *const anyopaque, buf: []u8) !usize {
|
||||||
|
const self: *const Posix = @ptrCast(@alignCast(ptr));
|
||||||
|
return posix.read(self.fd, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn anyReader(self: *const Posix) std.io.AnyReader {
|
||||||
|
return .{
|
||||||
|
.context = self,
|
||||||
|
.readFn = Posix.opaqueRead,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Install a signal handler for winsize. A maximum of 8 handlers may be
|
||||||
|
/// installed
|
||||||
|
pub fn notifyWinsize(handler: SignalHandler) !void {
|
||||||
|
handler_mutex.lock();
|
||||||
|
defer handler_mutex.unlock();
|
||||||
|
if (handler_idx == handlers.len) return error.OutOfMemory;
|
||||||
|
handlers[handler_idx] = handler;
|
||||||
|
handler_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleWinch(_: c_int) callconv(.C) void {
|
||||||
|
handler_mutex.lock();
|
||||||
|
defer handler_mutex.unlock();
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < handler_idx) : (i += 1) {
|
||||||
|
const handler = handlers[i];
|
||||||
|
handler.callback(handler.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// makeRaw enters the raw state for the terminal.
|
||||||
|
pub fn makeRaw(fd: posix.fd_t) !posix.termios {
|
||||||
|
const state = try posix.tcgetattr(fd);
|
||||||
|
var raw = state;
|
||||||
|
// see termios(3)
|
||||||
|
raw.iflag.IGNBRK = false;
|
||||||
|
raw.iflag.BRKINT = false;
|
||||||
|
raw.iflag.PARMRK = false;
|
||||||
|
raw.iflag.ISTRIP = false;
|
||||||
|
raw.iflag.INLCR = false;
|
||||||
|
raw.iflag.IGNCR = false;
|
||||||
|
raw.iflag.ICRNL = false;
|
||||||
|
raw.iflag.IXON = false;
|
||||||
|
|
||||||
|
raw.oflag.OPOST = false;
|
||||||
|
|
||||||
|
raw.lflag.ECHO = false;
|
||||||
|
raw.lflag.ECHONL = false;
|
||||||
|
raw.lflag.ICANON = false;
|
||||||
|
raw.lflag.ISIG = false;
|
||||||
|
raw.lflag.IEXTEN = false;
|
||||||
|
|
||||||
|
raw.cflag.CSIZE = .CS8;
|
||||||
|
raw.cflag.PARENB = false;
|
||||||
|
|
||||||
|
raw.cc[@intFromEnum(posix.V.MIN)] = 1;
|
||||||
|
raw.cc[@intFromEnum(posix.V.TIME)] = 0;
|
||||||
|
try posix.tcsetattr(fd, .FLUSH, raw);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the window size from the kernel
|
||||||
|
pub fn getWinsize(fd: posix.fd_t) !Winsize {
|
||||||
|
var winsize = posix.winsize{
|
||||||
|
.ws_row = 0,
|
||||||
|
.ws_col = 0,
|
||||||
|
.ws_xpixel = 0,
|
||||||
|
.ws_ypixel = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const err = posix.system.ioctl(fd, posix.T.IOCGWINSZ, @intFromPtr(&winsize));
|
||||||
|
if (posix.errno(err) == .SUCCESS)
|
||||||
|
return Winsize{
|
||||||
|
.rows = winsize.ws_row,
|
||||||
|
.cols = winsize.ws_col,
|
||||||
|
.x_pixel = winsize.ws_xpixel,
|
||||||
|
.y_pixel = winsize.ws_ypixel,
|
||||||
|
};
|
||||||
|
return error.IoctlError;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bufferedWriter(self: *const Posix) std.io.BufferedWriter(4096, std.io.AnyWriter) {
|
||||||
|
return std.io.bufferedWriter(self.anyWriter());
|
||||||
|
}
|
190
src/tty.zig
190
src/tty.zig
|
@ -1,190 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
const builtin = @import("builtin");
|
|
||||||
const posix = std.posix;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.tty);
|
|
||||||
|
|
||||||
pub const Tty = switch (builtin.os.tag) {
|
|
||||||
.windows => @compileError("windows not supported, try wsl"),
|
|
||||||
else => PosixTty,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The size of the terminal screen
|
|
||||||
pub const Winsize = struct {
|
|
||||||
rows: usize,
|
|
||||||
cols: usize,
|
|
||||||
x_pixel: usize,
|
|
||||||
y_pixel: usize,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// TTY implementation conforming to posix standards
|
|
||||||
pub const PosixTty = struct {
|
|
||||||
/// the original state of the terminal, prior to calling makeRaw
|
|
||||||
termios: posix.termios,
|
|
||||||
|
|
||||||
/// The file descriptor of the tty
|
|
||||||
fd: posix.fd_t,
|
|
||||||
|
|
||||||
pub const SignalHandler = struct {
|
|
||||||
context: *anyopaque,
|
|
||||||
callback: *const fn (context: *anyopaque) void,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// global signal handlers
|
|
||||||
var handlers: [8]SignalHandler = undefined;
|
|
||||||
var handler_mutex: std.Thread.Mutex = .{};
|
|
||||||
var handler_idx: usize = 0;
|
|
||||||
|
|
||||||
/// global tty instance, used in case of a panic. Not guaranteed to work if
|
|
||||||
/// for some reason there are multiple TTYs open under a single vaxis
|
|
||||||
/// compilation unit - but this is better than nothing
|
|
||||||
pub var global_tty: ?PosixTty = null;
|
|
||||||
|
|
||||||
/// initializes a Tty instance by opening /dev/tty and "making it raw". A
|
|
||||||
/// signal handler is installed for SIGWINCH. No callbacks are installed, be
|
|
||||||
/// sure to register a callback when initializing the event loop
|
|
||||||
pub fn init() !PosixTty {
|
|
||||||
// Open our tty
|
|
||||||
const fd = try posix.open("/dev/tty", .{ .ACCMODE = .RDWR }, 0);
|
|
||||||
|
|
||||||
// Set the termios of the tty
|
|
||||||
const termios = try makeRaw(fd);
|
|
||||||
|
|
||||||
var act = posix.Sigaction{
|
|
||||||
.handler = .{ .handler = PosixTty.handleWinch },
|
|
||||||
.mask = switch (builtin.os.tag) {
|
|
||||||
.macos => 0,
|
|
||||||
.linux => posix.empty_sigset,
|
|
||||||
else => @compileError("os not supported"),
|
|
||||||
},
|
|
||||||
.flags = 0,
|
|
||||||
};
|
|
||||||
try posix.sigaction(posix.SIG.WINCH, &act, null);
|
|
||||||
|
|
||||||
const self: PosixTty = .{
|
|
||||||
.fd = fd,
|
|
||||||
.termios = termios,
|
|
||||||
};
|
|
||||||
|
|
||||||
global_tty = self;
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// release resources associated with the Tty return it to its original state
|
|
||||||
pub fn deinit(self: PosixTty) void {
|
|
||||||
posix.tcsetattr(self.fd, .FLUSH, self.termios) catch |err| {
|
|
||||||
log.err("couldn't restore terminal: {}", .{err});
|
|
||||||
};
|
|
||||||
if (builtin.os.tag != .macos) // closing /dev/tty may block indefinitely on macos
|
|
||||||
posix.close(self.fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write bytes to the tty
|
|
||||||
pub fn write(self: *const PosixTty, bytes: []const u8) !usize {
|
|
||||||
return posix.write(self.fd, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn opaqueWrite(ptr: *const anyopaque, bytes: []const u8) !usize {
|
|
||||||
const self: *const PosixTty = @ptrCast(@alignCast(ptr));
|
|
||||||
return posix.write(self.fd, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn anyWriter(self: *const PosixTty) std.io.AnyWriter {
|
|
||||||
return .{
|
|
||||||
.context = self,
|
|
||||||
.writeFn = PosixTty.opaqueWrite,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read(self: *const PosixTty, buf: []u8) !usize {
|
|
||||||
return posix.read(self.fd, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn opaqueRead(ptr: *const anyopaque, buf: []u8) !usize {
|
|
||||||
const self: *const PosixTty = @ptrCast(@alignCast(ptr));
|
|
||||||
return posix.read(self.fd, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn anyReader(self: *const PosixTty) std.io.AnyReader {
|
|
||||||
return .{
|
|
||||||
.context = self,
|
|
||||||
.readFn = PosixTty.opaqueRead,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Install a signal handler for winsize. A maximum of 8 handlers may be
|
|
||||||
/// installed
|
|
||||||
pub fn notifyWinsize(handler: SignalHandler) !void {
|
|
||||||
handler_mutex.lock();
|
|
||||||
defer handler_mutex.unlock();
|
|
||||||
if (handler_idx == handlers.len) return error.OutOfMemory;
|
|
||||||
handlers[handler_idx] = handler;
|
|
||||||
handler_idx += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handleWinch(_: c_int) callconv(.C) void {
|
|
||||||
handler_mutex.lock();
|
|
||||||
defer handler_mutex.unlock();
|
|
||||||
var i: usize = 0;
|
|
||||||
while (i < handler_idx) : (i += 1) {
|
|
||||||
const handler = handlers[i];
|
|
||||||
handler.callback(handler.context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// makeRaw enters the raw state for the terminal.
|
|
||||||
pub fn makeRaw(fd: posix.fd_t) !posix.termios {
|
|
||||||
const state = try posix.tcgetattr(fd);
|
|
||||||
var raw = state;
|
|
||||||
// see termios(3)
|
|
||||||
raw.iflag.IGNBRK = false;
|
|
||||||
raw.iflag.BRKINT = false;
|
|
||||||
raw.iflag.PARMRK = false;
|
|
||||||
raw.iflag.ISTRIP = false;
|
|
||||||
raw.iflag.INLCR = false;
|
|
||||||
raw.iflag.IGNCR = false;
|
|
||||||
raw.iflag.ICRNL = false;
|
|
||||||
raw.iflag.IXON = false;
|
|
||||||
|
|
||||||
raw.oflag.OPOST = false;
|
|
||||||
|
|
||||||
raw.lflag.ECHO = false;
|
|
||||||
raw.lflag.ECHONL = false;
|
|
||||||
raw.lflag.ICANON = false;
|
|
||||||
raw.lflag.ISIG = false;
|
|
||||||
raw.lflag.IEXTEN = false;
|
|
||||||
|
|
||||||
raw.cflag.CSIZE = .CS8;
|
|
||||||
raw.cflag.PARENB = false;
|
|
||||||
|
|
||||||
raw.cc[@intFromEnum(posix.V.MIN)] = 1;
|
|
||||||
raw.cc[@intFromEnum(posix.V.TIME)] = 0;
|
|
||||||
try posix.tcsetattr(fd, .FLUSH, raw);
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the window size from the kernel
|
|
||||||
pub fn getWinsize(fd: posix.fd_t) !Winsize {
|
|
||||||
var winsize = posix.winsize{
|
|
||||||
.ws_row = 0,
|
|
||||||
.ws_col = 0,
|
|
||||||
.ws_xpixel = 0,
|
|
||||||
.ws_ypixel = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const err = posix.system.ioctl(fd, posix.T.IOCGWINSZ, @intFromPtr(&winsize));
|
|
||||||
if (posix.errno(err) == .SUCCESS)
|
|
||||||
return Winsize{
|
|
||||||
.rows = winsize.ws_row,
|
|
||||||
.cols = winsize.ws_col,
|
|
||||||
.x_pixel = winsize.ws_xpixel,
|
|
||||||
.y_pixel = winsize.ws_ypixel,
|
|
||||||
};
|
|
||||||
return error.IoctlError;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bufferedWriter(self: *const PosixTty) std.io.BufferedWriter(4096, std.io.AnyWriter) {
|
|
||||||
return std.io.bufferedWriter(self.anyWriter());
|
|
||||||
}
|
|
||||||
};
|
|
389
src/windows/Tty.zig
Normal file
389
src/windows/Tty.zig
Normal file
|
@ -0,0 +1,389 @@
|
||||||
|
//! A Windows TTY implementation, using virtual terminal process output and
|
||||||
|
//! native windows input
|
||||||
|
const Tty = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const Event = @import("../event.zig").Event;
|
||||||
|
const Key = @import("../Key.zig");
|
||||||
|
const windows = std.os.windows;
|
||||||
|
|
||||||
|
stdin: windows.HANDLE,
|
||||||
|
stdout: windows.HANDLE,
|
||||||
|
|
||||||
|
initial_codepage: c_uint,
|
||||||
|
initial_input_mode: u32,
|
||||||
|
initial_output_mode: u32,
|
||||||
|
|
||||||
|
// a buffer to write key text into
|
||||||
|
buf: [4]u8 = undefined,
|
||||||
|
|
||||||
|
pub var global_tty: ?Tty = null;
|
||||||
|
|
||||||
|
const utf8_codepage: c_uint = 65001;
|
||||||
|
|
||||||
|
const InputMode = struct {
|
||||||
|
const enable_window_input: u32 = 0x0008; // resize events
|
||||||
|
const enable_mouse_input: u32 = 0x0010;
|
||||||
|
|
||||||
|
pub fn rawMode() u32 {
|
||||||
|
return enable_window_input | enable_mouse_input;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const OutputMode = struct {
|
||||||
|
const enable_processed_output: u32 = 0x0001; // handle control sequences
|
||||||
|
const enable_virtual_terminal_processing: u32 = 0x0004; // handle ANSI sequences
|
||||||
|
const disable_newline_auto_return: u32 = 0x0008; // disable inserting a new line when we write at the last column
|
||||||
|
const enable_lvb_grid_worldwide: u32 = 0x0010; // enables reverse video and underline
|
||||||
|
|
||||||
|
fn rawMode() u32 {
|
||||||
|
return enable_processed_output |
|
||||||
|
enable_virtual_terminal_processing |
|
||||||
|
disable_newline_auto_return |
|
||||||
|
enable_lvb_grid_worldwide;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init() !Tty {
|
||||||
|
const stdin = try windows.GetStdHandle(windows.STD_INPUT_HANDLE);
|
||||||
|
const stdout = try windows.GetStdHandle(windows.STD_OUTPUT_HANDLE);
|
||||||
|
|
||||||
|
// get initial modes
|
||||||
|
var initial_input_mode: windows.DWORD = undefined;
|
||||||
|
var initial_output_mode: windows.DWORD = undefined;
|
||||||
|
const initial_output_codepage = windows.kernel32.GetConsoleOutputCP();
|
||||||
|
{
|
||||||
|
if (windows.kernel32.GetConsoleMode(stdin, &initial_input_mode) == 0) {
|
||||||
|
return windows.unexpectedError(windows.kernel32.GetLastError());
|
||||||
|
}
|
||||||
|
if (windows.kernel32.GetConsoleMode(stdout, &initial_output_mode) == 0) {
|
||||||
|
return windows.unexpectedError(windows.kernel32.GetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set new modes
|
||||||
|
{
|
||||||
|
if (SetConsoleMode(stdin, InputMode.rawMode()) == 0)
|
||||||
|
return windows.unexpectedError(windows.kernel32.GetLastError());
|
||||||
|
|
||||||
|
if (SetConsoleMode(stdout, OutputMode.rawMode()) == 0)
|
||||||
|
return windows.unexpectedError(windows.kernel32.GetLastError());
|
||||||
|
|
||||||
|
if (windows.kernel32.SetConsoleOutputCP(utf8_codepage) == 0)
|
||||||
|
return windows.unexpectedError(windows.kernel32.GetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
const self: Tty = .{
|
||||||
|
.stdin = stdin,
|
||||||
|
.stdout = stdout,
|
||||||
|
.initial_codepage = initial_output_codepage,
|
||||||
|
.initial_input_mode = initial_input_mode,
|
||||||
|
.initial_output_mode = initial_output_mode,
|
||||||
|
};
|
||||||
|
|
||||||
|
// save a copy of this tty as the global_tty for panic handling
|
||||||
|
global_tty = self;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Tty) void {
|
||||||
|
_ = windows.kernel32.SetConsoleOutputCP(self.initial_codepage);
|
||||||
|
_ = SetConsoleMode(self.stdin, self.initial_input_mode);
|
||||||
|
_ = SetConsoleMode(self.stdout, self.initial_output_mode);
|
||||||
|
windows.CloseHandle(self.stdin);
|
||||||
|
windows.CloseHandle(self.stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn opaqueWrite(ptr: *const anyopaque, bytes: []const u8) !usize {
|
||||||
|
const self: *const Tty = @ptrCast(@alignCast(ptr));
|
||||||
|
return windows.WriteFile(self.stdout, bytes, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn anyWriter(self: *const Tty) std.io.AnyWriter {
|
||||||
|
return .{
|
||||||
|
.context = self,
|
||||||
|
.writeFn = Tty.opaqueWrite,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bufferedWriter(self: *const Tty) std.io.BufferedWriter(4096, std.io.AnyWriter) {
|
||||||
|
return std.io.bufferedWriter(self.anyWriter());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
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 => { // TODO: ConPTY doesn't pass through MOUSE_EVENT input records yet.
|
||||||
|
// see https://learn.microsoft.com/en-us/windows/console/mouse-event-record-str
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
const height = window_rect.Bottom - window_rect.Top;
|
||||||
|
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 => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn translateMods(mods: u32) Key.Modifiers {
|
||||||
|
const left_alt: u32 = 0x0002;
|
||||||
|
const right_alt: u32 = 0x0001;
|
||||||
|
const left_ctrl: u32 = 0x0008;
|
||||||
|
const right_ctrl: u32 = 0x0004;
|
||||||
|
|
||||||
|
const caps: u32 = 0x0080;
|
||||||
|
const num_lock: u32 = 0x0020;
|
||||||
|
const shift: u32 = 0x0010;
|
||||||
|
const alt: u32 = left_alt | right_alt;
|
||||||
|
const ctrl: u32 = left_ctrl | right_ctrl;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.shift = mods & shift > 0,
|
||||||
|
.alt = mods & alt > 0,
|
||||||
|
.ctrl = mods & ctrl > 0,
|
||||||
|
.caps_lock = mods & caps > 0,
|
||||||
|
.num_lock = mods & num_lock > 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// From gitub.com/ziglibs/zig-windows-console. Thanks :)
|
||||||
|
//
|
||||||
|
// Events
|
||||||
|
const union_unnamed_248 = extern union {
|
||||||
|
UnicodeChar: windows.WCHAR,
|
||||||
|
AsciiChar: windows.CHAR,
|
||||||
|
};
|
||||||
|
pub const KEY_EVENT_RECORD = extern struct {
|
||||||
|
bKeyDown: windows.BOOL,
|
||||||
|
wRepeatCount: windows.WORD,
|
||||||
|
wVirtualKeyCode: windows.WORD,
|
||||||
|
wVirtualScanCode: windows.WORD,
|
||||||
|
uChar: union_unnamed_248,
|
||||||
|
dwControlKeyState: windows.DWORD,
|
||||||
|
};
|
||||||
|
pub const PKEY_EVENT_RECORD = *KEY_EVENT_RECORD;
|
||||||
|
|
||||||
|
pub const MOUSE_EVENT_RECORD = extern struct {
|
||||||
|
dwMousePosition: windows.COORD,
|
||||||
|
dwButtonState: windows.DWORD,
|
||||||
|
dwControlKeyState: windows.DWORD,
|
||||||
|
dwEventFlags: windows.DWORD,
|
||||||
|
};
|
||||||
|
pub const PMOUSE_EVENT_RECORD = *MOUSE_EVENT_RECORD;
|
||||||
|
|
||||||
|
pub const WINDOW_BUFFER_SIZE_RECORD = extern struct {
|
||||||
|
dwSize: windows.COORD,
|
||||||
|
};
|
||||||
|
pub const PWINDOW_BUFFER_SIZE_RECORD = *WINDOW_BUFFER_SIZE_RECORD;
|
||||||
|
|
||||||
|
pub const MENU_EVENT_RECORD = extern struct {
|
||||||
|
dwCommandId: windows.UINT,
|
||||||
|
};
|
||||||
|
pub const PMENU_EVENT_RECORD = *MENU_EVENT_RECORD;
|
||||||
|
|
||||||
|
pub const FOCUS_EVENT_RECORD = extern struct {
|
||||||
|
bSetFocus: windows.BOOL,
|
||||||
|
};
|
||||||
|
pub const PFOCUS_EVENT_RECORD = *FOCUS_EVENT_RECORD;
|
||||||
|
|
||||||
|
const union_unnamed_249 = extern union {
|
||||||
|
KeyEvent: KEY_EVENT_RECORD,
|
||||||
|
MouseEvent: MOUSE_EVENT_RECORD,
|
||||||
|
WindowBufferSizeEvent: WINDOW_BUFFER_SIZE_RECORD,
|
||||||
|
MenuEvent: MENU_EVENT_RECORD,
|
||||||
|
FocusEvent: FOCUS_EVENT_RECORD,
|
||||||
|
};
|
||||||
|
pub const INPUT_RECORD = extern struct {
|
||||||
|
EventType: windows.WORD,
|
||||||
|
Event: union_unnamed_249,
|
||||||
|
};
|
||||||
|
pub const PINPUT_RECORD = *INPUT_RECORD;
|
||||||
|
|
||||||
|
pub extern "kernel32" fn ReadConsoleInputW(hConsoleInput: windows.HANDLE, lpBuffer: PINPUT_RECORD, nLength: windows.DWORD, lpNumberOfEventsRead: *windows.DWORD) callconv(windows.WINAPI) windows.BOOL;
|
||||||
|
// TODO: remove this in zig 0.13.0
|
||||||
|
pub extern "kernel32" fn SetConsoleMode(in_hConsoleHandle: windows.HANDLE, in_dwMode: windows.DWORD) callconv(windows.WINAPI) windows.BOOL;
|
|
@ -1,8 +1,8 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const xev = @import("xev");
|
const xev = @import("xev");
|
||||||
|
|
||||||
const Tty = @import("tty.zig").Tty;
|
const Tty = @import("main.zig").Tty;
|
||||||
const Winsize = @import("tty.zig").Winsize;
|
const Winsize = @import("main.zig").Winsize;
|
||||||
const Vaxis = @import("Vaxis.zig");
|
const Vaxis = @import("Vaxis.zig");
|
||||||
const Parser = @import("Parser.zig");
|
const Parser = @import("Parser.zig");
|
||||||
const Key = @import("Key.zig");
|
const Key = @import("Key.zig");
|
||||||
|
@ -161,6 +161,7 @@ pub fn TtyWatcher(comptime Userdata: type) type {
|
||||||
.paste => |paste| .{ .paste = paste },
|
.paste => |paste| .{ .paste = paste },
|
||||||
.color_report => |report| .{ .color_report = report },
|
.color_report => |report| .{ .color_report = report },
|
||||||
.color_scheme => |scheme| .{ .color_scheme = scheme },
|
.color_scheme => |scheme| .{ .color_scheme = scheme },
|
||||||
|
.winsize => |ws| .{ .winsize = ws },
|
||||||
|
|
||||||
// capability events which we handle below
|
// capability events which we handle below
|
||||||
.cap_kitty_keyboard,
|
.cap_kitty_keyboard,
|
||||||
|
@ -192,6 +193,7 @@ pub fn TtyWatcher(comptime Userdata: type) type {
|
||||||
.paste,
|
.paste,
|
||||||
.color_report,
|
.color_report,
|
||||||
.color_scheme,
|
.color_scheme,
|
||||||
|
.winsize,
|
||||||
=> unreachable, // handled above
|
=> unreachable, // handled above
|
||||||
|
|
||||||
.cap_kitty_keyboard => {
|
.cap_kitty_keyboard => {
|
||||||
|
|
Loading…
Reference in a new issue