diff options
Diffstat (limited to 'modules/TUI/unix.jai')
| -rw-r--r-- | modules/TUI/unix.jai | 297 |
1 files changed, 175 insertions, 122 deletions
diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index 8eeb6c0..a6cd467 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -1,22 +1,171 @@ #scope_file /* -TODO : then log all error on unix... TODO : then do a good implementation of the libc functions about attributes... */ +USE_LIBC :: true; + #import "Atomics"; #import "System"; #import "POSIX"; + // Set Attributes Actions. + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-tcflow.h + // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html + // OptionalActions :: enum s32 { + TCSANOW :: 0; // Change immediately. + TCSADRAIN :: 1; // Change when pending output is written. + TCSAFLUSH :: 2; // Flush pending input before changing. + // } + + // TODO + // QueueSelector :: enum s32 { + // queue_selector + #if OS == { + case .LINUX; + // https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios.h + TCIFLUSH :: 0; // Discard data received but not yet read. + TCOFLUSH :: 1; // Discard data written but not yet sent. + TCIOFLUSH :: 2; // Discard all pending data. + + case .MACOS; + // https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html + TCIFLUSH :: 1; // Discard data received but not yet read. + TCOFLUSH :: 2; // Discard data written but not yet sent. + TCIOFLUSH :: 3; // Discard all pending data. + } + } + + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-struct.h + // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html + #if OS == { + case .LINUX; + NCCS :: 32; + + case .MACOS; + NCCS :: 20; + } + + // Information for the termios.h enums is platform dependent and was retrieved from: + // https://elixir.bootlin.com/glibc/latest/source/sysdeps/unix/sysv/linux/bits/termios.h + + + + // The struct termios. + 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 : [NCCS]Control_Chars; // Control characters. + c_ispeed : u32; // Input speed (baud rates). + c_ospeed : u32; // Output speed (baud rates). + } + + // Input modes. + Input_Modes :: enum_flags u32 { + IGNBRK :: 0000001; // Ignore break condition. + BRKINT :: 0000002; // Signal interrupt on break. + IGNPAR :: 0000004; // Ignore characters with parity errors. + PARMRK :: 0000010; // Mark parity and framing errors. + INPCK :: 0000020; // Enable input parity check. + ISTRIP :: 0000040; // Strip 8th bit off characters. + INLCR :: 0000100; // Map NL to CR on input. + IGNCR :: 0000200; // Ignore CR. + ICRNL :: 0000400; // Map CR to NL on input. + IUCLC :: 0001000; // Translate upper case input to lower case (not in POSIX). + IXON :: 0002000; // Enable start/stop output control. + IXANY :: 0004000; // Any character will restart after stop. + IXOFF :: 0010000; // Enable start/stop input control. + IMAXBEL :: 0020000; // Ring bell when input queue is full (not in POSIX). + IUTF8 :: 0040000; // Input is UTF8 (not in POSIX). + } + + // Output modes. + Output_Modes :: enum_flags u32 { + OPOST :: 0000001; // Perform output processing. + OLCUC :: 0000002; // Map lowercase characters to uppercase on output (not in POSIX). + ONLCR :: 0000004; // Map NL to CR-NL on output. + OCRNL :: 0000010; // Map CR to NL. + ONOCR :: 0000020; // Discard CR's when on column 0. + ONLRET :: 0000040; // Move to column 0 on NL. + OFILL :: 0000100; // Send fill characters for delays. + OFDEL :: 0000200; // Fill is DEL. + VTDLY :: 0040000; // Select vertical-tab delays: + VT0 :: 0000000; // Vertical-tab delay type 0. + VT1 :: 0040000; // Vertical-tab delay type 1. + } + + // Control modes. + Control_Modes :: enum u32 { + CS5 :: 0000000; // 5 bits per byte. + CS6 :: 0000020; // 6 bits per byte. + CS7 :: 0000040; // 7 bits per byte. + CS8 :: 0000060; // 8 bits per byte. + CSIZE :: 0000060; // Number of bits per byte (mask). + CSTOPB :: 0000100; // Two stop bits instead of one. + CREAD :: 0000200; // Enable receiver. + PARENB :: 0000400; // Parity enable. + PARODD :: 0001000; // Odd parity instead of even. + HUPCL :: 0002000; // Hang up on last close. + CLOCAL :: 0004000; + } + + // Local modes. + Local_Modes :: enum_flags u32 { + ISIG :: 0000001; // Enable signals. + ICANON :: 0000002; // Do erase and kill processing. + ECHO :: 0000010; // Enable echo. + ECHOE :: 0000020; // Visual erase for ERASE. + ECHOK :: 0000040; // Echo NL after KILL. + ECHONL :: 0000100; // Echo NL even if ECHO is off. + NOFLSH :: 0000200; // Disable flush after interrupt. + TOSTOP :: 0000400; // Send SIGTTOU for background output. + IEXTEN :: 0100000; // Enable DISCARD and LNEXT. + } + + // Control Characters + Control_Chars :: enum u8 { + VINTR :: 0; + VQUIT :: 1; + VERASE :: 2; + VKILL :: 3; + VEOF :: 4; + VTIME :: 5; // Time-out value (tenths of a second) [!ICANON]. + VMIN :: 6; // Minimum number of bytes read at once [!ICANON]. + VSWTC :: 7; + VSTART :: 8; + VSTOP :: 9; + VSUSP :: 10; + VEOL :: 11; + VREPRINT :: 12; + VDISCARD :: 13; + VWERASE :: 14; + VLNEXT :: 15; + VEOL2 :: 16; + } + + +#if USE_LIBC { // 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; + + // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcflush.c.html + tcflush :: (fd: s32, queue_selector: s32) -> s32 #foreign libc; +} +else { // 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; tcsetattr :: (fd: s32, optional_actions: s32, termios_p : *Terminal_IO_Mode) -> s32 { - // TODO IMPLEMENT ME - + #if OS == .LINUX { TCSETS :: 0x5402; TCSETSW :: 0x5403; @@ -71,7 +220,6 @@ TODO : then do a good implementation of the libc functions about attributes... } // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcgetattr.c.html - // tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 #foreign libc; tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 { TCSETS :: 0x5402; TCSETSW :: 0x5403; @@ -135,128 +283,25 @@ TODO : then do a good implementation of the libc functions about attributes... } // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcflush.c.html - // tcflush :: (fd: s32, queue_selector: s32) -> s32 #foreign libc; tcflush :: inline (fd: s32, queue_selector: s32) -> s32 { TCFLSH :: 0x540B; return ioctl(fd, TCFLSH, queue_selector); } - - // Information for the termios.h enums is platform dependent and was retrieved from: - // https://elixir.bootlin.com/glibc/latest/source/sysdeps/unix/sysv/linux/bits/termios.h - - // Set Attributes Actions. - SetAttributesActions :: enum u32 { - TCSANOW :: 0; // Change immediately. - TCSADRAIN :: 1; // Change when pending output is written. - TCSAFLUSH :: 2; // Flush pending input before changing. - } - - // Input modes. - Input_Modes :: enum_flags u32 { - IGNBRK :: 0000001; // Ignore break condition. - BRKINT :: 0000002; // Signal interrupt on break. - IGNPAR :: 0000004; // Ignore characters with parity errors. - PARMRK :: 0000010; // Mark parity and framing errors. - INPCK :: 0000020; // Enable input parity check. - ISTRIP :: 0000040; // Strip 8th bit off characters. - INLCR :: 0000100; // Map NL to CR on input. - IGNCR :: 0000200; // Ignore CR. - ICRNL :: 0000400; // Map CR to NL on input. - IUCLC :: 0001000; // Translate upper case input to lower case (not in POSIX). - IXON :: 0002000; // Enable start/stop output control. - IXANY :: 0004000; // Any character will restart after stop. - IXOFF :: 0010000; // Enable start/stop input control. - IMAXBEL :: 0020000; // Ring bell when input queue is full (not in POSIX). - IUTF8 :: 0040000; // Input is UTF8 (not in POSIX). - } - - // Output modes. - Output_Modes :: enum_flags u32 { - OPOST :: 0000001; // Perform output processing. - OLCUC :: 0000002; // Map lowercase characters to uppercase on output (not in POSIX). - ONLCR :: 0000004; // Map NL to CR-NL on output. - OCRNL :: 0000010; // Map CR to NL. - ONOCR :: 0000020; // Discard CR's when on column 0. - ONLRET :: 0000040; // Move to column 0 on NL. - OFILL :: 0000100; // Send fill characters for delays. - OFDEL :: 0000200; // Fill is DEL. - VTDLY :: 0040000; // Select vertical-tab delays: - VT0 :: 0000000; // Vertical-tab delay type 0. - VT1 :: 0040000; // Vertical-tab delay type 1. - } - - // Control modes. - Control_Modes :: enum u32 { - CS5 :: 0000000; // 5 bits per byte. - CS6 :: 0000020; // 6 bits per byte. - CS7 :: 0000040; // 7 bits per byte. - CS8 :: 0000060; // 8 bits per byte. - CSIZE :: 0000060; // Number of bits per byte (mask). - CSTOPB :: 0000100; // Two stop bits instead of one. - CREAD :: 0000200; // Enable receiver. - PARENB :: 0000400; // Parity enable. - PARODD :: 0001000; // Odd parity instead of even. - HUPCL :: 0002000; // Hang up on last close. - CLOCAL :: 0004000; - } - - // Local modes. - Local_Modes :: enum_flags u32 { - ISIG :: 0000001; // Enable signals. - ICANON :: 0000002; // Do erase and kill processing. - ECHO :: 0000010; // Enable echo. - ECHOE :: 0000020; // Visual erase for ERASE. - ECHOK :: 0000040; // Echo NL after KILL. - ECHONL :: 0000100; // Echo NL even if ECHO is off. - NOFLSH :: 0000200; // Disable flush after interrupt. - TOSTOP :: 0000400; // Send SIGTTOU for background output. - IEXTEN :: 0100000; // Enable DISCARD and LNEXT. - } +} - // Control Characters - Control_Chars :: enum u8 { - VINTR :: 0; - VQUIT :: 1; - VERASE :: 2; - VKILL :: 3; - VEOF :: 4; - VTIME :: 5; // Time-out value (tenths of a second) [!ICANON]. - VMIN :: 6; // Minimum number of bytes read at once [!ICANON]. - VSWTC :: 7; - VSTART :: 8; - VSTOP :: 9; - VSUSP :: 10; - VEOL :: 11; - VREPRINT :: 12; - VDISCARD :: 13; - VWERASE :: 14; - VLNEXT :: 15; - VEOL2 :: 16; - } +//////////////////////////////////////////////////////////////////////////////// - // The struct termios. - 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]Control_Chars;// Control characters. - c_ispeed : u32; // Input speed (baud rates). - c_ospeed : u32; // Output speed (baud rates). - } - initial_tio_mode: Terminal_IO_Mode; raw_tio_mode: Terminal_IO_Mode; + was_resized : bool; + //////////////////////////////////////////////////////////////////////////////// -// Resize detection -was_resized : bool; resize_handler :: (signal_code : s32) #c_call { new_context : Context; push_context new_context { - if signal_code != SIGWINCH then return; + if signal_code != SIGWINCH then return; atomic_swap(*was_resized, true); } } @@ -279,13 +324,14 @@ restore_resize_handler :: () { #scope_export -OS_prepare_terminal :: () -> success := true { // On critical paths, we may use the #must for the success return value. +OS_prepare_terminal :: () -> success := true { error: int = ---; error = tcgetattr(STDIN_FILENO, *initial_tio_mode); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to get initial_tio_mode: code %, %", error_code, error_string); + return false; } raw_tio_mode = initial_tio_mode; @@ -297,7 +343,7 @@ OS_prepare_terminal :: () -> success := true { // On critical paths, we may use raw_tio_mode.c_cc[Control_Chars.VMIN] = 1; raw_tio_mode.c_cc[Control_Chars.VTIME] = 0; - error = tcsetattr(STDIN_FILENO, 0, *raw_tio_mode); + error = tcsetattr(STDIN_FILENO, TCSANOW, *raw_tio_mode); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to set raw_tio_mode: code %, %", error_code, error_string); @@ -311,7 +357,7 @@ OS_prepare_terminal :: () -> success := true { // On critical paths, we may use OS_reset_terminal :: inline () -> success := true { restore_resize_handler(); - error := tcsetattr(STDIN_FILENO, 0, *initial_tio_mode); + error := tcsetattr(STDIN_FILENO, TCSANOW, *initial_tio_mode); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to set initial_tio_mode: code %, %", error_code, error_string); @@ -321,7 +367,6 @@ OS_reset_terminal :: inline () -> success := true { } OS_flush_input :: inline () -> success := true { - TCIFLUSH :: 0; // TODO Is this always zero in all systems? error := tcflush(STDIN_FILENO, TCIFLUSH); if error { error_code, error_string := get_error_value_and_string(); @@ -331,7 +376,6 @@ OS_flush_input :: inline () -> success := true { return; } -// TODO Nothing is checking for the errors returned by this... shame! OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success := true { bytes_read := read(STDIN_FILENO, buffer, xx bytes_to_read); if bytes_read < 0 { @@ -345,12 +389,21 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : // timeout_milliseconds // 0: do not wait // -1: wait indefinitely -OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool { +OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool, success := true { fds := pollfd.[ .{ fd = STDIN_FILENO, events = POLLIN, revents = 0 } ]; nfds := fds.count; - poll_return := poll(fds.data, xx nfds, xx timeout_milliseconds); // TODO Wait for input using poll syscall. This breaks and throws '-1 | 4 | Interrupted system call' when we resize the window while polling. - error_code, error_message := get_error_value_and_string(); // FIXME Not used. - return ifx poll_return > 0 then true else false; + result := poll(fds.data, xx nfds, xx timeout_milliseconds); // Returns '-1' with errno '4 | Interrupted system call' on window resize. + + if result == -1 { + error_code, error_string := get_error_value_and_string(); + // Ignore window resize events (error_code 4). + if error_code != 4 { + log_error("Unexpected error while waiting for input: code %, %", error_code, error_string); + return false, false; + } + } + + return ifx result > 0 then true else false; } OS_was_terminal_resized :: () -> bool { |
