mirror of
https://git.planet-casio.com/Lephenixnoir/fxsdk.git
synced 2024-12-29 13:03: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)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
project(fxSDK VERSION 2.3.1 LANGUAGES C)
|
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(PkgConfig REQUIRED)
|
||||||
find_package(PNG REQUIRED)
|
find_package(PNG REQUIRED)
|
||||||
pkg_check_modules(libusb REQUIRED libusb-1.0 IMPORTED_TARGET)
|
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(CMAKE_INSTALL_MESSAGE LAZY)
|
||||||
set(SRC "${CMAKE_CURRENT_SOURCE_DIR}")
|
set(SRC "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
@ -26,6 +32,16 @@ add_custom_command(OUTPUT "${BIN}/fxsdk.sh"
|
||||||
DEPENDS "${SRC}/fxsdk/fxsdk.sh")
|
DEPENDS "${SRC}/fxsdk/fxsdk.sh")
|
||||||
add_custom_target(fxsdk ALL DEPENDS "${BIN}/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
|
# Install rules
|
||||||
|
|
||||||
# fxsdk
|
# fxsdk
|
||||||
|
@ -37,3 +53,5 @@ install(TARGETS fxg1a)
|
||||||
# fxconv
|
# fxconv
|
||||||
install(PROGRAMS fxconv/fxconv-main.py TYPE BIN RENAME fxconv)
|
install(PROGRAMS fxconv/fxconv-main.py TYPE BIN RENAME fxconv)
|
||||||
install(FILES fxconv/fxconv.py TYPE BIN)
|
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