const std = @import("std"); const code_point = @import("code_point"); const ctlseqs = @import("ctlseqs.zig"); const input = @import("input.zig"); const Key = input.Key; const Position = @import("size.zig").Position; const Size = @import("size.zig").Size; const Cell = @import("cell.zig"); const log = std.log.scoped(.terminal); // 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: std.posix.winsize = undefined; _ = std.posix.system.ioctl(std.posix.STDIN_FILENO, std.posix.T.IOCGWINSZ, @intFromPtr(&ws)); return .{ .cols = ws.col, .rows = ws.row }; } pub fn saveScreen() !void { _ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.save_screen); } pub fn restoreScreen() !void { _ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.restore_screen); } pub fn enterAltScreen() !void { _ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.smcup); } pub fn exitAltScreen() !void { _ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.rmcup); } pub fn clearScreen() !void { _ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.clear_screen); } pub fn hideCursor() !void { _ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.hide_cursor); } pub fn showCursor() !void { _ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.show_cursor); } pub fn setCursorPositionHome() !void { _ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.home); } pub fn enableMouseSupport() !void { _ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.mouse_set); } pub fn disableMouseSupport() !void { _ = try std.posix.write(std.posix.STDIN_FILENO, ctlseqs.mouse_reset); } pub fn read(buf: []u8) !usize { return try std.posix.read(std.posix.STDIN_FILENO, buf); } pub fn write(buf: []const u8) !usize { return try std.posix.write(std.posix.STDIN_FILENO, buf); } fn contextWrite(context: @This(), data: []const u8) anyerror!usize { _ = context; return try std.posix.write(std.posix.STDOUT_FILENO, data); } const Writer = std.io.Writer( @This(), anyerror, contextWrite, ); pub fn writer() Writer { return .{ .context = .{} }; } pub fn setCursorPosition(pos: Position) !void { var buf: [64]u8 = undefined; const value = try std.fmt.bufPrint(&buf, "\x1b[{d};{d}H", .{ pos.row, pos.col }); _ = try std.posix.write(std.posix.STDIN_FILENO, value); } pub fn getCursorPosition() !Size.Position { // Needs Raw mode (no wait for \n) to work properly cause // control sequence will not be written without it. _ = try std.posix.write(std.posix.STDIN_FILENO, "\x1b[6n"); var buf: [64]u8 = undefined; // format: \x1b, "[", R1,..., Rn, ";", C1, ..., Cn, "R" const len = try std.posix.read(std.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 .{ .row = try std.fmt.parseInt(u16, row[0..ridx], 10) - 1, .col = try std.fmt.parseInt(u16, col[0..cidx], 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: *std.posix.termios) !void { var termios = try std.posix.tcgetattr(std.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(std.posix.V.MIN)] = 1; termios.cc[@intFromEnum(std.posix.V.TIME)] = 0; try std.posix.tcsetattr( std.posix.STDIN_FILENO, .FLUSH, termios, ); } /// Reverts `enableRawMode` to restore initial functionality. pub fn disableRawMode(bak: *std.posix.termios) !void { try std.posix.tcsetattr( std.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 std.posix.write(std.posix.STDIN_FILENO, "\x1b[?2026$p"); var buf: [64]u8 = undefined; // format: \x1b, "[", "?", "2", "0", "2", "6", ";", n, "$", "y" const len = try std.posix.read(std.posix.STDIN_FILENO, &buf); if (!std.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, }; }