#scope_file #import "Atomics"; #import "System"; #import "Windows"; // 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"; // https://learn.microsoft.com/windows/console/getconsolescreenbufferinfo GetConsoleScreenBufferInfo :: (hConsoleOutput: HANDLE, lpConsoleScreenBufferInfo: *CONSOLE_SCREEN_BUFFER_INFO) -> 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/windows/console/getconsolemode GetConsoleMode :: (hConsoleHandle: HANDLE, lpMode: *DWORD) -> BOOL #foreign kernel32; // https://learn.microsoft.com/windows/console/setconsolemode SetConsoleMode :: (hConsoleHandle: HANDLE, dwMode: DWORD) -> BOOL #foreign kernel32; // 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 { 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_; 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 // 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; // _UNUSED_0020_; _UNUSED_0040_; _UNUSED_0080_; } 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; raw_stdin_mode: Console_Input_Mode; stdout: HANDLE; initial_stdout_mode: u32; raw_stdout_mode: Console_Output_Mode; //////////////////////////////////////////////////////////////////////////////// // Resize detection 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 xx GetConsoleMode(stdin, *initial_stdin_mode) == false { print("Failed to get input mode.", to_standard_error = true); return; } 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 raw_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 xx GetConsoleMode(stdout, *initial_stdout_mode) == false { print("Failed to get output mode.", to_standard_error = true); return; } 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 raw_stdout_mode) == false { print("Failed to set output mode: %.", GetLastError(), to_standard_error = true); return; } } OS_reset_terminal :: () { if xx SetConsoleMode(stdin, initial_stdin_mode) == false { print("Failed to reset input mode: %.", GetLastError(), to_standard_error = true); return; } if xx SetConsoleMode(stdout, initial_stdout_mode) == false { print("Failed to reset output mode: %.", GetLastError(), to_standard_error = true); return; } } OS_flush_input :: inline () { // TODO - https://learn.microsoft.com/en-us/windows/console/flushconsoleinputbuffer // BOOL WINAPI FlushConsoleInputBuffer(_In_ HANDLE hConsoleInput); /* NOTE 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. */ } 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; } OS_wait_for_input :: (timeout_milliseconds: s32) -> is_input_available: bool { /* TODO Try to implement using: https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobjectex */ poll_return := WaitForSingleObjectEx(stdin, timeout_milliseconds, true); error_code, error_message := get_error_value_and_string(); // FIXME Not used. // 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 { return atomic_swap(*was_resized, false); // TODO If the windows implementation is similar, we may push this into the main module file. }