#scope_file #import "POSIX"; #import "System"; // Required to do unlocking input. libc :: #system_library "libc"; // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcsetattr.c.html tcsetattr :: (fd : s32, optional_actions : s32, termios_p : *Terminal_IO_Mode) -> s32 #foreign libc; // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcgetattr.c.html tcgetattr :: (fd : s32, termios_p : *Terminal_IO_Mode) -> s32 #foreign libc; // Input modes. // https://elixir.bootlin.com/glibc/latest/source/bits/termios.h Input_Modes :: enum_flags u32 { IGNBRK; // Ignore break condition. BRKINT; // Signal interrupt on break. IGNPAR; // Ignore characters with parity errors. PARMRK; // Mark parity and framing errors. INPCK; // Enable input parity check. ISTRIP; // Strip 8th bit off characters. INLCR; // Map NL to CR on input. IGNCR; // Ignore CR. ICRNL; // Map CR to NL on input. IXON; // Enable start/stop output control. IXOFF; // Enable start/stop input control. IXANY; // Any character will restart after stop. _1000_; IMAXBEL; // Ring bell when input queue is full. IUCLC; // Translate upper case input to lower case. not in POSIX _8000_; } // Output modes. // https://elixir.bootlin.com/glibc/latest/source/bits/termios.h Output_Modes :: enum_flags u32 { OPOST; // Perform output processing. ONLCR; // Map NL to CR-NL on output. _UNUSED_0004_; ONOEOT; // Discard EOT (^D) on output. OCRNL; // Map CR to NL. ONOCR; // Discard CR's when on column 0. ONLRET; // Move to column 0 on NL. _UNUSED_0080_; } // Control modes. // https://elixir.bootlin.com/glibc/latest/source/bits/termios.h Control_Modes :: enum_flags u32 { CIGNORE; // Ignore these control flags. _0002_; _0004_; _0008_; _0010_; _0020_; _0040_; _0080_; _0100_; _0200_; CSTOPB; // Two stop bits instead of one. CREAD; // Enable receiver. PARENB; // Parity enable. PARODD; // Odd parity instead of even. HUPCL; // Hang up on last close. CLOCAL; // Ignore modem status lines. CRTSCTS; // RTS/CTS flow control. CDTRCTS; // DTR/CTS flow control. _0004_0000_; _0008_0000_; MDMBUF; // DTR/DCD flow control. } CHWFLOW :Control_Modes : (.MDMBUF | .CRTSCTS | .CDTRCTS); // All types of flow control. CS5 :Control_Modes : 0x0000; // 5 bits per byte. CS6 :Control_Modes : 0x0100; // 6 bits per byte. CS7 :Control_Modes : 0x0200; // 7 bits per byte. CS8 :Control_Modes : CS6|CS7; // 8 bits per byte. CSIZE :Control_Modes : CS5|CS6|CS7|CS8; // Number of bits per byte (mask). // Local modes. // https://elixir.bootlin.com/glibc/latest/source/bits/termios.h Local_Modes :: enum_flags u32 { ECHOKE; // Visual erase for KILL. ECHOE; // Visual erase for ERASE. ECHOK; // Echo NL after KILL. ECHO; // Enable echo. ECHONL; // Echo NL even if ECHO is off. ECHOPRT; // Hardcopy visual erase. ECHOCTL; // Echo control characters as ^X. ISIG; // Enable signals. ICANON; // Do erase and kill processing. ALTWERASE; // Alternate WERASE algorithm. IEXTEN; // Enable DISCARD and LNEXT. EXTPROC; // External processing. _1000_; _2000_; _4000_; _8000_; _0001_0000_; _0002_0000_; // TODO Maybe reduce the amount of unused flags by setting the value when there are holes on the table. // TOSTOP :: 0x0004_0000; // Send SIGTTOU for background output. TOSTOP; // Send SIGTTOU for background output. FLUSHO; // Output being flushed (state). XCASE; // Canonical upper/lower case. NOKERNINFO; // Disable VSTATUS. _0040_0000_; _0080_0000_; _0100_0000_; PENDIN; // Retype pending input (state). _0400_0000_; NOFLSH; // Disable flush after interrupt. } Terminal_IO_Mode :: struct { c_iflag : Input_Modes; // Input mode flags. c_oflag : Output_Modes; // Output mode flags. c_cflag : Control_Modes; // Control modes flags. c_lflag : Local_Modes; // Local modes flags. c_line : u8; // Line discipline. c_cc : [32]u8; // Control characters. c_ispeed : u32; // Input speed (baud rates). c_ospeed : u32; // Output speed (baud rates). } initial_tio_mode: Terminal_IO_Mode; default_tio_mode: Terminal_IO_Mode; // TODO Rename to 'raw_tio_mode' human_tio_mode: Terminal_IO_Mode; // TODO Rename to 'cooked_tio_mode' #scope_export OS_prepare_terminal :: () { tcgetattr(STDIN_FILENO, *initial_tio_mode); default_tio_mode = initial_tio_mode; default_tio_mode.c_iflag &= ~(.IGNBRK | .BRKINT | .PARMRK | .ISTRIP | .INLCR | .IGNCR | .ICRNL | .IXOFF); default_tio_mode.c_oflag &= ~(.OPOST); default_tio_mode.c_cflag &= ~(CSIZE | .PARENB); default_tio_mode.c_lflag &= ~(.ECHOCTL |.ECHO | .ECHOE | .ECHOKE); human_tio_mode = default_tio_mode; human_tio_mode.c_iflag |= (.IXOFF | .ICRNL); human_tio_mode.c_lflag |= (.NOKERNINFO | .ECHO | .ECHOE | .ECHOKE); tcsetattr(STDIN_FILENO, 0, *default_tio_mode); } OS_reset_terminal :: () { tcsetattr(STDIN_FILENO, 0, *initial_tio_mode); } OS_get_terminal_size :: () -> rows: int, columns: int { auto_release_temp(); input := talloc_string(64); write_string(Commands.QueryWindowSizeInChars); input.count = OS_read_input(input.data, input.count); // Expected message format: [8;;t // where is the number of rows and of columns. assert( input[0] == #char "\e" && input[1] == #char "[" && input[2] == #char "8", "Query windows size in chars returned invalid response."); parts := split(input, ";"); rows := parse_int(*parts[1]); columns := parse_int(*parts[2]); return rows, columns; } OS_set_input_mode :: (mode: Input_Mode) { tio_mode: *Terminal_IO_Mode = ---; if mode == { case .HUMAN; tio_mode = *human_tio_mode; case; tio_mode = *default_tio_mode; } tcsetattr(STDIN_FILENO, 0, tio_mode); // TODO get_error_value_and_string :: () -> (error_code: OS_Error_Code, description: string) } OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, error: bool = false, error_message: string = "" { bytes_read := read(STDIN_FILENO, buffer, xx bytes_to_read); if bytes_read < 0 { error_code, error_message := get_error_value_and_string(); return -1, true, error_message; } return bytes_read; }