mirror of
https://git.planet-casio.com/Lephenixnoir/fxsdk.git
synced 2024-12-28 04:23:37 +01:00
fxlink: new tool with features from libusb and (optionally) UDisks2
This commit is contained in:
parent
1a295958ee
commit
c059e2395d
14 changed files with 1549 additions and 0 deletions
|
@ -3,9 +3,15 @@
|
|||
cmake_minimum_required(VERSION 3.16)
|
||||
project(fxSDK VERSION 2.3.1 LANGUAGES C)
|
||||
|
||||
option(FXLINK_DISABLE_UDISKS2 "Do not build the UDisks2-based features of fxlink")
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
find_package(PNG REQUIRED)
|
||||
pkg_check_modules(libusb REQUIRED libusb-1.0 IMPORTED_TARGET)
|
||||
# pkg_check_modules(libudev libudev IMPORTED_TARGET)
|
||||
if(NOT FXLINK_DISABLE_UDISKS2)
|
||||
pkg_check_modules(udisks2 REQUIRED udisks2 IMPORTED_TARGET)
|
||||
endif()
|
||||
|
||||
set(CMAKE_INSTALL_MESSAGE LAZY)
|
||||
set(SRC "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
@ -26,6 +32,16 @@ add_custom_command(OUTPUT "${BIN}/fxsdk.sh"
|
|||
DEPENDS "${SRC}/fxsdk/fxsdk.sh")
|
||||
add_custom_target(fxsdk ALL DEPENDS "${BIN}/fxsdk.sh")
|
||||
|
||||
# fxlink
|
||||
configure_file(fxlink/config.h.in "${BIN}/include/fxlink/config.h")
|
||||
add_executable(fxlink fxlink/usb.c fxlink/filter.c fxlink/main.c
|
||||
fxlink/properties.c fxlink/ud2.c fxlink/util.c)
|
||||
target_link_libraries(fxlink PkgConfig::libusb) # PkgConfig::libudev
|
||||
target_include_directories(fxlink PRIVATE "${BIN}/include/fxlink")
|
||||
if(NOT FXLINK_DISABLE_UDISKS2)
|
||||
target_link_libraries(fxlink PkgConfig::udisks2)
|
||||
endif()
|
||||
|
||||
# Install rules
|
||||
|
||||
# fxsdk
|
||||
|
@ -37,3 +53,5 @@ install(TARGETS fxg1a)
|
|||
# fxconv
|
||||
install(PROGRAMS fxconv/fxconv-main.py TYPE BIN RENAME fxconv)
|
||||
install(FILES fxconv/fxconv.py TYPE BIN)
|
||||
#fxlink
|
||||
install(TARGETS fxlink)
|
||||
|
|
11
fxlink/config.h.in
Normal file
11
fxlink/config.h.in
Normal file
|
@ -0,0 +1,11 @@
|
|||
//---
|
||||
// fxlink:config - Compile-time configuration
|
||||
//---
|
||||
|
||||
#ifndef FXLINK_CONFIG_H
|
||||
#define FXLINK_CONFIG_H
|
||||
|
||||
/* Disables UDisks2 interfaces for systems that don't use it. */
|
||||
#cmakedefine FXLINK_DISABLE_UDISKS2
|
||||
|
||||
#endif /* FXLINK_CONFIG_H */
|
194
fxlink/filter.c
Normal file
194
fxlink/filter.c
Normal file
|
@ -0,0 +1,194 @@
|
|||
#include "filter.h"
|
||||
#include "util.h"
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
//---
|
||||
// Property parser
|
||||
//---
|
||||
|
||||
/* skip_spaces(): Skip spaces, returns true if end of string is reached */
|
||||
bool skip_spaces(char const **input)
|
||||
{
|
||||
while(isspace(**input)) (*input)++;
|
||||
return (**input == 0);
|
||||
}
|
||||
/* isword(): Identify valid word characters for the filter */
|
||||
bool isword(int c)
|
||||
{
|
||||
return c && !strchr(" \t\n,;=", c);
|
||||
}
|
||||
/* read_word(): Copy the next word in the string, assumes word is non-empty */
|
||||
char *read_word(char const **input)
|
||||
{
|
||||
char const *str = *input;
|
||||
while(**input && isword(**input)) (*input)++;
|
||||
return strndup(str, *input - str);
|
||||
}
|
||||
|
||||
enum {
|
||||
T_END, /* End of string */
|
||||
T_PROP, /* Property; (*name) and (*value) are set */
|
||||
T_COMMA = ',', /* Comma character (property separator) */
|
||||
T_SEMI = ';', /* Semicolon character (option separator) */
|
||||
};
|
||||
|
||||
/* lex(): Read a token from the input source
|
||||
Returns the token type, updates (*input) and sets (*name) and (*value) to
|
||||
either NULL or some freshly-allocated copies of the name and (optional)
|
||||
value of the property (always NULL unless T_PROP is returned). The caller
|
||||
should free() both. */
|
||||
static int lex(char const **input, char **name, char **value)
|
||||
{
|
||||
*name = *value = NULL;
|
||||
if(skip_spaces(input)) return T_END;
|
||||
|
||||
if(**input == ',' || **input == ';') {
|
||||
(*input)++;
|
||||
return (*input)[-1];
|
||||
}
|
||||
|
||||
if(!isword(**input)) {
|
||||
wrn("expected property name in filter, skipping '%c'", **input);
|
||||
(*input)++;
|
||||
return lex(input, name, value);
|
||||
}
|
||||
*name = read_word(input);
|
||||
if(skip_spaces(input) || **input != '=')
|
||||
return T_PROP;
|
||||
|
||||
(*input)++;
|
||||
if(skip_spaces(input))
|
||||
wrn("no value after '=' in filter property '%s'", *name);
|
||||
else if(!isword(**input))
|
||||
wrn("ignoring invalid value for filter property '%s'", *name);
|
||||
else
|
||||
*value = read_word(input);
|
||||
return T_PROP;
|
||||
}
|
||||
|
||||
filter_t *filter_parse(char const *input)
|
||||
{
|
||||
char *name=NULL, *value=NULL;
|
||||
int t;
|
||||
|
||||
/* Create an initial filter with a single otion */
|
||||
filter_t *filter = malloc(sizeof *filter);
|
||||
if(!filter) return NULL;
|
||||
filter->options = calloc(1, sizeof(properties_t));
|
||||
if(!filter->options) {
|
||||
free(filter);
|
||||
return NULL;
|
||||
}
|
||||
filter->length = 1;
|
||||
|
||||
/* Current option */
|
||||
properties_t *option = &filter->options[0];
|
||||
|
||||
while((t = lex(&input, &name, &value)) != T_END) {
|
||||
/* Ignore property separators (tokens are already separated) */
|
||||
if(t == ',') continue;
|
||||
|
||||
/* Add a new option in the filter */
|
||||
if(t == ';') {
|
||||
size_t new_size = (filter->length + 1) * sizeof(properties_t);
|
||||
properties_t *new_options = realloc(filter->options, new_size);
|
||||
if(!new_options) continue;
|
||||
|
||||
filter->options = new_options;
|
||||
option = &filter->options[filter->length++];
|
||||
*option = (properties_t){ 0 };
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Add a new property to the current option */
|
||||
if(!strcmp(name, "p7") && !value)
|
||||
option->p7 = true;
|
||||
else if(!strcmp(name, "mass_storage") && !value)
|
||||
option->mass_storage = true;
|
||||
else if(!strcmp(name, "series_cg") && !value)
|
||||
option->series_cg = true;
|
||||
else if(!strcmp(name, "series_g3") && !value)
|
||||
option->series_g3 = true;
|
||||
else if(!strcmp(name, "serial_number") && value) {
|
||||
option->serial_number = strdup(value);
|
||||
}
|
||||
else wrn("ignoring invalid filter property: '%s' %s value", name,
|
||||
value ? "with" : "without");
|
||||
|
||||
free(name);
|
||||
free(value);
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
void filter_free(filter_t *filter)
|
||||
{
|
||||
if(!filter) return;
|
||||
|
||||
for(size_t i = 0; i < filter->length; i++)
|
||||
free(filter->options[i].serial_number);
|
||||
|
||||
free(filter->options);
|
||||
free(filter);
|
||||
}
|
||||
|
||||
//---
|
||||
// Filtering API
|
||||
//---
|
||||
|
||||
void filter_clean_libusb(filter_t *filter)
|
||||
{
|
||||
if(!filter) return;
|
||||
|
||||
for(size_t i = 0; i < filter->length; i++) {
|
||||
properties_t *prop = &filter->options[i];
|
||||
|
||||
/* Suppress series_cg and series_g3, which are based off the USB Mass
|
||||
Storage metadata provided only by UDisks2 */
|
||||
if(prop->series_cg) {
|
||||
wrn("ignoring series_cg in libusb filter (cannot be detected)");
|
||||
prop->series_cg = false;
|
||||
}
|
||||
if(prop->series_g3) {
|
||||
wrn("ignoring series_g3 in libusb filter (cannot be detected)");
|
||||
prop->series_g3 = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void filter_clean_udisks2(filter_t *filter)
|
||||
{
|
||||
/* Every property can be used */
|
||||
(void)filter;
|
||||
}
|
||||
|
||||
bool filter_match(properties_t const *props, filter_t const *filter)
|
||||
{
|
||||
/* No filter is a pass-through */
|
||||
if(!filter || !filter->length)
|
||||
return true;
|
||||
|
||||
for(size_t i = 0; i < filter->length; i++) {
|
||||
if(properties_match(props, &filter->options[i]))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void filter_print(FILE *fp, filter_t const *filter)
|
||||
{
|
||||
#define output(...) { \
|
||||
if(sep) fprintf(fp, ", "); \
|
||||
fprintf(fp, __VA_ARGS__); \
|
||||
sep = true; \
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < filter->length; i++) {
|
||||
if(i > 0) printf("; ");
|
||||
properties_t *prop = &filter->options[i];
|
||||
properties_print(fp, prop);
|
||||
}
|
||||
}
|
49
fxlink/filter.h
Normal file
49
fxlink/filter.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
//---
|
||||
// fxlink:filter - Property-based device filtering
|
||||
//---
|
||||
|
||||
#ifndef FXLINK_FILTER_H
|
||||
#define FXLINK_FILTER_H
|
||||
|
||||
#include "properties.h"
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* filter_t: An OR-combination of property filters
|
||||
|
||||
Attributes of properties_t objects have AND-semantics when used as filters;
|
||||
all of them must match. For more flexibility, the command-line allows the
|
||||
user to specify an OR-combination of such filters, called "options". */
|
||||
typedef struct {
|
||||
/* Array of options to be matched against; not terminated. */
|
||||
properties_t *options;
|
||||
/* Length of (options). */
|
||||
size_t length;
|
||||
|
||||
} filter_t;
|
||||
|
||||
/* Return values for backend-specific matching functions */
|
||||
enum {
|
||||
FILTER_UNIQUE = 0,
|
||||
FILTER_NONE = 1,
|
||||
FILTER_MULTIPLE = 2,
|
||||
FILTER_ERROR = 3,
|
||||
};
|
||||
|
||||
/* filter_parse(): Parse a filter string */
|
||||
filter_t *filter_parse(char const *specification);
|
||||
/* filter_free(): Free a created by filter_parse() */
|
||||
void filter_free(filter_t *filter);
|
||||
|
||||
/* filter_clean_libusb(): Disable filter properties unsupported for libusb */
|
||||
void filter_clean_libusb(filter_t *filter);
|
||||
/* filter_clean_udisks2(): Disable filter properties unsupported for udisks2 */
|
||||
void filter_clean_udisks2(filter_t *filter);
|
||||
|
||||
/* filter_match(): Check whether some properties match the supplied filter */
|
||||
bool filter_match(properties_t const *props, filter_t const *filter);
|
||||
|
||||
/* filter_print(): Print a parser filter (one-line; for debugging) */
|
||||
void filter_print(FILE *fp, filter_t const *filter);
|
||||
|
||||
#endif /* FXLINK_FILTER_H */
|
21
fxlink/fxlink.h
Normal file
21
fxlink/fxlink.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
//---
|
||||
// fxlink:fxlink - Application logic
|
||||
//---
|
||||
|
||||
#ifndef FXLINK_FXLINK_H
|
||||
#define FXLINK_FXLINK_H
|
||||
|
||||
#include <libusb.h>
|
||||
#include "filter.h"
|
||||
#include "util.h"
|
||||
|
||||
/* Main function for -l */
|
||||
int main_list(filter_t *filter, delay_t *delay, libusb_context *context);
|
||||
|
||||
/* Main function for -b */
|
||||
int main_blocks(filter_t *filter, delay_t *delay);
|
||||
|
||||
/* Main function for -s */
|
||||
int main_send(filter_t *filter, delay_t *delay, char **files);
|
||||
|
||||
#endif /* FXLINK_FXLINK_H */
|
308
fxlink/main.c
Normal file
308
fxlink/main.c
Normal file
|
@ -0,0 +1,308 @@
|
|||
#include "config.h"
|
||||
#include "fxlink.h"
|
||||
#include "util.h"
|
||||
#include "properties.h"
|
||||
#include "filter.h"
|
||||
#include "usb.h"
|
||||
|
||||
#include <libusb.h>
|
||||
#include <getopt.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
/* Main functions for each mdoe */
|
||||
int main_list(filter_t *filter, delay_t *delay, libusb_context *context);
|
||||
int main_test(libusb_device *device, libusb_context *context);
|
||||
|
||||
static const char *help_string =
|
||||
"usage: %1$s -l [options...]\n"
|
||||
" %1$s -b [options...]\n"
|
||||
" %1$s -s [options...] <FILES...>\n"
|
||||
" %1$s --test\n"
|
||||
"\n"
|
||||
"fxlink interacts with CASIO calculators of the fx-9860G and fx-CG 50 series\n"
|
||||
"over the USB port, through mass storage and custom USB protocols. Depending\n"
|
||||
"on the mode, fxlink uses libusb (for discovery and USB communication)or\n"
|
||||
"the UDisks2 library (to mount and use Mass Storage devics).\n"
|
||||
"\n"
|
||||
"Operating modes:\n"
|
||||
" -l, --list List detected calculators on the USB ports (libusb)\n"
|
||||
" -b, --blocks List detected Mass Storage filesystems (udisks2)\n"
|
||||
" -s, --send Send a file to a Mass Storage calculator (udisks2)\n"
|
||||
" --test Communication tests by Lephe (libusb) [WIP!]\n"
|
||||
"\n"
|
||||
"General options:\n"
|
||||
" -w DELAY Wait up to this many seconds for a calculator to\n"
|
||||
" connect. If DELAY is unspecified, wait indefinitely.\n"
|
||||
" -f FILTER Filter which calculators can be detected and used\n"
|
||||
" --libusb-log=LEVEL libusb log level: NONE, ERROR, WARNING, INFO, DEBUG\n"
|
||||
"\n"
|
||||
"Device filters:\n"
|
||||
" A device filter is a comma-separated list of properties that a device has\n"
|
||||
" to match in order to be listed or used, such as 'p7,serial=00000001'.\n"
|
||||
" Several filters can be separated with a semicolon, in which case a device\n"
|
||||
" will be considered as long as it matches one of the filters. For example,\n"
|
||||
" 'p7 ; mass_storage,serial=IGQcGRe9'.\n"
|
||||
"\n"
|
||||
" The following properties are defined; the libraries in which each can be\n"
|
||||
" detected and used is indicated in brackets.\n"
|
||||
" p7 Matches Protocol 7 calculators (all the FX models\n"
|
||||
" except the G-III). [libusb, udisks2]\n"
|
||||
" mass_storage Matches Mass Storage calculators (the CG series and\n"
|
||||
" the G-III). [libusb, udisks2]\n"
|
||||
" series_cg Matches CG-series calculators. [udisks2]\n"
|
||||
" series_g3 Matches G-III series calculators. [udisks2]\n"
|
||||
" serial_number=ID Matches this specific serial number. Requires write\n"
|
||||
" access to the device in libusb. [libusb, udisks2]\n";
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int rc=1, mode=0, error=0, option=0, loglevel=LIBUSB_LOG_LEVEL_ERROR;
|
||||
delay_t delay = delay_seconds(0);
|
||||
filter_t *filter = NULL;
|
||||
|
||||
//---
|
||||
// Command-line argument parsing
|
||||
//---
|
||||
|
||||
enum { TEST=1, LIBUSB_LOG };
|
||||
const struct option longs[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "list", no_argument, NULL, 'l' },
|
||||
{ "blocks", no_argument, NULL, 'b' },
|
||||
{ "send", no_argument, NULL, 's' },
|
||||
{ "test", no_argument, NULL, TEST },
|
||||
{ "libusb-log", required_argument, NULL, LIBUSB_LOG },
|
||||
};
|
||||
|
||||
while(option >= 0 && option != '?')
|
||||
switch((option = getopt_long(argc, argv, "hlbsf:w::", longs, NULL)))
|
||||
{
|
||||
case 'h':
|
||||
fprintf(stderr, help_string, argv[0]);
|
||||
return 0;
|
||||
case 'l':
|
||||
case 'b':
|
||||
case 's':
|
||||
case TEST:
|
||||
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 'w':
|
||||
if(!optarg) {
|
||||
delay = delay_infinite();
|
||||
break;
|
||||
}
|
||||
char *end;
|
||||
int seconds = strtol(optarg, &end, 10);
|
||||
if(seconds < 0 || *end != 0) {
|
||||
error = err("invalid delay '%s'\n", optarg);
|
||||
break;
|
||||
}
|
||||
delay = delay_seconds(seconds);
|
||||
break;
|
||||
case 'f':
|
||||
filter = filter_parse(optarg);
|
||||
break;
|
||||
case '?':
|
||||
error = 1;
|
||||
}
|
||||
|
||||
if(mode == 's' && optind == argc)
|
||||
error = err("send mode requires additional arguments (file names)");
|
||||
|
||||
/* No arguments or bad arguments */
|
||||
if(error)
|
||||
return 1;
|
||||
if(!mode) {
|
||||
fprintf(stderr, help_string, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
//---
|
||||
// libusb initialization
|
||||
//---
|
||||
|
||||
libusb_context *context = NULL;
|
||||
|
||||
/* Initialize libusb for corresponding modes */
|
||||
if(mode == 'l' || mode == TEST) {
|
||||
if((rc = libusb_init(&context)))
|
||||
return libusb_err(rc, "error initializing libusb");
|
||||
libusb_set_option(context, LIBUSB_OPTION_LOG_LEVEL, loglevel);
|
||||
}
|
||||
|
||||
//---
|
||||
// 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 = err("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);
|
||||
#else
|
||||
rc = err("this fxlink was built without UDisks2; -s is disabled");
|
||||
#endif
|
||||
}
|
||||
else if(mode == TEST) {
|
||||
libusb_device *dev = NULL;
|
||||
int rc = usb_unique_wait(filter, &delay, context, &dev);
|
||||
|
||||
if(rc == FILTER_NONE)
|
||||
printf("No device found.\n");
|
||||
else if(rc == FILTER_MULTIPLE)
|
||||
printf("Multiple devices found, ambiguous!\n");
|
||||
else if(rc == FILTER_UNIQUE) {
|
||||
rc = main_test(dev, context);
|
||||
libusb_unref_device(dev);
|
||||
}
|
||||
}
|
||||
|
||||
if(context) libusb_exit(context);
|
||||
return rc;
|
||||
}
|
||||
|
||||
//---
|
||||
// Device list
|
||||
//---
|
||||
|
||||
int main_list(filter_t *filter, delay_t *delay, libusb_context *context)
|
||||
{
|
||||
/* Wait for a device to be connected */
|
||||
filter_clean_libusb(filter);
|
||||
usb_unique_wait(filter, delay, context, NULL);
|
||||
|
||||
int total_devices = 0;
|
||||
bool error;
|
||||
|
||||
for_libusb_devices(it, context, &error) {
|
||||
if(!filter_match(&it.props, filter)) continue;
|
||||
|
||||
if(total_devices > 0) printf("\n");
|
||||
|
||||
if(it.dc.idProduct == 0x6101)
|
||||
printf("fx-9860G series (Protocol 7) calculator\n");
|
||||
else if(it.dc.idProduct == 0x6102)
|
||||
printf("fx-CG or G-III series (USB Mass Storage) calculator\n");
|
||||
else
|
||||
printf("Unknown calculator (idProduct: %04x)\n", it.dc.idProduct);
|
||||
|
||||
printf(" Device location: Bus %d, Port %d, Device %d\n",
|
||||
libusb_get_bus_number(it.dev),
|
||||
libusb_get_port_number(it.dev),
|
||||
libusb_get_device_address(it.dev));
|
||||
printf(" Identification: idVendor: %04x, idProduct: %04x\n",
|
||||
it.dc.idVendor, it.dc.idProduct);
|
||||
/* FIXME: This assumes a short path (no hub or dual-device) */
|
||||
printf(" Guessed sysfs path: /sys/bus/usb/devices/%d-%d/\n",
|
||||
libusb_get_bus_number(it.dev),
|
||||
libusb_get_port_number(it.dev));
|
||||
|
||||
char *serial = it.dh ? usb_serial_number(it.dh) : NULL;
|
||||
if(serial)
|
||||
printf(" Serial number: %s\n", serial);
|
||||
free(serial);
|
||||
|
||||
printf(" Properties: ");
|
||||
properties_print(stdout, &it.props);
|
||||
printf("\n");
|
||||
|
||||
total_devices++;
|
||||
}
|
||||
|
||||
if(!error && !total_devices)
|
||||
printf("No%s device found.\n", filter ? " matching" : "");
|
||||
return 0;
|
||||
}
|
||||
|
||||
//---
|
||||
// WIP tests
|
||||
//---
|
||||
|
||||
int main_test(libusb_device *dev, libusb_context *context)
|
||||
{
|
||||
libusb_device_handle *dh;
|
||||
int rc;
|
||||
(void)context;
|
||||
|
||||
if((rc = libusb_open(dev, &dh)))
|
||||
return libusb_err(rc, "cannot open device %s", usb_id(dev));
|
||||
|
||||
printf("driver active: %d\n", libusb_kernel_driver_active(dh, 0));
|
||||
|
||||
if((rc = libusb_claim_interface(dh, 0))) {
|
||||
libusb_close(dh);
|
||||
return libusb_err(rc, "cannot claim interface on %s", usb_id(dev));
|
||||
}
|
||||
|
||||
uint8_t buffer[512];
|
||||
int transferred;
|
||||
|
||||
rc = libusb_bulk_transfer(dh, 0x81, buffer, 512, &transferred, 2000);
|
||||
|
||||
if(rc)
|
||||
rc = libusb_err(rc, "cannot perform bulk transfer on %s", usb_id(dev));
|
||||
else {
|
||||
printf("Got some data!\n");
|
||||
fwrite(buffer, 1, transferred, stdout);
|
||||
rc = 0;
|
||||
}
|
||||
|
||||
libusb_release_interface(dh, 0);
|
||||
libusb_close(dh);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* libudev tests, work but not useful yet */
|
||||
#if 0
|
||||
#include <libudev.h>
|
||||
int main_udev_test(libusb_device *dev)
|
||||
{
|
||||
struct udev *udev = NULL;
|
||||
struct udev_device *udev_device = NULL;
|
||||
|
||||
udev = udev_new();
|
||||
if(!udev) return err("cannot create udev context");
|
||||
|
||||
static char sys_path[128];
|
||||
sprintf(sys_path, "/sys/bus/usb/devices/%d-%d",
|
||||
libusb_get_bus_number(dev),
|
||||
libusb_get_port_number(dev));
|
||||
|
||||
udev_device = udev_device_new_from_syspath(udev, sys_path);
|
||||
if(!udev_device) {
|
||||
udev_unref(udev);
|
||||
return err("cannot get udev device for %s", sys_path);
|
||||
}
|
||||
|
||||
printf("Device number: %ld\n", udev_device_get_devnum(udev_device));
|
||||
printf("Device devnode: %s\n", udev_device_get_devnode(udev_device));
|
||||
|
||||
if(udev) udev_unref(udev);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
42
fxlink/properties.c
Normal file
42
fxlink/properties.c
Normal file
|
@ -0,0 +1,42 @@
|
|||
#include "properties.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
bool properties_match(properties_t const *props, properties_t const *option)
|
||||
{
|
||||
if(option->p7 && !props->p7)
|
||||
return false;
|
||||
if(option->mass_storage && !props->mass_storage)
|
||||
return false;
|
||||
if(option->series_cg && !props->series_cg)
|
||||
return false;
|
||||
if(option->series_g3 && !props->series_g3)
|
||||
return false;
|
||||
if(option->serial_number && (!props->serial_number ||
|
||||
strcmp(option->serial_number, props->serial_number)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void properties_print(FILE *fp, properties_t const *props)
|
||||
{
|
||||
#define output(...) { \
|
||||
if(sep) fprintf(fp, ", "); \
|
||||
fprintf(fp, __VA_ARGS__); \
|
||||
sep = true; \
|
||||
}
|
||||
|
||||
bool sep = false;
|
||||
if(props->p7)
|
||||
output("p7");
|
||||
if(props->mass_storage)
|
||||
output("mass_storage");
|
||||
if(props->series_cg)
|
||||
output("series_cg");
|
||||
if(props->series_g3)
|
||||
output("series_g3");
|
||||
if(props->serial_number)
|
||||
output("serial_number=%s", props->serial_number);
|
||||
}
|
65
fxlink/properties.h
Normal file
65
fxlink/properties.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
//---
|
||||
// fxlink:properties - Detected models and properties of devices
|
||||
//---
|
||||
|
||||
#ifndef FXLINK_PROPERTIES_H
|
||||
#define FXLINK_PROPERTIES_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* properties_t: Type of properties that can be detected on a device
|
||||
|
||||
This structure lists all the properties that fxlink can detect on connected
|
||||
devices. These properties help identify the devices in interactive use, and
|
||||
accurately specify which calculators to interact with when several models
|
||||
are connected simultaneously or when the same command in run against
|
||||
different models in a script.
|
||||
|
||||
Depending on the backend and access privileges, not all properties can be
|
||||
detected. The backends supporting each property are listed in brackets at
|
||||
the end of each description.
|
||||
|
||||
An instance of this structure can also be used as a filter. In order for the
|
||||
semantics of filtering to work out, every attribute needs to have a "total
|
||||
information order", meaning that two values can be compared for how specific
|
||||
they are. A device will match a filter if and only if all of its properties
|
||||
are more specific than the values provided in the filter.
|
||||
|
||||
Currently the order is "true more specific than false" for all booleans and
|
||||
"any value more specific than NULL" for the serial number. Properties that
|
||||
cannot be detected by back-ends are reset to their least specific value (ie.
|
||||
ignored). */
|
||||
typedef struct {
|
||||
|
||||
/* The calculator is a Protocol 7 calculator (idProduct: 0x6101). This
|
||||
makes no sense in UDisks2 as a P7 calculator has no disks, therefore
|
||||
this property is always false in UDisks2. [libusb, udisks2] */
|
||||
bool p7;
|
||||
/* The calculator is a Mass Storage calculator (idProduct: 0x6102). All
|
||||
devices detected in UDisks2 are of this type. [libusb, udisks2] */
|
||||
bool mass_storage;
|
||||
/* The calculator is an fx-CG series. [udisks2] */
|
||||
bool series_cg;
|
||||
/* The calculator is a G-III series. [udisks2] */
|
||||
bool series_g3;
|
||||
|
||||
/* Serial number. This can only be obtained in libusb if the user has
|
||||
write access to the device, because libusb needs to send a request for
|
||||
the STRING descriptor holding the serial number. [libusb, udisks2] */
|
||||
char *serial_number;
|
||||
|
||||
} properties_t;
|
||||
|
||||
/* properties_match(): Check whether some properties match a given option
|
||||
|
||||
Returns true if (props) is more specific than (option), meaning that every
|
||||
property mentioned in (option) is indeed set in (props). This is a building
|
||||
block for filter_match() and probably doesn't need to be used directly. */
|
||||
bool properties_match(properties_t const *props, properties_t const *option);
|
||||
|
||||
/* properties_print(): Print a property set (one-line) */
|
||||
void properties_print(FILE *fp, properties_t const *props);
|
||||
|
||||
#endif /* FXLINK_PROPERTIES_H */
|
383
fxlink/ud2.c
Normal file
383
fxlink/ud2.c
Normal file
|
@ -0,0 +1,383 @@
|
|||
#include "config.h"
|
||||
#ifndef FXLINK_DISABLE_UDISKS2
|
||||
|
||||
#include "ud2.h"
|
||||
#include "fxlink.h"
|
||||
#include "util.h"
|
||||
#include "properties.h"
|
||||
#include "filter.h"
|
||||
|
||||
#include <udisks/udisks.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
//---
|
||||
// UDisks2 utility functions
|
||||
//---
|
||||
|
||||
int ud2_start(UDisksClient **udc_ptr, UDisksManager **udm_ptr)
|
||||
{
|
||||
GError *error = NULL;
|
||||
|
||||
UDisksClient *udc = udisks_client_new_sync(NULL, &error);
|
||||
if(error)
|
||||
return err("cannot open udisks2 client: %s", error->message);
|
||||
|
||||
UDisksManager *udm = udisks_client_get_manager(udc);
|
||||
if(!udm) {
|
||||
g_object_unref(udc);
|
||||
return err("udisks2 daemon does not seem to be running");
|
||||
}
|
||||
|
||||
*udc_ptr = udc;
|
||||
*udm_ptr = udm;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ud2_end(UDisksClient *udc, __attribute__((unused)) UDisksManager *udm)
|
||||
{
|
||||
g_object_unref(udc);
|
||||
}
|
||||
|
||||
gchar **ud2_block_devices(UDisksManager *udm)
|
||||
{
|
||||
gchar **blocks = NULL;
|
||||
GVariant *args = g_variant_new("a{sv}", NULL);
|
||||
GError *error = NULL;
|
||||
|
||||
udisks_manager_call_get_block_devices_sync(udm,args,&blocks,NULL,&error);
|
||||
if(error) {
|
||||
err("cannot list udisks2 block devices: %s", error->message);
|
||||
}
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
UDisksBlock *ud2_block(UDisksClient *udc, gchar const *name)
|
||||
{
|
||||
UDisksObject *obj = udisks_client_get_object(udc, name);
|
||||
return obj ? udisks_object_get_block(obj) : NULL;
|
||||
}
|
||||
|
||||
UDisksDrive *ud2_drive(UDisksClient *udc, gchar const *name)
|
||||
{
|
||||
UDisksObject *obj = udisks_client_get_object(udc, name);
|
||||
return obj ? udisks_object_get_drive(obj) : NULL;
|
||||
}
|
||||
|
||||
UDisksFilesystem *ud2_filesystem(UDisksClient *udc, gchar const *name)
|
||||
{
|
||||
UDisksObject *obj = udisks_client_get_object(udc, name);
|
||||
return obj ? udisks_object_get_filesystem(obj) : NULL;
|
||||
}
|
||||
|
||||
//---
|
||||
// Matching and properties
|
||||
//---
|
||||
|
||||
bool is_casio_drive(UDisksDrive *drive)
|
||||
{
|
||||
return strstr(udisks_drive_get_vendor(drive), "CASIO") != NULL;
|
||||
}
|
||||
|
||||
properties_t ud2_properties(UDisksDrive *drive)
|
||||
{
|
||||
properties_t props = { 0 };
|
||||
props.p7 = false;
|
||||
props.mass_storage = true;
|
||||
|
||||
if(!strcmp(udisks_drive_get_model(drive), "ColorGraph"))
|
||||
props.series_cg = true;
|
||||
else if(!strcmp(udisks_drive_get_model(drive), "Calculator"))
|
||||
props.series_g3 = true;
|
||||
|
||||
gchar const *s = udisks_drive_get_serial(drive);
|
||||
/* LINK sends a 12-byte serial number with four leading 0. Remove them */
|
||||
if(s && strlen(s) == 12 && !strncmp(s, "0000", 4)) s+= 4;
|
||||
props.serial_number = (char *)s;
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
int ud2_unique_matching(filter_t const *filter, UDisksClient *udc,
|
||||
UDisksManager *udm, UDisksBlock **block_ptr, UDisksDrive **drive_ptr,
|
||||
UDisksFilesystem **fs_ptr)
|
||||
{
|
||||
int status = FILTER_NONE;
|
||||
bool error;
|
||||
|
||||
UDisksBlock *block = NULL;
|
||||
UDisksDrive *drive = NULL;
|
||||
UDisksFilesystem *fs = NULL;
|
||||
|
||||
for_udisks2_devices(it, udc, udm, &error) {
|
||||
if(!filter_match(&it.props, filter)) continue;
|
||||
|
||||
/* Already found a device before */
|
||||
if(status == FILTER_UNIQUE) {
|
||||
status = FILTER_MULTIPLE;
|
||||
g_object_unref(fs);
|
||||
g_object_unref(drive);
|
||||
g_object_unref(block);
|
||||
block = NULL;
|
||||
drive = NULL;
|
||||
fs = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
/* First device: record it */
|
||||
block = g_object_ref(it.block);
|
||||
drive = g_object_ref(it.drive);
|
||||
fs = g_object_ref(it.fs);
|
||||
status = FILTER_UNIQUE;
|
||||
}
|
||||
if(error)
|
||||
return FILTER_ERROR;
|
||||
|
||||
if(block_ptr) *block_ptr = block;
|
||||
else g_object_unref(block);
|
||||
|
||||
if(drive_ptr) *drive_ptr = drive;
|
||||
else g_object_unref(drive);
|
||||
|
||||
if(fs_ptr) *fs_ptr = fs;
|
||||
else g_object_unref(fs);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
int ud2_unique_wait(filter_t const *filter, delay_t *delay, UDisksClient *udc,
|
||||
UDisksManager *udm, UDisksBlock **block, UDisksDrive **drive,
|
||||
UDisksFilesystem **fs)
|
||||
{
|
||||
while(true) {
|
||||
int rc = ud2_unique_matching(filter, udc, udm, block, drive, fs);
|
||||
if(rc != FILTER_NONE) return rc;
|
||||
if(delay_cycle(delay)) return FILTER_NONE;
|
||||
udisks_client_settle(udc);
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
// Iteration on UDisks2 devices
|
||||
//---
|
||||
|
||||
ud2_iterator_t ud2_iter_start(UDisksClient *udc, UDisksManager *udm,
|
||||
bool *error)
|
||||
{
|
||||
ud2_iterator_t it = { .udc = udc };
|
||||
|
||||
it.devices = ud2_block_devices(udm);
|
||||
if(!it.devices) {
|
||||
it.done = true;
|
||||
if(error) *error = true;
|
||||
return it;
|
||||
}
|
||||
|
||||
it.index = -1;
|
||||
ud2_iter_next(&it);
|
||||
if(error) *error = false;
|
||||
return it;
|
||||
}
|
||||
|
||||
void ud2_iter_next(ud2_iterator_t *it)
|
||||
{
|
||||
if(it->done == true) return;
|
||||
|
||||
/* Free the resources from the previous iteration */
|
||||
if(it->fs) g_object_unref(it->fs);
|
||||
if(it->drive) g_object_unref(it->drive);
|
||||
if(it->block) g_object_unref(it->block);
|
||||
it->block = NULL;
|
||||
it->drive = NULL;
|
||||
it->fs = NULL;
|
||||
|
||||
/* Load the next device */
|
||||
if(!it->devices[++it->index]) {
|
||||
it->done = true;
|
||||
}
|
||||
else {
|
||||
gchar const *path = it->devices[it->index];
|
||||
|
||||
it->block = ud2_block(it->udc, path);
|
||||
if(!it->block) return ud2_iter_next(it);
|
||||
|
||||
/* Skip non-CASIO devices right away */
|
||||
it->drive = ud2_drive(it->udc, udisks_block_get_drive(it->block));
|
||||
if(!it->drive || !is_casio_drive(it->drive))
|
||||
return ud2_iter_next(it);
|
||||
|
||||
/* Only consider file systems (not partition tables) */
|
||||
it->fs = ud2_filesystem(it->udc, path);
|
||||
if(!it->fs) return ud2_iter_next(it);
|
||||
|
||||
it->props = ud2_properties(it->drive);
|
||||
}
|
||||
|
||||
if(it->done)
|
||||
g_strfreev(it->devices);
|
||||
}
|
||||
|
||||
//---
|
||||
// Main functions
|
||||
//---
|
||||
|
||||
int main_blocks(filter_t *filter, delay_t *delay)
|
||||
{
|
||||
filter_clean_udisks2(filter);
|
||||
|
||||
UDisksClient *udc = NULL;
|
||||
UDisksManager *udm = NULL;
|
||||
if(ud2_start(&udc, &udm)) return 1;
|
||||
|
||||
ud2_unique_wait(filter, delay, udc, udm, NULL, NULL, NULL);
|
||||
|
||||
int total_devices = 0;
|
||||
bool error;
|
||||
|
||||
for_udisks2_devices(it, udc, udm, &error) {
|
||||
if(!filter_match(&it.props, filter)) continue;
|
||||
|
||||
if(total_devices > 0) printf("\n");
|
||||
|
||||
if(it.props.series_cg)
|
||||
printf("fx-CG series USB Mass Storage filesystem\n");
|
||||
else if(it.props.series_g3)
|
||||
printf("G-III series USB Mass Storage filesystem\n");
|
||||
else
|
||||
printf("Unknown USB Mass Storage filesystem\n");
|
||||
|
||||
printf(" Block device: %s\n",
|
||||
udisks_block_get_device(it.block));
|
||||
printf(" Identification: Vendor: %s, Model: %s\n",
|
||||
udisks_drive_get_vendor(it.drive),
|
||||
udisks_drive_get_model(it.drive));
|
||||
|
||||
if(it.props.serial_number)
|
||||
printf(" Serial number: %s\n", it.props.serial_number);
|
||||
|
||||
gchar const * const * mount_points =
|
||||
udisks_filesystem_get_mount_points(it.fs);
|
||||
if(!mount_points || !mount_points[0]) {
|
||||
printf(" Mounted: no\n");
|
||||
}
|
||||
else for(int i = 0; mount_points[i]; i++) {
|
||||
printf(" Mounted at: %s\n", mount_points[i]);
|
||||
}
|
||||
|
||||
printf(" Properties: ");
|
||||
properties_print(stdout, &it.props);
|
||||
printf("\n");
|
||||
|
||||
total_devices++;
|
||||
}
|
||||
|
||||
if(!error && !total_devices)
|
||||
printf("No%s device found.\n", filter ? " matching" : "");
|
||||
|
||||
ud2_end(udc, udm);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main_send(filter_t *filter, delay_t *delay, char **files)
|
||||
{
|
||||
filter_clean_udisks2(filter);
|
||||
GError *error = NULL;
|
||||
char **argv = NULL;
|
||||
int rc = 0;
|
||||
|
||||
UDisksClient *udc = NULL;
|
||||
UDisksManager *udm = NULL;
|
||||
if(ud2_start(&udc, &udm)) return 1;
|
||||
|
||||
UDisksBlock *block = NULL;
|
||||
UDisksDrive *drive = NULL;
|
||||
UDisksFilesystem *fs = NULL;
|
||||
rc = ud2_unique_wait(filter, delay, udc, udm, &block, &drive, &fs);
|
||||
if(rc != FILTER_UNIQUE) {
|
||||
rc = 1;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Determine a mount folder, mounting the volume if needed */
|
||||
gchar *folder = NULL;
|
||||
bool mounted_here = false;
|
||||
|
||||
gchar const *dev = udisks_block_get_device(block);
|
||||
gchar const * const * mount_points =
|
||||
udisks_filesystem_get_mount_points(fs);
|
||||
|
||||
if(!mount_points || !mount_points[0]) {
|
||||
GVariant *args = g_variant_new("a{sv}", NULL);
|
||||
udisks_filesystem_call_mount_sync(fs, args, &folder, NULL, &error);
|
||||
if(error) {
|
||||
rc = err("cannot mount %s: %s", dev, error->message);
|
||||
goto end;
|
||||
}
|
||||
printf("Mounted %s to %s.\n", dev, folder);
|
||||
mounted_here = true;
|
||||
}
|
||||
else {
|
||||
folder = strdup(mount_points[0]);
|
||||
printf("Already mounted at %s.\n", folder);
|
||||
mounted_here = false;
|
||||
}
|
||||
|
||||
/* Copy files with external cp(1) */
|
||||
int file_count = 0;
|
||||
while(files[file_count]) file_count++;
|
||||
|
||||
argv = malloc((file_count + 3) * sizeof *argv);
|
||||
if(!argv) {
|
||||
rc = err("cannot allocate argv array for cp(1)");
|
||||
goto end;
|
||||
}
|
||||
argv[0] = "cp";
|
||||
for(int i = 0; files[i]; i++)
|
||||
argv[i+1] = files[i];
|
||||
argv[file_count+1] = folder;
|
||||
argv[file_count+2] = NULL;
|
||||
|
||||
/* Print command */
|
||||
printf("Running cp");
|
||||
for(int i = 1; argv[i]; i++) printf(" '%s'", argv[i]);
|
||||
printf("\n");
|
||||
|
||||
pid_t pid = fork();
|
||||
|
||||
if(pid == 0) {
|
||||
execvp("cp", argv);
|
||||
}
|
||||
else if(pid == -1) {
|
||||
rc = err("failed to fork to invoke cp");
|
||||
goto end;
|
||||
}
|
||||
else {
|
||||
waitpid(pid, NULL, 0);
|
||||
}
|
||||
|
||||
/* Unmount the filesystem and eject the device if we mounted it */
|
||||
if(mounted_here) {
|
||||
GVariant *args = g_variant_new("a{sv}", NULL);
|
||||
udisks_filesystem_call_unmount_sync(fs, args, NULL, &error);
|
||||
if(error) err("while unmounting %s: %s", dev, error->message);
|
||||
else printf("Unmounted %s.\n", dev);
|
||||
|
||||
args = g_variant_new("a{sv}", NULL);
|
||||
udisks_drive_call_power_off_sync(drive, args, NULL, &error);
|
||||
if(error) err("while ejecting %s: %s", dev, error->message);
|
||||
else printf("Ejected %s.\n", dev);
|
||||
}
|
||||
|
||||
end:
|
||||
free(folder);
|
||||
if(argv) free(argv);
|
||||
if(fs) g_object_unref(fs);
|
||||
if(drive) g_object_unref(drive);
|
||||
if(block) g_object_unref(block);
|
||||
if(udc && udm) ud2_end(udc, udm);
|
||||
return rc;
|
||||
}
|
||||
|
||||
#endif /* FXLINK_DISABLE_UDISKS2 */
|
70
fxlink/ud2.h
Normal file
70
fxlink/ud2.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
//---
|
||||
// fxlink:ud2 - UDisks2 functions
|
||||
//---
|
||||
|
||||
#ifndef FXLINK_UD2_H
|
||||
#define FXLINK_UD2_H
|
||||
|
||||
#ifndef FXLINK_DISABLE_UDISKS2
|
||||
|
||||
#include <udisks/udisks.h>
|
||||
#include "config.h"
|
||||
#include "properties.h"
|
||||
#include "filter.h"
|
||||
#include "util.h"
|
||||
|
||||
/* ud2_properties(): Determine properties of a UDisks2 USB drive */
|
||||
properties_t ud2_properties(UDisksDrive *drive);
|
||||
|
||||
/* ud2_unique_matching(): Device matching the provided filter, if unique
|
||||
Similar to usb_unique_matching(), please refer to "usb.h" for details.
|
||||
There are just many more inputs and outputs. */
|
||||
int ud2_unique_matching(filter_t const *filter, UDisksClient *udc,
|
||||
UDisksManager *udm, UDisksBlock **block, UDisksDrive **drive,
|
||||
UDisksFilesystem **fs);
|
||||
|
||||
/* ud2_unique_wait(): Wait for a device matching the provided filter to connect
|
||||
Like usb_unique_wait(), please see "usb.h" for details. */
|
||||
int ud2_unique_wait(filter_t const *filter, delay_t *delay, UDisksClient *udc,
|
||||
UDisksManager *udm, UDisksBlock **block, UDisksDrive **drive,
|
||||
UDisksFilesystem **fs);
|
||||
|
||||
//---
|
||||
// Iteration on UDisks2 devices
|
||||
//---
|
||||
|
||||
typedef struct {
|
||||
/* Current block, associated drive and filesystem */
|
||||
UDisksBlock *block;
|
||||
UDisksDrive *drive;
|
||||
UDisksFilesystem *fs;
|
||||
/* Device properties */
|
||||
properties_t props;
|
||||
/* Whether the iteration has finished */
|
||||
bool done;
|
||||
|
||||
/* Internal indicators: list of devices and current index */
|
||||
gchar **devices;
|
||||
int index;
|
||||
/* Client for object queries */
|
||||
UDisksClient *udc;
|
||||
|
||||
} ud2_iterator_t;
|
||||
|
||||
/* ud2_iter_start(): Start an iteration on UDisks2 devices
|
||||
If the first step fails, returns an iterator with (done = true) and sets
|
||||
(*error) to true; otherwise, sets (*error) to false. */
|
||||
ud2_iterator_t ud2_iter_start(UDisksClient *udc, UDisksManager *udm,
|
||||
bool *error);
|
||||
|
||||
/* ud2_iter_next(): Iterate to the next UDisks2 device */
|
||||
void ud2_iter_next(ud2_iterator_t *it);
|
||||
|
||||
/* Convenience for-loop macro for iteration */
|
||||
#define for_udisks2_devices(NAME, udc, udm, error) \
|
||||
for(ud2_iterator_t NAME = ud2_iter_start(udc, udm, error); \
|
||||
!NAME.done; ud2_iter_next(&NAME)) if(!NAME.done)
|
||||
|
||||
#endif /* FXLINK_DISABLE_UDISKS2 */
|
||||
|
||||
#endif /* FXLINK_UD2_H */
|
159
fxlink/usb.c
Normal file
159
fxlink/usb.c
Normal file
|
@ -0,0 +1,159 @@
|
|||
#include "usb.h"
|
||||
#include "fxlink.h"
|
||||
#include "util.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
char const *usb_id(libusb_device *dev)
|
||||
{
|
||||
static char id[32];
|
||||
sprintf(id, "%d:%d",
|
||||
libusb_get_bus_number(dev),
|
||||
libusb_get_device_address(dev));
|
||||
return id;
|
||||
}
|
||||
|
||||
char *usb_serial_number(libusb_device_handle *dh)
|
||||
{
|
||||
struct libusb_device_descriptor dc;
|
||||
libusb_device *dev = libusb_get_device(dh);
|
||||
|
||||
if(libusb_get_device_descriptor(dev, &dc))
|
||||
return NULL;
|
||||
if(!dc.iSerialNumber)
|
||||
return NULL;
|
||||
|
||||
char serial[256];
|
||||
int length = libusb_get_string_descriptor_ascii(dh, dc.iSerialNumber,
|
||||
(unsigned char *)serial, 256);
|
||||
if(length < 0)
|
||||
return NULL;
|
||||
|
||||
/* LINK sends a 12-byte serial number with four leading 0. Remove them */
|
||||
int start = (length == 12 && !strncmp(serial, "0000", 4)) ? 4 : 0;
|
||||
return strndup(serial + start, length - start);
|
||||
}
|
||||
|
||||
properties_t usb_properties(struct libusb_device_descriptor *dc,
|
||||
libusb_device_handle *dh)
|
||||
{
|
||||
properties_t props = { 0 };
|
||||
|
||||
/* Type of calculator based on USB behavior, detected by idProduct */
|
||||
if(dc->idProduct == 0x6101)
|
||||
props.p7 = true;
|
||||
if(dc->idProduct == 0x6102)
|
||||
props.mass_storage = true;
|
||||
if(dh)
|
||||
props.serial_number = usb_serial_number(dh);
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
int usb_unique_matching(filter_t const *filter, libusb_context *context,
|
||||
libusb_device **dev)
|
||||
{
|
||||
libusb_device *unique = NULL;
|
||||
int status = FILTER_NONE;
|
||||
bool error;
|
||||
|
||||
for_libusb_devices(it, context, &error) {
|
||||
if(!filter_match(&it.props, filter)) continue;
|
||||
|
||||
/* Already found a device before */
|
||||
if(unique) {
|
||||
status = FILTER_MULTIPLE;
|
||||
libusb_unref_device(unique);
|
||||
unique = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
/* First device: record it */
|
||||
unique = libusb_ref_device(it.dev);
|
||||
status = FILTER_UNIQUE;
|
||||
}
|
||||
if(error)
|
||||
return FILTER_ERROR;
|
||||
|
||||
/* Don't keep the reference to the device if we're not returning it */
|
||||
if(unique && !dev)
|
||||
libusb_unref_device(unique);
|
||||
if(unique && dev)
|
||||
*dev = unique;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
int usb_unique_wait(filter_t const *filter, delay_t *delay,
|
||||
libusb_context *context, libusb_device **dev)
|
||||
{
|
||||
while(true) {
|
||||
int rc = usb_unique_matching(filter, context, dev);
|
||||
|
||||
/* If a device is found, multiple devices are found, or an error
|
||||
occurs, forward the result; wait only if nothing was found */
|
||||
if(rc != FILTER_NONE) return rc;
|
||||
if(delay_cycle(delay)) return FILTER_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
//---
|
||||
// Iteration on libusb devices
|
||||
//---
|
||||
|
||||
usb_iterator_t usb_iter_start(libusb_context *context, bool *error)
|
||||
{
|
||||
usb_iterator_t it = { 0 };
|
||||
|
||||
it.device_count = libusb_get_device_list(context, &it.devices);
|
||||
if(it.device_count < 0) {
|
||||
libusb_err(it.device_count, "cannot get libusb device list");
|
||||
it.done = true;
|
||||
if(error) *error = true;
|
||||
return it;
|
||||
}
|
||||
|
||||
it.index = -1;
|
||||
usb_iter_next(&it);
|
||||
if(error) *error = false;
|
||||
return it;
|
||||
}
|
||||
|
||||
void usb_iter_next(usb_iterator_t *it)
|
||||
{
|
||||
if(it->done == true) return;
|
||||
int rc;
|
||||
|
||||
/* Free the resources from the previous iteration */
|
||||
if(it->dh)
|
||||
libusb_close(it->dh);
|
||||
it->dev = NULL;
|
||||
it->dh = NULL;
|
||||
|
||||
/* Load the next device */
|
||||
if(++it->index >= it->device_count) {
|
||||
it->done = true;
|
||||
}
|
||||
else {
|
||||
it->dev = it->devices[it->index];
|
||||
|
||||
if((rc = libusb_get_device_descriptor(it->dev, &it->dc))) {
|
||||
libusb_err(rc, "cannot get descriptor for device %s",
|
||||
usb_id(it->dev));
|
||||
return usb_iter_next(it);
|
||||
}
|
||||
|
||||
/* Ignore non-CASIO devices */
|
||||
if(it->dc.idVendor != 0x07cf)
|
||||
return usb_iter_next(it);
|
||||
|
||||
if((rc = libusb_open(it->dev, &it->dh)))
|
||||
libusb_wrn(rc, "cannot open device %s", usb_id(it->dev));
|
||||
|
||||
it->props = usb_properties(&it->dc, it->dh);
|
||||
}
|
||||
|
||||
if(it->done)
|
||||
libusb_free_device_list(it->devices, true);
|
||||
}
|
137
fxlink/usb.h
Normal file
137
fxlink/usb.h
Normal file
|
@ -0,0 +1,137 @@
|
|||
//---
|
||||
// fxlink:usb - libusb functions
|
||||
//---
|
||||
|
||||
#ifndef FXLINK_USB_H
|
||||
#define FXLINK_USB_H
|
||||
|
||||
#include "util.h"
|
||||
#include "properties.h"
|
||||
#include "filter.h"
|
||||
#include <libusb.h>
|
||||
|
||||
/* usb_properties(): Determine as many properties of the device as possible
|
||||
|
||||
If the device can be opened, an open handle should be supplied as (dh). This
|
||||
is used to determine the serial number; if the device cannot be opened, the
|
||||
serial number is omitted from the device properties.
|
||||
|
||||
@dc Device descriptor
|
||||
@dh Open handle if the device can be opened, or NULL
|
||||
-> Returns detected properties of the device. */
|
||||
properties_t usb_properties(struct libusb_device_descriptor *dc,
|
||||
libusb_device_handle *dh);
|
||||
|
||||
/* usb_unique_matching(): Device that matches the provided filter, if unique
|
||||
|
||||
This function runs through the list of devices provided by libusb and
|
||||
determines whether there is exactly one device matching the filter. If so,
|
||||
a pointer to this device is set in (*dev) and FILTER_UNIQUE is returned. The
|
||||
device is referenced and should be un-referenced after use for the data to
|
||||
be freed. If (dev) is NULL, the pointer is not recorded and not referenced.
|
||||
|
||||
If there are no devices matching the filter, (*dev) is unchanged and this
|
||||
function returns FILTER_NONE. If several devices match the filter, (*dev) is
|
||||
unchanged and FILTER_MULTIPLE is returned. If an error occurs and the
|
||||
function cannot complete, an error is printed and FILTER_ERROR is returned.
|
||||
|
||||
@filter Device filter to refine the search
|
||||
@context Previously-initialized libusb context
|
||||
@dev Output: unique device matching the filter (may be NULL)
|
||||
-> Returns one of FILTER_{UNIQUE,NONE,MULTIPLE,ERROR}. */
|
||||
int usb_unique_matching(filter_t const *filter, libusb_context *context,
|
||||
libusb_device **dev);
|
||||
|
||||
/* usb_unique_wait(): Wait for a device matching the provided filter to connect
|
||||
|
||||
This function waits up to the provided delay for a device matching the
|
||||
specified filter to be connected. It calls usb_unique_matching() several
|
||||
times per second to check for new devices being attached and initialized.
|
||||
|
||||
If several devices are connected when usb_unique_wait() is first called, or
|
||||
several devices are connected between two calls to usb_unique_matching(),
|
||||
this function returns FILTER_MULTIPLE. As soon as a unique matching device
|
||||
is found, the pointer is referenced and set in (*dev) if (dev) is not NULL,
|
||||
and FILTER_UNIQUE is returned, regardless of whether other matching devices
|
||||
are attached before the end of the wait period.
|
||||
|
||||
If no matching device is attached during the specified period, this function
|
||||
returns FILTER_NONE. If an error occurs during scanning, it returns
|
||||
FILTER_ERROR.
|
||||
|
||||
@filter Device filter to refine the search
|
||||
@delay Time resource to use delay from
|
||||
@context Previously-initialized libusb context
|
||||
@dev Output: unique device matching the filter (can be NULL)
|
||||
-> Returns one of FILTER_{UNIQUE,NONE,MULTIPLE,ERROR}. */
|
||||
int usb_unique_wait(filter_t const *filter, delay_t *delay,
|
||||
libusb_context *context, libusb_device **dev);
|
||||
|
||||
//---
|
||||
// Iteration on libusb devices
|
||||
//---
|
||||
|
||||
typedef struct {
|
||||
/* Current device and its device descriptor */
|
||||
libusb_device *dev;
|
||||
struct libusb_device_descriptor dc;
|
||||
/* If the device can be opened, its open handle, otherwise NULL */
|
||||
libusb_device_handle *dh;
|
||||
/* Device properties */
|
||||
properties_t props;
|
||||
/* Whether the iteration has finished */
|
||||
bool done;
|
||||
|
||||
/* Internal indicators: list of devices and current index */
|
||||
libusb_device **devices;
|
||||
int device_count;
|
||||
int index;
|
||||
|
||||
} usb_iterator_t;
|
||||
|
||||
/* usb_iter_start(): Start an iteration on libusb devices
|
||||
If the first step fails, returns an iterator with (done = true) and sets
|
||||
(*error) to true; otherwise, sets (*error) to false. */
|
||||
usb_iterator_t usb_iter_start(libusb_context *context, bool *error);
|
||||
|
||||
/* usb_iter_next(): Iterate to the next libusb device */
|
||||
void usb_iter_next(usb_iterator_t *it);
|
||||
|
||||
/* Convenience for-loop macro for iteration */
|
||||
#define for_libusb_devices(NAME, context, error) \
|
||||
for(usb_iterator_t NAME = usb_iter_start(context, error); \
|
||||
!NAME.done; usb_iter_next(&NAME)) if(!NAME.done)
|
||||
|
||||
//---
|
||||
// Miscellaneous
|
||||
//---
|
||||
|
||||
/* usb_id(): Printable address-based identifier for error messages
|
||||
This function is used in error messages to describe the device on which an
|
||||
error occurred in a useful way. The pointer returned is to a static buffer
|
||||
that changes at every call to this function, and should only be used briefly
|
||||
to generate messages. */
|
||||
char const *usb_id(libusb_device *dev);
|
||||
|
||||
/* usb_serial_number(): Serial number advertised by the device
|
||||
|
||||
This function returns the serial number (as presented with iSerialNumber in
|
||||
the device descriptor) of the provided device, which may or may not be
|
||||
present.
|
||||
|
||||
Serial numbers for CASIO calculators normally have 8 letters. The LINK
|
||||
application presents a 12-character code with "0000" prepended. This
|
||||
function detects this quirk and only returns the last 8 characters. gint's
|
||||
driver doesn't send to "0000" prefix.
|
||||
|
||||
This function requires the device to be open in order to send the request
|
||||
for the STRING descriptor, and cannot be used if the process user doesn't
|
||||
have write access to the device.
|
||||
|
||||
@dh Open device handle
|
||||
-> Returns a freshly-allocated copy of the serial number string, to be
|
||||
free()'d after use, or NULL if the serial number is unspecified or cannot
|
||||
be retrieved. */
|
||||
char *usb_serial_number(libusb_device_handle *dh);
|
||||
|
||||
#endif /* FXLINK_USB_H */
|
32
fxlink/util.c
Normal file
32
fxlink/util.c
Normal file
|
@ -0,0 +1,32 @@
|
|||
#include "util.h"
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
|
||||
delay_t delay_none(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
delay_t delay_seconds(int seconds)
|
||||
{
|
||||
return seconds * 4;
|
||||
}
|
||||
delay_t delay_infinite(void)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool delay_cycle(delay_t *delay)
|
||||
{
|
||||
if(*delay == 0) return true;
|
||||
|
||||
struct timespec spec = { .tv_sec=0, .tv_nsec=250000000 };
|
||||
int rc;
|
||||
|
||||
/* Account for interrupts in the nanosleep(2) call */
|
||||
struct timespec req = spec;
|
||||
do rc = nanosleep(&req, &req);
|
||||
while(rc == -1 && errno == EINTR);
|
||||
|
||||
if(*delay > 0) (*delay)--;
|
||||
return false;
|
||||
}
|
60
fxlink/util.h
Normal file
60
fxlink/util.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
//---
|
||||
// fxlink:util - Utility functions and error reporting mechanisms
|
||||
//---
|
||||
|
||||
#ifndef FXLINK_UTIL_H
|
||||
#define FXLINK_UTIL_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* Literal error message printed to stderr, evaluates to 1 for a combined
|
||||
return/exit() call */
|
||||
#define err(fmt, ...) ({ \
|
||||
fprintf(stderr, "error: " fmt "\n" __VA_OPT__(,) __VA_ARGS__); \
|
||||
1; \
|
||||
})
|
||||
/* Fatal error that includes a libusb error message */
|
||||
#define libusb_err(rc, fmt, ...) ({ \
|
||||
fprintf(stderr, "error: " fmt ": %s\n" __VA_OPT__(,) __VA_ARGS__, \
|
||||
libusb_strerror(rc)); \
|
||||
1; \
|
||||
})
|
||||
/* Warning message */
|
||||
#define wrn(fmt, ...) \
|
||||
fprintf(stderr, "warning: " fmt "\n" __VA_OPT__(,) __VA_ARGS__)
|
||||
|
||||
/* Warning that includes a libusb error message */
|
||||
#define libusb_wrn(rc, fmt, ...) \
|
||||
fprintf(stderr, "error: " fmt ": %s\n" __VA_OPT__(,) __VA_ARGS__, \
|
||||
libusb_strerror(rc))
|
||||
|
||||
//---
|
||||
// Delay
|
||||
//---
|
||||
|
||||
/* delay_t: An expandable allocated time used to wait for devices */
|
||||
typedef int delay_t;
|
||||
|
||||
/* delay_none(): No delay allowed */
|
||||
delay_t delay_none(void);
|
||||
/* delay_seconds(): Initial delay from a duration in seconds */
|
||||
delay_t delay_seconds(int seconds);
|
||||
/* delay_infinite(): Delay that can run through delay_cycle() indefinitely */
|
||||
delay_t delay_infinite(void);
|
||||
|
||||
/* delay_cycle(): Wait for a short cycle
|
||||
|
||||
This function returns (true) if the delay has expired; otherwise, it waits
|
||||
for a short while (250 ms), decreases the supplied delay pointer, and
|
||||
returns (false).
|
||||
|
||||
Not returning (true) after waiting, even if the delay expires then, allows
|
||||
the caller to perform the task they were waiting on one last time before
|
||||
giving up.
|
||||
|
||||
@delay Input-output: Delay resource to take time from
|
||||
-> Return (true) if (*delay) has expired, or (false) after waiting. */
|
||||
bool delay_cycle(delay_t *delay);
|
||||
|
||||
#endif /* FXLINK_UTIL_H */
|
Loading…
Reference in a new issue