feat: add libvaxis renderer
This commit is contained in:
parent
b15fa47f30
commit
1cd3cb17ce
32 changed files with 1559 additions and 516 deletions
312
src/renderer/vaxis/renderer.zig
Normal file
312
src/renderer/vaxis/renderer.zig
Normal file
|
@ -0,0 +1,312 @@
|
|||
const std = @import("std");
|
||||
const cbor = @import("cbor");
|
||||
const log = @import("log");
|
||||
const Style = @import("theme").Style;
|
||||
|
||||
const vaxis = @import("vaxis");
|
||||
|
||||
pub const input = @import("input.zig");
|
||||
|
||||
pub const Plane = @import("Plane.zig");
|
||||
pub const Cell = @import("Cell.zig");
|
||||
|
||||
pub const style = @import("style.zig").StyleBits;
|
||||
|
||||
const mod = input.modifier;
|
||||
const key = input.key;
|
||||
const event_type = input.event_type;
|
||||
|
||||
const Self = @This();
|
||||
pub const log_name = "vaxis";
|
||||
|
||||
a: std.mem.Allocator,
|
||||
|
||||
vx: vaxis.Vaxis,
|
||||
|
||||
no_alternate: bool,
|
||||
event_buffer: std.ArrayList(u8),
|
||||
|
||||
bracketed_paste: bool = false,
|
||||
bracketed_paste_buffer: std.ArrayList(u8),
|
||||
|
||||
handler_ctx: *anyopaque,
|
||||
dispatch_input: ?*const fn (ctx: *anyopaque, cbor_msg: []const u8) void = null,
|
||||
dispatch_mouse: ?*const fn (ctx: *anyopaque, y: c_int, x: c_int, cbor_msg: []const u8) void = null,
|
||||
dispatch_mouse_drag: ?*const fn (ctx: *anyopaque, y: c_int, x: c_int, dragging: bool, cbor_msg: []const u8) void = null,
|
||||
dispatch_event: ?*const fn (ctx: *anyopaque, cbor_msg: []const u8) void = null,
|
||||
|
||||
logger: log.Logger,
|
||||
|
||||
const ModState = struct { ctrl: bool = false, shift: bool = false, alt: bool = false };
|
||||
|
||||
const Event = union(enum) {
|
||||
key_press: vaxis.Key,
|
||||
winsize: vaxis.Winsize,
|
||||
focus_in,
|
||||
};
|
||||
|
||||
pub fn init(a: std.mem.Allocator, handler_ctx: *anyopaque, no_alternate: bool) !Self {
|
||||
return .{
|
||||
.a = a,
|
||||
.vx = try vaxis.init(a, .{}),
|
||||
.no_alternate = no_alternate,
|
||||
.event_buffer = std.ArrayList(u8).init(a),
|
||||
.bracketed_paste_buffer = std.ArrayList(u8).init(a),
|
||||
.handler_ctx = handler_ctx,
|
||||
.logger = log.logger(log_name),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.vx.screen.tty.write(vaxis.ctlseqs.show_cursor);
|
||||
self.vx.screen.tty.flush();
|
||||
self.vx.deinit(self.a);
|
||||
self.bracketed_paste_buffer.deinit();
|
||||
self.event_buffer.deinit();
|
||||
}
|
||||
|
||||
pub fn run(self: *Self) !void {
|
||||
if (self.vx.tty == null) self.vx.tty = try vaxis.Tty.init();
|
||||
if (!self.no_alternate) try self.vx.enterAltScreen();
|
||||
try self.vx.queryTerminal();
|
||||
const ws = try vaxis.Tty.getWinsize(self.input_fd());
|
||||
try self.vx.resize(self.a, ws);
|
||||
self.vx.queueRefresh();
|
||||
try self.vx.setMouseMode(true);
|
||||
}
|
||||
|
||||
pub fn render(self: *Self) !void {
|
||||
self.vx.queueRefresh(); // FIXME: why do differential updates not work?
|
||||
return self.vx.render();
|
||||
}
|
||||
|
||||
pub fn refresh(self: *Self) !void {
|
||||
const ws = try vaxis.Tty.getWinsize(self.input_fd());
|
||||
try self.vx.resize(self.a, ws);
|
||||
self.vx.queueRefresh();
|
||||
}
|
||||
|
||||
pub fn stop(self: *Self) void {
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn stdplane(self: *Self) Plane {
|
||||
const name = "root";
|
||||
var plane: Plane = .{
|
||||
.window = self.vx.window(),
|
||||
.name_buf = undefined,
|
||||
.name_len = name.len,
|
||||
};
|
||||
@memcpy(plane.name_buf[0..name.len], name);
|
||||
return plane;
|
||||
}
|
||||
|
||||
pub fn input_fd(self: Self) i32 {
|
||||
return self.vx.tty.?.fd;
|
||||
}
|
||||
|
||||
pub fn leave_alternate_screen(self: *Self) void {
|
||||
self.vx.exitAltScreen() catch {};
|
||||
}
|
||||
|
||||
pub fn process_input(self: *Self) !void {
|
||||
var parser: vaxis.Parser = .{
|
||||
.grapheme_data = &self.vx.screen.unicode.grapheme_data,
|
||||
};
|
||||
var buf: [1024]u8 = undefined;
|
||||
var start: usize = 0;
|
||||
const n = std.posix.read(self.input_fd(), &buf) catch |e| switch (e) {
|
||||
error.WouldBlock => return,
|
||||
else => return e,
|
||||
};
|
||||
while (start < n) {
|
||||
const result = try parser.parse(buf[start..n]);
|
||||
start += result.n;
|
||||
const event = result.event orelse continue;
|
||||
switch (event) {
|
||||
.key_press => |key_| {
|
||||
const cbor_msg = try self.fmtmsg(.{
|
||||
"I",
|
||||
event_type.PRESS,
|
||||
key_.codepoint,
|
||||
key_.shifted_codepoint orelse key_.codepoint,
|
||||
key_.text orelse input.utils.key_id_string(key_.codepoint),
|
||||
@as(u8, @bitCast(key_.mods)),
|
||||
});
|
||||
if (self.dispatch_input) |f| f(self.handler_ctx, cbor_msg);
|
||||
},
|
||||
.key_release => |*key_| {
|
||||
const cbor_msg = try self.fmtmsg(.{
|
||||
"I",
|
||||
event_type.RELEASE,
|
||||
key_.codepoint,
|
||||
key_.shifted_codepoint orelse key_.codepoint,
|
||||
key_.text orelse input.utils.key_id_string(key_.codepoint),
|
||||
@as(u8, @bitCast(key_.mods)),
|
||||
});
|
||||
if (self.dispatch_input) |f| f(self.handler_ctx, cbor_msg);
|
||||
},
|
||||
.mouse => |mouse| {
|
||||
if (self.dispatch_mouse) |f| switch (mouse.type) {
|
||||
.motion => f(self.handler_ctx, @intCast(mouse.row), @intCast(mouse.col), try self.fmtmsg(.{
|
||||
"M",
|
||||
mouse.col,
|
||||
mouse.row,
|
||||
0,
|
||||
0,
|
||||
})),
|
||||
.press => f(self.handler_ctx, @intCast(mouse.row), @intCast(mouse.col), try self.fmtmsg(.{
|
||||
"B",
|
||||
event_type.PRESS,
|
||||
@intFromEnum(mouse.button),
|
||||
input.utils.button_id_string(@intFromEnum(mouse.button)),
|
||||
mouse.col,
|
||||
mouse.row,
|
||||
0,
|
||||
0,
|
||||
})),
|
||||
.release => f(self.handler_ctx, @intCast(mouse.row), @intCast(mouse.col), try self.fmtmsg(.{
|
||||
"B",
|
||||
event_type.RELEASE,
|
||||
@intFromEnum(mouse.button),
|
||||
input.utils.button_id_string(@intFromEnum(mouse.button)),
|
||||
mouse.col,
|
||||
mouse.row,
|
||||
0,
|
||||
0,
|
||||
})),
|
||||
.drag => f(self.handler_ctx, @intCast(mouse.row), @intCast(mouse.col), try self.fmtmsg(.{
|
||||
"D",
|
||||
event_type.PRESS,
|
||||
@intFromEnum(mouse.button),
|
||||
input.utils.button_id_string(@intFromEnum(mouse.button)),
|
||||
mouse.col,
|
||||
mouse.row,
|
||||
0,
|
||||
0,
|
||||
})),
|
||||
};
|
||||
},
|
||||
.focus_in => {
|
||||
// FIXME
|
||||
},
|
||||
.focus_out => {
|
||||
// FIXME
|
||||
},
|
||||
.paste_start => {
|
||||
self.bracketed_paste = true;
|
||||
self.bracketed_paste_buffer.clearRetainingCapacity();
|
||||
},
|
||||
.paste_end => {
|
||||
defer self.bracketed_paste_buffer.clearAndFree();
|
||||
if (!self.bracketed_paste) return;
|
||||
self.bracketed_paste = false;
|
||||
if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{ "system_clipboard", self.bracketed_paste_buffer.items }));
|
||||
},
|
||||
.cap_unicode => {
|
||||
self.vx.caps.unicode = .unicode;
|
||||
self.vx.screen.width_method = .unicode;
|
||||
},
|
||||
.cap_da1 => {
|
||||
std.Thread.Futex.wake(&self.vx.query_futex, 10);
|
||||
},
|
||||
.cap_kitty_keyboard => {
|
||||
self.vx.caps.kitty_keyboard = true;
|
||||
},
|
||||
.cap_kitty_graphics => {
|
||||
if (!self.vx.caps.kitty_graphics) {
|
||||
self.vx.caps.kitty_graphics = true;
|
||||
}
|
||||
},
|
||||
.cap_rgb => {
|
||||
self.vx.caps.rgb = true;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fmtmsg(self: *Self, value: anytype) ![]const u8 {
|
||||
self.event_buffer.clearRetainingCapacity();
|
||||
try cbor.writeValue(self.event_buffer.writer(), value);
|
||||
return self.event_buffer.items;
|
||||
}
|
||||
|
||||
const OSC = "\x1B]"; // Operating System Command
|
||||
const ST = "\x1B\\"; // String Terminator
|
||||
const BEL = "\x07";
|
||||
const OSC0_title = OSC ++ "0;";
|
||||
const OSC52_clipboard = OSC ++ "52;c;";
|
||||
const OSC52_clipboard_paste = OSC ++ "52;p;";
|
||||
const OSC22_cursor = OSC ++ "22;";
|
||||
const OSC22_cursor_reply = OSC ++ "22:";
|
||||
|
||||
const CSI = "\x1B["; // Control Sequence Introducer
|
||||
const CSI_bracketed_paste_enable = CSI ++ "?2004h";
|
||||
const CSI_bracketed_paste_disable = CSI ++ "?2004h";
|
||||
const CIS_bracketed_paste_begin = CSI ++ "200~";
|
||||
const CIS_bracketed_paste_end = CSI ++ "201~";
|
||||
|
||||
pub fn set_terminal_title(text: []const u8) void {
|
||||
var writer = std.io.getStdOut().writer();
|
||||
var buf: [std.posix.PATH_MAX]u8 = undefined;
|
||||
const term_cmd = std.fmt.bufPrint(&buf, OSC0_title ++ "{s}" ++ BEL, .{text}) catch return;
|
||||
_ = writer.write(term_cmd) catch return;
|
||||
}
|
||||
|
||||
pub fn copy_to_system_clipboard(tmp_a: std.mem.Allocator, text: []const u8) void {
|
||||
copy_to_system_clipboard_with_errors(tmp_a, text) catch |e| log.logger(log_name).err("copy_to_system_clipboard", e);
|
||||
}
|
||||
|
||||
fn copy_to_system_clipboard_with_errors(tmp_a: std.mem.Allocator, text: []const u8) !void {
|
||||
var writer = std.io.getStdOut().writer();
|
||||
const encoder = std.base64.standard.Encoder;
|
||||
const size = OSC52_clipboard.len + encoder.calcSize(text.len) + ST.len;
|
||||
const buf = try tmp_a.alloc(u8, size);
|
||||
defer tmp_a.free(buf);
|
||||
@memcpy(buf[0..OSC52_clipboard.len], OSC52_clipboard);
|
||||
const b64 = encoder.encode(buf[OSC52_clipboard.len..], text);
|
||||
@memcpy(buf[OSC52_clipboard.len + b64.len ..], ST);
|
||||
_ = try writer.write(buf);
|
||||
}
|
||||
|
||||
pub fn request_system_clipboard() void {
|
||||
write_stdout(OSC52_clipboard ++ "?" ++ ST);
|
||||
}
|
||||
|
||||
pub fn request_mouse_cursor_text(push_or_pop: bool) void {
|
||||
if (push_or_pop) mouse_cursor_push("text") else mouse_cursor_pop();
|
||||
}
|
||||
|
||||
pub fn request_mouse_cursor_pointer(push_or_pop: bool) void {
|
||||
if (push_or_pop) mouse_cursor_push("pointer") else mouse_cursor_pop();
|
||||
}
|
||||
|
||||
pub fn request_mouse_cursor_default(push_or_pop: bool) void {
|
||||
if (push_or_pop) mouse_cursor_push("default") else mouse_cursor_pop();
|
||||
}
|
||||
|
||||
fn mouse_cursor_push(comptime name: []const u8) void {
|
||||
write_stdout(OSC22_cursor ++ name ++ ST);
|
||||
}
|
||||
|
||||
fn mouse_cursor_pop() void {
|
||||
write_stdout(OSC22_cursor ++ "default" ++ ST);
|
||||
}
|
||||
|
||||
fn write_stdout(bytes: []const u8) void {
|
||||
_ = std.io.getStdOut().writer().write(bytes) catch |e| log.logger(log_name).err("stdout", e);
|
||||
}
|
||||
|
||||
pub fn cursor_enable(self: *Self, y: c_int, x: c_int) !void {
|
||||
self.vx.screen.cursor_vis = true;
|
||||
self.vx.screen.cursor_row = @intCast(y);
|
||||
self.vx.screen.cursor_col = @intCast(x);
|
||||
}
|
||||
|
||||
pub fn cursor_disable(self: *Self) void {
|
||||
self.vx.screen.cursor_vis = false;
|
||||
}
|
||||
|
||||
pub fn ucs32_to_utf8(ucs32: []const u32, utf8: []u8) !usize {
|
||||
return @intCast(try std.unicode.utf8Encode(@intCast(ucs32[0]), utf8));
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue