#scope_file #import "Atomics"; #import "System"; #import "POSIX"; // Required to do unlocking input. libc :: #system_library "libc"; // TODO Remote this. // cfmakeraw :: (termios: *Terminal_IO_Mode) -> void #foreign libc; /* https://elixir.bootlin.com/glibc/glibc-2.28/source/termios/cfmakeraw.c#L22 void cfmakeraw (struct termios *t) { t->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); t->c_oflag &= ~OPOST; t->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); t->c_cflag &= ~(CSIZE|PARENB); t->c_cflag |= CS8; t->c_cc[VMIN] = 1; // read returns when one char is available. t->c_cc[VTIME] = 0; } */ // 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; 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 #foreign libc; // tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 { // TODO IMPLEMENT ME // } // 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; //////////////////////////////////////////////////////////////////////////////// // 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; 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 :: () { tcgetattr(STDIN_FILENO, *initial_tio_mode); // TODO Log error using `log()` from jai/modules/Basic/Print.jai ? 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; tcsetattr(STDIN_FILENO, 0, *raw_tio_mode); // TODO Log on error. was_resized = false; prepare_resize_handler(); } OS_reset_terminal :: () { restore_resize_handler(); tcsetattr(STDIN_FILENO, 0, *initial_tio_mode); // TODO Log on error. } OS_flush_input :: inline () { TCIFLUSH :: 0; // TODO Is this always zero in all systems? tcflush(STDIN_FILENO, TCIFLUSH); } // TODO Nothing is checking for the errors returned by this... shame! 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; } // timeout_milliseconds // 0: do not wait // -1: wait indefinitely OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool { 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; } // TODO This procedure hides the behaviour of reseting on read. // We should have the `was_resized` on module.jai so that we know it may be used in another thread. OS_was_terminal_resized :: () -> bool { return atomic_swap(*was_resized, false); // TODO If the windows implementation is similar, we may push this into the main module file. }