Removed old vaxis.zig
This commit is contained in:
parent
6e97c8d21d
commit
173ff4e216
1 changed files with 0 additions and 715 deletions
715
src/vaxis.zig
715
src/vaxis.zig
|
@ -1,715 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
const builtin = @import("builtin");
|
|
||||||
const atomic = std.atomic;
|
|
||||||
const base64 = std.base64.standard.Encoder;
|
|
||||||
const posix = std.posix;
|
|
||||||
|
|
||||||
pub const Cell = @import("Cell.zig");
|
|
||||||
pub const Image = @import("Image.zig");
|
|
||||||
pub const InternalScreen = @import("InternalScreen.zig");
|
|
||||||
pub const Key = @import("Key.zig");
|
|
||||||
pub const Mouse = @import("Mouse.zig");
|
|
||||||
pub const Options = @import("Options.zig");
|
|
||||||
pub const Queue = @import("queue.zig").Queue;
|
|
||||||
pub const Screen = @import("Screen.zig");
|
|
||||||
pub const Tty = @import("Tty.zig");
|
|
||||||
pub const Window = @import("Window.zig");
|
|
||||||
pub const Winsize = Tty.Winsize;
|
|
||||||
pub const Style = Cell.Style;
|
|
||||||
|
|
||||||
pub const ctlseqs = @import("ctlseqs.zig");
|
|
||||||
pub const gwidth = @import("gwidth.zig");
|
|
||||||
pub const widgets = @import("widgets.zig");
|
|
||||||
|
|
||||||
const zigimg = @import("zigimg");
|
|
||||||
|
|
||||||
|
|
||||||
/// Initialize a Vaxis application.
|
|
||||||
pub fn init(comptime Event: type, opts: Options) !Vaxis(Event) {
|
|
||||||
return Vaxis(Event).init(opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Vaxis is the entrypoint for a Vaxis application. The provided type T should
|
|
||||||
/// be a tagged union which contains all of the events the application will
|
|
||||||
/// handle. Vaxis will look for the following fields on the union and, if
|
|
||||||
/// found, emit them via the "nextEvent" method
|
|
||||||
///
|
|
||||||
/// The following events are available:
|
|
||||||
/// - `key_press: Key`, for key press events
|
|
||||||
/// - `winsize: Winsize`, for resize events. Must call app.resize when receiving
|
|
||||||
/// this event
|
|
||||||
/// - `focus_in` and `focus_out` for focus events
|
|
||||||
pub fn Vaxis(comptime T: type) type {
|
|
||||||
return struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
const log = std.log.scoped(.vaxis);
|
|
||||||
|
|
||||||
pub const Event = T;
|
|
||||||
|
|
||||||
pub const Capabilities = struct {
|
|
||||||
kitty_keyboard: bool = false,
|
|
||||||
kitty_graphics: bool = false,
|
|
||||||
rgb: bool = false,
|
|
||||||
unicode: bool = false,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// the event queue for Vaxis
|
|
||||||
//
|
|
||||||
// TODO: is 512 ok?
|
|
||||||
queue: Queue(T, 512),
|
|
||||||
|
|
||||||
tty: ?Tty,
|
|
||||||
read_thread: ?std.Thread = null,
|
|
||||||
|
|
||||||
/// the screen we write to
|
|
||||||
screen: Screen,
|
|
||||||
/// The last screen we drew. We keep this so we can efficiently update on
|
|
||||||
/// the next render
|
|
||||||
screen_last: InternalScreen = undefined,
|
|
||||||
|
|
||||||
state: struct {
|
|
||||||
/// if we are in the alt screen
|
|
||||||
alt_screen: bool = false,
|
|
||||||
/// if we have entered kitty keyboard
|
|
||||||
kitty_keyboard: bool = false,
|
|
||||||
bracketed_paste: bool = false,
|
|
||||||
mouse: bool = false,
|
|
||||||
/// if our read thread is running
|
|
||||||
reading: bool = false,
|
|
||||||
} = .{},
|
|
||||||
|
|
||||||
caps: Capabilities = .{},
|
|
||||||
|
|
||||||
/// if we should redraw the entire screen on the next render
|
|
||||||
refresh: bool = false,
|
|
||||||
|
|
||||||
/// blocks the main thread until a DA1 query has been received, or the
|
|
||||||
/// futex times out
|
|
||||||
query_futex: atomic.Value(u32) = atomic.Value(u32).init(0),
|
|
||||||
|
|
||||||
// images
|
|
||||||
next_img_id: u32 = 1,
|
|
||||||
|
|
||||||
// statistics
|
|
||||||
renders: usize = 0,
|
|
||||||
render_dur: i128 = 0,
|
|
||||||
render_timer: std.time.Timer,
|
|
||||||
|
|
||||||
/// Initialize Vaxis with runtime options
|
|
||||||
pub fn init(_: Options) !Self {
|
|
||||||
return .{
|
|
||||||
.queue = .{},
|
|
||||||
.tty = null,
|
|
||||||
.screen = .{},
|
|
||||||
.screen_last = .{},
|
|
||||||
.render_timer = try std.time.Timer.start(),
|
|
||||||
.read_thread = null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resets the terminal to it's original state. If an allocator is
|
|
||||||
/// passed, this will free resources associated with Vaxis. This is left as an
|
|
||||||
/// optional so applications can choose to not free resources when the
|
|
||||||
/// application will be exiting anyways
|
|
||||||
pub fn deinit(self: *Self, alloc: ?std.mem.Allocator) void {
|
|
||||||
if (self.tty) |*tty| {
|
|
||||||
if (self.state.kitty_keyboard) {
|
|
||||||
_ = tty.write(ctlseqs.csi_u_pop) catch {};
|
|
||||||
}
|
|
||||||
if (self.state.mouse) {
|
|
||||||
_ = tty.write(ctlseqs.mouse_reset) catch {};
|
|
||||||
}
|
|
||||||
if (self.state.bracketed_paste) {
|
|
||||||
_ = tty.write(ctlseqs.bp_reset) catch {};
|
|
||||||
}
|
|
||||||
if (self.state.alt_screen) {
|
|
||||||
_ = tty.write(ctlseqs.rmcup) catch {};
|
|
||||||
}
|
|
||||||
// always show the cursor on exit
|
|
||||||
_ = tty.write(ctlseqs.show_cursor) catch {};
|
|
||||||
tty.flush() catch {};
|
|
||||||
tty.deinit();
|
|
||||||
}
|
|
||||||
if (alloc) |a| {
|
|
||||||
self.screen.deinit(a);
|
|
||||||
self.screen_last.deinit(a);
|
|
||||||
}
|
|
||||||
if (self.renders > 0) {
|
|
||||||
const tpr = @divTrunc(self.render_dur, self.renders);
|
|
||||||
log.debug("total renders = {d}", .{self.renders});
|
|
||||||
log.debug("microseconds per render = {d}", .{tpr});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// spawns the input thread to start listening to the tty for input
|
|
||||||
pub fn startReadThread(self: *Self) !void {
|
|
||||||
if (self.state.reading) return;
|
|
||||||
self.tty = try Tty.init();
|
|
||||||
// run our tty read loop in it's own thread
|
|
||||||
self.read_thread = try std.Thread.spawn(.{}, Tty.run, .{ &self.tty.?, T, self });
|
|
||||||
try self.read_thread.?.setName("vaxis_tty");
|
|
||||||
self.state.reading = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// stops reading from the tty
|
|
||||||
pub fn stopReadThread(self: *Self) void {
|
|
||||||
if (!self.state.reading) return;
|
|
||||||
if (self.tty) |*tty| tty.stop();
|
|
||||||
if (self.read_thread) |*read| {
|
|
||||||
read.join();
|
|
||||||
self.read_thread = null;
|
|
||||||
}
|
|
||||||
self.state.reading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// returns the next available event, blocking until one is available
|
|
||||||
pub fn nextEvent(self: *Self) T {
|
|
||||||
return self.queue.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// blocks until an event is available. Useful when your application is
|
|
||||||
/// operating on a poll + drain architecture (see tryEvent)
|
|
||||||
pub fn pollEvent(self: *Self) void {
|
|
||||||
self.queue.poll();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// returns an event if one is available, otherwise null. Non-blocking.
|
|
||||||
pub fn tryEvent(self: *Self) ?Event {
|
|
||||||
return self.queue.tryPop();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// posts an event into the event queue. Will block if there is not
|
|
||||||
/// capacity for the event
|
|
||||||
pub fn postEvent(self: *Self, event: T) void {
|
|
||||||
self.queue.push(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// resize allocates a slice of cells equal to the number of cells
|
|
||||||
/// required to display the screen (ie width x height). Any previous screen is
|
|
||||||
/// freed when resizing
|
|
||||||
pub fn resize(self: *Self, alloc: std.mem.Allocator, winsize: Tty.Winsize) !void {
|
|
||||||
log.debug("resizing screen: width={d} height={d}", .{ winsize.cols, winsize.rows });
|
|
||||||
self.screen.deinit(alloc);
|
|
||||||
self.screen = try Screen.init(alloc, winsize);
|
|
||||||
self.screen.unicode = self.caps.unicode;
|
|
||||||
// try self.screen.int(alloc, winsize.cols, winsize.rows);
|
|
||||||
// we only init our current screen. This has the effect of redrawing
|
|
||||||
// every cell
|
|
||||||
self.screen_last.deinit(alloc);
|
|
||||||
self.screen_last = try InternalScreen.init(alloc, winsize.cols, winsize.rows);
|
|
||||||
// try self.screen_last.resize(alloc, winsize.cols, winsize.rows);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// returns a Window comprising of the entire terminal screen
|
|
||||||
pub fn window(self: *Self) Window {
|
|
||||||
return .{
|
|
||||||
.x_off = 0,
|
|
||||||
.y_off = 0,
|
|
||||||
.width = self.screen.width,
|
|
||||||
.height = self.screen.height,
|
|
||||||
.screen = &self.screen,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// enter the alternate screen. The alternate screen will automatically
|
|
||||||
/// be exited if calling deinit while in the alt screen
|
|
||||||
pub fn enterAltScreen(self: *Self) !void {
|
|
||||||
if (self.state.alt_screen) return;
|
|
||||||
var tty = self.tty orelse return;
|
|
||||||
_ = try tty.write(ctlseqs.smcup);
|
|
||||||
try tty.flush();
|
|
||||||
self.state.alt_screen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// exit the alternate screen
|
|
||||||
pub fn exitAltScreen(self: *Self) !void {
|
|
||||||
if (!self.state.alt_screen) return;
|
|
||||||
var tty = self.tty orelse return;
|
|
||||||
_ = try tty.write(ctlseqs.rmcup);
|
|
||||||
try tty.flush();
|
|
||||||
self.state.alt_screen = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// write queries to the terminal to determine capabilities. Individual
|
|
||||||
/// capabilities will be delivered to the client and possibly intercepted by
|
|
||||||
/// Vaxis to enable features
|
|
||||||
pub fn queryTerminal(self: *Self) !void {
|
|
||||||
var tty = self.tty orelse return;
|
|
||||||
|
|
||||||
const colorterm = posix.getenv("COLORTERM") orelse "";
|
|
||||||
if (std.mem.eql(u8, colorterm, "truecolor") or
|
|
||||||
std.mem.eql(u8, colorterm, "24bit"))
|
|
||||||
{
|
|
||||||
if (@hasField(Event, "cap_rgb")) {
|
|
||||||
self.postEvent(.cap_rgb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: decide if we actually want to query for focus and sync. It
|
|
||||||
// doesn't hurt to blindly use them
|
|
||||||
// _ = try tty.write(ctlseqs.decrqm_focus);
|
|
||||||
// _ = try tty.write(ctlseqs.decrqm_sync);
|
|
||||||
_ = try tty.write(ctlseqs.decrqm_unicode);
|
|
||||||
_ = try tty.write(ctlseqs.decrqm_color_theme);
|
|
||||||
// TODO: XTVERSION has a DCS response. uncomment when we can parse
|
|
||||||
// that
|
|
||||||
// _ = try tty.write(ctlseqs.xtversion);
|
|
||||||
_ = try tty.write(ctlseqs.csi_u_query);
|
|
||||||
_ = try tty.write(ctlseqs.kitty_graphics_query);
|
|
||||||
// TODO: sixel geometry query interferes with F4 keys.
|
|
||||||
// _ = try tty.write(ctlseqs.sixel_geometry_query);
|
|
||||||
|
|
||||||
// TODO: XTGETTCAP queries ("RGB", "Smulx")
|
|
||||||
|
|
||||||
_ = try tty.write(ctlseqs.primary_device_attrs);
|
|
||||||
try tty.flush();
|
|
||||||
|
|
||||||
// 1 second timeout
|
|
||||||
std.Thread.Futex.timedWait(&self.query_futex, 0, 1 * std.time.ns_per_s) catch {};
|
|
||||||
|
|
||||||
// enable detected features
|
|
||||||
if (self.caps.kitty_keyboard) {
|
|
||||||
try self.enableKittyKeyboard(.{});
|
|
||||||
}
|
|
||||||
if (self.caps.unicode) {
|
|
||||||
_ = try tty.write(ctlseqs.unicode_set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// the next render call will refresh the entire screen
|
|
||||||
pub fn queueRefresh(self: *Self) void {
|
|
||||||
self.refresh = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// draws the screen to the terminal
|
|
||||||
pub fn render(self: *Self) !void {
|
|
||||||
var tty = self.tty orelse return;
|
|
||||||
self.renders += 1;
|
|
||||||
self.render_timer.reset();
|
|
||||||
defer {
|
|
||||||
self.render_dur += self.render_timer.read() / std.time.ns_per_us;
|
|
||||||
}
|
|
||||||
|
|
||||||
defer self.refresh = false;
|
|
||||||
defer tty.flush() catch {};
|
|
||||||
|
|
||||||
// Set up sync before we write anything
|
|
||||||
// TODO: optimize sync so we only sync _when we have changes_. This
|
|
||||||
// requires a smarter buffered writer, we'll probably have to write
|
|
||||||
// our own
|
|
||||||
_ = try tty.write(ctlseqs.sync_set);
|
|
||||||
defer _ = tty.write(ctlseqs.sync_reset) catch {};
|
|
||||||
|
|
||||||
// Send the cursor to 0,0
|
|
||||||
// TODO: this needs to move after we optimize writes. We only do
|
|
||||||
// this if we have an update to make. We also need to hide cursor
|
|
||||||
// and then reshow it if needed
|
|
||||||
_ = try tty.write(ctlseqs.hide_cursor);
|
|
||||||
_ = try tty.write(ctlseqs.home);
|
|
||||||
_ = try tty.write(ctlseqs.sgr_reset);
|
|
||||||
|
|
||||||
// initialize some variables
|
|
||||||
var reposition: bool = false;
|
|
||||||
var row: usize = 0;
|
|
||||||
var col: usize = 0;
|
|
||||||
var cursor: Cell.Style = .{};
|
|
||||||
var link: Cell.Hyperlink = .{};
|
|
||||||
|
|
||||||
// Clear all images
|
|
||||||
_ = try tty.write(ctlseqs.kitty_graphics_clear);
|
|
||||||
|
|
||||||
var i: usize = 0;
|
|
||||||
while (i < self.screen.buf.len) {
|
|
||||||
const cell = self.screen.buf[i];
|
|
||||||
defer {
|
|
||||||
// advance by the width of this char mod 1
|
|
||||||
const w = blk: {
|
|
||||||
if (cell.char.width != 0) break :blk cell.char.width;
|
|
||||||
|
|
||||||
const method: gwidth.Method = if (self.caps.unicode) .unicode else .wcwidth;
|
|
||||||
const width = gwidth.gwidth(cell.char.grapheme, method) catch 1;
|
|
||||||
break :blk @max(1, width);
|
|
||||||
};
|
|
||||||
std.debug.assert(w > 0);
|
|
||||||
var j = i + 1;
|
|
||||||
while (j < i + w) : (j += 1) {
|
|
||||||
if (j >= self.screen_last.buf.len) break;
|
|
||||||
self.screen_last.buf[j].skipped = true;
|
|
||||||
}
|
|
||||||
col += w;
|
|
||||||
i += w;
|
|
||||||
}
|
|
||||||
if (col >= self.screen.width) {
|
|
||||||
row += 1;
|
|
||||||
col = 0;
|
|
||||||
reposition = true;
|
|
||||||
}
|
|
||||||
// If cell is the same as our last frame, we don't need to do
|
|
||||||
// anything
|
|
||||||
const last = self.screen_last.buf[i];
|
|
||||||
if (!self.refresh and last.eql(cell) and !last.skipped and cell.image == null) {
|
|
||||||
reposition = true;
|
|
||||||
// Close any osc8 sequence we might be in before
|
|
||||||
// repositioning
|
|
||||||
if (link.uri.len > 0) {
|
|
||||||
_ = try tty.write(ctlseqs.osc8_clear);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
self.screen_last.buf[i].skipped = false;
|
|
||||||
defer {
|
|
||||||
cursor = cell.style;
|
|
||||||
link = cell.link;
|
|
||||||
}
|
|
||||||
// Set this cell in the last frame
|
|
||||||
self.screen_last.writeCell(col, row, cell);
|
|
||||||
|
|
||||||
// reposition the cursor, if needed
|
|
||||||
if (reposition) {
|
|
||||||
try std.fmt.format(tty.buffered_writer.writer(), ctlseqs.cup, .{ row + 1, col + 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cell.image) |img| {
|
|
||||||
if (img.size) |size| {
|
|
||||||
try std.fmt.format(
|
|
||||||
tty.buffered_writer.writer(),
|
|
||||||
ctlseqs.kitty_graphics_scale,
|
|
||||||
.{ img.img_id, img.z_index, size.cols, size.rows },
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
try std.fmt.format(
|
|
||||||
tty.buffered_writer.writer(),
|
|
||||||
ctlseqs.kitty_graphics_place,
|
|
||||||
.{ img.img_id, img.z_index },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// something is different, so let's loop through everything and
|
|
||||||
// find out what
|
|
||||||
|
|
||||||
// foreground
|
|
||||||
if (!std.meta.eql(cursor.fg, cell.style.fg)) {
|
|
||||||
const writer = tty.buffered_writer.writer();
|
|
||||||
switch (cell.style.fg) {
|
|
||||||
.default => _ = try tty.write(ctlseqs.fg_reset),
|
|
||||||
.index => |idx| {
|
|
||||||
switch (idx) {
|
|
||||||
0...7 => try std.fmt.format(writer, ctlseqs.fg_base, .{idx}),
|
|
||||||
8...15 => try std.fmt.format(writer, ctlseqs.fg_bright, .{idx - 8}),
|
|
||||||
else => try std.fmt.format(writer, ctlseqs.fg_indexed, .{idx}),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.rgb => |rgb| {
|
|
||||||
try std.fmt.format(writer, ctlseqs.fg_rgb, .{ rgb[0], rgb[1], rgb[2] });
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// background
|
|
||||||
if (!std.meta.eql(cursor.bg, cell.style.bg)) {
|
|
||||||
const writer = tty.buffered_writer.writer();
|
|
||||||
switch (cell.style.bg) {
|
|
||||||
.default => _ = try tty.write(ctlseqs.bg_reset),
|
|
||||||
.index => |idx| {
|
|
||||||
switch (idx) {
|
|
||||||
0...7 => try std.fmt.format(writer, ctlseqs.bg_base, .{idx}),
|
|
||||||
8...15 => try std.fmt.format(writer, ctlseqs.bg_bright, .{idx - 8}),
|
|
||||||
else => try std.fmt.format(writer, ctlseqs.bg_indexed, .{idx}),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.rgb => |rgb| {
|
|
||||||
try std.fmt.format(writer, ctlseqs.bg_rgb, .{ rgb[0], rgb[1], rgb[2] });
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// underline color
|
|
||||||
if (!std.meta.eql(cursor.ul, cell.style.ul)) {
|
|
||||||
const writer = tty.buffered_writer.writer();
|
|
||||||
switch (cell.style.bg) {
|
|
||||||
.default => _ = try tty.write(ctlseqs.ul_reset),
|
|
||||||
.index => |idx| {
|
|
||||||
try std.fmt.format(writer, ctlseqs.ul_indexed, .{idx});
|
|
||||||
},
|
|
||||||
.rgb => |rgb| {
|
|
||||||
try std.fmt.format(writer, ctlseqs.ul_rgb, .{ rgb[0], rgb[1], rgb[2] });
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// underline style
|
|
||||||
if (!std.meta.eql(cursor.ul_style, cell.style.ul_style)) {
|
|
||||||
const seq = switch (cell.style.ul_style) {
|
|
||||||
.off => ctlseqs.ul_off,
|
|
||||||
.single => ctlseqs.ul_single,
|
|
||||||
.double => ctlseqs.ul_double,
|
|
||||||
.curly => ctlseqs.ul_curly,
|
|
||||||
.dotted => ctlseqs.ul_dotted,
|
|
||||||
.dashed => ctlseqs.ul_dashed,
|
|
||||||
};
|
|
||||||
_ = try tty.write(seq);
|
|
||||||
}
|
|
||||||
// bold
|
|
||||||
if (cursor.bold != cell.style.bold) {
|
|
||||||
const seq = switch (cell.style.bold) {
|
|
||||||
true => ctlseqs.bold_set,
|
|
||||||
false => ctlseqs.bold_dim_reset,
|
|
||||||
};
|
|
||||||
_ = try tty.write(seq);
|
|
||||||
if (cell.style.dim) {
|
|
||||||
_ = try tty.write(ctlseqs.dim_set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// dim
|
|
||||||
if (cursor.dim != cell.style.dim) {
|
|
||||||
const seq = switch (cell.style.dim) {
|
|
||||||
true => ctlseqs.dim_set,
|
|
||||||
false => ctlseqs.bold_dim_reset,
|
|
||||||
};
|
|
||||||
_ = try tty.write(seq);
|
|
||||||
if (cell.style.bold) {
|
|
||||||
_ = try tty.write(ctlseqs.bold_set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// dim
|
|
||||||
if (cursor.italic != cell.style.italic) {
|
|
||||||
const seq = switch (cell.style.italic) {
|
|
||||||
true => ctlseqs.italic_set,
|
|
||||||
false => ctlseqs.italic_reset,
|
|
||||||
};
|
|
||||||
_ = try tty.write(seq);
|
|
||||||
}
|
|
||||||
// dim
|
|
||||||
if (cursor.blink != cell.style.blink) {
|
|
||||||
const seq = switch (cell.style.blink) {
|
|
||||||
true => ctlseqs.blink_set,
|
|
||||||
false => ctlseqs.blink_reset,
|
|
||||||
};
|
|
||||||
_ = try tty.write(seq);
|
|
||||||
}
|
|
||||||
// reverse
|
|
||||||
if (cursor.reverse != cell.style.reverse) {
|
|
||||||
const seq = switch (cell.style.reverse) {
|
|
||||||
true => ctlseqs.reverse_set,
|
|
||||||
false => ctlseqs.reverse_reset,
|
|
||||||
};
|
|
||||||
_ = try tty.write(seq);
|
|
||||||
}
|
|
||||||
// invisible
|
|
||||||
if (cursor.invisible != cell.style.invisible) {
|
|
||||||
const seq = switch (cell.style.invisible) {
|
|
||||||
true => ctlseqs.invisible_set,
|
|
||||||
false => ctlseqs.invisible_reset,
|
|
||||||
};
|
|
||||||
_ = try tty.write(seq);
|
|
||||||
}
|
|
||||||
// strikethrough
|
|
||||||
if (cursor.strikethrough != cell.style.strikethrough) {
|
|
||||||
const seq = switch (cell.style.strikethrough) {
|
|
||||||
true => ctlseqs.strikethrough_set,
|
|
||||||
false => ctlseqs.strikethrough_reset,
|
|
||||||
};
|
|
||||||
_ = try tty.write(seq);
|
|
||||||
}
|
|
||||||
|
|
||||||
// url
|
|
||||||
if (!std.meta.eql(link.uri, cell.link.uri)) {
|
|
||||||
var ps = cell.link.params;
|
|
||||||
if (cell.link.uri.len == 0) {
|
|
||||||
// Empty out the params no matter what if we don't have
|
|
||||||
// a url
|
|
||||||
ps = "";
|
|
||||||
}
|
|
||||||
const writer = tty.buffered_writer.writer();
|
|
||||||
try std.fmt.format(writer, ctlseqs.osc8, .{ ps, cell.link.uri });
|
|
||||||
}
|
|
||||||
_ = try tty.write(cell.char.grapheme);
|
|
||||||
}
|
|
||||||
if (self.screen.cursor_vis) {
|
|
||||||
try std.fmt.format(
|
|
||||||
tty.buffered_writer.writer(),
|
|
||||||
ctlseqs.cup,
|
|
||||||
.{
|
|
||||||
self.screen.cursor_row + 1,
|
|
||||||
self.screen.cursor_col + 1,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
_ = try tty.write(ctlseqs.show_cursor);
|
|
||||||
}
|
|
||||||
if (self.screen.mouse_shape != self.screen_last.mouse_shape) {
|
|
||||||
try std.fmt.format(
|
|
||||||
tty.buffered_writer.writer(),
|
|
||||||
ctlseqs.osc22_mouse_shape,
|
|
||||||
.{@tagName(self.screen.mouse_shape)},
|
|
||||||
);
|
|
||||||
self.screen_last.mouse_shape = self.screen.mouse_shape;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enableKittyKeyboard(self: *Self, flags: Key.KittyFlags) !void {
|
|
||||||
self.state.kitty_keyboard = true;
|
|
||||||
const flag_int: u5 = @bitCast(flags);
|
|
||||||
try std.fmt.format(
|
|
||||||
self.tty.?.buffered_writer.writer(),
|
|
||||||
ctlseqs.csi_u_push,
|
|
||||||
.{
|
|
||||||
flag_int,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
try self.tty.?.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// send a system notification
|
|
||||||
pub fn notify(self: *Self, title: ?[]const u8, body: []const u8) !void {
|
|
||||||
if (self.tty == null) return;
|
|
||||||
if (title) |t| {
|
|
||||||
try std.fmt.format(
|
|
||||||
self.tty.?.buffered_writer.writer(),
|
|
||||||
ctlseqs.osc777_notify,
|
|
||||||
.{ t, body },
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
try std.fmt.format(
|
|
||||||
self.tty.?.buffered_writer.writer(),
|
|
||||||
ctlseqs.osc9_notify,
|
|
||||||
.{body},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
try self.tty.?.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// sets the window title
|
|
||||||
pub fn setTitle(self: *Self, title: []const u8) !void {
|
|
||||||
if (self.tty == null) return;
|
|
||||||
try std.fmt.format(
|
|
||||||
self.tty.?.buffered_writer.writer(),
|
|
||||||
ctlseqs.osc2_set_title,
|
|
||||||
.{title},
|
|
||||||
);
|
|
||||||
try self.tty.?.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
// turn bracketed paste on or off. An event will be sent at the
|
|
||||||
// beginning and end of a detected paste. All keystrokes between these
|
|
||||||
// events were pasted
|
|
||||||
pub fn setBracketedPaste(self: *Self, enable: bool) !void {
|
|
||||||
if (self.tty == null) return;
|
|
||||||
self.state.bracketed_paste = enable;
|
|
||||||
const seq = if (enable) {
|
|
||||||
self.state.bracketed_paste = true;
|
|
||||||
ctlseqs.bp_set;
|
|
||||||
} else {
|
|
||||||
self.state.bracketed_paste = true;
|
|
||||||
ctlseqs.bp_reset;
|
|
||||||
};
|
|
||||||
_ = try self.tty.?.write(seq);
|
|
||||||
try self.tty.?.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// set the mouse shape
|
|
||||||
pub fn setMouseShape(self: *Self, shape: Mouse.Shape) void {
|
|
||||||
self.screen.mouse_shape = shape;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// turn mouse reporting on or off
|
|
||||||
pub fn setMouseMode(self: *Self, enable: bool) !void {
|
|
||||||
var tty = self.tty orelse return;
|
|
||||||
self.state.mouse = enable;
|
|
||||||
if (enable) {
|
|
||||||
_ = try tty.write(ctlseqs.mouse_set);
|
|
||||||
try tty.flush();
|
|
||||||
} else {
|
|
||||||
_ = try tty.write(ctlseqs.mouse_reset);
|
|
||||||
try tty.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn loadImage(
|
|
||||||
self: *Self,
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
src: Image.Source,
|
|
||||||
) !Image {
|
|
||||||
if (!self.caps.kitty_graphics) return error.NoGraphicsCapability;
|
|
||||||
var tty = self.tty orelse return error.NoTTY;
|
|
||||||
defer self.next_img_id += 1;
|
|
||||||
|
|
||||||
const writer = tty.buffered_writer.writer();
|
|
||||||
|
|
||||||
var img = switch (src) {
|
|
||||||
.path => |path| try zigimg.Image.fromFilePath(alloc, path),
|
|
||||||
.mem => |bytes| try zigimg.Image.fromMemory(alloc, bytes),
|
|
||||||
};
|
|
||||||
defer img.deinit();
|
|
||||||
const png_buf = try alloc.alloc(u8, img.imageByteSize());
|
|
||||||
defer alloc.free(png_buf);
|
|
||||||
const png = try img.writeToMemory(png_buf, .{ .png = .{} });
|
|
||||||
const b64_buf = try alloc.alloc(u8, base64.calcSize(png.len));
|
|
||||||
const encoded = base64.encode(b64_buf, png);
|
|
||||||
defer alloc.free(b64_buf);
|
|
||||||
|
|
||||||
const id = self.next_img_id;
|
|
||||||
|
|
||||||
log.debug("transmitting kitty image: id={d}, len={d}", .{ id, encoded.len });
|
|
||||||
|
|
||||||
if (encoded.len < 4096) {
|
|
||||||
try std.fmt.format(
|
|
||||||
writer,
|
|
||||||
"\x1b_Gf=100,i={d};{s}\x1b\\",
|
|
||||||
.{
|
|
||||||
id,
|
|
||||||
encoded,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
var n: usize = 4096;
|
|
||||||
|
|
||||||
try std.fmt.format(
|
|
||||||
writer,
|
|
||||||
"\x1b_Gf=100,i={d},m=1;{s}\x1b\\",
|
|
||||||
.{ id, encoded[0..n] },
|
|
||||||
);
|
|
||||||
while (n < encoded.len) : (n += 4096) {
|
|
||||||
const end: usize = @min(n + 4096, encoded.len);
|
|
||||||
const m: u2 = if (end == encoded.len) 0 else 1;
|
|
||||||
try std.fmt.format(
|
|
||||||
writer,
|
|
||||||
"\x1b_Gm={d};{s}\x1b\\",
|
|
||||||
.{
|
|
||||||
m,
|
|
||||||
encoded[n..end],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try tty.buffered_writer.flush();
|
|
||||||
return .{
|
|
||||||
.id = id,
|
|
||||||
.width = img.width,
|
|
||||||
.height = img.height,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// deletes an image from the terminal's memory
|
|
||||||
pub fn freeImage(self: Self, id: u32) void {
|
|
||||||
var tty = self.tty orelse return;
|
|
||||||
const writer = tty.buffered_writer.writer();
|
|
||||||
std.fmt.format(writer, "\x1b_Ga=d,d=I,i={d};\x1b\\", .{id}) catch |err| {
|
|
||||||
log.err("couldn't delete image {d}: {}", .{ id, err });
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
tty.buffered_writer.flush() catch |err| {
|
|
||||||
log.err("couldn't flush writer: {}", .{err});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// test "Vaxis: event queueing" {
|
|
||||||
// const Event = union(enum) {
|
|
||||||
// key: void,
|
|
||||||
// };
|
|
||||||
// var vx: Vaxis(Event) = try Vaxis(Event).init(.{});
|
|
||||||
// defer vx.deinit(null);
|
|
||||||
// vx.postEvent(.{ .key = {} });
|
|
||||||
// const event = vx.nextEvent();
|
|
||||||
// try std.testing.expect(event == .key);
|
|
||||||
// }
|
|
Loading…
Reference in a new issue