#scope_file #import "Basic"; #import "Atomics"; #import "System"; #import "Windows"; // 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 :: s32; LPDWORD :: *s32; PINPUT_RECORD :: *INPUT_RECORD; // 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"; // TODO Cleanup unused foreign procedures. // 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/readconsoleinput ReadConsoleInputA :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> success: bool #foreign kernel32; // https://learn.microsoft.com/windows/console/readconsole ReadConsoleA :: (hConsoleInput: HANDLE, lpBuffer: LPVOID, nNumberOfCharsToRead: DWORD, lpNumberOfCharsRead: LPVOID, pInputControl := LPVOID) -> success: 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-waitforsingleobject WaitForSingleObject :: (hHandle: HANDLE, dwMilliseconds: DWORD) -> s32 #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 PeekConsoleInputA :: (hConsoleInput: HANDLE, lpBuffer: PINPUT_RECORD, nLength: DWORD, lpNumberOfEventsRead: LPDWORD) -> bool #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. ENABLE_WINDOW_INPUT; 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; } INPUT_RECORD :: struct { EventType : INPUT_RECORD_EVENT_TYPE; union { KeyEvent : KEY_EVENT_RECORD; MouseEvent : MOUSE_EVENT_RECORD; WindowBufferSizeEvent : WINDOW_BUFFER_SIZE_RECORD; // These events are used internally and should be ignored. //MenuEvent : MENU_EVENT_RECORD; //FocusEvent : FOCUS_EVENT_RECORD; } //Event : EventUnion; } INPUT_RECORD_EVENT_TYPE :: enum s32 { KEY_EVENT :: 0x0001; MOUSE_EVENT :: 0x0002; WINDOW_BUFFER_SIZE_EVENT :: 0x0004; MENU_EVENT :: 0x0008; FOCUS_EVENT :: 0x0010; } KEY_EVENT_RECORD :: struct { bKeyDown : BOOL; wRepeatCount : WORD; wVirtualKeyCode : WORD; wVirtualScanCode : WORD; union { UnicodeChar : WCHAR; AsciiChar : CHAR; } dwControlKeyState : DWORD; } MOUSE_EVENT_RECORD :: struct { dwMousePosition : COORD; dwButtonState : DWORD; dwControlKeyState : DWORD; dwEventFlags : DWORD; } WINDOW_BUFFER_SIZE_RECORD :: struct { dwSize : 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 was_resized : bool; 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); */ } peek_input :: inline () -> INPUT_RECORD { record: INPUT_RECORD; records_read: s32; if PeekConsoleInputA(stdin, *record, 1, *records_read) == false { _, error_message := get_error_value_and_string(); assert(false, error_message); } return record; } read_input :: inline () -> INPUT_RECORD { record: INPUT_RECORD; records_read: s32; if ReadConsoleInputA(stdin, *record, 1, *records_read) == false { _, error_message := get_error_value_and_string(); assert(false, error_message); } return record; } count_input :: inline () -> s32 { count: s32; if GetNumberOfConsoleInputEvents(stdin, *count) == false { _, error_message := get_error_value_and_string(); assert(false, error_message); } return count; } //////////////////////////////////////////////////////////////////////////////// #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 () { /* 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. */ success := FlushConsoleInputBuffer(stdin); if success == false { _, error_message := get_error_value_and_string(); assert(false, error_message); // TODO A bit harsh arent we? } } 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; } // print(">%:%<", bytes_to_read, bytes_read); TODO DEBUG return bytes_read; } // timeout_milliseconds // 0: do not wait // -1: wait indefinitely OS_wait_for_input :: (timeout_milliseconds: s32 = -1) -> is_input_available: bool { /* TODO Try to implement using: https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobjectex This wait procedure on windows should check if next input is valid (keyboard down) before returning, otherwise it should discar that input and go back to wait (if more sleep is allowed). */ expiration := current_time_monotonic() + to_apollo(timeout_milliseconds / 1000.0); // 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 == WAIT_OBJECT_0 then true else false; while true { poll_return := WaitForSingleObject(stdin, timeout_milliseconds); // TODO Weird looking code... if poll_return == { case WAIT_TIMEOUT; return false; case WAIT_ABANDONED; print("MUTEX STUFF"); // https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject return false; case WAIT_FAILED; _, error_message := get_error_value_and_string(); assert(false, error_message); } // if poll_return != WAIT_OBJECT_0 then return false; // Discard invalid input until a valid input is found or no input is left. count := count_input(); while count > 0 { record := peek_input(); if record.EventType == .KEY_EVENT && record.KeyEvent.bKeyDown == true || record.EventType == .WINDOW_BUFFER_SIZE_EVENT { return true; } read_input(); // TODO Discard input. count -= 1; } now := current_time_monotonic(); if now >= expiration then return false; timeout_milliseconds = xx to_milliseconds(expiration - now); } return false; } OS_was_terminal_resized :: () -> bool { defer was_resized = false; // TODO Not using this flag... what happens if ... not sure what... :thinking: flag: = false; record := peek_input(); while record.EventType == .WINDOW_BUFFER_SIZE_EVENT { read_input(); record = peek_input(); flag = true; } //return was_resized; return flag; }