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