idk
This commit is contained in:
parent
23dd09bbf9
commit
84016c924b
4 changed files with 951 additions and 791 deletions
|
@ -42,6 +42,13 @@ pub const RGBA = struct {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn eql(a: *const RGBA, b: *const RGBA) bool {
|
||||
return a.r == b.r and
|
||||
a.g == b.g and
|
||||
a.b == b.b and
|
||||
a.a == b.a;
|
||||
}
|
||||
|
||||
pub fn blend(base: *const RGBA, add: *const RGBA) RGBA {
|
||||
var r: RGBA = undefined;
|
||||
r.a = 1 - (1 - add.a) * (1 - base.a);
|
||||
|
|
193
src/control.zig
Normal file
193
src/control.zig
Normal file
|
@ -0,0 +1,193 @@
|
|||
const std = @import("std");
|
||||
const mem = std.mem;
|
||||
const Terminal = @import("terminal.zig");
|
||||
const Cell = Terminal.Cell;
|
||||
const Buffer = Terminal.Buffer;
|
||||
const Color = @import("color.zig").RGBA;
|
||||
const Control = @This();
|
||||
|
||||
allocator: mem.Allocator,
|
||||
|
||||
parent: ?*Control,
|
||||
children: std.ArrayList(*Control),
|
||||
|
||||
rect: ?Rect = null, // TODO: maybe unionize this with .parent
|
||||
|
||||
left: u32,
|
||||
right: u32,
|
||||
top: u32,
|
||||
bottom: u32,
|
||||
|
||||
border_type: enum { line, bg, none } = .none,
|
||||
border_char: u8 = ' ',
|
||||
border_bg: Color = Color{ .r = 0.0, .g = 0.0, .b = 0.0 },
|
||||
border_fg: Color = Color{ .r = 1.0, .g = 1.0, .b = 1.0 },
|
||||
|
||||
content: std.ArrayList([]const u8),
|
||||
content_halign: enum { left, center, right } = .left,
|
||||
content_valign: enum { top, center, bottom } = .top,
|
||||
|
||||
_draw_buf: ?*Buffer = null,
|
||||
|
||||
pub fn init(allocator: mem.Allocator) Control {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.parent = null,
|
||||
.children = std.ArrayList(*Control).init(allocator),
|
||||
.left = 2,
|
||||
.right = 2,
|
||||
.top = 1,
|
||||
.bottom = 1,
|
||||
.content = std.ArrayList([]const u8).init(allocator),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Control) void {
|
||||
if (self._draw_buf) |b| b.deinit();
|
||||
for (self.children.items) |child| child.deinit();
|
||||
self.children.deinit();
|
||||
self.content.deinit();
|
||||
}
|
||||
|
||||
pub fn addChild(self: *Control, child: *Control) !void {
|
||||
std.debug.assert(child.parent == null);
|
||||
child.parent = self;
|
||||
try self.children.append(child);
|
||||
}
|
||||
|
||||
pub fn removeChild(self: *Control, child: *Control) !void {
|
||||
_ = self;
|
||||
_ = child;
|
||||
}
|
||||
|
||||
pub fn removeChildByIndex(self: *Control, child: usize) !void {
|
||||
_ = self;
|
||||
_ = child;
|
||||
}
|
||||
|
||||
pub fn moveChild(self: *Control, child: *Control, idx: usize) !void {
|
||||
_ = self;
|
||||
_ = child;
|
||||
_ = idx;
|
||||
}
|
||||
|
||||
pub fn moveChildByIndex(self: *Control, child: usize, idx: usize) !void {
|
||||
_ = self;
|
||||
_ = child;
|
||||
_ = idx;
|
||||
}
|
||||
|
||||
pub fn makeDirty(self: *Control) void {
|
||||
if (self._draw_buf) |b| b.deinit();
|
||||
self._draw_buf = null;
|
||||
for (self.children.items) |child| child.makeDirty();
|
||||
}
|
||||
|
||||
pub fn draw(self: *Control, term: *Terminal) !void {
|
||||
if (self._draw_buf) |draw_buf|
|
||||
try draw_buf.draw(term)
|
||||
else {
|
||||
const rect = self.getRect();
|
||||
var buffer = try Buffer.init(self.allocator, rect.h, rect.w);
|
||||
errdefer buffer.deinit();
|
||||
|
||||
switch (self.border_type) {
|
||||
.none => {
|
||||
var y: u32 = 0;
|
||||
// try term.cursorSet(rect.x, rect.y);
|
||||
// while (y < rect.h) : (y += 1) {
|
||||
// try term.cursorSet(rect.x, rect.y + y);
|
||||
// var x: u32 = 0;
|
||||
// while (x < rect.w) : (x += 1) try term.print(" ", .{});
|
||||
// }
|
||||
if (self.content.items.len > 0) {
|
||||
var lines = self.content.items.len;
|
||||
for (self.content.items) |line| lines += mem.count(u8, line, "\n");
|
||||
const tx = switch (self.content_halign) {
|
||||
.left => rect.x,
|
||||
.center => rect.x + (rect.w / 2) - (@as(u32, @intCast(self.content.items.len)) / 2),
|
||||
.right => rect.x + rect.w - (@as(u32, @intCast(self.content.items.len))),
|
||||
};
|
||||
var x = tx;
|
||||
y = switch (self.content_valign) {
|
||||
.top => rect.y,
|
||||
.center => rect.y + (rect.h / 2) - (@as(u32, @intCast(lines)) / 2),
|
||||
.bottom => rect.y + rect.h - 1 - @as(u32, @intCast(lines)),
|
||||
};
|
||||
|
||||
for (self.content.items) |line| {
|
||||
var split = mem.splitScalar(u8, line, '\n');
|
||||
while (split.next()) |l| {
|
||||
var spliit = mem.splitScalar(u8, l, '\r');
|
||||
while (spliit.next()) |s| for (s) |c| {
|
||||
// if (y * rect.w + x >= buffer.buf.len) break :blk;
|
||||
buffer.cell(x, y).char = c;
|
||||
// std.debug.print("x{d} y{d} {c}\r\n", .{ x, y, c });
|
||||
x += 1;
|
||||
if (x >= rect.w) {
|
||||
x = tx;
|
||||
y += 1;
|
||||
}
|
||||
};
|
||||
while (x < rect.w) : (x += 1) try term.print(" ", .{});
|
||||
y += 1;
|
||||
x = tx;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
.line => {
|
||||
try term.setFg(self.getBorderFgColor());
|
||||
try term.setBg(self.getBorderBgColor());
|
||||
var x: u32 = 0;
|
||||
var y: u32 = 0;
|
||||
try term.cursorSet(rect.x, rect.y);
|
||||
try term.print("┌", .{});
|
||||
while (x < rect.w - 2) : (x += 1) try term.print("─", .{});
|
||||
try term.print("┐", .{});
|
||||
y += 1;
|
||||
while (y < rect.h - 1) : (y += 1) {
|
||||
try term.cursorSet(rect.x, rect.y + y);
|
||||
try term.print("│", .{});
|
||||
try term.cursorSet(rect.x + rect.w - 1, rect.y + y);
|
||||
try term.print("│", .{});
|
||||
}
|
||||
x = 0;
|
||||
try term.cursorSet(rect.x, rect.y + rect.h - 1);
|
||||
try term.print("└", .{});
|
||||
while (x < rect.w - 2) : (x += 1) try term.print("─", .{});
|
||||
try term.print("┘", .{});
|
||||
},
|
||||
.bg => {},
|
||||
}
|
||||
self._draw_buf = buffer;
|
||||
}
|
||||
for (self.children.items) |child| try child.draw(term);
|
||||
}
|
||||
|
||||
fn getRect(self: *Control) Rect {
|
||||
var rect: Rect = undefined;
|
||||
if (self.parent) |parent| {
|
||||
const parent_rect = parent.getRect();
|
||||
rect.x = parent_rect.x + self.left;
|
||||
rect.y = parent_rect.y + self.top;
|
||||
rect.w = parent_rect.w + parent_rect.x - rect.x - self.right;
|
||||
rect.h = parent_rect.h + parent_rect.y - rect.y - self.bottom;
|
||||
} else return self.rect.?;
|
||||
return rect;
|
||||
}
|
||||
const Rect = struct { x: u32, y: u32, w: u32, h: u32 };
|
||||
|
||||
fn getBorderFgColor(self: *Control) Color {
|
||||
if (self.parent) |parent| {
|
||||
const parent_color = parent.getBorderFgColor();
|
||||
return parent_color.blend(&self.border_fg);
|
||||
} else return self.border_fg;
|
||||
}
|
||||
|
||||
fn getBorderBgColor(self: *Control) Color {
|
||||
if (self.parent) |parent| {
|
||||
const parent_color = parent.getBorderBgColor();
|
||||
return parent_color.blend(&self.border_bg);
|
||||
} else return self.border_bg;
|
||||
}
|
800
src/main.zig
800
src/main.zig
|
@ -1,11 +1,13 @@
|
|||
const std = @import("std");
|
||||
const os = std.os;
|
||||
const io = std.io;
|
||||
const fs = std.fs;
|
||||
const mem = std.mem;
|
||||
const heap = std.heap;
|
||||
const fmt = std.fmt;
|
||||
const posix = std.posix;
|
||||
pub const Terminal = @import("terminal.zig");
|
||||
pub const Control = @import("control.zig");
|
||||
pub const Color = @import("color.zig").RGBA;
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = heap.GeneralPurposeAllocator(.{}){};
|
||||
|
@ -17,26 +19,26 @@ pub fn main() !void {
|
|||
try term.clearScreen();
|
||||
try term.cursorHide();
|
||||
|
||||
var box1 = Terminal.Box.init(allocator);
|
||||
var box1 = Control.init(allocator);
|
||||
// defer box1.deinit();
|
||||
box1.border_bg = try Terminal.Color.init("#3ace37ff");
|
||||
box1.border_bg = try Color.init("#3ace37ff");
|
||||
box1.top = 0;
|
||||
box1.bottom = 0;
|
||||
box1.left = 0;
|
||||
box1.right = 0;
|
||||
|
||||
var box2 = Terminal.Box.init(allocator);
|
||||
var box2 = Control.init(allocator);
|
||||
// defer box2.deinit();
|
||||
box2.border_bg = try Terminal.Color.init("#000000c0");
|
||||
box2.border_bg = try Color.init("#000000c0");
|
||||
box2.content = "hi";
|
||||
box2.top = 1;
|
||||
box2.bottom = 3;
|
||||
box2.left = 20;
|
||||
box2.right = 2;
|
||||
|
||||
var box3 = Terminal.Box.init(allocator);
|
||||
var box3 = Control.init(allocator);
|
||||
// defer box3.deinit();
|
||||
box3.border_bg = try Terminal.Color.init("#48d5eaa0");
|
||||
box3.border_bg = try Color.init("#48d5eaa0");
|
||||
box3.content = "hello";
|
||||
box3.content_halign = .center;
|
||||
box3.content_valign = .center;
|
||||
|
@ -65,787 +67,3 @@ pub fn main() !void {
|
|||
std.time.sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
pub const Terminal = struct {
|
||||
tty: fs.File,
|
||||
buffered_writer: io.BufferedWriter(32768, fs.File.Writer),
|
||||
original_termios: os.linux.termios,
|
||||
info: Info,
|
||||
allocator: mem.Allocator,
|
||||
|
||||
box: Box,
|
||||
|
||||
events: std.ArrayList(Event),
|
||||
|
||||
pub fn init(allocator: mem.Allocator) !Terminal {
|
||||
var term = Terminal{
|
||||
.tty = try fs.openFileAbsolute("/dev/tty", .{ .mode = .read_write }),
|
||||
.buffered_writer = undefined,
|
||||
.original_termios = undefined,
|
||||
.info = try Info.init(allocator),
|
||||
.allocator = allocator,
|
||||
.box = Box.init(allocator),
|
||||
.events = std.ArrayList(Event).init(allocator),
|
||||
};
|
||||
errdefer term.tty.close();
|
||||
errdefer term.info.deinit();
|
||||
term.buffered_writer = io.BufferedWriter(32768, fs.File.Writer){ .unbuffered_writer = term.tty.writer() };
|
||||
try term.uncook();
|
||||
try attachSignalHandlers();
|
||||
try term.updateWinSize();
|
||||
try term.print("\x1b[>{d}u", .{KittyFlags.asInt(.{})});
|
||||
return term;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Terminal) void {
|
||||
self.print("\x1b[<u", .{}) catch @panic("failed to restore keyboard mode");
|
||||
self.events.deinit();
|
||||
self.box.deinit();
|
||||
self.cook() catch @panic("failed to restore termios");
|
||||
self.cursorShow() catch @panic("failed to unhide cursor");
|
||||
self.buffered_writer.flush() catch @panic("couldn't flush output buffer");
|
||||
self.tty.close();
|
||||
self.info.deinit();
|
||||
}
|
||||
|
||||
fn uncook(self: *Terminal) !void {
|
||||
self.original_termios = try posix.tcgetattr(self.tty.handle);
|
||||
var raw = self.original_termios;
|
||||
|
||||
raw.lflag.ECHO = false;
|
||||
raw.lflag.ICANON = false;
|
||||
// raw.lflag.ISIG = false;
|
||||
raw.lflag.ISIG = true;
|
||||
raw.lflag.IEXTEN = false;
|
||||
|
||||
raw.iflag.IXON = false;
|
||||
raw.iflag.ICRNL = false;
|
||||
raw.iflag.BRKINT = false;
|
||||
raw.iflag.INPCK = false;
|
||||
raw.iflag.ISTRIP = false;
|
||||
|
||||
raw.oflag.OPOST = false;
|
||||
|
||||
raw.cc[@intFromEnum(os.linux.V.TIME)] = 0;
|
||||
raw.cc[@intFromEnum(os.linux.V.MIN)] = 0;
|
||||
|
||||
try posix.tcsetattr(self.tty.handle, .FLUSH, raw);
|
||||
}
|
||||
|
||||
fn cook(self: *Terminal) !void {
|
||||
try posix.tcsetattr(self.tty.handle, .FLUSH, self.original_termios);
|
||||
}
|
||||
|
||||
fn attachSignalHandlers() !void {
|
||||
var act = posix.Sigaction{
|
||||
.handler = .{ .handler = &S.handlerFn },
|
||||
.mask = posix.empty_sigset,
|
||||
.flags = 0,
|
||||
};
|
||||
// posix.sigaction(posix.SIG.INT, &act, null);
|
||||
posix.sigaction(posix.SIG.USR1, &act, null);
|
||||
posix.sigaction(posix.SIG.USR2, &act, null);
|
||||
posix.sigaction(posix.SIG.WINCH, &act, null);
|
||||
}
|
||||
|
||||
pub fn getEvents(self: *Terminal) ![]Event {
|
||||
if (S.ev) |ev| {
|
||||
switch (ev) {
|
||||
Event.system => {
|
||||
switch (ev.system) {
|
||||
.winch => try self.updateWinSize(),
|
||||
else => try self.events.append(ev),
|
||||
}
|
||||
},
|
||||
else => try self.events.append(ev),
|
||||
}
|
||||
S.ev = null;
|
||||
}
|
||||
var pfd = [_]os.linux.pollfd{.{
|
||||
.fd = self.tty.handle,
|
||||
.events = os.linux.POLL.IN,
|
||||
.revents = 0,
|
||||
}};
|
||||
if (os.linux.poll(&pfd, 1, 0) > 0) {
|
||||
var key: [1]u8 = undefined;
|
||||
_ = try self.tty.reader().read(&key);
|
||||
if (key[0] != '\x1b') try self.events.append(Event{ .keyboard = .{ .code = key[0], .mods = Modifiers.init(1) } }) else {
|
||||
_ = try self.tty.reader().read(&key);
|
||||
if (key[0] == '[') {
|
||||
var code_list = std.ArrayList(u8).init(self.allocator);
|
||||
defer code_list.deinit();
|
||||
var delim: u8 = 0;
|
||||
while (code_list.items.len < 1024) {
|
||||
const c = self.tty.reader().readByte() catch |e| {
|
||||
if (e == error.EndOfStream) {
|
||||
std.debug.print("\n\r\n{s}\r\n", .{fmt.fmtSliceHexLower(code_list.items)});
|
||||
return error.UnknownControlCode;
|
||||
} else return e;
|
||||
};
|
||||
switch (c) {
|
||||
'u', '~', 'A', 'B', 'C', 'D', 'E', 'F', 'H', 'P', 'Q', 'S' => {
|
||||
delim = c;
|
||||
break;
|
||||
},
|
||||
else => try code_list.append(c),
|
||||
}
|
||||
}
|
||||
// std.debug.print("{s}{s}\r\n", .{ code_list.items, &[1]u8{delim} });
|
||||
|
||||
var keycode: u21 = 1;
|
||||
var alt_keycode: ?u21 = null;
|
||||
var modifiers: u9 = 1;
|
||||
var event_type: EventType = .press;
|
||||
var text: ?u21 = null;
|
||||
|
||||
var section: u8 = 0;
|
||||
var semicolon_it = mem.splitScalar(u8, code_list.items, ';');
|
||||
while (semicolon_it.next()) |semi| : (section += 1) try switch (section) {
|
||||
0 => if (semi.len > 0) {
|
||||
const colon = mem.indexOfScalar(u8, semi, ':');
|
||||
keycode = try fmt.parseInt(u21, if (colon) |c| semi[0..c] else semi, 10);
|
||||
if (colon) |c| alt_keycode = try fmt.parseInt(u21, semi[c + 1 ..], 10);
|
||||
},
|
||||
1 => if (semi.len > 0) {
|
||||
const colon = mem.indexOfScalar(u8, semi, ':');
|
||||
modifiers = try fmt.parseInt(u9, if (colon) |c| semi[0..c] else semi, 10);
|
||||
if (colon) |c| event_type = EventType.init(try fmt.parseInt(u2, semi[c + 1 ..], 10));
|
||||
},
|
||||
2 => text = try fmt.parseInt(u21, semi, 10),
|
||||
else => error.InvalidKittyEscape,
|
||||
};
|
||||
try self.events.append(Event{ .keyboard = .{
|
||||
.code = keycode,
|
||||
.altcode = alt_keycode,
|
||||
.mods = Modifiers.init(modifiers),
|
||||
.evtype = event_type,
|
||||
.text = text,
|
||||
.fnkey = FunctionalKey.init(keycode, delim),
|
||||
} });
|
||||
// std.debug.print("{any}\r\n", .{self.events.items[self.events.items.len - 1].keyboard});
|
||||
// std.debug.print("keycode: {}\r\nalt_keycode: {?}\r\nmodifiers: {}\r\n{}\r\ntext: {?}\r\n\nspecial: {?}\r\n\n", .{ keycode, alt_keycode, modifiers, event_type, text, FunctionalKey.init(keycode, delim) });
|
||||
}
|
||||
}
|
||||
}
|
||||
return try self.events.toOwnedSlice();
|
||||
}
|
||||
|
||||
const S = struct {
|
||||
var ev: ?Event = null;
|
||||
|
||||
fn handlerFn(sig: i32) callconv(.C) void {
|
||||
switch (sig) {
|
||||
// posix.SIG.INT => ev = Event{ .system = .int },
|
||||
posix.SIG.USR1 => ev = Event{ .system = .usr1 },
|
||||
posix.SIG.USR2 => ev = Event{ .system = .usr2 },
|
||||
posix.SIG.WINCH => ev = Event{ .system = .winch },
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Event = union(enum) {
|
||||
system: enum {
|
||||
// int,
|
||||
usr1,
|
||||
usr2,
|
||||
winch,
|
||||
},
|
||||
keyboard: struct {
|
||||
code: u21,
|
||||
altcode: ?u21 = null,
|
||||
mods: Modifiers,
|
||||
evtype: EventType = .press,
|
||||
text: ?u21 = null,
|
||||
fnkey: ?FunctionalKey = null,
|
||||
},
|
||||
};
|
||||
|
||||
pub const Modifiers = struct {
|
||||
shift: bool,
|
||||
alt: bool,
|
||||
ctrl: bool,
|
||||
super: bool,
|
||||
hyper: bool,
|
||||
meta: bool,
|
||||
caps_lock: bool,
|
||||
num_lock: bool,
|
||||
|
||||
pub fn init(bits: u9) Modifiers {
|
||||
const b = bits - 1;
|
||||
return .{
|
||||
.shift = (b & 0b1) > 0,
|
||||
.alt = (b & 0b10) > 0,
|
||||
.ctrl = (b & 0b100) > 0,
|
||||
.super = (b & 0b1000) > 0,
|
||||
.hyper = (b & 0b10000) > 0,
|
||||
.meta = (b & 0b100000) > 0,
|
||||
.caps_lock = (b & 0b1000000) > 0,
|
||||
.num_lock = (b & 0b10000000) > 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const EventType = enum(u2) {
|
||||
press = 1,
|
||||
repeat = 2,
|
||||
release = 3,
|
||||
|
||||
pub fn init(int: u2) EventType {
|
||||
return if (int == 0) .press else @enumFromInt(int);
|
||||
}
|
||||
};
|
||||
|
||||
pub const FunctionalKey = enum {
|
||||
escape,
|
||||
enter,
|
||||
tab,
|
||||
backspace,
|
||||
insert,
|
||||
delete,
|
||||
left,
|
||||
right,
|
||||
up,
|
||||
down,
|
||||
page_up,
|
||||
page_down,
|
||||
home,
|
||||
end,
|
||||
caps_lock,
|
||||
scroll_lock,
|
||||
num_lock,
|
||||
print_screen,
|
||||
pause,
|
||||
menu,
|
||||
f1,
|
||||
f2,
|
||||
f3,
|
||||
f4,
|
||||
f5,
|
||||
f6,
|
||||
f7,
|
||||
f8,
|
||||
f9,
|
||||
f10,
|
||||
f11,
|
||||
f12,
|
||||
f13,
|
||||
f14,
|
||||
f15,
|
||||
f16,
|
||||
f17,
|
||||
f18,
|
||||
f19,
|
||||
f20,
|
||||
f21,
|
||||
f22,
|
||||
f23,
|
||||
f24,
|
||||
f25,
|
||||
f26,
|
||||
f27,
|
||||
f28,
|
||||
f29,
|
||||
f30,
|
||||
f31,
|
||||
f32,
|
||||
f33,
|
||||
f34,
|
||||
f35,
|
||||
kp_0,
|
||||
kp_1,
|
||||
kp_2,
|
||||
kp_3,
|
||||
kp_4,
|
||||
kp_5,
|
||||
kp_6,
|
||||
kp_7,
|
||||
kp_8,
|
||||
kp_9,
|
||||
kp_decimal,
|
||||
kp_divide,
|
||||
kp_multiply,
|
||||
kp_subtract,
|
||||
kp_add,
|
||||
kp_enter,
|
||||
kp_equal,
|
||||
kp_separator,
|
||||
kp_left,
|
||||
kp_right,
|
||||
kp_up,
|
||||
kp_down,
|
||||
kp_page_up,
|
||||
kp_page_down,
|
||||
kp_home,
|
||||
kp_end,
|
||||
kp_insert,
|
||||
kp_delete,
|
||||
kp_begin,
|
||||
media_play,
|
||||
media_pause,
|
||||
media_play_pause,
|
||||
media_reverse,
|
||||
media_stop,
|
||||
media_fast_forward,
|
||||
media_rewind,
|
||||
media_track_next,
|
||||
media_track_previous,
|
||||
media_record,
|
||||
lower_volume,
|
||||
raise_volume,
|
||||
mute_volume,
|
||||
left_shift,
|
||||
left_control,
|
||||
left_alt,
|
||||
left_super,
|
||||
left_hyper,
|
||||
left_meta,
|
||||
right_shift,
|
||||
right_control,
|
||||
right_alt,
|
||||
right_super,
|
||||
right_hyper,
|
||||
right_meta,
|
||||
iso_level3_shift,
|
||||
iso_level5_shift,
|
||||
|
||||
fn init(keycode: u21, delim: u8) ?FunctionalKey {
|
||||
return switch (delim) {
|
||||
'~' => switch (keycode) {
|
||||
2 => .insert,
|
||||
3 => .delete,
|
||||
5 => .page_up,
|
||||
6 => .page_down,
|
||||
7 => .home,
|
||||
8 => .end,
|
||||
11 => .f1,
|
||||
12 => .f2,
|
||||
13 => .f3,
|
||||
14 => .f4,
|
||||
15 => .f5,
|
||||
17 => .f6,
|
||||
18 => .f7,
|
||||
19 => .f8,
|
||||
20 => .f9,
|
||||
21 => .f10,
|
||||
23 => .f11,
|
||||
24 => .f12,
|
||||
57427 => .kp_begin,
|
||||
else => null,
|
||||
},
|
||||
'A' => if (keycode == 1) .up else null,
|
||||
'B' => if (keycode == 1) .down else null,
|
||||
'C' => if (keycode == 1) .right else null,
|
||||
'D' => if (keycode == 1) .left else null,
|
||||
'E' => if (keycode == 1) .kp_begin else null,
|
||||
'F' => if (keycode == 1) .end else null,
|
||||
'H' => if (keycode == 1) .home else null,
|
||||
'P' => if (keycode == 1) .f1 else null,
|
||||
'Q' => if (keycode == 1) .f2 else null,
|
||||
'S' => if (keycode == 1) .f4 else null,
|
||||
'u' => switch (keycode) {
|
||||
27 => .escape,
|
||||
13 => .enter,
|
||||
9 => .tab,
|
||||
127 => .backspace,
|
||||
57358 => .caps_lock,
|
||||
57359 => .scroll_lock,
|
||||
57360 => .num_lock,
|
||||
57361 => .print_screen,
|
||||
57362 => .pause,
|
||||
57363 => .menu,
|
||||
57376 => .f13,
|
||||
57377 => .f14,
|
||||
57378 => .f15,
|
||||
57379 => .f16,
|
||||
57380 => .f17,
|
||||
57381 => .f18,
|
||||
57382 => .f19,
|
||||
57383 => .f20,
|
||||
57384 => .f21,
|
||||
57385 => .f22,
|
||||
57386 => .f23,
|
||||
57387 => .f24,
|
||||
57388 => .f25,
|
||||
57389 => .f26,
|
||||
57390 => .f27,
|
||||
57391 => .f28,
|
||||
57392 => .f29,
|
||||
57393 => .f30,
|
||||
57394 => .f31,
|
||||
57395 => .f32,
|
||||
57396 => .f33,
|
||||
57397 => .f34,
|
||||
57398 => .f35,
|
||||
57399 => .kp_0,
|
||||
57400 => .kp_1,
|
||||
57401 => .kp_2,
|
||||
57402 => .kp_3,
|
||||
57403 => .kp_4,
|
||||
57404 => .kp_5,
|
||||
57405 => .kp_6,
|
||||
57406 => .kp_7,
|
||||
57407 => .kp_8,
|
||||
57408 => .kp_9,
|
||||
57409 => .kp_decimal,
|
||||
57410 => .kp_divide,
|
||||
57411 => .kp_multiply,
|
||||
57412 => .kp_subtract,
|
||||
57413 => .kp_add,
|
||||
57414 => .kp_enter,
|
||||
57415 => .kp_equal,
|
||||
57416 => .kp_separator,
|
||||
57417 => .kp_left,
|
||||
57418 => .kp_right,
|
||||
57419 => .kp_up,
|
||||
57420 => .kp_down,
|
||||
57421 => .kp_page_up,
|
||||
57422 => .kp_page_down,
|
||||
57423 => .kp_home,
|
||||
57424 => .kp_end,
|
||||
57425 => .kp_insert,
|
||||
57426 => .kp_delete,
|
||||
57428 => .media_play,
|
||||
57429 => .media_pause,
|
||||
57430 => .media_play_pause,
|
||||
57431 => .media_reverse,
|
||||
57432 => .media_stop,
|
||||
57433 => .media_fast_forward,
|
||||
57434 => .media_rewind,
|
||||
57435 => .media_track_next,
|
||||
57436 => .media_track_previous,
|
||||
57437 => .media_record,
|
||||
57438 => .lower_volume,
|
||||
57439 => .raise_volume,
|
||||
57440 => .mute_volume,
|
||||
57441 => .left_shift,
|
||||
57442 => .left_control,
|
||||
57443 => .left_alt,
|
||||
57444 => .left_super,
|
||||
57445 => .left_hyper,
|
||||
57446 => .left_meta,
|
||||
57447 => .right_shift,
|
||||
57448 => .right_control,
|
||||
57449 => .right_alt,
|
||||
57450 => .right_super,
|
||||
57451 => .right_hyper,
|
||||
57452 => .right_meta,
|
||||
57453 => .iso_level3_shift,
|
||||
57454 => .iso_level5_shift,
|
||||
else => null,
|
||||
},
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
fn updateWinSize(self: *Terminal) !void {
|
||||
var sz: posix.winsize = undefined;
|
||||
const ret = os.linux.ioctl(0, os.linux.T.IOCGWINSZ, @intFromPtr(&sz));
|
||||
// std.debug.print("ret: {d}, {any}\r\n", .{ ret, sz });
|
||||
if (ret == 0) {
|
||||
self.box.rect = .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.h = @intCast(sz.row),
|
||||
.w = @intCast(sz.col),
|
||||
};
|
||||
self.box.makeDirty();
|
||||
} else unreachable; // TODO: handle else case
|
||||
}
|
||||
|
||||
pub fn draw(self: *Terminal) !void {
|
||||
if (self.box.dirty) try self.clearScreen();
|
||||
try self.box.draw(self);
|
||||
try self.buffered_writer.flush();
|
||||
}
|
||||
|
||||
pub fn print(self: *Terminal, comptime format: []const u8, args: anytype) !void {
|
||||
const formatted = try fmt.allocPrint(self.allocator, format, args);
|
||||
defer self.allocator.free(formatted);
|
||||
try self.buffered_writer.writer().writeAll(formatted);
|
||||
}
|
||||
|
||||
pub fn cursorShow(self: *Terminal) !void {
|
||||
try self.info.writeString(.cursor_visible, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn cursorHide(self: *Terminal) !void {
|
||||
try self.info.writeString(.cursor_invisible, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn cursorUp(self: *Terminal) !void {
|
||||
try self.info.writeString(.cursor_up, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn cursorDown(self: *Terminal) !void {
|
||||
try self.info.writeString(.cursor_down, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn cursorLeft(self: *Terminal) !void {
|
||||
try self.info.writeString(.cursor_left, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn cursorRight(self: *Terminal) !void {
|
||||
try self.info.writeString(.cursor_right, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn cursorSet(self: *Terminal, x: u32, y: u32) !void {
|
||||
try self.info.writeString(.cursor_address, self.buffered_writer.writer(), &[_]u32{ y, x });
|
||||
}
|
||||
|
||||
pub fn blinkOn(self: *Terminal) !void {
|
||||
try self.info.writeString(.enter_blink_mode, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn blinkOff(self: *Terminal) !void {
|
||||
try self.info.writeString(.exit_blink_mode, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn boldOn(self: *Terminal) !void {
|
||||
try self.info.writeString(.enter_bold_mode, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn boldOff(self: *Terminal) !void {
|
||||
try self.info.writeString(.exit_bold_mode, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn italicsOn(self: *Terminal) !void {
|
||||
try self.info.writeString(.enter_italics_mode, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn italicsOff(self: *Terminal) !void {
|
||||
try self.info.writeString(.exit_italics_mode, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn underlineOn(self: *Terminal) !void {
|
||||
try self.info.writeString(.enter_underline_mode, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn underlineOff(self: *Terminal) !void {
|
||||
try self.info.writeString(.exit_underline_mode, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn clearScreen(self: *Terminal) !void {
|
||||
try self.info.writeString(.clear_screen, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
// pub fn clearRegion(self: *Terminal, x: u32, y: u32, width: u32, height: u32) !void {
|
||||
// var row = y;
|
||||
// var i: u32 = 0;
|
||||
// while (i < height) {
|
||||
|
||||
// i += 1;
|
||||
// row += 1;
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn setFg(self: *Terminal, color: Color) !void {
|
||||
try self.info.writeString(.set_rgb_foreground, self.buffered_writer.writer(), &[_]u32{
|
||||
@intFromFloat(color.r * 0xff),
|
||||
@intFromFloat(color.g * 0xff),
|
||||
@intFromFloat(color.b * 0xff),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn setBg(self: *Terminal, color: Color) !void {
|
||||
try self.info.writeString(.set_rgb_background, self.buffered_writer.writer(), &[_]u32{
|
||||
@intFromFloat(color.r * 0xff),
|
||||
@intFromFloat(color.g * 0xff),
|
||||
@intFromFloat(color.b * 0xff),
|
||||
});
|
||||
}
|
||||
|
||||
pub const Info = @import("terminfo.zig");
|
||||
pub const Color = @import("color.zig").RGBA;
|
||||
|
||||
pub const Box = struct {
|
||||
parent: ?*Box,
|
||||
children: std.ArrayList(*Box),
|
||||
|
||||
rect: ?Rect = null, // TODO: maybe unionize this with .parent
|
||||
dirty: bool = true,
|
||||
|
||||
left: u32,
|
||||
right: u32,
|
||||
top: u32,
|
||||
bottom: u32,
|
||||
|
||||
border_type: enum { line, bg } = .bg,
|
||||
border_char: u8 = ' ',
|
||||
border_bg: Color = Color{ .r = 0.0, .g = 0.0, .b = 0.0 },
|
||||
border_fg: Color = Color{ .r = 1.0, .g = 1.0, .b = 1.0 },
|
||||
|
||||
content: []const u8 = "",
|
||||
content_halign: enum { left, center, right } = .left,
|
||||
content_valign: enum { top, center, bottom } = .top,
|
||||
|
||||
pub fn init(allocator: mem.Allocator) Box {
|
||||
return .{
|
||||
.parent = null,
|
||||
.children = std.ArrayList(*Box).init(allocator),
|
||||
.left = 2,
|
||||
.right = 2,
|
||||
.top = 1,
|
||||
.bottom = 1,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Box) void {
|
||||
for (self.children.items) |child| child.deinit();
|
||||
self.children.deinit();
|
||||
}
|
||||
|
||||
pub fn addChild(self: *Box, child: *Box) !void {
|
||||
std.debug.assert(child.parent == null);
|
||||
child.parent = self;
|
||||
try self.children.append(child);
|
||||
}
|
||||
|
||||
pub fn removeChild(self: *Box, child: *Box) !void {
|
||||
_ = self;
|
||||
_ = child;
|
||||
}
|
||||
|
||||
pub fn removeChildByIndex(self: *Box, child: usize) !void {
|
||||
_ = self;
|
||||
_ = child;
|
||||
}
|
||||
|
||||
pub fn moveChild(self: *Box, child: *Box, idx: usize) !void {
|
||||
_ = self;
|
||||
_ = child;
|
||||
_ = idx;
|
||||
}
|
||||
|
||||
pub fn moveChildByIndex(self: *Box, child: usize, idx: usize) !void {
|
||||
_ = self;
|
||||
_ = child;
|
||||
_ = idx;
|
||||
}
|
||||
|
||||
fn makeDirty(self: *Box) void {
|
||||
self.dirty = true;
|
||||
for (self.children.items) |child| child.makeDirty();
|
||||
}
|
||||
|
||||
pub fn draw(self: *Box, term: *Terminal) !void {
|
||||
if (self.dirty) {
|
||||
const rect = self.getRect();
|
||||
switch (self.border_type) {
|
||||
.line => {
|
||||
try term.setFg(self.getBorderFgColor());
|
||||
try term.setBg(self.getBorderBgColor());
|
||||
var x: u32 = 0;
|
||||
var y: u32 = 0;
|
||||
try term.cursorSet(rect.x, rect.y);
|
||||
try term.print("┌", .{});
|
||||
while (x < rect.w - 2) : (x += 1) try term.print("─", .{});
|
||||
try term.print("┐", .{});
|
||||
y += 1;
|
||||
while (y < rect.h - 1) : (y += 1) {
|
||||
try term.cursorSet(rect.x, rect.y + y);
|
||||
try term.print("│", .{});
|
||||
try term.cursorSet(rect.x + rect.w - 1, rect.y + y);
|
||||
try term.print("│", .{});
|
||||
}
|
||||
x = 0;
|
||||
try term.cursorSet(rect.x, rect.y + rect.h - 1);
|
||||
try term.print("└", .{});
|
||||
while (x < rect.w - 2) : (x += 1) try term.print("─", .{});
|
||||
try term.print("┘", .{});
|
||||
},
|
||||
.bg => {
|
||||
try term.setBg(self.getBorderBgColor());
|
||||
var y: u32 = 0;
|
||||
try term.cursorSet(rect.x, rect.y);
|
||||
while (y < rect.h) : (y += 1) {
|
||||
try term.cursorSet(rect.x, rect.y + y);
|
||||
var x: u32 = 0;
|
||||
while (x < rect.w) : (x += 1) try term.print(" ", .{});
|
||||
}
|
||||
if (self.content.len > 0) {
|
||||
const lines = mem.count(u8, self.content, "\n");
|
||||
const x = switch (self.content_halign) {
|
||||
.left => rect.x,
|
||||
.center => rect.x + (rect.w / 2) - (@as(u32, @intCast(self.content.len)) / 2),
|
||||
.right => rect.x + rect.w - (@as(u32, @intCast(self.content.len))),
|
||||
};
|
||||
y = switch (self.content_valign) {
|
||||
.top => rect.y,
|
||||
.center => rect.y + (rect.h / 2) - (@as(u32, @intCast(lines)) / 2),
|
||||
.bottom => rect.y + rect.h - 1 - @as(u32, @intCast(lines)),
|
||||
};
|
||||
try term.cursorSet(x, y);
|
||||
|
||||
var split = mem.splitScalar(u8, self.content, '\n');
|
||||
while (split.next()) |s| {
|
||||
var spliit = mem.splitScalar(u8, s, '\r');
|
||||
while (spliit.next()) |c| {
|
||||
try term.cursorSet(x, y);
|
||||
try term.print("{s}", .{c});
|
||||
}
|
||||
y += 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
self.dirty = false;
|
||||
}
|
||||
for (self.children.items) |child| try child.draw(term);
|
||||
}
|
||||
|
||||
fn getRect(self: *Box) Rect {
|
||||
var rect: Rect = undefined;
|
||||
if (self.parent) |parent| {
|
||||
const parent_rect = parent.getRect();
|
||||
rect.x = parent_rect.x + self.left;
|
||||
rect.y = parent_rect.y + self.top;
|
||||
rect.w = parent_rect.w + parent_rect.x - rect.x - self.right;
|
||||
rect.h = parent_rect.h + parent_rect.y - rect.y - self.bottom;
|
||||
} else return self.rect.?;
|
||||
return rect;
|
||||
}
|
||||
const Rect = struct { x: u32, y: u32, w: u32, h: u32 };
|
||||
|
||||
fn getBorderFgColor(self: *Box) Color {
|
||||
if (self.parent) |parent| {
|
||||
const parent_color = parent.getBorderFgColor();
|
||||
return parent_color.blend(&self.border_fg);
|
||||
} else return self.border_fg;
|
||||
}
|
||||
|
||||
fn getBorderBgColor(self: *Box) Color {
|
||||
if (self.parent) |parent| {
|
||||
const parent_color = parent.getBorderBgColor();
|
||||
return parent_color.blend(&self.border_bg);
|
||||
} else return self.border_bg;
|
||||
}
|
||||
};
|
||||
|
||||
const KittyFlags = struct {
|
||||
/// Disambiguate escape codes
|
||||
disambiguate: bool = true,
|
||||
/// Report event types
|
||||
event_types: bool = true,
|
||||
/// Report alternate keys
|
||||
alt_keys: bool = true,
|
||||
/// Report all keys as escape codes
|
||||
all_escapes: bool = true,
|
||||
/// Report associated text
|
||||
associated_text: bool = true,
|
||||
|
||||
fn asInt(flags: KittyFlags) u5 {
|
||||
const d: u5 = if (flags.disambiguate) 1 else 0;
|
||||
const e: u5 = if (flags.event_types) 1 else 0;
|
||||
const k: u5 = if (flags.alt_keys) 1 else 0;
|
||||
const a: u5 = if (flags.all_escapes) 1 else 0;
|
||||
const t: u5 = if (flags.associated_text) 1 else 0;
|
||||
return d << 0 |
|
||||
e << 1 |
|
||||
k << 2 |
|
||||
a << 3 |
|
||||
t << 4;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
742
src/terminal.zig
Normal file
742
src/terminal.zig
Normal file
|
@ -0,0 +1,742 @@
|
|||
const std = @import("std");
|
||||
const fs = std.fs;
|
||||
const io = std.io;
|
||||
const os = std.os;
|
||||
const mem = std.mem;
|
||||
const fmt = std.fmt;
|
||||
const posix = std.posix;
|
||||
const Control = @import("control.zig");
|
||||
const Terminal = @This();
|
||||
|
||||
tty: fs.File,
|
||||
buffered_writer: io.BufferedWriter(32768, fs.File.Writer),
|
||||
original_termios: os.linux.termios,
|
||||
info: Info,
|
||||
allocator: mem.Allocator,
|
||||
|
||||
root_control: Control,
|
||||
buffer: *Buffer,
|
||||
|
||||
events: std.ArrayList(Event),
|
||||
|
||||
pub fn init(allocator: mem.Allocator) !Terminal {
|
||||
var term = Terminal{
|
||||
.tty = try fs.openFileAbsolute("/dev/tty", .{ .mode = .read_write }),
|
||||
.buffered_writer = undefined,
|
||||
.original_termios = undefined,
|
||||
.info = try Info.init(allocator),
|
||||
.allocator = allocator,
|
||||
.root_control = Control.init(allocator),
|
||||
.buffer = undefined,
|
||||
.events = std.ArrayList(Event).init(allocator),
|
||||
};
|
||||
errdefer term.tty.close();
|
||||
errdefer term.info.deinit();
|
||||
term.buffered_writer = io.BufferedWriter(32768, fs.File.Writer){ .unbuffered_writer = term.tty.writer() };
|
||||
try term.uncook();
|
||||
try attachSignalHandlers();
|
||||
try term.updateWinSize();
|
||||
term.buffer = try Buffer.init(allocator, term.root_control.rect.?.h, term.root_control.rect.?.w);
|
||||
errdefer term.buffer.deinit();
|
||||
try term.print("\x1b[>{d}u", .{KittyFlags.asInt(.{})});
|
||||
return term;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Terminal) void {
|
||||
self.print("\x1b[<u", .{}) catch @panic("failed to restore keyboard mode");
|
||||
self.buffer.deinit();
|
||||
self.events.deinit();
|
||||
self.root_control.deinit();
|
||||
self.cook() catch @panic("failed to restore termios");
|
||||
self.cursorShow() catch @panic("failed to unhide cursor");
|
||||
self.buffered_writer.flush() catch @panic("couldn't flush output buffer");
|
||||
self.tty.close();
|
||||
self.info.deinit();
|
||||
}
|
||||
|
||||
fn uncook(self: *Terminal) !void {
|
||||
self.original_termios = try posix.tcgetattr(self.tty.handle);
|
||||
var raw = self.original_termios;
|
||||
|
||||
raw.lflag.ECHO = false;
|
||||
raw.lflag.ICANON = false;
|
||||
// raw.lflag.ISIG = false;
|
||||
raw.lflag.ISIG = true;
|
||||
raw.lflag.IEXTEN = false;
|
||||
|
||||
raw.iflag.IXON = false;
|
||||
raw.iflag.ICRNL = false;
|
||||
raw.iflag.BRKINT = false;
|
||||
raw.iflag.INPCK = false;
|
||||
raw.iflag.ISTRIP = false;
|
||||
|
||||
raw.oflag.OPOST = false;
|
||||
|
||||
raw.cc[@intFromEnum(os.linux.V.TIME)] = 0;
|
||||
raw.cc[@intFromEnum(os.linux.V.MIN)] = 0;
|
||||
|
||||
try posix.tcsetattr(self.tty.handle, .FLUSH, raw);
|
||||
}
|
||||
|
||||
fn cook(self: *Terminal) !void {
|
||||
try posix.tcsetattr(self.tty.handle, .FLUSH, self.original_termios);
|
||||
}
|
||||
|
||||
fn attachSignalHandlers() !void {
|
||||
var act = posix.Sigaction{
|
||||
.handler = .{ .handler = &S.handlerFn },
|
||||
.mask = posix.empty_sigset,
|
||||
.flags = 0,
|
||||
};
|
||||
// posix.sigaction(posix.SIG.INT, &act, null);
|
||||
posix.sigaction(posix.SIG.USR1, &act, null);
|
||||
posix.sigaction(posix.SIG.USR2, &act, null);
|
||||
posix.sigaction(posix.SIG.WINCH, &act, null);
|
||||
}
|
||||
|
||||
pub fn getEvents(self: *Terminal) ![]Event {
|
||||
if (S.ev) |ev| {
|
||||
switch (ev) {
|
||||
Event.system => {
|
||||
switch (ev.system) {
|
||||
.winch => try self.updateWinSize(),
|
||||
else => try self.events.append(ev),
|
||||
}
|
||||
},
|
||||
else => try self.events.append(ev),
|
||||
}
|
||||
S.ev = null;
|
||||
}
|
||||
var pfd = [_]os.linux.pollfd{.{
|
||||
.fd = self.tty.handle,
|
||||
.events = os.linux.POLL.IN,
|
||||
.revents = 0,
|
||||
}};
|
||||
if (os.linux.poll(&pfd, 1, 0) > 0) {
|
||||
var key: [1]u8 = undefined;
|
||||
_ = try self.tty.reader().read(&key);
|
||||
if (key[0] != '\x1b') try self.events.append(Event{ .keyboard = .{ .code = key[0], .mods = Modifiers.init(1) } }) else {
|
||||
_ = try self.tty.reader().read(&key);
|
||||
if (key[0] == '[') {
|
||||
var code_list = std.ArrayList(u8).init(self.allocator);
|
||||
defer code_list.deinit();
|
||||
var delim: u8 = 0;
|
||||
while (code_list.items.len < 1024) {
|
||||
const c = self.tty.reader().readByte() catch |e| {
|
||||
if (e == error.EndOfStream) {
|
||||
std.debug.print("\n\r\n{s}\r\n", .{fmt.fmtSliceHexLower(code_list.items)});
|
||||
return error.UnknownControlCode;
|
||||
} else return e;
|
||||
};
|
||||
switch (c) {
|
||||
'u', '~', 'A', 'B', 'C', 'D', 'E', 'F', 'H', 'P', 'Q', 'S' => {
|
||||
delim = c;
|
||||
break;
|
||||
},
|
||||
else => try code_list.append(c),
|
||||
}
|
||||
}
|
||||
// std.debug.print("{s}{s}\r\n", .{ code_list.items, &[1]u8{delim} });
|
||||
|
||||
var keycode: u21 = 1;
|
||||
var alt_keycode: ?u21 = null;
|
||||
var modifiers: u9 = 1;
|
||||
var event_type: EventType = .press;
|
||||
var text: ?u21 = null;
|
||||
|
||||
var section: u8 = 0;
|
||||
var semicolon_it = mem.splitScalar(u8, code_list.items, ';');
|
||||
while (semicolon_it.next()) |semi| : (section += 1) try switch (section) {
|
||||
0 => if (semi.len > 0) {
|
||||
const colon = mem.indexOfScalar(u8, semi, ':');
|
||||
keycode = try fmt.parseInt(u21, if (colon) |c| semi[0..c] else semi, 10);
|
||||
if (colon) |c| alt_keycode = try fmt.parseInt(u21, semi[c + 1 ..], 10);
|
||||
},
|
||||
1 => if (semi.len > 0) {
|
||||
const colon = mem.indexOfScalar(u8, semi, ':');
|
||||
modifiers = try fmt.parseInt(u9, if (colon) |c| semi[0..c] else semi, 10);
|
||||
if (colon) |c| event_type = EventType.init(try fmt.parseInt(u2, semi[c + 1 ..], 10));
|
||||
},
|
||||
2 => text = try fmt.parseInt(u21, semi, 10),
|
||||
else => error.InvalidKittyEscape,
|
||||
};
|
||||
try self.events.append(Event{ .keyboard = .{
|
||||
.code = keycode,
|
||||
.altcode = alt_keycode,
|
||||
.mods = Modifiers.init(modifiers),
|
||||
.evtype = event_type,
|
||||
.text = text,
|
||||
.fnkey = FunctionalKey.init(keycode, delim),
|
||||
} });
|
||||
// std.debug.print("{any}\r\n", .{self.events.items[self.events.items.len - 1].keyboard});
|
||||
// std.debug.print("keycode: {}\r\nalt_keycode: {?}\r\nmodifiers: {}\r\n{}\r\ntext: {?}\r\n\nspecial: {?}\r\n\n", .{ keycode, alt_keycode, modifiers, event_type, text, FunctionalKey.init(keycode, delim) });
|
||||
}
|
||||
}
|
||||
}
|
||||
return try self.events.toOwnedSlice();
|
||||
}
|
||||
|
||||
const S = struct {
|
||||
var ev: ?Event = null;
|
||||
|
||||
fn handlerFn(sig: i32) callconv(.C) void {
|
||||
switch (sig) {
|
||||
// posix.SIG.INT => ev = Event{ .system = .int },
|
||||
posix.SIG.USR1 => ev = Event{ .system = .usr1 },
|
||||
posix.SIG.USR2 => ev = Event{ .system = .usr2 },
|
||||
posix.SIG.WINCH => ev = Event{ .system = .winch },
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Event = union(enum) {
|
||||
system: enum {
|
||||
// int,
|
||||
usr1,
|
||||
usr2,
|
||||
winch,
|
||||
},
|
||||
keyboard: struct {
|
||||
code: u21,
|
||||
altcode: ?u21 = null,
|
||||
mods: Modifiers,
|
||||
evtype: EventType = .press,
|
||||
text: ?u21 = null,
|
||||
fnkey: ?FunctionalKey = null,
|
||||
},
|
||||
};
|
||||
|
||||
pub const Modifiers = struct {
|
||||
shift: bool,
|
||||
alt: bool,
|
||||
ctrl: bool,
|
||||
super: bool,
|
||||
hyper: bool,
|
||||
meta: bool,
|
||||
caps_lock: bool,
|
||||
num_lock: bool,
|
||||
|
||||
pub fn init(bits: u9) Modifiers {
|
||||
const b = bits - 1;
|
||||
return .{
|
||||
.shift = (b & 0b1) > 0,
|
||||
.alt = (b & 0b10) > 0,
|
||||
.ctrl = (b & 0b100) > 0,
|
||||
.super = (b & 0b1000) > 0,
|
||||
.hyper = (b & 0b10000) > 0,
|
||||
.meta = (b & 0b100000) > 0,
|
||||
.caps_lock = (b & 0b1000000) > 0,
|
||||
.num_lock = (b & 0b10000000) > 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const EventType = enum(u2) {
|
||||
press = 1,
|
||||
repeat = 2,
|
||||
release = 3,
|
||||
|
||||
pub fn init(int: u2) EventType {
|
||||
return if (int == 0) .press else @enumFromInt(int);
|
||||
}
|
||||
};
|
||||
|
||||
fn updateWinSize(self: *Terminal) !void {
|
||||
var sz: posix.winsize = undefined;
|
||||
const ret = os.linux.ioctl(0, os.linux.T.IOCGWINSZ, @intFromPtr(&sz));
|
||||
// std.debug.print("ret: {d}, {any}\r\n", .{ ret, sz });
|
||||
if (ret == 0) {
|
||||
self.root_control.rect = .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.h = @intCast(sz.row),
|
||||
.w = @intCast(sz.col),
|
||||
};
|
||||
self.root_control.makeDirty();
|
||||
} else unreachable; // TODO: handle else case
|
||||
}
|
||||
|
||||
pub fn draw(self: *Terminal) !void {
|
||||
// if (self.root_control.buffer) try self.clearScreen();
|
||||
try self.root_control.draw(self);
|
||||
try self.buffered_writer.flush();
|
||||
}
|
||||
|
||||
pub fn print(self: *Terminal, comptime format: []const u8, args: anytype) !void {
|
||||
const formatted = try fmt.allocPrint(self.allocator, format, args);
|
||||
defer self.allocator.free(formatted);
|
||||
try self.buffered_writer.writer().writeAll(formatted);
|
||||
}
|
||||
|
||||
pub fn cursorShow(self: *Terminal) !void {
|
||||
try self.info.writeString(.cursor_visible, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn cursorHide(self: *Terminal) !void {
|
||||
try self.info.writeString(.cursor_invisible, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn cursorUp(self: *Terminal) !void {
|
||||
try self.info.writeString(.cursor_up, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn cursorDown(self: *Terminal) !void {
|
||||
try self.info.writeString(.cursor_down, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn cursorLeft(self: *Terminal) !void {
|
||||
try self.info.writeString(.cursor_left, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn cursorRight(self: *Terminal) !void {
|
||||
try self.info.writeString(.cursor_right, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn cursorSet(self: *Terminal, x: u32, y: u32) !void {
|
||||
try self.info.writeString(.cursor_address, self.buffered_writer.writer(), &[_]u32{ y, x });
|
||||
}
|
||||
|
||||
pub fn blinkOn(self: *Terminal) !void {
|
||||
try self.info.writeString(.enter_blink_mode, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn blinkOff(self: *Terminal) !void {
|
||||
try self.info.writeString(.exit_blink_mode, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn boldOn(self: *Terminal) !void {
|
||||
try self.info.writeString(.enter_bold_mode, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn boldOff(self: *Terminal) !void {
|
||||
try self.info.writeString(.exit_bold_mode, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn italicsOn(self: *Terminal) !void {
|
||||
try self.info.writeString(.enter_italics_mode, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn italicsOff(self: *Terminal) !void {
|
||||
try self.info.writeString(.exit_italics_mode, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn underlineOn(self: *Terminal) !void {
|
||||
try self.info.writeString(.enter_underline_mode, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn underlineOff(self: *Terminal) !void {
|
||||
try self.info.writeString(.exit_underline_mode, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
pub fn clearScreen(self: *Terminal) !void {
|
||||
try self.info.writeString(.clear_screen, self.buffered_writer.writer(), &[_]u32{});
|
||||
}
|
||||
|
||||
// pub fn clearRegion(self: *Terminal, x: u32, y: u32, width: u32, height: u32) !void {
|
||||
// var row = y;
|
||||
// var i: u32 = 0;
|
||||
// while (i < height) {
|
||||
|
||||
// i += 1;
|
||||
// row += 1;
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn setFg(self: *Terminal, color: Color) !void {
|
||||
try self.info.writeString(.set_rgb_foreground, self.buffered_writer.writer(), &[_]u32{
|
||||
@intFromFloat(color.r * 0xff),
|
||||
@intFromFloat(color.g * 0xff),
|
||||
@intFromFloat(color.b * 0xff),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn setBg(self: *Terminal, color: Color) !void {
|
||||
try self.info.writeString(.set_rgb_background, self.buffered_writer.writer(), &[_]u32{
|
||||
@intFromFloat(color.r * 0xff),
|
||||
@intFromFloat(color.g * 0xff),
|
||||
@intFromFloat(color.b * 0xff),
|
||||
});
|
||||
}
|
||||
|
||||
const Info = @import("terminfo.zig");
|
||||
const Color = @import("color.zig").RGBA;
|
||||
|
||||
const KittyFlags = struct {
|
||||
/// Disambiguate escape codes
|
||||
disambiguate: bool = true,
|
||||
/// Report event types
|
||||
event_types: bool = true,
|
||||
/// Report alternate keys
|
||||
alt_keys: bool = true,
|
||||
/// Report all keys as escape codes
|
||||
all_escapes: bool = true,
|
||||
/// Report associated text
|
||||
associated_text: bool = true,
|
||||
|
||||
fn asInt(flags: KittyFlags) u5 {
|
||||
const d: u5 = if (flags.disambiguate) 1 else 0;
|
||||
const e: u5 = if (flags.event_types) 1 else 0;
|
||||
const k: u5 = if (flags.alt_keys) 1 else 0;
|
||||
const a: u5 = if (flags.all_escapes) 1 else 0;
|
||||
const t: u5 = if (flags.associated_text) 1 else 0;
|
||||
return d << 0 |
|
||||
e << 1 |
|
||||
k << 2 |
|
||||
a << 3 |
|
||||
t << 4;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Buffer = struct {
|
||||
// allocator: mem.Allocator,
|
||||
buf: std.ArrayList(Cell),
|
||||
height: u32,
|
||||
width: u32,
|
||||
|
||||
pub fn init(allocator: mem.Allocator, height: u32, width: u32) !*Buffer {
|
||||
var buffer = try allocator.create(Buffer);
|
||||
errdefer allocator.destroy(buffer);
|
||||
// buffer.buf = try allocator.alloc(Cell, height * width);
|
||||
// errdefer allocator.free(buffer.buf);
|
||||
// var buffer: Buffer = undefined;
|
||||
// buffer.allocator = allocator;
|
||||
buffer.buf = std.ArrayList(Cell).init(allocator);
|
||||
try buffer.resize(height, width);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Buffer) void {
|
||||
// self.allocator.free(self.buf);
|
||||
self.buf.deinit();
|
||||
// self.allocator.destroy(self);
|
||||
}
|
||||
|
||||
pub fn resize(self: *Buffer, h: u32, w: u32) !void {
|
||||
self.buf.clearAndFree();
|
||||
self.height = h;
|
||||
self.width = w;
|
||||
_ = try self.buf.addManyAsSlice(h * w);
|
||||
}
|
||||
|
||||
pub fn cell(self: *Buffer, x: u32, y: u32) *Cell {
|
||||
return &self.buf.items[self.width * y + x];
|
||||
}
|
||||
|
||||
pub fn draw(self: *Buffer, term: *Terminal) !void {
|
||||
var x: u32 = 0; // target x
|
||||
var y: u32 = 0; // target y
|
||||
var current_cell = term.buffer.cell(x, y);
|
||||
var line = try self.allocator.alloc(u21, self.width);
|
||||
defer self.allocator.free(line);
|
||||
for (self.buf.items, 0..) |target_cell, i| {
|
||||
if (x >= self.width) {}
|
||||
var dirty = false;
|
||||
if (current_cell.char != target_cell.char) {
|
||||
if (!dirty) try term.cursorSet(x, y);
|
||||
if (!dirty) {
|
||||
try self.reprintLine(x, y);
|
||||
}
|
||||
dirty = true;
|
||||
try term.print("{c}", .{target_cell.char});
|
||||
}
|
||||
// if (current_cell.italic != target_cell.italic) {
|
||||
// if (!dirty) try term.cursorSet(tx, ty);
|
||||
// dirty = true;
|
||||
// try term.setFg(target_cell.fg);
|
||||
// }
|
||||
// if (current_cell.bold != target_cell.bold) {
|
||||
// if (!dirty) try term.cursorSet(tx, ty);
|
||||
// dirty = true;
|
||||
// try term.setFg(target_cell.fg);
|
||||
// }
|
||||
// if (current_cell.underline != target_cell.underline) {
|
||||
// if (!dirty) try term.cursorSet(tx, ty);
|
||||
// dirty = true;
|
||||
// try term.setFg(target_cell.fg);
|
||||
// }
|
||||
if (!current_cell.fg.eql(&target_cell.fg)) {
|
||||
if (!dirty) try term.cursorSet(x, y);
|
||||
dirty = true;
|
||||
try term.setFg(target_cell.fg);
|
||||
}
|
||||
if (!current_cell.bg.eql(&target_cell.bg)) {
|
||||
if (!dirty) try term.cursorSet(x, y);
|
||||
dirty = true;
|
||||
try term.setFg(target_cell.bg);
|
||||
}
|
||||
|
||||
if (i >= self.buf.items.len - 1) break;
|
||||
x += 1;
|
||||
if (x >= self.width) {
|
||||
y += 1;
|
||||
x = 0;
|
||||
}
|
||||
current_cell = term.buffer.cell(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
fn reprintLine(self: *Buffer, term: *Terminal, x: u32, y: u32) !void {
|
||||
const half = self.width / 2;
|
||||
try term.cursorSet(x, y);
|
||||
if (x <= half) {
|
||||
try term.print("\x1b[1K", .{});
|
||||
try term.print("{u}", .{self.buf.items[self.width * y + x]});
|
||||
} else {
|
||||
try term.print("\x1b[0K", .{});
|
||||
try term.print("{u}", .{self.buf.items[self.width * y + x]});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Cell = struct {
|
||||
fg: Color,
|
||||
bg: Color,
|
||||
char: u8,
|
||||
italic: bool,
|
||||
bold: bool,
|
||||
underline: bool,
|
||||
};
|
||||
|
||||
pub const FunctionalKey = enum {
|
||||
escape,
|
||||
enter,
|
||||
tab,
|
||||
backspace,
|
||||
insert,
|
||||
delete,
|
||||
left,
|
||||
right,
|
||||
up,
|
||||
down,
|
||||
page_up,
|
||||
page_down,
|
||||
home,
|
||||
end,
|
||||
caps_lock,
|
||||
scroll_lock,
|
||||
num_lock,
|
||||
print_screen,
|
||||
pause,
|
||||
menu,
|
||||
f1,
|
||||
f2,
|
||||
f3,
|
||||
f4,
|
||||
f5,
|
||||
f6,
|
||||
f7,
|
||||
f8,
|
||||
f9,
|
||||
f10,
|
||||
f11,
|
||||
f12,
|
||||
f13,
|
||||
f14,
|
||||
f15,
|
||||
f16,
|
||||
f17,
|
||||
f18,
|
||||
f19,
|
||||
f20,
|
||||
f21,
|
||||
f22,
|
||||
f23,
|
||||
f24,
|
||||
f25,
|
||||
f26,
|
||||
f27,
|
||||
f28,
|
||||
f29,
|
||||
f30,
|
||||
f31,
|
||||
f32,
|
||||
f33,
|
||||
f34,
|
||||
f35,
|
||||
kp_0,
|
||||
kp_1,
|
||||
kp_2,
|
||||
kp_3,
|
||||
kp_4,
|
||||
kp_5,
|
||||
kp_6,
|
||||
kp_7,
|
||||
kp_8,
|
||||
kp_9,
|
||||
kp_decimal,
|
||||
kp_divide,
|
||||
kp_multiply,
|
||||
kp_subtract,
|
||||
kp_add,
|
||||
kp_enter,
|
||||
kp_equal,
|
||||
kp_separator,
|
||||
kp_left,
|
||||
kp_right,
|
||||
kp_up,
|
||||
kp_down,
|
||||
kp_page_up,
|
||||
kp_page_down,
|
||||
kp_home,
|
||||
kp_end,
|
||||
kp_insert,
|
||||
kp_delete,
|
||||
kp_begin,
|
||||
media_play,
|
||||
media_pause,
|
||||
media_play_pause,
|
||||
media_reverse,
|
||||
media_stop,
|
||||
media_fast_forward,
|
||||
media_rewind,
|
||||
media_track_next,
|
||||
media_track_previous,
|
||||
media_record,
|
||||
lower_volume,
|
||||
raise_volume,
|
||||
mute_volume,
|
||||
left_shift,
|
||||
left_control,
|
||||
left_alt,
|
||||
left_super,
|
||||
left_hyper,
|
||||
left_meta,
|
||||
right_shift,
|
||||
right_control,
|
||||
right_alt,
|
||||
right_super,
|
||||
right_hyper,
|
||||
right_meta,
|
||||
iso_level3_shift,
|
||||
iso_level5_shift,
|
||||
|
||||
fn init(keycode: u21, delim: u8) ?FunctionalKey {
|
||||
return switch (delim) {
|
||||
'~' => switch (keycode) {
|
||||
2 => .insert,
|
||||
3 => .delete,
|
||||
5 => .page_up,
|
||||
6 => .page_down,
|
||||
7 => .home,
|
||||
8 => .end,
|
||||
11 => .f1,
|
||||
12 => .f2,
|
||||
13 => .f3,
|
||||
14 => .f4,
|
||||
15 => .f5,
|
||||
17 => .f6,
|
||||
18 => .f7,
|
||||
19 => .f8,
|
||||
20 => .f9,
|
||||
21 => .f10,
|
||||
23 => .f11,
|
||||
24 => .f12,
|
||||
57427 => .kp_begin,
|
||||
else => null,
|
||||
},
|
||||
'A' => if (keycode == 1) .up else null,
|
||||
'B' => if (keycode == 1) .down else null,
|
||||
'C' => if (keycode == 1) .right else null,
|
||||
'D' => if (keycode == 1) .left else null,
|
||||
'E' => if (keycode == 1) .kp_begin else null,
|
||||
'F' => if (keycode == 1) .end else null,
|
||||
'H' => if (keycode == 1) .home else null,
|
||||
'P' => if (keycode == 1) .f1 else null,
|
||||
'Q' => if (keycode == 1) .f2 else null,
|
||||
'S' => if (keycode == 1) .f4 else null,
|
||||
'u' => switch (keycode) {
|
||||
27 => .escape,
|
||||
13 => .enter,
|
||||
9 => .tab,
|
||||
127 => .backspace,
|
||||
57358 => .caps_lock,
|
||||
57359 => .scroll_lock,
|
||||
57360 => .num_lock,
|
||||
57361 => .print_screen,
|
||||
57362 => .pause,
|
||||
57363 => .menu,
|
||||
57376 => .f13,
|
||||
57377 => .f14,
|
||||
57378 => .f15,
|
||||
57379 => .f16,
|
||||
57380 => .f17,
|
||||
57381 => .f18,
|
||||
57382 => .f19,
|
||||
57383 => .f20,
|
||||
57384 => .f21,
|
||||
57385 => .f22,
|
||||
57386 => .f23,
|
||||
57387 => .f24,
|
||||
57388 => .f25,
|
||||
57389 => .f26,
|
||||
57390 => .f27,
|
||||
57391 => .f28,
|
||||
57392 => .f29,
|
||||
57393 => .f30,
|
||||
57394 => .f31,
|
||||
57395 => .f32,
|
||||
57396 => .f33,
|
||||
57397 => .f34,
|
||||
57398 => .f35,
|
||||
57399 => .kp_0,
|
||||
57400 => .kp_1,
|
||||
57401 => .kp_2,
|
||||
57402 => .kp_3,
|
||||
57403 => .kp_4,
|
||||
57404 => .kp_5,
|
||||
57405 => .kp_6,
|
||||
57406 => .kp_7,
|
||||
57407 => .kp_8,
|
||||
57408 => .kp_9,
|
||||
57409 => .kp_decimal,
|
||||
57410 => .kp_divide,
|
||||
57411 => .kp_multiply,
|
||||
57412 => .kp_subtract,
|
||||
57413 => .kp_add,
|
||||
57414 => .kp_enter,
|
||||
57415 => .kp_equal,
|
||||
57416 => .kp_separator,
|
||||
57417 => .kp_left,
|
||||
57418 => .kp_right,
|
||||
57419 => .kp_up,
|
||||
57420 => .kp_down,
|
||||
57421 => .kp_page_up,
|
||||
57422 => .kp_page_down,
|
||||
57423 => .kp_home,
|
||||
57424 => .kp_end,
|
||||
57425 => .kp_insert,
|
||||
57426 => .kp_delete,
|
||||
57428 => .media_play,
|
||||
57429 => .media_pause,
|
||||
57430 => .media_play_pause,
|
||||
57431 => .media_reverse,
|
||||
57432 => .media_stop,
|
||||
57433 => .media_fast_forward,
|
||||
57434 => .media_rewind,
|
||||
57435 => .media_track_next,
|
||||
57436 => .media_track_previous,
|
||||
57437 => .media_record,
|
||||
57438 => .lower_volume,
|
||||
57439 => .raise_volume,
|
||||
57440 => .mute_volume,
|
||||
57441 => .left_shift,
|
||||
57442 => .left_control,
|
||||
57443 => .left_alt,
|
||||
57444 => .left_super,
|
||||
57445 => .left_hyper,
|
||||
57446 => .left_meta,
|
||||
57447 => .right_shift,
|
||||
57448 => .right_control,
|
||||
57449 => .right_alt,
|
||||
57450 => .right_super,
|
||||
57451 => .right_hyper,
|
||||
57452 => .right_meta,
|
||||
57453 => .iso_level3_shift,
|
||||
57454 => .iso_level5_shift,
|
||||
else => null,
|
||||
},
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
};
|
Loading…
Add table
Reference in a new issue