refactor(vaxis): move tty out of vaxis
Refactor to move the tty out of the vaxis struct. All vaxis writes now take an io.AnyWriter
This commit is contained in:
parent
cf2d2ab50b
commit
59abd7d7d4
6 changed files with 571 additions and 288 deletions
|
@ -29,24 +29,32 @@ pub fn main() !void {
|
||||||
}
|
}
|
||||||
const alloc = gpa.allocator();
|
const alloc = gpa.allocator();
|
||||||
|
|
||||||
|
// Initalize a tty
|
||||||
|
var tty = try vaxis.Tty.init();
|
||||||
|
defer tty.deinit();
|
||||||
|
|
||||||
|
// Use a buffered writer for better performance. There are a lot of writes
|
||||||
|
// in the render loop and this can have a significant savings
|
||||||
|
var buffered_writer = tty.bufferedWriter();
|
||||||
|
const writer = buffered_writer.writer().any();
|
||||||
|
|
||||||
// Initialize Vaxis
|
// Initialize Vaxis
|
||||||
var vx = try vaxis.init(alloc, .{});
|
var vx = try vaxis.init(alloc, .{});
|
||||||
// deinit takes an optional allocator. If your program is exiting, you can
|
defer vx.deinit(tty.anyWriter(), alloc);
|
||||||
// choose to pass a null allocator to save some exit time.
|
|
||||||
defer vx.deinit(alloc);
|
|
||||||
|
|
||||||
// create our event loop
|
|
||||||
var loop: vaxis.Loop(Event) = .{
|
var loop: vaxis.Loop(Event) = .{
|
||||||
.vaxis = &vx,
|
.vaxis = &vx,
|
||||||
|
.tty = &tty,
|
||||||
};
|
};
|
||||||
|
try loop.init();
|
||||||
|
|
||||||
// Start the read loop. This puts the terminal in raw mode and begins
|
// Start the read loop. This puts the terminal in raw mode and begins
|
||||||
// reading user input
|
// reading user input
|
||||||
try loop.run();
|
try loop.start();
|
||||||
defer loop.stop();
|
defer loop.stop();
|
||||||
|
|
||||||
// Optionally enter the alternate screen
|
// Optionally enter the alternate screen
|
||||||
try vx.enterAltScreen();
|
try vx.enterAltScreen(writer);
|
||||||
|
|
||||||
// We'll adjust the color index every keypress for the border
|
// We'll adjust the color index every keypress for the border
|
||||||
var color_idx: u8 = 0;
|
var color_idx: u8 = 0;
|
||||||
|
@ -58,9 +66,11 @@ pub fn main() !void {
|
||||||
|
|
||||||
// Sends queries to terminal to detect certain features. This should
|
// Sends queries to terminal to detect certain features. This should
|
||||||
// _always_ be called, but is left to the application to decide when
|
// _always_ be called, but is left to the application to decide when
|
||||||
try vx.queryTerminal();
|
// try vx.queryTerminal();
|
||||||
|
|
||||||
try vx.setMouseMode(true);
|
try vx.setMouseMode(writer, true);
|
||||||
|
|
||||||
|
try buffered_writer.flush();
|
||||||
|
|
||||||
// The main event loop. Vaxis provides a thread safe, blocking, buffered
|
// The main event loop. Vaxis provides a thread safe, blocking, buffered
|
||||||
// queue which can serve as the primary event queue for an application
|
// queue which can serve as the primary event queue for an application
|
||||||
|
@ -81,12 +91,12 @@ pub fn main() !void {
|
||||||
} else if (key.matches('l', .{ .ctrl = true })) {
|
} else if (key.matches('l', .{ .ctrl = true })) {
|
||||||
vx.queueRefresh();
|
vx.queueRefresh();
|
||||||
} else if (key.matches('n', .{ .ctrl = true })) {
|
} else if (key.matches('n', .{ .ctrl = true })) {
|
||||||
try vx.notify("vaxis", "hello from vaxis");
|
try vx.notify(tty.anyWriter(), "vaxis", "hello from vaxis");
|
||||||
loop.stop();
|
loop.stop();
|
||||||
var child = std.process.Child.init(&.{"nvim"}, alloc);
|
var child = std.process.Child.init(&.{"nvim"}, alloc);
|
||||||
_ = try child.spawnAndWait();
|
_ = try child.spawnAndWait();
|
||||||
try loop.run();
|
try loop.start();
|
||||||
try vx.enterAltScreen();
|
try vx.enterAltScreen(tty.anyWriter());
|
||||||
vx.queueRefresh();
|
vx.queueRefresh();
|
||||||
} else if (key.matches(vaxis.Key.enter, .{})) {
|
} else if (key.matches(vaxis.Key.enter, .{})) {
|
||||||
text_input.clearAndFree();
|
text_input.clearAndFree();
|
||||||
|
@ -110,7 +120,7 @@ pub fn main() !void {
|
||||||
// more than one byte will incur an allocation on the first render
|
// more than one byte will incur an allocation on the first render
|
||||||
// after it is drawn. Thereafter, it will not allocate unless the
|
// after it is drawn. Thereafter, it will not allocate unless the
|
||||||
// screen is resized
|
// screen is resized
|
||||||
.winsize => |ws| try vx.resize(alloc, ws),
|
.winsize => |ws| try vx.resize(alloc, ws, tty.anyWriter()),
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,6 +150,7 @@ pub fn main() !void {
|
||||||
text_input.draw(child);
|
text_input.draw(child);
|
||||||
|
|
||||||
// Render the screen
|
// Render the screen
|
||||||
try vx.render();
|
try vx.render(writer);
|
||||||
|
try buffered_writer.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
208
src/Loop.zig
208
src/Loop.zig
|
@ -1,28 +1,49 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const grapheme = @import("grapheme");
|
||||||
|
|
||||||
|
const GraphemeCache = @import("GraphemeCache.zig");
|
||||||
|
const Parser = @import("Parser.zig");
|
||||||
const Queue = @import("queue.zig").Queue;
|
const Queue = @import("queue.zig").Queue;
|
||||||
const Tty = @import("Tty.zig");
|
const tty = @import("tty.zig");
|
||||||
|
const Tty = tty.Tty;
|
||||||
const Vaxis = @import("Vaxis.zig");
|
const Vaxis = @import("Vaxis.zig");
|
||||||
|
|
||||||
pub fn Loop(comptime T: type) type {
|
pub fn Loop(comptime T: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
const Event = T;
|
||||||
|
|
||||||
const log = std.log.scoped(.loop);
|
const log = std.log.scoped(.loop);
|
||||||
|
|
||||||
queue: Queue(T, 512) = .{},
|
tty: *Tty,
|
||||||
|
|
||||||
thread: ?std.Thread = null,
|
|
||||||
|
|
||||||
vaxis: *Vaxis,
|
vaxis: *Vaxis,
|
||||||
|
|
||||||
|
queue: Queue(T, 512) = .{},
|
||||||
|
thread: ?std.Thread = null,
|
||||||
|
should_quit: bool = false,
|
||||||
|
|
||||||
|
/// Initialize the event loop. This is an intrusive init so that we have
|
||||||
|
/// a stable pointer to register signal callbacks with posix TTYs
|
||||||
|
pub fn init(self: *Self) !void {
|
||||||
|
switch (builtin.os.tag) {
|
||||||
|
.windows => @compileError("windows not supported"),
|
||||||
|
else => {
|
||||||
|
const handler: Tty.SignalHandler = .{
|
||||||
|
.context = self,
|
||||||
|
.callback = Self.winsizeCallback,
|
||||||
|
};
|
||||||
|
try Tty.notifyWinsize(handler);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// spawns the input thread to read input from the tty
|
/// spawns the input thread to read input from the tty
|
||||||
pub fn run(self: *Self) !void {
|
pub fn start(self: *Self) !void {
|
||||||
if (self.thread) |_| return;
|
if (self.thread) |_| return;
|
||||||
if (self.vaxis.tty == null) self.vaxis.tty = try Tty.init();
|
self.thread = try std.Thread.spawn(.{}, Self.ttyRun, .{
|
||||||
self.thread = try std.Thread.spawn(.{}, Tty.run, .{
|
|
||||||
&self.vaxis.tty.?,
|
|
||||||
T,
|
|
||||||
self,
|
self,
|
||||||
&self.vaxis.unicode.grapheme_data,
|
&self.vaxis.unicode.grapheme_data,
|
||||||
self.vaxis.opts.system_clipboard_allocator,
|
self.vaxis.opts.system_clipboard_allocator,
|
||||||
|
@ -31,16 +52,13 @@ pub fn Loop(comptime T: type) type {
|
||||||
|
|
||||||
/// stops reading from the tty and returns it to it's initial state
|
/// stops reading from the tty and returns it to it's initial state
|
||||||
pub fn stop(self: *Self) void {
|
pub fn stop(self: *Self) void {
|
||||||
if (self.vaxis.tty) |*tty| {
|
self.should_quit = true;
|
||||||
// stop the read loop, then join the thread
|
// trigger a read
|
||||||
tty.stop();
|
self.vaxis.deviceStatusReport(self.tty.anyWriter()) catch {};
|
||||||
if (self.thread) |thread| {
|
|
||||||
thread.join();
|
if (self.thread) |thread| {
|
||||||
self.thread = null;
|
thread.join();
|
||||||
}
|
self.thread = null;
|
||||||
// once thread is closed we can deinit the tty
|
|
||||||
tty.deinit();
|
|
||||||
self.vaxis.tty = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,5 +87,155 @@ pub fn Loop(comptime T: type) type {
|
||||||
pub fn tryPostEvent(self: *Self, event: T) bool {
|
pub fn tryPostEvent(self: *Self, event: T) bool {
|
||||||
return self.queue.tryPush(event);
|
return self.queue.tryPush(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn winsizeCallback(ptr: *anyopaque) void {
|
||||||
|
const self: *Self = @ptrCast(@alignCast(ptr));
|
||||||
|
|
||||||
|
const winsize = Tty.getWinsize(self.tty.fd) catch return;
|
||||||
|
if (@hasField(Event, "winsize")) {
|
||||||
|
self.postEvent(.{ .winsize = winsize });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// read input from the tty. This is run in a separate thread
|
||||||
|
fn ttyRun(
|
||||||
|
self: *Self,
|
||||||
|
grapheme_data: *const grapheme.GraphemeData,
|
||||||
|
paste_allocator: ?std.mem.Allocator,
|
||||||
|
) !void {
|
||||||
|
// get our initial winsize
|
||||||
|
const winsize = try Tty.getWinsize(self.tty.fd);
|
||||||
|
if (@hasField(Event, "winsize")) {
|
||||||
|
self.postEvent(.{ .winsize = winsize });
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize a grapheme cache
|
||||||
|
var cache: GraphemeCache = .{};
|
||||||
|
|
||||||
|
var parser: Parser = .{
|
||||||
|
.grapheme_data = grapheme_data,
|
||||||
|
};
|
||||||
|
|
||||||
|
// initialize the read buffer
|
||||||
|
var buf: [1024]u8 = undefined;
|
||||||
|
var read_start: usize = 0;
|
||||||
|
// read loop
|
||||||
|
while (!self.should_quit) {
|
||||||
|
const n = try self.tty.read(buf[read_start..]);
|
||||||
|
var seq_start: usize = 0;
|
||||||
|
while (seq_start < n) {
|
||||||
|
const result = try parser.parse(buf[seq_start..n], paste_allocator);
|
||||||
|
if (result.n == 0) {
|
||||||
|
// copy the read to the beginning. We don't use memcpy because
|
||||||
|
// this could be overlapping, and it's also rare
|
||||||
|
const initial_start = seq_start;
|
||||||
|
while (seq_start < n) : (seq_start += 1) {
|
||||||
|
buf[seq_start - initial_start] = buf[seq_start];
|
||||||
|
}
|
||||||
|
read_start = seq_start - initial_start + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
read_start = 0;
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ const assert = std.debug.assert;
|
||||||
const Cell = @import("Cell.zig");
|
const Cell = @import("Cell.zig");
|
||||||
const Shape = @import("Mouse.zig").Shape;
|
const Shape = @import("Mouse.zig").Shape;
|
||||||
const Image = @import("Image.zig");
|
const Image = @import("Image.zig");
|
||||||
const Winsize = @import("Tty.zig").Winsize;
|
const Winsize = @import("tty.zig").Winsize;
|
||||||
const Unicode = @import("Unicode.zig");
|
const Unicode = @import("Unicode.zig");
|
||||||
const Method = @import("gwidth.zig").Method;
|
const Method = @import("gwidth.zig").Method;
|
||||||
|
|
||||||
|
|
415
src/Vaxis.zig
415
src/Vaxis.zig
|
@ -9,15 +9,16 @@ const InternalScreen = @import("InternalScreen.zig");
|
||||||
const Key = @import("Key.zig");
|
const Key = @import("Key.zig");
|
||||||
const Mouse = @import("Mouse.zig");
|
const Mouse = @import("Mouse.zig");
|
||||||
const Screen = @import("Screen.zig");
|
const Screen = @import("Screen.zig");
|
||||||
const Tty = @import("Tty.zig");
|
const tty = @import("tty.zig");
|
||||||
const Unicode = @import("Unicode.zig");
|
const Unicode = @import("Unicode.zig");
|
||||||
const Window = @import("Window.zig");
|
const Window = @import("Window.zig");
|
||||||
|
|
||||||
|
const AnyWriter = std.io.AnyWriter;
|
||||||
const Hyperlink = Cell.Hyperlink;
|
const Hyperlink = Cell.Hyperlink;
|
||||||
const KittyFlags = Key.KittyFlags;
|
const KittyFlags = Key.KittyFlags;
|
||||||
const Shape = Mouse.Shape;
|
const Shape = Mouse.Shape;
|
||||||
const Style = Cell.Style;
|
const Style = Cell.Style;
|
||||||
const Winsize = Tty.Winsize;
|
const Winsize = tty.Winsize;
|
||||||
|
|
||||||
const ctlseqs = @import("ctlseqs.zig");
|
const ctlseqs = @import("ctlseqs.zig");
|
||||||
const gwidth = @import("gwidth.zig");
|
const gwidth = @import("gwidth.zig");
|
||||||
|
@ -43,8 +44,6 @@ pub const Options = struct {
|
||||||
system_clipboard_allocator: ?std.mem.Allocator = null,
|
system_clipboard_allocator: ?std.mem.Allocator = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
tty: ?Tty,
|
|
||||||
|
|
||||||
/// the screen we write to
|
/// the screen we write to
|
||||||
screen: Screen,
|
screen: Screen,
|
||||||
/// The last screen we drew. We keep this so we can efficiently update on
|
/// The last screen we drew. We keep this so we can efficiently update on
|
||||||
|
@ -96,7 +95,6 @@ state: struct {
|
||||||
pub fn init(alloc: std.mem.Allocator, opts: Options) !Vaxis {
|
pub fn init(alloc: std.mem.Allocator, opts: Options) !Vaxis {
|
||||||
return .{
|
return .{
|
||||||
.opts = opts,
|
.opts = opts,
|
||||||
.tty = null,
|
|
||||||
.screen = .{},
|
.screen = .{},
|
||||||
.screen_last = .{},
|
.screen_last = .{},
|
||||||
.render_timer = try std.time.Timer.start(),
|
.render_timer = try std.time.Timer.start(),
|
||||||
|
@ -108,10 +106,11 @@ pub fn init(alloc: std.mem.Allocator, opts: Options) !Vaxis {
|
||||||
/// passed, this will free resources associated with Vaxis. This is left as an
|
/// passed, this will free resources associated with Vaxis. This is left as an
|
||||||
/// optional so applications can choose to not free resources when the
|
/// optional so applications can choose to not free resources when the
|
||||||
/// application will be exiting anyways
|
/// application will be exiting anyways
|
||||||
pub fn deinit(self: *Vaxis, alloc: ?std.mem.Allocator) void {
|
pub fn deinit(self: *Vaxis, writer: AnyWriter, alloc: ?std.mem.Allocator) void {
|
||||||
if (self.tty) |*tty| {
|
self.resetState(writer) catch {};
|
||||||
tty.deinit();
|
|
||||||
}
|
// always show the cursor on exit
|
||||||
|
writer.writeAll(ctlseqs.show_cursor) catch {};
|
||||||
if (alloc) |a| {
|
if (alloc) |a| {
|
||||||
self.screen.deinit(a);
|
self.screen.deinit(a);
|
||||||
self.screen_last.deinit(a);
|
self.screen_last.deinit(a);
|
||||||
|
@ -124,11 +123,32 @@ pub fn deinit(self: *Vaxis, alloc: ?std.mem.Allocator) void {
|
||||||
self.unicode.deinit();
|
self.unicode.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// resets enabled features
|
||||||
|
pub fn resetState(self: *Vaxis, writer: AnyWriter) !void {
|
||||||
|
if (self.state.kitty_keyboard) {
|
||||||
|
try writer.writeAll(ctlseqs.csi_u_pop);
|
||||||
|
self.state.kitty_keyboard = false;
|
||||||
|
}
|
||||||
|
if (self.state.mouse) {
|
||||||
|
try self.setMouseMode(writer, false);
|
||||||
|
}
|
||||||
|
if (self.state.bracketed_paste) {
|
||||||
|
try self.setBracketedPaste(writer, false);
|
||||||
|
}
|
||||||
|
if (self.state.alt_screen) {
|
||||||
|
try self.exitAltScreen(writer);
|
||||||
|
}
|
||||||
|
if (self.state.color_scheme_updates) {
|
||||||
|
try writer.writeAll(ctlseqs.color_scheme_reset);
|
||||||
|
self.state.color_scheme_updates = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// resize allocates a slice of cells equal to the number of cells
|
/// 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
|
/// required to display the screen (ie width x height). Any previous screen is
|
||||||
/// freed when resizing. The cursor will be sent to it's home position and a
|
/// freed when resizing. The cursor will be sent to it's home position and a
|
||||||
/// hardware clear-below-cursor will be sent
|
/// hardware clear-below-cursor will be sent
|
||||||
pub fn resize(self: *Vaxis, alloc: std.mem.Allocator, winsize: Winsize) !void {
|
pub fn resize(self: *Vaxis, alloc: std.mem.Allocator, winsize: Winsize, writer: AnyWriter) !void {
|
||||||
log.debug("resizing screen: width={d} height={d}", .{ winsize.cols, winsize.rows });
|
log.debug("resizing screen: width={d} height={d}", .{ winsize.cols, winsize.rows });
|
||||||
self.screen.deinit(alloc);
|
self.screen.deinit(alloc);
|
||||||
self.screen = try Screen.init(alloc, winsize, &self.unicode);
|
self.screen = try Screen.init(alloc, winsize, &self.unicode);
|
||||||
|
@ -138,18 +158,16 @@ pub fn resize(self: *Vaxis, alloc: std.mem.Allocator, winsize: Winsize) !void {
|
||||||
// every cell
|
// every cell
|
||||||
self.screen_last.deinit(alloc);
|
self.screen_last.deinit(alloc);
|
||||||
self.screen_last = try InternalScreen.init(alloc, winsize.cols, winsize.rows);
|
self.screen_last = try InternalScreen.init(alloc, winsize.cols, winsize.rows);
|
||||||
var tty = self.tty orelse return;
|
|
||||||
if (self.state.alt_screen)
|
if (self.state.alt_screen)
|
||||||
_ = try tty.write(ctlseqs.home)
|
try writer.writeAll(ctlseqs.home)
|
||||||
else {
|
else {
|
||||||
_ = try tty.buffered_writer.write("\r");
|
try writer.writeByte('\r');
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < self.state.cursor.row) : (i += 1) {
|
while (i < self.state.cursor.row) : (i += 1) {
|
||||||
_ = try tty.buffered_writer.write(ctlseqs.ri);
|
try writer.writeAll(ctlseqs.ri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = try tty.write(ctlseqs.erase_below_cursor);
|
try writer.writeAll(ctlseqs.erase_below_cursor);
|
||||||
try tty.flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns a Window comprising of the entire terminal screen
|
/// returns a Window comprising of the entire terminal screen
|
||||||
|
@ -165,23 +183,15 @@ pub fn window(self: *Vaxis) Window {
|
||||||
|
|
||||||
/// enter the alternate screen. The alternate screen will automatically
|
/// enter the alternate screen. The alternate screen will automatically
|
||||||
/// be exited if calling deinit while in the alt screen
|
/// be exited if calling deinit while in the alt screen
|
||||||
pub fn enterAltScreen(self: *Vaxis) !void {
|
pub fn enterAltScreen(self: *Vaxis, writer: AnyWriter) !void {
|
||||||
if (self.tty) |*tty| {
|
try writer.writeAll(ctlseqs.smcup);
|
||||||
if (self.state.alt_screen) return;
|
self.state.alt_screen = true;
|
||||||
_ = try tty.write(ctlseqs.smcup);
|
|
||||||
try tty.flush();
|
|
||||||
self.state.alt_screen = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// exit the alternate screen
|
/// exit the alternate screen
|
||||||
pub fn exitAltScreen(self: *Vaxis) !void {
|
pub fn exitAltScreen(self: *Vaxis, writer: AnyWriter) !void {
|
||||||
if (self.tty) |*tty| {
|
try writer.writeAll(ctlseqs.rmcup);
|
||||||
if (!self.state.alt_screen) return;
|
self.state.alt_screen = false;
|
||||||
_ = try tty.write(ctlseqs.rmcup);
|
|
||||||
try tty.flush();
|
|
||||||
self.state.alt_screen = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// write queries to the terminal to determine capabilities. Individual
|
/// write queries to the terminal to determine capabilities. Individual
|
||||||
|
@ -199,8 +209,7 @@ pub fn queryTerminal(self: *Vaxis) !void {
|
||||||
/// write queries to the terminal to determine capabilities. This function
|
/// write queries to the terminal to determine capabilities. This function
|
||||||
/// is only for use with a custom main loop. Call Vaxis.queryTerminal() if
|
/// is only for use with a custom main loop. Call Vaxis.queryTerminal() if
|
||||||
/// you are using Loop.run()
|
/// you are using Loop.run()
|
||||||
pub fn queryTerminalSend(self: *Vaxis) !void {
|
pub fn queryTerminalSend(_: Vaxis, writer: AnyWriter) !void {
|
||||||
var tty = self.tty orelse return;
|
|
||||||
|
|
||||||
// TODO: re-enable this
|
// TODO: re-enable this
|
||||||
// const colorterm = std.posix.getenv("COLORTERM") orelse "";
|
// const colorterm = std.posix.getenv("COLORTERM") orelse "";
|
||||||
|
@ -216,29 +225,26 @@ pub fn queryTerminalSend(self: *Vaxis) !void {
|
||||||
// doesn't hurt to blindly use them
|
// doesn't hurt to blindly use them
|
||||||
// _ = try tty.write(ctlseqs.decrqm_focus);
|
// _ = try tty.write(ctlseqs.decrqm_focus);
|
||||||
// _ = try tty.write(ctlseqs.decrqm_sync);
|
// _ = try tty.write(ctlseqs.decrqm_sync);
|
||||||
_ = try tty.write(ctlseqs.decrqm_sgr_pixels);
|
try writer.writeAll(ctlseqs.decrqm_sgr_pixels);
|
||||||
_ = try tty.write(ctlseqs.decrqm_unicode);
|
try writer.writeAll(ctlseqs.decrqm_unicode);
|
||||||
_ = try tty.write(ctlseqs.decrqm_color_scheme);
|
try writer.writeAll(ctlseqs.decrqm_color_scheme);
|
||||||
// TODO: XTVERSION has a DCS response. uncomment when we can parse
|
// TODO: XTVERSION has a DCS response. uncomment when we can parse
|
||||||
// that
|
// that
|
||||||
// _ = try tty.write(ctlseqs.xtversion);
|
// _ = try tty.write(ctlseqs.xtversion);
|
||||||
_ = try tty.write(ctlseqs.csi_u_query);
|
try writer.writeAll(ctlseqs.csi_u_query);
|
||||||
_ = try tty.write(ctlseqs.kitty_graphics_query);
|
try writer.writeAll(ctlseqs.kitty_graphics_query);
|
||||||
// TODO: sixel geometry query interferes with F4 keys.
|
// TODO: sixel geometry query interferes with F4 keys.
|
||||||
// _ = try tty.write(ctlseqs.sixel_geometry_query);
|
// _ = try tty.write(ctlseqs.sixel_geometry_query);
|
||||||
|
|
||||||
// TODO: XTGETTCAP queries ("RGB", "Smulx")
|
// TODO: XTGETTCAP queries ("RGB", "Smulx")
|
||||||
|
|
||||||
_ = try tty.write(ctlseqs.primary_device_attrs);
|
try writer.writeAll(ctlseqs.primary_device_attrs);
|
||||||
try tty.flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable features detected by responses to queryTerminal. This function
|
/// Enable features detected by responses to queryTerminal. This function
|
||||||
/// is only for use with a custom main loop. Call Vaxis.queryTerminal() if
|
/// is only for use with a custom main loop. Call Vaxis.queryTerminal() if
|
||||||
/// you are using Loop.run()
|
/// you are using Loop.run()
|
||||||
pub fn enableDetectedFeatures(self: *Vaxis) !void {
|
pub fn enableDetectedFeatures(self: *Vaxis) !void {
|
||||||
var tty = self.tty orelse return;
|
|
||||||
|
|
||||||
// Apply any environment variables
|
// Apply any environment variables
|
||||||
if (std.posix.getenv("ASCIINEMA_REC")) |_|
|
if (std.posix.getenv("ASCIINEMA_REC")) |_|
|
||||||
self.sgr = .legacy;
|
self.sgr = .legacy;
|
||||||
|
@ -270,8 +276,7 @@ pub fn queueRefresh(self: *Vaxis) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// draws the screen to the terminal
|
/// draws the screen to the terminal
|
||||||
pub fn render(self: *Vaxis) !void {
|
pub fn render(self: *Vaxis, writer: AnyWriter) !void {
|
||||||
var tty = self.tty orelse return;
|
|
||||||
self.renders += 1;
|
self.renders += 1;
|
||||||
self.render_timer.reset();
|
self.render_timer.reset();
|
||||||
defer {
|
defer {
|
||||||
|
@ -279,30 +284,29 @@ pub fn render(self: *Vaxis) !void {
|
||||||
}
|
}
|
||||||
|
|
||||||
defer self.refresh = false;
|
defer self.refresh = false;
|
||||||
defer tty.flush() catch {};
|
|
||||||
|
|
||||||
// Set up sync before we write anything
|
// Set up sync before we write anything
|
||||||
// TODO: optimize sync so we only sync _when we have changes_. This
|
// TODO: optimize sync so we only sync _when we have changes_. This
|
||||||
// requires a smarter buffered writer, we'll probably have to write
|
// requires a smarter buffered writer, we'll probably have to write
|
||||||
// our own
|
// our own
|
||||||
_ = try tty.write(ctlseqs.sync_set);
|
try writer.writeAll(ctlseqs.sync_set);
|
||||||
defer _ = tty.write(ctlseqs.sync_reset) catch {};
|
defer writer.writeAll(ctlseqs.sync_reset) catch {};
|
||||||
|
|
||||||
// Send the cursor to 0,0
|
// Send the cursor to 0,0
|
||||||
// TODO: this needs to move after we optimize writes. We only do
|
// 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
|
// this if we have an update to make. We also need to hide cursor
|
||||||
// and then reshow it if needed
|
// and then reshow it if needed
|
||||||
_ = try tty.write(ctlseqs.hide_cursor);
|
try writer.writeAll(ctlseqs.hide_cursor);
|
||||||
if (self.state.alt_screen)
|
if (self.state.alt_screen)
|
||||||
_ = try tty.write(ctlseqs.home)
|
try writer.writeAll(ctlseqs.home)
|
||||||
else {
|
else {
|
||||||
_ = try tty.write("\r");
|
try writer.writeAll("\r");
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < self.state.cursor.row) : (i += 1) {
|
while (i < self.state.cursor.row) : (i += 1) {
|
||||||
_ = try tty.write(ctlseqs.ri);
|
try writer.writeAll(ctlseqs.ri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = try tty.write(ctlseqs.sgr_reset);
|
try writer.writeAll(ctlseqs.sgr_reset);
|
||||||
|
|
||||||
// initialize some variables
|
// initialize some variables
|
||||||
var reposition: bool = false;
|
var reposition: bool = false;
|
||||||
|
@ -317,7 +321,7 @@ pub fn render(self: *Vaxis) !void {
|
||||||
|
|
||||||
// Clear all images
|
// Clear all images
|
||||||
if (self.caps.kitty_graphics)
|
if (self.caps.kitty_graphics)
|
||||||
_ = try tty.write(ctlseqs.kitty_graphics_clear);
|
try writer.writeAll(ctlseqs.kitty_graphics_clear);
|
||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < self.screen.buf.len) {
|
while (i < self.screen.buf.len) {
|
||||||
|
@ -353,7 +357,7 @@ pub fn render(self: *Vaxis) !void {
|
||||||
// Close any osc8 sequence we might be in before
|
// Close any osc8 sequence we might be in before
|
||||||
// repositioning
|
// repositioning
|
||||||
if (link.uri.len > 0) {
|
if (link.uri.len > 0) {
|
||||||
_ = try tty.write(ctlseqs.osc8_clear);
|
try writer.writeAll(ctlseqs.osc8_clear);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -369,76 +373,52 @@ pub fn render(self: *Vaxis) !void {
|
||||||
if (reposition) {
|
if (reposition) {
|
||||||
reposition = false;
|
reposition = false;
|
||||||
if (self.state.alt_screen)
|
if (self.state.alt_screen)
|
||||||
try std.fmt.format(tty.buffered_writer.writer(), ctlseqs.cup, .{ row + 1, col + 1 })
|
try writer.print(ctlseqs.cup, .{ row + 1, col + 1 })
|
||||||
else {
|
else {
|
||||||
if (cursor_pos.row == row) {
|
if (cursor_pos.row == row) {
|
||||||
const n = col - cursor_pos.col;
|
const n = col - cursor_pos.col;
|
||||||
try std.fmt.format(tty.buffered_writer.writer(), ctlseqs.cuf, .{n});
|
try writer.print(ctlseqs.cuf, .{n});
|
||||||
} else {
|
} else {
|
||||||
|
try writer.writeByte('\r');
|
||||||
const n = row - cursor_pos.row;
|
const n = row - cursor_pos.row;
|
||||||
var _i: usize = 0;
|
try writer.writeByteNTimes('\n', n);
|
||||||
_ = try tty.buffered_writer.write("\r");
|
|
||||||
while (_i < n) : (_i += 1) {
|
|
||||||
_ = try tty.buffered_writer.write("\n");
|
|
||||||
}
|
|
||||||
if (col > 0)
|
|
||||||
try std.fmt.format(tty.buffered_writer.writer(), ctlseqs.cuf, .{col});
|
|
||||||
}
|
}
|
||||||
|
if (col > 0)
|
||||||
|
try writer.print(ctlseqs.cuf, .{col});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cell.image) |img| {
|
if (cell.image) |img| {
|
||||||
try tty.buffered_writer.writer().print(
|
try writer.print(
|
||||||
ctlseqs.kitty_graphics_preamble,
|
ctlseqs.kitty_graphics_preamble,
|
||||||
.{img.img_id},
|
.{img.img_id},
|
||||||
);
|
);
|
||||||
if (img.options.pixel_offset) |offset| {
|
if (img.options.pixel_offset) |offset| {
|
||||||
try tty.buffered_writer.writer().print(
|
try writer.print(
|
||||||
",X={d},Y={d}",
|
",X={d},Y={d}",
|
||||||
.{ offset.x, offset.y },
|
.{ offset.x, offset.y },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (img.options.clip_region) |clip| {
|
if (img.options.clip_region) |clip| {
|
||||||
if (clip.x) |x|
|
if (clip.x) |x|
|
||||||
try tty.buffered_writer.writer().print(
|
try writer.print(",x={d}", .{x});
|
||||||
",x={d}",
|
|
||||||
.{x},
|
|
||||||
);
|
|
||||||
if (clip.y) |y|
|
if (clip.y) |y|
|
||||||
try tty.buffered_writer.writer().print(
|
try writer.print(",y={d}", .{y});
|
||||||
",y={d}",
|
|
||||||
.{y},
|
|
||||||
);
|
|
||||||
if (clip.width) |width|
|
if (clip.width) |width|
|
||||||
try tty.buffered_writer.writer().print(
|
try writer.print(",w={d}", .{width});
|
||||||
",w={d}",
|
|
||||||
.{width},
|
|
||||||
);
|
|
||||||
if (clip.height) |height|
|
if (clip.height) |height|
|
||||||
try tty.buffered_writer.writer().print(
|
try writer.print(",h={d}", .{height});
|
||||||
",h={d}",
|
|
||||||
.{height},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (img.options.size) |size| {
|
if (img.options.size) |size| {
|
||||||
if (size.rows) |rows|
|
if (size.rows) |rows|
|
||||||
try tty.buffered_writer.writer().print(
|
try writer.print(",r={d}", .{rows});
|
||||||
",r={d}",
|
|
||||||
.{rows},
|
|
||||||
);
|
|
||||||
if (size.cols) |cols|
|
if (size.cols) |cols|
|
||||||
try tty.buffered_writer.writer().print(
|
try writer.print(",c={d}", .{cols});
|
||||||
",c={d}",
|
|
||||||
.{cols},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (img.options.z_index) |z| {
|
if (img.options.z_index) |z| {
|
||||||
try tty.buffered_writer.writer().print(
|
try writer.print(",z={d}", .{z});
|
||||||
",z={d}",
|
|
||||||
.{z},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
try tty.buffered_writer.writer().writeAll(ctlseqs.kitty_graphics_closing);
|
try writer.writeAll(ctlseqs.kitty_graphics_closing);
|
||||||
}
|
}
|
||||||
|
|
||||||
// something is different, so let's loop through everything and
|
// something is different, so let's loop through everything and
|
||||||
|
@ -446,69 +426,66 @@ pub fn render(self: *Vaxis) !void {
|
||||||
|
|
||||||
// foreground
|
// foreground
|
||||||
if (!Cell.Color.eql(cursor.fg, cell.style.fg)) {
|
if (!Cell.Color.eql(cursor.fg, cell.style.fg)) {
|
||||||
const writer = tty.buffered_writer.writer();
|
|
||||||
switch (cell.style.fg) {
|
switch (cell.style.fg) {
|
||||||
.default => _ = try tty.write(ctlseqs.fg_reset),
|
.default => try writer.writeAll(ctlseqs.fg_reset),
|
||||||
.index => |idx| {
|
.index => |idx| {
|
||||||
switch (idx) {
|
switch (idx) {
|
||||||
0...7 => try std.fmt.format(writer, ctlseqs.fg_base, .{idx}),
|
0...7 => try writer.print(ctlseqs.fg_base, .{idx}),
|
||||||
8...15 => try std.fmt.format(writer, ctlseqs.fg_bright, .{idx - 8}),
|
8...15 => try writer.print(ctlseqs.fg_bright, .{idx - 8}),
|
||||||
else => {
|
else => {
|
||||||
switch (self.sgr) {
|
switch (self.sgr) {
|
||||||
.standard => try std.fmt.format(writer, ctlseqs.fg_indexed, .{idx}),
|
.standard => try writer.print(ctlseqs.fg_indexed, .{idx}),
|
||||||
.legacy => try std.fmt.format(writer, ctlseqs.fg_indexed_legacy, .{idx}),
|
.legacy => try writer.print(ctlseqs.fg_indexed_legacy, .{idx}),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.rgb => |rgb| {
|
.rgb => |rgb| {
|
||||||
switch (self.sgr) {
|
switch (self.sgr) {
|
||||||
.standard => try std.fmt.format(writer, ctlseqs.fg_rgb, .{ rgb[0], rgb[1], rgb[2] }),
|
.standard => try writer.print(ctlseqs.fg_rgb, .{ rgb[0], rgb[1], rgb[2] }),
|
||||||
.legacy => try std.fmt.format(writer, ctlseqs.fg_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }),
|
.legacy => try writer.print(ctlseqs.fg_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// background
|
// background
|
||||||
if (!Cell.Color.eql(cursor.bg, cell.style.bg)) {
|
if (!Cell.Color.eql(cursor.bg, cell.style.bg)) {
|
||||||
const writer = tty.buffered_writer.writer();
|
|
||||||
switch (cell.style.bg) {
|
switch (cell.style.bg) {
|
||||||
.default => _ = try tty.write(ctlseqs.bg_reset),
|
.default => try writer.writeAll(ctlseqs.bg_reset),
|
||||||
.index => |idx| {
|
.index => |idx| {
|
||||||
switch (idx) {
|
switch (idx) {
|
||||||
0...7 => try std.fmt.format(writer, ctlseqs.bg_base, .{idx}),
|
0...7 => try writer.print(ctlseqs.bg_base, .{idx}),
|
||||||
8...15 => try std.fmt.format(writer, ctlseqs.bg_bright, .{idx - 8}),
|
8...15 => try writer.print(ctlseqs.bg_bright, .{idx - 8}),
|
||||||
else => {
|
else => {
|
||||||
switch (self.sgr) {
|
switch (self.sgr) {
|
||||||
.standard => try std.fmt.format(writer, ctlseqs.bg_indexed, .{idx}),
|
.standard => try writer.print(ctlseqs.bg_indexed, .{idx}),
|
||||||
.legacy => try std.fmt.format(writer, ctlseqs.bg_indexed_legacy, .{idx}),
|
.legacy => try writer.print(ctlseqs.bg_indexed_legacy, .{idx}),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.rgb => |rgb| {
|
.rgb => |rgb| {
|
||||||
switch (self.sgr) {
|
switch (self.sgr) {
|
||||||
.standard => try std.fmt.format(writer, ctlseqs.bg_rgb, .{ rgb[0], rgb[1], rgb[2] }),
|
.standard => try writer.print(ctlseqs.bg_rgb, .{ rgb[0], rgb[1], rgb[2] }),
|
||||||
.legacy => try std.fmt.format(writer, ctlseqs.bg_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }),
|
.legacy => try writer.print(ctlseqs.bg_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// underline color
|
// underline color
|
||||||
if (!Cell.Color.eql(cursor.ul, cell.style.ul)) {
|
if (!Cell.Color.eql(cursor.ul, cell.style.ul)) {
|
||||||
const writer = tty.buffered_writer.writer();
|
|
||||||
switch (cell.style.bg) {
|
switch (cell.style.bg) {
|
||||||
.default => _ = try tty.write(ctlseqs.ul_reset),
|
.default => try writer.writeAll(ctlseqs.ul_reset),
|
||||||
.index => |idx| {
|
.index => |idx| {
|
||||||
switch (self.sgr) {
|
switch (self.sgr) {
|
||||||
.standard => try std.fmt.format(writer, ctlseqs.ul_indexed, .{idx}),
|
.standard => try writer.print(ctlseqs.ul_indexed, .{idx}),
|
||||||
.legacy => try std.fmt.format(writer, ctlseqs.ul_indexed_legacy, .{idx}),
|
.legacy => try writer.print(ctlseqs.ul_indexed_legacy, .{idx}),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.rgb => |rgb| {
|
.rgb => |rgb| {
|
||||||
switch (self.sgr) {
|
switch (self.sgr) {
|
||||||
.standard => try std.fmt.format(writer, ctlseqs.ul_rgb, .{ rgb[0], rgb[1], rgb[2] }),
|
.standard => try writer.print(ctlseqs.ul_rgb, .{ rgb[0], rgb[1], rgb[2] }),
|
||||||
.legacy => try std.fmt.format(writer, ctlseqs.ul_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }),
|
.legacy => try writer.print(ctlseqs.ul_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -523,7 +500,7 @@ pub fn render(self: *Vaxis) !void {
|
||||||
.dotted => ctlseqs.ul_dotted,
|
.dotted => ctlseqs.ul_dotted,
|
||||||
.dashed => ctlseqs.ul_dashed,
|
.dashed => ctlseqs.ul_dashed,
|
||||||
};
|
};
|
||||||
_ = try tty.write(seq);
|
try writer.writeAll(seq);
|
||||||
}
|
}
|
||||||
// bold
|
// bold
|
||||||
if (cursor.bold != cell.style.bold) {
|
if (cursor.bold != cell.style.bold) {
|
||||||
|
@ -531,9 +508,9 @@ pub fn render(self: *Vaxis) !void {
|
||||||
true => ctlseqs.bold_set,
|
true => ctlseqs.bold_set,
|
||||||
false => ctlseqs.bold_dim_reset,
|
false => ctlseqs.bold_dim_reset,
|
||||||
};
|
};
|
||||||
_ = try tty.write(seq);
|
try writer.writeAll(seq);
|
||||||
if (cell.style.dim) {
|
if (cell.style.dim) {
|
||||||
_ = try tty.write(ctlseqs.dim_set);
|
try writer.writeAll(ctlseqs.dim_set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// dim
|
// dim
|
||||||
|
@ -542,9 +519,9 @@ pub fn render(self: *Vaxis) !void {
|
||||||
true => ctlseqs.dim_set,
|
true => ctlseqs.dim_set,
|
||||||
false => ctlseqs.bold_dim_reset,
|
false => ctlseqs.bold_dim_reset,
|
||||||
};
|
};
|
||||||
_ = try tty.write(seq);
|
try writer.writeAll(seq);
|
||||||
if (cell.style.bold) {
|
if (cell.style.bold) {
|
||||||
_ = try tty.write(ctlseqs.bold_set);
|
try writer.writeAll(ctlseqs.bold_set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// dim
|
// dim
|
||||||
|
@ -553,7 +530,7 @@ pub fn render(self: *Vaxis) !void {
|
||||||
true => ctlseqs.italic_set,
|
true => ctlseqs.italic_set,
|
||||||
false => ctlseqs.italic_reset,
|
false => ctlseqs.italic_reset,
|
||||||
};
|
};
|
||||||
_ = try tty.write(seq);
|
try writer.writeAll(seq);
|
||||||
}
|
}
|
||||||
// dim
|
// dim
|
||||||
if (cursor.blink != cell.style.blink) {
|
if (cursor.blink != cell.style.blink) {
|
||||||
|
@ -561,7 +538,7 @@ pub fn render(self: *Vaxis) !void {
|
||||||
true => ctlseqs.blink_set,
|
true => ctlseqs.blink_set,
|
||||||
false => ctlseqs.blink_reset,
|
false => ctlseqs.blink_reset,
|
||||||
};
|
};
|
||||||
_ = try tty.write(seq);
|
try writer.writeAll(seq);
|
||||||
}
|
}
|
||||||
// reverse
|
// reverse
|
||||||
if (cursor.reverse != cell.style.reverse) {
|
if (cursor.reverse != cell.style.reverse) {
|
||||||
|
@ -569,7 +546,7 @@ pub fn render(self: *Vaxis) !void {
|
||||||
true => ctlseqs.reverse_set,
|
true => ctlseqs.reverse_set,
|
||||||
false => ctlseqs.reverse_reset,
|
false => ctlseqs.reverse_reset,
|
||||||
};
|
};
|
||||||
_ = try tty.write(seq);
|
try writer.writeAll(seq);
|
||||||
}
|
}
|
||||||
// invisible
|
// invisible
|
||||||
if (cursor.invisible != cell.style.invisible) {
|
if (cursor.invisible != cell.style.invisible) {
|
||||||
|
@ -577,7 +554,7 @@ pub fn render(self: *Vaxis) !void {
|
||||||
true => ctlseqs.invisible_set,
|
true => ctlseqs.invisible_set,
|
||||||
false => ctlseqs.invisible_reset,
|
false => ctlseqs.invisible_reset,
|
||||||
};
|
};
|
||||||
_ = try tty.write(seq);
|
try writer.writeAll(seq);
|
||||||
}
|
}
|
||||||
// strikethrough
|
// strikethrough
|
||||||
if (cursor.strikethrough != cell.style.strikethrough) {
|
if (cursor.strikethrough != cell.style.strikethrough) {
|
||||||
|
@ -585,7 +562,7 @@ pub fn render(self: *Vaxis) !void {
|
||||||
true => ctlseqs.strikethrough_set,
|
true => ctlseqs.strikethrough_set,
|
||||||
false => ctlseqs.strikethrough_reset,
|
false => ctlseqs.strikethrough_reset,
|
||||||
};
|
};
|
||||||
_ = try tty.write(seq);
|
try writer.writeAll(seq);
|
||||||
}
|
}
|
||||||
|
|
||||||
// url
|
// url
|
||||||
|
@ -596,17 +573,15 @@ pub fn render(self: *Vaxis) !void {
|
||||||
// a url
|
// a url
|
||||||
ps = "";
|
ps = "";
|
||||||
}
|
}
|
||||||
const writer = tty.buffered_writer.writer();
|
try writer.print(ctlseqs.osc8, .{ ps, cell.link.uri });
|
||||||
try std.fmt.format(writer, ctlseqs.osc8, .{ ps, cell.link.uri });
|
|
||||||
}
|
}
|
||||||
_ = try tty.write(cell.char.grapheme);
|
try writer.writeAll(cell.char.grapheme);
|
||||||
cursor_pos.col = col + w;
|
cursor_pos.col = col + w;
|
||||||
cursor_pos.row = row;
|
cursor_pos.row = row;
|
||||||
}
|
}
|
||||||
if (self.screen.cursor_vis) {
|
if (self.screen.cursor_vis) {
|
||||||
if (self.state.alt_screen) {
|
if (self.state.alt_screen) {
|
||||||
try std.fmt.format(
|
try writer.print(
|
||||||
tty.buffered_writer.writer(),
|
|
||||||
ctlseqs.cup,
|
ctlseqs.cup,
|
||||||
.{
|
.{
|
||||||
self.screen.cursor_row + 1,
|
self.screen.cursor_row + 1,
|
||||||
|
@ -615,39 +590,30 @@ pub fn render(self: *Vaxis) !void {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// TODO: position cursor relative to current location
|
// TODO: position cursor relative to current location
|
||||||
_ = try tty.write("\r");
|
try writer.writeByte('\r');
|
||||||
var r: usize = 0;
|
if (self.screen.cursor_row >= cursor_pos.row)
|
||||||
if (self.screen.cursor_row >= cursor_pos.row) {
|
try writer.writeByteNTimes('\n', self.screen.cursor_row - cursor_pos.row)
|
||||||
while (r < (self.screen.cursor_row - cursor_pos.row)) : (r += 1) {
|
else
|
||||||
_ = try tty.write("\n");
|
try writer.writeBytesNTimes(ctlseqs.ri, cursor_pos.row - self.screen.cursor_row);
|
||||||
}
|
if (self.screen.cursor_col > 0)
|
||||||
} else {
|
try writer.print(ctlseqs.cuf, .{self.screen.cursor_col});
|
||||||
while (r < (cursor_pos.row - self.screen.cursor_row)) : (r += 1) {
|
|
||||||
_ = try tty.write(ctlseqs.ri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (self.screen.cursor_col > 0) {
|
|
||||||
try std.fmt.format(tty.buffered_writer.writer(), ctlseqs.cuf, .{self.screen.cursor_col});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.state.cursor.row = self.screen.cursor_row;
|
self.state.cursor.row = self.screen.cursor_row;
|
||||||
self.state.cursor.col = self.screen.cursor_col;
|
self.state.cursor.col = self.screen.cursor_col;
|
||||||
_ = try tty.write(ctlseqs.show_cursor);
|
try writer.writeAll(ctlseqs.show_cursor);
|
||||||
} else {
|
} else {
|
||||||
self.state.cursor.row = cursor_pos.row;
|
self.state.cursor.row = cursor_pos.row;
|
||||||
self.state.cursor.col = cursor_pos.col;
|
self.state.cursor.col = cursor_pos.col;
|
||||||
}
|
}
|
||||||
if (self.screen.mouse_shape != self.screen_last.mouse_shape) {
|
if (self.screen.mouse_shape != self.screen_last.mouse_shape) {
|
||||||
try std.fmt.format(
|
try writer.print(
|
||||||
tty.buffered_writer.writer(),
|
|
||||||
ctlseqs.osc22_mouse_shape,
|
ctlseqs.osc22_mouse_shape,
|
||||||
.{@tagName(self.screen.mouse_shape)},
|
.{@tagName(self.screen.mouse_shape)},
|
||||||
);
|
);
|
||||||
self.screen_last.mouse_shape = self.screen.mouse_shape;
|
self.screen_last.mouse_shape = self.screen.mouse_shape;
|
||||||
}
|
}
|
||||||
if (self.screen.cursor_shape != self.screen_last.cursor_shape) {
|
if (self.screen.cursor_shape != self.screen_last.cursor_shape) {
|
||||||
try std.fmt.format(
|
try writer.print(
|
||||||
tty.buffered_writer.writer(),
|
|
||||||
ctlseqs.cursor_shape,
|
ctlseqs.cursor_shape,
|
||||||
.{@intFromEnum(self.screen.cursor_shape)},
|
.{@intFromEnum(self.screen.cursor_shape)},
|
||||||
);
|
);
|
||||||
|
@ -655,64 +621,35 @@ pub fn render(self: *Vaxis) !void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enableKittyKeyboard(self: *Vaxis, flags: Key.KittyFlags) !void {
|
fn enableKittyKeyboard(self: *Vaxis, writer: AnyWriter, flags: Key.KittyFlags) !void {
|
||||||
if (self.tty) |*tty| {
|
const flag_int: u5 = @bitCast(flags);
|
||||||
const flag_int: u5 = @bitCast(flags);
|
try writer.print(ctlseqs.csi_u_push, .{flag_int});
|
||||||
try std.fmt.format(
|
self.state.kitty_keyboard = true;
|
||||||
tty.buffered_writer.writer(),
|
|
||||||
ctlseqs.csi_u_push,
|
|
||||||
.{
|
|
||||||
flag_int,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
try tty.flush();
|
|
||||||
self.state.kitty_keyboard = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// send a system notification
|
/// send a system notification
|
||||||
pub fn notify(self: *Vaxis, title: ?[]const u8, body: []const u8) !void {
|
pub fn notify(_: *Vaxis, writer: AnyWriter, title: ?[]const u8, body: []const u8) !void {
|
||||||
if (self.tty == null) return;
|
if (title) |t|
|
||||||
if (title) |t| {
|
try writer.print(ctlseqs.osc777_notify, .{ t, body })
|
||||||
try std.fmt.format(
|
else
|
||||||
self.tty.?.buffered_writer.writer(),
|
try writer.print(ctlseqs.osc9_notify, .{body});
|
||||||
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
|
/// sets the window title
|
||||||
pub fn setTitle(self: *Vaxis, title: []const u8) !void {
|
pub fn setTitle(_: *Vaxis, writer: AnyWriter, title: []const u8) !void {
|
||||||
if (self.tty == null) return;
|
try writer.print(ctlseqs.osc2_set_title, .{title});
|
||||||
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
|
// turn bracketed paste on or off. An event will be sent at the
|
||||||
// beginning and end of a detected paste. All keystrokes between these
|
// beginning and end of a detected paste. All keystrokes between these
|
||||||
// events were pasted
|
// events were pasted
|
||||||
pub fn setBracketedPaste(self: *Vaxis, enable: bool) !void {
|
pub fn setBracketedPaste(self: *Vaxis, writer: AnyWriter, enable: bool) !void {
|
||||||
if (self.tty) |*tty| {
|
const seq = if (enable)
|
||||||
const seq = if (enable)
|
ctlseqs.bp_set
|
||||||
ctlseqs.bp_set
|
else
|
||||||
else
|
ctlseqs.bp_reset;
|
||||||
ctlseqs.bp_reset;
|
try writer.writeAll(seq);
|
||||||
_ = try tty.write(seq);
|
self.state.bracketed_paste = enable;
|
||||||
try tty.flush();
|
|
||||||
self.state.bracketed_paste = enable;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// set the mouse shape
|
/// set the mouse shape
|
||||||
|
@ -721,22 +658,19 @@ pub fn setMouseShape(self: *Vaxis, shape: Shape) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the mouse reporting mode
|
/// Change the mouse reporting mode
|
||||||
pub fn setMouseMode(self: *Vaxis, enable: bool) !void {
|
pub fn setMouseMode(self: *Vaxis, writer: AnyWriter, enable: bool) !void {
|
||||||
if (self.tty) |*tty| {
|
if (enable) {
|
||||||
if (enable) {
|
self.state.mouse = true;
|
||||||
self.state.mouse = true;
|
if (self.caps.sgr_pixels) {
|
||||||
if (self.caps.sgr_pixels) {
|
log.debug("enabling mouse mode: pixel coordinates", .{});
|
||||||
log.debug("enabling mouse mode: pixel coordinates", .{});
|
self.state.pixel_mouse = true;
|
||||||
self.state.pixel_mouse = true;
|
try writer.writeAll(ctlseqs.mouse_set_pixels);
|
||||||
_ = try tty.write(ctlseqs.mouse_set_pixels);
|
|
||||||
} else {
|
|
||||||
log.debug("enabling mouse mode: cell coordinates", .{});
|
|
||||||
_ = try tty.write(ctlseqs.mouse_set);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
_ = try tty.write(ctlseqs.mouse_reset);
|
log.debug("enabling mouse mode: cell coordinates", .{});
|
||||||
|
try writer.writeAll(ctlseqs.mouse_set);
|
||||||
}
|
}
|
||||||
try tty.flush();
|
} else {
|
||||||
|
try writer.writeAll(ctlseqs.mouse_reset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -775,14 +709,12 @@ pub fn translateMouse(self: Vaxis, mouse: Mouse) Mouse {
|
||||||
pub fn loadImage(
|
pub fn loadImage(
|
||||||
self: *Vaxis,
|
self: *Vaxis,
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
|
writer: AnyWriter,
|
||||||
src: Image.Source,
|
src: Image.Source,
|
||||||
) !Image {
|
) !Image {
|
||||||
if (!self.caps.kitty_graphics) return error.NoGraphicsCapability;
|
if (!self.caps.kitty_graphics) return error.NoGraphicsCapability;
|
||||||
var tty = self.tty orelse return error.NoTTY;
|
|
||||||
defer self.next_img_id += 1;
|
defer self.next_img_id += 1;
|
||||||
|
|
||||||
const writer = tty.buffered_writer.writer();
|
|
||||||
|
|
||||||
var img = switch (src) {
|
var img = switch (src) {
|
||||||
.path => |path| try zigimg.Image.fromFilePath(alloc, path),
|
.path => |path| try zigimg.Image.fromFilePath(alloc, path),
|
||||||
.mem => |bytes| try zigimg.Image.fromMemory(alloc, bytes),
|
.mem => |bytes| try zigimg.Image.fromMemory(alloc, bytes),
|
||||||
|
@ -798,8 +730,7 @@ pub fn loadImage(
|
||||||
const id = self.next_img_id;
|
const id = self.next_img_id;
|
||||||
|
|
||||||
if (encoded.len < 4096) {
|
if (encoded.len < 4096) {
|
||||||
try std.fmt.format(
|
try writer.print(
|
||||||
writer,
|
|
||||||
"\x1b_Gf=100,i={d};{s}\x1b\\",
|
"\x1b_Gf=100,i={d};{s}\x1b\\",
|
||||||
.{
|
.{
|
||||||
id,
|
id,
|
||||||
|
@ -809,16 +740,14 @@ pub fn loadImage(
|
||||||
} else {
|
} else {
|
||||||
var n: usize = 4096;
|
var n: usize = 4096;
|
||||||
|
|
||||||
try std.fmt.format(
|
try writer.print(
|
||||||
writer,
|
|
||||||
"\x1b_Gf=100,i={d},m=1;{s}\x1b\\",
|
"\x1b_Gf=100,i={d},m=1;{s}\x1b\\",
|
||||||
.{ id, encoded[0..n] },
|
.{ id, encoded[0..n] },
|
||||||
);
|
);
|
||||||
while (n < encoded.len) : (n += 4096) {
|
while (n < encoded.len) : (n += 4096) {
|
||||||
const end: usize = @min(n + 4096, encoded.len);
|
const end: usize = @min(n + 4096, encoded.len);
|
||||||
const m: u2 = if (end == encoded.len) 0 else 1;
|
const m: u2 = if (end == encoded.len) 0 else 1;
|
||||||
try std.fmt.format(
|
try writer.print(
|
||||||
writer,
|
|
||||||
"\x1b_Gm={d};{s}\x1b\\",
|
"\x1b_Gm={d};{s}\x1b\\",
|
||||||
.{
|
.{
|
||||||
m,
|
m,
|
||||||
|
@ -827,7 +756,6 @@ pub fn loadImage(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try tty.buffered_writer.flush();
|
|
||||||
return .{
|
return .{
|
||||||
.id = id,
|
.id = id,
|
||||||
.width = img.width,
|
.width = img.width,
|
||||||
|
@ -836,56 +764,43 @@ pub fn loadImage(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// deletes an image from the terminal's memory
|
/// deletes an image from the terminal's memory
|
||||||
pub fn freeImage(self: Vaxis, id: u32) void {
|
pub fn freeImage(_: Vaxis, writer: AnyWriter, id: u32) void {
|
||||||
var tty = self.tty orelse return;
|
writer.print("\x1b_Ga=d,d=I,i={d};\x1b\\", .{id}) catch |err| {
|
||||||
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 });
|
log.err("couldn't delete image {d}: {}", .{ id, err });
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
tty.buffered_writer.flush() catch |err| {
|
|
||||||
log.err("couldn't flush writer: {}", .{err});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn copyToSystemClipboard(self: Vaxis, text: []const u8, encode_allocator: std.mem.Allocator) !void {
|
pub fn copyToSystemClipboard(_: Vaxis, writer: AnyWriter, text: []const u8, encode_allocator: std.mem.Allocator) !void {
|
||||||
var tty = self.tty orelse return;
|
|
||||||
const encoder = std.base64.standard.Encoder;
|
const encoder = std.base64.standard.Encoder;
|
||||||
const size = encoder.calcSize(text.len);
|
const size = encoder.calcSize(text.len);
|
||||||
const buf = try encode_allocator.alloc(u8, size);
|
const buf = try encode_allocator.alloc(u8, size);
|
||||||
const b64 = encoder.encode(buf, text);
|
const b64 = encoder.encode(buf, text);
|
||||||
defer encode_allocator.free(buf);
|
defer encode_allocator.free(buf);
|
||||||
try std.fmt.format(
|
try writer.print(
|
||||||
tty.buffered_writer.writer(),
|
|
||||||
ctlseqs.osc52_clipboard_copy,
|
ctlseqs.osc52_clipboard_copy,
|
||||||
.{b64},
|
.{b64},
|
||||||
);
|
);
|
||||||
try tty.flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn requestSystemClipboard(self: Vaxis) !void {
|
pub fn requestSystemClipboard(self: Vaxis, writer: AnyWriter) !void {
|
||||||
if (self.opts.system_clipboard_allocator == null) return error.NoClipboardAllocator;
|
if (self.opts.system_clipboard_allocator == null) return error.NoClipboardAllocator;
|
||||||
var tty = self.tty orelse return;
|
try writer.print(
|
||||||
try std.fmt.format(
|
|
||||||
tty.buffered_writer.writer(),
|
|
||||||
ctlseqs.osc52_clipboard_request,
|
ctlseqs.osc52_clipboard_request,
|
||||||
.{},
|
.{},
|
||||||
);
|
);
|
||||||
try tty.flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request a color report from the terminal. Note: not all terminals support
|
/// Request a color report from the terminal. Note: not all terminals support
|
||||||
/// reporting colors. It is always safe to try, but you may not receive a
|
/// reporting colors. It is always safe to try, but you may not receive a
|
||||||
/// response.
|
/// response.
|
||||||
pub fn queryColor(self: Vaxis, kind: Cell.Color.Kind) !void {
|
pub fn queryColor(_: Vaxis, writer: AnyWriter, kind: Cell.Color.Kind) !void {
|
||||||
var tty = self.tty orelse return;
|
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
.fg => _ = try tty.write(ctlseqs.osc10_query),
|
.fg => try writer.writeAll(ctlseqs.osc10_query),
|
||||||
.bg => _ = try tty.write(ctlseqs.osc11_query),
|
.bg => try writer.writeAll(ctlseqs.osc11_query),
|
||||||
.cursor => _ = try tty.write(ctlseqs.osc12_query),
|
.cursor => try writer.writeAll(ctlseqs.osc12_query),
|
||||||
.index => |idx| try tty.buffered_writer.writer().print(ctlseqs.osc4_query, .{idx}),
|
.index => |idx| try writer.print(ctlseqs.osc4_query, .{idx}),
|
||||||
}
|
}
|
||||||
try tty.flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Subscribe to color theme updates. A `color_scheme: Color.Scheme` tag must
|
/// Subscribe to color theme updates. A `color_scheme: Color.Scheme` tag must
|
||||||
|
@ -893,10 +808,12 @@ pub fn queryColor(self: Vaxis, kind: Cell.Color.Kind) !void {
|
||||||
/// capability. Support can be detected by checking the value of
|
/// capability. Support can be detected by checking the value of
|
||||||
/// vaxis.caps.color_scheme_updates. The initial scheme will be reported when
|
/// vaxis.caps.color_scheme_updates. The initial scheme will be reported when
|
||||||
/// subscribing.
|
/// subscribing.
|
||||||
pub fn subscribeToColorSchemeUpdates(self: Vaxis) !void {
|
pub fn subscribeToColorSchemeUpdates(self: Vaxis, writer: AnyWriter) !void {
|
||||||
var tty = self.tty orelse return;
|
try writer.writeAll(ctlseqs.color_scheme_request);
|
||||||
_ = try tty.write(ctlseqs.color_scheme_request);
|
try writer.writeAll(ctlseqs.color_scheme_set);
|
||||||
_ = try tty.write(ctlseqs.color_scheme_set);
|
|
||||||
try tty.flush();
|
|
||||||
self.state.color_scheme_updates = true;
|
self.state.color_scheme_updates = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deviceStatusReport(_: Vaxis, writer: AnyWriter) !void {
|
||||||
|
try writer.writeAll(ctlseqs.device_status_report);
|
||||||
|
}
|
||||||
|
|
16
src/main.zig
16
src/main.zig
|
@ -15,9 +15,10 @@ pub const Mouse = @import("Mouse.zig");
|
||||||
pub const Screen = @import("Screen.zig");
|
pub const Screen = @import("Screen.zig");
|
||||||
pub const AllocatingScreen = @import("InternalScreen.zig");
|
pub const AllocatingScreen = @import("InternalScreen.zig");
|
||||||
pub const Parser = @import("Parser.zig");
|
pub const Parser = @import("Parser.zig");
|
||||||
pub const Tty = @import("Tty.zig");
|
|
||||||
pub const Window = @import("Window.zig");
|
pub const Window = @import("Window.zig");
|
||||||
pub const Winsize = Tty.Winsize;
|
pub const tty = @import("tty.zig");
|
||||||
|
pub const Tty = tty.Tty;
|
||||||
|
pub const Winsize = tty.Winsize;
|
||||||
|
|
||||||
pub const widgets = @import("widgets.zig");
|
pub const widgets = @import("widgets.zig");
|
||||||
pub const gwidth = @import("gwidth.zig");
|
pub const gwidth = @import("gwidth.zig");
|
||||||
|
@ -36,8 +37,13 @@ pub const logo =
|
||||||
;
|
;
|
||||||
|
|
||||||
test {
|
test {
|
||||||
std.testing.refAllDecls(@This());
|
_ = @import("gwidth.zig");
|
||||||
std.testing.refAllDecls(widgets);
|
_ = @import("Cell.zig");
|
||||||
|
_ = @import("Key.zig");
|
||||||
_ = @import("Parser.zig");
|
_ = @import("Parser.zig");
|
||||||
_ = @import("Tty.zig");
|
_ = @import("Window.zig");
|
||||||
|
|
||||||
|
_ = @import("gwidth.zig");
|
||||||
|
_ = @import("queue.zig");
|
||||||
|
_ = @import("widgets/TextInput.zig");
|
||||||
}
|
}
|
||||||
|
|
181
src/tty.zig
Normal file
181
src/tty.zig
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const posix = std.posix;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.tty);
|
||||||
|
|
||||||
|
pub const Tty = switch (builtin.os.tag) {
|
||||||
|
.windows => @compileError("windows not supported, try wsl"),
|
||||||
|
else => PosixTty,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The size of the terminal screen
|
||||||
|
pub const Winsize = struct {
|
||||||
|
rows: usize,
|
||||||
|
cols: usize,
|
||||||
|
x_pixel: usize,
|
||||||
|
y_pixel: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// TTY implementation conforming to posix standards
|
||||||
|
pub const PosixTty = struct {
|
||||||
|
/// the original state of the terminal, prior to calling makeRaw
|
||||||
|
termios: posix.termios,
|
||||||
|
|
||||||
|
/// The file descriptor of the tty
|
||||||
|
fd: posix.fd_t,
|
||||||
|
|
||||||
|
pub const SignalHandler = struct {
|
||||||
|
context: *anyopaque,
|
||||||
|
callback: *const fn (context: *anyopaque) void,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// global signal handlers
|
||||||
|
var handlers: [8]SignalHandler = undefined;
|
||||||
|
var handler_mutex: std.Thread.Mutex = .{};
|
||||||
|
var handler_idx: usize = 0;
|
||||||
|
|
||||||
|
/// initializes a Tty instance by opening /dev/tty and "making it raw". A
|
||||||
|
/// signal handler is installed for SIGWINCH. No callbacks are installed, be
|
||||||
|
/// sure to register a callback when initializing the event loop
|
||||||
|
pub fn init() !PosixTty {
|
||||||
|
// Open our tty
|
||||||
|
const fd = try posix.open("/dev/tty", .{ .ACCMODE = .RDWR }, 0);
|
||||||
|
|
||||||
|
// Set the termios of the tty
|
||||||
|
const termios = try makeRaw(fd);
|
||||||
|
|
||||||
|
var act = posix.Sigaction{
|
||||||
|
.handler = .{ .handler = PosixTty.handleWinch },
|
||||||
|
.mask = switch (builtin.os.tag) {
|
||||||
|
.macos => 0,
|
||||||
|
.linux => posix.empty_sigset,
|
||||||
|
else => @compileError("os not supported"),
|
||||||
|
},
|
||||||
|
.flags = 0,
|
||||||
|
};
|
||||||
|
try posix.sigaction(posix.SIG.WINCH, &act, null);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.fd = fd,
|
||||||
|
.termios = termios,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// release resources associated with the Tty return it to its original state
|
||||||
|
pub fn deinit(self: PosixTty) void {
|
||||||
|
posix.tcsetattr(self.fd, .FLUSH, self.termios) catch |err| {
|
||||||
|
log.err("couldn't restore terminal: {}", .{err});
|
||||||
|
};
|
||||||
|
if (builtin.os.tag != .macos) // closing /dev/tty may block indefinitely on macos
|
||||||
|
posix.close(self.fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write bytes to the tty
|
||||||
|
pub fn write(self: *const PosixTty, bytes: []const u8) !usize {
|
||||||
|
return posix.write(self.fd, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn opaqueWrite(ptr: *const anyopaque, bytes: []const u8) !usize {
|
||||||
|
const self: *const PosixTty = @ptrCast(@alignCast(ptr));
|
||||||
|
return posix.write(self.fd, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn anyWriter(self: *const PosixTty) std.io.AnyWriter {
|
||||||
|
return .{
|
||||||
|
.context = self,
|
||||||
|
.writeFn = PosixTty.opaqueWrite,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(self: *const PosixTty, buf: []u8) !usize {
|
||||||
|
return posix.read(self.fd, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn opaqueRead(ptr: *const anyopaque, buf: []u8) !usize {
|
||||||
|
const self: *const PosixTty = @ptrCast(@alignCast(ptr));
|
||||||
|
return posix.read(self.fd, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn anyReader(self: *const PosixTty) std.io.AnyReader {
|
||||||
|
return .{
|
||||||
|
.context = self,
|
||||||
|
.readFn = PosixTty.opaqueRead,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Install a signal handler for winsize. A maximum of 8 handlers may be
|
||||||
|
/// installed
|
||||||
|
pub fn notifyWinsize(handler: SignalHandler) !void {
|
||||||
|
handler_mutex.lock();
|
||||||
|
defer handler_mutex.unlock();
|
||||||
|
if (handler_idx == handlers.len) return error.OutOfMemory;
|
||||||
|
handlers[handler_idx] = handler;
|
||||||
|
handler_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleWinch(_: c_int) callconv(.C) void {
|
||||||
|
handler_mutex.lock();
|
||||||
|
defer handler_mutex.unlock();
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < handler_idx) : (i += 1) {
|
||||||
|
const handler = handlers[i];
|
||||||
|
handler.callback(handler.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// makeRaw enters the raw state for the terminal.
|
||||||
|
pub fn makeRaw(fd: posix.fd_t) !posix.termios {
|
||||||
|
const state = try posix.tcgetattr(fd);
|
||||||
|
var raw = state;
|
||||||
|
// see termios(3)
|
||||||
|
raw.iflag.IGNBRK = false;
|
||||||
|
raw.iflag.BRKINT = false;
|
||||||
|
raw.iflag.PARMRK = false;
|
||||||
|
raw.iflag.ISTRIP = false;
|
||||||
|
raw.iflag.INLCR = false;
|
||||||
|
raw.iflag.IGNCR = false;
|
||||||
|
raw.iflag.ICRNL = false;
|
||||||
|
raw.iflag.IXON = false;
|
||||||
|
|
||||||
|
raw.oflag.OPOST = false;
|
||||||
|
|
||||||
|
raw.lflag.ECHO = false;
|
||||||
|
raw.lflag.ECHONL = false;
|
||||||
|
raw.lflag.ICANON = false;
|
||||||
|
raw.lflag.ISIG = false;
|
||||||
|
raw.lflag.IEXTEN = false;
|
||||||
|
|
||||||
|
raw.cflag.CSIZE = .CS8;
|
||||||
|
raw.cflag.PARENB = false;
|
||||||
|
|
||||||
|
raw.cc[@intFromEnum(posix.V.MIN)] = 1;
|
||||||
|
raw.cc[@intFromEnum(posix.V.TIME)] = 0;
|
||||||
|
try posix.tcsetattr(fd, .FLUSH, raw);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the window size from the kernel
|
||||||
|
pub fn getWinsize(fd: posix.fd_t) !Winsize {
|
||||||
|
var winsize = posix.winsize{
|
||||||
|
.ws_row = 0,
|
||||||
|
.ws_col = 0,
|
||||||
|
.ws_xpixel = 0,
|
||||||
|
.ws_ypixel = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const err = posix.system.ioctl(fd, posix.T.IOCGWINSZ, @intFromPtr(&winsize));
|
||||||
|
if (posix.errno(err) == .SUCCESS)
|
||||||
|
return Winsize{
|
||||||
|
.rows = winsize.ws_row,
|
||||||
|
.cols = winsize.ws_col,
|
||||||
|
.x_pixel = winsize.ws_xpixel,
|
||||||
|
.y_pixel = winsize.ws_ypixel,
|
||||||
|
};
|
||||||
|
return error.IoctlError;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bufferedWriter(self: *const PosixTty) std.io.BufferedWriter(4096, std.io.AnyWriter) {
|
||||||
|
return std.io.bufferedWriter(self.anyWriter());
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in a new issue