Compare commits
20 commits
0f9824d08b
...
9a47d18ac6
Author | SHA1 | Date | |
---|---|---|---|
9a47d18ac6 | |||
36a276a10b | |||
7b7b84ad21 | |||
|
40e51ad054 | ||
|
dff7681c30 | ||
|
09a4de63e5 | ||
|
72d96638a4 | ||
|
b82f4e14b4 | ||
|
ca85cbf3b2 | ||
|
2605613019 | ||
|
1a52178c1f | ||
|
1e5d39d9b1 | ||
|
c4b5372253 | ||
|
99942da4e1 | ||
|
49ed160268 | ||
|
b68864c3ba | ||
|
763d2a14a3 | ||
|
edaeb17f3d | ||
|
9b78bb8a78 | ||
|
9c2d18d5a2 |
26 changed files with 727 additions and 530 deletions
|
@ -32,6 +32,7 @@ Unix-likes.
|
|||
| Synchronized Output (DEC 2026) | ✅ |
|
||||
| Unicode Core (DEC 2027) | ✅ |
|
||||
| Color Mode Updates (DEC 2031) | ✅ |
|
||||
| [In-Band Resize Reports](https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83) | ✅ |
|
||||
| Images (kitty) | ✅ |
|
||||
|
||||
## Usage
|
||||
|
|
|
@ -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" });
|
||||
};
|
||||
|
|
|
@ -25,6 +25,7 @@ pub fn main() !void {
|
|||
defer user_list.deinit();
|
||||
|
||||
var tty = try vaxis.Tty.init();
|
||||
defer tty.deinit();
|
||||
|
||||
var vx = try vaxis.init(alloc, .{});
|
||||
defer vx.deinit(alloc, tty.anyWriter());
|
||||
|
|
|
@ -21,6 +21,8 @@ pub fn main() !void {
|
|||
const alloc = gpa.allocator();
|
||||
|
||||
var tty = try vaxis.Tty.init();
|
||||
defer tty.deinit();
|
||||
|
||||
var vx = try vaxis.init(alloc, .{});
|
||||
defer vx.deinit(alloc, tty.anyWriter());
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ pub fn main() !void {
|
|||
};
|
||||
const shell = env.get("SHELL") orelse "bash";
|
||||
const argv = [_][]const u8{shell};
|
||||
std.debug.print("executing: {s}\n", .{shell});
|
||||
var vt = try vaxis.widgets.Terminal.init(
|
||||
alloc,
|
||||
&argv,
|
||||
|
@ -61,9 +62,10 @@ pub fn main() !void {
|
|||
|
||||
var redraw: bool = false;
|
||||
while (true) {
|
||||
std.time.sleep(8 * std.time.ns_per_ms);
|
||||
std.debug.print("inside while loop before resize\n", .{});
|
||||
// try vt events first
|
||||
while (vt.tryEvent()) |event| {
|
||||
std.debug.print("inside stryEventloop \n", .{});
|
||||
redraw = true;
|
||||
switch (event) {
|
||||
.bell => {},
|
||||
|
@ -74,6 +76,7 @@ pub fn main() !void {
|
|||
}
|
||||
}
|
||||
while (loop.tryEvent()) |event| {
|
||||
std.debug.print("inside loop.tryEvent\n", .{});
|
||||
redraw = true;
|
||||
switch (event) {
|
||||
.key_press => |key| {
|
||||
|
|
|
@ -6,6 +6,9 @@ style: Style = .{},
|
|||
link: Hyperlink = .{},
|
||||
image: ?Image.Placement = null,
|
||||
default: bool = false,
|
||||
/// Set to true if this cell is the last cell printed in a row before wrap. Vaxis will determine if
|
||||
/// it should rely on the terminal's autowrap feature which can help with primary screen resizes
|
||||
wrapped: bool = false,
|
||||
|
||||
/// Segment is a contiguous run of text that has a constant style
|
||||
pub const Segment = struct {
|
||||
|
|
|
@ -6,8 +6,6 @@ const zigimg = @import("zigimg");
|
|||
|
||||
const Window = @import("Window.zig");
|
||||
|
||||
const log = std.log.scoped(.image);
|
||||
|
||||
const Image = @This();
|
||||
|
||||
const transmit_opener = "\x1b_Gf=32,i={d},s={d},v={d},m={d};";
|
||||
|
|
|
@ -5,7 +5,7 @@ const Cell = @import("Cell.zig");
|
|||
const MouseShape = @import("Mouse.zig").Shape;
|
||||
const CursorShape = Cell.CursorShape;
|
||||
|
||||
const log = std.log.scoped(.internal_screen);
|
||||
const log = std.log.scoped(.vaxis);
|
||||
|
||||
const InternalScreen = @This();
|
||||
|
||||
|
|
288
src/Loop.zig
288
src/Loop.zig
|
@ -6,17 +6,18 @@ const grapheme = @import("grapheme");
|
|||
const GraphemeCache = @import("GraphemeCache.zig");
|
||||
const Parser = @import("Parser.zig");
|
||||
const Queue = @import("queue.zig").Queue;
|
||||
const Tty = @import("main.zig").Tty;
|
||||
const vaxis = @import("main.zig");
|
||||
const Tty = vaxis.Tty;
|
||||
const Vaxis = @import("Vaxis.zig");
|
||||
|
||||
const log = std.log.scoped(.vaxis);
|
||||
|
||||
pub fn Loop(comptime T: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
const Event = T;
|
||||
|
||||
const log = std.log.scoped(.loop);
|
||||
|
||||
tty: *Tty,
|
||||
vaxis: *Vaxis,
|
||||
|
||||
|
@ -51,6 +52,8 @@ pub fn Loop(comptime T: type) type {
|
|||
|
||||
/// stops reading from the tty.
|
||||
pub fn stop(self: *Self) void {
|
||||
// If we don't have a thread, we have nothing to stop
|
||||
if (self.thread == null) return;
|
||||
self.should_quit = true;
|
||||
// trigger a read
|
||||
self.vaxis.deviceStatusReport(self.tty.anyWriter()) catch {};
|
||||
|
@ -90,6 +93,8 @@ pub fn Loop(comptime T: type) type {
|
|||
|
||||
pub fn winsizeCallback(ptr: *anyopaque) void {
|
||||
const self: *Self = @ptrCast(@alignCast(ptr));
|
||||
// We will be receiving winsize updates in-band
|
||||
if (self.vaxis.state.in_band_resize) return;
|
||||
|
||||
const winsize = Tty.getWinsize(self.tty.fd) catch return;
|
||||
if (@hasField(Event, "winsize")) {
|
||||
|
@ -108,40 +113,12 @@ pub fn Loop(comptime T: type) type {
|
|||
|
||||
switch (builtin.os.tag) {
|
||||
.windows => {
|
||||
var parser: Parser = .{
|
||||
.grapheme_data = grapheme_data,
|
||||
};
|
||||
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 => {},
|
||||
}
|
||||
const event = try self.tty.nextEvent(&parser, paste_allocator);
|
||||
try handleEventGeneric(self, self.vaxis, &cache, Event, event, null);
|
||||
}
|
||||
},
|
||||
else => {
|
||||
|
@ -178,102 +155,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 +163,145 @@ 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 => |winsize| {
|
||||
vx.state.in_band_resize = true;
|
||||
if (@hasField(Event, "winsize")) {
|
||||
self.postEvent(.{ .winsize = winsize });
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,9 @@ const Key = @import("Key.zig");
|
|||
const Mouse = @import("Mouse.zig");
|
||||
const code_point = @import("code_point");
|
||||
const grapheme = @import("grapheme");
|
||||
const Winsize = @import("main.zig").Winsize;
|
||||
|
||||
const log = std.log.scoped(.parser);
|
||||
const log = std.log.scoped(.vaxis_parser);
|
||||
|
||||
const Parser = @This();
|
||||
|
||||
|
@ -138,8 +139,17 @@ inline fn parseGround(input: []const u8, data: *const grapheme.GraphemeData) !Re
|
|||
}
|
||||
|
||||
inline fn parseSs3(input: []const u8) Result {
|
||||
std.debug.assert(input.len >= 3);
|
||||
if (input.len < 3) {
|
||||
return .{
|
||||
.event = null,
|
||||
.n = 0,
|
||||
};
|
||||
}
|
||||
const key: Key = switch (input[2]) {
|
||||
0x1B => return .{
|
||||
.event = null,
|
||||
.n = 2,
|
||||
},
|
||||
'A' => .{ .codepoint = Key.up },
|
||||
'B' => .{ .codepoint = Key.down },
|
||||
'C' => .{ .codepoint = Key.right },
|
||||
|
@ -166,7 +176,12 @@ inline fn parseSs3(input: []const u8) Result {
|
|||
}
|
||||
|
||||
inline fn parseApc(input: []const u8) Result {
|
||||
std.debug.assert(input.len >= 3);
|
||||
if (input.len < 3) {
|
||||
return .{
|
||||
.event = null,
|
||||
.n = 0,
|
||||
};
|
||||
}
|
||||
const end = std.mem.indexOfScalarPos(u8, input, 2, 0x1b) orelse return .{
|
||||
.event = null,
|
||||
.n = 0,
|
||||
|
@ -187,11 +202,22 @@ inline fn parseApc(input: []const u8) Result {
|
|||
|
||||
/// Skips sequences until we see an ST (String Terminator, ESC \)
|
||||
inline fn skipUntilST(input: []const u8) Result {
|
||||
std.debug.assert(input.len >= 3);
|
||||
if (input.len < 3) {
|
||||
return .{
|
||||
.event = null,
|
||||
.n = 0,
|
||||
};
|
||||
}
|
||||
const end = std.mem.indexOfScalarPos(u8, input, 2, 0x1b) orelse return .{
|
||||
.event = null,
|
||||
.n = 0,
|
||||
};
|
||||
if (input.len < end + 1 + 1) {
|
||||
return .{
|
||||
.event = null,
|
||||
.n = 0,
|
||||
};
|
||||
}
|
||||
const sequence = input[0 .. end + 1 + 1];
|
||||
return .{
|
||||
.event = null,
|
||||
|
@ -201,6 +227,12 @@ inline fn skipUntilST(input: []const u8) Result {
|
|||
|
||||
/// Parses an OSC sequence
|
||||
inline fn parseOsc(input: []const u8, paste_allocator: ?std.mem.Allocator) !Result {
|
||||
if (input.len < 3) {
|
||||
return .{
|
||||
.event = null,
|
||||
.n = 0,
|
||||
};
|
||||
}
|
||||
var bel_terminated: bool = false;
|
||||
// end is the index of the terminating byte(s) (either the last byte of an
|
||||
// ST or BEL)
|
||||
|
@ -288,7 +320,13 @@ inline fn parseOsc(input: []const u8, paste_allocator: ?std.mem.Allocator) !Resu
|
|||
}
|
||||
|
||||
inline fn parseCsi(input: []const u8, text_buf: []u8) Result {
|
||||
// We start iterating at index 2 to get past te '['
|
||||
if (input.len < 3) {
|
||||
return .{
|
||||
.event = null,
|
||||
.n = 0,
|
||||
};
|
||||
}
|
||||
// We start iterating at index 2 to get past the '['
|
||||
const sequence = for (input[2..], 2..) |b, i| {
|
||||
switch (b) {
|
||||
0x40...0xFF => break input[0 .. i + 1],
|
||||
|
@ -470,6 +508,32 @@ inline fn parseCsi(input: []const u8, text_buf: []u8) Result {
|
|||
else => return null_event,
|
||||
}
|
||||
},
|
||||
't' => {
|
||||
// XTWINOPS
|
||||
// Split first into fields delimited by ';'
|
||||
var iter = std.mem.splitScalar(u8, sequence[2 .. sequence.len - 1], ';');
|
||||
const ps = iter.first();
|
||||
if (std.mem.eql(u8, "48", ps)) {
|
||||
// in band window resize
|
||||
// CSI 48 ; height ; width ; height_pix ; width_pix t
|
||||
const height_char = iter.next() orelse return null_event;
|
||||
const width_char = iter.next() orelse return null_event;
|
||||
const height_pix = iter.next() orelse "0";
|
||||
const width_pix = iter.next() orelse "0";
|
||||
|
||||
const winsize: Winsize = .{
|
||||
.rows = std.fmt.parseUnsigned(usize, height_char, 10) catch return null_event,
|
||||
.cols = std.fmt.parseUnsigned(usize, width_char, 10) catch return null_event,
|
||||
.x_pixel = std.fmt.parseUnsigned(usize, width_pix, 10) catch return null_event,
|
||||
.y_pixel = std.fmt.parseUnsigned(usize, height_pix, 10) catch return null_event,
|
||||
};
|
||||
return .{
|
||||
.event = .{ .winsize = winsize },
|
||||
.n = sequence.len,
|
||||
};
|
||||
}
|
||||
return null_event;
|
||||
},
|
||||
'u' => {
|
||||
// Kitty keyboard
|
||||
// CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u
|
||||
|
|
|
@ -8,8 +8,6 @@ const Winsize = @import("main.zig").Winsize;
|
|||
const Unicode = @import("Unicode.zig");
|
||||
const Method = @import("gwidth.zig").Method;
|
||||
|
||||
const log = std.log.scoped(.screen);
|
||||
|
||||
const Screen = @This();
|
||||
|
||||
width: usize = 0,
|
||||
|
|
|
@ -68,7 +68,7 @@ unicode: Unicode,
|
|||
|
||||
// statistics
|
||||
renders: usize = 0,
|
||||
render_dur: i128 = 0,
|
||||
render_dur: u64 = 0,
|
||||
render_timer: std.time.Timer,
|
||||
|
||||
sgr: enum {
|
||||
|
@ -85,6 +85,7 @@ state: struct {
|
|||
mouse: bool = false,
|
||||
pixel_mouse: bool = false,
|
||||
color_scheme_updates: bool = false,
|
||||
in_band_resize: bool = false,
|
||||
cursor: struct {
|
||||
row: usize = 0,
|
||||
col: usize = 0,
|
||||
|
@ -151,6 +152,10 @@ pub fn resetState(self: *Vaxis, tty: AnyWriter) !void {
|
|||
try tty.writeAll(ctlseqs.color_scheme_reset);
|
||||
self.state.color_scheme_updates = false;
|
||||
}
|
||||
if (self.state.in_band_resize) {
|
||||
try tty.writeAll(ctlseqs.in_band_resize_reset);
|
||||
self.state.in_band_resize = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// resize allocates a slice of cells equal to the number of cells
|
||||
|
@ -244,6 +249,7 @@ pub fn queryTerminalSend(_: Vaxis, tty: AnyWriter) !void {
|
|||
try tty.writeAll(ctlseqs.decrqm_sgr_pixels ++
|
||||
ctlseqs.decrqm_unicode ++
|
||||
ctlseqs.decrqm_color_scheme ++
|
||||
ctlseqs.in_band_resize_set ++
|
||||
ctlseqs.xtversion ++
|
||||
ctlseqs.csi_u_query ++
|
||||
ctlseqs.kitty_graphics_query ++
|
||||
|
@ -360,7 +366,9 @@ pub fn render(self: *Vaxis, tty: AnyWriter) !void {
|
|||
if (col >= self.screen.width) {
|
||||
row += 1;
|
||||
col = 0;
|
||||
reposition = true;
|
||||
// Rely on terminal wrapping to reposition into next row instead of forcing it
|
||||
if (!cell.wrapped)
|
||||
reposition = true;
|
||||
}
|
||||
// If cell is the same as our last frame, we don't need to do
|
||||
// anything
|
||||
|
@ -488,7 +496,7 @@ pub fn render(self: *Vaxis, tty: AnyWriter) !void {
|
|||
}
|
||||
// underline color
|
||||
if (!Cell.Color.eql(cursor.ul, cell.style.ul)) {
|
||||
switch (cell.style.bg) {
|
||||
switch (cell.style.ul) {
|
||||
.default => try tty.writeAll(ctlseqs.ul_reset),
|
||||
.index => |idx| {
|
||||
switch (self.sgr) {
|
||||
|
|
|
@ -7,8 +7,6 @@ const Segment = @import("Cell.zig").Segment;
|
|||
const Unicode = @import("Unicode.zig");
|
||||
const gw = @import("gwidth.zig");
|
||||
|
||||
const log = std.log.scoped(.window);
|
||||
|
||||
const Window = @This();
|
||||
|
||||
pub const Size = union(enum) {
|
||||
|
@ -311,6 +309,7 @@ pub fn print(self: Window, segments: []const Segment, opts: PrintOptions) !Print
|
|||
},
|
||||
.style = segment.style,
|
||||
.link = segment.link,
|
||||
.wrapped = col + w >= self.width,
|
||||
});
|
||||
col += w;
|
||||
}
|
||||
|
|
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;
|
||||
};
|
||||
|
|
|
@ -20,6 +20,10 @@ pub const mouse_set = "\x1b[?1002;1003;1004;1006h";
|
|||
pub const mouse_set_pixels = "\x1b[?1002;1003;1004;1016h";
|
||||
pub const mouse_reset = "\x1b[?1002;1003;1004;1006;1016l";
|
||||
|
||||
// in-band window size reports
|
||||
pub const in_band_resize_set = "\x1b[?2048h";
|
||||
pub const in_band_resize_reset = "\x1b[?2048l";
|
||||
|
||||
// sync
|
||||
pub const sync_set = "\x1b[?2026h";
|
||||
pub const sync_reset = "\x1b[?2026l";
|
||||
|
|
|
@ -64,6 +64,10 @@ pub fn panic_handler(msg: []const u8, error_return_trace: ?*std.builtin.StackTra
|
|||
std.builtin.default_panic(msg, error_return_trace, ret_addr);
|
||||
}
|
||||
|
||||
pub const log_scopes = enum {
|
||||
vaxis,
|
||||
};
|
||||
|
||||
/// the vaxis logo. In PixelCode
|
||||
pub const logo =
|
||||
\\▄ ▄ ▄▄▄ ▄ ▄ ▄▄▄ ▄▄▄
|
||||
|
|
|
@ -3,8 +3,6 @@ const assert = std.debug.assert;
|
|||
const atomic = std.atomic;
|
||||
const Condition = std.Thread.Condition;
|
||||
|
||||
const log = std.log.scoped(.queue);
|
||||
|
||||
/// Thread safe. Fixed size. Blocking push and pop.
|
||||
pub fn Queue(
|
||||
comptime T: type,
|
||||
|
|
|
@ -6,8 +6,6 @@ const Window = @import("../Window.zig");
|
|||
const GapBuffer = @import("gap_buffer").GapBuffer;
|
||||
const Unicode = @import("../Unicode.zig");
|
||||
|
||||
const log = std.log.scoped(.text_input);
|
||||
|
||||
const TextInput = @This();
|
||||
|
||||
/// The events that this widget handles
|
||||
|
|
|
@ -17,6 +17,23 @@ pid: ?std.posix.pid_t = null,
|
|||
env_map: *const std.process.EnvMap,
|
||||
|
||||
pty: Pty,
|
||||
const TIOCSCTTY = if (builtin.os.tag == .macos) 536900705 else c.TIOCSCTTY;
|
||||
const TIOCSWINSZ = if (builtin.os.tag == .macos) 2148037735 else c.TIOCSWINSZ;
|
||||
const TIOCGWINSZ = if (builtin.os.tag == .macos) 1074295912 else c.TIOCGWINSZ;
|
||||
// extern "c" fn setsid() std.c.pid_t;
|
||||
const c = struct {
|
||||
usingnamespace switch (builtin.os.tag) {
|
||||
.macos => @cImport({
|
||||
@cInclude("sys/ioctl.h"); // ioctl and constants
|
||||
@cInclude("util.h");
|
||||
@cInclude("unistd.h"); // openpty()
|
||||
}),
|
||||
else => @cImport({
|
||||
@cInclude("sys/ioctl.h"); // ioctl and constants
|
||||
@cInclude("pty.h");
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
pub fn spawn(self: *Command, allocator: std.mem.Allocator) !void {
|
||||
var arena_allocator = std.heap.ArenaAllocator.init(allocator);
|
||||
|
@ -31,12 +48,26 @@ pub fn spawn(self: *Command, allocator: std.mem.Allocator) !void {
|
|||
|
||||
const pid = try std.posix.fork();
|
||||
if (pid == 0) {
|
||||
// we are the child
|
||||
_ = std.os.linux.setsid();
|
||||
|
||||
// set the controlling terminal
|
||||
std.posix.close(self.pty.pty);
|
||||
std.debug.print("inside child\n", .{});
|
||||
if (c.setsid() != 0) return error.SetSid;
|
||||
std.debug.print("setting up io for tty\n", .{});
|
||||
var u: c_uint = std.posix.STDIN_FILENO;
|
||||
if (posix.system.ioctl(self.pty.tty, posix.T.IOCSCTTY, @intFromPtr(&u)) != 0) return error.IoctlError;
|
||||
|
||||
if (c.ioctl(self.pty.tty, TIOCSCTTY, @intFromPtr(&u)) != 0) return error.IoctlError;
|
||||
|
||||
// switch (builtin.os.tag) {
|
||||
// .linux => {},
|
||||
// .ios, .macos => {
|
||||
// // Mac doesn't support dup3 so we use dup2. We purposely clear
|
||||
// // CLO_ON_EXEC for this fd.
|
||||
// const flags = try posix.fcntl(self.pty.tty, posix.F.GETFD, 0);
|
||||
// if (flags & posix.FD_CLOEXEC != 0) {
|
||||
// _ = try posix.fcntl(self.pty.tty, posix.F.SETFD, flags & ~@as(u32, posix.FD_CLOEXEC));
|
||||
// }
|
||||
// },
|
||||
// else => @compileError("unsupported platform"),
|
||||
// }
|
||||
|
||||
// set up io
|
||||
try posix.dup2(self.pty.tty, std.posix.STDIN_FILENO);
|
||||
|
@ -51,13 +82,17 @@ pub fn spawn(self: *Command, allocator: std.mem.Allocator) !void {
|
|||
}
|
||||
|
||||
// exec
|
||||
const err = std.posix.execvpeZ(argv_buf.ptr[0].?, argv_buf.ptr, envp);
|
||||
_ = err catch {};
|
||||
_ = std.posix.execvpeZ(argv_buf.ptr[0].?, argv_buf.ptr, envp) catch null;
|
||||
|
||||
//return error.ExecFailed;
|
||||
// const err = std.posix.execvpeZ("/bin/zsh", "/bin/zsh", envp);
|
||||
// _ = err catch {};
|
||||
}
|
||||
|
||||
// we are the parent
|
||||
self.pid = @intCast(pid);
|
||||
|
||||
_ = posix.waitpid(pid, 0);
|
||||
std.debug.print("waitpid in parent\n", .{});
|
||||
if (!Terminal.global_sigchild_installed) {
|
||||
Terminal.global_sigchild_installed = true;
|
||||
var act = posix.Sigaction{
|
||||
|
|
|
@ -7,6 +7,25 @@ const Winsize = @import("../../main.zig").Winsize;
|
|||
|
||||
const posix = std.posix;
|
||||
|
||||
extern "c" fn setsid() std.c.pid_t;
|
||||
|
||||
const c = struct {
|
||||
usingnamespace switch (builtin.os.tag) {
|
||||
.macos => @cImport({
|
||||
@cInclude("sys/ioctl.h"); // ioctl and constants
|
||||
@cInclude("util.h"); // openpty()
|
||||
}),
|
||||
else => @cImport({
|
||||
@cInclude("sys/ioctl.h"); // ioctl and constants
|
||||
@cInclude("pty.h");
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
const TIOCSCTTY = if (builtin.os.tag == .macos) 536900705 else c.TIOCSCTTY;
|
||||
const TIOCSWINSZ = if (builtin.os.tag == .macos) 2148037735 else c.TIOCSWINSZ;
|
||||
const TIOCGWINSZ = if (builtin.os.tag == .macos) 1074295912 else c.TIOCGWINSZ;
|
||||
|
||||
pty: posix.fd_t,
|
||||
tty: posix.fd_t,
|
||||
|
||||
|
@ -14,6 +33,7 @@ tty: posix.fd_t,
|
|||
pub fn init() !Pty {
|
||||
switch (builtin.os.tag) {
|
||||
.linux => return openPtyLinux(),
|
||||
.macos => return openPtyMacos(),
|
||||
else => @compileError("unsupported os"),
|
||||
}
|
||||
}
|
||||
|
@ -32,10 +52,75 @@ pub fn setSize(self: Pty, ws: Winsize) !void {
|
|||
.ws_xpixel = @truncate(ws.x_pixel),
|
||||
.ws_ypixel = @truncate(ws.y_pixel),
|
||||
};
|
||||
if (posix.system.ioctl(self.pty, posix.T.IOCSWINSZ, @intFromPtr(&_ws)) != 0)
|
||||
if (c.ioctl(self.pty, TIOCSWINSZ, @intFromPtr(&_ws)) != 0)
|
||||
return error.SetWinsizeError;
|
||||
}
|
||||
|
||||
fn openPtyMacos2() !Pty {
|
||||
const p = try posix.open("/dev/ptmx", .{ .ACCMODE = .RDWR, .NOCTTY = true }, 0);
|
||||
errdefer posix.close(p);
|
||||
|
||||
// unlockpt
|
||||
// var n: c_uint = 0;
|
||||
if (c.ioctl(p, c.TIOCPTYUNLK) != 0) return error.IoctlError;
|
||||
|
||||
// ptsname
|
||||
if (c.ioctl(p, c.TIOCPTYGRANT) != 0) return error.IoctlError;
|
||||
|
||||
var buf: [128]u8 = undefined;
|
||||
// var buf2: [128]u8 = undefined;
|
||||
if (c.ioctl(p, c.TIOCPTYGNAME, &buf) != 0) return error.IoctlError;
|
||||
const sname = buf[0 .. 13 - 1];
|
||||
// std.debug.print("sizeof buf: {d}", .{buf.len});
|
||||
// const sname = try std.fmt.bufPrint(&buf2, "{s}", .{buf});
|
||||
std.debug.print("slave name: {s}\n", .{sname});
|
||||
// const sname = try std.fmt.bufPrint(&buf, "/dev/pts/{d}", .{n});
|
||||
// std.log.err("pts: {s}", .{sname});
|
||||
|
||||
const t = try posix.open(sname, .{ .ACCMODE = .RDWR, .NOCTTY = true }, 0);
|
||||
std.debug.print("posix opened : {s}\n", .{sname});
|
||||
|
||||
var attrs: c.termios = undefined;
|
||||
if (c.tcgetattr(p, &attrs) != 0)
|
||||
return error.OpenptyFailed;
|
||||
attrs.c_iflag |= c.IUTF8;
|
||||
if (c.tcsetattr(p, c.TCSANOW, &attrs) != 0)
|
||||
return error.OpenptyFailed;
|
||||
|
||||
return .{
|
||||
.pty = p,
|
||||
.tty = t,
|
||||
};
|
||||
}
|
||||
|
||||
fn openPtyMacos() !Pty {
|
||||
var master_fd: posix.fd_t = undefined;
|
||||
var slave_fd: posix.fd_t = undefined;
|
||||
if (c.openpty(
|
||||
&master_fd,
|
||||
&slave_fd,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
) < 0)
|
||||
return error.OpenptyFailed;
|
||||
errdefer {
|
||||
_ = posix.system.close(master_fd);
|
||||
_ = posix.system.close(slave_fd);
|
||||
}
|
||||
var attrs: c.termios = undefined;
|
||||
if (c.tcgetattr(master_fd, &attrs) != 0)
|
||||
return error.OpenptyFailed;
|
||||
attrs.c_iflag |= c.IUTF8;
|
||||
if (c.tcsetattr(master_fd, c.TCSANOW, &attrs) != 0)
|
||||
return error.OpenptyFailed;
|
||||
|
||||
return .{
|
||||
.pty = master_fd,
|
||||
.tty = slave_fd,
|
||||
};
|
||||
}
|
||||
|
||||
fn openPtyLinux() !Pty {
|
||||
const p = try posix.open("/dev/ptmx", .{ .ACCMODE = .RDWR, .NOCTTY = true }, 0);
|
||||
errdefer posix.close(p);
|
||||
|
|
|
@ -4,7 +4,7 @@ const vaxis = @import("../../main.zig");
|
|||
|
||||
const ansi = @import("ansi.zig");
|
||||
|
||||
const log = std.log.scoped(.terminal);
|
||||
const log = std.log.scoped(.vaxis_terminal);
|
||||
|
||||
const Screen = @This();
|
||||
|
||||
|
|
|
@ -94,8 +94,12 @@ pub fn init(
|
|||
unicode: *const vaxis.Unicode,
|
||||
opts: Options,
|
||||
) !Terminal {
|
||||
if (opts.initial_working_directory) |pwd| {
|
||||
if (!std.fs.path.isAbsolute(pwd)) return error.InvalidWorkingDirectory;
|
||||
}
|
||||
const pty = try Pty.init();
|
||||
try pty.setSize(opts.winsize);
|
||||
std.debug.print("set size done\n", .{});
|
||||
const cmd: Command = .{
|
||||
.argv = argv,
|
||||
.env_map = env,
|
||||
|
@ -155,11 +159,22 @@ pub fn deinit(self: *Terminal) void {
|
|||
}
|
||||
|
||||
pub fn spawn(self: *Terminal) !void {
|
||||
std.debug.print("inside spawn", .{});
|
||||
if (self.thread != null) return;
|
||||
self.back_screen = &self.back_screen_pri;
|
||||
|
||||
try self.cmd.spawn(self.allocator);
|
||||
|
||||
self.working_directory.clearRetainingCapacity();
|
||||
if (self.cmd.working_directory) |pwd| {
|
||||
try self.working_directory.appendSlice(pwd);
|
||||
} else {
|
||||
const pwd = std.fs.cwd();
|
||||
var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
||||
const out_path = try std.os.getFdPath(pwd.fd, &buffer);
|
||||
try self.working_directory.appendSlice(out_path);
|
||||
}
|
||||
|
||||
{
|
||||
// add to our global list
|
||||
global_vt_mutex.lock();
|
||||
|
@ -259,6 +274,7 @@ fn anyReader(self: *const Terminal) std.io.AnyReader {
|
|||
|
||||
/// process the output from the command on the pty
|
||||
fn run(self: *Terminal) !void {
|
||||
std.debug.print("inside run\n", .{});
|
||||
var parser: Parser = .{
|
||||
.buf = try std.ArrayList(u8).initCapacity(self.allocator, 128),
|
||||
};
|
||||
|
@ -268,6 +284,7 @@ fn run(self: *Terminal) !void {
|
|||
var reader = std.io.bufferedReader(self.anyReader());
|
||||
|
||||
while (!self.should_quit) {
|
||||
std.debug.print("inside while loop\n", .{});
|
||||
const event = try parser.parseReader(&reader);
|
||||
self.back_mutex.lock();
|
||||
defer self.back_mutex.unlock();
|
||||
|
@ -275,8 +292,10 @@ fn run(self: *Terminal) !void {
|
|||
if (!self.dirty and self.event_queue.tryPush(.redraw))
|
||||
self.dirty = true;
|
||||
|
||||
std.debug.print("before switch event\n", .{});
|
||||
switch (event) {
|
||||
.print => |str| {
|
||||
std.debug.print("inside print event\n", .{});
|
||||
var iter = grapheme.Iterator.init(str, &self.unicode.grapheme_data);
|
||||
while (iter.next()) |g| {
|
||||
const gr = g.bytes(str);
|
||||
|
@ -287,6 +306,7 @@ fn run(self: *Terminal) !void {
|
|||
},
|
||||
.c0 => |b| try self.handleC0(b),
|
||||
.escape => |esc| {
|
||||
std.debug.print("inside escape event\n", .{});
|
||||
const final = esc[esc.len - 1];
|
||||
switch (final) {
|
||||
'B' => {}, // TODO: handle charsets
|
||||
|
@ -700,6 +720,7 @@ fn run(self: *Terminal) !void {
|
|||
}
|
||||
|
||||
inline fn handleC0(self: *Terminal, b: ansi.C0) !void {
|
||||
std.debug.print("inside handlec0\n", .{});
|
||||
switch (b) {
|
||||
.NUL, .SOH, .STX => {},
|
||||
.EOT => {}, // we send EOT to quit the read thread
|
||||
|
@ -716,6 +737,7 @@ inline fn handleC0(self: *Terminal, b: ansi.C0) !void {
|
|||
}
|
||||
|
||||
pub fn setMode(self: *Terminal, mode: u16, val: bool) void {
|
||||
std.debug.print("inside setmode\n", .{});
|
||||
switch (mode) {
|
||||
7 => self.mode.autowrap = val,
|
||||
25 => self.mode.cursor = val,
|
||||
|
|
5
src/widgets/terminal/log.lg
Normal file
5
src/widgets/terminal/log.lg
Normal file
|
@ -0,0 +1,5 @@
|
|||
info(loop): pixel mouse capability detected
|
||||
info(loop): unicode capability detected
|
||||
info(loop): color_scheme_updates capability detected
|
||||
info(loop): kitty keyboard capability detected
|
||||
info(loop): kitty graphics capability detected
|
|
@ -6,6 +6,7 @@ const std = @import("std");
|
|||
const Event = @import("../event.zig").Event;
|
||||
const Key = @import("../Key.zig");
|
||||
const Mouse = @import("../Mouse.zig");
|
||||
const Parser = @import("../Parser.zig");
|
||||
const windows = std.os.windows;
|
||||
|
||||
stdin: windows.HANDLE,
|
||||
|
@ -117,288 +118,304 @@ pub fn bufferedWriter(self: *const Tty) std.io.BufferedWriter(4096, std.io.AnyWr
|
|||
return std.io.bufferedWriter(self.anyWriter());
|
||||
}
|
||||
|
||||
pub fn nextEvent(self: *Tty) !Event {
|
||||
pub fn nextEvent(self: *Tty, parser: *Parser, paste_allocator: ?std.mem.Allocator) !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;
|
||||
if (try self.eventFromRecord(&input_record, &state, parser, paste_allocator)) |ev| {
|
||||
return ev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
pub const EventState = struct {
|
||||
ansi_buf: [128]u8 = undefined,
|
||||
ansi_idx: usize = 0,
|
||||
utf16_buf: [2]u16 = undefined,
|
||||
utf16_half: bool = false,
|
||||
};
|
||||
|
||||
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];
|
||||
},
|
||||
}
|
||||
pub fn eventFromRecord(self: *Tty, record: *const INPUT_RECORD, state: *EventState, parser: *Parser, paste_allocator: ?std.mem.Allocator) !?Event {
|
||||
switch (record.EventType) {
|
||||
0x0001 => { // Key event
|
||||
const event = record.Event.KeyEvent;
|
||||
|
||||
if (state.utf16_half) half: {
|
||||
state.utf16_half = false;
|
||||
state.utf16_buf[1] = event.uChar.UnicodeChar;
|
||||
const codepoint: u21 = std.unicode.utf16DecodeSurrogatePair(&state.utf16_buf) catch break :half;
|
||||
const n = std.unicode.utf8Encode(codepoint, &self.buf) catch return null;
|
||||
|
||||
const key: Key = .{
|
||||
.codepoint = codepoint,
|
||||
.base_layout_codepoint = base_layout,
|
||||
.base_layout_codepoint = codepoint,
|
||||
.mods = translateMods(event.dwControlKeyState),
|
||||
.text = text,
|
||||
.text = self.buf[0..n],
|
||||
};
|
||||
|
||||
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;
|
||||
const base_layout: u16 = switch (event.wVirtualKeyCode) {
|
||||
0x00 => blk: { // delivered when we get an escape sequence or a unicode codepoint
|
||||
if (state.ansi_idx == 0 and event.uChar.AsciiChar != 27)
|
||||
break :blk event.uChar.UnicodeChar;
|
||||
state.ansi_buf[state.ansi_idx] = event.uChar.AsciiChar;
|
||||
state.ansi_idx += 1;
|
||||
if (state.ansi_idx <= 2) return null;
|
||||
const result = try parser.parse(state.ansi_buf[0..state.ansi_idx], paste_allocator);
|
||||
return if (result.n == 0) null else evt: {
|
||||
state.ansi_idx = 0;
|
||||
break :evt result.event;
|
||||
};
|
||||
},
|
||||
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,
|
||||
};
|
||||
|
||||
// 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);
|
||||
};
|
||||
if (std.unicode.utf16IsHighSurrogate(base_layout)) {
|
||||
state.utf16_buf[0] = base_layout;
|
||||
state.utf16_half = true;
|
||||
return null;
|
||||
}
|
||||
if (std.unicode.utf16IsLowSurrogate(base_layout)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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 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(codepoint, &self.buf);
|
||||
text = self.buf[0..n];
|
||||
},
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
const key: Key = .{
|
||||
.codepoint = codepoint,
|
||||
.base_layout_codepoint = base_layout,
|
||||
.mods = translateMods(event.dwControlKeyState),
|
||||
.text = text,
|
||||
};
|
||||
|
||||
// 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;
|
||||
}
|
||||
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
|
||||
|
||||
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 event = record.Event.MouseEvent;
|
||||
|
||||
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,
|
||||
};
|
||||
// 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 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 => {},
|
||||
}
|
||||
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 {
|
||||
|
|
|
@ -9,7 +9,7 @@ const Key = @import("Key.zig");
|
|||
const Mouse = @import("Mouse.zig");
|
||||
const Color = @import("Cell.zig").Color;
|
||||
|
||||
const log = std.log.scoped(.tty_watcher);
|
||||
const log = std.log.scoped(.vaxis_xev);
|
||||
|
||||
pub const Event = union(enum) {
|
||||
key_press: Key,
|
||||
|
@ -99,8 +99,6 @@ pub fn TtyWatcher(comptime Userdata: type) type {
|
|||
.callback = Self.signalCallback,
|
||||
};
|
||||
try Tty.notifyWinsize(handler);
|
||||
const winsize = try Tty.getWinsize(self.tty.fd);
|
||||
_ = self.callback(self.ud, loop, self, .{ .winsize = winsize });
|
||||
}
|
||||
|
||||
fn signalCallback(ptr: *anyopaque) void {
|
||||
|
|
Loading…
Reference in a new issue