aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/TUI/module.jai38
-rw-r--r--modules/TUI/unix.jai297
-rw-r--r--modules/TUI/windows.jai118
-rw-r--r--snake.jai7
-rw-r--r--ttt.jai42
5 files changed, 285 insertions, 217 deletions
diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai
index 5fd29f6..121aa42 100644
--- a/modules/TUI/module.jai
+++ b/modules/TUI/module.jai
@@ -2,6 +2,9 @@
#scope_file
+// - fix/implement/finish `TODO` on `TUI\module` (use some sort of buffet to reduce io/print calls)
+// - fix/implement/finish `TODO` on `TUI\module` (use `dirty_bit_flag` to only update ehat has been changed)
+
#if OS == {
case .LINUX;
#load "unix.jai";
@@ -525,16 +528,17 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key {
return result, key;
}
-start :: () {
+start :: () -> success := true #must {
if active == true return;
- // TODO Should start() call flush_input internally?
-
- setup_key_map(); // TODO This is being called multiple times... please fix me!
-
input_string.data = input_buffer.data;
input_string.count = 0;
input_override = xx Keys.None;
+
+ previous_logger = context.logger;
+ context.logger = module_logger;
+
+ setup_key_map();
write_strings(
Commands.HideCursor,
@@ -542,26 +546,34 @@ start :: () {
Commands.AlternateScreenBuffer,
Commands.EncodingUTF8,
Commands.CursorNormalMode,
- Commands.KeypadNumMode);
-
- previous_logger = context.logger;
- context.logger = module_logger;
+ Commands.KeypadNumMode
+ );
- OS_prepare_terminal();
+ if !OS_prepare_terminal() then return false;
active = true;
+
+ return;
}
-stop :: () {
+stop :: () -> success := true #must {
if active == false return;
+
active = false;
clear_style();
- OS_reset_terminal();
+
+ if !OS_reset_terminal() then return false;
context.logger = previous_logger;
- write_strings(Commands.MainScreenBuffer, Commands.RestoreCursorPosition, Commands.ShowCursor);
+ write_strings(
+ Commands.MainScreenBuffer,
+ Commands.RestoreCursorPosition,
+ Commands.ShowCursor
+ );
+
+ return;
}
flush_input :: () {
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 {
diff --git a/modules/TUI/windows.jai b/modules/TUI/windows.jai
index 5755ef4..608266d 100644
--- a/modules/TUI/windows.jai
+++ b/modules/TUI/windows.jai
@@ -9,47 +9,30 @@
CP_UTF8 :: 65001;
// https://learn.microsoft.com/windows/win32/winprog/windows-data-types
- LPVOID :: *void;
- BOOL :: bool;
- CHAR :: s8;
- WCHAR :: s16;
- SHORT :: s16;
- USHORT :: u16;
- WORD :: u16;
- DWORD :: u32;
- LPDWORD :: *u32;
- UINT :: u32;
-
- PINPUT_RECORD :: *INPUT_RECORD;
-
- // https://learn.microsoft.com/en-us/windows/console/readconsoleinput
- ReadConsoleInputW :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> success: bool #foreign kernel32;
-
- // https://learn.microsoft.com/windows/console/readconsole
- ReadConsoleW :: (hConsoleInput: HANDLE, lpBuffer: LPVOID, nNumberOfchars_to_read: DWORD, lpNumberOfchars_read: LPVOID, pInputControl: LPVOID) -> success: bool #foreign kernel32;
-
- // https://learn.microsoft.com/windows/console/flushconsoleinputbuffer
- FlushConsoleInputBuffer :: (hConsoleInput: HANDLE) -> bool #foreign kernel32;
-
- // https://learn.microsoft.com/windows/console/getnumberofconsoleinputevents
- GetNumberOfConsoleInputEvents :: (hConsoleInput: HANDLE, lpcNumberOfEvents: LPDWORD) -> bool #foreign kernel32;
-
- // https://learn.microsoft.com/en-us/windows/console/peekconsoleinput
- PeekConsoleInputW :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> bool #foreign kernel32;
+ LPVOID :: *void;
+ BOOL :: bool;
+ CHAR :: s8;
+ WCHAR :: s16;
+ SHORT :: s16;
+ WORD :: u16;
+ DWORD :: u32;
+ LPDWORD :: *u32;
+ UINT :: u32;
+ PINPUT_RECORD :: *INPUT_RECORD;
// https://learn.microsoft.com/en-us/windows/console/setconsolemode
// https://learn.microsoft.com/en-us/windows/console/high-level-console-modes
Console_Input_Mode :: enum_flags u32 {
- ENABLE_PROCESSED_INPUT; // If enable, control keys (Ctrl+C, Backspace, ...) are processed by the system.
- ENABLE_LINE_INPUT; // If enable, ReadFile or ReadConsole function return on CR; otherwise they return when one or more characters are available.
- ENABLE_ECHO_INPUT; // Echoes input on screen. Only available if ENABLE_LINE_INPUT is set.
+ ENABLE_PROCESSED_INPUT; // If set, control sequences are processed by the system.
+ ENABLE_LINE_INPUT; // If set, ReadFile and ReadConsole function return on CR; otherwise return when characters are available.
+ ENABLE_ECHO_INPUT; // If set, Echoes input on screen. Only available if ENABLE_LINE_INPUT is set.
ENABLE_WINDOW_INPUT;
- ENABLE_MOUSE_INPUT; // Makes mouse events available to the ReadConsoleInput function.
- ENABLE_INSERT_MODE; // If enabled, text entered will be inserted at the current cursor location and all text following that location will not be overwritten. When disabled, all following text will be overwritten.
+ ENABLE_MOUSE_INPUT;
+ ENABLE_INSERT_MODE;
_UNUSED_0040_;
_UNUSED_0080_;
_UNUSED_0100_;
- ENABLE_VIRTUAL_TERMINAL_INPUT; // If enable, makes user input available to the ReadConsole function.
+ ENABLE_VIRTUAL_TERMINAL_INPUT; // If set, makes user input available to the ReadConsole function.
_UNUSED_0400_;
_UNUSED_0800_;
}
@@ -57,11 +40,11 @@
// https://learn.microsoft.com/en-us/windows/console/setconsolemode
// https://learn.microsoft.com/en-us/windows/console/high-level-console-modes
Console_Output_Mode :: enum_flags u32 {
- ENABLE_PROCESSED_OUTPUT; //
- ENABLE_WRAP_AT_EOL_OUTPUT; //
- ENABLE_VIRTUAL_TERMINAL_PROCESSING; //
- DISABLE_NEWLINE_AUTO_RETURN; //
- ENABLE_LVB_GRID_WORLDWIDE; //
+ ENABLE_PROCESSED_OUTPUT; // If set, ASCII control sequences are processed by the system.
+ ENABLE_WRAP_AT_EOL_OUTPUT; // If set, the cursor moves to the beginning of the next row when it reaches the end of the current row.
+ ENABLE_VIRTUAL_TERMINAL_PROCESSING; // If set, VT100 control sequences are processed by the system.
+ DISABLE_NEWLINE_AUTO_RETURN;
+ ENABLE_LVB_GRID_WORLDWIDE;
_UNUSED_0020_;
_UNUSED_0040_;
_UNUSED_0080_;
@@ -123,6 +106,23 @@
bSetFocus : BOOL;
}
+ // https://learn.microsoft.com/en-us/windows/console/readconsoleinput
+ ReadConsoleInputW :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> success: bool #foreign kernel32;
+
+ // https://learn.microsoft.com/windows/console/readconsole
+ ReadConsoleW :: (hConsoleInput: HANDLE, lpBuffer: LPVOID, nNumberOfchars_to_read: DWORD, lpNumberOfchars_read: LPVOID, pInputControl: LPVOID) -> success: bool #foreign kernel32;
+
+ // https://learn.microsoft.com/windows/console/flushconsoleinputbuffer
+ FlushConsoleInputBuffer :: (hConsoleInput: HANDLE) -> bool #foreign kernel32;
+
+ // https://learn.microsoft.com/windows/console/getnumberofconsoleinputevents
+ GetNumberOfConsoleInputEvents :: (hConsoleInput: HANDLE, lpcNumberOfEvents: LPDWORD) -> bool #foreign kernel32;
+
+ // https://learn.microsoft.com/en-us/windows/console/peekconsoleinput
+ PeekConsoleInputW :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> bool #foreign kernel32;
+
+////////////////////////////////////////////////////////////////////////////////
+
stdin: HANDLE;
initial_stdin_mode: u32;
raw_stdin_mode: Console_Input_Mode;
@@ -135,6 +135,7 @@
widechar_buffer: [512] u16;
+////////////////////////////////////////////////////////////////////////////////
peek_input :: inline () -> INPUT_RECORD, success := true {
record: INPUT_RECORD;
@@ -172,19 +173,19 @@ count_input :: inline () -> u32, success := true {
#scope_export
-OS_prepare_terminal :: () {
+OS_prepare_terminal :: () -> success := true {
// stdin
stdin = GetStdHandle(STD_INPUT_HANDLE);
if stdin == INVALID_HANDLE_VALUE {
error_code, error_string := get_error_value_and_string();
log_error("Invalid input handler: code %, %", error_code, error_string);
- return;
+ return false;
}
if xx GetConsoleMode(stdin, *initial_stdin_mode) == false {
error_code, error_string := get_error_value_and_string();
log_error("Failed to get input mode: code %, %", error_code, error_string);
- return;
+ return false;
}
raw_stdin_mode = (cast(Console_Input_Mode) initial_stdin_mode);
raw_stdin_mode |= (.ENABLE_VIRTUAL_TERMINAL_INPUT);
@@ -193,7 +194,7 @@ OS_prepare_terminal :: () {
if xx SetConsoleMode(stdin, xx raw_stdin_mode) == false {
error_code, error_string := get_error_value_and_string();
log_error("Failed to set input mode: code %, %", error_code, error_string);
- return;
+ return false;
}
// stdout
@@ -201,12 +202,12 @@ OS_prepare_terminal :: () {
if stdout == INVALID_HANDLE_VALUE {
error_code, error_string := get_error_value_and_string();
log_error("Invalid output handler: code %, %", error_code, error_string);
- return;
+ return false;
}
if xx GetConsoleMode(stdout, *initial_stdout_mode) == false {
error_code, error_string := get_error_value_and_string();
log_error("Failed to get output mode: code %, %", error_code, error_string);
- return;
+ return false;
}
raw_stdout_mode = (cast(Console_Output_Mode) initial_stdout_mode);
raw_stdout_mode |= (.ENABLE_VIRTUAL_TERMINAL_PROCESSING | .ENABLE_PROCESSED_OUTPUT | .ENABLE_WRAP_AT_EOL_OUTPUT);
@@ -214,7 +215,7 @@ OS_prepare_terminal :: () {
if xx SetConsoleMode(stdout, xx raw_stdout_mode) == false {
error_code, error_string := get_error_value_and_string();
log_error("Failed to set output mode: code %, %", error_code, error_string);
- return;
+ return false;
}
// Acording to [documentation](https://learn.microsoft.com/en-us/windows/win32/intl/code-pages)
@@ -223,26 +224,27 @@ OS_prepare_terminal :: () {
// As long we use the Unicode functions, we shouldn't need to set the code page to UTF8.
// SetConsoleCP(CP_UTF8);
// SetConsoleOutputCP(CP_UTF8);
+
+ return;
}
-OS_reset_terminal :: () {
+OS_reset_terminal :: () -> success := true {
if xx SetConsoleMode(stdin, initial_stdin_mode) == false {
error_code, error_string := get_error_value_and_string();
log_error("Failed to reset input mode: code %, %", error_code, error_string);
- return;
+ return false;
}
if xx SetConsoleMode(stdout, initial_stdout_mode) == false {
error_code, error_string := get_error_value_and_string();
log_error("Failed to reset output mode: code %, %", error_code, error_string);
- return;
+ return false;
}
+ return;
}
OS_flush_input :: inline () {
- /*
- This API is not recommended and does not have a virtual terminal equivalent.
- Attempting to empty the input queue all at once can destroy state in the queue in an unexpected manner.
- */
+ // This API is not recommended and does not have a virtual terminal equivalent.
+ // Attempting to empty the input queue all at once can destroy state in the queue in an unexpected manner.
if FlushConsoleInputBuffer(stdin) == false {
error_code, error_string := get_error_value_and_string();
log_error("Failed to flush input: code %, %", error_code, error_string);
@@ -318,13 +320,11 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success :
// 0: do not wait
// -1: wait indefinitely
OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool, success := true {
- /*
- The Windows API provides all input events (keyboard, mouse, window resize) on a single input buffer.
- To make it match this module's API, we need to do some pre-processing while waiting for input.
- This means that OS_wait_for_input will peek at the input events, signal if a window resize is found,
- and discard unwanted events (like button release events).
- A similar logic is applied in OS_read_input.
- */
+ // The Windows API provides all input events (keyboard, mouse, window resize) on a single input buffer.
+ // To make it match this module's API, we need to do some pre-processing while waiting for input.
+ // This means that OS_wait_for_input will peek at the input events, signal if a window resize is found,
+ // and discard unwanted events (like button release events).
+ // A similar logic is applied in OS_read_input.
expiration := current_time_monotonic() + to_apollo(timeout_milliseconds / 1000.0);
diff --git a/snake.jai b/snake.jai
index e3a15f6..465ae4d 100644
--- a/snake.jai
+++ b/snake.jai
@@ -136,8 +136,11 @@ main :: () {
GAME_OVER_TEXT :: "~ game over ~";
INSTRUCTIONS_TEXT :: "(esc to exit)";
+
+ seed: u64 = xx to_milliseconds(current_time_monotonic()) | 0x01; // Seed must be odd.
+ random_seed(seed);
- TUI.start();
+ assert(TUI.start(), "Failed to start TUI.");
TUI.set_cursor_position(1, 1);
write_string("Please enter player name: ");
@@ -158,5 +161,5 @@ main :: () {
if TUI.get_key() == TUI.Keys.Escape then break;
}
- TUI.stop();
+ assert(TUI.stop(), "Failed to stop TUI.");
}
diff --git a/ttt.jai b/ttt.jai
index 8f39b56..45dc7f8 100644
--- a/ttt.jai
+++ b/ttt.jai
@@ -866,7 +866,7 @@ initialize_tui :: () {
}
}
- TUI.start();
+ assert(TUI.start(), "Failed to start TUI.");
}
update_layout :: () {
@@ -1165,7 +1165,7 @@ main :: () {
print("- success\n", to_standard_error = true);
}
else {
- TUI.stop();
+ assert(TUI.stop(), "Failed to stop TUI.");
print("- ERROR: %", error_message, to_standard_error = true);
exit(1);
}
@@ -1178,19 +1178,19 @@ main :: () {
if perform_test && 1 {
print("TEST : set and get cursor position\n", to_standard_error = true);
- TUI.start();
+ assert(TUI.start(), "Failed to start TUI.");
X :: 2;
Y :: 3;
TUI.set_cursor_position(X, Y);
x, y := TUI.get_cursor_position();
- TUI.stop();
+ assert(TUI.stop(), "Failed to stop TUI.");
assert_result(x == X && y == Y, "Failed set/get cursor position.\n");
}
if perform_test && 1 {
print("TEST : module logger\n", to_standard_error = true);
log("- log: before module start.");
- TUI.start();
+ assert(TUI.start(), "Failed to start TUI.");
TUI.set_cursor_position(3, 3);
print("wait");
sleep_milliseconds(1000);
@@ -1198,14 +1198,14 @@ main :: () {
sleep_milliseconds(1000);
print(" a bit");
sleep_milliseconds(1000);
- TUI.stop();
+ assert(TUI.stop(), "Failed to stop TUI.");
log("- log: after module stop.");
}
if perform_test && 1 {
print("TEST : test key input\n", to_standard_error = true);
auto_release_temp();
- TUI.start();
+ assert(TUI.start(), "Failed to start TUI.");
TUI.clear_terminal();
TUI.set_cursor_position(1, 1);
write_string("Press q to exit, other key to print it to screen, wait 1s to see animation.");
@@ -1224,28 +1224,28 @@ main :: () {
write_string(TUI.to_string(key));
}
}
- TUI.stop();
+ assert(TUI.stop(), "Failed to stop TUI.");
print("- success\n", to_standard_error = true);
}
if perform_test && 1 {
print("TEST : draw box\n", to_standard_error = true);
auto_release_temp();
- TUI.start();
+ assert(TUI.start(), "Failed to start TUI.");
TUI.flush_input();
TUI.clear_terminal();
TUI.draw_box(1, 2, 5, 3);
TUI.set_cursor_position(1, 1);
print("Can you see the box below? (y/n)");
key := TUI.get_key();
- TUI.stop();
+ assert(TUI.stop(), "Failed to stop TUI.");
assert_result(key == #char "y", "Failed to draw box.\n");
}
if perform_test && 1 {
print("TEST : get terminal size\n", to_standard_error = true);
auto_release_temp();
- TUI.start();
+ assert(TUI.start(), "Failed to start TUI.");
TUI.clear_terminal();
width, height := TUI.get_terminal_size();
TUI.set_cursor_position(1, 1);
@@ -1254,13 +1254,13 @@ main :: () {
while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) {
key = TUI.get_key();
}
- TUI.stop();
+ assert(TUI.stop(), "Failed to stop TUI.");
assert_result(key == #char "y", "Failed to get terminal size.\n");
}
if perform_test && 1 {
print("TEST : set terminal title\n", to_standard_error = true);
- TUI.start();
+ assert(TUI.start(), "Failed to start TUI.");
title := "BAZINGA";
TUI.set_terminal_title(title);
TUI.set_cursor_position(1, 1);
@@ -1269,14 +1269,14 @@ main :: () {
while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) {
key = TUI.get_key();
}
- TUI.stop();
+ assert(TUI.stop(), "Failed to stop TUI.");
assert_result(key == #char "y", "Failed to set terminal title.\n");
}
if perform_test && 1 {
print("TEST : print keys and set terminal title\n", to_standard_error = true);
auto_release_temp();
- TUI.start();
+ assert(TUI.start(), "Failed to start TUI.");
TUI.set_terminal_title("bazinga");
key: TUI.Key = #char "d";
last_none_char := "X";
@@ -1335,13 +1335,13 @@ main :: () {
// set_temporary_storage_mark(__mark);
}
print("- success");
- TUI.stop();
+ assert(TUI.stop(), "Failed to stop TUI.");
}
if perform_test && 1 {
print("TEST : user input\n", to_standard_error = true);
auto_release_temp();
- TUI.start();
+ assert(TUI.start(), "Failed to start TUI.");
TUI.clear_terminal();
TUI.set_cursor_position(1, 1);
print("Enter some text (use Enter to finish, Esc to cancel, or resize to abort):");
@@ -1366,14 +1366,14 @@ main :: () {
}
}
answer := TUI.get_key();
- TUI.stop();
+ assert(TUI.stop(), "Failed to stop TUI.");
assert_result(answer == #char "y", error_message);
}
if perform_test && 1 {
print("TEST : hidden user input\n", to_standard_error = true);
auto_release_temp();
- TUI.start();
+ assert(TUI.start(), "Failed to start TUI.");
TUI.clear_terminal();
TUI.set_cursor_position(1, 1);
print("Enter some secret (use Enter to finish, Esc to cancel, or resize to abort):");
@@ -1397,7 +1397,7 @@ main :: () {
}
}
answer := TUI.get_key();
- TUI.stop();
+ assert(TUI.stop(), "Failed to stop TUI.");
assert_result(answer == #char "y", error_message);
}
@@ -2018,7 +2018,7 @@ main :: () {
TUI.get_key();
}
- TUI.stop();
+ assert(TUI.stop(), "Failed to stop TUI.");
exit(xx ifx error_saving then 1 else 0);
}