#+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
}