From e5d8eaa14407608a15a639da14bbea99dd8ef61a Mon Sep 17 00:00:00 2001 From: dam Date: Sun, 7 Jan 2024 01:28:21 +0000 Subject: Fixed Windows raw IO modes. --- TUI/module.jai | 62 +++++++++++++------------ TUI/windows.jai | 140 +++++++++++++++++++++++++++++++++++--------------------- ttt.jai | 4 +- 3 files changed, 122 insertions(+), 84 deletions(-) diff --git a/TUI/module.jai b/TUI/module.jai index df88f0c..21f1dd7 100644 --- a/TUI/module.jai +++ b/TUI/module.jai @@ -667,7 +667,13 @@ start :: () { input_string.count = 0; input_override = xx Keys.None; - write_strings(Commands.HideCursor, Commands.SaveCursorPosition, Commands.AlternateScreenBuffer, Commands.SetUTF8); + write_strings( + Commands.HideCursor, + Commands.SaveCursorPosition, + Commands.AlternateScreenBuffer, + Commands.SetUTF8, + Commands.CursorNormalMode, + Commands.KeypadNumMode); OS_prepare_terminal(); initialized = true; @@ -744,39 +750,37 @@ clear_terminal :: inline () { // TODO Maybe rename to "get_size()" get_terminal_size :: () -> rows: int, columns: int { assert_is_initialized(); + + auto_release_temp(); + rows, columns: int = ---; - #if OS == .WINDOWS { - rows, columns = OS_get_terminal_size(); - } - else { - auto_release_temp(); - flush_input(); - write_string(Commands.QueryWindowSizeInChars); - input := get_string(64,, temporary_allocator); - - // Expected response format: \e[8;;t - // where is the number of rows and of columns. - FORMAT :: "\e[8;;t"; - - // Discard head noise. - while input.count >= 3 && (input[0] != FORMAT[0] || input[1] != FORMAT[1] || input[2] != FORMAT[2]) { - advance(*input); - } + flush_input(); + write_string(Commands.QueryWindowSizeInChars); + input := get_string(64,, temporary_allocator); - // Discard tail noise. - while input.count >= 3 && input[input.count-1] != FORMAT[FORMAT.count-1] { - input.count -= 1; - } - - 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."); + // Expected response format: \e[8;;t + // where is the number of rows and of columns. + FORMAT :: "\e[8;;t"; + + // Discard head noise. + while input.count >= 3 && (input[0] != FORMAT[0] || input[1] != FORMAT[1] || input[2] != FORMAT[2]) { + advance(*input); + } - parts := split(input, ";",, temporary_allocator); - rows = parse_int(*parts[1]); - columns = parse_int(*parts[2]); + // Discard tail noise. + while input.count >= 3 && input[input.count-1] != FORMAT[FORMAT.count-1] { + input.count -= 1; } + + 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."); + + parts := split(input, ";",, temporary_allocator); + rows = parse_int(*parts[1]); + columns = parse_int(*parts[2]); + return rows, columns; } diff --git a/TUI/windows.jai b/TUI/windows.jai index ed8c996..f704262 100644 --- a/TUI/windows.jai +++ b/TUI/windows.jai @@ -4,33 +4,42 @@ #import "System"; #import "Windows"; -// https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences -// https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#designate-character-set -// https://github.com/MicrosoftDocs/Console-Docs/blob/main/docs/console-virtual-terminal-sequences.md - + // https://learn.microsoft.com/windows/win32/winprog/windows-data-types + LPVOID :: *void; + LPDWORD :: *s32; + BOOL :: bool; + SHORT :: s16; + WORD :: u16; + DWORD :: s32; +// https://learn.microsoft.com/windows/console/console-virtual-terminal-sequences +// https://learn.microsoft.com/windows/console/console-virtual-terminal-sequences#designate-character-set +// https://github.com/MicrosoftDocs/Console-Docs/blob/main/docs/console-virtual-terminal-sequences.md - kernel32 :: #system_library "kernel32"; + kernel32 :: #system_library "kernel32"; - // https://learn.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo + // https://learn.microsoft.com/windows/console/getconsolescreenbufferinfo GetConsoleScreenBufferInfo :: (hConsoleOutput: HANDLE, lpConsoleScreenBufferInfo: *CONSOLE_SCREEN_BUFFER_INFO) -> bool #foreign kernel32; - // https://learn.microsoft.com/en-us/windows/console/readconsole - ReadConsoleA :: (hConsoleInput: HANDLE, lpBuffer: *u8, nNumberOfCharsToRead: s32, lpNumberOfCharsRead: *s32, pInputControl := *void) -> bool #foreign kernel32; + // https://learn.microsoft.com/windows/console/readconsole + ReadConsoleA :: (hConsoleInput: HANDLE, lpBuffer: LPVOID, nNumberOfCharsToRead: DWORD, lpNumberOfCharsRead: LPVOID, pInputControl := LPVOID) -> bool #foreign kernel32; - // https://learn.microsoft.com/en-us/windows/console/getconsolemode - GetConsoleMode :: (hConsoleHandle: HANDLE, lpMode: *u32) -> bool #foreign kernel32; + // https://learn.microsoft.com/windows/console/getconsolemode + GetConsoleMode :: (hConsoleHandle: HANDLE, lpMode: *DWORD) -> BOOL #foreign kernel32; - // https://learn.microsoft.com/en-us/windows/console/setconsolemode - SetConsoleMode :: (hConsoleHandle: HANDLE, dwMode: u32) -> bool #foreign kernel32; + // https://learn.microsoft.com/windows/console/setconsolemode + SetConsoleMode :: (hConsoleHandle: HANDLE, dwMode: DWORD) -> BOOL #foreign kernel32; - // https://learn.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror + // https://learn.microsoft.com/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror GetLastError :: () -> s32 #foreign kernel32; + // https://learn.microsoft.com/windows/win32/api/synchapi/nf-synchapi-waitforsingleobjectex + WaitForSingleObjectEx :: (hHandle: HANDLE, dwMilliseconds: DWORD, bAlertable: BOOL) -> s32 #foreign kernel32; // 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 { - _UNUSED_0001_; + 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. _UNUSED_0008_; @@ -45,6 +54,7 @@ } // 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; // @@ -56,12 +66,6 @@ _UNUSED_0080_; } - - // https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types - SHORT :: s16; - WORD :: u16; - DWORD :: s32; - COORD :: struct { X : SHORT; Y : SHORT; @@ -85,36 +89,69 @@ stdin: HANDLE; initial_stdin_mode: u32; - default_stdin_mode: Console_Input_Mode; - blocking_stdin_mode: Console_Input_Mode; - unblocking_stdin_mode: Console_Input_Mode; + raw_stdin_mode: Console_Input_Mode; stdout: HANDLE; initial_stdout_mode: u32; - default_stdout_mode: Console_Output_Mode; + raw_stdout_mode: Console_Output_Mode; -#scope_export +//////////////////////////////////////////////////////////////////////////////// +// Resize detection -OS_prepare_terminal :: () { - print("TODO TUI\n", to_standard_error = true); +resize_handler :: (signal_code : s32) #c_call { + /* TODO + Try to implement using: + https://learn.microsoft.com/en-us/windows/console/reading-input-buffer-events + https://learn.microsoft.com/en-us/windows/console/readconsoleinput + https://learn.microsoft.com/en-us/windows/console/getnumberofconsoleinputevents + */ + new_context : Context; + push_context new_context { + if signal_code != SIGWINCH then return; + atomic_swap(*was_resized, true); + } +} +prepare_resize_handler :: () { + /* TODO + sa : sigaction_t; + sa.sa_handler = resize_handler; + sigemptyset(*(sa.sa_mask)); + sa.sa_flags = SA_RESTART; + sigaction(SIGWINCH, *sa, null); + */ +} +restore_resize_handler :: () { + /* TODO + sa : sigaction_t; + sa.sa_handler = SIG_DFL; + sigaction(SIGWINCH, null, *sa); + */ +} + +//////////////////////////////////////////////////////////////////////////////// + +#scope_export + +OS_prepare_terminal :: () { + // stdin stdin = GetStdHandle(STD_INPUT_HANDLE); if stdin == INVALID_HANDLE_VALUE { print("Invalid input handler.", to_standard_error = true); return; } - if GetConsoleMode(stdin, *initial_stdin_mode) == false { + if xx GetConsoleMode(stdin, *initial_stdin_mode) == false { print("Failed to get input mode.", to_standard_error = true); return; } - default_stdin_mode = (cast(Console_Input_Mode) initial_stdin_mode) | .ENABLE_VIRTUAL_TERMINAL_INPUT; - blocking_stdin_mode = default_stdin_mode | .ENABLE_LINE_INPUT | .ENABLE_ECHO_INPUT; - unblocking_stdin_mode = default_stdin_mode & ~.ENABLE_LINE_INPUT & ~.ENABLE_ECHO_INPUT; + raw_stdin_mode = (cast(Console_Input_Mode) initial_stdin_mode); + raw_stdin_mode |= (.ENABLE_VIRTUAL_TERMINAL_INPUT); + raw_stdin_mode &= ~(.ENABLE_LINE_INPUT | .ENABLE_PROCESSED_INPUT | .ENABLE_ECHO_INPUT); - if SetConsoleMode(stdin, xx default_stdin_mode) == false { + if SetConsoleMode(stdin, xx raw_stdin_mode) == false { print("Failed to set input mode: %.", GetLastError(), to_standard_error = true); return; } @@ -125,24 +162,25 @@ OS_prepare_terminal :: () { print("Invalid output handler.", to_standard_error = true); return; } - if GetConsoleMode(stdout, *initial_stdout_mode) == false { + if xx GetConsoleMode(stdout, *initial_stdout_mode) == false { print("Failed to get output mode.", to_standard_error = true); return; } - default_stdout_mode = (cast(Console_Output_Mode) initial_stdout_mode) | .ENABLE_PROCESSED_OUTPUT| .ENABLE_VIRTUAL_TERMINAL_PROCESSING; + 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); - if SetConsoleMode(stdout, xx default_stdout_mode) == false { + if SetConsoleMode(stdout, xx raw_stdout_mode) == false { print("Failed to set output mode: %.", GetLastError(), to_standard_error = true); return; } } OS_reset_terminal :: () { - if SetConsoleMode(stdin, initial_stdin_mode) == false { + if xx SetConsoleMode(stdin, initial_stdin_mode) == false { print("Failed to reset input mode: %.", GetLastError(), to_standard_error = true); return; } - if SetConsoleMode(stdout, initial_stdout_mode) == false { + if xx SetConsoleMode(stdout, initial_stdout_mode) == false { print("Failed to reset output mode: %.", GetLastError(), to_standard_error = true); return; } @@ -157,16 +195,6 @@ OS_flush_input :: inline () { */ } -OS_get_terminal_size :: () -> rows: int, columns: int { - - ScreenBufferInfo: CONSOLE_SCREEN_BUFFER_INFO; - GetConsoleScreenBufferInfo(stdout, *ScreenBufferInfo); - columns := ScreenBufferInfo.srWindow.Right - ScreenBufferInfo.srWindow.Left + 1; - rows := ScreenBufferInfo.srWindow.Bottom - ScreenBufferInfo.srWindow.Top + 1; - - return rows, columns; -} - OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, error: bool = false, error_message: string = "" { assert(bytes_to_read <= 0x7fff_ffff, "The Windows API only allows to read up to s32 bytes from the standard input."); bytes_read: s32; @@ -181,14 +209,20 @@ OS_read_input :: (buffer: *u8, bytes_to_read: s64) -> bytes_read: s64, error: bo OS_wait_for_input :: (timeout_milliseconds: s32) -> is_input_available: bool { /* TODO Try to implement using: - https://learn.microsoft.com/en-us/windows/console/reading-input-buffer-events - https://learn.microsoft.com/en-us/windows/console/readconsoleinput + https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobjectex */ - 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. + + poll_return := WaitForSingleObjectEx(stdin, timeout_milliseconds, true); error_code, error_message := get_error_value_and_string(); // FIXME Not used. - return ifx poll_return > 0 then true else false; + + // Possible values for poll_return TODO NOT BEING USED + WAIT_ABANDONED :: 0x00000080; + WAIT_IO_COMPLETION :: 0x000000C0; + WAIT_OBJECT_0 :: 0x00000000; + WAIT_TIMEOUT :: 0x00000102; + WAIT_FAILED :: 0xFFFFFFFF; + + return ifx poll_return == 0 then true else false; } OS_was_terminal_resized :: () -> bool { diff --git a/ttt.jai b/ttt.jai index 654cde4..4540699 100644 --- a/ttt.jai +++ b/ttt.jai @@ -1239,7 +1239,7 @@ main :: () { // TODO Test input - if 0 { + if 1 { print("TEST : set and get cursor position --\n", to_standard_error = true); TUI.start(); ROW :: 3; @@ -1251,7 +1251,7 @@ main :: () { print("> success\n", to_standard_error = true); } - if 0 { + if 1 { print("TEST : test key input --\n", to_standard_error = true); auto_release_temp(); TUI.start(); -- cgit v1.2.3