mirror of
https://git.planet-casio.com/Lephenixnoir/fxsdk.git
synced 2025-05-21 17:09:15 +02:00
fxlink: usable TUI command setup + gintctl test commands
This commit is contained in:
parent
cef9d21076
commit
3f4aa1e750
6 changed files with 937 additions and 288 deletions
|
@ -60,6 +60,7 @@ add_executable(fxlink
|
|||
fxlink/tooling/sdl2.c
|
||||
fxlink/tooling/udisks2.c
|
||||
fxlink/tui/commands.c
|
||||
fxlink/tui/command-util.c
|
||||
fxlink/tui/input.c
|
||||
fxlink/tui/layout.c
|
||||
fxlink/tui/render.c
|
||||
|
|
379
fxlink/tui/command-util.c
Normal file
379
fxlink/tui/command-util.c
Normal file
|
@ -0,0 +1,379 @@
|
|||
//---------------------------------------------------------------------------//
|
||||
// ==>/[_]\ 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 "command-util.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
|
||||
//---
|
||||
// Command parsing utilities
|
||||
//---
|
||||
|
||||
struct fxlink_tui_cmd fxlink_tui_cmd_parse(char const *input)
|
||||
{
|
||||
struct fxlink_tui_cmd 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;
|
||||
cmd.argv = realloc(cmd.argv, (cmd.argc + 1) * sizeof *cmd.argv);
|
||||
cmd.argv[cmd.argc] = 0;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
void fxlink_tui_cmd_dump(struct fxlink_tui_cmd const *cmd)
|
||||
{
|
||||
print(TUI.wConsole, "[%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");
|
||||
}
|
||||
|
||||
void fxlink_tui_cmd_free(struct fxlink_tui_cmd const *cmd)
|
||||
{
|
||||
free(cmd->argv);
|
||||
free(cmd->data);
|
||||
}
|
||||
|
||||
static struct fxlink_device *find_connected_device(void)
|
||||
{
|
||||
/* TODO: Use the "selected" device */
|
||||
for(int i = 0; i < TUI.devices.count; i++) {
|
||||
if(TUI.devices.devices[i].status == FXLINK_FDEV_STATUS_CONNECTED)
|
||||
return &TUI.devices.devices[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool fxlink_tui_parse_args(int argc, char const **argv, char const *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
|
||||
int i = 0;
|
||||
char const *spec = fmt;
|
||||
bool got_variadic = false;
|
||||
|
||||
for(; *spec; i++, spec++) {
|
||||
/* Implicit/silent specifiers */
|
||||
if(*spec == 'd') {
|
||||
struct fxlink_device **ptr = va_arg(args, struct fxlink_device **);
|
||||
*ptr = find_connected_device();
|
||||
if(!*ptr) {
|
||||
fprint(TUI.wConsole, FMT_RED, "error: ");
|
||||
print(TUI.wConsole, "no device connected\n");
|
||||
goto failure;
|
||||
}
|
||||
/* Bad */
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* No specifier that consumes stuff allowed after '*' */
|
||||
if(got_variadic) {
|
||||
fprint(TUI.wConsole, FMT_RED, "error: ");
|
||||
print(TUI.wConsole, "got specifiers '%s' after '*'\n", spec);
|
||||
goto failure;
|
||||
}
|
||||
|
||||
/* Specifiers allowed even when there is no argument left */
|
||||
if(*spec == '*') {
|
||||
char const ***ptr = va_arg(args, char const ***);
|
||||
*ptr = argv + i;
|
||||
got_variadic = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Argument required beyond this point */
|
||||
if(i >= argc && *spec != '*') {
|
||||
fprint(TUI.wConsole, FMT_RED, "error: ");
|
||||
print(TUI.wConsole, "too few arguments\n");
|
||||
goto failure;
|
||||
}
|
||||
|
||||
/* Standard specifiers */
|
||||
if(*spec == 's') {
|
||||
char const **ptr = va_arg(args, char const **);
|
||||
*ptr = argv[i];
|
||||
}
|
||||
else if(*spec == 'i') {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
return true;
|
||||
|
||||
failure:
|
||||
va_end(args);
|
||||
return false;
|
||||
}
|
||||
|
||||
//---
|
||||
// Command tree
|
||||
//---
|
||||
|
||||
struct node {
|
||||
/* Command or subtree name */
|
||||
char *name;
|
||||
/* true if tree node, false if raw command */
|
||||
bool is_tree;
|
||||
|
||||
union {
|
||||
struct node *children; /* is_subtree = true */
|
||||
int (*func)(int argc, char const **argv); /* is_subtree = false */
|
||||
};
|
||||
|
||||
/* Next sibling */
|
||||
struct node *next;
|
||||
};
|
||||
|
||||
static struct node *node_mkcmd(char const *name, int (*func)())
|
||||
{
|
||||
assert(name);
|
||||
struct node *cmd = calloc(1, sizeof *cmd);
|
||||
cmd->name = strdup(name);
|
||||
cmd->func = func;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
static struct node *node_mktree(char const *name)
|
||||
{
|
||||
assert(name);
|
||||
struct node *tree = calloc(1, sizeof *tree);
|
||||
tree->is_tree = true;
|
||||
tree->name = strdup(name);
|
||||
return tree;
|
||||
}
|
||||
|
||||
static void node_free(struct node *node);
|
||||
|
||||
static void node_free_chain(struct node *node)
|
||||
{
|
||||
struct node *next;
|
||||
while(node) {
|
||||
next = node->next;
|
||||
node_free(node);
|
||||
node = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void node_free(struct node *node)
|
||||
{
|
||||
free(node->name);
|
||||
if(node->is_tree) {
|
||||
node_free_chain(node->children);
|
||||
free(node);
|
||||
}
|
||||
}
|
||||
|
||||
static void node_tree_add(struct node *tree, struct node *node)
|
||||
{
|
||||
assert(tree->is_tree);
|
||||
node->next = tree->children;
|
||||
tree->children = node;
|
||||
}
|
||||
|
||||
static struct node *node_tree_get(struct node const *tree, char const *name)
|
||||
{
|
||||
assert(tree->is_tree);
|
||||
for(struct node *n = tree->children; n; n = n->next) {
|
||||
if(!strcmp(n->name, name))
|
||||
return n;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct node *node_tree_get_or_make_subtree(struct node *tree,
|
||||
char const *name)
|
||||
{
|
||||
assert(tree->is_tree);
|
||||
struct node *n = node_tree_get(tree, name);
|
||||
if(n)
|
||||
return n;
|
||||
n = node_mktree(name);
|
||||
node_tree_add(tree, n);
|
||||
return n;
|
||||
}
|
||||
|
||||
static struct node *node_tree_get_path(struct node *tree, char const **path,
|
||||
int *path_end_index)
|
||||
{
|
||||
assert(tree->is_tree);
|
||||
struct node *n = node_tree_get(tree, path[0]);
|
||||
if(!n)
|
||||
return NULL;
|
||||
|
||||
(*path_end_index)++;
|
||||
if(!n->is_tree)
|
||||
return n;
|
||||
|
||||
if(!path[1]) {
|
||||
fprint(TUI.wConsole, FMT_RED, "error: ");
|
||||
print(TUI.wConsole, "'%s' takes a sub-command argument\n", path[0]);
|
||||
return NULL;
|
||||
}
|
||||
return node_tree_get_path(n, path+1, path_end_index);
|
||||
}
|
||||
|
||||
static void node_insert_command(struct node *tree, char const **path,
|
||||
int (*func)(), int i)
|
||||
{
|
||||
assert(tree->is_tree);
|
||||
|
||||
if(!path[i]) {
|
||||
fprintf(stderr, "error: cannot register empty command!\n");
|
||||
return;
|
||||
}
|
||||
else if(!path[i+1]) {
|
||||
struct node *cmd = node_tree_get(tree, path[i]);
|
||||
if(cmd) {
|
||||
fprintf(stderr, "error: '%s' already registred!\n", path[i]);
|
||||
return;
|
||||
}
|
||||
node_tree_add(tree, node_mkcmd(path[i], func));
|
||||
}
|
||||
else {
|
||||
struct node *subtree = node_tree_get_or_make_subtree(tree, path[i]);
|
||||
if(!subtree->is_tree) {
|
||||
fprintf(stderr, "error: '%s' is not a category!\n", path[i]);
|
||||
return;
|
||||
}
|
||||
return node_insert_command(subtree, path, func, i+1);
|
||||
}
|
||||
}
|
||||
|
||||
static void node_dump(struct node const *node, int indent)
|
||||
{
|
||||
print(TUI.wConsole, "%*s", 2*indent, "");
|
||||
|
||||
if(node->is_tree) {
|
||||
print(TUI.wConsole, "%s\n", node->name);
|
||||
struct node *child = node->children;
|
||||
while(child) {
|
||||
node_dump(child, indent+1);
|
||||
child = child->next;
|
||||
}
|
||||
}
|
||||
else {
|
||||
print(TUI.wConsole, "%s: %p\n", node->name, node->func);
|
||||
}
|
||||
}
|
||||
|
||||
static struct node *cmdtree = NULL;
|
||||
|
||||
void fxlink_tui_register_cmd(char const *name,
|
||||
int (*func)(int argc, char const **argv))
|
||||
{
|
||||
int i = 0;
|
||||
while(name[i] && (isalpha(name[i]) || strchr("?/-_ ", name[i])))
|
||||
i++;
|
||||
if(name[i] != 0) {
|
||||
fprintf(stderr, "error: invalid command path '%s'\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!cmdtree)
|
||||
cmdtree = node_mktree("(root)");
|
||||
|
||||
/* Parse as a command because why not */
|
||||
struct fxlink_tui_cmd path = fxlink_tui_cmd_parse(name);
|
||||
node_insert_command(cmdtree, path.argv, func, 0);
|
||||
|
||||
fxlink_tui_cmd_free(&path);
|
||||
}
|
||||
|
||||
__attribute__((destructor))
|
||||
static void free_command_tree(void)
|
||||
{
|
||||
node_free(cmdtree);
|
||||
cmdtree = NULL;
|
||||
}
|
||||
|
||||
void TUI_execute_command(char const *command)
|
||||
{
|
||||
struct fxlink_tui_cmd cmd = fxlink_tui_cmd_parse(command);
|
||||
if(cmd.argc < 1)
|
||||
goto end;
|
||||
|
||||
int args_index = 0;
|
||||
struct node *node = node_tree_get_path(cmdtree, cmd.argv, &args_index);
|
||||
|
||||
if(node) {
|
||||
node->func(cmd.argc - args_index, cmd.argv + args_index);
|
||||
/* ignore return code? */
|
||||
}
|
||||
else {
|
||||
fprint(TUI.wConsole, FMT_RED, "error: ");
|
||||
print(TUI.wConsole, "unrecognized command: ");
|
||||
fxlink_tui_cmd_dump(&cmd);
|
||||
}
|
||||
|
||||
end:
|
||||
fxlink_tui_cmd_free(&cmd);
|
||||
}
|
||||
|
||||
FXLINK_COMMAND("?cmdtree")
|
||||
{
|
||||
node_dump(cmdtree, 0);
|
||||
return 0;
|
||||
}
|
157
fxlink/tui/command-util.h
Normal file
157
fxlink/tui/command-util.h
Normal file
|
@ -0,0 +1,157 @@
|
|||
//---------------------------------------------------------------------------//
|
||||
// ==>/[_]\ 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.command-util: Preprocessor black magic for command definition
|
||||
//
|
||||
// This header provides the following method for declaring TUI commands that
|
||||
// are automatically registered at startup, and are invoked with arguments
|
||||
// pre-parsed:
|
||||
//
|
||||
// FXLINK_COMMAND("<NAME>", <TYPE>(<NAME>), <TYPE>(<NAME>), ...) {
|
||||
// /* normal code... */
|
||||
// return <STATUS>;
|
||||
// }
|
||||
//
|
||||
// The command name is a string. It can have multiple space-separated words as
|
||||
// in "gintctl test", in which case it is matched against argv[0], argv[1], etc
|
||||
// and the prefix ("gintctl") is automatically made into a sub-category command
|
||||
// with relevant error messages.
|
||||
//
|
||||
// Each argument has a type and a name, as in INT(x). The type carries
|
||||
// information on the parsing method, the acceptable range, and of course the
|
||||
// actual runtime type of the argument. Available types are:
|
||||
//
|
||||
// Name Runtime type Meaning and range
|
||||
// --------------------------------------------------------------------------
|
||||
// INT int Any integer
|
||||
// STRING char const * Any string argument from argv[]
|
||||
// VARIADIC char const ** End of the argv array (NULL-terminated)
|
||||
// --------------------------------------------------------------------------
|
||||
// DEVICE struct fxlink_device * Selected device (implicit; never NULL)
|
||||
// --------------------------------------------------------------------------
|
||||
//
|
||||
// The function returns a status code, which is an integer. The entire command
|
||||
// declaration might look like:
|
||||
//
|
||||
// FXLINK_COMMAND("gintctl test", INT(lower_bound), INT(upper_bound)) {
|
||||
// int avg = (lower_bound + upper_bound) / 2;
|
||||
// return 0;
|
||||
// }
|
||||
//
|
||||
// I considered doing the entire thing in C++, but absolute preprocessor abuse
|
||||
// is fun once in a while.
|
||||
//---
|
||||
|
||||
#include <fxlink/defs.h>
|
||||
|
||||
//---
|
||||
// Shell-like command parsing (without the features)
|
||||
//---
|
||||
|
||||
struct fxlink_tui_cmd {
|
||||
int argc;
|
||||
char const **argv;
|
||||
char *data;
|
||||
};
|
||||
|
||||
/* Parse a string into an argument vector */
|
||||
struct fxlink_tui_cmd fxlink_tui_cmd_parse(char const *input);
|
||||
|
||||
/* Dump a command to TUI console for debugging */
|
||||
void fxlink_tui_cmd_dump(struct fxlink_tui_cmd const *cmd);
|
||||
|
||||
/* Free a command */
|
||||
void fxlink_tui_cmd_free(struct fxlink_tui_cmd const *cmd);
|
||||
|
||||
//---
|
||||
// Command registration and argument scanning
|
||||
//---
|
||||
|
||||
/* Parse a list of arguments into structured data. The format is a string of
|
||||
argument specifiers, each of which can be:
|
||||
s String (char *)
|
||||
d Integer (int)
|
||||
* Other variadic arguments (char **)
|
||||
(-- will probably be expanded later.)
|
||||
Returns true if parsing succeeded, false otherwise (including if arguments
|
||||
are missing) after printing an error message. */
|
||||
bool fxlink_tui_parse_args(int argc, char const **argv, char const *fmt, ...);
|
||||
|
||||
/* Register a command with the specified name and invocation function. This can
|
||||
be called manually or generated (along with the parser) using the macro
|
||||
FXLINK_COMMAND. */
|
||||
void fxlink_tui_register_cmd(char const *name,
|
||||
int (*func)(int argc, char const **argv));
|
||||
|
||||
/* Apply a macro to every variadic argument. _M1 is applied to the first
|
||||
argument and _Mn is applied to all subsequent arguments. */
|
||||
#define MAPn(_M1,_Mn,...) __VA_OPT__(MAP_1(_M1,_Mn,__VA_ARGS__))
|
||||
#define MAP_1(_M1,_Mn,_X,...) _M1(_X) __VA_OPT__(MAP_2(_M1,_Mn,__VA_ARGS__))
|
||||
#define MAP_2(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_3(_M1,_Mn,__VA_ARGS__))
|
||||
#define MAP_3(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_4(_M1,_Mn,__VA_ARGS__))
|
||||
#define MAP_4(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_5(_M1,_Mn,__VA_ARGS__))
|
||||
#define MAP_5(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_6(_M1,_Mn,__VA_ARGS__))
|
||||
#define MAP_6(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_7(_M1,_Mn,__VA_ARGS__))
|
||||
#define MAP_7(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_8(_M1,_Mn,__VA_ARGS__))
|
||||
#define MAP_8(_M1,_Mn,_X,...) _Mn(_X) __VA_OPT__(MAP_MAX_8_ARGS(_))
|
||||
#define MAP_MAX_8_ARGS()
|
||||
/* Simpler version where the same macro is applied to all arguments */
|
||||
#define MAP(_M, ...) MAPn(_M, _M, ##__VA_ARGS__)
|
||||
|
||||
/* Command declaration macro. Builds an invocation function and a registration
|
||||
function so the command name doesn't have to be repeated. */
|
||||
#define FXLINK_COMMAND(_NAME, ...) DO_COMMAND1(_NAME, __COUNTER__, __VA_ARGS__)
|
||||
/* This call forces __COUNTER__ to expand */
|
||||
#define DO_COMMAND1(...) DO_COMMAND(__VA_ARGS__)
|
||||
|
||||
#define DO_COMMAND(_NAME, _COUNTER, ...) \
|
||||
static int ___command_ ## _COUNTER(); \
|
||||
static int ___invoke_command_ ## _COUNTER \
|
||||
(int ___argc, char const **___argv) { \
|
||||
MAP(MKVAR, ##__VA_ARGS__) \
|
||||
if(!fxlink_tui_parse_args(___argc, ___argv, \
|
||||
"" MAP(MKFMT, ##__VA_ARGS__) \
|
||||
MAP(MKPTR, ##__VA_ARGS__))) return 1; \
|
||||
return ___command_ ## _COUNTER( \
|
||||
MAPn(MKCALL_1, MKCALL_n, ##__VA_ARGS__)); \
|
||||
} \
|
||||
__attribute__((constructor)) \
|
||||
static void ___declare_command_ ## _COUNTER (void) { \
|
||||
fxlink_tui_register_cmd(_NAME, ___invoke_command_ ## _COUNTER); \
|
||||
} \
|
||||
static int ___command_ ## _COUNTER(MAPn(MKFML_1, MKFML_n, ##__VA_ARGS__))
|
||||
|
||||
/* Make the format string for an argument */
|
||||
#define MKFMT(_TV) MKFMT_ ## _TV
|
||||
#define MKFMT_INT(_X) "i"
|
||||
#define MKFMT_STRING(_X) "s"
|
||||
#define MKFMT_VARIADIC(_X) "*"
|
||||
#define MKFMT_DEVICE(_X) "d"
|
||||
|
||||
/* Make the formal function parameter for an argument */
|
||||
#define MKFML_1(_TV) MKFML_ ## _TV
|
||||
#define MKFML_n(_TV) , MKFML_1(_TV)
|
||||
#define MKFML_INT(_X) int _X
|
||||
#define MKFML_STRING(_X) char const * _X
|
||||
#define MKFML_VARIADIC(_X) char const ** _X
|
||||
#define MKFML_DEVICE(_X) struct fxlink_device * _X
|
||||
|
||||
/* Create a variable */
|
||||
#define MKVAR(_TV) MKFML_1(_TV);
|
||||
|
||||
/* Make a pointer to an argument (sadly we can't get the name directly) */
|
||||
#define MKPTR(_TV) , MKPTR_ ## _TV
|
||||
#define MKPTR_INT(_X) &_X
|
||||
#define MKPTR_STRING(_X) &_X
|
||||
#define MKPTR_VARIADIC(_X) &_X
|
||||
#define MKPTR_DEVICE(_X) &_X
|
||||
|
||||
/* Pass a variable as a function argument */
|
||||
#define MKCALL_1(_TV) MKCALL_ ## _TV
|
||||
#define MKCALL_n(_TV) , MKCALL_1(_TV)
|
||||
#define MKCALL_INT(_X) _X
|
||||
#define MKCALL_STRING(_X) _X
|
||||
#define MKCALL_VARIADIC(_X) _X
|
||||
#define MKCALL_DEVICE(_X) _X
|
|
@ -5,236 +5,269 @@
|
|||
//---------------------------------------------------------------------------//
|
||||
|
||||
#include "tui.h"
|
||||
#include "command-util.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
#include <assert.h>
|
||||
|
||||
//---
|
||||
// Parsing utilities
|
||||
// Standard commands
|
||||
//---
|
||||
|
||||
struct command {
|
||||
int argc;
|
||||
char const **argv;
|
||||
char *data;
|
||||
};
|
||||
|
||||
static struct command parse_command(char const *input)
|
||||
FXLINK_COMMAND("/echo", DEVICE(fdev), VARIADIC(argv))
|
||||
{
|
||||
struct command cmd;
|
||||
cmd.argc = 0;
|
||||
cmd.argv = NULL;
|
||||
cmd.data = malloc(strlen(input) + 1);
|
||||
if(!cmd.data)
|
||||
return cmd;
|
||||
int l = 5, j = 5;
|
||||
for(int i = 0; argv[i]; i++)
|
||||
l += strlen(argv[i]) + 1;
|
||||
|
||||
char const *escapes1 = "\\nter";
|
||||
char const *escapes2 = "\\\n\t\e\r";
|
||||
char *concat = malloc(l + 1);
|
||||
strcpy(concat, "echo ");
|
||||
for(int i = 0; argv[i]; i++) {
|
||||
strcpy(concat + j, argv[i]);
|
||||
j += strlen(argv[i]);
|
||||
concat[j++] = (argv[i+1] == NULL) ? '\n' : ' ';
|
||||
}
|
||||
concat[j] = '\0';
|
||||
|
||||
/* Whether a new word needs to be created at the next character */
|
||||
bool word_finished = true;
|
||||
/* Offset into cmd.data */
|
||||
int i = 0;
|
||||
fxlink_device_start_bulk_OUT(fdev,
|
||||
"fxlink", "command", concat, l, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Read words eagerly, appending to cmd.data as we go */
|
||||
for(int j = 0; input[j]; j++) {
|
||||
int c = input[j];
|
||||
FXLINK_COMMAND("/identify", DEVICE(fdev))
|
||||
{
|
||||
fxlink_device_start_bulk_OUT(fdev,
|
||||
"fxlink", "command", "identify", 8, false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Stop words at spaces */
|
||||
if(isspace(c)) {
|
||||
if(!word_finished)
|
||||
cmd.data[i++] = 0;
|
||||
word_finished = true;
|
||||
continue;
|
||||
}
|
||||
//---
|
||||
// gintctl commands
|
||||
//---
|
||||
|
||||
/* Translate escapes */
|
||||
if(c == '\\') {
|
||||
char *p = strchr(escapes1, input[j+1]);
|
||||
if(p) {
|
||||
c = escapes2[p - escapes1];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
static char const *lipsum =
|
||||
"When the war of the beasts brings about the world's end,\n"
|
||||
"The goddess descends from the sky.\n"
|
||||
"Wings of light and dark spread afar,\n"
|
||||
"She guides us to bliss, her gift everlasting.\n"
|
||||
"\n"
|
||||
"Infinite in mystery is the gift of the goddess.\n"
|
||||
"We seek it thus, and take to the sky.\n"
|
||||
"Ripples form on the water's surface;\n"
|
||||
"The wandering soul knows no rest.\n"
|
||||
"\n"
|
||||
"There is no hate, only joy,\n"
|
||||
"For you are beloved by the goddess.\n"
|
||||
"Hero of the dawn, healer of worlds,\n"
|
||||
"Dreams of the morrow hath the shattered soul.\n"
|
||||
"Pride is lost -- wings stripped away, the end is nigh.\n"
|
||||
"\n"
|
||||
"My friend, do you fly away now?\n"
|
||||
"To a world that abhors you and I?\n"
|
||||
"All that awaits you is a somber morrow\n"
|
||||
"No matter where the winds may blow.\n"
|
||||
"My friend, your desire\n"
|
||||
"Is the bringer of life, the gift of the goddess.\n"
|
||||
"Even if the morrow is barren of promises,\n"
|
||||
"Nothing shall forestall my return.\n"
|
||||
"\n"
|
||||
"My friend, the fates are cruel.\n"
|
||||
"There are no dreams, no honor remains.\n"
|
||||
"The arrow has left the bow of the goddess.\n"
|
||||
"My soul, corrupted by vengeance,\n"
|
||||
"Hath endured torment to find the end of the journey\n"
|
||||
"In my own salvation and your eternal slumber.\n"
|
||||
"Legend shall speak of sacrifice at world's end\n"
|
||||
"The wind sails over the water's surface, quietly, but surely.\n"
|
||||
"\n"
|
||||
"Even if the morrow is barren of promises,\n"
|
||||
"Nothing shall forestall my return.\n"
|
||||
"To become the dew that quenches the lands,\n"
|
||||
"To spare the sands, the seas, the skies,\n"
|
||||
"I offer thee this silent sacrifice.\n";
|
||||
|
||||
/* 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;
|
||||
FXLINK_COMMAND("gintctl echo-bounds", DEVICE(fdev), INT(count))
|
||||
{
|
||||
if(count < 0 || count > 8192) {
|
||||
fprint(TUI.wConsole, FMT_RED, "error: ");
|
||||
print(TUI.wConsole, "count should be 0..8192 (not %d)\n", count);
|
||||
return 1;
|
||||
}
|
||||
|
||||
cmd.data[i++] = 0;
|
||||
return cmd;
|
||||
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 0;
|
||||
}
|
||||
|
||||
static void dump_command(struct command const *cmd)
|
||||
FXLINK_COMMAND("gintctl garbage", DEVICE(fdev), INT(count))
|
||||
{
|
||||
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));
|
||||
if(count < 0 || count > 8192) {
|
||||
fprint(TUI.wConsole, FMT_RED, "error: ");
|
||||
print(TUI.wConsole, "count should be 0..8192 (not %d)\n", count);
|
||||
return 1;
|
||||
}
|
||||
print(TUI.wConsole, "\n");
|
||||
|
||||
uint32_t *data = malloc(count * 4);
|
||||
for(int i = 0; i < count; i++)
|
||||
data[i] = i + 0xdead0000;
|
||||
fxlink_device_start_bulk_OUT(fdev,
|
||||
"gintctl", "garbage", data, count * 4, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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, ...)
|
||||
static void status(bool b, 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++;
|
||||
}
|
||||
|
||||
fprint(TUI.wConsole, b ? FMT_GREEN : FMT_RED, b ? "<PASSED> ":"<FAILED> ");
|
||||
vw_printw(TUI.wConsole, fmt, args);
|
||||
va_end(args);
|
||||
return true;
|
||||
|
||||
failure:
|
||||
va_end(args);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool run_no_device(int argc, char const **argv)
|
||||
static void unit_echo(struct fxlink_device *fdev, char const *str,
|
||||
char const *description)
|
||||
{
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
return false;
|
||||
char *echo = malloc(5 + strlen(str) + 1);
|
||||
strcpy(echo, "echo ");
|
||||
strcat(echo, str);
|
||||
|
||||
fxlink_device_start_bulk_OUT(fdev,
|
||||
"fxlink", "command", echo, strlen(echo), true);
|
||||
|
||||
struct fxlink_message *msg = NULL;
|
||||
while(TUI_wait_message(fdev, "fxlink", "text", &msg)) {
|
||||
bool success =
|
||||
msg->size == strlen(str)
|
||||
&& !strncmp(msg->data, str, msg->size);
|
||||
if(description)
|
||||
status(success, "%s\n", description);
|
||||
else
|
||||
status(success, "echo of '%s': '%.*s' (%d)\n", str, msg->size,
|
||||
(char *)msg->data, msg->size);
|
||||
}
|
||||
}
|
||||
|
||||
static void run_gintctl_command(int argc, char const **argv,
|
||||
struct fxlink_device *fdev)
|
||||
static void unit_echo_bounds(struct fxlink_device *fdev, int count)
|
||||
{
|
||||
if(!strcmp(argv[0], "read-long")) {
|
||||
int count = 0;
|
||||
if(!parse_arguments(argc, argv, "d", &count))
|
||||
return;
|
||||
if(count < 0 || count > 8192)
|
||||
return;
|
||||
char reference[256];
|
||||
sprintf(reference, "first=%08x last=%08x total=%d B\n",
|
||||
0, count-1, 4*count);
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
struct fxlink_message *msg = NULL;
|
||||
while(TUI_wait_message(fdev, "fxlink", "text", &msg)) {
|
||||
bool success =
|
||||
msg->size == strlen(reference)
|
||||
&& !strncmp(msg->data, reference, msg->size);
|
||||
|
||||
status(success, "echo bounds %d B\n", count * 4);
|
||||
}
|
||||
}
|
||||
|
||||
static bool run_with_device(int argc, char const **argv,
|
||||
struct fxlink_device *fdev)
|
||||
static void unit_read_unaligned(struct fxlink_device *fdev, char const *str,
|
||||
int kind)
|
||||
{
|
||||
if(!strcmp(argv[0], "/echo")) {
|
||||
int l = 5, j = 5;
|
||||
for(int i = 1; i < argc; i++)
|
||||
l += strlen(argv[i]) + 1;
|
||||
char *payload = malloc(strlen(str) + 2);
|
||||
sprintf(payload, "%c%s", kind, str);
|
||||
|
||||
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,
|
||||
"gintctl", "read-unaligned", payload, strlen(payload), true);
|
||||
|
||||
fxlink_device_start_bulk_OUT(fdev,
|
||||
"fxlink", "command", concat, l, true);
|
||||
struct fxlink_message *msg = NULL;
|
||||
while(TUI_wait_message(fdev, "fxlink", "text", &msg)) {
|
||||
bool success =
|
||||
msg->size == strlen(str)
|
||||
&& !strncmp(msg->data, str, msg->size);
|
||||
if(strlen(str) < 20)
|
||||
status(success, "unaligned echo type '%c' of '%s'\n", kind, str);
|
||||
else
|
||||
status(success, "unaligned echo type '%c' of %d-byte string\n",
|
||||
kind, strlen(str));
|
||||
}
|
||||
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)
|
||||
static void test_read_basic(struct fxlink_device *fdev)
|
||||
{
|
||||
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);
|
||||
unit_echo(fdev, "123", NULL);
|
||||
unit_echo(fdev, "1234", NULL);
|
||||
unit_echo(fdev, "12345", NULL);
|
||||
unit_echo(fdev, "123456", NULL);
|
||||
unit_echo(fdev, lipsum, "echo of better lorem ipsum");
|
||||
}
|
||||
|
||||
static void test_read_buffers(struct fxlink_device *fdev)
|
||||
{
|
||||
/* 128 and 384 bytes -> less than a packet */
|
||||
unit_echo_bounds(fdev, 32);
|
||||
unit_echo_bounds(fdev, 96);
|
||||
/* 512 bytes -> exactly one packet */
|
||||
unit_echo_bounds(fdev, 128);
|
||||
unit_echo_bounds(fdev, 128);
|
||||
unit_echo_bounds(fdev, 128);
|
||||
/* 516 and 768 -> one packet and a short one */
|
||||
unit_echo_bounds(fdev, 129);
|
||||
unit_echo_bounds(fdev, 192);
|
||||
/* 1024 bytes -> exactly two packets */
|
||||
unit_echo_bounds(fdev, 256);
|
||||
/* 2044 bytes -> just shy of a full buffer */
|
||||
unit_echo_bounds(fdev, 511);
|
||||
/* 2048 bytes -> a full buffer */
|
||||
unit_echo_bounds(fdev, 512);
|
||||
unit_echo_bounds(fdev, 512);
|
||||
/* 2300 bytes -> more than a full buffer */
|
||||
unit_echo_bounds(fdev, 575);
|
||||
/* 6000 bytes -> non-integral number of full buffers but more than 2 */
|
||||
unit_echo_bounds(fdev, 1500);
|
||||
/* 8192 bytes -> "large" amount of full buffers */
|
||||
unit_echo_bounds(fdev, 2048);
|
||||
}
|
||||
|
||||
static void test_read_unaligned(struct fxlink_device *fdev)
|
||||
{
|
||||
char const *alpha = "aBcDeFgHiJkLmNoPqR";
|
||||
|
||||
for(int i = 1; i <= 9; i++)
|
||||
unit_read_unaligned(fdev, alpha, '0' + i);
|
||||
unit_read_unaligned(fdev, alpha, 'i');
|
||||
unit_read_unaligned(fdev, alpha, 'r');
|
||||
|
||||
for(int i = 1; i <= 9; i++)
|
||||
unit_read_unaligned(fdev, lipsum, '0' + i);
|
||||
unit_read_unaligned(fdev, lipsum, 'i');
|
||||
unit_read_unaligned(fdev, lipsum, 'r');
|
||||
}
|
||||
|
||||
FXLINK_COMMAND("gintctl test read-basic", DEVICE(fdev))
|
||||
{
|
||||
test_read_basic(fdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
FXLINK_COMMAND("gintctl test read-buffers", DEVICE(fdev))
|
||||
{
|
||||
test_read_buffers(fdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
FXLINK_COMMAND("gintctl test read-unaligned", DEVICE(fdev))
|
||||
{
|
||||
test_read_unaligned(fdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
FXLINK_COMMAND("gintctl test all", DEVICE(fdev))
|
||||
{
|
||||
test_read_basic(fdev);
|
||||
test_read_buffers(fdev);
|
||||
test_read_unaligned(fdev);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -399,11 +399,130 @@ static void handle_fxlink_log(int display_fmt, char const *str)
|
|||
wattroff(TUI.wLogs, attr);
|
||||
}
|
||||
|
||||
bool TUI_core_update(bool allow_console, bool auto_refresh, bool *has_command)
|
||||
{
|
||||
struct timeval zero_tv = { 0 };
|
||||
struct timeval usb_timeout;
|
||||
struct pollfd stdinfd = { .fd = STDIN_FILENO, .events = POLLIN };
|
||||
|
||||
int rc = libusb_get_next_timeout(TUI.ctx, &usb_timeout);
|
||||
int timeout = -1;
|
||||
if(rc > 0)
|
||||
timeout = usb_timeout.tv_sec * 1000 + usb_timeout.tv_usec / 1000;
|
||||
bool timeout_is_libusb = true;
|
||||
/* Time out at least every 100 ms so we can handle SDL events */
|
||||
if(timeout < 0 || timeout > 100) {
|
||||
timeout = 100;
|
||||
timeout_is_libusb = false;
|
||||
}
|
||||
|
||||
if(has_command)
|
||||
*has_command = false;
|
||||
|
||||
rc = fxlink_multipoll(timeout,
|
||||
&stdinfd, 1, TUI.polled_fds.fds, TUI.polled_fds.count, NULL);
|
||||
|
||||
if(rc < 0 && errno != EINTR) {
|
||||
elog("poll: %s\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
if(rc < 0 && errno == EINTR)
|
||||
return false;
|
||||
|
||||
/* Handle SIGWINCH */
|
||||
if(TUI.resize_needed) {
|
||||
endwin();
|
||||
refresh();
|
||||
TUI_setup_windows();
|
||||
TUI.resize_needed = false;
|
||||
TUI_render_all(true);
|
||||
TUI_refresh_all(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Determine which even source was activated */
|
||||
bool stdin_activity = (stdinfd.revents & POLLIN) != 0;
|
||||
bool usb_activity = false;
|
||||
for(int i = 0; i < TUI.polled_fds.count; i++)
|
||||
usb_activity |= (TUI.polled_fds.fds[i].revents != 0);
|
||||
|
||||
/* Determine what to do. We update the console on stdin activity. We
|
||||
update libusb on USB activity or appropriate timeout. We update SDL
|
||||
events on any timeout. */
|
||||
bool update_console = stdin_activity;
|
||||
bool update_usb = usb_activity || (rc == 0 && timeout_is_libusb);
|
||||
bool update_sdl = (rc == 0);
|
||||
|
||||
if(allow_console && update_console) {
|
||||
bool finished = fxlink_TUI_input_getch(&TUI.input, TUI.wLogs);
|
||||
TUI_refresh_console();
|
||||
if(has_command)
|
||||
*has_command = finished;
|
||||
}
|
||||
|
||||
if(update_usb) {
|
||||
libusb_handle_events_timeout(TUI.ctx, &zero_tv);
|
||||
fxlink_device_list_refresh(&TUI.devices);
|
||||
|
||||
for(int i = 0; i < TUI.devices.count; i++) {
|
||||
struct fxlink_device *fdev = &TUI.devices.devices[i];
|
||||
|
||||
/* Check for devices ready to connect to */
|
||||
if(fdev->status == FXLINK_FDEV_STATUS_IDLE && fdev->comm
|
||||
&& fdev->comm->ep_bulk_IN != 0xff) {
|
||||
if(fxlink_device_claim_fxlink(fdev))
|
||||
fxlink_device_start_bulk_IN(fdev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(update_sdl) {
|
||||
fxlink_sdl2_handle_events();
|
||||
}
|
||||
|
||||
bool refresh = update_console || update_usb;
|
||||
if(auto_refresh) {
|
||||
TUI_render_all(false);
|
||||
TUI_refresh_all(false);
|
||||
}
|
||||
return refresh;
|
||||
}
|
||||
|
||||
bool TUI_wait_message(struct fxlink_device *fdev,
|
||||
char const *application, char const *type, struct fxlink_message **msg_ptr)
|
||||
{
|
||||
if(*msg_ptr) {
|
||||
fxlink_message_free(*msg_ptr, true);
|
||||
fxlink_device_start_bulk_IN(fdev);
|
||||
return false;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
TUI_core_update(false, true, NULL);
|
||||
|
||||
/* Check for new messages */
|
||||
struct fxlink_message *msg = fxlink_device_finish_bulk_IN(fdev);
|
||||
if(msg) {
|
||||
if(fxlink_message_is_apptype(msg, application, type)) {
|
||||
*msg_ptr = msg;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
fxlink_interactive_handle_message(msg);
|
||||
fxlink_message_free(msg, true);
|
||||
fxlink_device_start_bulk_IN(fdev);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main_tui_interactive(libusb_context *ctx)
|
||||
{
|
||||
if(!TUI_setup())
|
||||
return elog("error: failed to setup ncurses TUI o(x_x)o\n");
|
||||
|
||||
TUI.ctx = ctx;
|
||||
|
||||
/* Redirect fxlink logs to the logging window in the TUI */
|
||||
fxlink_log_set_handler(handle_fxlink_log);
|
||||
/* Set up hotplug notification */
|
||||
|
@ -411,10 +530,6 @@ int main_tui_interactive(libusb_context *ctx)
|
|||
/* Set up file descriptor tracking */
|
||||
fxlink_pollfds_track(&TUI.polled_fds, ctx);
|
||||
|
||||
struct timeval zero_tv = { 0 };
|
||||
struct timeval usb_timeout;
|
||||
struct pollfd stdinfd = { .fd = STDIN_FILENO, .events = POLLIN };
|
||||
|
||||
/* Initial render */
|
||||
print(TUI.wConsole, "fxlink version %s (libusb/TUI interactive mode)\n",
|
||||
FXLINK_VERSION);
|
||||
|
@ -424,113 +539,45 @@ int main_tui_interactive(libusb_context *ctx)
|
|||
TUI_render_all(true);
|
||||
TUI_refresh_all(true);
|
||||
|
||||
struct fxlink_TUI_input input;
|
||||
fxlink_TUI_input_init(&input, TUI.wConsole, 16);
|
||||
fxlink_TUI_input_init(&TUI.input, TUI.wConsole, 16);
|
||||
|
||||
while(1) {
|
||||
int rc = libusb_get_next_timeout(ctx, &usb_timeout);
|
||||
int timeout = -1;
|
||||
if(rc > 0)
|
||||
timeout = usb_timeout.tv_sec * 1000 + usb_timeout.tv_usec / 1000;
|
||||
bool timeout_is_libusb = true;
|
||||
/* Time out at least every 100 ms so we can handle SDL events */
|
||||
if(timeout < 0 || timeout > 100) {
|
||||
timeout = 100;
|
||||
timeout_is_libusb = false;
|
||||
}
|
||||
bool has_command;
|
||||
bool activity = TUI_core_update(true, false, &has_command);
|
||||
|
||||
rc = fxlink_multipoll(timeout,
|
||||
&stdinfd, 1, TUI.polled_fds.fds, TUI.polled_fds.count, NULL);
|
||||
|
||||
if(rc < 0 && errno != EINTR)
|
||||
elog("poll: %s\n", strerror(errno));
|
||||
|
||||
/* Handle SIGWINCH */
|
||||
if(TUI.resize_needed) {
|
||||
endwin();
|
||||
refresh();
|
||||
TUI_setup_windows();
|
||||
TUI.resize_needed = false;
|
||||
TUI_render_all(true);
|
||||
TUI_refresh_all(true);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Determine which even source was activated */
|
||||
bool stdin_activity = (stdinfd.revents & POLLIN) != 0;
|
||||
bool usb_activity = false;
|
||||
for(int i = 0; i < TUI.polled_fds.count; i++)
|
||||
usb_activity |= (TUI.polled_fds.fds[i].revents != 0);
|
||||
|
||||
/* Determine what to do. We update the console on stdin activity. We
|
||||
update libusb on USB activity or appropriate timeout. We update SDL
|
||||
events on any timeout. */
|
||||
bool update_console = stdin_activity;
|
||||
bool update_usb = usb_activity || (rc == 0 && timeout_is_libusb);
|
||||
bool update_sdl = (rc == 0);
|
||||
|
||||
if(update_console) {
|
||||
bool finished = fxlink_TUI_input_getch(&input, TUI.wLogs);
|
||||
TUI_refresh_console();
|
||||
|
||||
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 {
|
||||
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);
|
||||
|
||||
if(refresh_all) {
|
||||
TUI_render_all(false);
|
||||
TUI_refresh_all(false);
|
||||
}
|
||||
else
|
||||
TUI_refresh_console();
|
||||
/* Check for devices with finished transfers */
|
||||
for(int i = 0; i < TUI.devices.count; i++) {
|
||||
struct fxlink_device *fdev = &TUI.devices.devices[i];
|
||||
struct fxlink_message *msg = fxlink_device_finish_bulk_IN(fdev);
|
||||
if(msg) {
|
||||
fxlink_interactive_handle_message(msg);
|
||||
fxlink_message_free(msg, true);
|
||||
fxlink_device_start_bulk_IN(fdev);
|
||||
}
|
||||
}
|
||||
|
||||
if(update_usb) {
|
||||
libusb_handle_events_timeout(ctx, &zero_tv);
|
||||
fxlink_device_list_refresh(&TUI.devices);
|
||||
/* Check for console commands */
|
||||
if(has_command) {
|
||||
char *command = TUI.input.data;
|
||||
|
||||
for(int i = 0; i < TUI.devices.count; i++) {
|
||||
struct fxlink_device *fdev = &TUI.devices.devices[i];
|
||||
if(command[0] != 0)
|
||||
log_("command: '%s'\n", command);
|
||||
if(!strcmp(command, ""))
|
||||
{}
|
||||
else if(!strcmp(command, "q") || !strcmp(command, "quit"))
|
||||
break;
|
||||
else
|
||||
TUI_execute_command(command);
|
||||
|
||||
/* Check for devices ready to connect to */
|
||||
if(fdev->status == FXLINK_FDEV_STATUS_IDLE && fdev->comm
|
||||
&& fdev->comm->ep_bulk_IN != 0xff) {
|
||||
if(fxlink_device_claim_fxlink(fdev))
|
||||
fxlink_device_start_bulk_IN(fdev);
|
||||
}
|
||||
|
||||
/* Check for devices with finished transfers */
|
||||
struct fxlink_message *msg=fxlink_device_finish_bulk_IN(fdev);
|
||||
if(msg) {
|
||||
fxlink_interactive_handle_message(msg);
|
||||
fxlink_message_free(msg, true);
|
||||
fxlink_device_start_bulk_IN(fdev);
|
||||
}
|
||||
}
|
||||
fxlink_TUI_input_free(&TUI.input);
|
||||
print(TUI.wConsole, "%s", prompt);
|
||||
fxlink_TUI_input_init(&TUI.input, TUI.wConsole, 16);
|
||||
}
|
||||
|
||||
if(activity) {
|
||||
TUI_render_all(false);
|
||||
TUI_refresh_all(false);
|
||||
}
|
||||
|
||||
if(update_sdl) {
|
||||
fxlink_sdl2_handle_events();
|
||||
}
|
||||
}
|
||||
|
||||
while(fxlink_device_list_interrupt(&TUI.devices))
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
#include <ncurses.h>
|
||||
|
||||
struct TUIData {
|
||||
/* libusb context */
|
||||
libusb_context *ctx;
|
||||
/* SIGWINCH flag */
|
||||
bool resize_needed;
|
||||
/* ncurses window panels */
|
||||
|
@ -29,8 +31,38 @@ struct TUIData {
|
|||
/* Application data */
|
||||
struct fxlink_pollfds polled_fds;
|
||||
struct fxlink_device_list devices;
|
||||
/* Main console input */
|
||||
struct fxlink_TUI_input input;
|
||||
};
|
||||
|
||||
extern struct TUIData TUI;
|
||||
|
||||
/* Run a single asynchronous update. This polls a bunch of file descriptors
|
||||
along with a short timeout (< 1s). Returns true if there is any activity.
|
||||
|
||||
If `allow_console` is true, console events are handled; otherwise they are
|
||||
ignored so they can be collected by the main loop. Setting this parameter to
|
||||
false is useful when waiting for messages in TUI commands. `has_command` is
|
||||
set to whether there is a new command to be run at the console (only ever
|
||||
true when `allow_console` is true).
|
||||
|
||||
If `auto_refresh` is true, this function will refresh the TUI upon relevant
|
||||
activity. */
|
||||
bool TUI_core_update(bool allow_console, bool auto_refresh, bool *has_command);
|
||||
|
||||
/* Run the specified TUI command. */
|
||||
void TUI_execute_command(char const *command);
|
||||
|
||||
/* Wait for a message of a particular type to arrive, and then clean it up.
|
||||
This function should be called in a loop, eg.
|
||||
|
||||
struct fxlink_message *msg = NULL;
|
||||
while(TUI_wait_message(fdev, "fxlink", "text", &msg)) {
|
||||
// Handle msg...
|
||||
}
|
||||
|
||||
TUI_wait_message() will only return true once, however it will use the next
|
||||
call to free the message, restart communication on the device, and reset
|
||||
`msg` to NULL. */
|
||||
bool TUI_wait_message(struct fxlink_device *fdev,
|
||||
char const *application, char const *type, struct fxlink_message **msg);
|
||||
|
|
Loading…
Add table
Reference in a new issue