lib/sys/src/terminal.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const builtin = @import("builtin");
  3 
  4 const capabilities = @import("capabilities.zig");
  5 const env = @import("env.zig");
  6 const fd = @import("fd.zig");
  7 const posix = std.posix;
  8 
  9 pub const required_capabilities = capabilities.host(&.{ .descriptors, .environment, .terminal });
 10 
 11 pub const Size = struct {
 12     rows: u16,
 13     cols: u16,
 14     x_pixel: u16,
 15     y_pixel: u16,
 16 };
 17 
 18 pub const Attributes = posix.termios;
 19 pub const AttributeAction = posix.TCSA;
 20 pub const AttributeQueryError = posix.TermiosGetError || error{UnsupportedPlatform};
 21 pub const AttributeApplyError = posix.TermiosSetError || error{UnsupportedPlatform};
 22 pub const SizeError = error{ UnsupportedPlatform, QueryFailed, ApplyFailed };
 23 
 24 pub fn querySize(file: std.Io.File) ?Size {
 25     return queryDescriptorSize(file.handle);
 26 }
 27 
 28 pub fn isTty(file: std.Io.File) bool {
 29     if (file.isTty(std.Options.debug_io) catch false) return true;
 30     return isDescriptorTty(file.handle);
 31 }
 32 
 33 pub fn isDescriptorTty(descriptor: fd.Descriptor) bool {
 34     if (!supportsPosixTerminalIo()) return false;
 35     _ = posix.tcgetattr(descriptor) catch return false;
 36     return true;
 37 }
 38 
 39 pub fn queryDescriptorSize(descriptor: fd.Descriptor) ?Size {
 40     if (!supportsPosixTerminalIo()) return null;
 41 
 42     var winsize: posix.winsize = .{
 43         .row = 0,
 44         .col = 0,
 45         .xpixel = 0,
 46         .ypixel = 0,
 47     };
 48     const err = posix.system.ioctl(descriptor, posix.T.IOCGWINSZ, @intFromPtr(&winsize));
 49     if (posix.errno(err) != .SUCCESS) return null;
 50 
 51     return .{
 52         .rows = winsize.row,
 53         .cols = winsize.col,
 54         .x_pixel = winsize.xpixel,
 55         .y_pixel = winsize.ypixel,
 56     };
 57 }
 58 
 59 pub fn setDescriptorSize(descriptor: fd.Descriptor, size: Size) SizeError!void {
 60     if (!supportsPosixTerminalIo()) return error.UnsupportedPlatform;
 61     var winsize = posix.winsize{
 62         .row = size.rows,
 63         .col = size.cols,
 64         .xpixel = size.x_pixel,
 65         .ypixel = size.y_pixel,
 66     };
 67     const result = posix.system.ioctl(
 68         descriptor,
 69         posix.T.IOCSWINSZ,
 70         @intFromPtr(&winsize),
 71     );
 72     if (posix.errno(result) != .SUCCESS) return error.ApplyFailed;
 73 }
 74 
 75 pub fn getAttributes(descriptor: fd.Descriptor) AttributeQueryError!Attributes {
 76     if (!supportsPosixTerminalIo()) return error.UnsupportedPlatform;
 77     return posix.tcgetattr(descriptor);
 78 }
 79 
 80 pub fn setAttributes(
 81     descriptor: fd.Descriptor,
 82     action: AttributeAction,
 83     attributes: Attributes,
 84 ) AttributeApplyError!void {
 85     if (!supportsPosixTerminalIo()) return error.UnsupportedPlatform;
 86     return posix.tcsetattr(descriptor, action, attributes);
 87 }
 88 
 89 pub fn setReadTiming(
 90     attributes: *Attributes,
 91     minimum_bytes: u8,
 92     timeout_deciseconds: u8,
 93 ) void {
 94     attributes.cc[@backingInt(posix.V.MIN)] = minimum_bytes;
 95     attributes.cc[@backingInt(posix.V.TIME)] = timeout_deciseconds;
 96 }
 97 
 98 pub const KeyboardInterruptError = error{
 99     UnsupportedPlatform,
100     QueryFailed,
101     ApplyFailed,
102 };
103 
104 pub fn enableKeyboardInterrupt(descriptor: fd.Descriptor) KeyboardInterruptError!void {
105     if (!supportsPosixTerminalIo()) return error.UnsupportedPlatform;
106 
107     var raw = posix.tcgetattr(descriptor) catch return error.QueryFailed;
108     raw.lflag.ISIG = true;
109     posix.tcsetattr(descriptor, .FLUSH, raw) catch return error.ApplyFailed;
110 }
111 
112 pub fn columnsFromEnvironment() ?usize {
113     if (!supportsPosixTerminalEnvironment()) return null;
114     const text = env.getConstant("COLUMNS") orelse return null;
115     return std.fmt.parseInt(usize, text, 10) catch null;
116 }
117 
118 pub fn capabilityEnvironmentValue(comptime key: []const u8) ?[]const u8 {
119     if (!supportsPosixTerminalEnvironment()) return null;
120     return env.getConstant(key);
121 }
122 
123 fn supportsPosixTerminalIo() bool {
124     return switch (builtin.os.tag) {
125         .windows, .wasi => false,
126         else => true,
127     };
128 }
129 
130 fn supportsPosixTerminalEnvironment() bool {
131     return switch (builtin.os.tag) {
132         .windows, .wasi => false,
133         else => true,
134     };
135 }
136 
137 test "columnsFromEnvironment ignores missing or unparsable values" {
138     if (env.getConstant("TINY_SYS_TERMINAL_TEST_COLUMNS")) |_| {
139         return error.SkipZigTest;
140     }
141     try std.testing.expect(columnsFromEnvironment() == null or columnsFromEnvironment().? > 0);
142 }