#+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 { GetConsoleScreenBufferInfo :: proc(hConsoleOutput: windows.HANDLE, lpConsoleScreenBufferInfo: ^CONSOLE_SCREEN_BUFFER_INFO) -> windows.BOOL --- GetConsoleWindow :: proc() -> windows.HWND --- AllocConsole :: proc() -> windows.BOOL --- AttachConsole :: proc(dwProcessId: windows.DWORD) -> windows.BOOL --- FreeConsole :: proc() -> windows.BOOL --- SetConsoleCP :: proc(wCodePageID: windows.UINT) -> windows.BOOL --- SetConsoleOutputCP :: proc(wCodePageID: windows.UINT) -> windows.BOOL --- } ENABLE_ECHO_INPUT :: 0x0004 ENABLE_INSERT_MODE :: 0x0020 ENABLE_LINE_INPUT :: 0x000 ENABLE_MOUSE_INPUT :: 0x0010 ENABLE_PROCESSED_INPUT :: 0x0001 ENABLE_QUICK_EDIT_MODE :: 0x0040 ENABLE_WINDOW_INPUT :: 0x0008 ENABLE_VIRTUAL_TERMINAL_INPUT :: 0x0200 ENABLE_PROCESSED_OUTPUT :: 0x0001 ENABLE_WRAP_AT_EOL_OUTPUT :: 0x0002 ENABLE_VIRTUAL_TERMINAL_PROCESSING :: 0x0004 ATTACH_PARENT_PROCESS: i32 : -1 CODEPAGE_UTF8 :: 65001 Console :: struct { prev_output_mode: windows.DWORD, prev_input_mode: windows.DWORD, output_handle: windows.HANDLE, input_handle: windows.HANDLE, width: int, height: int, destroy: proc(con: ^Console), clear: proc(con: ^Console), write: proc(con: ^Console, msg: string), write_at: proc(con: ^Console, msg: string, x, y: int), putch: proc(con: ^Console, c: rune, x, y: int), putch_s: proc(con: ^Console, c: rune, x, y: int) -> string, wait_for_input: proc(con: ^Console) -> Keys, get_size: proc(con: ^Console) -> (int, int), } create :: proc() -> 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 := Console { output_handle = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE), input_handle = windows.GetStdHandle(windows.STD_INPUT_HANDLE), destroy = con_destroy, clear = con_clear, write = con_write, write_at = con_write_at, putch = con_putch, putch_s = con_putch_s, wait_for_input = con_wait_for_input, get_size = con_get_size, } windows.GetConsoleMode(con.output_handle, &con.prev_output_mode) windows.SetConsoleMode(con.output_handle, ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING) SetConsoleOutputCP(CODEPAGE_UTF8) windows.GetConsoleMode(con.input_handle, &con.prev_input_mode) windows.SetConsoleMode(con.input_handle, ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT) SetConsoleCP(CODEPAGE_UTF8) con->write(CURSOR_HIDE) con.width, con.height = con->get_size() return con } @(private) con_destroy :: proc(con: ^Console) { con->write(CURSOR_SHOW) windows.SetConsoleMode(con.output_handle, con.prev_output_mode) windows.SetConsoleMode(con.input_handle, con.prev_input_mode) FreeConsole() } @(private) con_clear :: proc(con: ^Console) { con->write(CLEAR) } @(private) con_write :: proc(con: ^Console, msg: string) { windows.WriteConsoleW(con.output_handle, windows.utf8_to_wstring(msg), windows.DWORD(len(msg)), nil, nil) } @(private) con_write_at :: proc(con: ^Console, msg: string, x, y: int) { con->write(fmt.tprintf(CURSOR_POSITION + "%s", y, x, msg)) } @(private) con_putch :: proc(con: ^Console, c: rune, x, y: int) { con->write(fmt.tprintf(CURSOR_POSITION + "%c", y, x, c)) } @(private) con_putch_s :: proc(con: ^Console, c: rune, x, y: int) -> string { return fmt.tprintf(CURSOR_POSITION + "%c", y, x, c) } @(private) con_wait_for_input :: proc(con: ^Console) -> Keys { buffer: [256]u8 num_read: u32 foo := windows.ReadConsoleW(con.input_handle, &buffer, len(buffer), &num_read, nil) _ = foo // 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 } @(private) con_get_size :: proc(con: ^Console) -> (width: int, height: int) { screen_buffer_info: CONSOLE_SCREEN_BUFFER_INFO GetConsoleScreenBufferInfo(con.output_handle, &screen_buffer_info) width = int(screen_buffer_info.dwSize.X) height = int(screen_buffer_info.dwSize.Y) return }