diff --git a/CMakeLists.txt b/CMakeLists.txt index bbf59c8..2815141 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,14 +55,15 @@ add_executable(fxlink fxlink/modes/interactive.c fxlink/modes/list.c fxlink/modes/push.c - fxlink/modes/tui-interactive.c fxlink/modes/udisks2.c fxlink/tooling/libpng.c fxlink/tooling/sdl2.c fxlink/tooling/udisks2.c + fxlink/tui/commands.c fxlink/tui/input.c fxlink/tui/layout.c - fxlink/tui/render.c) + fxlink/tui/render.c + fxlink/tui/tui-interactive.c) target_link_libraries(fxlink PkgConfig::libpng PkgConfig::libusb PkgConfig::ncurses -lm) target_include_directories(fxlink PRIVATE diff --git a/fxlink/tui/commands.c b/fxlink/tui/commands.c new file mode 100644 index 0000000..be208c7 --- /dev/null +++ b/fxlink/tui/commands.c @@ -0,0 +1,240 @@ +//---------------------------------------------------------------------------// +// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. // +// |:::| Made by Lephe' as part of the fxSDK. // +// \___/ License: MIT // +//---------------------------------------------------------------------------// + +#include "tui.h" +#include +#include +#include +#include + +//--- +// Parsing utilities +//--- + +struct command { + int argc; + char const **argv; + char *data; +}; + +static struct command parse_command(char const *input) +{ + struct command cmd; + cmd.argc = 0; + cmd.argv = NULL; + cmd.data = malloc(strlen(input) + 1); + if(!cmd.data) + return cmd; + + char const *escapes1 = "\\nter"; + char const *escapes2 = "\\\n\t\e\r"; + + /* Whether a new word needs to be created at the next character */ + bool word_finished = true; + /* Offset into cmd.data */ + int i = 0; + + /* Read words eagerly, appending to cmd.data as we go */ + for(int j = 0; input[j]; j++) { + int c = input[j]; + + /* Stop words at spaces */ + if(isspace(c)) { + if(!word_finished) + cmd.data[i++] = 0; + word_finished = true; + continue; + } + + /* Translate escapes */ + if(c == '\\') { + char *p = strchr(escapes1, input[j+1]); + if(p) { + c = escapes2[p - escapes1]; + j++; + } + } + + /* Add a new word if necessary */ + if(word_finished) { + cmd.argv = realloc(cmd.argv, (++cmd.argc) * sizeof *cmd.argv); + cmd.argv[cmd.argc - 1] = cmd.data + i; + word_finished = false; + } + + /* Copy literals */ + cmd.data[i++] = c; + } + + cmd.data[i++] = 0; + return cmd; +} + +static void dump_command(struct command const *cmd) +{ + print(TUI.wConsole, "command(%d)", cmd->argc); + for(int i = 0; i < cmd->argc; i++) { + char const *arg = cmd->argv[i]; + print(TUI.wConsole, " '%s'(%d)", arg, (int)strlen(arg)); + } + print(TUI.wConsole, "\n"); +} + +static void free_command(struct command *cmd) +{ + free(cmd->argv); + free(cmd->data); +} + +/* Parse a list of arguments into strings/integers/etc. The format is a string + of argument specifiers, which can be: + s String + d Integer + * Other variadic arguments (integer holding first index) + (-- will probably be expanded later.) + Returns true if parsing succeeded, false otherwise (including if arguments + are missing) after printing an error message. */ +static bool parse_arguments(int argc, char const **argv, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + int i = 1; + char const *spec = fmt; + + while(*spec) { + if(i >= argc) { + fprint(TUI.wConsole, FMT_RED, "error: "); + print(TUI.wConsole, "too few arguments\n"); + goto failure; + } + + /* Parse an argument specifier */ + if(*spec == 's') { + char const **ptr = va_arg(args, char const **); + *ptr = argv[i]; + } + else if(*spec == 'd') { + int *ptr = va_arg(args, int *); + char *endptr; + long l = strtol(argv[i], &endptr, 0); + if(*endptr) { + fprint(TUI.wConsole, FMT_RED, "error: "); + print(TUI.wConsole, "not a valid integer: '%s'\n", argv[i]); + goto failure; + } + *ptr = l; + } + + spec++; + } + + va_end(args); + return true; + +failure: + va_end(args); + return false; +} + +static bool run_no_device(int argc, char const **argv) +{ + (void)argc; + (void)argv; + return false; +} + +static void run_gintctl_command(int argc, char const **argv, + struct fxlink_device *fdev) +{ + if(!strcmp(argv[0], "read-long")) { + int count = 0; + if(!parse_arguments(argc, argv, "d", &count)) + return; + if(count < 0 || count > 8192) + return; + + uint32_t *data = malloc(count * 4); + for(int i = 0; i < count; i++) + data[i] = i; + fxlink_device_start_bulk_OUT(fdev, + "gintctl", "echo-bounds", data, count * 4, true); + } +} + +static bool run_with_device(int argc, char const **argv, + struct fxlink_device *fdev) +{ + if(!strcmp(argv[0], "/echo")) { + int l = 5, j = 5; + for(int i = 1; i < argc; i++) + l += strlen(argv[i]) + 1; + + char *concat = malloc(l + 1); + strcpy(concat, "echo "); + for(int i = 1; i < argc; i++) { + strcpy(concat + j, argv[i]); + j += strlen(argv[i]); + concat[j++] = (i == argc - 1) ? '\n' : ' '; + } + concat[j] = '\0'; + + fxlink_device_start_bulk_OUT(fdev, + "fxlink", "command", concat, l, true); + } + else if(!strcmp(argv[0], "/identify")) { + fxlink_device_start_bulk_OUT(fdev, + "fxlink", "command", "identify", 8, false); + } + else if(!strcmp(argv[0], "gintctl")) { + if(argc <= 1) { + fprint(TUI.wConsole, FMT_RED, "error: "); + print(TUI.wConsole, "gintctl command needs sub-command\n"); + return true; + } + run_gintctl_command(argc-1, argv+1, fdev); + return true; + } + else + return false; + return true; +} + +void TUI_execute_command(char const *command) +{ + struct command cmd = parse_command(command); + (void)dump_command; + + /* Connected device (TODO: Use the "selected" device) */ + struct fxlink_device *fdev = NULL; + for(int i = 0; i < TUI.devices.count; i++) { + if(TUI.devices.devices[i].status == FXLINK_FDEV_STATUS_CONNECTED) { + fdev = &TUI.devices.devices[i]; + break; + } + } + + bool b = run_no_device(cmd.argc, cmd.argv); + if(b) { + free_command(&cmd); + return; + } + + /* The following commands require a connected device */ + if(!fdev) { + print(TUI.wConsole, "no connected device!\n"); + return; + } + print(TUI.wConsole, "using device %s (%s)\n", + fxlink_device_id(fdev), fdev->calc->serial); + + b = run_with_device(cmd.argc, cmd.argv, fdev); + if(!b) { + fprint(TUI.wConsole, FMT_RED, "error: "); + print(TUI.wConsole, "unrecognized command '%s'\n", cmd.argv[0]); + } + free_command(&cmd); +} diff --git a/fxlink/modes/tui-interactive.c b/fxlink/tui/tui-interactive.c similarity index 88% rename from fxlink/modes/tui-interactive.c rename to fxlink/tui/tui-interactive.c index 20fcc13..9103a80 100644 --- a/fxlink/modes/tui-interactive.c +++ b/fxlink/tui/tui-interactive.c @@ -5,17 +5,10 @@ //---------------------------------------------------------------------------// #include "../fxlink.h" -#include -#include -#include -#include -#include +#include "tui.h" #include #include -#include -#include -#include #include #include #include @@ -40,22 +33,7 @@ Application-specific commands: gintctl bench USB transfer speed benchmark */ -static struct TUIData { - /* SIGWINCH flag */ - bool resize_needed; - /* ncurses window panels */ - WINDOW *wStatus; - WINDOW *wLogs; - WINDOW *wTransfers; - WINDOW *wTextOutput; - WINDOW *wConsole; - /* Root box */ - struct fxlink_TUI_box *bRoot; - /* Application data */ - struct fxlink_pollfds polled_fds; - struct fxlink_device_list devices; - -} TUI = { 0 }; +struct TUIData TUI = { 0 }; //--- // TUI management and rendering @@ -421,50 +399,6 @@ static void handle_fxlink_log(int display_fmt, char const *str) wattroff(TUI.wLogs, attr); } -static void execute_command(char const *command) -{ - /* Connected device (TODO: Use the "selected" device) */ - struct fxlink_device *fdev = NULL; - for(int i = 0; i < TUI.devices.count; i++) { - if(TUI.devices.devices[i].status == FXLINK_FDEV_STATUS_CONNECTED) { - fdev = &TUI.devices.devices[i]; - break; - } - } - - /* The following commands require a connected device */ - if(!fdev) { - print(TUI.wConsole, "no connected device!\n"); - return; - } - print(TUI.wConsole, "using device %s (%s)\n", - fxlink_device_id(fdev), fdev->calc->serial); - - if(!strncmp(command, "/echo", 5)) { - int len = strlen(command+1); - fxlink_device_start_bulk_OUT(fdev, - "fxlink", "command", strdup(command+1), len, true); - return; - } - else if(!strcmp(command, "/identify")) { - fxlink_device_start_bulk_OUT(fdev, - "fxlink", "command", "identify", 8, false); - return; - } - else if(!strcmp(command, "gintctl read-long")) { - int count = 128; // 2048; - uint32_t *data = malloc(count * 4); - for(int i = 0; i < count; i++) - data[i] = i; - fxlink_device_start_bulk_OUT(fdev, - "gintctl", "echo-bounds", data, count * 4, true); - return; - } - - fprint(TUI.wConsole, FMT_RED, "error: "); - print(TUI.wConsole, "unrecognized command '%s'\n", command); -} - int main_tui_interactive(libusb_context *ctx) { if(!TUI_setup()) @@ -541,18 +475,29 @@ int main_tui_interactive(libusb_context *ctx) if(finished) { char *command = input.data; + bool refresh_all = false; + if(command[0] != 0) log_("command: '%s'\n", command); if(!strcmp(command, "")) {} else if(!strcmp(command, "q") || !strcmp(command, "quit")) break; - else - execute_command(command); + else { + TUI_execute_command(command); + refresh_all = true; + } + fxlink_TUI_input_free(&input); print(TUI.wConsole, "%s", prompt); fxlink_TUI_input_init(&input, TUI.wConsole, 16); - TUI_refresh_console(); + + if(refresh_all) { + TUI_render_all(false); + TUI_refresh_all(false); + } + else + TUI_refresh_console(); } } diff --git a/fxlink/tui/tui.h b/fxlink/tui/tui.h new file mode 100644 index 0000000..2258f67 --- /dev/null +++ b/fxlink/tui/tui.h @@ -0,0 +1,36 @@ +//---------------------------------------------------------------------------// +// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. // +// |:::| Made by Lephe' as part of the fxSDK. // +// \___/ License: MIT // +//---------------------------------------------------------------------------// +// fxlink.tui.tui: Global data and main functions for the interactive TUI + +#pragma once +#include +#include +#include +#include +#include + +#include +#include + +struct TUIData { + /* SIGWINCH flag */ + bool resize_needed; + /* ncurses window panels */ + WINDOW *wStatus; + WINDOW *wLogs; + WINDOW *wTransfers; + WINDOW *wTextOutput; + WINDOW *wConsole; + /* Root box */ + struct fxlink_TUI_box *bRoot; + /* Application data */ + struct fxlink_pollfds polled_fds; + struct fxlink_device_list devices; +}; + +extern struct TUIData TUI; + +void TUI_execute_command(char const *command);