aboutsummaryrefslogtreecommitdiff
path: root/modules/TUI/unix.jai
diff options
context:
space:
mode:
Diffstat (limited to 'modules/TUI/unix.jai')
-rw-r--r--modules/TUI/unix.jai286
1 files changed, 286 insertions, 0 deletions
diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai
new file mode 100644
index 0000000..861fe11
--- /dev/null
+++ b/modules/TUI/unix.jai
@@ -0,0 +1,286 @@
+#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.
+}