#+private #+build windows package console import "core:fmt" import "core:sys/windows" // https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences // https://github.com/odin-lang/Odin/blob/master/core/sys/windows/kernel32.odin SMALL_RECT :: struct { Left: windows.SHORT, Top: windows.SHORT, Right: windows.SHORT, Bottom: windows.SHORT, } COORD :: struct { X: windows.SHORT, Y: windows.SHORT, } CONSOLE_SCREEN_BUFFER_INFO :: struct { dwSize: COORD, dwCursorPosition: COORD, wAttributes: windows.WORD, srWindow: SMALL_RECT, dwMaximumWindowSize: COORD, } foreign import kernel32 "system:Kernel32.lib" @(default_calling_convention = "stdcall") foreign kernel32 { GetConsoleWindow :: proc() -> windows.HWND --- AllocConsole :: proc() -> windows.BOOL --- AttachConsole :: proc(dwProcessId: windows.DWORD) -> windows.BOOL --- SetConsoleCP :: proc(wCodePageID: windows.UINT) -> windows.BOOL --- } ATTACH_PARENT_PROCESS: i32 : -1 _Console :: struct { prev_output_mode: windows.DWORD, prev_input_mode: windows.DWORD, output_handle: windows.HANDLE, input_handle: windows.HANDLE, } should_quit_flag := false should_quit_handler :: proc "stdcall" (ctrl_type: windows.DWORD) -> windows.BOOL { switch ctrl_type { case windows.CTRL_C_EVENT, windows.CTRL_BREAK_EVENT, windows.CTRL_CLOSE_EVENT: should_quit_flag = true return windows.TRUE } return windows.FALSE } _create :: proc() -> Console { con: Console if !AttachConsole(transmute(windows.DWORD)ATTACH_PARENT_PROCESS) == windows.TRUE { AllocConsole() cx := windows.GetSystemMetrics(windows.SM_CXSCREEN) / 2 cy := windows.GetSystemMetrics(windows.SM_CYSCREEN) / 2 windows.MoveWindow(GetConsoleWindow(), cx - 640, cy - 360, 1280, 720, windows.TRUE) } con.output_handle = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) con.input_handle = windows.GetStdHandle(windows.STD_INPUT_HANDLE) windows.GetConsoleMode(con.output_handle, &con.prev_output_mode) windows.SetConsoleMode( con.output_handle, windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING, ) windows.SetConsoleOutputCP(.UTF8) windows.GetConsoleMode(con.input_handle, &con.prev_input_mode) windows.SetConsoleMode(con.input_handle, windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_VIRTUAL_TERMINAL_INPUT) SetConsoleCP(windows.CP_UTF8) _write(con, CURSOR_HIDE) con.width, con.height = _get_size(con) windows.SetConsoleCtrlHandler(should_quit_handler, windows.TRUE) return con } _destroy :: proc(con: ^Console) { _write(con^, CURSOR_SHOW) windows.SetConsoleMode(con.output_handle, con.prev_output_mode) windows.SetConsoleMode(con.input_handle, con.prev_input_mode) windows.FreeConsole() } _should_quit :: proc() -> bool { // FIXME: implement return false } _clear :: proc(con: Console) { _write(con, CLEAR) } _write :: proc(con: Console, msg: string) { windows.WriteConsoleW(con.output_handle, rawptr(windows.utf8_to_wstring(msg)), windows.DWORD(len(msg)), nil, nil) } _write_at :: proc(con: Console, msg: string, x, y: int) { _write(con, fmt.tprintf(CURSOR_POSITION + "%s", y, x, msg)) } _putch :: proc(con: Console, c: rune, x, y: int) { _write(con, fmt.tprintf(CURSOR_POSITION + "%c", y, x, c)) } _wait_for_input :: proc(con: Console) -> Keys { buffer: [256]u8 num_read: u32 windows.ReadConsoleW(con.input_handle, &buffer, len(buffer), &num_read, nil) // NOTE: Check two bytes due to W. if (buffer[0] == 27 && buffer[2] == 0) || buffer[0] == 3 || buffer[0] == 113 { // 27 = Escape, 3 = CTRL+C, 113 = q return .quit } else if buffer[0] == 32 { // 32 = Space return .forward } else { input := [?]u8{buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]} if (input == [?]u8{27, 0, 91, 0, 68, 0}) { return .back } else if (input == [?]u8{27, 0, 91, 0, 67, 0}) { return .forward } } return .unk } _get_size :: proc(con: Console) -> (width: int, height: int) { screen_buffer_info: windows.CONSOLE_SCREEN_BUFFER_INFO windows.GetConsoleScreenBufferInfo(con.output_handle, &screen_buffer_info) width = int(screen_buffer_info.dwSize.X) height = int(screen_buffer_info.dwSize.Y) return }