summaryrefslogtreecommitdiff
path: root/main.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-06-17 21:40:03 -0300
committerGabriel Schneider <[email protected]>2026-06-17 23:44:24 -0300
commit15b9bef58c80e918352567e909a88084ca3134ef (patch)
treeb3d791abf5888dc94a6f4ba280119b7d92a9afd2 /main.zig
parentffed14b9857b53a2aae4a0c39dee47949f1331f2 (diff)
downloadpardes-vibes-ghostty.tar.gz
pardes-vibes-ghostty.zip
vibe fast vibes-ghostty
Diffstat (limited to 'main.zig')
-rw-r--r--main.zig532
1 files changed, 440 insertions, 92 deletions
diff --git a/main.zig b/main.zig
index a55ad637..d43cb750 100644
--- a/main.zig
+++ b/main.zig
@@ -1,125 +1,473 @@
+//! Two shells, two ptys, side by side in one terminal. ghostty-vt parses each
+//! child's output into a grid, we paint both grids into bordered vaxis windows
+//! so it's obvious these are two ptys living *inside* one terminal, and key/mouse
+//! events go to the focused pane. 'o' switches focus, F12 quits.
+//!
+//! pty master ──read──▶ ghostty-vt Stream ──▶ RenderState ──▶ vaxis window
+//! ▲ │
+//! └──────── encodeKey / encodeMouse ◀── vaxis key/mouse events
+//!
+//! Prototype: flat globals, code written out twice on purpose (pane 0 / pane 1).
+//! ponytail: repetition over abstraction. Make it N panes the day there are N.
+
const std = @import("std");
const vaxis = @import("vaxis");
const ghostty_vt = @import("ghostty-vt");
+const posix = std.posix;
-// basic file navigation
-// basic shell io
-// basic layouting: for now following acme and ad
-// * /home/goblin/05-genizah/plan9port/src/cmd/acme
-// * /home/goblin/05-genizah/ad
-// * /usr/lib/zig
-// * ../03-plano
-// right click and enter on lines to open files at specified lines
-
-pub const Command = struct {
- prev: ?*Command = null,
- pub var value: union(enum) {
- close,
- ping,
- nop,
- quit,
- tick,
- key_press: vaxis.Key,
- winsize: vaxis.Winsize,
- mouse: vaxis.Mouse,
- } = .nop;
+const Event = union(enum) {
+ key_press: vaxis.Key,
+ winsize: vaxis.Winsize,
+ mouse: vaxis.Mouse,
+ pty_readable, // some child wrote output; repaint
+ child_exited, // a child died; quit
};
+const Loop = vaxis.Loop(Event);
+
+// libc bits std.posix doesn't wrap in Zig 0.16. ponytail: extern over a module.
+extern "c" fn forkpty(amaster: *c_int, name: ?[*:0]u8, termp: ?*const anyopaque, winp: ?*const posix.winsize) c_int;
+extern "c" fn execvp(file: [*:0]const u8, argv: [*:null]const ?[*:0]const u8) c_int;
+extern "c" fn setenv(name: [*:0]const u8, value: [*:0]const u8, overwrite: c_int) c_int;
+extern "c" fn getenv(name: [*:0]const u8) ?[*:0]const u8;
+extern "c" fn write(fd: c_int, buf: [*]const u8, count: usize) isize;
+extern "c" fn close(fd: c_int) c_int;
+extern "c" fn waitpid(pid: c_int, status: ?*c_int, options: c_int) c_int;
+extern "c" fn _exit(status: c_int) noreturn;
+
+const GSLOT = 16; // per-cell utf8 storage: base codepoint + a few combining marks
+
+var ga: std.mem.Allocator = undefined;
+var gio: std.Io = undefined;
+var vx: vaxis.Vaxis = undefined;
+var tty: vaxis.Tty = undefined;
+var loop: Loop = undefined;
+
+// Per-pane state, indexed [0]/[1]. Flat parallel arrays, no struct.
+var term: [2]ghostty_vt.Terminal = undefined;
+var stream: [2]ghostty_vt.TerminalStream = undefined;
+var rs: [2]ghostty_vt.RenderState = undefined;
+var tmutex: [2]std.Io.Mutex = undefined; // each guards its term/stream
+var master: [2]posix.fd_t = undefined;
+var child: [2]posix.pid_t = undefined;
+var gbuf: [2][]u8 = undefined;
+var pcols: [2]u16 = .{ 0, 0 }; // size we last synced pty+model to
+var prows: [2]u16 = .{ 0, 0 };
+var focus: usize = 0;
+// Coalesce repaint wakeups so a burst of output can't flood the event queue.
+var pending: std.atomic.Value(bool) = .init(false);
+
+pub fn main(init: std.process.Init) !void {
+ ga = init.gpa;
+ gio = init.io;
-const Loop = vaxis.Loop(@TypeOf(Command.value));
-const MAX_FILES: usize = 0x200;
+ var tty_buf: [0x10000]u8 = undefined;
+ tty = try vaxis.Tty.init(gio, &tty_buf);
+ vx = try vaxis.init(gio, ga, init.environ_map, .{});
+ defer vx.deinit(ga, tty.writer());
+ try vx.enterAltScreen(tty.writer());
+ defer vx.exitAltScreen(tty.writer()) catch {};
+ try vx.setMouseMode(tty.writer(), true); // outer terminal -> us
+
+ loop = .init(gio, &tty, &vx);
+
+ _ = setenv("TERM", "xterm-256color", 1);
+ const sh = getenv("SHELL") orelse "/bin/bash";
+
+ // pane 0
+ term[0] = try ghostty_vt.Terminal.init(ga, .{ .cols = 80, .rows = 24 });
+ stream[0] = term[0].vtStream(); // captures &term[0], stable global
+ rs[0] = .empty;
+ tmutex[0] = .init;
+ gbuf[0] = try ga.alloc(u8, 80 * 24 * GSLOT);
+ {
+ const argv = [_:null]?[*:0]const u8{ sh, null };
+ const ws = posix.winsize{ .row = 24, .col = 80, .xpixel = 0, .ypixel = 0 };
+ const pid = forkpty(&master[0], null, null, &ws);
+ if (pid < 0) return error.Forkpty;
+ if (pid == 0) {
+ _ = execvp(argv[0].?, &argv);
+ _exit(127);
+ }
+ child[0] = pid;
+ }
+ // pane 1
+ term[1] = try ghostty_vt.Terminal.init(ga, .{ .cols = 80, .rows = 24 });
+ stream[1] = term[1].vtStream();
+ rs[1] = .empty;
+ tmutex[1] = .init;
+ gbuf[1] = try ga.alloc(u8, 80 * 24 * GSLOT);
+ {
+ const argv = [_:null]?[*:0]const u8{ sh, null };
+ const ws = posix.winsize{ .row = 24, .col = 80, .xpixel = 0, .ypixel = 0 };
+ const pid = forkpty(&master[1], null, null, &ws);
+ if (pid < 0) return error.Forkpty;
+ if (pid == 0) {
+ _ = execvp(argv[0].?, &argv);
+ _exit(127);
+ }
+ child[1] = pid;
+ }
+ defer {
+ stream[0].deinit();
+ term[0].deinit(ga);
+ rs[0].deinit(ga);
+ ga.free(gbuf[0]);
+ stream[1].deinit();
+ term[1].deinit(ga);
+ rs[1].deinit(ga);
+ ga.free(gbuf[1]);
+ }
+
+ try loop.start();
+ defer loop.stop();
+ var r0 = try gio.concurrent(ptyReader, .{@as(usize, 0)});
+ var r1 = try gio.concurrent(ptyReader, .{@as(usize, 1)});
+ defer {
+ r0.await(gio);
+ r1.await(gio);
+ }
+ // SIGHUP the children first so the readers' read() hit EOF and await returns.
+ defer {
+ posix.kill(child[0], posix.SIG.HUP) catch {};
+ _ = close(master[0]);
+ _ = waitpid(child[0], null, 0);
+ posix.kill(child[1], posix.SIG.HUP) catch {};
+ _ = close(master[1]);
+ _ = waitpid(child[1], null, 0);
+ }
-fn ticker(io: std.Io, loop: *Loop) void {
while (true) {
- io.sleep(.fromMilliseconds(2), .awake) catch return;
- _ = loop.tryPostEvent(.tick) catch return;
+ switch (try loop.nextEvent()) {
+ .child_exited => break,
+ .pty_readable => {
+ pending.store(false, .release);
+ try repaint();
+ },
+ .key_press => |k| {
+ if (k.codepoint == vaxis.Key.f12) break; // quit
+ // plain 'o' alternates focus; prototype, so it's never sent to a
+ // child (you can't type lowercase o into the shells, who cares).
+ if (k.codepoint == 'o' and !k.mods.ctrl and !k.mods.alt and !k.mods.super and !k.mods.shift) {
+ focus = 1 - focus;
+ try repaint();
+ } else sendKey(k);
+ },
+ .mouse => |m| sendMouse(m),
+ .winsize => |w| {
+ try vx.resize(ga, tty.writer(), w);
+ try repaint(); // repaint lays out + resizes both panes to fit
+ },
+ }
}
}
-pub fn main(init: std.process.Init) !void {
- std.debug.print("hello!\n", .{});
+/// Concurrent task: blocking-read pane[idx]'s child output, feed ghostty, wake
+/// the loop.
+fn ptyReader(idx: usize) void {
+ var buf: [4096]u8 = undefined;
+ while (true) {
+ const n = posix.read(master[idx], &buf) catch break;
+ if (n == 0) break; // EOF: child closed the pty
+ tmutex[idx].lockUncancelable(gio);
+ stream[idx].nextSlice(buf[0..n]);
+ tmutex[idx].unlock(gio);
+ if (pending.swap(true, .acq_rel) == false)
+ _ = loop.tryPostEvent(.pty_readable) catch {};
+ }
+ _ = loop.tryPostEvent(.child_exited) catch {};
+}
- const io = init.io;
- const gpa = init.gpa;
+/// Paint both panes side by side in bordered boxes, plus a status line so it's
+/// obvious this is a wrapper around two ptys. Written out twice on purpose.
+fn repaint() !void {
+ const root = vx.window();
+ root.clear();
+ if (root.width < 4 or root.height < 3) {
+ try vx.render(tty.writer());
+ return;
+ }
+ const lw = root.width / 2; // left width; right gets the remainder
+ const body_h = root.height - 1; // last row is the status line
- var t: ghostty_vt.Terminal = try .init(gpa, .{
- .cols = 6,
- .rows = 40,
- });
- defer t.deinit(gpa);
+ // ===== pane 0 (left) =====
+ {
+ const focused = focus == 0;
+ const st: vaxis.Style = .{ .fg = if (focused) .{ .index = 4 } else .default, .bold = focused, .dim = !focused };
+ const win = root.child(.{ .x_off = 0, .width = lw, .height = body_h, .border = .{ .where = .all, .style = st } });
+ _ = root.print(&.{.{ .text = " pty 0 ", .style = st }}, .{ .col_offset = 2 }); // title on top border
- try t.printString("hello there!\nhowdy!");
- const str = try t.plainString(gpa);
+ if (win.width != 0 and win.height != 0 and (win.width != pcols[0] or win.height != prows[0])) {
+ pcols[0] = win.width;
+ prows[0] = win.height;
+ const need = @as(usize, win.width) * win.height * GSLOT;
+ if (need > gbuf[0].len) gbuf[0] = ga.realloc(gbuf[0], need) catch gbuf[0];
+ tmutex[0].lockUncancelable(gio);
+ term[0].resize(ga, win.width, win.height) catch {};
+ tmutex[0].unlock(gio);
+ const wsz = posix.winsize{ .row = win.height, .col = win.width, .xpixel = 0, .ypixel = 0 };
+ _ = posix.system.ioctl(master[0], posix.T.IOCSWINSZ, @intFromPtr(&wsz));
+ }
- var tty_buf: [0x10000]u8 = undefined;
- var lines: [0x1000][0x1000]u8 = undefined;
+ tmutex[0].lockUncancelable(gio);
+ rs[0].update(ga, &term[0]) catch {};
+ tmutex[0].unlock(gio);
- const dir = std.Io.Dir.cwd();
- const cwd = try std.Io.Dir.openDir(dir, io, ".", .{ .iterate = true });
- var walk = try cwd.walk(gpa);
- defer walk.deinit();
+ const cols: usize = rs[0].cols;
+ const cell_rows = rs[0].row_data.items(.cells);
+ var y: usize = 0;
+ while (y < rs[0].rows and y < win.height) : (y += 1) {
+ const row_cells = cell_rows[y];
+ var x: usize = 0;
+ while (x < cols and x < win.width) : (x += 1) {
+ const cell = row_cells.get(x);
+ if (cell.raw.wide == .spacer_tail) continue;
+ const slot = gbuf[0][(y * cols + x) * GSLOT ..][0..GSLOT];
+ const cp = cell.raw.codepoint();
+ var glen: usize = 1;
+ if (cp == 0) {
+ slot[0] = ' ';
+ } else {
+ glen = std.unicode.utf8Encode(cp, slot) catch e: {
+ slot[0] = ' ';
+ break :e 1;
+ };
+ if (cell.raw.hasGrapheme()) for (cell.grapheme) |g| {
+ if (glen + 4 > slot.len) break;
+ glen += std.unicode.utf8Encode(g, slot[glen..]) catch break;
+ };
+ }
+ const sty = if (cell.raw.hasStyling()) cell.style else ghostty_vt.Style{};
+ win.writeCell(@intCast(x), @intCast(y), .{
+ .char = .{ .grapheme = slot[0..glen], .width = if (cell.raw.wide == .wide) 2 else 1 },
+ .style = .{
+ .fg = vcol(sty.fg_color),
+ .bg = vcol(sty.bg_color),
+ .ul = vcol(sty.underline_color),
+ .ul_style = switch (sty.flags.underline) {
+ .none => .off,
+ .single => .single,
+ .double => .double,
+ .curly => .curly,
+ .dotted => .dotted,
+ .dashed => .dashed,
+ },
+ .bold = sty.flags.bold,
+ .dim = sty.flags.faint,
+ .italic = sty.flags.italic,
+ .blink = sty.flags.blink,
+ .reverse = sty.flags.inverse,
+ .invisible = sty.flags.invisible,
+ .strikethrough = sty.flags.strikethrough,
+ },
+ });
+ }
+ }
+ if (focused) if (rs[0].cursor.visible) if (rs[0].cursor.viewport) |vp| {
+ if (vp.x < win.width and vp.y < win.height) win.showCursor(vp.x, vp.y);
+ };
+ }
+ // ===== pane 1 (right) =====
{
- var i: usize = 0;
+ const focused = focus == 1;
+ const st: vaxis.Style = .{ .fg = if (focused) .{ .index = 4 } else .default, .bold = focused, .dim = !focused };
+ const win = root.child(.{ .x_off = lw, .width = root.width - lw, .height = body_h, .border = .{ .where = .all, .style = st } });
+ _ = root.print(&.{.{ .text = " pty 1 ", .style = st }}, .{ .col_offset = lw + 2 });
- while (try walk.next(io)) |e| : (i += 1) {
- if (i > MAX_FILES) break;
+ if (win.width != 0 and win.height != 0 and (win.width != pcols[1] or win.height != prows[1])) {
+ pcols[1] = win.width;
+ prows[1] = win.height;
+ const need = @as(usize, win.width) * win.height * GSLOT;
+ if (need > gbuf[1].len) gbuf[1] = ga.realloc(gbuf[1], need) catch gbuf[1];
+ tmutex[1].lockUncancelable(gio);
+ term[1].resize(ga, win.width, win.height) catch {};
+ tmutex[1].unlock(gio);
+ const wsz = posix.winsize{ .row = win.height, .col = win.width, .xpixel = 0, .ypixel = 0 };
+ _ = posix.system.ioctl(master[1], posix.T.IOCSWINSZ, @intFromPtr(&wsz));
+ }
- std.debug.print("path: {s}\n", .{e.path});
- @memcpy(lines[i][0..e.path.len], e.path);
- std.debug.dumpHex(lines[i][0..2]);
- lines[i][e.path.len] = 0;
+ tmutex[1].lockUncancelable(gio);
+ rs[1].update(ga, &term[1]) catch {};
+ tmutex[1].unlock(gio);
+
+ const cols: usize = rs[1].cols;
+ const cell_rows = rs[1].row_data.items(.cells);
+ var y: usize = 0;
+ while (y < rs[1].rows and y < win.height) : (y += 1) {
+ const row_cells = cell_rows[y];
+ var x: usize = 0;
+ while (x < cols and x < win.width) : (x += 1) {
+ const cell = row_cells.get(x);
+ if (cell.raw.wide == .spacer_tail) continue;
+ const slot = gbuf[1][(y * cols + x) * GSLOT ..][0..GSLOT];
+ const cp = cell.raw.codepoint();
+ var glen: usize = 1;
+ if (cp == 0) {
+ slot[0] = ' ';
+ } else {
+ glen = std.unicode.utf8Encode(cp, slot) catch e: {
+ slot[0] = ' ';
+ break :e 1;
+ };
+ if (cell.raw.hasGrapheme()) for (cell.grapheme) |g| {
+ if (glen + 4 > slot.len) break;
+ glen += std.unicode.utf8Encode(g, slot[glen..]) catch break;
+ };
+ }
+ const sty = if (cell.raw.hasStyling()) cell.style else ghostty_vt.Style{};
+ win.writeCell(@intCast(x), @intCast(y), .{
+ .char = .{ .grapheme = slot[0..glen], .width = if (cell.raw.wide == .wide) 2 else 1 },
+ .style = .{
+ .fg = vcol(sty.fg_color),
+ .bg = vcol(sty.bg_color),
+ .ul = vcol(sty.underline_color),
+ .ul_style = switch (sty.flags.underline) {
+ .none => .off,
+ .single => .single,
+ .double => .double,
+ .curly => .curly,
+ .dotted => .dotted,
+ .dashed => .dashed,
+ },
+ .bold = sty.flags.bold,
+ .dim = sty.flags.faint,
+ .italic = sty.flags.italic,
+ .blink = sty.flags.blink,
+ .reverse = sty.flags.inverse,
+ .invisible = sty.flags.invisible,
+ .strikethrough = sty.flags.strikethrough,
+ },
+ });
+ }
}
+ if (focused) if (rs[1].cursor.visible) if (rs[1].cursor.viewport) |vp| {
+ if (vp.x < win.width and vp.y < win.height) win.showCursor(vp.x, vp.y);
+ };
}
- var tty = try vaxis.Tty.init(io, &tty_buf);
+ _ = root.print(
+ &.{.{ .text = " pardes · two ptys, one terminal · o: switch focus · F12: quit ", .style = .{ .dim = true } }},
+ .{ .row_offset = body_h, .col_offset = 1 },
+ );
+ try vx.render(tty.writer());
+}
+
+fn vcol(c: ghostty_vt.Style.Color) vaxis.Color {
+ return switch (c) {
+ .none => .default,
+ .palette => |i| .{ .index = i },
+ .rgb => |rgb| .{ .rgb = .{ rgb.r, rgb.g, rgb.b } },
+ };
+}
- var vx = try vaxis.init(io, gpa, init.environ_map, .{});
- defer vx.deinit(gpa, tty.writer());
+/// Encode a vaxis key with ghostty-vt and write it to the focused pane's child.
+fn sendKey(k: vaxis.Key) void {
+ tmutex[focus].lockUncancelable(gio);
+ const opts = ghostty_vt.input.KeyEncodeOptions.fromTerminal(&term[focus]);
+ tmutex[focus].unlock(gio);
- try vx.enterAltScreen(tty.writer());
- defer vx.exitAltScreen(tty.writer()) catch {};
+ const cp = k.codepoint;
+ var ev: ghostty_vt.input.KeyEvent = .{
+ .action = .press,
+ .mods = .{ .shift = k.mods.shift, .ctrl = k.mods.ctrl, .alt = k.mods.alt, .super = k.mods.super },
+ .unshifted_codepoint = cp,
+ .key = switch (cp) {
+ vaxis.Key.enter => .enter,
+ vaxis.Key.tab => .tab,
+ vaxis.Key.escape => .escape,
+ vaxis.Key.space => .space,
+ vaxis.Key.backspace => .backspace,
+ vaxis.Key.up => .arrow_up,
+ vaxis.Key.down => .arrow_down,
+ vaxis.Key.left => .arrow_left,
+ vaxis.Key.right => .arrow_right,
+ vaxis.Key.home => .home,
+ vaxis.Key.end => .end,
+ vaxis.Key.page_up => .page_up,
+ vaxis.Key.page_down => .page_down,
+ vaxis.Key.insert => .insert,
+ vaxis.Key.delete => .delete,
+ vaxis.Key.f1 => .f1,
+ vaxis.Key.f2 => .f2,
+ vaxis.Key.f3 => .f3,
+ vaxis.Key.f4 => .f4,
+ vaxis.Key.f5 => .f5,
+ vaxis.Key.f6 => .f6,
+ vaxis.Key.f7 => .f7,
+ vaxis.Key.f8 => .f8,
+ vaxis.Key.f9 => .f9,
+ vaxis.Key.f10 => .f10,
+ vaxis.Key.f11 => .f11,
+ vaxis.Key.f12 => .f12,
+ 'a'...'z' => @enumFromInt(@intFromEnum(ghostty_vt.input.Key.key_a) + (cp - 'a')),
+ '0'...'9' => @enumFromInt(@intFromEnum(ghostty_vt.input.Key.digit_0) + (cp - '0')),
+ else => .unidentified,
+ },
+ };
+ // Only forward real text; control/private-use keys encode from .key above.
+ if (k.text) |t| if (cp >= 0x20 and cp != 0x7f and cp < 0xE000) {
+ ev.utf8 = t;
+ };
- try vx.setMouseMode(tty.writer(), true);
+ var buf: [128]u8 = undefined;
+ var w = std.Io.Writer.fixed(&buf);
+ ghostty_vt.input.encodeKey(&w, ev, opts) catch return;
+ ptyWrite(master[focus], w.buffered());
+}
- var loop: Loop = .init(io, &tty, &vx);
- try loop.start();
- defer loop.stop();
+/// Encode a vaxis mouse event for the focused pane and write it to its child
+/// (only when that child turned mouse reporting on). Coords are translated from
+/// screen space into the pane's content area.
+fn sendMouse(m: vaxis.Mouse) void {
+ const root = vx.window();
+ const lw = root.width / 2;
+ const inner_x: i32 = if (focus == 0) 1 else @as(i32, lw) + 1; // border-inset origin
+ const lx = @as(i32, m.col) - inner_x;
+ const ly = @as(i32, m.row) - 1;
+ if (lx < 0 or ly < 0 or lx >= pcols[focus] or ly >= prows[focus]) return;
- while (true) {
- const event = try loop.nextEvent();
- const win = vx.window();
- const win_child = win.child(.{ .border = .{ .where = .all }, .height = 8, .width = 42 });
- _ = win_child.printSegment(.{ .text = "hey" }, .{});
+ const Size = @FieldType(ghostty_vt.input.MouseEncodeOptions, "size");
+ tmutex[focus].lockUncancelable(gio);
+ const mouse_on = term[focus].flags.mouse_event != .none;
+ var size: Size = std.mem.zeroes(Size); // 1px cells: col/row == "pixels"
+ size.screen = .{ .width = term[focus].cols, .height = term[focus].rows };
+ size.cell = .{ .width = 1, .height = 1 };
+ var opts = ghostty_vt.input.MouseEncodeOptions.fromTerminal(&term[focus], size);
+ tmutex[focus].unlock(gio);
+ if (!mouse_on) return;
- switch (event) {
- .key_press => |key| {
- if (key.matches('c', .{ .ctrl = true })) {
- break;
- }
- },
- .mouse => |m| {
- _ = win.printSegment(.{ .text = str }, .{ .col_offset = 1, .row_offset = 1 });
+ opts.any_button_pressed = m.type == .press or m.type == .drag;
+ const ev: ghostty_vt.input.MouseEncodeEvent = .{
+ .action = switch (m.type) {
+ .press => .press,
+ .release => .release,
+ .motion, .drag => .motion,
+ },
+ .button = switch (m.button) {
+ .left => .left,
+ .middle => .middle,
+ .right => .right,
+ .wheel_up => .four,
+ .wheel_down => .five,
+ .wheel_right => .six,
+ .wheel_left => .seven,
+ else => null,
+ },
+ .mods = .{ .shift = m.mods.shift, .ctrl = m.mods.ctrl, .alt = m.mods.alt },
+ .pos = .{ .x = @floatFromInt(lx), .y = @floatFromInt(ly) },
+ };
- if (m.type == .press) {
- // const txt = try std.fmt.allocPrint(gpa, "col={} row={} xo={} yo={}", .{ m.col, m.row, m.xoffset, m.yoffset });
- // constlen = std.mem.findSentinel(u8, 0, @ptrCast(&lines[@intCast(m.row)]));
- // _ = win.printSegment(
- // .{
- // .text = lines[@intCast(m.row)][0..len],
- // .style = .{ .fg = .{ .rgb = .{ 0x00, 0xd0, 0xaa } } },
- // },
- // .{
- // .col_offset = @as(u16, @intCast(m.col)),
- // .row_offset = @as(u16, @intCast(m.row)),
- // .wrap = .none,
- // },
- // );
- }
- },
- .winsize => |ws| try vx.resize(gpa, tty.writer(), ws),
- else => {},
- }
+ var buf: [64]u8 = undefined;
+ var w = std.Io.Writer.fixed(&buf);
+ ghostty_vt.input.encodeMouse(&w, ev, opts) catch return;
+ ptyWrite(master[focus], w.buffered());
+}
- try vx.render(tty.writer());
+fn ptyWrite(fd: posix.fd_t, bytes: []const u8) void {
+ var i: usize = 0;
+ while (i < bytes.len) {
+ const n = write(fd, bytes.ptr + i, bytes.len - i);
+ if (n <= 0) return;
+ i += @intCast(n);
}
}