#scope_file /* 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 { #if OS == .LINUX { TCSETS :: 0x5402; TCSETSW :: 0x5403; TCSETSF :: 0x5404; tcflag_t :: u32; cc_t :: u8; __KERNEL_NCCS :: 19; __kernel_termios :: struct { c_iflag : tcflag_t; // input mode flags c_oflag : tcflag_t; // output mode flags c_cflag : tcflag_t; // control mode flags c_lflag : tcflag_t; // local mode flags c_line : cc_t; // line discipline c_cc : [__KERNEL_NCCS]cc_t; // control characters }; k_termios: __kernel_termios; cmd: u64; if optional_actions == { case xx SetAttributesActions.TCSANOW; cmd = TCSETS; case xx SetAttributesActions.TCSADRAIN; cmd = TCSETSW; case xx SetAttributesActions.TCSAFLUSH; cmd = TCSETSF; case; return EINVAL; } // k_termios.c_iflag = termios_p.c_iflag & ~IBAUD0; k_termios.c_iflag = xx termios_p.c_iflag; k_termios.c_oflag = xx termios_p.c_oflag; k_termios.c_cflag = xx termios_p.c_cflag; k_termios.c_lflag = xx termios_p.c_lflag; k_termios.c_line = xx termios_p.c_line; // #if _HAVE_C_ISPEED && _HAVE_STRUCT_TERMIOS_C_ISPEED // k_termios.c_ispeed = termios_p->c_ispeed; // #endif // #if _HAVE_C_OSPEED && _HAVE_STRUCT_TERMIOS_C_OSPEED // k_termios.c_ospeed = termios_p->c_ospeed; // #endif memcpy(*k_termios.c_cc[0], *termios_p.c_cc[0], __KERNEL_NCCS * 1);//size_of(cc_t)); return ioctl(fd, cmd, *k_termios); } #if OS == .MACOS { // return __ioctl (fd, TIOCSETAF, termios_p); #assert(false, "NOT IMPLEMENTED"); } return 0; } // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcgetattr.c.html tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 { TCSETS :: 0x5402; TCSETSW :: 0x5403; TCSETSF :: 0x5404; tcflag_t :: u32; cc_t :: u8; __KERNEL_NCCS :: 19; __kernel_termios :: struct { c_iflag : tcflag_t; // input mode flags c_oflag : tcflag_t; // output mode flags c_cflag : tcflag_t; // control mode flags c_lflag : tcflag_t; // local mode flags c_line : cc_t; // line discipline c_cc : [__KERNEL_NCCS]cc_t; // control characters }; // int // __tcgetattr (int fd, struct termios *termios_p) // { // struct __kernel_termios k_termios; k_termios: __kernel_termios; retval: int; retval = ioctl(fd, TCGETS, *k_termios); if retval == 0 { termios_p.c_iflag = xx k_termios.c_iflag; termios_p.c_oflag = xx k_termios.c_oflag; termios_p.c_cflag = xx k_termios.c_cflag; termios_p.c_lflag = xx k_termios.c_lflag; termios_p.c_line = xx k_termios.c_line; // #if _HAVE_STRUCT_TERMIOS_C_ISPEED // # if _HAVE_C_ISPEED // termios_p->c_ispeed = k_termios.c_ispeed; // # else // termios_p->c_ispeed = k_termios.c_cflag & (CBAUD | CBAUDEX); // # endif // #endif // #if _HAVE_STRUCT_TERMIOS_C_OSPEED // # if _HAVE_C_OSPEED // termios_p->c_ospeed = k_termios.c_ospeed; // # else // termios_p->c_ospeed = k_termios.c_cflag & (CBAUD | CBAUDEX); // # endif // #endif size_of_cc_t := __KERNEL_NCCS * 1; memcpy(*termios_p.c_cc[0], *k_termios.c_cc[0], size_of_cc_t); // memset(*termios_p.c_cc[0] + size_of_cc_t + 1, _POSIX_VDISABLE, (NCCS - __KERNEL_NCCS) * 1); // // if (sizeof (cc_t) == 1 || _POSIX_VDISABLE == 0 || (unsigned char) _POSIX_VDISABLE == (unsigned char) -1) { // memset (__mempcpy (&termios_p->c_cc[0], &k_termios.c_cc[0], __KERNEL_NCCS * sizeof (cc_t)), _POSIX_VDISABLE, (NCCS - __KERNEL_NCCS) * sizeof (cc_t)); // } // else // { // memcpy (&termios_p->c_cc[0], &k_termios.c_cc[0], __KERNEL_NCCS * sizeof (cc_t)); // for (size_t cnt = __KERNEL_NCCS; cnt < NCCS; ++cnt) { // termios_p->c_cc[cnt] = _POSIX_VDISABLE; // } // } } return xx retval; } // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcflush.c.html tcflush :: inline (fd: s32, queue_selector: s32) -> s32 { TCFLSH :: 0x540B; return ioctl(fd, TCFLSH, queue_selector); } } //////////////////////////////////////////////////////////////////////////////// initial_tio_mode: Terminal_IO_Mode; raw_tio_mode: Terminal_IO_Mode; was_resized : bool; //////////////////////////////////////////////////////////////////////////////// resize_handler :: (signal_code : s32) #c_call { new_context : Context; push_context new_context { if signal_code != SIGWINCH then return; atomic_swap(*was_resized, true); } } prepare_resize_handler :: () { sa : sigaction_t; sa.sa_handler = resize_handler; sigemptyset(*(sa.sa_mask)); sa.sa_flags = SA_RESTART; sigaction(SIGWINCH, *sa, null); } restore_resize_handler :: () { sa : sigaction_t; sa.sa_handler = SIG_DFL; sigaction(SIGWINCH, null, *sa); } //////////////////////////////////////////////////////////////////////////////// #scope_export 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; raw_tio_mode.c_iflag &= ~(.IGNBRK | .BRKINT | .PARMRK | .ISTRIP | .INLCR | .IGNCR | .ICRNL | .IXON); raw_tio_mode.c_oflag &= ~(.OPOST); raw_tio_mode.c_lflag &= ~(.ECHO | .ECHONL | .ICANON | .ISIG | .IEXTEN); raw_tio_mode.c_cflag &= ~(.CSIZE | .PARENB); raw_tio_mode.c_cflag |= .CS8; raw_tio_mode.c_cc[Control_Chars.VMIN] = 1; raw_tio_mode.c_cc[Control_Chars.VTIME] = 0; 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); return false; } was_resized = false; prepare_resize_handler(); return; } OS_reset_terminal :: inline () -> success := true { restore_resize_handler(); 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); return false; } return; } OS_flush_input :: inline () -> success := true { error := tcflush(STDIN_FILENO, TCIFLUSH); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to flush input: code %, %", error_code, error_string); return false; } return; } 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 { error_code, error_string := get_error_value_and_string(); log_error("Failed to read input: code %, %", error_code, error_string); return 0, false; } return bytes_read; } // timeout_milliseconds // 0: do not wait // -1: wait indefinitely 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; 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 { return atomic_swap(*was_resized, false); }