Files
zterm/src/terminal.zig
Yves Biener 97a240c54d mod: example shows how dynamic sizing is achived that can be independent form the reported terminal size
Setting the cursor with the `Direct` handler will cause the rendering
to halt at that point and leave the cursor at point.

Due to not enabling *raw mode* with the newly introduced `App.start`
configuration options corresponding inputs are only visible to `zterm`
once the input has been completed with a newline. With this it is not
necessary for the renderer to know nothing more than the width of the
terminal (which is implied through the `Container` sizes). Making it
very trivial to implement.
2026-01-20 13:57:55 +01:00

257 lines
6.7 KiB
Zig

// Ref: https://vt100.net/docs/vt510-rm/DECRPM.html
pub const ReportMode = enum {
not_recognized,
set,
reset,
permanently_set,
permanently_reset,
};
/// Gets number of rows and columns in the terminal
pub fn getTerminalSize() Size {
var ws: posix.winsize = undefined;
_ = posix.system.ioctl(posix.STDIN_FILENO, posix.T.IOCGWINSZ, @intFromPtr(&ws));
return .{ .x = ws.col, .y = ws.row };
}
pub fn saveScreen() !void {
_ = try posix.write(posix.STDIN_FILENO, ctlseqs.save_screen);
}
pub fn restoreScreen() !void {
_ = try posix.write(posix.STDIN_FILENO, ctlseqs.restore_screen);
}
pub fn enterAltScreen() !void {
_ = try posix.write(posix.STDIN_FILENO, ctlseqs.smcup);
}
pub fn exitAltScreen() !void {
_ = try posix.write(posix.STDIN_FILENO, ctlseqs.rmcup);
}
pub fn clearScreen() !void {
_ = try posix.write(posix.STDIN_FILENO, ctlseqs.clear_screen);
}
pub fn hideCursor() !void {
_ = try posix.write(posix.STDIN_FILENO, ctlseqs.hide_cursor);
}
pub fn showCursor() !void {
_ = try posix.write(posix.STDIN_FILENO, ctlseqs.show_cursor);
}
pub fn resetCursor() !void {
_ = try posix.write(posix.STDIN_FILENO, ctlseqs.reset_cursor_shape);
_ = try posix.write(posix.STDIN_FILENO, ctlseqs.osc12_reset);
}
pub fn setCursorPositionHome() !void {
_ = try posix.write(posix.STDIN_FILENO, ctlseqs.home);
}
pub fn enableMouseSupport() !void {
_ = try posix.write(posix.STDIN_FILENO, ctlseqs.mouse_set);
}
pub fn disableMouseSupport() !void {
_ = try posix.write(posix.STDIN_FILENO, ctlseqs.mouse_reset);
}
pub fn ringBell() !void {
_ = try posix.write(posix.STDIN_FILENO, &.{7});
}
pub fn read(buf: []u8) !usize {
return try posix.read(posix.STDIN_FILENO, buf);
}
pub fn write(buf: []const u8) !usize {
return try posix.write(posix.STDIN_FILENO, buf);
}
fn drainFn(w: *std.Io.Writer, data: []const []const u8, splat: usize) error{WriteFailed}!usize {
_ = w;
if (data.len == 0 or splat == 0) return 0;
var len: usize = 0;
for (data) |bytes| len += posix.write(posix.STDOUT_FILENO, bytes) catch return error.WriteFailed;
return len;
}
// TODO I now need to add that much, for just the one function above?
pub fn writer() std.Io.Writer {
return .{
.vtable = &.{
.drain = drainFn,
.flush = std.Io.Writer.noopFlush,
},
.buffer = &.{},
};
}
pub fn setCursorPosition(pos: Point) !void {
var buf: [64]u8 = undefined;
const value = try std.fmt.bufPrint(&buf, "\x1b[{d};{d}H", .{ pos.y + 1, pos.x + 1 });
_ = try posix.write(posix.STDIN_FILENO, value);
}
pub fn getCursorPosition() !Size {
// Needs Raw mode (no wait for \n) to work properly cause
// control sequence will not be written without it.
_ = try posix.write(posix.STDIN_FILENO, "\x1b[6n");
var buf: [64]u8 = undefined;
// format: \x1b, "[", R1,..., Rn, ";", C1, ..., Cn, "R"
const len = try posix.read(posix.STDIN_FILENO, &buf);
if (!isCursorPosition(buf[0..len])) {
return error.InvalidValueReturned;
}
var row: [8]u8 = undefined;
var col: [8]u8 = undefined;
var ridx: u3 = 0;
var cidx: u3 = 0;
var is_parsing_cols = false;
for (2..(len - 1)) |i| {
const b = buf[i];
if (b == ';') {
is_parsing_cols = true;
continue;
}
if (b == 'R') {
break;
}
if (is_parsing_cols) {
col[cidx] = buf[i];
cidx += 1;
} else {
row[ridx] = buf[i];
ridx += 1;
}
}
return .{
.x = try std.fmt.parseInt(u16, col[0..cidx], 10) - 1,
.y = try std.fmt.parseInt(u16, row[0..ridx], 10) - 1,
};
}
/// Function is more of a heuristic as opposed
/// to an exact check.
pub fn isCursorPosition(buf: []u8) bool {
if (buf.len < 6) {
return false;
}
if (buf[0] != 27 or buf[1] != '[') {
return false;
}
return true;
}
/// Sets the following
/// - IXON: disables start/stop output flow (reads CTRL-S, CTRL-Q)
/// - ICRNL: disables CR to NL translation (reads CTRL-M)
/// - IEXTEN: disable implementation defined functions (reads CTRL-V, CTRL-O)
/// - ECHO: user input is not printed to terminal
/// - ICANON: read runs for every input (no waiting for `\n`)
/// - ISIG: disable QUIT, ISIG, SUSP.
///
/// `bak`: pointer to store termios struct backup before
/// altering, this is used to disable raw mode.
pub fn enableRawMode(bak: *posix.termios) !void {
var termios = try posix.tcgetattr(posix.STDIN_FILENO);
bak.* = termios;
// termios flags used by termios(3)
termios.iflag.IGNBRK = false;
termios.iflag.BRKINT = false;
termios.iflag.PARMRK = false;
termios.iflag.ISTRIP = false;
termios.iflag.INLCR = false;
termios.iflag.IGNCR = false;
termios.iflag.ICRNL = false;
termios.iflag.IXON = false;
// messes with output -> not used
// termios.oflag.OPOST = false;
termios.lflag.ECHO = false;
termios.lflag.ECHONL = false;
termios.lflag.ICANON = false;
termios.lflag.ISIG = false;
termios.lflag.IEXTEN = false;
termios.cflag.CSIZE = .CS8;
termios.cflag.PARENB = false;
termios.cc[@intFromEnum(posix.V.MIN)] = 1;
termios.cc[@intFromEnum(posix.V.TIME)] = 0;
try posix.tcsetattr(
posix.STDIN_FILENO,
.FLUSH,
termios,
);
}
/// Reverts `enableRawMode` to restore initial functionality.
pub fn disableRawMode(bak: *const posix.termios) !void {
try posix.tcsetattr(
posix.STDIN_FILENO,
.FLUSH,
bak.*,
);
}
// Ref
pub fn canSynchornizeOutput() !bool {
// Needs Raw mode (no wait for \n) to work properly cause
// control sequence will not be written without it.
_ = try posix.write(posix.STDIN_FILENO, "\x1b[?2026$p");
var buf: [64]u8 = undefined;
// format: \x1b, "[", "?", "2", "0", "2", "6", ";", n, "$", "y"
const len = try posix.read(posix.STDIN_FILENO, &buf);
if (!mem.eql(u8, buf[0..len], "\x1b[?2026;") or len < 9) {
return false;
}
// Check value of n
return getReportMode(buf[8]) == .reset;
}
fn getReportMode(ps: u8) ReportMode {
return switch (ps) {
'1' => ReportMode.set,
'2' => ReportMode.reset,
'3' => ReportMode.permanently_set,
'4' => ReportMode.permanently_reset,
else => ReportMode.not_recognized,
};
}
const log = std.log.scoped(.terminal);
const std = @import("std");
const mem = std.mem;
const posix = std.posix;
const assert = std.debug.assert;
const ctlseqs = @import("ctlseqs.zig");
const input = @import("input.zig");
const Key = input.Key;
const Point = @import("point.zig").Point;
const Size = @import("point.zig").Point;
const Cell = @import("cell.zig");