#scope_file #import "Windows"; #import "System"; // 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 kernel32 :: #system_library "kernel32"; // https://learn.microsoft.com/en-us/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/en-us/windows/console/getconsolemode GetConsoleMode :: (hConsoleHandle: HANDLE, lpMode: *u32) -> bool #foreign kernel32; // https://learn.microsoft.com/en-us/windows/console/setconsolemode SetConsoleMode :: (hConsoleHandle: HANDLE, dwMode: u32) -> bool #foreign kernel32; // https://learn.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror GetLastError :: () -> s32 #foreign kernel32; // https://learn.microsoft.com/en-us/windows/console/setconsolemode Console_Input_Mode :: enum_flags u32 { _UNUSED_0001_; 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_; ENABLE_MOUSE_INPUT; // 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. _UNUSED_0040_; _UNUSED_0080_; _UNUSED_0100_; ENABLE_VIRTUAL_TERMINAL_INPUT; // _UNUSED_0400_; _UNUSED_0800_; } // https://learn.microsoft.com/en-us/windows/console/setconsolemode 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; // _UNUSED_0020_; _UNUSED_0040_; _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; } SMALL_RECT :: struct { Left : SHORT; Top : SHORT; Right : SHORT; Bottom : SHORT; } CONSOLE_SCREEN_BUFFER_INFO :: struct { dwSize : COORD; dwCursorPosition : COORD; wAttributes : WORD; srWindow : SMALL_RECT; dwMaximumWindowSize : COORD; } stdin: HANDLE; initial_stdin_mode: u32; default_stdin_mode: Console_Input_Mode; blocking_stdin_mode: Console_Input_Mode; unblocking_stdin_mode: Console_Input_Mode; stdout: HANDLE; initial_stdout_mode: u32; default_stdout_mode: Console_Output_Mode; #scope_export OS_prepare_terminal :: () { print("TODO TUI\n", to_standard_error = true); // 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 { 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; if SetConsoleMode(stdin, xx default_stdin_mode) == false { print("Failed to set input mode: %.", GetLastError(), to_standard_error = true); return; } // stdout stdout = GetStdHandle(STD_OUTPUT_HANDLE); if stdout == INVALID_HANDLE_VALUE { print("Invalid output handler.", to_standard_error = true); return; } if 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; if SetConsoleMode(stdout, xx default_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 { print("Failed to reset input mode: %.", GetLastError(), to_standard_error = true); return; } if SetConsoleMode(stdout, initial_stdout_mode) == false { print("Failed to reset output mode: %.", GetLastError(), to_standard_error = true); return; } } 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; } // TODO Maybe we should use a NON-BLOCKING state by default... and only change to blocking when performing a HUMAN read...? OS_set_input_mode :: (mode: Input_Mode) { if mode == { case .HUMAN; assert(SetConsoleMode(stdin, xx blocking_stdin_mode), "Failed to set input mode to blocking mode."); // TODO get_error_value_and_string :: () -> (error_code: OS_Error_Code, description: string) case .MACHINE; assert(SetConsoleMode(stdin, xx unblocking_stdin_mode), "Failed to set input mode to unblocking mode."); // TODO get_error_value_and_string :: () -> (error_code: OS_Error_Code, description: string) case; // TODO ERROR } } 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; success := ReadConsoleA(stdin, buffer, cast(s32)bytes_to_read, *bytes_read); if success == false { _, error_message := get_error_value_and_string(); return -1, true, error_message; } return bytes_read; }