mirror of
https://git.planet-casio.com/Lephenixnoir/fxsdk.git
synced 2024-12-28 04:23:37 +01:00
252 lines
7.5 KiB
C
252 lines
7.5 KiB
C
//---------------------------------------------------------------------------//
|
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
|
//---------------------------------------------------------------------------//
|
|
|
|
#include "fxlink.h"
|
|
#include <fxlink/filter.h>
|
|
#include <fxlink/logging.h>
|
|
|
|
#include <libusb.h>
|
|
#include <getopt.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <locale.h>
|
|
|
|
static const char *help_string =
|
|
"usage: %1$s (-l|-b|-t) [General options]\n"
|
|
" %1$s -i [-r] [--fxlink-log[=<FILE>]] [General options]\n"
|
|
" %1$s -p <FILE> [General options]\n"
|
|
" %1$s -s <FILES>... [--folder=OUTFOLDER] [General options]\n"
|
|
"\n"
|
|
"fxlink interacts with CASIO calculators of the fx and fx-CG families over\n"
|
|
"the USB port, using libusb. It can also transfer files for Mass Storage\n"
|
|
"calculators using UDisks2.\n"
|
|
"\n"
|
|
"Standard (libusb) modes:\n"
|
|
" -l, --list List detected calculators on the USB ports\n"
|
|
" -i, --interactive Messaging with a gint add-in (calc -> PC only)\n"
|
|
" -t, --tui TUI interactive mode\n"
|
|
" -p, --push Push a .bin file to the Add-In Push app\n"
|
|
"\n"
|
|
"Mass Storage (UDisks2) modes:\n"
|
|
" -b, --blocks List detected Mass Storage calculators\n"
|
|
" -s, --send Send a file to a Mass Storage calc and unmount it\n"
|
|
"\n"
|
|
"General options:\n"
|
|
" -v, --verbose Increased verbosity (mostly in -i/-t)\n"
|
|
" -w <SECONDS> Wait this many seconds for a calculator to connect\n"
|
|
" -w Wait indefinitely for a calculator to connect\n"
|
|
" -f <FILTER> Filter which calculators we connect to (see below)\n"
|
|
" --libusb-log=LEVEL libusb log level (NONE, ERROR, WARNING, INFO, DEBUG)\n"
|
|
"\n"
|
|
"Mode-specific options:\n"
|
|
" --fxlink-log[=FILE] -i: Append fxlink text messages to FILE. Without\n"
|
|
" argument, a unique name is generated.\n"
|
|
" -r, --repeat -i: Reconnect if the calc disconnects (implies -w)\n"
|
|
" --folder=FOLDER -s: Select destination folder for files\n"
|
|
"\n"
|
|
"Device filters:\n"
|
|
" A device filter narrows down what devices we list or connect to by\n"
|
|
" requiring a list of properties, such as \"p7,serial=00000001\". The\n"
|
|
" following properties can be tested:\n"
|
|
" - p7 Protocol 7 calcs (all fx models that use FA-124)\n"
|
|
" - mass_storage Mass Storage calcs (fx-CG models and the G-III)\n"
|
|
" - series_cg fx-CG models [udisks2 only]\n"
|
|
" - series_g3 G-III models [udisks2 only]\n"
|
|
" - serial=<SERIAL> This serial number (needs write access in libusb)\n";
|
|
|
|
/* Global options */
|
|
struct fxlink_options options;
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int rc=1, mode=0, error=0, option=0, loglevel=LIBUSB_LOG_LEVEL_WARNING;
|
|
delay_t delay = delay_seconds(0);
|
|
struct fxlink_filter *filter = NULL;
|
|
bool repeat = false;
|
|
char *outfolder = NULL;
|
|
|
|
options.log_file = NULL;
|
|
options.verbose = false;
|
|
|
|
setlocale(LC_ALL, "");
|
|
|
|
//---
|
|
// Command-line argument parsing
|
|
//---
|
|
|
|
enum { LIBUSB_LOG=1, LOG_TO_FILE=2, OUT_FOLDER=3 };
|
|
const struct option longs[] = {
|
|
{ "help", no_argument, NULL, 'h' },
|
|
{ "list", no_argument, NULL, 'l' },
|
|
{ "blocks", no_argument, NULL, 'b' },
|
|
{ "send", no_argument, NULL, 's' },
|
|
{ "interactive", no_argument, NULL, 'i' },
|
|
{ "tui", no_argument, NULL, 't' },
|
|
{ "push", no_argument, NULL, 'p' },
|
|
{ "libusb-log", required_argument, NULL, LIBUSB_LOG },
|
|
{ "fxlink-log", optional_argument, NULL, LOG_TO_FILE },
|
|
{ "repeat", no_argument, NULL, 'r' },
|
|
{ "verbose", no_argument, NULL, 'v' },
|
|
{ "folder", required_argument, NULL, OUT_FOLDER },
|
|
/* Deprecated options ignored for compatibility: */
|
|
{ "quiet", no_argument, NULL, 'q' },
|
|
{ "unmount", no_argument, NULL, 'u' },
|
|
{ NULL },
|
|
};
|
|
|
|
while(option >= 0 && option != '?')
|
|
switch((option = getopt_long(argc, argv, "hlbsitpquf:w::rv", longs, NULL)))
|
|
{
|
|
case 'h':
|
|
fprintf(stderr, help_string, argv[0]);
|
|
return 0;
|
|
case 'l':
|
|
case 'b':
|
|
case 's':
|
|
case 'i':
|
|
case 't':
|
|
case 'p':
|
|
mode = option;
|
|
break;
|
|
case LIBUSB_LOG:
|
|
if(!strcmp(optarg, "NONE"))
|
|
loglevel = LIBUSB_LOG_LEVEL_NONE;
|
|
else if(!strcmp(optarg, "ERROR"))
|
|
loglevel = LIBUSB_LOG_LEVEL_ERROR;
|
|
else if(!strcmp(optarg, "WARNING"))
|
|
loglevel = LIBUSB_LOG_LEVEL_WARNING;
|
|
else if(!strcmp(optarg, "INFO"))
|
|
loglevel = LIBUSB_LOG_LEVEL_INFO;
|
|
else if(!strcmp(optarg, "DEBUG"))
|
|
loglevel = LIBUSB_LOG_LEVEL_DEBUG;
|
|
else fprintf(stderr, "warning: ignoring log level '%s'; should be "
|
|
"NONE, ERROR, WARNING, INFO or DEBUG\n", optarg);
|
|
break;
|
|
case 'q':
|
|
/* Ignored: -q, --quiet used to control some messages but is now
|
|
supplanted by not setting -v */
|
|
break;
|
|
case 'u':
|
|
/* Ignored: -u, --unmount used to force unmounting filesystems after -s
|
|
(which is now the only behavior) */
|
|
break;
|
|
case 'r':
|
|
repeat = true;
|
|
delay = delay_infinite();
|
|
break;
|
|
case 'v':
|
|
options.verbose = true;
|
|
break;
|
|
case LOG_TO_FILE:
|
|
if(optarg)
|
|
options.log_file = fopen(optarg, "a");
|
|
else {
|
|
char *name = fxlink_gen_file_name(".", "logfile", ".log");
|
|
printf("--fxlink-log will output in '%s'\n", name);
|
|
options.log_file = fopen(name, "a");
|
|
free(name);
|
|
}
|
|
break;
|
|
case 'w':
|
|
if(!optarg) {
|
|
delay = delay_infinite();
|
|
break;
|
|
}
|
|
char *end;
|
|
int seconds = strtol(optarg, &end, 10);
|
|
if(seconds < 0 || *end != 0) {
|
|
error = elog("invalid delay '%s'\n", optarg);
|
|
break;
|
|
}
|
|
delay = delay_seconds(seconds);
|
|
break;
|
|
case 'f':
|
|
filter = fxlink_filter_parse(optarg);
|
|
break;
|
|
case OUT_FOLDER:
|
|
outfolder = strdup(optarg);
|
|
break;
|
|
case '?':
|
|
error = 1;
|
|
}
|
|
|
|
if(mode == 's' && optind == argc)
|
|
error = elog("send mode requires additional arguments (file names)\n");
|
|
|
|
if(mode == 'p' && optind == argc)
|
|
error = elog("push mode requires a file name\n");
|
|
if(mode == 'p' && optind < argc-1)
|
|
error = elog("push mode only accepts one file name\n");
|
|
|
|
/* No arguments or bad arguments */
|
|
if(error)
|
|
return 1;
|
|
if(!mode) {
|
|
fprintf(stderr, help_string, argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
/* Default filter */
|
|
if(filter == NULL)
|
|
filter = calloc(1, sizeof *filter);
|
|
|
|
//---
|
|
// libusb initialization
|
|
//---
|
|
|
|
libusb_context *context = NULL;
|
|
|
|
/* Initialize libusb for corresponding modes */
|
|
if(mode == 'l' || mode == 'i' || mode == 't' || mode == 'p') {
|
|
if((rc = libusb_init(&context)))
|
|
return elog_libusb(rc, "error initializing libusb");
|
|
libusb_set_option(context, LIBUSB_OPTION_LOG_LEVEL, loglevel);
|
|
fxlink_log_grab_libusb_logs();
|
|
}
|
|
|
|
//---
|
|
// Main functions
|
|
//---
|
|
|
|
if(mode == 'l') {
|
|
rc = main_list(filter, &delay, context);
|
|
}
|
|
else if(mode == 'b') {
|
|
#ifndef FXLINK_DISABLE_UDISKS2
|
|
rc = main_blocks(filter, &delay);
|
|
#else
|
|
rc = elog("this fxlink was built without UDisks2; -b is disabled");
|
|
#endif
|
|
}
|
|
else if(mode == 's') {
|
|
#ifndef FXLINK_DISABLE_UDISKS2
|
|
rc = main_send(filter, &delay, argv + optind, outfolder);
|
|
#else
|
|
rc = elog("this fxlink was built without UDisks2; -s is disabled");
|
|
#endif
|
|
}
|
|
else if(mode == 'i') {
|
|
do {
|
|
rc = main_interactive(filter, &delay, context);
|
|
}
|
|
while(repeat);
|
|
}
|
|
else if(mode == 't') {
|
|
rc = main_tui_interactive(context);
|
|
}
|
|
else if(mode == 'p') {
|
|
rc = main_push(filter, &delay, context, argv + optind);
|
|
}
|
|
|
|
fxlink_filter_free(filter);
|
|
if(context)
|
|
libusb_exit(context);
|
|
if(options.log_file)
|
|
fclose(options.log_file);
|
|
return rc;
|
|
}
|