wip: macos
This commit is contained in:
parent
40e51ad054
commit
7b7b84ad21
6 changed files with 298 additions and 2 deletions
|
@ -61,9 +61,11 @@ pub fn main() !void {
|
||||||
|
|
||||||
var redraw: bool = false;
|
var redraw: bool = false;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
std.debug.print("inside while loop before resize\n", .{});
|
||||||
std.time.sleep(8 * std.time.ns_per_ms);
|
std.time.sleep(8 * std.time.ns_per_ms);
|
||||||
// try vt events first
|
// try vt events first
|
||||||
while (vt.tryEvent()) |event| {
|
while (vt.tryEvent()) |event| {
|
||||||
|
std.debug.print("inside stryEventloop \n", .{});
|
||||||
redraw = true;
|
redraw = true;
|
||||||
switch (event) {
|
switch (event) {
|
||||||
.bell => {},
|
.bell => {},
|
||||||
|
@ -74,6 +76,7 @@ pub fn main() !void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while (loop.tryEvent()) |event| {
|
while (loop.tryEvent()) |event| {
|
||||||
|
std.debug.print("inside loop.tryEvent\n", .{});
|
||||||
redraw = true;
|
redraw = true;
|
||||||
switch (event) {
|
switch (event) {
|
||||||
.key_press => |key| {
|
.key_press => |key| {
|
||||||
|
|
172
log.lg
Normal file
172
log.lg
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
info(loop): pixel mouse capability detected
|
||||||
|
info(loop): unicode capability detected
|
||||||
|
info(loop): color_scheme_updates capability detected
|
||||||
|
info(loop): kitty keyboard capability detected
|
||||||
|
info(loop): kitty graphics capability detected
|
||||||
|
slave name: /dev/ttys008
|
||||||
|
posix opened : /dev/ttys008
|
||||||
|
set size done
|
||||||
|
inside spawnwe are not child: 56928
|
||||||
|
inside while loop before resize
|
||||||
|
inside run
|
||||||
|
inside while loop
|
||||||
|
inside loop.tryEvent
|
||||||
|
debug(vaxis): resizing screen: width=81 height=49
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside loop.tryEvent
|
||||||
|
before switch event
|
||||||
|
inside print event
|
||||||
|
inside while loop
|
||||||
|
inside while loop before resize
|
||||||
|
inside stryEventloop
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside loop.tryEvent
|
||||||
|
before switch event
|
||||||
|
inside print event
|
||||||
|
inside while loop
|
||||||
|
inside while loop before resize
|
||||||
|
inside stryEventloop
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside loop.tryEvent
|
||||||
|
before switch event
|
||||||
|
inside handlec0
|
||||||
|
inside while loop
|
||||||
|
before switch event
|
||||||
|
inside handlec0
|
||||||
|
inside while loop
|
||||||
|
inside while loop before resize
|
||||||
|
inside stryEventloop
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside loop.tryEvent
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside while loop before resize
|
||||||
|
inside loop.tryEvent
|
||||||
|
before switch event
|
||||||
|
inside handlec0
|
||||||
|
debug(vaxis): total renders = 8
|
||||||
|
debug(vaxis): microseconds per render = 1393
|
|
@ -17,6 +17,22 @@ pid: ?std.posix.pid_t = null,
|
||||||
env_map: *const std.process.EnvMap,
|
env_map: *const std.process.EnvMap,
|
||||||
|
|
||||||
pty: Pty,
|
pty: Pty,
|
||||||
|
const TIOCSCTTY = if (builtin.os.tag == .macos) 536900705 else c.TIOCSCTTY;
|
||||||
|
const TIOCSWINSZ = if (builtin.os.tag == .macos) 2148037735 else c.TIOCSWINSZ;
|
||||||
|
const TIOCGWINSZ = if (builtin.os.tag == .macos) 1074295912 else c.TIOCGWINSZ;
|
||||||
|
extern "c" fn setsid() std.c.pid_t;
|
||||||
|
const c = struct {
|
||||||
|
usingnamespace switch (builtin.os.tag) {
|
||||||
|
.macos => @cImport({
|
||||||
|
@cInclude("sys/ioctl.h"); // ioctl and constants
|
||||||
|
@cInclude("util.h"); // openpty()
|
||||||
|
}),
|
||||||
|
else => @cImport({
|
||||||
|
@cInclude("sys/ioctl.h"); // ioctl and constants
|
||||||
|
@cInclude("pty.h");
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
pub fn spawn(self: *Command, allocator: std.mem.Allocator) !void {
|
pub fn spawn(self: *Command, allocator: std.mem.Allocator) !void {
|
||||||
var arena_allocator = std.heap.ArenaAllocator.init(allocator);
|
var arena_allocator = std.heap.ArenaAllocator.init(allocator);
|
||||||
|
@ -33,10 +49,12 @@ pub fn spawn(self: *Command, allocator: std.mem.Allocator) !void {
|
||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
// we are the child
|
// we are the child
|
||||||
_ = std.os.linux.setsid();
|
_ = std.os.linux.setsid();
|
||||||
|
_ = setsid();
|
||||||
|
// if (setsid() < 0) return error.ProcessGroupFailed;
|
||||||
|
|
||||||
// set the controlling terminal
|
// set the controlling terminal
|
||||||
var u: c_uint = std.posix.STDIN_FILENO;
|
var u: c_uint = std.posix.STDIN_FILENO;
|
||||||
if (posix.system.ioctl(self.pty.tty, posix.T.IOCSCTTY, @intFromPtr(&u)) != 0) return error.IoctlError;
|
if (c.ioctl(self.pty.tty, TIOCSCTTY, @intFromPtr(&u)) != 0) return error.IoctlError;
|
||||||
|
|
||||||
// set up io
|
// set up io
|
||||||
try posix.dup2(self.pty.tty, std.posix.STDIN_FILENO);
|
try posix.dup2(self.pty.tty, std.posix.STDIN_FILENO);
|
||||||
|
@ -53,6 +71,10 @@ pub fn spawn(self: *Command, allocator: std.mem.Allocator) !void {
|
||||||
// exec
|
// exec
|
||||||
const err = std.posix.execvpeZ(argv_buf.ptr[0].?, argv_buf.ptr, envp);
|
const err = std.posix.execvpeZ(argv_buf.ptr[0].?, argv_buf.ptr, envp);
|
||||||
_ = err catch {};
|
_ = err catch {};
|
||||||
|
} else {
|
||||||
|
std.debug.print("we are not child: {d}\n", .{pid});
|
||||||
|
// posix.close(self.pty.tty);
|
||||||
|
_ = posix.waitpid(pid, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we are the parent
|
// we are the parent
|
||||||
|
|
|
@ -7,6 +7,25 @@ const Winsize = @import("../../main.zig").Winsize;
|
||||||
|
|
||||||
const posix = std.posix;
|
const posix = std.posix;
|
||||||
|
|
||||||
|
extern "c" fn setsid() std.c.pid_t;
|
||||||
|
|
||||||
|
const c = struct {
|
||||||
|
usingnamespace switch (builtin.os.tag) {
|
||||||
|
.macos => @cImport({
|
||||||
|
@cInclude("sys/ioctl.h"); // ioctl and constants
|
||||||
|
@cInclude("util.h"); // openpty()
|
||||||
|
}),
|
||||||
|
else => @cImport({
|
||||||
|
@cInclude("sys/ioctl.h"); // ioctl and constants
|
||||||
|
@cInclude("pty.h");
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const TIOCSCTTY = if (builtin.os.tag == .macos) 536900705 else c.TIOCSCTTY;
|
||||||
|
const TIOCSWINSZ = if (builtin.os.tag == .macos) 2148037735 else c.TIOCSWINSZ;
|
||||||
|
const TIOCGWINSZ = if (builtin.os.tag == .macos) 1074295912 else c.TIOCGWINSZ;
|
||||||
|
|
||||||
pty: posix.fd_t,
|
pty: posix.fd_t,
|
||||||
tty: posix.fd_t,
|
tty: posix.fd_t,
|
||||||
|
|
||||||
|
@ -14,6 +33,7 @@ tty: posix.fd_t,
|
||||||
pub fn init() !Pty {
|
pub fn init() !Pty {
|
||||||
switch (builtin.os.tag) {
|
switch (builtin.os.tag) {
|
||||||
.linux => return openPtyLinux(),
|
.linux => return openPtyLinux(),
|
||||||
|
.macos => return openPtyMacos2(),
|
||||||
else => @compileError("unsupported os"),
|
else => @compileError("unsupported os"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,10 +52,75 @@ pub fn setSize(self: Pty, ws: Winsize) !void {
|
||||||
.ws_xpixel = @truncate(ws.x_pixel),
|
.ws_xpixel = @truncate(ws.x_pixel),
|
||||||
.ws_ypixel = @truncate(ws.y_pixel),
|
.ws_ypixel = @truncate(ws.y_pixel),
|
||||||
};
|
};
|
||||||
if (posix.system.ioctl(self.pty, posix.T.IOCSWINSZ, @intFromPtr(&_ws)) != 0)
|
if (c.ioctl(self.pty, TIOCSWINSZ, @intFromPtr(&_ws)) != 0)
|
||||||
return error.SetWinsizeError;
|
return error.SetWinsizeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn openPtyMacos2() !Pty {
|
||||||
|
const p = try posix.open("/dev/ptmx", .{ .ACCMODE = .RDWR, .NOCTTY = true }, 0);
|
||||||
|
errdefer posix.close(p);
|
||||||
|
|
||||||
|
// unlockpt
|
||||||
|
// var n: c_uint = 0;
|
||||||
|
if (c.ioctl(p, c.TIOCPTYUNLK) != 0) return error.IoctlError;
|
||||||
|
|
||||||
|
// ptsname
|
||||||
|
if (c.ioctl(p, c.TIOCPTYGRANT) != 0) return error.IoctlError;
|
||||||
|
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
// var buf2: [128]u8 = undefined;
|
||||||
|
if (c.ioctl(p, c.TIOCPTYGNAME, &buf) != 0) return error.IoctlError;
|
||||||
|
const sname = buf[0 .. 13 - 1];
|
||||||
|
// std.debug.print("sizeof buf: {d}", .{buf.len});
|
||||||
|
// const sname = try std.fmt.bufPrint(&buf2, "{s}", .{buf});
|
||||||
|
std.debug.print("slave name: {s}\n", .{sname});
|
||||||
|
// const sname = try std.fmt.bufPrint(&buf, "/dev/pts/{d}", .{n});
|
||||||
|
// std.log.err("pts: {s}", .{sname});
|
||||||
|
|
||||||
|
const t = try posix.open(sname, .{ .ACCMODE = .RDWR, .NOCTTY = true }, 0);
|
||||||
|
std.debug.print("posix opened : {s}\n", .{sname});
|
||||||
|
|
||||||
|
var attrs: c.termios = undefined;
|
||||||
|
if (c.tcgetattr(p, &attrs) != 0)
|
||||||
|
return error.OpenptyFailed;
|
||||||
|
attrs.c_iflag |= c.IUTF8;
|
||||||
|
if (c.tcsetattr(p, c.TCSANOW, &attrs) != 0)
|
||||||
|
return error.OpenptyFailed;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.pty = p,
|
||||||
|
.tty = t,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn openPtyMacos() !Pty {
|
||||||
|
var master_fd: posix.fd_t = undefined;
|
||||||
|
var slave_fd: posix.fd_t = undefined;
|
||||||
|
if (c.openpty(
|
||||||
|
&master_fd,
|
||||||
|
&slave_fd,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
) < 0)
|
||||||
|
return error.OpenptyFailed;
|
||||||
|
errdefer {
|
||||||
|
_ = posix.system.close(master_fd);
|
||||||
|
_ = posix.system.close(slave_fd);
|
||||||
|
}
|
||||||
|
var attrs: c.termios = undefined;
|
||||||
|
if (c.tcgetattr(master_fd, &attrs) != 0)
|
||||||
|
return error.OpenptyFailed;
|
||||||
|
attrs.c_iflag |= c.IUTF8;
|
||||||
|
if (c.tcsetattr(master_fd, c.TCSANOW, &attrs) != 0)
|
||||||
|
return error.OpenptyFailed;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.pty = master_fd,
|
||||||
|
.tty = slave_fd,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn openPtyLinux() !Pty {
|
fn openPtyLinux() !Pty {
|
||||||
const p = try posix.open("/dev/ptmx", .{ .ACCMODE = .RDWR, .NOCTTY = true }, 0);
|
const p = try posix.open("/dev/ptmx", .{ .ACCMODE = .RDWR, .NOCTTY = true }, 0);
|
||||||
errdefer posix.close(p);
|
errdefer posix.close(p);
|
||||||
|
|
|
@ -100,6 +100,7 @@ pub fn init(
|
||||||
}
|
}
|
||||||
const pty = try Pty.init();
|
const pty = try Pty.init();
|
||||||
try pty.setSize(opts.winsize);
|
try pty.setSize(opts.winsize);
|
||||||
|
std.debug.print("set size done\n", .{});
|
||||||
const cmd: Command = .{
|
const cmd: Command = .{
|
||||||
.argv = argv,
|
.argv = argv,
|
||||||
.env_map = env,
|
.env_map = env,
|
||||||
|
@ -159,6 +160,7 @@ pub fn deinit(self: *Terminal) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn(self: *Terminal) !void {
|
pub fn spawn(self: *Terminal) !void {
|
||||||
|
std.debug.print("inside spawn", .{});
|
||||||
if (self.thread != null) return;
|
if (self.thread != null) return;
|
||||||
self.back_screen = &self.back_screen_pri;
|
self.back_screen = &self.back_screen_pri;
|
||||||
|
|
||||||
|
@ -273,6 +275,7 @@ fn anyReader(self: *const Terminal) std.io.AnyReader {
|
||||||
|
|
||||||
/// process the output from the command on the pty
|
/// process the output from the command on the pty
|
||||||
fn run(self: *Terminal) !void {
|
fn run(self: *Terminal) !void {
|
||||||
|
std.debug.print("inside run\n", .{});
|
||||||
var parser: Parser = .{
|
var parser: Parser = .{
|
||||||
.buf = try std.ArrayList(u8).initCapacity(self.allocator, 128),
|
.buf = try std.ArrayList(u8).initCapacity(self.allocator, 128),
|
||||||
};
|
};
|
||||||
|
@ -282,6 +285,7 @@ fn run(self: *Terminal) !void {
|
||||||
var reader = std.io.bufferedReader(self.anyReader());
|
var reader = std.io.bufferedReader(self.anyReader());
|
||||||
|
|
||||||
while (!self.should_quit) {
|
while (!self.should_quit) {
|
||||||
|
std.debug.print("inside while loop\n", .{});
|
||||||
const event = try parser.parseReader(&reader);
|
const event = try parser.parseReader(&reader);
|
||||||
self.back_mutex.lock();
|
self.back_mutex.lock();
|
||||||
defer self.back_mutex.unlock();
|
defer self.back_mutex.unlock();
|
||||||
|
@ -289,8 +293,10 @@ fn run(self: *Terminal) !void {
|
||||||
if (!self.dirty and self.event_queue.tryPush(.redraw))
|
if (!self.dirty and self.event_queue.tryPush(.redraw))
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
|
|
||||||
|
std.debug.print("before switch event\n", .{});
|
||||||
switch (event) {
|
switch (event) {
|
||||||
.print => |str| {
|
.print => |str| {
|
||||||
|
std.debug.print("inside print event\n", .{});
|
||||||
var iter = grapheme.Iterator.init(str, &self.unicode.grapheme_data);
|
var iter = grapheme.Iterator.init(str, &self.unicode.grapheme_data);
|
||||||
while (iter.next()) |g| {
|
while (iter.next()) |g| {
|
||||||
const gr = g.bytes(str);
|
const gr = g.bytes(str);
|
||||||
|
@ -301,6 +307,7 @@ fn run(self: *Terminal) !void {
|
||||||
},
|
},
|
||||||
.c0 => |b| try self.handleC0(b),
|
.c0 => |b| try self.handleC0(b),
|
||||||
.escape => |esc| {
|
.escape => |esc| {
|
||||||
|
std.debug.print("inside escape event\n", .{});
|
||||||
const final = esc[esc.len - 1];
|
const final = esc[esc.len - 1];
|
||||||
switch (final) {
|
switch (final) {
|
||||||
'B' => {}, // TODO: handle charsets
|
'B' => {}, // TODO: handle charsets
|
||||||
|
@ -714,6 +721,7 @@ fn run(self: *Terminal) !void {
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn handleC0(self: *Terminal, b: ansi.C0) !void {
|
inline fn handleC0(self: *Terminal, b: ansi.C0) !void {
|
||||||
|
std.debug.print("inside handlec0\n", .{});
|
||||||
switch (b) {
|
switch (b) {
|
||||||
.NUL, .SOH, .STX => {},
|
.NUL, .SOH, .STX => {},
|
||||||
.EOT => {}, // we send EOT to quit the read thread
|
.EOT => {}, // we send EOT to quit the read thread
|
||||||
|
@ -730,6 +738,7 @@ inline fn handleC0(self: *Terminal, b: ansi.C0) !void {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setMode(self: *Terminal, mode: u16, val: bool) void {
|
pub fn setMode(self: *Terminal, mode: u16, val: bool) void {
|
||||||
|
std.debug.print("inside setmode\n", .{});
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
7 => self.mode.autowrap = val,
|
7 => self.mode.autowrap = val,
|
||||||
25 => self.mode.cursor = val,
|
25 => self.mode.cursor = val,
|
||||||
|
|
5
src/widgets/terminal/log.lg
Normal file
5
src/widgets/terminal/log.lg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
info(loop): pixel mouse capability detected
|
||||||
|
info(loop): unicode capability detected
|
||||||
|
info(loop): color_scheme_updates capability detected
|
||||||
|
info(loop): kitty keyboard capability detected
|
||||||
|
info(loop): kitty graphics capability detected
|
Loading…
Reference in a new issue