#import "Windows"; 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; ENABLE_VIRTUAL_TERMINAL_INPUT :: 0x0200; ENABLE_PROCESSED_OUTPUT :: 0x0001; ENABLE_WRAP_AT_EOL_OUTPUT :: 0x0002; ENABLE_VIRTUAL_TERMINAL_PROCESSING :: 0x0004; DISABLE_NEWLINE_AUTO_RETURN :: 0x0008; ENABLE_LVB_GRID_WORLDWIDE :: 0x0010; // https://learn.microsoft.com/en-us/windows/console/setconsolemode Console_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. _UNSED_0040_; _UNSED_0080_; _UNSED_0100_; ENABLE_VIRTUAL_TERMINAL_INPUT; // _UNSED_0400_; _UNSED_0800_; _UNSED_1000_; _UNSED_2000_; _UNSED_4000_; _UNSED_8000_; } // 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_Mode; blocking_stdin_mode: Console_Mode; unblocking_stdin_mode: Console_Mode; stdout: HANDLE; initial_stdout_mode: u32; default_stdout_mode: Console_Mode; 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_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_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; } OS_read_input :: (mode: Input_Mode) -> string { result: string = ---; if mode == { case .HUMAN; assert(SetConsoleMode(stdin, xx blocking_stdin_mode), "Failed to set input mode to blocking mode."); // @speed TODO Could check if was already set before applying. case .MACHINE; // assert(SetConsoleMode(stdin, xx unblocking_stdin_mode), "Failed to set input mode to unblocking mode."); // @speed TODO Could check if was already set before applying. if SetConsoleMode(stdin, xx unblocking_stdin_mode) == false { print("Failed to set input mode: %.", GetLastError(), to_standard_error = true); exit(0); } } MAX_BYTES_TO_READ :: 10; temp : [MAX_BYTES_TO_READ] u8; bytes_read : s32; if ReadConsoleA(stdin, temp.data, xx temp.count, *bytes_read) { // TODO If the number of bytes_read is equal to the buffer size... we may have some more data to read?! // ---> To fix this, we should check the last read byte and see if it's a newline (at least for HUMAN mode)! // TODO Maybe pass the input result in the temporary memory (unless specified otherwise)?! result.data = alloc(bytes_read); result.count = bytes_read; memcpy(result.data, temp.data, bytes_read); } assert(SetConsoleMode(stdin, xx default_stdin_mode), "Failed to set default input mode."); // @speed TODO Maybe compare current mode with default one before applying?! return result; };