// TODO Change this into a module with subfiles windows.jai and unix.jai // #if OS == .WINDOWS { // #load "windows.jai"; // } else #if (OS == .LINUX) || (OS == .MACOS) { // #load "unix.jai"; // } else { // #assert(false, "Unsupported OS."); // } // TODO On OS validations, use .LINUX or .MACOS to allow MACOS users to enj... errmmm test this. #import "Basic"; #import "String"; // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#designate-character-set // https://github.com/MicrosoftDocs/Console-Docs/blob/main/docs/console-virtual-terminal-sequences.md isTUIActive := false; // TODO Rename this variable. Drawings :: struct { CornerBR :: "\x6A"; CornerTR :: "\x6B"; CornerTL :: "\x6C"; CornerBL :: "\x6D"; Cross :: "\x6E"; LineH :: "\x71"; TeeL :: "\x74"; TeeR :: "\x75"; TeeB :: "\x76"; TeeT :: "\x77"; LineV :: "\x78"; Blank :: "\x5F"; Diamond :: "\x60"; Checkerboard :: "\x61"; PlusMinus :: "\x67"; LessThanOrEqual :: "\x79"; GreaterThanOrEqual :: "\x7A"; Pi :: "\x7B"; NotEqual :: "\x7C"; CenteredDot :: "\x7E"; } Commands :: struct { EnterAlternateBuffer :: "\e[?1049h"; EnterMainBuffer :: "\e[?1049l"; EnterDrawingMode :: "\e(0"; EnterNormalMode :: "\e(B"; ClearScreen :: "\e[2J"; ClearLine :: "\e[2K"; RefreshWindow :: "\e[7t"; // TODO Not yet tested. SetUTF8 :: "\e%G"; // TODO TEST ME PLEASE // Cursor Visibility ShowCursor :: "\e[?25h"; HideCursor :: "\e[?25l"; StartBlinking :: "\e[?25h]"; StopBlinking :: "\e[?25l]"; SaveCursorPosition :: "\e7"; RestoreCursorPosition :: "\e8"; // Cursor Shape DefaultShape :: "\e[0 q"; BlinkingBlockShape :: "\e[1 q"; SteadyBlockShape :: "\e[2 q"; BlinkingUnderlineShape :: "\e[3 q"; SteadyUnderlineShape :: "\e[4 q"; BlinkingBarShape :: "\e[5 q"; SteadyBarShape :: "\e[6 q"; // Input Mode KeypadAppMode :: "\e="; KeypadNumMode :: "\e>"; CursorAppMode :: "\e[?1h"; CursorNormalMode :: "\e[?1l"; // Query State QueryCursorPosition :: "\e[6n"; // Emits the cursor position as: "ESC [ ; R" Where = row and = column. QueryDeviceAttributes :: "\e[0c"; QueryWindowSizeInChars :: "\e[18t"; // Emits the window size as: "ESC [ 8 ; t" Where = row and = column. } start :: () { #if OS == .LINUX { // TODO Required to do unlocking input. tcgetattr(STDIN_FILENO, *__term); term_new := __term; term_new.c_iflag &= 0xFFFFFA14;// ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); term_new.c_oflag &= 0xFFFFFFFE;// ~OPOST; term_new.c_lflag &= 0xFFFF7FB4;// ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); term_new.c_cflag &= 0xFFFFFECF;// ~(CSIZE | PARENB); term_new.c_cflag |= 0x00000030; tcsetattr(STDIN_FILENO, 0, *term_new); } else { print("TODO TUI\n", to_standard_error = true); // stdin stdin = GetStdHandle(STD_INPUT_HANDLE ); if stdin == INVALID_HANDLE_VALUE { print("Invalid input handler.", to_standard_error = true); return; } if GetConsoleMode(stdin, *initial_stdin_mode) == false { print("Failed to get input mode.", to_standard_error = true); return; } if SetConsoleMode(stdin, initial_stdin_mode | ENABLE_VIRTUAL_TERMINAL_INPUT) == false { print("Failed to set input mode: %.", GetLastError(), to_standard_error = true); return; } // stdout stdout = GetStdHandle(STD_OUTPUT_HANDLE); outMode: u32 = 0; if stdout == INVALID_HANDLE_VALUE { print("Invalid output handler.", to_standard_error = true); return; } if GetConsoleMode(stdout, *initial_stdout_mode) == false { print("Failed to get output mode.", to_standard_error = true); return; } if SetConsoleMode(stdout, initial_stdout_mode | ENABLE_PROCESSED_OUTPUT| ENABLE_VIRTUAL_TERMINAL_PROCESSING) == false { print("Failed to set output mode: %.", GetLastError(), to_standard_error = true); return; } } write_strings(Commands.HideCursor, Commands.SaveCursorPosition, Commands.EnterAlternateBuffer, Commands.SetUTF8); isTUIActive = true; } stop :: () { isTUIActive = false; write_strings(Commands.EnterMainBuffer, Commands.RestoreCursorPosition, Commands.ShowCursor); #if OS == .LINUX { tcsetattr(STDIN_FILENO, 0, *__term); // return echo } else { print("TODO TUI\n", to_standard_error = true); if SetConsoleMode(stdin, initial_stdin_mode) == false { print("Failed to reset input mode: %.", GetLastError(), to_standard_error = true); return; } if SetConsoleMode(stdout, initial_stdout_mode) == false { print("Failed to reset output mode: %.", GetLastError(), to_standard_error = true); return; } } } draw_box :: (x: int, y: int, width: int, height: int, to_standard_error := false) { // TODO Hardcoded box starting at 1,1... fix this! write_strings( // Commands.EnterNormalMode, Commands.EnterDrawingMode, "\e[1;1H", // Move to position 1,1 // TODO // Move pointer to top-left corner. Drawings.CornerTL, to_standard_error = to_standard_error); for 1..width-2 { write_string(Drawings.LineH, to_standard_error = to_standard_error); } write_string(Drawings.CornerTR, to_standard_error = to_standard_error); // TODO Take care of the temporary allocations. for idx: 2..height-1 { tmpL := tprint("\e[%;%H", idx, 1); tmpR := tprint("\e[%;%H", idx, width); write_strings( tmpL, Drawings.LineV, tmpR, Drawings.LineV, to_standard_error = to_standard_error); } tmpBL := tprint("\e[%;%H", height, 1); write_strings( tmpBL, Drawings.CornerBL, to_standard_error = to_standard_error); for 1..width-2 { write_string(Drawings.LineH, to_standard_error = to_standard_error); } write_string(Drawings.CornerBR, to_standard_error = to_standard_error); write_strings( // TODO // print Commands.EnterNormalMode, to_standard_error = to_standard_error); } clear_screen :: inline () { write_string(Commands.ClearScreen); } // get_cursor_position :: () -> row: int, column: int { // assert(isTUIActive, "TUI is not active."); // TODO // write_string(TUI.Commands.QueryCursorPosition); // Returned "\e[21;1R" // read_input() // } // read_input: () -> string; #if OS == .LINUX { #import "Basic"; #import "POSIX"; __term : My_Termios; My_Termios :: struct { c_iflag : u32; // Input mode flags. c_oflag : u32; // Output mode flags. c_cflag : u32; // Control modes flags. c_lflag : u32; // Local modes flags. c_line : u8; // Line discipline. c_cc : [32]u8; // Control characters. c_ispeed : u32; // Input speed (baud rates). c_ospeed : u32; // Output speed (baud rates). } // Required to do unlocking input. libc :: #system_library "libc"; // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcsetattr.c.html tcsetattr :: (fd : s32, optional_actions : s32, termios_p : *My_Termios) -> s32 #foreign libc; // https://codebrowser.dev/glibc/glibc/sysdeps/unix/sysv/linux/tcgetattr.c.html tcgetattr :: (fd : s32, termios_p : *My_Termios) -> s32 #foreign libc; read_input :: () -> string { buffer: [8192] u8; bytes_read := read(STDIN_FILENO, buffer.data, buffer.count-1); str := to_string(buffer.data, bytes_read); return str; }; get_buffer_size :: () -> rows: int, columns: int { buffer: [512] u8; write_string(Commands.QueryWindowSizeInChars); bytes_read := read(STDIN_FILENO, buffer.data, buffer.count-1); str := to_string(buffer.data, bytes_read); // Result: [8;79;156t assert( buffer.data[0] == #char "\e" && buffer.data[1] == #char "[" && buffer.data[2] == #char "8", "Query windows size in chars returned invalid response."); parts := split(str, ";"); rows := parse_int(*parts[1]); columns := parse_int(*parts[2]); return rows, columns; } } else #if OS == .WINDOWS { #import "Windows"; ENABLE_VIRTUAL_TERMINAL_INPUT :: 0x0200; ENABLE_PROCESSED_OUTPUT :: 0x0001; ENABLE_WRAP_AT_EOL_OUTPUT :: 0x0002; ENABLE_VIRTUAL_TERMINAL_PROCESSING :: 0x0004; DISABLE_NEWLINE_AUTO_RETURN :: 0x0008; ENABLE_LVB_GRID_WORLDWIDE :: 0x0010; SHORT :: s16; WORD :: u16; DWORD :: s32; 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; } kernel32 :: #system_library "kernel32"; GetConsoleScreenBufferInfo :: (hConsoleOutput: HANDLE, lpConsoleScreenBufferInfo: *CONSOLE_SCREEN_BUFFER_INFO) -> bool #foreign kernel32; ReadConsoleA :: (hConsoleHandle: HANDLE, buff : *u8, chars_to_read : s32, chars_read : *s32, lpInputControl := *void ) -> bool #foreign kernel32; // ReadConsole :: (hConsoleInput: HANDLE, lpBuffer: *u8, nNumberOfCharsToRead: s32, lpNumberOfCharsRead: *s32, pInputControl := *void) -> bool #foreign kernel32; GetConsoleMode :: (hConsoleHandle: HANDLE, lpMode: *u32) -> bool #foreign kernel32; SetConsoleMode :: (hConsoleHandle: HANDLE, dwMode: u32) -> bool #foreign kernel32; GetLastError :: () -> s32 #foreign kernel32; stdin: HANDLE; initial_stdin_mode: u32; stdout: HANDLE; initial_stdout_mode: u32; // #run read_input = () -> string { read_input :: () -> string { MAX_BYTES_TO_READ :: 1024; temp : [MAX_BYTES_TO_READ] u8; result: string = ---; bytes_read : s32; if !ReadConsoleA( stdin, temp.data, xx temp.count, *bytes_read ) return ""; result.data = alloc(bytes_read); result.count = bytes_read; memcpy(result.data, temp.data, bytes_read); return result; }; get_buffer_size :: () -> rows: int, columns: int { // GET WINDOW CHAR SIZE ScreenBufferInfo: CONSOLE_SCREEN_BUFFER_INFO; GetConsoleScreenBufferInfo(stdout, *ScreenBufferInfo); Size: COORD; Size.X = ScreenBufferInfo.srWindow.Right - ScreenBufferInfo.srWindow.Left + 1; Size.Y = ScreenBufferInfo.srWindow.Bottom - ScreenBufferInfo.srWindow.Top + 1; return Size.Y, Size.X; } }