fxlink: start implementing TUI commands (mainly gintctl tests)

This commit is contained in:
Lephenixnoir 2023-03-17 21:32:01 +01:00
parent f83ea7e3d3
commit cef9d21076
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
4 changed files with 295 additions and 73 deletions

View file

@ -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

240
fxlink/tui/commands.c Normal file
View file

@ -0,0 +1,240 @@
//---------------------------------------------------------------------------//
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
// |:::| Made by Lephe' as part of the fxSDK. //
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
//---------------------------------------------------------------------------//
#include "tui.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
//---
// 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);
}

View file

@ -5,17 +5,10 @@
//---------------------------------------------------------------------------//
#include "../fxlink.h"
#include <fxlink/tui/layout.h>
#include <fxlink/tui/render.h>
#include <fxlink/tui/input.h>
#include <fxlink/devices.h>
#include <fxlink/logging.h>
#include "tui.h"
#include <fxlink/tooling/libpng.h>
#include <fxlink/tooling/sdl2.h>
#include <libusb.h>
#include <ncurses.h>
#include <poll.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
@ -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();
}
}

36
fxlink/tui/tui.h Normal file
View file

@ -0,0 +1,36 @@
//---------------------------------------------------------------------------//
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
// |:::| Made by Lephe' as part of the fxSDK. //
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
//---------------------------------------------------------------------------//
// fxlink.tui.tui: Global data and main functions for the interactive TUI
#pragma once
#include <fxlink/tui/layout.h>
#include <fxlink/tui/render.h>
#include <fxlink/tui/input.h>
#include <fxlink/devices.h>
#include <fxlink/logging.h>
#include <libusb.h>
#include <ncurses.h>
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);