#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. 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. __NOT_USED__; IMAXBEL; // Ring bell when input queue is full. IUCLC; // Translate upper case input to lower case. } // Local modes. 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. TOSTOP; // Send SIGTTOU for background output. FLUSHO; // Output being flushed (state). XCASE; // Canonical upper/lower case. NOKERNINFO; // Disable VSTATUS. PENDIN; // Retype pending input (state). NOFLSH; // Disable flush after interrupt. } Terminal_IO_Mode :: struct { c_iflag : u32; // Input mode flags. c_oflag : u32; // Output mode flags. c_cflag : u32; // Control modes flags. c_lflag : u32; // 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; blocking_tio_mode: Terminal_IO_Mode; unblocking_tio_mode: Terminal_IO_Mode; #scope_export OS_prepare_terminal :: () { // TODO Required to do unlocking input. tcgetattr(STDIN_FILENO, *initial_tio_mode); default_tio_mode := initial_tio_mode; default_tio_mode.c_iflag &= 0xFFFFFA14;// ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); default_tio_mode.c_oflag &= 0xFFFFFFFE;// ~OPOST; default_tio_mode.c_lflag &= 0xFFFF7FB4;// ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); default_tio_mode.c_cflag &= 0xFFFFFECF;// ~(CSIZE | PARENB); default_tio_mode.c_cflag |= 0x00000030; // TODO WHAT IS THIS? blocking_tio_mode = default_tio_mode; blocking_tio_mode.c_iflag |= xx cast(Input_Modes)(.IXOFF | .ICRNL); blocking_tio_mode.c_lflag |= xx cast(Local_Modes)(.NOKERNINFO | .ECHO | .ECHOE | .ECHOKE); unblocking_tio_mode = default_tio_mode; iflags: Input_Modes = (.IGNBRK | .BRKINT | .PARMRK | .ISTRIP | .INLCR | .IGNCR | .ICRNL | .IXON); unblocking_tio_mode.c_iflag &= xx ~(iflags); lflags: Local_Modes = (.ECHO | .ECHONL | .ICANON | .IEXTEN); unblocking_tio_mode.c_lflag &= xx ~lflags; tcsetattr(STDIN_FILENO, 0, *default_tio_mode); } OS_reset_terminal :: () { tcsetattr(STDIN_FILENO, 0, *initial_tio_mode); // return echo } 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; } // TODO Maybe we should use a NON-BLOCKING state by default... and only change to blocking when performing a HUMAN read...? OS_set_input_mode :: (mode: Input_Mode) { if mode == { case .HUMAN; tcsetattr(STDIN_FILENO, 0, *blocking_tio_mode); // TODO get_error_value_and_string :: () -> (error_code: OS_Error_Code, description: string) case .MACHINE; tcsetattr(STDIN_FILENO, 0, *unblocking_tio_mode); // TODO get_error_value_and_string :: () -> (error_code: OS_Error_Code, description: string) case; // TODO ERROR } } 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; }