mirror of
https://git.planet-casio.com/Lephenixnoir/fxsdk.git
synced 2024-12-28 20:43:37 +01:00
fxlink: start implementing TUI commands (mainly gintctl tests)
This commit is contained in:
parent
f83ea7e3d3
commit
cef9d21076
4 changed files with 295 additions and 73 deletions
|
@ -55,14 +55,15 @@ add_executable(fxlink
|
||||||
fxlink/modes/interactive.c
|
fxlink/modes/interactive.c
|
||||||
fxlink/modes/list.c
|
fxlink/modes/list.c
|
||||||
fxlink/modes/push.c
|
fxlink/modes/push.c
|
||||||
fxlink/modes/tui-interactive.c
|
|
||||||
fxlink/modes/udisks2.c
|
fxlink/modes/udisks2.c
|
||||||
fxlink/tooling/libpng.c
|
fxlink/tooling/libpng.c
|
||||||
fxlink/tooling/sdl2.c
|
fxlink/tooling/sdl2.c
|
||||||
fxlink/tooling/udisks2.c
|
fxlink/tooling/udisks2.c
|
||||||
|
fxlink/tui/commands.c
|
||||||
fxlink/tui/input.c
|
fxlink/tui/input.c
|
||||||
fxlink/tui/layout.c
|
fxlink/tui/layout.c
|
||||||
fxlink/tui/render.c)
|
fxlink/tui/render.c
|
||||||
|
fxlink/tui/tui-interactive.c)
|
||||||
target_link_libraries(fxlink
|
target_link_libraries(fxlink
|
||||||
PkgConfig::libpng PkgConfig::libusb PkgConfig::ncurses -lm)
|
PkgConfig::libpng PkgConfig::libusb PkgConfig::ncurses -lm)
|
||||||
target_include_directories(fxlink PRIVATE
|
target_include_directories(fxlink PRIVATE
|
||||||
|
|
240
fxlink/tui/commands.c
Normal file
240
fxlink/tui/commands.c
Normal 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);
|
||||||
|
}
|
|
@ -5,17 +5,10 @@
|
||||||
//---------------------------------------------------------------------------//
|
//---------------------------------------------------------------------------//
|
||||||
|
|
||||||
#include "../fxlink.h"
|
#include "../fxlink.h"
|
||||||
#include <fxlink/tui/layout.h>
|
#include "tui.h"
|
||||||
#include <fxlink/tui/render.h>
|
|
||||||
#include <fxlink/tui/input.h>
|
|
||||||
#include <fxlink/devices.h>
|
|
||||||
#include <fxlink/logging.h>
|
|
||||||
#include <fxlink/tooling/libpng.h>
|
#include <fxlink/tooling/libpng.h>
|
||||||
#include <fxlink/tooling/sdl2.h>
|
#include <fxlink/tooling/sdl2.h>
|
||||||
|
|
||||||
#include <libusb.h>
|
|
||||||
#include <ncurses.h>
|
|
||||||
#include <poll.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
@ -40,22 +33,7 @@ Application-specific commands:
|
||||||
gintctl bench USB transfer speed benchmark
|
gintctl bench USB transfer speed benchmark
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static struct TUIData {
|
struct TUIData TUI = { 0 };
|
||||||
/* 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 };
|
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// TUI management and rendering
|
// TUI management and rendering
|
||||||
|
@ -421,50 +399,6 @@ static void handle_fxlink_log(int display_fmt, char const *str)
|
||||||
wattroff(TUI.wLogs, attr);
|
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)
|
int main_tui_interactive(libusb_context *ctx)
|
||||||
{
|
{
|
||||||
if(!TUI_setup())
|
if(!TUI_setup())
|
||||||
|
@ -541,18 +475,29 @@ int main_tui_interactive(libusb_context *ctx)
|
||||||
|
|
||||||
if(finished) {
|
if(finished) {
|
||||||
char *command = input.data;
|
char *command = input.data;
|
||||||
|
bool refresh_all = false;
|
||||||
|
|
||||||
if(command[0] != 0)
|
if(command[0] != 0)
|
||||||
log_("command: '%s'\n", command);
|
log_("command: '%s'\n", command);
|
||||||
if(!strcmp(command, ""))
|
if(!strcmp(command, ""))
|
||||||
{}
|
{}
|
||||||
else if(!strcmp(command, "q") || !strcmp(command, "quit"))
|
else if(!strcmp(command, "q") || !strcmp(command, "quit"))
|
||||||
break;
|
break;
|
||||||
else
|
else {
|
||||||
execute_command(command);
|
TUI_execute_command(command);
|
||||||
|
refresh_all = true;
|
||||||
|
}
|
||||||
|
|
||||||
fxlink_TUI_input_free(&input);
|
fxlink_TUI_input_free(&input);
|
||||||
print(TUI.wConsole, "%s", prompt);
|
print(TUI.wConsole, "%s", prompt);
|
||||||
fxlink_TUI_input_init(&input, TUI.wConsole, 16);
|
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
36
fxlink/tui/tui.h
Normal 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);
|
Loading…
Reference in a new issue