diff --git a/src/studio/screens/console.c b/src/studio/screens/console.c index 45c9373b6..ffea655d2 100644 --- a/src/studio/screens/console.c +++ b/src/studio/screens/console.c @@ -4003,7 +4003,9 @@ static void processConsoleCommand(Console* console) if(commandSize) { +#ifdef BAREMETALPI printf("%s", console->input.text); +#endif appendHistory(console, console->input.text); processCommand(console, console->input.text); } @@ -4271,6 +4273,26 @@ static void backspaceWord(Console* console) console->input.pos = pos; } +static void refreshTerminal(Console* console) +{ +#ifndef BAREMETALPI + if (!console->args.cli) + { + char dir[TICNAME_MAX]; + tic_fs_dir(console->fs, dir); + printf("\r\033[K"); + if(strlen(dir)) + printf("%s", dir); + printf(">%s", console->input.text); + + s32 tailLen = (s32)strlen(console->input.text) - (s32)console->input.pos; + if (tailLen > 0) + printf("\033[%dD", tailLen); + fflush(stdout); + } +#endif +} + static void processKeyboard(Console* console) { tic_mem* tic = console->tic; @@ -4356,6 +4378,7 @@ static void processKeyboard(Console* console) console->cursor.delay = CONSOLE_CURSOR_DELAY; } + refreshTerminal(console); } static void processGamepad(Console* console) @@ -4376,7 +4399,6 @@ static void tick(Console* console) tic_mem* tic = console->tic; processMouse(console); - processKeyboard(console); processGamepad(console); Start* start = getStartScreen(console->studio); @@ -4402,6 +4424,8 @@ static void tick(Console* console) else printBack(console, "\n loading cart..."); } + processKeyboard(console); + tic_api_cls(tic, TIC_COLOR_BG); drawConsoleText(console); @@ -4541,6 +4565,32 @@ static int apicmp(const void* a, const void* b) return strcmp(((const ApiItem*)a)->name, ((const ApiItem*)b)->name); } +// Handle terminal input events directly, bypassing the regular SDL event loop for some keys. +// This allows commands to be entered and executed even when the console is in the background. +void console_terminal_input(Console* console, tic_key key, char text) +{ + if(key == tic_key_return) + { + processConsoleCommand(console); + while(console->commands.current < console->commands.count) + { + const char* command = console->commands.items[console->commands.current]; + processCommand(console, command); + console->commands.current++; + } + } + else if(key == tic_key_backspace) processConsoleBackspace(console); + else if(key == tic_key_tab) processConsoleTab(console); + else if(key == tic_key_delete) processConsoleDel(console); + else if(key == tic_key_up) onHistoryUp(console); + else if(key == tic_key_down) onHistoryDown(console); + else if(key == tic_key_left) { if(console->input.pos > 0) console->input.pos--; } + else if(key == tic_key_right) { console->input.pos++; s32 len = (s32)strlen(console->input.text); if(console->input.pos > len) console->input.pos = (size_t)len; } + else if(text) insertInputText(console, (char[]){text, '\0'}); + + refreshTerminal(console); +} + void initConsole(Console* console, Studio* studio, tic_fs* fs, tic_net* net, Config* config, StartArgs args) { if(!console->text) console->text = malloc(CONSOLE_BUFFER_SIZE); diff --git a/src/studio/screens/console.h b/src/studio/screens/console.h index bea6ff548..4fdd9302a 100644 --- a/src/studio/screens/console.h +++ b/src/studio/screens/console.h @@ -116,4 +116,5 @@ struct Console void initConsole(Console*, Studio* studio, struct tic_fs* fs, struct tic_net* net, struct Config* config, StartArgs args); void freeConsole(Console* console); +void console_terminal_input(Console* console, tic_key key, char text); void forceAutoSave(Console* console, const char* cart_name); diff --git a/src/studio/screens/console_minimal.c b/src/studio/screens/console_minimal.c index ceb31918f..889a71bbb 100644 --- a/src/studio/screens/console_minimal.c +++ b/src/studio/screens/console_minimal.c @@ -124,3 +124,4 @@ void freeConsole(Console* console) } void forceAutoSave(Console* console, const char* cart_name) {} +void console_terminal_input(Console* console, tic_key key, char text) {} diff --git a/src/studio/studio.c b/src/studio/studio.c index 38d195545..7e0f0a47f 100644 --- a/src/studio/studio.c +++ b/src/studio/studio.c @@ -1385,6 +1385,11 @@ bool checkStudioViMode(Studio* studio, ViMode mode) { } #endif +void studio_terminal_input(Studio* studio, tic_key key, char text) +{ + console_terminal_input(studio->console, key, text); +} + static inline bool pointInRect(const tic_point* pt, const tic_rect* rect) { return (pt->x >= rect->x) diff --git a/src/studio/studio.h b/src/studio/studio.h index f43052fed..12b6e1218 100644 --- a/src/studio/studio.h +++ b/src/studio/studio.h @@ -227,6 +227,7 @@ void exitStudio(Studio* studio); void setStudioViMode(Studio* studio, ViMode mode); ViMode getStudioViMode(Studio* studio); bool checkStudioViMode(Studio* studio, ViMode mode); +void studio_terminal_input(Studio* studio, tic_key key, char text); void toClipboard(const void* data, s32 size, bool flip); bool fromClipboard(void* data, s32 size, bool flip, bool remove_white_spaces, bool sameSize); diff --git a/src/system/sdl/main.c b/src/system/sdl/main.c index ad3921898..f6144fb69 100644 --- a/src/system/sdl/main.c +++ b/src/system/sdl/main.c @@ -21,6 +21,7 @@ // SOFTWARE. #include "studio/system.h" +#include "studio/studio.h" #include "tools.h" #include "ext/fft.h" @@ -34,8 +35,15 @@ extern void gotoMenu(Studio* studio); #endif -#if defined(__TIC_LINUX__) +#if defined(__TIC_WINDOWS__) +#include +#include +#include +#elif defined(__TIC_LINUX__) || defined(__APPLE__) || defined(__TIC_MACOSX__) #include +#include +#include +#include #endif #if defined(CRT_SHADER_SUPPORT) @@ -339,7 +347,7 @@ static const u8* getSpritePtr(const tic_tile* tiles, s32 x, s32 y) return tiles[x / TIC_SPRITESIZE + y / TIC_SPRITESIZE * SheetCols].data; } -static u8 getSpritePixel(const tic_tile* tiles, s32 x, s32 y) +static u8 getSpritePixelLoc(const tic_tile* tiles, s32 x, s32 y) { return tic_tool_peek4(getSpritePtr(tiles, x, y), (x % TIC_SPRITESIZE) + (y % TIC_SPRITESIZE) * TIC_SPRITESIZE); } @@ -356,7 +364,7 @@ static void setWindowIcon() for(s32 j = 0, index = 0; j < Size; j++) for(s32 i = 0; i < Size; i++, index++) { - u8 color = getSpritePixel(studio_config(platform.studio)->cart->bank0.tiles.data, i/Scale, j/Scale); + u8 color = getSpritePixelLoc(studio_config(platform.studio)->cart->bank0.tiles.data, i/Scale, j/Scale); pixels[index] = color == ColorKey ? 0 : pal.data[color]; } @@ -410,7 +418,7 @@ static void drawKeyboardLabels(tic_mem* tic, s32 shift) } } -static void map2ram(tic_mem* tic) +static void map2ramLoc(tic_mem* tic) { memcpy(tic->ram->map.data, &studio_config(platform.studio)->cart->bank0.map, sizeof tic->ram->map); memcpy(tic->ram->tiles.data, &studio_config(platform.studio)->cart->bank0.tiles, sizeof tic->ram->tiles * TIC_SPRITE_BANKS); @@ -450,7 +458,7 @@ static void initTouchKeyboard() { memcpy(tic->ram->vram.palette.data, studio_config(platform.studio)->cart->bank0.palette.vbank0.data, sizeof(tic_palette)); tic_api_cls(tic, 0); - map2ram(tic); + map2ramLoc(tic); initTouchKeyboardState(tic, &platform.keyboard.touch.texture.up, &platform.keyboard.touch.texture.upPixels, false); initTouchKeyboardState(tic, &platform.keyboard.touch.texture.down, &platform.keyboard.touch.texture.downPixels, true); @@ -1263,6 +1271,111 @@ static void pollEvents() } } + // Poll terminal stdin input independently of the SDL event loop. + // This allows background command processing even when the GUI is busy or focused elsewhere. +#if defined(__TIC_WINDOWS__) + while (true) { + HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); + if (hIn == INVALID_HANDLE_VALUE) break; + + DWORD dwType = GetFileType(hIn); + bool hasInput = false; + + if (dwType == FILE_TYPE_CHAR) { // Console + if (_kbhit()) hasInput = true; + } else if (dwType == FILE_TYPE_PIPE) { // Pipe (Wine/MSYS) + DWORD totalBytes = 0; + if (PeekNamedPipe(hIn, NULL, 0, NULL, &totalBytes, NULL) && totalBytes > 0) + hasInput = true; + } + + if (!hasInput) break; + + int c; + if (dwType == FILE_TYPE_CHAR) { + c = _getch(); + } else { + char buf[1]; + DWORD readBytes = 0; + if (ReadFile(hIn, buf, 1, &readBytes, NULL) && readBytes > 0) + c = (unsigned char)buf[0]; + else break; + } + + if (c == 0 || c == 0xE0) { // 224 + if (dwType == FILE_TYPE_CHAR) { + c = _getch(); + if (c == 0x48) studio_terminal_input(platform.studio, tic_key_up, 0); // 72 + else if (c == 0x50) studio_terminal_input(platform.studio, tic_key_down, 0); // 80 + else if (c == 0x4D) studio_terminal_input(platform.studio, tic_key_right, 0); // 77 + else if (c == 0x4B) studio_terminal_input(platform.studio, tic_key_left, 0); // 75 + } + } + else if (c == '\r' || c == '\n') studio_terminal_input(platform.studio, tic_key_return, 0); + else if (c == '\b') studio_terminal_input(platform.studio, tic_key_backspace, 0); + else if (c == 0x7F) studio_terminal_input(platform.studio, tic_key_backspace, 0); // DEL as backspace + else if (c == '\t') studio_terminal_input(platform.studio, tic_key_tab, 0); + else if (c >= 32) studio_terminal_input(platform.studio, tic_key_unknown, (char)c); + } +#elif defined(__TIC_LINUX__) || defined(__APPLE__) || defined(__TIC_MACOSX__) + { + fd_set readfds; + struct timeval tv; + FD_ZERO(&readfds); + FD_SET(STDIN_FILENO, &readfds); + tv.tv_sec = 0; + tv.tv_usec = 0; + if (select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv) > 0) + { + char c; + if (read(STDIN_FILENO, &c, 1) > 0) + { + if (c == '\033') // Handle ANSI escape sequences for arrow keys + { + char seq[2]; + FD_ZERO(&readfds); + FD_SET(STDIN_FILENO, &readfds); + tv.tv_sec = 0; tv.tv_usec = 10000; + if (select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv) > 0) + { + if (read(STDIN_FILENO, &seq[0], 1) > 0 && seq[0] == '[') + { + FD_ZERO(&readfds); + FD_SET(STDIN_FILENO, &readfds); + if (select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv) > 0) + { + if (read(STDIN_FILENO, &seq[1], 1) > 0) + { + if (seq[1] == 'A') studio_terminal_input(platform.studio, tic_key_up, 0); + else if (seq[1] == 'B') studio_terminal_input(platform.studio, tic_key_down, 0); + else if (seq[1] == 'C') studio_terminal_input(platform.studio, tic_key_right, 0); + else if (seq[1] == 'D') studio_terminal_input(platform.studio, tic_key_left, 0); + } + } + } + } + } + else if (c == '\n' || c == '\r') + { + studio_terminal_input(platform.studio, tic_key_return, 0); + } + else if (c == '\b' || c == 0x7f) + { + studio_terminal_input(platform.studio, tic_key_backspace, 0); + } + else if (c == '\t') + { + studio_terminal_input(platform.studio, tic_key_tab, 0); + } + else + { + studio_terminal_input(platform.studio, tic_key_unknown, c); + } + } + } + } +#endif + processMouse(); #if defined(TOUCH_INPUT_SUPPORT) @@ -2138,11 +2251,92 @@ s32 main(s32 argc, char **argv) { #if defined(__TIC_WINDOWS__) { - CONSOLE_SCREEN_BUFFER_INFO info; - if(GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info) && !info.dwCursorPosition.X && !info.dwCursorPosition.Y) - FreeConsole(); + bool isWine = GetProcAddress(GetModuleHandleA("ntdll.dll"), "wine_get_version") != NULL; + HWND consoleWnd = GetConsoleWindow(); + + if (GetStdHandle(STD_OUTPUT_HANDLE) != INVALID_HANDLE_VALUE) + { + if (consoleWnd != NULL) + { + bool shouldHide = false; + + if (isWine) + { + // On Wine, terminal launches don't have a visible console window (Vis: 0). + // GUI launches (e.g. from KDE) create a visible virtual console (Vis: 1). + if (IsWindowVisible(consoleWnd)) + shouldHide = true; + } + else + { + // Native Windows: A fresh console window (GUI launch) is shared by only 1 process. + // Terminal launches (CMD/PowerShell) share the console with the shell (2+ processes). + DWORD procList[2]; + if (GetConsoleProcessList(procList, 2) == 1) + shouldHide = true; + } + + if (shouldHide) + { + FreeConsole(); + goto skip_console; + } + } + + // Standard stream redirection for attached consoles + if (freopen("CONIN$", "r", stdin) == NULL) + { + HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE); + if (hIn != INVALID_HANDLE_VALUE) + { + int fd = _open_osfhandle((intptr_t)hIn, _O_TEXT); + if (fd != -1) _dup2(fd, 0); + } + } + + if (freopen("CONOUT$", "w", stdout) == NULL) + { + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hOut != INVALID_HANDLE_VALUE) + { + int fd = _open_osfhandle((intptr_t)hOut, _O_TEXT); + if (fd != -1) _dup2(fd, 1); + } + } + + if (freopen("CONOUT$", "w", stderr) == NULL) + { + HANDLE hErr = GetStdHandle(STD_ERROR_HANDLE); + if (hErr != INVALID_HANDLE_VALUE) + { + int fd = _open_osfhandle((intptr_t)hErr, _O_TEXT); + if (fd != -1) _dup2(fd, 2); + } + } + + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + } + else + { + // Fallback for cases where no handles are available at all + if (consoleWnd != NULL && IsWindowVisible(consoleWnd)) + { + CONSOLE_SCREEN_BUFFER_INFO info; + if(GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info) && !info.dwCursorPosition.X && !info.dwCursorPosition.Y) + FreeConsole(); + } + } + skip_console:; } -#elif defined(__TIC_LINUX__) +#elif defined(__TIC_LINUX__) || defined(__APPLE__) || defined(__TIC_MACOSX__) + // Configure terminal to Raw Mode to capture input immediately without OS buffering. + struct termios oldt, newt; + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + signal(SIGPIPE, SIG_IGN); #endif @@ -2170,8 +2364,13 @@ s32 main(s32 argc, char **argv) ); #else + // Start TIC-80 and restore terminal mode on exit. + int ret = start(argc, argv, folder); - return start(argc, argv, folder); +#if defined(__TIC_LINUX__) || defined(__APPLE__) || defined(__TIC_MACOSX__) + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); +#endif + return ret; #endif }