From d828e742d7702c7e2698db4371012a49dfeb95d5 Mon Sep 17 00:00:00 2001 From: dam Date: Wed, 28 Feb 2024 00:03:27 +0000 Subject: Moved custom modules to newly supported local modules folder. --- modules/TUI/unix.jai | 286 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 modules/TUI/unix.jai (limited to 'modules/TUI/unix.jai') 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. +} -- cgit v1.2.3 From bb1d53daa526d734fb2e33c2090f8396f87544ec Mon Sep 17 00:00:00 2001 From: dam Date: Mon, 15 Apr 2024 16:50:42 +0100 Subject: Improved TUI logger; added snake example using TUI. --- modules/TUI/module.jai | 72 ++++++++++++----------- modules/TUI/unix.jai | 2 +- snake.jai | 157 +++++++++++++++++++++++++++++++++++++++++++++++++ ttt.jai | 10 ---- 4 files changed, 195 insertions(+), 46 deletions(-) create mode 100644 snake.jai (limited to 'modules/TUI/unix.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 070161c..b5866d5 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -59,38 +59,44 @@ Drawings :: struct { } Commands :: struct { + // Screen buffers AlternateScreenBuffer :: "\e[?1049h"; - MainScreenBuffer :: "\e[?1049l"; + MainScreenBuffer :: "\e[?1049l"; + // Device. + Bell :: "\x07"; + QueryDeviceAttributes :: "\e[0c"; + + // Draw/text. DrawingMode :: "\e(0"; TextMode :: "\e(B"; ClearScreen :: "\e[2J"; ClearLine :: "\e[2K"; ClearScrollBack :: "\e[3J"; - - Bell :: "\x07"; - + SetGraphicsRendition :: "\e[%m"; + + // Character encoding. + EncodingIEC2022 :: "\e%@"; + EncodingUTF8 :: "\e%G"; + + // Window. SetWindowTitle :: "\e]0;%\e\\"; - - RefreshWindow :: "\e[7t"; // TODO Not yet tested. Is this required? + RefreshWindow :: "\e[7t"; + QueryWindowSizeInChars :: "\e[18t"; - SetIEC2022 :: "\e%@"; // TODO Remove this!? - SetUTF8 :: "\e%G"; // TODO Remove this!? - - SetGraphicsRendition :: "\e[%m"; - - // Cursor Position + // Cursor position. + SaveCursorPosition :: "\e7"; + RestoreCursorPosition :: "\e8"; SetCursorPosition :: "\e[%;%H"; - - // Cursor Visibility + QueryCursorPosition :: "\e[6n"; + + // Cursor visibility. ShowCursor :: "\e[?25h"; HideCursor :: "\e[?25l"; - StartBlinking :: "\e[?25h"; - StopBlinking :: "\e[?25l"; - SaveCursorPosition :: "\e7"; - RestoreCursorPosition :: "\e8"; - - // Cursor Shape + StartBlinking :: "\e[?12h"; + StopBlinking :: "\e[?12l"; + + // Cursor shape DefaultShape :: "\e[0 q"; BlinkingBlockShape :: "\e[1 q"; SteadyBlockShape :: "\e[2 q"; @@ -98,17 +104,12 @@ Commands :: struct { SteadyUnderlineShape :: "\e[4 q"; BlinkingBarShape :: "\e[5 q"; SteadyBarShape :: "\e[6 q"; - - // Input Mode + + // Input mode. KeypadAppMode :: "\e="; KeypadNumMode :: "\e>"; CursorAppMode :: "\e[?1h"; CursorNormalMode :: "\e[?1l"; - - // Query State - QueryCursorPosition :: "\e[6n"; // Emits the cursor position as: "ESC [ ; R" Where = row and = column. - QueryDeviceAttributes :: "\e[0c"; - QueryWindowSizeInChars :: "\e[18t"; // Emits the window size as: "ESC [ 8 ; t" Where = row and = column. TODO Does not work on windows. } #if COLOR_MODE == 4 { @@ -150,12 +151,16 @@ else { Style :: struct { #if COLOR_MODE == 4 || COLOR_MODE == 8 { - background: Palette = .BLACK; - foreground: Palette = .WHITE; + background: Palette; + foreground: Palette; } else { background: Color_24b; foreground: Color_24b; } + + background = Palette.BLACK; + foreground = Palette.WHITE; + bold: bool; underline: bool; strike_through: bool; @@ -279,12 +284,9 @@ input_override : Key; previous_logger : (message: string, data: *void, info: Log_Info); module_logger :: (message: string, data: *void, info: Log_Info) { - // print("%0%0\n%0", Commands.MainScreenBuffer, message, Commands.AlternateScreenBuffer); - x, y := get_cursor_position(); - write_string(Commands.MainScreenBuffer); + write_strings(Commands.SaveCursorPosition, Commands.MainScreenBuffer); previous_logger(message, data, info); - write_string(Commands.AlternateScreenBuffer); - set_cursor_position(x, y); + write_strings(Commands.AlternateScreenBuffer, Commands.RestoreCursorPosition); } assert_is_active :: inline () { @@ -534,7 +536,7 @@ start :: () { Commands.HideCursor, Commands.SaveCursorPosition, Commands.AlternateScreenBuffer, - Commands.SetUTF8, + Commands.EncodingUTF8, Commands.CursorNormalMode, Commands.KeypadNumMode); diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index 861fe11..7103d47 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -7,7 +7,7 @@ // Required to do unlocking input. libc :: #system_library "libc"; - // TODO Remote this. + // TODO Remove this. // cfmakeraw :: (termios: *Terminal_IO_Mode) -> void #foreign libc; /* https://elixir.bootlin.com/glibc/glibc-2.28/source/termios/cfmakeraw.c#L22 void diff --git a/snake.jai b/snake.jai new file mode 100644 index 0000000..ea8926f --- /dev/null +++ b/snake.jai @@ -0,0 +1,157 @@ +#import "Basic"; +#import "Random"; +TUI :: #import "TUI"; + +Vec2D :: struct { + x: int; + y: int; +} + +operator == :: (a: Vec2D, b: Vec2D) -> bool { + return a.x == b.x && a.y == b.y; +} + +screen_size_x: int = ---; +screen_size_y: int = ---; +player_name: string = ---; + +main :: () { + + game_loop :: () { + + LOOP_PERIOD_MS :: 30; + + score := 0; + dir := Vec2D.{1, 0}; + food := Vec2D.{5, 5}; + + random_food :: () -> Vec2D { + return Vec2D.{ + cast(int)(random_get_zero_to_one_open() * (screen_size_x-3) + 2), + cast(int)(random_get_zero_to_one_open() * (screen_size_y-3) + 2) + }; + } + + snake_parts: [..] Vec2D; + for 0..13 array_add(*snake_parts, Vec2D.{3, 3}); + snake_parts[0].x += 1; + + TUI.flush_input(); + TUI.set_next_key(TUI.Keys.Resize); + timer := current_time_monotonic(); + while main_loop := true { + + timestamp := current_time_monotonic(); + key := TUI.get_key(LOOP_PERIOD_MS); + + if key == { + case TUI.Keys.Resize; + TUI.clear_terminal(); + screen_size_x, screen_size_y = TUI.get_terminal_size(); + TUI.draw_box(1, 1, screen_size_x, screen_size_y); + TUI.set_cursor_position(3, screen_size_y); + write_strings(" ", player_name, " "); + food = random_food(); + + case #char "q"; #through; + case #char "Q"; #through; + case TUI.Keys.Escape; + break main_loop; + + case TUI.Keys.Up; + if dir != Vec2D.{0, 1} then dir = Vec2D.{0, -1}; + + case TUI.Keys.Down; + if dir != Vec2D.{0, -1} then dir = Vec2D.{0, 1}; + + case TUI.Keys.Left; + if dir != Vec2D.{1, 0} then dir = Vec2D.{-1, 0}; + + case TUI.Keys.Right; + if dir != Vec2D.{-1, 0} then dir = Vec2D.{1, 0}; + } + + last_pos := snake_parts[snake_parts.count-1]; + + // Update position. + for < snake_parts.count-1..1 { + if snake_parts[it] != snake_parts[it-1] { + snake_parts[it] = snake_parts[it-1]; + } + } + snake_parts[0].x += dir.x; + snake_parts[0].y += dir.y; + + // Teleport on borders. + if snake_parts[0].x < 2 then snake_parts[0].x = screen_size_x - 1; + if snake_parts[0].x >= screen_size_x then snake_parts[0].x = 2; + if snake_parts[0].y < 2 then snake_parts[0].y = screen_size_y - 1; + if snake_parts[0].y >= screen_size_y then snake_parts[0].y = 2; + + // Check for game-over. + for 1..snake_parts.count-1 { + if snake_parts[it] == snake_parts[0] { + break main_loop; + } + } + + // Check for food. + if snake_parts[0] == food { + score += 1; + array_add(*snake_parts, snake_parts[snake_parts.count-1]); + food = random_food(); + } + + // Wait to match game loop time. + delta := to_milliseconds(current_time_monotonic() - timestamp); + if delta < LOOP_PERIOD_MS { + sleep_milliseconds(xx (LOOP_PERIOD_MS - delta)); + } + + // Draw snake. + write_string(TUI.Commands.DrawingMode); + TUI.set_cursor_position(last_pos.x, last_pos.y); + write_string(TUI.Drawings.Blank); + for snake_parts { + TUI.set_cursor_position(it.x, it.y); + write_string(TUI.Drawings.Checkerboard); + } + // Draw food. + { + TUI.using_style(TUI.Style.{ foreground = TUI.Palette.RED, bold = true, }); + TUI.set_cursor_position(food.x, food.y); + write_string(TUI.Drawings.Diamond); + } + write_string(TUI.Commands.TextMode); + + // Set score + TUI.set_cursor_position(3, 1); + print(" % ", score); + } + } + + GAME_OVER_TEXT :: "~ game over ~"; + INSTRUCTIONS_TEXT :: "(esc to exit)"; + + TUI.start(); + TUI.set_cursor_position(1, 1); + + write_string("Please enter player name: "); + player_name = TUI.read_input_line(64); + + while true { + game_loop(); + + // Game over screen. + TUI.draw_box(screen_size_x/3, screen_size_y/2-1, screen_size_x/3, 4); + TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, screen_size_y/2); + write_string(GAME_OVER_TEXT); + TUI.set_cursor_position((screen_size_x-GAME_OVER_TEXT.count)/2, screen_size_y/2+1); + write_string(INSTRUCTIONS_TEXT); + sleep_milliseconds(100); + TUI.flush_input(); + if TUI.get_key() == TUI.Keys.Escape then break; + } + + TUI.stop(); +} diff --git a/ttt.jai b/ttt.jai index 8dda204..8f39b56 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1191,7 +1191,6 @@ main :: () { print("TEST : module logger\n", to_standard_error = true); log("- log: before module start."); TUI.start(); - TUI.set_cursor_position(3, 3); print("wait"); sleep_milliseconds(1000); @@ -1199,15 +1198,6 @@ main :: () { sleep_milliseconds(1000); print(" a bit"); sleep_milliseconds(1000); - - #import "Windows"; - handle: HANDLE = ---; - initial_stdin_mode: u32; - if xx GetConsoleMode(handle, *initial_stdin_mode) == false { - error_code, error_string := get_error_value_and_string(); - log_error("- log: error code %, %", error_code, error_string); - } - TUI.stop(); log("- log: after module stop."); } -- cgit v1.2.3 From b8d743d5b307e0c230ebdb2fe314e041483d6a14 Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 16 Apr 2024 10:10:59 +0100 Subject: Adding error logging on TUI/unix. --- modules/TUI/module.jai | 8 ++- modules/TUI/unix.jai | 140 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 112 insertions(+), 36 deletions(-) (limited to 'modules/TUI/unix.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index b5866d5..5fd29f6 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -1,5 +1,7 @@ #module_parameters(COLOR_MODE := 24); +#scope_file + #if OS == { case .LINUX; #load "unix.jai"; @@ -22,6 +24,8 @@ assert(input_buffer.count >= KEY_SIZE, "The input buffer size must be capable to hold an entire Key, which should be able to hold an UTF8 code (4 bytes) or a terminal escape code (6 bytes)."); } +#scope_export; + // Special Graphics Characters Drawings :: struct { Blank :: "\x5F"; @@ -441,7 +445,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line x, y := get_cursor_position(); - write_strings(Commands.StartBlinking, Commands.BlinkingBarShape); + write_strings(Commands.ShowCursor, Commands.StartBlinking, Commands.BlinkingBarShape); key := Keys.None; while true { @@ -515,7 +519,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { } } - write_strings(Commands.StopBlinking, Commands.DefaultShape); + write_strings(Commands.StopBlinking, Commands.DefaultShape, Commands.HideCursor); result := ifx key == Keys.Enter then str else ""; return result, key; diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index 7103d47..8eeb6c0 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -1,5 +1,10 @@ #scope_file +/* +TODO : then log all error on unix... +TODO : then do a good implementation of the libc functions about attributes... +*/ + #import "Atomics"; #import "System"; #import "POSIX"; @@ -7,22 +12,6 @@ // Required to do unlocking input. libc :: #system_library "libc"; - // TODO Remove 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 { @@ -82,10 +71,68 @@ } // 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 - // } + // tcgetattr :: (fd: s32, termios_p: *Terminal_IO_Mode) -> s32 #foreign libc; + 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 :: (fd: s32, queue_selector: s32) -> s32 #foreign libc; @@ -232,8 +279,15 @@ restore_resize_handler :: () { #scope_export -OS_prepare_terminal :: () { - tcgetattr(STDIN_FILENO, *initial_tio_mode); // TODO Log error using `log()` from jai/modules/Basic/Print.jai ? +OS_prepare_terminal :: () -> success := true { // On critical paths, we may use the #must for the success return value. + 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); + } + 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); @@ -242,28 +296,48 @@ OS_prepare_terminal :: () { 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. - + + error = tcsetattr(STDIN_FILENO, 0, *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 :: () { +OS_reset_terminal :: inline () -> success := true { restore_resize_handler(); - tcsetattr(STDIN_FILENO, 0, *initial_tio_mode); // TODO Log on error. + error := tcsetattr(STDIN_FILENO, 0, *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 () { +OS_flush_input :: inline () -> success := true { TCIFLUSH :: 0; // TODO Is this always zero in all systems? - tcflush(STDIN_FILENO, TCIFLUSH); + 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; } // 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 = "" { +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_message := get_error_value_and_string(); - return -1, true, error_message; + 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; } @@ -279,8 +353,6 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo 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. + return atomic_swap(*was_resized, false); } -- cgit v1.2.3 From d7c2c312fe2ac08cadc3534a0a35357ef50cca20 Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 30 Apr 2024 00:05:26 +0100 Subject: WIP : Add logger and cleanup TUI module. --- modules/TUI/module.jai | 38 ++++--- modules/TUI/unix.jai | 297 ++++++++++++++++++++++++++++-------------------- modules/TUI/windows.jai | 118 +++++++++---------- snake.jai | 7 +- ttt.jai | 42 +++---- 5 files changed, 285 insertions(+), 217 deletions(-) (limited to 'modules/TUI/unix.jai') 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); } -- cgit v1.2.3 From c7a4f1c0c98a1df1e683b3de0454b3d981519e9e Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 30 Apr 2024 02:31:41 +0100 Subject: WIP : OMG I add copied octal values from C which became decimal ones in jai. --- modules/TUI/unix.jai | 217 ++++++++++++++++++++++++++++++++------------------- ttt.jai | 1 + 2 files changed, 136 insertions(+), 82 deletions(-) (limited to 'modules/TUI/unix.jai') diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index a6cd467..bb22030 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -10,50 +10,45 @@ USE_LIBC :: true; #import "System"; #import "POSIX"; - // Set Attributes Actions. - // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-tcflow.h + // Queue selector used in tcflush(...). + // 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 - // 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 + QueueSelector :: enum s32 { #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. + 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. + TCIFLUSH :: 1; // Discard data received but not yet read. + TCOFLUSH :: 2; // Discard data written but not yet sent. + TCIOFLUSH :: 3; // Discard all pending data. } } + + // Optional actions used in tcsetattr(...). + // 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. + } + // Terminal control (struct termios). // 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; + Terminal_IO_Mode :: struct { + + #if OS == { + case .LINUX; NCCS :: 32; - case .MACOS; + 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. @@ -65,68 +60,126 @@ USE_LIBC :: true; } // Input modes. + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-c_iflag.h + // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html 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). + IGNBRK :: 0x00000001; // Ignore break condition. + BRKINT :: 0x00000002; // Signal interrupt on break. + IGNPAR :: 0x00000004; // Ignore characters with parity errors. + PARMRK :: 0x00000008; // Mark parity and framing errors. + INPCK :: 0x00000010; // Enable input parity check. + ISTRIP :: 0x00000020; // Strip 8th bit off characters. + INLCR :: 0x00000040; // Map NL to CR on input. + IGNCR :: 0x00000080; // Ignore CR. + ICRNL :: 0x00000100; // Map CR to NL on input. + + #if OS == { + + case .LINUX; + IXON :: 0x00000400; // Enable start/stop output control. + IXANY :: 0x00000800; // Any character will restart after stop. + IXOFF :: 0x00001000; // Enable start/stop input control. + + case .MACOS; + IXON :: 0x00000200; // Enable start/stop output control. + IXANY :: 0x00000400; // Any character will restart after stop. + IXOFF :: 0x00000800; // Enable start/stop input control. + + } } // Output modes. + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios.h + // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html 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. + #if OS == { + + case .LINUX; + OPOST :: 0x00000001; // Perform output processing. + ONLCR :: 0x00000004; // Map NL to CR-NL on output. + OCRNL :: 0x00000008; // Map CR to NL. + ONOCR :: 0x00000010; // Discard CR's when on column 0. + ONLRET :: 0x00000020; // Move to column 0 on NL. + OFILL :: 0x00000040; // Send fill characters for delays. + + case .MACOS; + OPOST :: 0x00000001; // Perform output processing. + ONLCR :: 0x00000002; // Map NL to CR-NL on output. + OCRNL :: 0x00000010; // Map CR to NL. + ONOCR :: 0x00000020; // Discard CR's when on column 0. + ONLRET :: 0x00000040; // Move to column 0 on NL. + OFILL :: 0x00000080; // Send fill characters for delays. + } } // Control modes. + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios.h + // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html 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; + #if OS == { + + case .LINUX; + CS5 :: 0x00000000; // 5 bits per byte. + CS6 :: 0x00000010; // 6 bits per byte. + CS7 :: 0x00000020; // 7 bits per byte. + CS8 :: 0x00000030; // 8 bits per byte. + CSIZE :: 0x00000030; // Number of bits per byte (mask). + CSTOPB :: 0x00000040; // Two stop bits instead of one. + CREAD :: 0x00000080; // Enable receiver. + PARENB :: 0x00000100; // Parity enable. + PARODD :: 0x00000200; // Odd parity instead of even. + HUPCL :: 0x00000400; // Hang up on last close. + CLOCAL :: 0x00000800; + + case .MACOS; + CS5 :: 0x00000000; // 5 bits per byte. + CS6 :: 0x00000100; // 6 bits per byte. + CS7 :: 0x00000200; // 7 bits per byte. + CS8 :: 0x00000300; // 8 bits per byte. + CSIZE :: 0x00000300; // Number of bits per byte (mask). + CSTOPB :: 0x00000400; // Two stop bits instead of one. + CREAD :: 0x00000800; // Enable receiver. + PARENB :: 0x00001000; // Parity enable. + PARODD :: 0x00002000; // Odd parity instead of even. + HUPCL :: 0x00004000; // Hang up on last close. + CLOCAL :: 0x00008000; + } } // Local modes. + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios.h + // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html 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. + #if OS == { + + case .LINUX; + ISIG :: 0x00000001; // Enable signals. + ICANON :: 0x00000002; // Do erase and kill processing. + ECHO :: 0x00000008; // Enable echo. + ECHOE :: 0x00000010; // Visual erase for ERASE. + ECHOK :: 0x00000020; // Echo NL after KILL. + ECHONL :: 0x00000040; // Echo NL even if ECHO is off. + NOFLSH :: 0x00000080; // Disable flush after interrupt. + TOSTOP :: 0x00000100; // Send SIGTTOU for background output. + IEXTEN :: 0x00008000; // Enable DISCARD and LNEXT. + + case .MACOS; + ISIG :: 0x00000080; // Enable signals. + ICANON :: 0x00000100; // Do erase and kill processing. + ECHO :: 0x00000008; // Enable echo. + ECHOE :: 0x00000002; // Visual erase for ERASE. + ECHOK :: 0x00000004; // Echo NL after KILL. + ECHONL :: 0x00000010; // Echo NL even if ECHO is off. + NOFLSH :: 0x80000000; // Disable flush after interrupt. + TOSTOP :: 0x00400000; // Send SIGTTOU for background output. + IEXTEN :: 0x00000400; // Enable DISCARD and LNEXT. + } } // Control Characters + TODO WIP + // LINUX : ??? + // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html Control_Chars :: enum u8 { VINTR :: 0; VQUIT :: 1; @@ -185,13 +238,13 @@ else { k_termios: __kernel_termios; cmd: u64; if optional_actions == { - case xx SetAttributesActions.TCSANOW; + case xx OptionalActions.TCSANOW; cmd = TCSETS; - case xx SetAttributesActions.TCSADRAIN; + case xx OptionalActions.TCSADRAIN; cmd = TCSETSW; - case xx SetAttributesActions.TCSAFLUSH; + case xx OptionalActions.TCSAFLUSH; cmd = TCSETSF; case; @@ -343,7 +396,7 @@ OS_prepare_terminal :: () -> success := true { 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); + error = tcsetattr(STDIN_FILENO, xx OptionalActions.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); @@ -357,7 +410,7 @@ OS_prepare_terminal :: () -> success := true { OS_reset_terminal :: inline () -> success := true { restore_resize_handler(); - error := tcsetattr(STDIN_FILENO, TCSANOW, *initial_tio_mode); + error := tcsetattr(STDIN_FILENO, xx OptionalActions.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); @@ -367,7 +420,7 @@ OS_reset_terminal :: inline () -> success := true { } OS_flush_input :: inline () -> success := true { - error := tcflush(STDIN_FILENO, TCIFLUSH); + error := tcflush(STDIN_FILENO, xx QueueSelector.TCIFLUSH); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to flush input: code %, %", error_code, error_string); diff --git a/ttt.jai b/ttt.jai index 45dc7f8..63317d7 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1956,6 +1956,7 @@ main :: () { // Coalesce similar tasks. case #char "c"; #through; case #char "C"; + // TODO Active task is lost... if (db.tasks.count <= 0) continue; TUI.using_style(action_style); if (prompt_user_key(selected_task_row, "Press enter to coalesce similar tasks.") != TUI.Keys.Enter) continue; -- cgit v1.2.3 From 2037cba123cb98f367024346d2d882a505fd961e Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 30 Apr 2024 12:54:13 +0100 Subject: Fixed TUI\unix from c_octal-to-jai mis-conversion. --- modules/TUI/unix.jai | 201 +++++++-------------------------------------------- unused.jai | 145 +++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 173 deletions(-) (limited to 'modules/TUI/unix.jai') diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index bb22030..e1c0b3a 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -1,11 +1,5 @@ #scope_file -/* -TODO : then do a good implementation of the libc functions about attributes... -*/ - -USE_LIBC :: true; - #import "Atomics"; #import "System"; #import "POSIX"; @@ -13,7 +7,7 @@ USE_LIBC :: true; // Queue selector used in tcflush(...). // 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 - QueueSelector :: enum s32 { + Queue_Selector :: enum s32 { #if OS == { case .LINUX; TCIFLUSH :: 0; // Discard data received but not yet read. @@ -30,7 +24,7 @@ USE_LIBC :: true; // Optional actions used in tcsetattr(...). // 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 { + Optional_Actions :: enum s32 { TCSANOW :: 0; // Change immediately. TCSADRAIN :: 1; // Change when pending output is written. TCSAFLUSH :: 2; // Flush pending input before changing. @@ -74,7 +68,7 @@ USE_LIBC :: true; ICRNL :: 0x00000100; // Map CR to NL on input. #if OS == { - + case .LINUX; IXON :: 0x00000400; // Enable start/stop output control. IXANY :: 0x00000800; // Any character will restart after stop. @@ -84,12 +78,11 @@ USE_LIBC :: true; IXON :: 0x00000200; // Enable start/stop output control. IXANY :: 0x00000400; // Any character will restart after stop. IXOFF :: 0x00000800; // Enable start/stop input control. - } } // Output modes. - // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios.h + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-c_oflag.h // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html Output_Modes :: enum_flags u32 { #if OS == { @@ -113,7 +106,7 @@ USE_LIBC :: true; } // Control modes. - // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios.h + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-c_cflag.h // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html Control_Modes :: enum u32 { #if OS == { @@ -147,64 +140,54 @@ USE_LIBC :: true; } // Local modes. - // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios.h + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-c_lflag.h // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html Local_Modes :: enum_flags u32 { #if OS == { case .LINUX; ISIG :: 0x00000001; // Enable signals. - ICANON :: 0x00000002; // Do erase and kill processing. + ICANON :: 0x00000002; // Canonical input (erase and kill processing). ECHO :: 0x00000008; // Enable echo. ECHOE :: 0x00000010; // Visual erase for ERASE. ECHOK :: 0x00000020; // Echo NL after KILL. ECHONL :: 0x00000040; // Echo NL even if ECHO is off. - NOFLSH :: 0x00000080; // Disable flush after interrupt. + NOFLSH :: 0x00000080; // Disable flush after interrupt or quit. TOSTOP :: 0x00000100; // Send SIGTTOU for background output. IEXTEN :: 0x00008000; // Enable DISCARD and LNEXT. case .MACOS; - ISIG :: 0x00000080; // Enable signals. - ICANON :: 0x00000100; // Do erase and kill processing. + ISIG :: 0x00000080; // Enable signals INTR, QUIT, [D]SUSP. + ICANON :: 0x00000100; // Canonicalize input lines. ECHO :: 0x00000008; // Enable echo. ECHOE :: 0x00000002; // Visual erase for ERASE. ECHOK :: 0x00000004; // Echo NL after KILL. ECHONL :: 0x00000010; // Echo NL even if ECHO is off. NOFLSH :: 0x80000000; // Disable flush after interrupt. - TOSTOP :: 0x00400000; // Send SIGTTOU for background output. + TOSTOP :: 0x00400000; // Stop background jobs from output. IEXTEN :: 0x00000400; // Enable DISCARD and LNEXT. } } // Control Characters - TODO WIP - // LINUX : ??? + // LINUX : https://sourceware.org/git/glibc.git -> ./sysdeps/unix/sysv/linux/bits/termios-c_cc.h // MACOS : https://opensource.apple.com/source/xnu/xnu-792/bsd/sys/termios.h.auto.html 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; - } - + // Unused consts: + // VINTR, VQUIT, VERASE, VKILL, VEOF, VSWTC, VSTART, VSTOP, VSUSP, VEOL, VREPRINT, VDISCARD, VWERASE, VLNEXT, VEOL2 + + #if OS == { -#if USE_LIBC { - // Required to do unlocking input. - libc :: #system_library "libc"; + case .LINUX; + VTIME :: 5; // Time-out value (tenths of a second) [!ICANON]. + VMIN :: 6; // Minimum number of bytes read at once [!ICANON]. + case .MACOS; + VTIME :: 17; // Time-out value (tenths of a second) [!ICANON]. + VMIN :: 16; // Minimum number of bytes read at once [!ICANON]. + } + } + // 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; @@ -213,134 +196,6 @@ USE_LIBC :: true; // 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 OptionalActions.TCSANOW; - cmd = TCSETS; - - case xx OptionalActions.TCSADRAIN; - cmd = TCSETSW; - - case xx OptionalActions.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); - } -} //////////////////////////////////////////////////////////////////////////////// @@ -396,7 +251,7 @@ OS_prepare_terminal :: () -> success := true { raw_tio_mode.c_cc[Control_Chars.VMIN] = 1; raw_tio_mode.c_cc[Control_Chars.VTIME] = 0; - error = tcsetattr(STDIN_FILENO, xx OptionalActions.TCSANOW, *raw_tio_mode); + error = tcsetattr(STDIN_FILENO, xx Optional_Actions.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); @@ -410,7 +265,7 @@ OS_prepare_terminal :: () -> success := true { OS_reset_terminal :: inline () -> success := true { restore_resize_handler(); - error := tcsetattr(STDIN_FILENO, xx OptionalActions.TCSANOW, *initial_tio_mode); + error := tcsetattr(STDIN_FILENO, xx Optional_Actions.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); @@ -420,7 +275,7 @@ OS_reset_terminal :: inline () -> success := true { } OS_flush_input :: inline () -> success := true { - error := tcflush(STDIN_FILENO, xx QueueSelector.TCIFLUSH); + error := tcflush(STDIN_FILENO, xx Queue_Selector.TCIFLUSH); if error { error_code, error_string := get_error_value_and_string(); log_error("Failed to flush input: code %, %", error_code, error_string); diff --git a/unused.jai b/unused.jai index 9e05904..0425f8d 100644 --- a/unused.jai +++ b/unused.jai @@ -37,6 +37,151 @@ print_database :: (db: Database) { ); } } + +// --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- // + +// Implementation of tcsetattr, tcgetattr, and tcflush using only 'ioctl' which is provided by jai. + +#if USE_LIBC { + + 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 Optional_Actions.TCSANOW; + cmd = TCSETS; + + case xx Optional_Actions.TCSADRAIN; + cmd = TCSETSW; + + case xx Optional_Actions.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); + } +} // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- // -- cgit v1.2.3 From 90d17cde7d92be5e2c1bedb97c8c8d5ca324a39d Mon Sep 17 00:00:00 2001 From: dam Date: Tue, 30 Apr 2024 18:32:28 +0100 Subject: Removed some 'inline' marks, and added some TODO entries. --- modules/TUI/module.jai | 5 +++-- modules/TUI/unix.jai | 6 +++--- modules/TUI/windows.jai | 10 +++++----- 3 files changed, 11 insertions(+), 10 deletions(-) (limited to 'modules/TUI/unix.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 121aa42..f8b8265 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -381,7 +381,7 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { read_input :: (count_limit: int = -1, terminators: .. u8) -> string { assert_is_active(); - assert(count_limit >= 0 || terminators.count > 0, "Infinite loop detected, aborting."); // TODO Maybe just return!? + assert(count_limit >= 0 || terminators.count > 0, "Infinite loop detected, aborting."); // TODO Maybe just return!? Or simply return success=false with #must if count_limit < 0 { builder: String_Builder; @@ -438,7 +438,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { Resize discards the input returning an empty string and a Resize key. */ - assert(count_limit >= 0, "Invalid value on count_limit parameter."); + assert(count_limit >= 0, "Invalid value on count_limit parameter."); // TODO Too agressive str := alloc_string(count_limit); str.count = 0; @@ -446,6 +446,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { // TODO Some of these may be nice to have: // > https://unix.stackexchange.com/questions/255707/what-are-the-keyboard-shortcuts-for-the-command-line + // At least... add the Ctrl+u to clear the entire line. x, y := get_cursor_position(); write_strings(Commands.ShowCursor, Commands.StartBlinking, Commands.BlinkingBarShape); diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index e1c0b3a..a8e0edf 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -263,7 +263,7 @@ OS_prepare_terminal :: () -> success := true { return; } -OS_reset_terminal :: inline () -> success := true { +OS_reset_terminal :: () -> success := true { restore_resize_handler(); error := tcsetattr(STDIN_FILENO, xx Optional_Actions.TCSANOW, *initial_tio_mode); if error { @@ -274,7 +274,7 @@ OS_reset_terminal :: inline () -> success := true { return; } -OS_flush_input :: inline () -> success := true { +OS_flush_input :: () -> success := true { error := tcflush(STDIN_FILENO, xx Queue_Selector.TCIFLUSH); if error { error_code, error_string := get_error_value_and_string(); @@ -314,6 +314,6 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo return ifx result > 0 then true else false; } -OS_was_terminal_resized :: () -> bool { +OS_was_terminal_resized :: inline () -> bool { return atomic_swap(*was_resized, false); } diff --git a/modules/TUI/windows.jai b/modules/TUI/windows.jai index 608266d..fb50e49 100644 --- a/modules/TUI/windows.jai +++ b/modules/TUI/windows.jai @@ -137,7 +137,7 @@ //////////////////////////////////////////////////////////////////////////////// -peek_input :: inline () -> INPUT_RECORD, success := true { +peek_input :: () -> INPUT_RECORD, success := true { record: INPUT_RECORD; records_read: u32; if PeekConsoleInputW(stdin, *record, 1, *records_read) == false { @@ -148,7 +148,7 @@ peek_input :: inline () -> INPUT_RECORD, success := true { return record; } -read_input :: inline () -> INPUT_RECORD, success := true { +read_input :: () -> INPUT_RECORD, success := true { record: INPUT_RECORD; records_read: u32; if ReadConsoleInputW(stdin, *record, 1, *records_read) == false { @@ -159,7 +159,7 @@ read_input :: inline () -> INPUT_RECORD, success := true { return record; } -count_input :: inline () -> u32, success := true { +count_input :: () -> u32, success := true { count: u32; if GetNumberOfConsoleInputEvents(stdin, *count) == false { error_code, error_string := get_error_value_and_string(); @@ -242,7 +242,7 @@ OS_reset_terminal :: () -> success := true { return; } -OS_flush_input :: inline () { +OS_flush_input :: () { // 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 { @@ -379,7 +379,7 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo return false; } -OS_was_terminal_resized :: () -> bool { +OS_was_terminal_resized :: inline () -> bool { while peek_input().EventType == .WINDOW_BUFFER_SIZE_EVENT { was_resized = true; read_input(); -- cgit v1.2.3 From 675b0a5fea60dc33a97b0dc1871bb9a0b61818cc Mon Sep 17 00:00:00 2001 From: dam Date: Sat, 4 May 2024 01:43:34 +0100 Subject: WIP : Cleanup TUI module. Finally decided to go with hard-asserts (vs soft-errors/logs). --- modules/TUI/module.jai | 70 ++++++-------- modules/TUI/tests.jai | 247 +++++++++++++++++++++++++++++++++++++++++++++++ modules/TUI/unix.jai | 2 +- ttt.jai | 255 +------------------------------------------------ 4 files changed, 282 insertions(+), 292 deletions(-) create mode 100644 modules/TUI/tests.jai (limited to 'modules/TUI/unix.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index 464b0d9..a6a3d11 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -120,6 +120,9 @@ Commands :: struct #type_info_none { CursorNormalMode :: "\e[?1l"; } +// TODO Check which procedures need the assert_is_active call. +// TODO Review the error messages on the asserts. + #if COLOR_MODE_BITS == 4 { #load "palette_4b.jai"; @@ -189,15 +192,15 @@ set_font_style :: inline (bold: bool, underline: bool = false, strike_through: b set_style :: (style: Style) { set_font_style(style.bold, style.underline, style.strike_through, style.negative); set_colors(style.foreground, style.background); - context.tui_style = style; + context.terminal_style = style; } -clear_style :: () { +clear_style :: inline () { write_string(#run sprint(Commands.SetGraphicsRendition, "0")); } using_style :: (style: Style) #expand { - __style := context.tui_style; + __style := context.terminal_style; set_style(style); `defer set_style(__style); } @@ -215,24 +218,23 @@ using_style :: (style: Style) #expand { Key :: u64; // Terminal key-codes have 1 to 6 bytes so we'll use 8 bytes. -to_key :: inline (str: $T) -> Key #modify { return T == ([]u8) || T == string; } { +to_key :: (str: $T) -> Key #modify { return T == ([]u8) || T == string; } { + assert(str.count <= KEY_SIZE, "Invalid argument passed to to_key(): 'str.count' must be less-than or equal to %, but it was %.", KEY_SIZE, str.count); + k: Key; - // #if DEBUG { - // assert(str.count <= 8); // TODO Add DEBUG to module parameters. - // } - for 0..str.count-1 #no_abc { + for 0..str.count-1 { k |= ((cast(u64)str[it]) << (it*8)); } return k; } -to_string :: inline (key: Key) -> string { // TODO FIXME TEMPORARY MEMORY - str := talloc_string(KEY_SIZE); +to_string :: (key: Key) -> string { + str := alloc_string(KEY_SIZE); str.count = 0; - while key != 0 #no_abc { - str[str.count] = xx key & 0xFF; - key >>= 8; + while key != 0 { str.count += 1; + str[str.count-1] = xx key & 0xFF; + key >>= 8; } return str; } @@ -280,7 +282,7 @@ Keys :: struct #type_info_none { F12 : Key : #run to_key("#f12"); } -#add_context tui_style: Style; +#add_context terminal_style: Style; active := false; @@ -297,7 +299,7 @@ module_logger :: (message: string, data: *void, info: Log_Info) { } assert_is_active :: inline () { - assert(active, "Please call TUI.setup_terminal() before using this procedure."); // TODO Improve error message... and maybe use the logger and return success=false flag + assert(active, "Please call setup_terminal() before using the module procedures."); } //////////////////////////////////////////////////////////////////////////////// @@ -309,6 +311,7 @@ set_next_key :: inline (key: Key) { input_override = key; } +// TODO Provide some documentation comments. get_key :: (timeout_milliseconds: s32 = -1) -> Key { assert_is_active(); @@ -377,15 +380,15 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { return to_key(to_parse); } -// TODO Review me and add some comments. -read_input :: (count_limit: int = -1, terminators: .. u8) -> string, success := true { +// If count_limit has a non-negative value it will be used as the limit to the number of bytes on the returned string. +// If any characters are provided in the terminators list, they will be used to scan and interrupt the input, including +// the terminator as the last character. +// At least one of the arguments must be properly setup to avoid an infinite-loop reading the input. +read_input :: (count_limit: int = -1, terminators: .. u8) -> string { assert_is_active(); - - if count_limit < 0 && terminators.count <= 0 { - log_error("Invalid arguments passed to read_input(): (%).\n", count_limit); // TODO Improve error message. - return "", false; - } - + assert(count_limit >= 0 || terminators.count > 0, "Invalid arguments passed to read_input() will result in infinite-loop."); + + // TODO Provide some documentation comments. if count_limit < 0 { builder: String_Builder; init_string_builder(*builder); @@ -439,13 +442,7 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string, success := // Resize discards the input returning an empty string and a Resize key. read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { assert_is_active(); - - // TODO If we pass success... then, does it make sense to return success=true on resize? - assert(count_limit >= 0, "Invalid value passed to count_limit."); - // if count_limit < 0 { - // log_error("Invalid arguments passed to read_input_line(): (%, %).\n", count_limit, is_visible); - // return "", Keys.None, false; - // } + assert(count_limit >= 0, "Invalid value passed to count_limit(): %.", count_limit); builder := String_Builder.{ allocator = temporary_allocator }; @@ -510,7 +507,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { if is_escape_code(key) continue; buff_idx := map_character_to_buffer_idx(str, idx); - key_str := to_string(key); + key_str := to_string(key,, allocator = temporary_allocator); // Make sure we have space to append the new character at the end (in case we're trying to do it). if buff_idx > count_limit - key_str.count then continue; @@ -538,7 +535,7 @@ read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { setup_terminal :: () -> success := true #must { if active == true return; - + input_string.data = input_buffer.data; input_string.count = 0; input_override = xx Keys.None; @@ -591,14 +588,9 @@ flush_input :: () { input_string.count = 0; } -// TODO What if we return success and fail when input arguments are invalid? draw_box :: (x: int, y: int, width: int, height: int) { - assert_is_active(); //TODO NOT NEEDED - - if x <= 0 || y <= 0 || width <= 1 || height <= 1 { - log_error("Invalid arguments passed to draw_box(): (%, %, %, %).\n", x, y, width, height); // TODO Improve error message. - return; - } + assert_is_active(); + assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): (%, %, %, %).\n", x, y, width, height); auto_release_temp(); diff --git a/modules/TUI/tests.jai b/modules/TUI/tests.jai new file mode 100644 index 0000000..fe213cb --- /dev/null +++ b/modules/TUI/tests.jai @@ -0,0 +1,247 @@ +// build: jai -import_dir ../ tests.jai +#import "Basic"; +TUI :: #import "TUI"; + +main :: () { + + assert_result :: (result: bool, error_message: string) { + if result == true { + print("- success\n", to_standard_error = true); + } + else { + assert(TUI.reset_terminal(), "Failed to reset TUI."); + print("- ERROR: %", error_message, to_standard_error = true); + exit(1); + } + } + + next_line :: inline () { + x, y := TUI.get_cursor_position(); + TUI.set_cursor_position(1, y+1); + } + + if 1 { + print("TEST : set and get cursor position\n", to_standard_error = true); + assert(TUI.setup_terminal(), "Failed to setup TUI."); + X :: 2; + Y :: 3; + TUI.set_cursor_position(X, Y); + x, y := TUI.get_cursor_position(); + assert(TUI.reset_terminal(), "Failed to reset TUI."); + assert_result(x == X && y == Y, "Failed set/get cursor position.\n"); + } + + if 1 { + print("TEST : module logger\n", to_standard_error = true); + log("- log: before module start."); + assert(TUI.setup_terminal(), "Failed to setup TUI."); + TUI.set_cursor_position(3, 3); + print("wait"); + sleep_milliseconds(500); + log("- log: while module is active."); + sleep_milliseconds(500); + print(" a bit"); + sleep_milliseconds(1000); + assert(TUI.reset_terminal(), "Failed to reset TUI."); + log("- log: after module stop."); + } + + if 1 { + print("TEST : test key input\n", to_standard_error = true); + auto_release_temp(); + assert(TUI.setup_terminal(), "Failed to setup 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."); + next_line(); + key: TUI.Key; + while(key != #char "q") { + key = TUI.get_key(1000); + if key == TUI.Keys.None { + write_string("-"); + } + else if key == TUI.Keys.Resize { + write_string("#"); + } + else { + // else if key >= 32 && key <= 128 then print_character(cast,force(u8)key) + write_string(TUI.to_string(key)); + } + } + assert(TUI.reset_terminal(), "Failed to reset TUI."); + print("- success\n", to_standard_error = true); + } + + if 1 { + print("TEST : draw box\n", to_standard_error = true); + auto_release_temp(); + assert(TUI.setup_terminal(), "Failed to setup 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(); + assert(TUI.reset_terminal(), "Failed to reset TUI."); + assert_result(key == #char "y", "Failed to draw box.\n"); + } + + if 1 { + print("TEST : get terminal size\n", to_standard_error = true); + auto_release_temp(); + assert(TUI.setup_terminal(), "Failed to setup TUI."); + TUI.clear_terminal(); + width, height := TUI.get_terminal_size(); + TUI.set_cursor_position(1, 1); + print("Is terminal size %x%? (y/n)", width, height); + key: TUI.Key = xx TUI.Keys.None; + while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) { + key = TUI.get_key(); + } + assert(TUI.reset_terminal(), "Failed to reset TUI."); + assert_result(key == #char "y", "Failed to get terminal size.\n"); + } + + if 1 { + print("TEST : set terminal title\n", to_standard_error = true); + assert(TUI.setup_terminal(), "Failed to setup TUI."); + title := "BAZINGA"; + TUI.set_terminal_title(title); + TUI.set_cursor_position(1, 1); + print("Is terminal title '%'? (y/n)", title); + key: TUI.Key = xx TUI.Keys.None; + while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) { + key = TUI.get_key(); + } + assert(TUI.reset_terminal(), "Failed to reset TUI."); + assert_result(key == #char "y", "Failed to set terminal title.\n"); + } + + if 1 { + print("TEST : print keys\n", to_standard_error = true); + auto_release_temp(); + assert(TUI.setup_terminal(), "Failed to setup TUI."); + key: TUI.Key = #char "d"; + last_none_char := "X"; + + width, height := TUI.get_terminal_size(); + TUI.clear_terminal(); + TUI.draw_box(1, 1, width, height); + drop_down := 0; + + while(key != #char "q") { + + if key == { + case TUI.Keys.None; { + TUI.set_cursor_position(2, 2); + last_none_char = ifx last_none_char == "X" then "+" else "X"; + write_strings(last_none_char, " (press q to exit)"); + } + + case TUI.Keys.Resize; #through; + case #char "c"; { + width, height = TUI.get_terminal_size(); + TUI.clear_terminal(); + TUI.draw_box(1, 1, width, height); + drop_down = 0; + } + + case; { + TUI.set_cursor_position(2, 3+drop_down); + str := TUI.to_string(key); + array_to_print: [..] string; + write_string(": "); + for 0..str.count-1 { + print("% ", FormatInt.{value = cast(u8)str[it], base=16}); + } + write_string(": "); + for 0..str.count-1 { + if str[it] == #char "\e" { + str[it] = #char "#"; + } + } + write_string(str); + write_string(" :"); + drop_down += 1; + } + } + + x := ifx width > 24 then width-24 else 1; + y := ifx height > 1 then height-1 else 1; + + TUI.set_cursor_position(x, y); + print("size = %x%\n", width, height); + key = TUI.get_key(1000); + + // __mark := get_temporary_storage_mark(); + // set_temporary_storage_mark(__mark); + } + print("- success"); + assert(TUI.reset_terminal(), "Failed to reset TUI."); + } + + if 1 { + print("TEST : user input\n", to_standard_error = true); + auto_release_temp(); + assert(TUI.setup_terminal(), "Failed to setup 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):"); + next_line(); + str, key := TUI.read_input_line(15); + TUI.set_cursor_position(1, 3); + error_message: string; + if key == { + case TUI.Keys.Escape; { + print("Have you pressed Esc? (y/n)"); + error_message = "Failed to read line on Esc."; + } + + case TUI.Keys.Resize; { + print("Have you resized the terminal? (y/n)"); + error_message = "Failed to read line on resize."; + + } + case; { + print("Have you entered '%'? (y/n)", str); + error_message = "Failed to read line."; + } + } + answer := TUI.get_key(); + assert(TUI.reset_terminal(), "Failed to reset TUI."); + assert_result(answer == #char "y", error_message); + } + + if 1 { + print("TEST : hidden user input\n", to_standard_error = true); + auto_release_temp(); + assert(TUI.setup_terminal(), "Failed to setup 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):"); + next_line(); + str, key := TUI.read_input_line(15, false); + TUI.set_cursor_position(1, 3); + error_message: string; + if key == { + case TUI.Keys.Escape; { + print("Have you pressed Esc? (y/n)"); + error_message = "Failed to read line on Esc."; + } + + case TUI.Keys.Resize; { + print("Have you resized the terminal? (y/n)"); + error_message = "Failed to read line on resize."; + } + case; { + print("Have you entered '%'? (y/n)", str); + error_message = "Failed to read line."; + } + } + answer := TUI.get_key(); + assert(TUI.reset_terminal(), "Failed to reset TUI."); + assert_result(answer == #char "y", error_message); + } + + // -- -- -- Testing TUI -- STOP +} diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index a8e0edf..940ac80 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -195,7 +195,7 @@ 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; + tcflush :: (fd: s32, queue_selector: s32) -> s32 #foreign libc; //////////////////////////////////////////////////////////////////////////////// diff --git a/ttt.jai b/ttt.jai index 3512d20..d471661 100644 --- a/ttt.jai +++ b/ttt.jai @@ -17,8 +17,7 @@ // - release : jai ttt.jai -quiet -x64 -release // - debug : jai ttt.jai -quiet -x64 -// #import "Basic"()(MEMORY_DEBUGGER=true); // TODO Remove after final debug sessions. This takes up ~30MB of RAM. -#import "Basic"; +#import "Basic"()(MEMORY_DEBUGGER=true); // TODO Remove after final debug sessions. This takes up ~30MB of RAM. #import "System"; #import "Sort"; #import "Math"; @@ -1107,7 +1106,7 @@ read_input_string :: (x: int, y: int, input_limit: int, padding: int = 0) -> val write_string(TUI.Commands.TextMode); TUI.set_cursor_position(x, y); - style_input := context.tui_style; + style_input := context.terminal_style; style_input.underline = true; TUI.using_style(style_input); @@ -1158,256 +1157,8 @@ prompt_user_key :: (y: int, message: string) -> TUI.Key { } main :: () { - - // -- -- -- Testing TUI -- START - - perform_test := false; - - assert_result :: (result: bool, error_message: string) { - if result == true { - print("- success\n", to_standard_error = true); - } - else { - assert(TUI.reset_terminal(), "Failed to reset TUI."); - print("- ERROR: %", error_message, to_standard_error = true); - exit(1); - } - } - - next_line :: inline () { - x, y := TUI.get_cursor_position(); - TUI.set_cursor_position(1, y+1); - } - - if perform_test && 1 { - print("TEST : set and get cursor position\n", to_standard_error = true); - assert(TUI.setup_terminal(), "Failed to setup TUI."); - X :: 2; - Y :: 3; - TUI.set_cursor_position(X, Y); - x, y := TUI.get_cursor_position(); - assert(TUI.reset_terminal(), "Failed to reset 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."); - assert(TUI.setup_terminal(), "Failed to setup TUI."); - TUI.set_cursor_position(3, 3); - print("wait"); - sleep_milliseconds(1000); - log("- log: while module is active."); - sleep_milliseconds(1000); - print(" a bit"); - sleep_milliseconds(1000); - assert(TUI.reset_terminal(), "Failed to reset TUI."); - log("- log: after module stop."); - } - - if perform_test && 1 { - print("TEST : test key input\n", to_standard_error = true); - auto_release_temp(); - assert(TUI.setup_terminal(), "Failed to setup 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."); - next_line(); - key: TUI.Key; - while(key != #char "q") { - key = TUI.get_key(1000); - if key == TUI.Keys.None { - write_string("-"); - } - else if key == TUI.Keys.Resize { - write_string("#"); - } - else { - // else if key >= 32 && key <= 128 then print_character(cast,force(u8)key) - write_string(TUI.to_string(key)); - } - } - assert(TUI.reset_terminal(), "Failed to reset TUI."); - print("- success\n", to_standard_error = true); - } - - if perform_test && 1 { - print("TEST : draw box\n", to_standard_error = true); - auto_release_temp(); - assert(TUI.setup_terminal(), "Failed to setup 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(); - assert(TUI.reset_terminal(), "Failed to reset 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(); - assert(TUI.setup_terminal(), "Failed to setup TUI."); - TUI.clear_terminal(); - width, height := TUI.get_terminal_size(); - TUI.set_cursor_position(1, 1); - print("Is terminal size %x%? (y/n)", width, height); - key: TUI.Key = xx TUI.Keys.None; - while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) { - key = TUI.get_key(); - } - assert(TUI.reset_terminal(), "Failed to reset 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); - assert(TUI.setup_terminal(), "Failed to setup TUI."); - title := "BAZINGA"; - TUI.set_terminal_title(title); - TUI.set_cursor_position(1, 1); - print("Is terminal title '%'? (y/n)", title); - key: TUI.Key = xx TUI.Keys.None; - while (key == xx TUI.Keys.None || key == xx TUI.Keys.Resize) { - key = TUI.get_key(); - } - assert(TUI.reset_terminal(), "Failed to reset 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(); - assert(TUI.setup_terminal(), "Failed to setup TUI."); - TUI.set_terminal_title("bazinga"); - key: TUI.Key = #char "d"; - last_none_char := "X"; - - width, height := TUI.get_terminal_size(); - TUI.clear_terminal(); - TUI.draw_box(1, 1, width, height); - drop_down := 0; - - while(key != #char "q") { - - if key == { - case TUI.Keys.None; { - TUI.set_cursor_position(2, 2); - last_none_char = ifx last_none_char == "X" then "+" else "X"; - write_string(last_none_char); - } - - case TUI.Keys.Resize; #through; - case #char "c"; { - width, height = TUI.get_terminal_size(); - TUI.clear_terminal(); - TUI.draw_box(1, 1, width, height); - drop_down = 0; - } - - case; { - TUI.set_cursor_position(2, 3+drop_down); - str := TUI.to_string(key); - array_to_print: [..] string; - for 0..str.count-1 { - tmp := tprint("%", FormatInt.{value = cast(u8)str[it], base=16},, temporary_allocator); - array_add(*array_to_print, tmp); - } - string_to_print := join(..array_to_print, separator = " "); - print(": % : ", string_to_print); - for 0..str.count-1 { - if str[it] == #char "\e" { - str[it] = #char "#"; - } - } - write_string(str); - write_string(" :"); - drop_down += 1; - } - } - - x := ifx width > 24 then width-24 else 1; - y := ifx height > 1 then height-1 else 1; - - TUI.set_cursor_position(x, y); - print("size = %x%\n", width, height); - key = TUI.get_key(1000); - - // __mark := get_temporary_storage_mark(); - // set_temporary_storage_mark(__mark); - } - print("- success"); - assert(TUI.reset_terminal(), "Failed to reset TUI."); - } - - if perform_test && 1 { - print("TEST : user input\n", to_standard_error = true); - auto_release_temp(); - assert(TUI.setup_terminal(), "Failed to setup 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):"); - next_line(); - str, key := TUI.read_input_line(15); - TUI.set_cursor_position(1, 3); - error_message: string; - if key == { - case TUI.Keys.Escape; { - print("Have you pressed Esc? (y/n)"); - error_message = "Failed to read line on Esc."; - } - - case TUI.Keys.Resize; { - print("Have you resized the terminal? (y/n)"); - error_message = "Failed to read line on resize."; - - } - case; { - print("Have you entered '%'? (y/n)", str); - error_message = "Failed to read line."; - } - } - answer := TUI.get_key(); - assert(TUI.reset_terminal(), "Failed to reset 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(); - assert(TUI.setup_terminal(), "Failed to setup 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):"); - next_line(); - str, key := TUI.read_input_line(15, false); - TUI.set_cursor_position(1, 3); - error_message: string; - if key == { - case TUI.Keys.Escape; { - print("Have you pressed Esc? (y/n)"); - error_message = "Failed to read line on Esc."; - } - - case TUI.Keys.Resize; { - print("Have you resized the terminal? (y/n)"); - error_message = "Failed to read line on resize."; - } - case; { - print("Have you entered '%'? (y/n)", str); - error_message = "Failed to read line."; - } - } - answer := TUI.get_key(); - assert(TUI.reset_terminal(), "Failed to reset TUI."); - assert_result(answer == #char "y", error_message); - } - - // -- -- -- Testing TUI -- STOP - - // defer report_memory_leaks(); // TODO Remove after final debug sessions. + defer report_memory_leaks(); // TODO Remove after final debug sessions. defer free_memory(); -- cgit v1.2.3 From 36af624cdd9cb54454587bfae21b30096986d22e Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 5 May 2024 02:34:33 +0100 Subject: WIP : Cleanup TUI module and improved assert/error messages. --- modules/TUI/module.jai | 137 ++++++++++++++++++++---------------------------- modules/TUI/unix.jai | 14 ++--- modules/TUI/windows.jai | 34 ++++++------ 3 files changed, 81 insertions(+), 104 deletions(-) (limited to 'modules/TUI/unix.jai') diff --git a/modules/TUI/module.jai b/modules/TUI/module.jai index ab2567d..07d121f 100644 --- a/modules/TUI/module.jai +++ b/modules/TUI/module.jai @@ -15,36 +15,46 @@ #assert(false, "Unsupported OS."); } +#if COLOR_MODE_BITS == { + case 4; + #load "palette_4b.jai"; + case 8; + #load "palette_8b.jai"; + case 24; + #load "palette_24b.jai"; + _; + assert(false, "Invalid COLOR_MODE_BITS. Valid values are 4, 8, or 24 (default)."); +} + #import "Basic"; #import "String"; #import "Thread"; #import "UTF8"; #load "key_map.jai"; +active := false; +input_override : Key; +input_string : string; +input_buffer : [1024] u8; + KEY_SIZE :: #run type_info(Key).runtime_size; -#run { - assert(COLOR_MODE_BITS == 4 || COLOR_MODE_BITS == 8 || COLOR_MODE_BITS == 24, "Invalid COLOR_MODE_BITS. Valid values are 4, 8, or 24 (default)."); - assert(input_buffer.count >= KEY_SIZE, "The input buffer size must be capable to hold an entire Key, which should be able to hold an UTF8 code (4 bytes) or a terminal escape code (6 bytes)."); -} +#assert(input_buffer.count >= KEY_SIZE); // The input buffer size must be capable to hold an entire Key. -active := false; -input_buffer : [1024] u8; -input_string : string; -input_override : Key; -previous_logger : (message: string, data: *void, info: Log_Info); +#scope_module -module_logger :: (message: string, data: *void, info: Log_Info) { - write_strings(Commands.SaveCursorPosition, Commands.MainScreenBuffer); - previous_logger(message, data, info); - write_strings(Commands.AlternateScreenBuffer, Commands.RestoreCursorPosition); -} assert_is_active :: inline () { assert(active, "Please call setup_terminal() to start using this module."); } +log_tui_error :: (format_string: string, args: .. Any) { + write_strings(Commands.SaveCursorPosition, Commands.MainScreenBuffer); + log_error(format_string, args); + write_strings(Commands.AlternateScreenBuffer, Commands.RestoreCursorPosition); +} + #scope_export; @@ -141,56 +151,7 @@ Commands :: struct #type_info_none { CursorNormalMode :: "\e[?1l"; } -// TODO Check which procedures need the assert_is_active call. -// TODO Review the error messages on the asserts. - -//////////////////////////////////////////////////////////////////////////////// - - -#scope_file - - -#if COLOR_MODE_BITS == 4 { - #load "palette_4b.jai"; - - set_colors :: inline (foreground: Palette, background: Palette) { - print( - #run sprint("%0%0", Commands.SetGraphicsRendition, Commands.SetGraphicsRendition), - cast(u8)foreground + 30, cast(u8)background + 40); - } -} -else #if COLOR_MODE_BITS == 8 { - #load "palette_8b.jai"; - - set_colors :: inline (foreground: Palette, background: Palette) { - print( - #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), - cast(u8)foreground, cast(u8)background); - } -} -else { - #load "palette_24b.jai"; - - set_colors :: inline (foreground: Color_24b, background: Color_24b) { - print( - #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), - foreground.r, foreground.g, foreground.b, - background.r, background.g, background.b); - } -} - -set_font_style :: inline (bold: bool, underline: bool = false, strike_through: bool = false, negative: bool = false) { - print( - #run sprint(Commands.SetGraphicsRendition, "%;%;%;%"), - ifx bold then 1 else 22, - ifx underline then 4 else 24, - ifx strike_through then 9 else 29, - ifx negative then 7 else 27); -} - - -#scope_export - +#add_context terminal_style: Style; Style :: struct { #if COLOR_MODE_BITS == 4 || COLOR_MODE_BITS == 8 { @@ -214,11 +175,32 @@ Style :: struct { negative: bool; } -#add_context terminal_style: Style; - set_style :: inline (style: Style) { - set_font_style(style.bold, style.underline, style.strike_through, style.negative); - set_colors(style.foreground, style.background); + print( + #run sprint(Commands.SetGraphicsRendition, "%;%;%;%"), + ifx style.bold then 1 else 22, + ifx style.underline then 4 else 24, + ifx style.strike_through then 9 else 29, + ifx style.negative then 7 else 27); + + #if COLOR_MODE_BITS == { + case 4; + print( + #run sprint("%0%0", Commands.SetGraphicsRendition, Commands.SetGraphicsRendition), + cast(u8)style.foreground + 30, cast(u8)style.background + 40); + + case 8; + print( + #run sprint(Commands.SetGraphicsRendition, "38;5;%;48;5;%"), + cast(u8)style.foreground, cast(u8)style.background); + + case 24; + print( + #run sprint(Commands.SetGraphicsRendition, "38;2;%;%;%;48;2;%;%;%"), + style.foreground.r, style.foreground.g, style.foreground.b, + style.background.r, style.background.g, style.background.b); + } + context.terminal_style = style; } @@ -286,7 +268,7 @@ Keys :: struct #type_info_none { } to_key :: (str: $T) -> Key #modify { return T == ([]u8) || T == string; } { - assert(str.count <= KEY_SIZE, "Invalid argument passed to to_key(): 'str.count' must be less-than or equal to %, but it was %.", KEY_SIZE, str.count); + assert(str.count <= KEY_SIZE, "Invalid arguments passed to to_key(): 'str' has more than % bytes and cannot be stored as a Key.", KEY_SIZE); k: Key; for 0..str.count-1 { @@ -325,9 +307,6 @@ setup_terminal :: () -> success := true #must { input_string.count = 0; input_override = xx Keys.None; - previous_logger = context.logger; - context.logger = module_logger; - setup_key_map(); write_strings( @@ -355,8 +334,6 @@ reset_terminal :: () -> success := true #must { if !OS_reset_terminal() then return false; - context.logger = previous_logger; - write_strings( Commands.MainScreenBuffer, Commands.RestoreCursorPosition, @@ -446,8 +423,8 @@ get_key :: (timeout_milliseconds: s32 = -1) -> Key { // At least one of the arguments must be properly setup to avoid an infinite-loop reading the input. read_input :: (count_limit: int = -1, terminators: .. u8) -> string { assert_is_active(); - assert(count_limit >= 0 || terminators.count > 0, "Invalid arguments passed to read_input() will result in infinite-loop."); - + assert(count_limit >= 0 || terminators.count > 0, "Invalid arguments passed to read_input(): when 'count_limit' is less-than 0 (ignored), you need to provide 'terminators' to avoid an infinite-loop."); + // Read until one of the terminator characters is found. // Since we don't know the resulting size of the returned string, we must keep the string builder growing. if count_limit < 0 { @@ -504,7 +481,7 @@ read_input :: (count_limit: int = -1, terminators: .. u8) -> string { // Resize discards the input returning an empty string and a Resize key. read_input_line :: (count_limit: int, is_visible: bool = true) -> string, Key { assert_is_active(); - assert(count_limit >= 0, "Invalid value passed to count_limit(): %.", count_limit); + assert(count_limit >= 0, "Invalid arguments passed to read_input_line(): 'count_limit' must be greater-than or equal to 0."); builder := String_Builder.{ allocator = temporary_allocator }; @@ -604,7 +581,7 @@ flush_input :: () { draw_box :: (x: int, y: int, width: int, height: int) { assert_is_active(); - assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): (%, %, %, %).\n", x, y, width, height); + assert(x > 0 && y > 0 && width > 1 && height > 1, "Invalid arguments passed to draw_box(): 'x' and 'y' must be greater-than 0; 'width' and 'height' must be greater-than 1."); auto_release_temp(); @@ -674,7 +651,7 @@ get_terminal_size :: () -> width: int, height: int { assert(input.count >= 3 && input[0] == FORMAT[0] && input[1] == FORMAT[1] && input[2] == FORMAT[2] && input[input.count-1] == FORMAT[FORMAT.count-1], - "Query window size in chars returned invalid response."); + "Failed to query window size: invalid response."); parts := split(input, ";",, temporary_allocator); rows = parse_int(*parts[1]); @@ -723,7 +700,7 @@ get_cursor_position :: () -> x: int, y: int { assert(input.count >= 2 && input[0] == FORMAT[0] && input[1] == FORMAT[1] && input[input.count-1] == FORMAT[FORMAT.count-1], - "Query cursor position returned invalid response."); + "Failed to query cursor position: invalid response."); advance(*input, 2); parts := split(input, ";",, temporary_allocator); diff --git a/modules/TUI/unix.jai b/modules/TUI/unix.jai index 940ac80..99cc61d 100644 --- a/modules/TUI/unix.jai +++ b/modules/TUI/unix.jai @@ -230,7 +230,7 @@ restore_resize_handler :: () { //////////////////////////////////////////////////////////////////////////////// -#scope_export +#scope_module OS_prepare_terminal :: () -> success := true { error: int = ---; @@ -238,7 +238,7 @@ OS_prepare_terminal :: () -> success := true { 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); + log_tui_error("Failed to get initial_tio_mode: code %, %", error_code, error_string); return false; } @@ -254,7 +254,7 @@ OS_prepare_terminal :: () -> success := true { error = tcsetattr(STDIN_FILENO, xx Optional_Actions.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); + log_tui_error("Failed to set raw_tio_mode: code %, %", error_code, error_string); return false; } @@ -268,7 +268,7 @@ OS_reset_terminal :: () -> success := true { error := tcsetattr(STDIN_FILENO, xx Optional_Actions.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); + log_tui_error("Failed to set initial_tio_mode: code %, %", error_code, error_string); return false; } return; @@ -278,7 +278,7 @@ OS_flush_input :: () -> success := true { error := tcflush(STDIN_FILENO, xx Queue_Selector.TCIFLUSH); if error { error_code, error_string := get_error_value_and_string(); - log_error("Failed to flush input: code %, %", error_code, error_string); + log_tui_error("Failed to flush input: code %, %", error_code, error_string); return false; } return; @@ -288,7 +288,7 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : 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); + log_tui_error("Failed to read input: code %, %", error_code, error_string); return 0, false; } return bytes_read; @@ -306,7 +306,7 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo 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); + log_tui_error("Unexpected error while waiting for input: code %, %", error_code, error_string); return false, false; } } diff --git a/modules/TUI/windows.jai b/modules/TUI/windows.jai index fb50e49..f8d8bc8 100644 --- a/modules/TUI/windows.jai +++ b/modules/TUI/windows.jai @@ -142,7 +142,7 @@ peek_input :: () -> INPUT_RECORD, success := true { records_read: u32; if PeekConsoleInputW(stdin, *record, 1, *records_read) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to peek input: code %, %", error_code, error_string); + log_tui_error("Failed to peek input: code %, %", error_code, error_string); return record, false; } return record; @@ -153,7 +153,7 @@ read_input :: () -> INPUT_RECORD, success := true { records_read: u32; if ReadConsoleInputW(stdin, *record, 1, *records_read) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to read input: code %, %", error_code, error_string); + log_tui_error("Failed to read input: code %, %", error_code, error_string); return record, false; } return record; @@ -163,7 +163,7 @@ count_input :: () -> u32, success := true { count: u32; if GetNumberOfConsoleInputEvents(stdin, *count) == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to count input: code %, %", error_code, error_string); + log_tui_error("Failed to count input: code %, %", error_code, error_string); return 0, false; } return count; @@ -171,7 +171,7 @@ count_input :: () -> u32, success := true { //////////////////////////////////////////////////////////////////////////////// -#scope_export +#scope_module OS_prepare_terminal :: () -> success := true { @@ -179,12 +179,12 @@ OS_prepare_terminal :: () -> success := true { 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); + log_tui_error("Invalid input handler: code %, %", error_code, error_string); 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); + log_tui_error("Failed to get input mode: code %, %", error_code, error_string); return false; } raw_stdin_mode = (cast(Console_Input_Mode) initial_stdin_mode); @@ -193,7 +193,7 @@ OS_prepare_terminal :: () -> success := true { 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); + log_tui_error("Failed to set input mode: code %, %", error_code, error_string); return false; } @@ -201,12 +201,12 @@ OS_prepare_terminal :: () -> success := true { stdout = GetStdHandle(STD_OUTPUT_HANDLE); if stdout == INVALID_HANDLE_VALUE { error_code, error_string := get_error_value_and_string(); - log_error("Invalid output handler: code %, %", error_code, error_string); + log_tui_error("Invalid output handler: code %, %", error_code, error_string); 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); + log_tui_error("Failed to get output mode: code %, %", error_code, error_string); return false; } raw_stdout_mode = (cast(Console_Output_Mode) initial_stdout_mode); @@ -214,7 +214,7 @@ OS_prepare_terminal :: () -> success := true { 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); + log_tui_error("Failed to set output mode: code %, %", error_code, error_string); return false; } @@ -231,12 +231,12 @@ OS_prepare_terminal :: () -> success := true { 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); + log_tui_error("Failed to reset input mode: code %, %", error_code, error_string); 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); + log_tui_error("Failed to reset output mode: code %, %", error_code, error_string); return false; } return; @@ -247,7 +247,7 @@ OS_flush_input :: () { // 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); + log_tui_error("Failed to flush input: code %, %", error_code, error_string); } } @@ -255,7 +255,7 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : S32_MAX :: 0x7fff_ffff; if (bytes_to_read > S32_MAX) { - log_error("The Windows API only allows to read up to 2^32 bytes from the standard input. Clamping input argument."); + log_tui_error("The Windows API only allows to read up to 2^32 bytes from the standard input. Clamping input argument."); bytes_to_read = S32_MAX; } @@ -288,14 +288,14 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, success : success = ReadConsoleW(stdin, widechar_view.data, chars_to_read, *widechar_view.count, null); if success == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to read console: code %, %", error_code, error_string); + log_tui_error("Failed to read console: code %, %", error_code, error_string); return 0, false; } result:, success = wide_to_utf8_new(widechar_view.data, xx widechar_view.count); if success == false { error_code, error_string := get_error_value_and_string(); - log_error("Failed to convert from wide to utf8: code %, %", error_code, error_string); + log_tui_error("Failed to convert from wide to UTF8: code %, %", error_code, error_string); return 0, false; } @@ -338,7 +338,7 @@ OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: boo if wait_result == WAIT_FAILED { error_code, error_string := get_error_value_and_string(); - log_error("Error while waiting for input: code %, %", error_code, error_string); + log_tui_error("Error while waiting for input: code %, %", error_code, error_string); return false, false; } -- cgit v1.2.3