mirror of
https://git.planet-casio.com/Lephenixnoir/fxsdk.git
synced 2024-12-29 13:03:37 +01:00
fxlink: full rewrite; deeper device management and TUI
This commit rewrites the entire device management layer of fxlink on the libusb side, providing new abstractions that support live/async device management, including communication. This system is put to use in a new TUI interactive mode (fxlink -t) which can run in the background, connects to calculators automatically without interfering with file transfer tools, and is much more detailed in its interface than the previous interactive mode (fxlink -i). The TUI mode will also soon be extended to support sending data.
This commit is contained in:
parent
d6ed47b133
commit
9d30377d90
45 changed files with 4608 additions and 1707 deletions
|
@ -15,6 +15,7 @@ endif()
|
||||||
if(NOT FXLINK_DISABLE_SDL2)
|
if(NOT FXLINK_DISABLE_SDL2)
|
||||||
pkg_check_modules(sdl2 REQUIRED sdl2 IMPORTED_TARGET)
|
pkg_check_modules(sdl2 REQUIRED sdl2 IMPORTED_TARGET)
|
||||||
endif()
|
endif()
|
||||||
|
pkg_check_modules(ncurses REQUIRED ncurses IMPORTED_TARGET)
|
||||||
|
|
||||||
set(CMAKE_INSTALL_MESSAGE LAZY)
|
set(CMAKE_INSTALL_MESSAGE LAZY)
|
||||||
set(SRC "${CMAKE_CURRENT_SOURCE_DIR}")
|
set(SRC "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
@ -28,7 +29,7 @@ add_executable(fxgxa fxgxa/dump.c fxgxa/edit.c fxgxa/file.c fxgxa/icon.c
|
||||||
target_include_directories(fxgxa PUBLIC fxgxa/)
|
target_include_directories(fxgxa PUBLIC fxgxa/)
|
||||||
target_link_libraries(fxgxa PkgConfig::libpng)
|
target_link_libraries(fxgxa PkgConfig::libpng)
|
||||||
|
|
||||||
# fxg1a as a symlink (for compatibility=
|
# fxg1a as a symlink (for compatibility)
|
||||||
add_custom_target(fxg1a ALL
|
add_custom_target(fxg1a ALL
|
||||||
COMMAND ${CMAKE_COMMAND} -E create_symlink "fxgxa" "fxg1a")
|
COMMAND ${CMAKE_COMMAND} -E create_symlink "fxgxa" "fxg1a")
|
||||||
|
|
||||||
|
@ -42,12 +43,31 @@ add_custom_command(OUTPUT "${BIN}/fxsdk.sh"
|
||||||
add_custom_target(fxsdk ALL DEPENDS "${BIN}/fxsdk.sh")
|
add_custom_target(fxsdk ALL DEPENDS "${BIN}/fxsdk.sh")
|
||||||
|
|
||||||
# fxlink
|
# fxlink
|
||||||
configure_file(fxlink/config.h.in "${BIN}/include/fxlink/config.h")
|
configure_file(fxlink/include/fxlink/config.h.in
|
||||||
add_executable(fxlink fxlink/usb.c fxlink/filter.c fxlink/interactive.c fxlink/push.c
|
"${BIN}/include/fxlink/config.h")
|
||||||
fxlink/main.c fxlink/png.c fxlink/properties.c fxlink/ud2.c fxlink/util.c
|
add_executable(fxlink
|
||||||
fxlink/protocol.c fxlink/sdl2.c)
|
fxlink/defs.c
|
||||||
target_link_libraries(fxlink PkgConfig::libpng PkgConfig::libusb)
|
fxlink/devices.c
|
||||||
target_include_directories(fxlink PRIVATE "${BIN}/include/fxlink")
|
fxlink/filter.c
|
||||||
|
fxlink/logging.c
|
||||||
|
fxlink/main.c
|
||||||
|
fxlink/protocol.c
|
||||||
|
fxlink/modes/interactive.c
|
||||||
|
fxlink/modes/list.c
|
||||||
|
fxlink/modes/push.c
|
||||||
|
fxlink/modes/tui-interactive.c
|
||||||
|
fxlink/modes/udisks2.c
|
||||||
|
fxlink/tooling/libpng.c
|
||||||
|
fxlink/tooling/sdl2.c
|
||||||
|
fxlink/tooling/udisks2.c
|
||||||
|
fxlink/tui/input.c
|
||||||
|
fxlink/tui/layout.c
|
||||||
|
fxlink/tui/render.c)
|
||||||
|
target_link_libraries(fxlink
|
||||||
|
PkgConfig::libpng PkgConfig::libusb PkgConfig::ncurses -lm)
|
||||||
|
target_include_directories(fxlink PRIVATE
|
||||||
|
"${BIN}/include"
|
||||||
|
"${SRC}/fxlink/include")
|
||||||
if(NOT FXLINK_DISABLE_UDISKS2)
|
if(NOT FXLINK_DISABLE_UDISKS2)
|
||||||
target_link_libraries(fxlink PkgConfig::udisks2)
|
target_link_libraries(fxlink PkgConfig::udisks2)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
//---
|
|
||||||
// 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
|
|
||||||
|
|
||||||
/* Disable SDL2 interfaces. */
|
|
||||||
#cmakedefine FXLINK_DISABLE_SDL2
|
|
||||||
|
|
||||||
#endif /* FXLINK_CONFIG_H */
|
|
148
fxlink/defs.c
Normal file
148
fxlink/defs.c
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
|
||||||
|
#include <fxlink/defs.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
char const *fmt_to_ANSI(int format)
|
||||||
|
{
|
||||||
|
static char buf[64];
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
strcpy(buf, "\e[0m");
|
||||||
|
n += 4;
|
||||||
|
|
||||||
|
int FG = fmt_FG(format);
|
||||||
|
int BG = fmt_BG(format);
|
||||||
|
|
||||||
|
if(FG != 0)
|
||||||
|
n += sprintf(buf+n, "\e[%dm", 30 + FG - 1);
|
||||||
|
if(BG != 0)
|
||||||
|
n += sprintf(buf+n, "\e[%dm", 40 + BG - 1);
|
||||||
|
if(fmt_BOLD(format))
|
||||||
|
strcpy(buf+n, "\e[1m"), n += 4;
|
||||||
|
if(fmt_DIM(format))
|
||||||
|
strcpy(buf+n, "\e[2m"), n += 4;
|
||||||
|
if(fmt_ITALIC(format))
|
||||||
|
strcpy(buf+n, "\e[3m"), n += 4;
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *fxlink_gen_file_name(char const *path, char const *name,
|
||||||
|
char const *suffix)
|
||||||
|
{
|
||||||
|
char *filename = NULL;
|
||||||
|
int counter = 1;
|
||||||
|
|
||||||
|
time_t time_raw;
|
||||||
|
struct tm time_bd;
|
||||||
|
time(&time_raw);
|
||||||
|
localtime_r(&time_raw, &time_bd);
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
asprintf(&filename, "%s/fxlink-%.16s-%04d.%02d.%02d-%02dh%02d-%d%s",
|
||||||
|
path, name, time_bd.tm_year + 1900, time_bd.tm_mon + 1,
|
||||||
|
time_bd.tm_mday, time_bd.tm_hour, time_bd.tm_min, counter, suffix);
|
||||||
|
if(!filename)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Try to find a name for a file that doesn't exist */
|
||||||
|
if(access(filename, F_OK) == -1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
free(filename);
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fxlink_multipoll(int timeout, struct pollfd *fds1, int count1, ...)
|
||||||
|
{
|
||||||
|
/* Convenience macro to iterate on file descriptor arrays */
|
||||||
|
#define FOREACH_FD_ARRAY(FDS, COUNT, COUNT_ACC, BODY) do { \
|
||||||
|
struct pollfd *FDS; int COUNT, COUNT_ACC; va_list args; \
|
||||||
|
va_start(args, count1); \
|
||||||
|
for(FDS = fds1, COUNT = count1, COUNT_ACC = 0; FDS; \
|
||||||
|
COUNT_ACC += COUNT, \
|
||||||
|
FDS = va_arg(args, struct pollfd *), \
|
||||||
|
COUNT = va_arg(args, int)) { BODY } \
|
||||||
|
va_end(args); } while(0)
|
||||||
|
|
||||||
|
/* Determine total number of file descriptors to watch */
|
||||||
|
int total = 0;
|
||||||
|
FOREACH_FD_ARRAY(fds, count, count_acc, {
|
||||||
|
total += count;
|
||||||
|
});
|
||||||
|
|
||||||
|
struct pollfd *concat = malloc(total * sizeof *concat);
|
||||||
|
if(!concat)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/* Copy from individual arrays to full array */
|
||||||
|
FOREACH_FD_ARRAY(fds, count, count_acc, {
|
||||||
|
memcpy(concat + count_acc, fds, count * sizeof *fds);
|
||||||
|
});
|
||||||
|
|
||||||
|
int rc = poll(concat, total, timeout);
|
||||||
|
|
||||||
|
/* Copy back from full array to individual arrays */
|
||||||
|
FOREACH_FD_ARRAY(fds, count, count_acc, {
|
||||||
|
memcpy(fds, concat + count_acc, count * sizeof *fds);
|
||||||
|
});
|
||||||
|
|
||||||
|
free(concat);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *fxlink_size_string(int bytes)
|
||||||
|
{
|
||||||
|
static char str[32];
|
||||||
|
|
||||||
|
if(bytes > 1000000)
|
||||||
|
sprintf(str, "%d.%d MB", bytes / 1000000, (bytes % 1000000) / 100000);
|
||||||
|
else if(bytes > 1000)
|
||||||
|
sprintf(str, "%d.%d kB", bytes / 1000, (bytes % 1000) / 100);
|
||||||
|
else
|
||||||
|
sprintf(str, "%d B", bytes);
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
812
fxlink/devices.c
Normal file
812
fxlink/devices.c
Normal file
|
@ -0,0 +1,812 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
|
||||||
|
#include <fxlink/devices.h>
|
||||||
|
#include <fxlink/logging.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
char const *fxlink_device_id(struct fxlink_device const *fdev)
|
||||||
|
{
|
||||||
|
static char str[32];
|
||||||
|
sprintf(str, "%03d:%03d", fdev->busNumber, fdev->deviceAddress);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *fxlink_device_status_string(struct fxlink_device const *fdev)
|
||||||
|
{
|
||||||
|
switch(fdev->status) {
|
||||||
|
case FXLINK_FDEV_STATUS_PENDING:
|
||||||
|
return "PENDING";
|
||||||
|
case FXLINK_FDEV_STATUS_IGNORED:
|
||||||
|
return "IGNORED";
|
||||||
|
case FXLINK_FDEV_STATUS_ERROR:
|
||||||
|
return "ERROR";
|
||||||
|
case FXLINK_FDEV_STATUS_IDLE:
|
||||||
|
return "IDLE";
|
||||||
|
case FXLINK_FDEV_STATUS_CONNECTED:
|
||||||
|
return "CONNECTED";
|
||||||
|
default:
|
||||||
|
return "<INVALID>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *fxlink_device_system_string(struct fxlink_device const *fdev)
|
||||||
|
{
|
||||||
|
if(!fdev->calc)
|
||||||
|
return "<!CALC>";
|
||||||
|
|
||||||
|
switch(fdev->calc->system) {
|
||||||
|
case FXLINK_CALC_SYSTEM_UNKNOWN:
|
||||||
|
return "UNKNOWN";
|
||||||
|
case FXLINK_CALC_SYSTEM_LINKSCSI:
|
||||||
|
return "LINKSCSI";
|
||||||
|
case FXLINK_CALC_SYSTEM_CESG502:
|
||||||
|
return "CESG502";
|
||||||
|
case FXLINK_CALC_SYSTEM_GINT:
|
||||||
|
return "GINT";
|
||||||
|
default:
|
||||||
|
return "<INVALID>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_casio_calculator(int idVendor, int idProduct)
|
||||||
|
{
|
||||||
|
return idVendor == 0x07cf && (idProduct == 0x6101 || idProduct == 0x6102);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct libusb_config_descriptor *active_config_descriptor(
|
||||||
|
struct fxlink_device const *fdev)
|
||||||
|
{
|
||||||
|
struct libusb_config_descriptor *cd = NULL;
|
||||||
|
int rc = libusb_get_active_config_descriptor(fdev->dp, &cd);
|
||||||
|
if(rc != 0) {
|
||||||
|
if(rc != LIBUSB_ERROR_NOT_FOUND)
|
||||||
|
elog_libusb(rc, "cannot request config descriptor");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return cd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gather a list of a classes; either one is specified for the entire device in
|
||||||
|
the device descriptor, either one is specified for each interface in
|
||||||
|
interface descriptors. */
|
||||||
|
static bool find_interface_classes(struct fxlink_device *fdev)
|
||||||
|
{
|
||||||
|
struct fxlink_calc *calc = fdev->calc;
|
||||||
|
struct libusb_device_descriptor dc;
|
||||||
|
libusb_get_device_descriptor(fdev->dp, &dc);
|
||||||
|
|
||||||
|
/* Fixed class specified in the device descriptor */
|
||||||
|
if(dc.bDeviceClass != LIBUSB_CLASS_PER_INTERFACE) {
|
||||||
|
calc->interface_count = 1;
|
||||||
|
calc->classes = malloc(1 * sizeof *calc->classes);
|
||||||
|
calc->classes[0] = (dc.bDeviceClass << 8) | dc.bDeviceSubClass;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Class specified by the interface descriptors */
|
||||||
|
struct libusb_config_descriptor *cd = active_config_descriptor(fdev);
|
||||||
|
if(!cd)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
calc->interface_count = cd->bNumInterfaces;
|
||||||
|
calc->classes = malloc(cd->bNumInterfaces * sizeof *calc->classes);
|
||||||
|
|
||||||
|
for(int i = 0; i < cd->bNumInterfaces; i++) {
|
||||||
|
struct libusb_interface const *intf = &cd->interface[i];
|
||||||
|
struct libusb_interface_descriptor const *id = &intf->altsetting[0];
|
||||||
|
|
||||||
|
calc->classes[i] = (id->bInterfaceClass << 8) | id->bInterfaceSubClass;
|
||||||
|
if(calc->classes[i] == 0xff77 && calc->fxlink_inum < 0)
|
||||||
|
calc->fxlink_inum = i;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find the endpoints for the fxlink interface. */
|
||||||
|
static bool find_fxlink_endpoints(struct fxlink_device *fdev, bool quiet)
|
||||||
|
{
|
||||||
|
struct fxlink_calc *calc = fdev->calc;
|
||||||
|
struct fxlink_comm *comm = calloc(1, sizeof *comm);
|
||||||
|
if(!comm) {
|
||||||
|
elog("find_fxlink_endpoints(): %m\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fdev->comm = comm;
|
||||||
|
comm->ep_bulk_IN = 0xff;
|
||||||
|
comm->ep_bulk_OUT = 0xff;
|
||||||
|
|
||||||
|
struct libusb_config_descriptor *cd = active_config_descriptor(fdev);
|
||||||
|
if(!cd) {
|
||||||
|
free(comm);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct libusb_interface const *intf = &cd->interface[calc->fxlink_inum];
|
||||||
|
struct libusb_interface_descriptor const *id = &intf->altsetting[0];
|
||||||
|
|
||||||
|
if(!quiet) {
|
||||||
|
hlog("calculators %s", fxlink_device_id(fdev));
|
||||||
|
log_("fxlink interface has %d endpoints\n", id->bNumEndpoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < id->bNumEndpoints; i++) {
|
||||||
|
struct libusb_endpoint_descriptor const *ed = &id->endpoint[i];
|
||||||
|
|
||||||
|
int dir = ed->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK;
|
||||||
|
int type = ed->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK;
|
||||||
|
|
||||||
|
if(dir == LIBUSB_ENDPOINT_OUT
|
||||||
|
&& type == LIBUSB_ENDPOINT_TRANSFER_TYPE_BULK)
|
||||||
|
comm->ep_bulk_OUT = ed->bEndpointAddress;
|
||||||
|
if(dir == LIBUSB_ENDPOINT_IN
|
||||||
|
&& type == LIBUSB_ENDPOINT_TRANSFER_TYPE_BULK)
|
||||||
|
comm->ep_bulk_IN = ed->bEndpointAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Determine system based on interface heuristics */
|
||||||
|
static void determine_system_type(struct fxlink_device *fdev)
|
||||||
|
{
|
||||||
|
struct fxlink_calc *calc = fdev->calc;
|
||||||
|
if(!calc)
|
||||||
|
return;
|
||||||
|
|
||||||
|
calc->system = FXLINK_CALC_SYSTEM_UNKNOWN;
|
||||||
|
|
||||||
|
/* Single class of type SCSI -> LINKSCSI */
|
||||||
|
if(calc->interface_count == 1 && calc->classes[0] == 0x0806) {
|
||||||
|
calc->system = FXLINK_CALC_SYSTEM_LINKSCSI;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* Single class of type vendor-specific 00 -> CESG502 */
|
||||||
|
if(calc->interface_count == 1 && calc->classes[0] == 0xff00) {
|
||||||
|
calc->system = FXLINK_CALC_SYSTEM_CESG502;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* Has an fxlink interface -> GINT */
|
||||||
|
for(int i = 0; i < calc->interface_count; i++) {
|
||||||
|
if(calc->classes[i] == 0xff77) {
|
||||||
|
calc->system = FXLINK_CALC_SYSTEM_GINT;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Retrieve the serial. Returns a duplicated copy, NULL on error. */
|
||||||
|
static char *retrieve_serial_number(struct fxlink_device *fdev)
|
||||||
|
{
|
||||||
|
if(!fdev->dh)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
struct libusb_device_descriptor dc;
|
||||||
|
libusb_get_device_descriptor(fdev->dp, &dc);
|
||||||
|
|
||||||
|
if(!dc.iSerialNumber)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
char str[256];
|
||||||
|
int rc = libusb_get_string_descriptor_ascii(fdev->dh, dc.iSerialNumber,
|
||||||
|
(void *)str, 256);
|
||||||
|
if(rc < 0) {
|
||||||
|
wlog_libusb(rc, "could not retrieve serial number");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LINK sends a 12-byte serial number with four leading 0. Remove them */
|
||||||
|
char *serial = str;
|
||||||
|
if(rc == 12 && !strncmp(serial, "0000", 4))
|
||||||
|
serial += 4;
|
||||||
|
return strdup(serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_device_analysis_1(struct fxlink_device *fdev, bool quiet)
|
||||||
|
{
|
||||||
|
struct fxlink_calc *calc = calloc(1, sizeof *calc);
|
||||||
|
if(!calc) {
|
||||||
|
elog("analyze_calculator(): %m\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fdev->calc = calc;
|
||||||
|
calc->system = FXLINK_CALC_SYSTEM_UNKNOWN;
|
||||||
|
calc->fxlink_inum = -1;
|
||||||
|
if(!find_interface_classes(fdev)) {
|
||||||
|
fdev->status = FXLINK_FDEV_STATUS_ERROR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!quiet) {
|
||||||
|
hlog("calculators %s", fxlink_device_id(fdev));
|
||||||
|
log_("%1$d interface%2$s, class code%2$s", calc->interface_count,
|
||||||
|
calc->interface_count != 1 ? "s" : "");
|
||||||
|
|
||||||
|
for(int i = 0; i < calc->interface_count; i++) {
|
||||||
|
log_(" %02x.%02x", calc->classes[i] >> 8, calc->classes[i] & 0xff);
|
||||||
|
if(i == calc->fxlink_inum)
|
||||||
|
log_("(*)");
|
||||||
|
}
|
||||||
|
log_("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
determine_system_type(fdev);
|
||||||
|
|
||||||
|
/* Don't open SCSI devices because we can't interact with them (the kernel
|
||||||
|
already claims the interface) and doing so will prevent them from
|
||||||
|
subspending and disconnecting */
|
||||||
|
if(calc->system == FXLINK_CALC_SYSTEM_LINKSCSI) {
|
||||||
|
fdev->status = FXLINK_FDEV_STATUS_IGNORED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(calc->fxlink_inum >= 0 && !find_fxlink_endpoints(fdev, quiet)) {
|
||||||
|
hlog("calculators %s", fxlink_device_id(fdev));
|
||||||
|
elog("non-conforming fxlink interface!\n");
|
||||||
|
fdev->status = FXLINK_FDEV_STATUS_ERROR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = libusb_open(fdev->dp, &fdev->dh);
|
||||||
|
|
||||||
|
if(!quiet)
|
||||||
|
hlog("calculators %s", fxlink_device_id(fdev));
|
||||||
|
if(rc != 0) {
|
||||||
|
elog("opening device failed: %s\n", libusb_strerror(rc));
|
||||||
|
fdev->status = FXLINK_FDEV_STATUS_ERROR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!quiet)
|
||||||
|
log_("successfully opened device!\n");
|
||||||
|
|
||||||
|
/* Don't detach kernel drivers to avoid breaking the Mass Storage
|
||||||
|
communications if fxlink is ever started while the native LINK
|
||||||
|
application is running! */
|
||||||
|
libusb_set_auto_detach_kernel_driver(fdev->dh, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_device_analysis_2(struct fxlink_device *fdev)
|
||||||
|
{
|
||||||
|
if(fdev->status != FXLINK_FDEV_STATUS_PENDING)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fdev->calc->serial = retrieve_serial_number(fdev);
|
||||||
|
fdev->status = FXLINK_FDEV_STATUS_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fxlink_device_claim_fxlink(struct fxlink_device *fdev)
|
||||||
|
{
|
||||||
|
/* Only connect to calculators with an fxlink interface */
|
||||||
|
if(!fdev->comm || fdev->status != FXLINK_FDEV_STATUS_IDLE)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Allocate transfer data */
|
||||||
|
fdev->comm->buffer_IN_size = 2048;
|
||||||
|
fdev->comm->buffer_IN = malloc(fdev->comm->buffer_IN_size);
|
||||||
|
if(!fdev->comm->buffer_IN) {
|
||||||
|
elog("could not allocate buffer for bulk IN transfer\n");
|
||||||
|
fdev->status = FXLINK_FDEV_STATUS_ERROR;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hlog("calculators %s", fxlink_device_id(fdev));
|
||||||
|
int rc = libusb_claim_interface(fdev->dh, fdev->calc->fxlink_inum);
|
||||||
|
if(rc != 0) {
|
||||||
|
elog_libusb(rc, "claiming fxlink interface #%d failed",
|
||||||
|
fdev->calc->fxlink_inum);
|
||||||
|
fdev->status = FXLINK_FDEV_STATUS_ERROR;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fdev->comm->claimed = true;
|
||||||
|
fdev->status = FXLINK_FDEV_STATUS_CONNECTED;
|
||||||
|
log_("successfully claimed interface!\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_device_get_properties(struct fxlink_device const *fdev,
|
||||||
|
struct fxlink_filter *p)
|
||||||
|
{
|
||||||
|
memset(p, 0, sizeof *p);
|
||||||
|
|
||||||
|
if(fdev->idProduct == 0x6101)
|
||||||
|
p->p7 = true;
|
||||||
|
if(fdev->idProduct == 0x6102)
|
||||||
|
p->mass_storage = true;
|
||||||
|
if(fdev->calc) {
|
||||||
|
p->serial = fdev->calc->serial;
|
||||||
|
p->intf_cesg502 = (fdev->calc->system == FXLINK_CALC_SYSTEM_CESG502);
|
||||||
|
p->intf_fxlink = (fdev->calc->fxlink_inum >= 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_device_interrupt_transfers(struct fxlink_device *fdev)
|
||||||
|
{
|
||||||
|
struct fxlink_comm *comm = fdev->comm;
|
||||||
|
if(!comm)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Interrupt transfers if any are still running */
|
||||||
|
if(comm->tr_bulk_IN && !comm->cancelled_IN) {
|
||||||
|
libusb_cancel_transfer(comm->tr_bulk_IN);
|
||||||
|
comm->cancelled_IN = true;
|
||||||
|
}
|
||||||
|
if(comm->tr_bulk_OUT && !comm->cancelled_OUT) {
|
||||||
|
libusb_cancel_transfer(comm->tr_bulk_OUT);
|
||||||
|
comm->cancelled_OUT = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_device_cleanup(struct fxlink_device *fdev)
|
||||||
|
{
|
||||||
|
/* Close the device if it's open */
|
||||||
|
if(fdev->dh) {
|
||||||
|
/* Release the fxlink interface if it's claimed */
|
||||||
|
if(fdev->comm && fdev->comm->claimed)
|
||||||
|
libusb_release_interface(fdev->dh, fdev->calc->fxlink_inum);
|
||||||
|
|
||||||
|
libusb_close(fdev->dh);
|
||||||
|
fdev->dh = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free associated memory */
|
||||||
|
if(fdev->calc) {
|
||||||
|
free(fdev->calc->classes);
|
||||||
|
free(fdev->calc->serial);
|
||||||
|
free(fdev->calc);
|
||||||
|
}
|
||||||
|
free(fdev->comm);
|
||||||
|
|
||||||
|
/* Unreference libusb devices so it can also be freed */
|
||||||
|
libusb_unref_device(fdev->dp);
|
||||||
|
fdev->dp = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Bulk transfers
|
||||||
|
//---
|
||||||
|
|
||||||
|
/* Note: this function is run by the even handler and can't do any crazy libusb
|
||||||
|
stuff like sync I/O or getting descriptors. */
|
||||||
|
static void bulk_IN_callback(struct libusb_transfer *transfer)
|
||||||
|
{
|
||||||
|
struct fxlink_device *fdev = transfer->user_data;
|
||||||
|
struct fxlink_comm *comm = fdev->comm;
|
||||||
|
|
||||||
|
bool resubmit = false;
|
||||||
|
void *data = transfer->buffer;
|
||||||
|
int data_size = transfer->actual_length;
|
||||||
|
|
||||||
|
if(transfer->status != LIBUSB_TRANSFER_COMPLETED)
|
||||||
|
hlog("calculators %s", fxlink_device_id(fdev));
|
||||||
|
|
||||||
|
switch(transfer->status) {
|
||||||
|
case LIBUSB_TRANSFER_COMPLETED:
|
||||||
|
/* Start or continue an fxlink transfer. When finished, don't resubmit,
|
||||||
|
instead have the user pick up the message before continuing. */
|
||||||
|
resubmit = true;
|
||||||
|
if(comm->ftransfer_IN) {
|
||||||
|
fxlink_transfer_receive(comm->ftransfer_IN, data, data_size);
|
||||||
|
if(fxlink_transfer_complete(comm->ftransfer_IN))
|
||||||
|
resubmit = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
comm->ftransfer_IN = fxlink_transfer_make_IN(data, data_size);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Error: drop data and don't resubmit */
|
||||||
|
case LIBUSB_TRANSFER_ERROR:
|
||||||
|
log_("transfer error (%d bytes dropped)\n", data_size);
|
||||||
|
break;
|
||||||
|
/* Timeout: drop data, but resubmit */
|
||||||
|
case LIBUSB_TRANSFER_TIMED_OUT:
|
||||||
|
log_("transfer timed out (%d bytes dropped)\n", data_size);
|
||||||
|
break;
|
||||||
|
/* Cancelled transfer: drop data and don't try to resubmit */
|
||||||
|
case LIBUSB_TRANSFER_CANCELLED:
|
||||||
|
log_("transfer cancelled (%d bytes dropped)\n", data_size);
|
||||||
|
break;
|
||||||
|
/* Stall: treat as an error */
|
||||||
|
case LIBUSB_TRANSFER_STALL:
|
||||||
|
log_("transfer stalled (%d bytes dropped)\n", data_size);
|
||||||
|
break;
|
||||||
|
/* Overflow: treat as an error (should not happen because we set our buffer
|
||||||
|
size to a multiple of the maximum packet size) */
|
||||||
|
case LIBUSB_TRANSFER_OVERFLOW:
|
||||||
|
log_("transfer overflowed (%d bytes dropped)\n", data_size);
|
||||||
|
break;
|
||||||
|
/* No device: this is normal */
|
||||||
|
case LIBUSB_TRANSFER_NO_DEVICE:
|
||||||
|
log_("stop listening (calculator disconnected)\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resubmit transfer so we can get new data as soon as possible */
|
||||||
|
if(resubmit) {
|
||||||
|
libusb_submit_transfer(comm->tr_bulk_IN);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
libusb_free_transfer(comm->tr_bulk_IN);
|
||||||
|
comm->tr_bulk_IN = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_device_start_bulk_IN(struct fxlink_device *fdev)
|
||||||
|
{
|
||||||
|
if(!fdev->comm || !fdev->comm->claimed || fdev->comm->tr_bulk_IN)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fdev->comm->tr_bulk_IN = libusb_alloc_transfer(0);
|
||||||
|
if(!fdev->comm->tr_bulk_IN) {
|
||||||
|
elog("allocation of bulk IN transfer failed\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
libusb_fill_bulk_transfer(fdev->comm->tr_bulk_IN,
|
||||||
|
fdev->dh, /* Device handle */
|
||||||
|
fdev->comm->ep_bulk_IN, /* Endpoint */
|
||||||
|
fdev->comm->buffer_IN, /* Buffer */
|
||||||
|
fdev->comm->buffer_IN_size, /* Buffer size */
|
||||||
|
bulk_IN_callback, fdev, /* Callback function and argument */
|
||||||
|
-1 /* Timeout */
|
||||||
|
);
|
||||||
|
|
||||||
|
int rc = libusb_submit_transfer(fdev->comm->tr_bulk_IN);
|
||||||
|
if(rc < 0) {
|
||||||
|
elog("bulk IN transfer failed to submit: %s\n", libusb_strerror(rc));
|
||||||
|
fdev->status = FXLINK_FDEV_STATUS_ERROR;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hlog("calculators %s", fxlink_device_id(fdev));
|
||||||
|
// log_("submitted new IN transfer (no timeout)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fxlink_message *fxlink_device_finish_bulk_IN(struct fxlink_device *fdev)
|
||||||
|
{
|
||||||
|
struct fxlink_comm *comm = fdev->comm;
|
||||||
|
|
||||||
|
if(!comm || !comm->ftransfer_IN)
|
||||||
|
return NULL;
|
||||||
|
if(!fxlink_transfer_complete(comm->ftransfer_IN))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
struct fxlink_message *msg = fxlink_transfer_finish_IN(comm->ftransfer_IN);
|
||||||
|
if(!msg)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
int version_major = (msg->version >> 8) & 0xff;
|
||||||
|
int version_minor = msg->version & 0xff;
|
||||||
|
|
||||||
|
hlog("calculators %s", fxlink_device_id(fdev));
|
||||||
|
log_("new message (v%d.%d): %.16s:%.16s, %s\n",
|
||||||
|
version_major, version_minor,
|
||||||
|
msg->application, msg->type, fxlink_size_string(msg->size));
|
||||||
|
comm->ftransfer_IN = NULL;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Polled file descriptor tracking
|
||||||
|
//---
|
||||||
|
|
||||||
|
static void generate_poll_fds(struct fxlink_pollfds *tracker)
|
||||||
|
{
|
||||||
|
/* Get the set of libusb file descriptors to poll for news */
|
||||||
|
struct libusb_pollfd const **usb_fds = libusb_get_pollfds(tracker->ctx);
|
||||||
|
int usb_n = 0;
|
||||||
|
|
||||||
|
if(!usb_fds) {
|
||||||
|
elog("libusb_get_pollfds() returned NULL, devices will probably not "
|
||||||
|
"be detected!\n");
|
||||||
|
free(tracker->fds);
|
||||||
|
tracker->fds = NULL;
|
||||||
|
tracker->count = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hlog("libusb");
|
||||||
|
log_("fds to poll:");
|
||||||
|
for(int i = 0; usb_fds[i] != NULL; i++) {
|
||||||
|
log_(" %d(%s%s)",
|
||||||
|
usb_fds[i]->fd,
|
||||||
|
(usb_fds[i]->events & POLLIN) ? "i" : "",
|
||||||
|
(usb_fds[i]->events & POLLOUT) ? "o" : "");
|
||||||
|
}
|
||||||
|
if(!usb_fds[0])
|
||||||
|
log_(" (none)");
|
||||||
|
log_("\n");
|
||||||
|
|
||||||
|
while(usb_fds[usb_n])
|
||||||
|
usb_n++;
|
||||||
|
|
||||||
|
/* Allocate a bunch of `struct pollfd` and also monitor STDIN_FILENO */
|
||||||
|
tracker->count = usb_n;
|
||||||
|
tracker->fds = realloc(tracker->fds, usb_n * sizeof *tracker->fds);
|
||||||
|
|
||||||
|
for(int i = 0; i < usb_n; i++) {
|
||||||
|
tracker->fds[i].fd = usb_fds[i]->fd;
|
||||||
|
tracker->fds[i].events = usb_fds[i]->events;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_add_poll_fd(int fd, short events, void *data)
|
||||||
|
{
|
||||||
|
(void)fd;
|
||||||
|
(void)events;
|
||||||
|
generate_poll_fds(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_remove_poll_fd(int fd, void *data)
|
||||||
|
{
|
||||||
|
(void)fd;
|
||||||
|
generate_poll_fds(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_pollfds_track(struct fxlink_pollfds *tracker, libusb_context *ctx)
|
||||||
|
{
|
||||||
|
memset(tracker, 0, sizeof *tracker);
|
||||||
|
tracker->ctx = ctx;
|
||||||
|
|
||||||
|
libusb_set_pollfd_notifiers(ctx, handle_add_poll_fd, handle_remove_poll_fd,
|
||||||
|
tracker);
|
||||||
|
generate_poll_fds(tracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_pollfds_stop(struct fxlink_pollfds *tracker)
|
||||||
|
{
|
||||||
|
libusb_set_pollfd_notifiers(tracker->ctx, NULL, NULL, NULL);
|
||||||
|
free(tracker->fds);
|
||||||
|
memset(tracker, 0, sizeof *tracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Device tracking
|
||||||
|
//---
|
||||||
|
|
||||||
|
static void enumerate_devices(libusb_context *ctx,
|
||||||
|
struct fxlink_device_list *list)
|
||||||
|
{
|
||||||
|
libusb_device **libusb_list = NULL;
|
||||||
|
int new_count = libusb_get_device_list(ctx, &libusb_list);
|
||||||
|
|
||||||
|
if(new_count < 0) {
|
||||||
|
elog("libusb_get_device_list() failed with error %d\n", new_count);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We now diff the previous array with the current one */
|
||||||
|
struct fxlink_device *new_fdevs = calloc(new_count, sizeof *new_fdevs);
|
||||||
|
if(!new_fdevs) {
|
||||||
|
elog("enumerate_devices(): %m\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int k = 0;
|
||||||
|
|
||||||
|
/* First copy over any device that is still connected */
|
||||||
|
for(int i = 0; i < list->count; i++) {
|
||||||
|
struct fxlink_device *fdev = &list->devices[i];
|
||||||
|
assert(fdev->dp != NULL);
|
||||||
|
|
||||||
|
bool still_connected = false;
|
||||||
|
for(int j = 0; j < new_count; j++)
|
||||||
|
still_connected = still_connected || (libusb_list[j] == fdev->dp);
|
||||||
|
|
||||||
|
if(still_connected)
|
||||||
|
new_fdevs[k++] = list->devices[i];
|
||||||
|
else {
|
||||||
|
fxlink_device_cleanup(fdev);
|
||||||
|
hlog("devices %s", fxlink_device_id(fdev));
|
||||||
|
log_("disconnected\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Then add all the new ones */
|
||||||
|
for(int j = 0; j < new_count; j++) {
|
||||||
|
libusb_device *dp = libusb_list[j];
|
||||||
|
struct libusb_device_descriptor dc;
|
||||||
|
|
||||||
|
bool already_known = false;
|
||||||
|
for(int i = 0; i < list->count; i++)
|
||||||
|
already_known = already_known || (list->devices[i].dp == dp);
|
||||||
|
if(already_known)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
libusb_ref_device(dp);
|
||||||
|
libusb_get_device_descriptor(dp, &dc);
|
||||||
|
|
||||||
|
new_fdevs[k].dp = dp;
|
||||||
|
new_fdevs[k].status = FXLINK_FDEV_STATUS_PENDING;
|
||||||
|
new_fdevs[k].busNumber = libusb_get_bus_number(dp);
|
||||||
|
new_fdevs[k].deviceAddress = libusb_get_device_address(dp);
|
||||||
|
new_fdevs[k].idVendor = dc.idVendor;
|
||||||
|
new_fdevs[k].idProduct = dc.idProduct;
|
||||||
|
new_fdevs[k].calc = NULL;
|
||||||
|
new_fdevs[k].comm = NULL;
|
||||||
|
|
||||||
|
hlog("devices %s", fxlink_device_id(&new_fdevs[k]));
|
||||||
|
|
||||||
|
if(is_casio_calculator(dc.idVendor, dc.idProduct)) {
|
||||||
|
log_("new CASIO calculator (%04x:%04x)\n",
|
||||||
|
dc.idVendor, dc.idProduct);
|
||||||
|
fxlink_device_analysis_1(&new_fdevs[k], false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log_("new non-CASIO-calculator device (%04x:%04x)\n",
|
||||||
|
dc.idVendor, dc.idProduct);
|
||||||
|
new_fdevs[k].status = FXLINK_FDEV_STATUS_IGNORED;
|
||||||
|
}
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(k == new_count);
|
||||||
|
free(list->devices);
|
||||||
|
list->devices = new_fdevs;
|
||||||
|
list->count = new_count;
|
||||||
|
libusb_free_device_list(libusb_list, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_hotplug(libusb_context *ctx, libusb_device *device,
|
||||||
|
libusb_hotplug_event event, void *user_data)
|
||||||
|
{
|
||||||
|
/* Note that due to threading considerations in libusb, a device may be
|
||||||
|
notified for hotplugging twice, or may depart without ever having been
|
||||||
|
notified for arrival. */
|
||||||
|
(void)device;
|
||||||
|
|
||||||
|
if(event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED)
|
||||||
|
enumerate_devices(ctx, user_data);
|
||||||
|
else if(event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
|
||||||
|
enumerate_devices(ctx, user_data);
|
||||||
|
else
|
||||||
|
wlog("unhandled libusb hotplug event of type %d\n", event);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fxlink_device_list_track(struct fxlink_device_list *list,
|
||||||
|
libusb_context *ctx)
|
||||||
|
{
|
||||||
|
memset(list, 0, sizeof *list);
|
||||||
|
|
||||||
|
if(!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) {
|
||||||
|
elog("libusb doesn't handle hotplug; devices may not be detected\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
list->ctx = ctx;
|
||||||
|
libusb_hotplug_register_callback(ctx,
|
||||||
|
/* Both arriving and departing devices */
|
||||||
|
LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
|
||||||
|
/* Perform an initial enumeration right now */
|
||||||
|
LIBUSB_HOTPLUG_ENUMERATE,
|
||||||
|
LIBUSB_HOTPLUG_MATCH_ANY, /* vendorId */
|
||||||
|
LIBUSB_HOTPLUG_MATCH_ANY, /* productId */
|
||||||
|
LIBUSB_HOTPLUG_MATCH_ANY, /* deviceClass */
|
||||||
|
handle_hotplug, list, &list->hotplug_handle);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_device_list_refresh(struct fxlink_device_list *list)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < list->count; i++) {
|
||||||
|
struct fxlink_device *fdev = &list->devices[i];
|
||||||
|
/* Finish analysis */
|
||||||
|
if(fdev->calc && fdev->status == FXLINK_FDEV_STATUS_PENDING)
|
||||||
|
fxlink_device_analysis_2(fdev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fxlink_device_list_interrupt(struct fxlink_device_list *list)
|
||||||
|
{
|
||||||
|
bool still_running = false;
|
||||||
|
|
||||||
|
for(int i = 0; i < list->count; i++) {
|
||||||
|
struct fxlink_device *fdev = &list->devices[i];
|
||||||
|
fxlink_device_interrupt_transfers(fdev);
|
||||||
|
|
||||||
|
still_running |= fdev->comm && fdev->comm->tr_bulk_IN;
|
||||||
|
still_running |= fdev->comm && fdev->comm->tr_bulk_OUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return still_running;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_device_list_stop(struct fxlink_device_list *list)
|
||||||
|
{
|
||||||
|
if(!list->ctx)
|
||||||
|
return;
|
||||||
|
|
||||||
|
libusb_hotplug_deregister_callback(list->ctx, list->hotplug_handle);
|
||||||
|
|
||||||
|
/* Now free the device list proper */
|
||||||
|
for(int i = 0; i < list->count; i++)
|
||||||
|
fxlink_device_cleanup(&list->devices[i]);
|
||||||
|
|
||||||
|
free(list->devices);
|
||||||
|
memset(list, 0, sizeof *list);
|
||||||
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Simplified device enumeration
|
||||||
|
//---
|
||||||
|
|
||||||
|
struct fxlink_device *fxlink_device_find(libusb_context *ctx,
|
||||||
|
struct fxlink_filter const *filter)
|
||||||
|
{
|
||||||
|
libusb_device **list = NULL;
|
||||||
|
int count = libusb_get_device_list(ctx, &list);
|
||||||
|
struct fxlink_device *fdev = NULL;
|
||||||
|
|
||||||
|
if(count < 0) {
|
||||||
|
elog("libusb_get_device_list() failed with error %d\n", count);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Look for any suitable calculator */
|
||||||
|
for(int i = 0; i < count; i++) {
|
||||||
|
libusb_device *dp = list[i];
|
||||||
|
struct libusb_device_descriptor dc;
|
||||||
|
|
||||||
|
libusb_get_device_descriptor(dp, &dc);
|
||||||
|
if(!is_casio_calculator(dc.idVendor, dc.idProduct))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Since we found a calculator, make the fdev and check further */
|
||||||
|
fdev = calloc(1, sizeof *fdev);
|
||||||
|
if(!fdev) {
|
||||||
|
elog("failed to allocate device structure: %m\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
libusb_ref_device(dp);
|
||||||
|
fdev->dp = dp;
|
||||||
|
fdev->status = FXLINK_FDEV_STATUS_PENDING;
|
||||||
|
fdev->busNumber = libusb_get_bus_number(dp);
|
||||||
|
fdev->deviceAddress = libusb_get_device_address(dp);
|
||||||
|
fdev->idVendor = dc.idVendor;
|
||||||
|
fdev->idProduct = dc.idProduct;
|
||||||
|
fdev->calc = NULL;
|
||||||
|
fdev->comm = NULL;
|
||||||
|
fxlink_device_analysis_1(fdev, true);
|
||||||
|
fxlink_device_analysis_2(fdev);
|
||||||
|
|
||||||
|
if(fdev->status == FXLINK_FDEV_STATUS_IDLE) {
|
||||||
|
/* Check the filter */
|
||||||
|
struct fxlink_filter properties;
|
||||||
|
fxlink_device_get_properties(fdev, &properties);
|
||||||
|
|
||||||
|
/* Success: return that device (no interfaces claimed) */
|
||||||
|
if(fxlink_filter_match(&properties, filter))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Failure: free it and try the next one */
|
||||||
|
fxlink_device_cleanup(fdev);
|
||||||
|
free(fdev);
|
||||||
|
fdev = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
libusb_free_device_list(list, true);
|
||||||
|
return fdev;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fxlink_device *fxlink_device_find_wait(libusb_context *ctx,
|
||||||
|
struct fxlink_filter const *filter, delay_t *delay)
|
||||||
|
{
|
||||||
|
while(true) {
|
||||||
|
struct fxlink_device *fdev = fxlink_device_find(ctx, filter);
|
||||||
|
if(fdev)
|
||||||
|
return fdev;
|
||||||
|
if(delay_cycle(delay))
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
230
fxlink/filter.c
230
fxlink/filter.c
|
@ -1,184 +1,153 @@
|
||||||
#include "filter.h"
|
//---------------------------------------------------------------------------//
|
||||||
#include "util.h"
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
|
||||||
|
#include <fxlink/filter.h>
|
||||||
|
#include <fxlink/logging.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// Property parser
|
// Filter parser
|
||||||
//---
|
//---
|
||||||
|
|
||||||
/* skip_spaces(): Skip spaces, returns true if end of string is reached */
|
/* Identify property separating characters to be skipped */
|
||||||
bool skip_spaces(char const **input)
|
static bool issep(int c)
|
||||||
{
|
{
|
||||||
while(isspace(**input)) (*input)++;
|
return (c == ' ' || c == '\t' || c == '\n' || c == ',');
|
||||||
return (**input == 0);
|
|
||||||
}
|
}
|
||||||
/* isword(): Identify valid word characters for the filter */
|
/* Identify valid word characters for the filter */
|
||||||
bool isword(int c)
|
static bool isword(int c)
|
||||||
{
|
{
|
||||||
return c && !strchr(" \t\n,;=", c);
|
return c && !strchr(" \t\n,=", c);
|
||||||
}
|
}
|
||||||
/* read_word(): Copy the next word in the string, assumes word is non-empty */
|
/* Copy the next word in the string, assumes word is non-empty */
|
||||||
char *read_word(char const **input)
|
static char *read_word(char const **input)
|
||||||
{
|
{
|
||||||
char const *str = *input;
|
char const *str = *input;
|
||||||
while(**input && isword(**input)) (*input)++;
|
while(**input && isword(**input)) (*input)++;
|
||||||
return strndup(str, *input - str);
|
return strndup(str, *input - str);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum {
|
/* Reads a property from the input source. Advances *input and sets *name and
|
||||||
T_END, /* End of string */
|
*value to newly-allocated copies of the name and (optional) value of the
|
||||||
T_PROP, /* Property; (*name) and (*value) are set */
|
property (T_PROP). Both should be free()'d after use. At the end of the
|
||||||
T_COMMA = ',', /* Comma character (property separator) */
|
input, returns false and sets *name = *value = NULL. */
|
||||||
T_SEMI = ';', /* Semicolon character (option separator) */
|
static bool read_property(char const **input, char **name, char **value)
|
||||||
};
|
|
||||||
|
|
||||||
/* 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;
|
*name = *value = NULL;
|
||||||
if(skip_spaces(input)) return T_END;
|
|
||||||
|
|
||||||
if(**input == ',' || **input == ';') {
|
while(issep(**input))
|
||||||
(*input)++;
|
(*input)++;
|
||||||
return (*input)[-1];
|
if(!**input)
|
||||||
}
|
return false;
|
||||||
|
|
||||||
if(!isword(**input)) {
|
if(!isword(**input)) {
|
||||||
wrn("expected property name in filter, skipping '%c'", **input);
|
elog("expected property name in filter, found '%c'; stopping\n",
|
||||||
(*input)++;
|
**input);
|
||||||
return lex(input, name, value);
|
return false;
|
||||||
}
|
}
|
||||||
*name = read_word(input);
|
*name = read_word(input);
|
||||||
if(skip_spaces(input) || **input != '=')
|
|
||||||
return T_PROP;
|
|
||||||
|
|
||||||
(*input)++;
|
if(**input == '=') {
|
||||||
if(skip_spaces(input))
|
(*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);
|
*value = read_word(input);
|
||||||
return T_PROP;
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
filter_t *filter_parse(char const *input)
|
struct fxlink_filter *fxlink_filter_parse(char const *input)
|
||||||
{
|
{
|
||||||
char *name=NULL, *value=NULL;
|
char *name=NULL, *value=NULL;
|
||||||
int t;
|
struct fxlink_filter *filter = calloc(1, sizeof *filter);
|
||||||
|
if(!filter)
|
||||||
/* 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;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
while(read_property(&input, &name, &value)) {
|
||||||
/* Add a new property to the current option */
|
/* Add a new property to the current option */
|
||||||
if(!strcmp(name, "p7") && !value)
|
if(!strcmp(name, "p7") && !value)
|
||||||
option->p7 = true;
|
filter->p7 = true;
|
||||||
else if(!strcmp(name, "mass_storage") && !value)
|
else if(!strcmp(name, "mass_storage") && !value)
|
||||||
option->mass_storage = true;
|
filter->mass_storage = true;
|
||||||
else if(!strcmp(name, "series_cg") && !value)
|
else if(!strcmp(name, "series_cg") && !value)
|
||||||
option->series_cg = true;
|
filter->series_cg = true;
|
||||||
else if(!strcmp(name, "series_g3") && !value)
|
else if(!strcmp(name, "series_g3") && !value)
|
||||||
option->series_g3 = true;
|
filter->series_g3 = true;
|
||||||
else if(!strcmp(name, "serial_number") && value) {
|
else if(!strcmp(name, "intf_fxlink") && !value)
|
||||||
option->serial_number = strdup(value);
|
filter->intf_fxlink = true;
|
||||||
}
|
else if(!strcmp(name, "intf_cesg502") && !value)
|
||||||
else wrn("ignoring invalid filter property: '%s' %s value", name,
|
filter->intf_cesg502 = true;
|
||||||
value ? "with" : "without");
|
else if(!strcmp(name, "serial") && value)
|
||||||
|
filter->serial = strdup(value);
|
||||||
|
else if(!strcmp(name, "serial_number") && value) // Old name
|
||||||
|
filter->serial = strdup(value);
|
||||||
|
else wlog("ignoring invalid filter property: '%s' (%s value)\n",
|
||||||
|
name, value ? "with" : "without");
|
||||||
|
|
||||||
free(name);
|
free(name);
|
||||||
free(value);
|
free(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return filter;
|
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
|
// Filtering API
|
||||||
//---
|
//---
|
||||||
|
|
||||||
void filter_clean_libusb(filter_t *filter)
|
void fxlink_filter_clean_libusb(struct fxlink_filter *filter)
|
||||||
{
|
{
|
||||||
if(!filter) return;
|
if(!filter)
|
||||||
|
return;
|
||||||
|
|
||||||
for(size_t i = 0; i < filter->length; i++) {
|
/* Suppress series_cg and series_g3, which are based off the SCSI metadata
|
||||||
properties_t *prop = &filter->options[i];
|
provided only to UDisks2 */
|
||||||
|
if(filter->series_cg) {
|
||||||
/* Suppress series_cg and series_g3, which are based off the USB Mass
|
wlog("ignoring series_cg in libusb filter (cannot be detected)\n");
|
||||||
Storage metadata provided only by UDisks2 */
|
filter->series_cg = false;
|
||||||
if(prop->series_cg) {
|
}
|
||||||
wrn("ignoring series_cg in libusb filter (cannot be detected)");
|
if(filter->series_g3) {
|
||||||
prop->series_cg = false;
|
wlog("ignoring series_g3 in libusb filter (cannot be detected)\n");
|
||||||
}
|
filter->series_g3 = 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)
|
void fxlink_filter_clean_udisks2(struct fxlink_filter *filter)
|
||||||
{
|
{
|
||||||
/* Every property can be used */
|
/* Every property can be used */
|
||||||
(void)filter;
|
(void)filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool filter_match(properties_t const *props, filter_t const *filter)
|
bool fxlink_filter_match(
|
||||||
|
struct fxlink_filter const *props,
|
||||||
|
struct fxlink_filter const *filter)
|
||||||
{
|
{
|
||||||
/* No filter is a pass-through */
|
/* No filter is a pass-through */
|
||||||
if(!filter || !filter->length)
|
if(!filter)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
for(size_t i = 0; i < filter->length; i++) {
|
if(filter->p7 && !props->p7)
|
||||||
if(properties_match(props, &filter->options[i]))
|
return false;
|
||||||
return true;
|
if(filter->mass_storage && !props->mass_storage)
|
||||||
}
|
return false;
|
||||||
return false;
|
if(filter->intf_fxlink && !props->intf_fxlink)
|
||||||
|
return false;
|
||||||
|
if(filter->intf_cesg502 && !props->intf_cesg502)
|
||||||
|
return false;
|
||||||
|
if(filter->series_cg && !props->series_cg)
|
||||||
|
return false;
|
||||||
|
if(filter->series_g3 && !props->series_g3)
|
||||||
|
return false;
|
||||||
|
if(filter->serial &&
|
||||||
|
(!props->serial || strcmp(filter->serial, props->serial)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void filter_print(FILE *fp, filter_t const *filter)
|
void fxlink_filter_print(FILE *fp, struct fxlink_filter const *filter)
|
||||||
{
|
{
|
||||||
#define output(...) { \
|
#define output(...) { \
|
||||||
if(sep) fprintf(fp, ", "); \
|
if(sep) fprintf(fp, ", "); \
|
||||||
|
@ -186,9 +155,26 @@ void filter_print(FILE *fp, filter_t const *filter)
|
||||||
sep = true; \
|
sep = true; \
|
||||||
}
|
}
|
||||||
|
|
||||||
for(size_t i = 0; i < filter->length; i++) {
|
bool sep = false;
|
||||||
if(i > 0) printf("; ");
|
if(filter->p7)
|
||||||
properties_t *prop = &filter->options[i];
|
output("p7");
|
||||||
properties_print(fp, prop);
|
if(filter->mass_storage)
|
||||||
}
|
output("mass_storage");
|
||||||
|
if(filter->intf_fxlink)
|
||||||
|
output("intf_fxlink");
|
||||||
|
if(filter->intf_cesg502)
|
||||||
|
output("intf_cesg502");
|
||||||
|
if(filter->series_cg)
|
||||||
|
output("series_cg");
|
||||||
|
if(filter->series_g3)
|
||||||
|
output("series_g3");
|
||||||
|
if(filter->serial)
|
||||||
|
output("serial=%s", filter->serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_filter_free(struct fxlink_filter *filter)
|
||||||
|
{
|
||||||
|
if(filter)
|
||||||
|
free(filter->serial);
|
||||||
|
free(filter);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
//---
|
|
||||||
// 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 */
|
|
|
@ -1,36 +1,42 @@
|
||||||
//---
|
//---------------------------------------------------------------------------//
|
||||||
// fxlink:fxlink - Application logic
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
//---
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
#ifndef FXLINK_FXLINK_H
|
//---------------------------------------------------------------------------//
|
||||||
#define FXLINK_FXLINK_H
|
// fxlink.fxlink: Options and mode functions
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <fxlink/filter.h>
|
||||||
#include <libusb.h>
|
#include <libusb.h>
|
||||||
#include "filter.h"
|
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
struct fxlink_options
|
/* Global and command-line options. */
|
||||||
{
|
struct fxlink_options {
|
||||||
bool quiet;
|
/* If not NULL, gets a copy of all text messages received in either
|
||||||
bool force_unmount;
|
interactive mode */
|
||||||
FILE *log_file;
|
FILE *log_file;
|
||||||
|
/* Extra details (mainly interactive messages) */
|
||||||
|
bool verbose;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern struct fxlink_options options;
|
extern struct fxlink_options options;
|
||||||
|
|
||||||
/* Main function for -l */
|
/* Main function for -l */
|
||||||
int main_list(filter_t *filter, delay_t *delay, libusb_context *context);
|
int main_list(struct fxlink_filter *filter, delay_t *delay,
|
||||||
|
libusb_context *context);
|
||||||
|
|
||||||
/* Main function for -b */
|
/* Main function for -b */
|
||||||
int main_blocks(filter_t *filter, delay_t *delay);
|
int main_blocks(struct fxlink_filter *filter, delay_t *delay);
|
||||||
|
|
||||||
/* Main function for -s */
|
/* Main function for -s */
|
||||||
int main_send(filter_t *filter, delay_t *delay, char **files);
|
int main_send(struct fxlink_filter *filter, delay_t *delay, char **files);
|
||||||
|
|
||||||
/* Main function for -i */
|
/* Main function for -i */
|
||||||
int main_interactive(filter_t *filter, delay_t *delay, libusb_context *context);
|
int main_interactive(struct fxlink_filter *filter, delay_t *delay,
|
||||||
|
libusb_context *context);
|
||||||
|
|
||||||
|
/* Main function for -t */
|
||||||
|
int main_tui_interactive(libusb_context *context);
|
||||||
|
|
||||||
/* Main function for -p */
|
/* Main function for -p */
|
||||||
int main_push(filter_t *filter, delay_t *delay, libusb_context *context, char **files);
|
int main_push(struct fxlink_filter *filter, delay_t *delay,
|
||||||
|
libusb_context *context, char **files);
|
||||||
#endif /* FXLINK_FXLINK_H */
|
|
||||||
|
|
17
fxlink/include/fxlink/config.h.in
Normal file
17
fxlink/include/fxlink/config.h.in
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// fxlink.config: Compile-time configuration
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/* Disables UDisks2 interfaces for systems that don't use it. */
|
||||||
|
#cmakedefine FXLINK_DISABLE_UDISKS2
|
||||||
|
|
||||||
|
/* Disable SDL2 interfaces. */
|
||||||
|
#cmakedefine FXLINK_DISABLE_SDL2
|
||||||
|
|
||||||
|
/* fxSDK version */
|
||||||
|
#define FXLINK_VERSION "@CMAKE_PROJECT_VERSION@"
|
111
fxlink/include/fxlink/defs.h
Normal file
111
fxlink/include/fxlink/defs.h
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// fxlink.defs: Utility definitions and functions
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <fxlink/config.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <poll.h>
|
||||||
|
|
||||||
|
static inline int min(int x, int y)
|
||||||
|
{
|
||||||
|
return (x < y) ? x : y;
|
||||||
|
}
|
||||||
|
static inline int max(int x, int y)
|
||||||
|
{
|
||||||
|
return (x > y) ? x : y;
|
||||||
|
}
|
||||||
|
static inline int clamp(int x, int min, int max)
|
||||||
|
{
|
||||||
|
return (x < min) ? min : (x > max) ? max : x;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Text formatting
|
||||||
|
//
|
||||||
|
// We stick to ANSI style and provide 8 colors (foreground and background) with
|
||||||
|
// bold/italic/dim attributes. The standard ANSI escape translation is provided
|
||||||
|
// here while the ncurses versions is implemented by the TUI.
|
||||||
|
//---
|
||||||
|
|
||||||
|
enum {
|
||||||
|
/* Main colors */
|
||||||
|
FMT_BLACK = 0x01,
|
||||||
|
FMT_RED = 0x02,
|
||||||
|
FMT_GREEN = 0x03,
|
||||||
|
FMT_YELLOW = 0x04,
|
||||||
|
FMT_BLUE = 0x05,
|
||||||
|
FMT_MAGENTA = 0x06,
|
||||||
|
FMT_CYAN = 0x07,
|
||||||
|
FMT_WHITE = 0x08,
|
||||||
|
/* Background colors */
|
||||||
|
FMT_BGBLACK = 0x10,
|
||||||
|
FMT_BGRED = 0x20,
|
||||||
|
FMT_BGGREEN = 0x30,
|
||||||
|
FMT_BGYELLOW = 0x40,
|
||||||
|
FMT_BGBLUE = 0x50,
|
||||||
|
FMT_BGMAGENTA = 0x60,
|
||||||
|
FMT_BGCYAN = 0x70,
|
||||||
|
FMT_BGWHITE = 0x80,
|
||||||
|
/* Modifiers */
|
||||||
|
FMT_BOLD = 0x100,
|
||||||
|
FMT_DIM = 0x200,
|
||||||
|
FMT_ITALIC = 0x400,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define fmt_FG(fmt) ((fmt) & 0xf)
|
||||||
|
#define fmt_BG(fmt) (((fmt) >> 4) & 0xf)
|
||||||
|
#define fmt_BOLD(fmt) (((fmt) & FMT_BOLD) != 0)
|
||||||
|
#define fmt_DIM(fmt) (((fmt) & FMT_DIM) != 0)
|
||||||
|
#define fmt_ITALIC(fmt) (((fmt) & FMT_ITALIC) != 0)
|
||||||
|
|
||||||
|
/* Returns the escape sequence that switches to the desired format. The
|
||||||
|
returned pointer is to a static buffer overwritten on the next call. */
|
||||||
|
char const *fmt_to_ANSI(int format);
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Misc.
|
||||||
|
//---
|
||||||
|
|
||||||
|
/* Generates a unique name for a file to be stored in `path`, with `name` as a
|
||||||
|
component and the provided `suffix`. The generated path looks like
|
||||||
|
|
||||||
|
<path>/fxlink-<name>-2021.05.09-19h23-1<suffix>
|
||||||
|
|
||||||
|
with the `-1` suffix being chosen as to avoid overriding existing files.
|
||||||
|
Returns a newly-allocated string to be free()'d after use. */
|
||||||
|
char *fxlink_gen_file_name(char const *path, char const *name,
|
||||||
|
char const *suffix);
|
||||||
|
|
||||||
|
/* Modified poll with a variable number of arrays. */
|
||||||
|
int fxlink_multipoll(int timeout, struct pollfd *fds1, int count1, ...);
|
||||||
|
|
||||||
|
/* Write out the given size (in bytes) in a human-readable form. Returns a
|
||||||
|
pointer to a statically-allocated string. */
|
||||||
|
char const *fxlink_size_string(int bytes);
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Delay
|
||||||
|
//---
|
||||||
|
|
||||||
|
/* An expandable allocated time used to wait for devices */
|
||||||
|
typedef int delay_t;
|
||||||
|
|
||||||
|
/* Builds an empty delay. */
|
||||||
|
delay_t delay_none(void);
|
||||||
|
/* Builds a delay that lasts the specified number of seconds. */
|
||||||
|
delay_t delay_seconds(int seconds);
|
||||||
|
/* Builds an infinite delay. */
|
||||||
|
delay_t delay_infinite(void);
|
||||||
|
|
||||||
|
/* Returns `true` if the delay has expired; otherwise, waits for a short while
|
||||||
|
(250 ms), decreases the supplied delay pointer, and returns `false`. Never
|
||||||
|
returns `true` after waiting (even if the delay just expired) so the caller
|
||||||
|
can attempt their task one last time before giving up on a timeout. */
|
||||||
|
bool delay_cycle(delay_t *delay);
|
325
fxlink/include/fxlink/devices.h
Normal file
325
fxlink/include/fxlink/devices.h
Normal file
|
@ -0,0 +1,325 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// fxlink.devices: Device management and state tracking
|
||||||
|
//
|
||||||
|
// This module provides definitions for fxlink's view of devices and their
|
||||||
|
// states. There are three "tiers" of devices depending on how much fxlink
|
||||||
|
// interacts with them:
|
||||||
|
//
|
||||||
|
// - Every connected USB device is inspected and given a `struct fxlink_device`
|
||||||
|
// which track metadata like vendor and product IDs. A device at this level
|
||||||
|
// isn't very useful so fxlink won't do anything with them.
|
||||||
|
//
|
||||||
|
// - Devices with the vendor and product IDs of CASIO calculators are opened;
|
||||||
|
// their configuration descriptor is analyzed to determine what interfaces
|
||||||
|
// they offer, and some communication is performed to retrieve information
|
||||||
|
// like the serial number of the calculator. This information is stored in a
|
||||||
|
// `struct fxlink_calc` structure linked in the `calc` field of the main
|
||||||
|
// device structure. Only after this stage can device filters be applied.
|
||||||
|
//
|
||||||
|
// The key operation to do next is claiming an interface so communication can
|
||||||
|
// start. fxlink devices can be used for manual communication using the device
|
||||||
|
// pointer `fdev->dp` and the device handle `fdev->dh`. This is useful for
|
||||||
|
// communication with arbitrary interfaces like the CESG502 interface used by
|
||||||
|
// CASIO's Comm syscalls. However, when gint is used and the fxlink-specific
|
||||||
|
// interface (protocol) is opened, this header provides more automatic tools.
|
||||||
|
//
|
||||||
|
// - When calculators expose an fxlink interface, that interface can be claimed
|
||||||
|
// with fxlink_device_claim_fxlink(). This module finds relevant endpoints,
|
||||||
|
// allocates transfer buffers/utilities, and exposes a higher-level message-
|
||||||
|
// based interface. Associated data is stored in a `struct fxlink_comm`
|
||||||
|
// structure linked in the `comm` field of the main device structure.
|
||||||
|
//
|
||||||
|
// In addition to handling single devices, this header provides two types of
|
||||||
|
// tools to find devices:
|
||||||
|
//
|
||||||
|
// 1. Tracking tools, which are used to watch connected devices in real-time.
|
||||||
|
// These are asynchronous/non-blocking, keep track of what devices we've
|
||||||
|
// seen before and overall are the most flexible. These are used by the TUI.
|
||||||
|
//
|
||||||
|
// 2. "Simplified enumeration" functions which simply search for a single
|
||||||
|
// device matching a filter. These are relevant when handling multiple
|
||||||
|
// connections are not a concern, and used eg. by legacy interactive mode.
|
||||||
|
//---
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <fxlink/protocol.h>
|
||||||
|
#include <fxlink/filter.h>
|
||||||
|
#include <libusb.h>
|
||||||
|
#include <poll.h>
|
||||||
|
|
||||||
|
/* Device information tracked for every USB device. */
|
||||||
|
struct fxlink_device {
|
||||||
|
/* libusb device pointer. This is libusb_ref_device()'d for the entire
|
||||||
|
lifetime of the structure (either managed by the device list or obtained
|
||||||
|
by simplified enumeration and freed by fxlink_device_cleanup()). */
|
||||||
|
libusb_device *dp;
|
||||||
|
/* libusb device handle (NULL when the device is not open) */
|
||||||
|
libusb_device_handle *dh;
|
||||||
|
/* Device status (an FDEV_STATUS_* enumerated value) */
|
||||||
|
uint8_t status;
|
||||||
|
|
||||||
|
/* Standard USB information */
|
||||||
|
short busNumber;
|
||||||
|
short deviceAddress;
|
||||||
|
uint16_t idVendor;
|
||||||
|
uint16_t idProduct;
|
||||||
|
|
||||||
|
/* Calculator data. This field is present whenever a device is a CASIO
|
||||||
|
calculator. Information can be accessed freely. */
|
||||||
|
struct fxlink_calc *calc;
|
||||||
|
/* Communication data for the fxlink interface. This field is present when
|
||||||
|
calculators have an fxlink interface. It can be used when not NULL but
|
||||||
|
note that the device status might still be IGNORED (if filtered out) or
|
||||||
|
ERROR (if communication errors occurs) in which case communication might
|
||||||
|
not be allowed. */
|
||||||
|
struct fxlink_comm *comm;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
/* The device has just been connected to the host, and we have yet to
|
||||||
|
inspect it or open it. We don't know yet whether there are any supported
|
||||||
|
interfaces. */
|
||||||
|
FXLINK_FDEV_STATUS_PENDING,
|
||||||
|
/* The device is ignored by fxlink. This is either because it's not a CASIO
|
||||||
|
calculator or because it was excluded by a device filter. The device
|
||||||
|
might have been opened (to find out the serial number) but it is now
|
||||||
|
closed and no interfaces are claimed. */
|
||||||
|
FXLINK_FDEV_STATUS_IGNORED,
|
||||||
|
/* The device could not be used due to an error: access denied, interface
|
||||||
|
already claimed, transient errors, etc. */
|
||||||
|
FXLINK_FDEV_STATUS_ERROR,
|
||||||
|
/* The device is a calculator and it's not ignored, but no interfaces have
|
||||||
|
been claimed yet. This header only sets the status to CONNECTED when
|
||||||
|
using an fxlink interface, but that status is mostly cosmetic and it's
|
||||||
|
entirely possible to manually claim and use an interface (eg. CESG502)
|
||||||
|
while the device is in the IDLE state. The `comm` field may or may not
|
||||||
|
be present depending on whether there is an fxlink interface. */
|
||||||
|
FXLINK_FDEV_STATUS_IDLE,
|
||||||
|
/* The device is a calculator, is not ignored, has an fxlink interface and
|
||||||
|
that interface was claimed. The `comm` field is non-NULL and can be
|
||||||
|
checked to be determine whether communication is going on. */
|
||||||
|
FXLINK_FDEV_STATUS_CONNECTED,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Return a string representation of the device's bus number and device
|
||||||
|
address, which make up a human-readable a locally unique device ID. The
|
||||||
|
address of a static string is returned. */
|
||||||
|
char const *fxlink_device_id(struct fxlink_device const *fdev);
|
||||||
|
|
||||||
|
/* Return a string representation of the current status. */
|
||||||
|
char const *fxlink_device_status_string(struct fxlink_device const *fdev);
|
||||||
|
|
||||||
|
/* Analyze a CASIO calculator device to reach "calculator" tier.
|
||||||
|
|
||||||
|
These functions can be called on CASIO calculators (idVendor 07cf, idProduct
|
||||||
|
6101 or 6102). They determine available interfaces, open the device, and
|
||||||
|
initialize the `calc` field with information about the device's interfaces.
|
||||||
|
If an fxlink interface is found, they also initialize the `comm` field with
|
||||||
|
static information (but doesn't claim the interface).
|
||||||
|
|
||||||
|
After analysis, either the device status is ERROR, or it is IDLE and
|
||||||
|
interfaces are ready to be claimed.
|
||||||
|
|
||||||
|
fxlink_device_analysis_1() is generally called in an event handling context,
|
||||||
|
so its capabilities are limited. fxlink_device_analysis_2() is called after
|
||||||
|
event handling finishes to finalize analysis. Both calls are managed by the
|
||||||
|
device list or simplified enumeration functions so direct calls are normally
|
||||||
|
not needed. `quiet` suppresses logs. */
|
||||||
|
void fxlink_device_analysis_1(struct fxlink_device *fdev, bool quiet);
|
||||||
|
void fxlink_device_analysis_2(struct fxlink_device *fdev);
|
||||||
|
|
||||||
|
/* Determine the filter properties of a calculator. This can be used for any
|
||||||
|
device at the calculator tier. This function only reads the device structure
|
||||||
|
and does not communicate. */
|
||||||
|
void fxlink_device_get_properties(struct fxlink_device const *fdev,
|
||||||
|
struct fxlink_filter *properties);
|
||||||
|
|
||||||
|
/* Device information tracked for CASIO calculators. */
|
||||||
|
struct fxlink_calc {
|
||||||
|
/* System running on the calculator (a CALC_SYSTEM_* enumerated value) */
|
||||||
|
uint8_t system;
|
||||||
|
/* Number of interfaces */
|
||||||
|
uint8_t interface_count;
|
||||||
|
/* fxlink interface number (-1 if not found) */
|
||||||
|
int8_t fxlink_inum;
|
||||||
|
/* List of interface classes (interface_count elements). Each element is a
|
||||||
|
two-byte value, MSB being class and LSB subclass. */
|
||||||
|
uint16_t *classes;
|
||||||
|
/* Serial number (obtained with string descriptor SETUP request) */
|
||||||
|
char *serial;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
/* The device is running an unidentified system. */
|
||||||
|
FXLINK_CALC_SYSTEM_UNKNOWN,
|
||||||
|
/* The device is using LINK app's SCSI (Mass Storage) interface. */
|
||||||
|
FXLINK_CALC_SYSTEM_LINKSCSI,
|
||||||
|
/* The device is using the OS' native bulk interface with Comm syscalls. */
|
||||||
|
FXLINK_CALC_SYSTEM_CESG502,
|
||||||
|
/* The device is using gint's USB driver. */
|
||||||
|
FXLINK_CALC_SYSTEM_GINT,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Return a string representation of the calc determined system. */
|
||||||
|
char const *fxlink_device_system_string(struct fxlink_device const *fdev);
|
||||||
|
|
||||||
|
/* Device state tracked for communication targets. */
|
||||||
|
struct fxlink_comm {
|
||||||
|
/* Whether the fxlink interface could be claimed */
|
||||||
|
bool claimed;
|
||||||
|
/* Endpoints of the fxlink interface */
|
||||||
|
uint8_t ep_bulk_IN;
|
||||||
|
uint8_t ep_bulk_OUT;
|
||||||
|
|
||||||
|
/* Current IN transfer */
|
||||||
|
struct libusb_transfer *tr_bulk_IN;
|
||||||
|
/* IN transfer buffer and its size */
|
||||||
|
uint8_t *buffer_IN;
|
||||||
|
int buffer_IN_size;
|
||||||
|
/* fxlink message construction for the IN transfer */
|
||||||
|
struct fxlink_transfer *ftransfer_IN;
|
||||||
|
/* Cancellation flag */
|
||||||
|
bool cancelled_IN;
|
||||||
|
/* Completed transfer objects (to be checked by user every frame) */
|
||||||
|
struct fxlink_message *message_IN;
|
||||||
|
|
||||||
|
/* Current OUT transfer */
|
||||||
|
struct libusb_transfer *tr_bulk_OUT;
|
||||||
|
/* fxlink message construction for the OUT transfer */
|
||||||
|
struct fxlink_transfer *ftransfer_OUT;
|
||||||
|
/* Cancellation flag */
|
||||||
|
bool cancelled_OUT;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Claim the fxlink interface (for a device that has one). Returns false and
|
||||||
|
sets the status to ERROR on failure. */
|
||||||
|
bool fxlink_device_claim_fxlink(struct fxlink_device *fdev);
|
||||||
|
|
||||||
|
/* Start an IN transfer on the device if none is currently running, so that the
|
||||||
|
device structure is always ready to receive data from the calculator. */
|
||||||
|
void fxlink_device_start_bulk_IN(struct fxlink_device *fdev);
|
||||||
|
|
||||||
|
/* Finish an IN transfer and obtain the completed message. This function should
|
||||||
|
be checked every frame as it will return a non-NULL pointer as soon as the
|
||||||
|
message is completed and the device will only start a new bulk IN transfer
|
||||||
|
until the message is moved out by this function. */
|
||||||
|
struct fxlink_message *fxlink_device_finish_bulk_IN(
|
||||||
|
struct fxlink_device *fdev);
|
||||||
|
|
||||||
|
/* Interrupt any active transfers on the device. */
|
||||||
|
void fxlink_device_interrupt_transfers(struct fxlink_device *fdev);
|
||||||
|
|
||||||
|
/* Clean everything that needs to be cleaned for the device to be destroyed.
|
||||||
|
This closes the libusb device handle, frees dynamically allocated memory,
|
||||||
|
etc. This function finishes immediately so no transfers must be running
|
||||||
|
when it is call; use fxlink_device_interrupt_transfers() first. */
|
||||||
|
void fxlink_device_cleanup(struct fxlink_device *fdev);
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Polled file descriptor tracking
|
||||||
|
//
|
||||||
|
// fxlink has some asynchronous main loops where events from libusb are mixed
|
||||||
|
// with user input. We poll libusb file descriptors, along with other event
|
||||||
|
// sources such as stdin, to listen to all event sources at the same time. This
|
||||||
|
// is possible on Linux because libusb exposes its active file descriptors. The
|
||||||
|
// following utility is a wrapper to track them, built around libusb's pollfd
|
||||||
|
// notification API.
|
||||||
|
//---
|
||||||
|
|
||||||
|
/* Tracker for libusb file descriptors to be polled in a main loop. */
|
||||||
|
struct fxlink_pollfds {
|
||||||
|
/* libusb context to be tracked (must remain constant) */
|
||||||
|
libusb_context *ctx;
|
||||||
|
/* Array of file descriptors currently being polled */
|
||||||
|
struct pollfd *fds;
|
||||||
|
/* Number of elements in `fds` */
|
||||||
|
int count;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Start tracking file descriptors for a given context. This sets up notifiers
|
||||||
|
so tracker->fds and tracker->count will be updated automatically every time
|
||||||
|
libusb events are processed. */
|
||||||
|
void fxlink_pollfds_track(struct fxlink_pollfds *tracker, libusb_context *ctx);
|
||||||
|
|
||||||
|
/* Stop tracking file descriptors and free tracker's resources. This must be
|
||||||
|
called before the tracker structure gets destroyed. */
|
||||||
|
void fxlink_pollfds_stop(struct fxlink_pollfds *tracker);
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Device tracking
|
||||||
|
//
|
||||||
|
// For real-time interactions and device detection it is useful to keep an eye
|
||||||
|
// on connected devices, process incoming devices as well as properly free
|
||||||
|
// resources for disconnected devices. libusb has a hotplug notification
|
||||||
|
// mechanism, which we wrap here into a dynamic device list.
|
||||||
|
//
|
||||||
|
// The list stays constantly updated by means of the hotplug notification and
|
||||||
|
// initializes an fxlink_device structure for every device. It does all the
|
||||||
|
// "safe" work, ie. everything that doesn't involve actual communication. For
|
||||||
|
// instance, it fills in the fields of both the fxlink_device and fxlink_calc
|
||||||
|
// structures, but it doesn't query serial numbers.
|
||||||
|
//---
|
||||||
|
|
||||||
|
/* A dynamically updated list of all connected devices. */
|
||||||
|
struct fxlink_device_list {
|
||||||
|
/* libusb context that we track the devices for */
|
||||||
|
libusb_context *ctx;
|
||||||
|
/* Callback handle */
|
||||||
|
libusb_hotplug_callback_handle hotplug_handle;
|
||||||
|
/* Array of connected devices */
|
||||||
|
struct fxlink_device *devices;
|
||||||
|
/* Number of elements in `devices` */
|
||||||
|
int count;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Start tracking connected devices for the given context. The list will update
|
||||||
|
whenever libusb events are processed and new devices will be available (ie.
|
||||||
|
in the IDLE state) after calling fxlink_device_list_refresh(). */
|
||||||
|
bool fxlink_device_list_track(struct fxlink_device_list *list,
|
||||||
|
libusb_context *ctx);
|
||||||
|
|
||||||
|
/* Refresh devices. This should be called after handling libusb events; it
|
||||||
|
finishes analysis for new devices. After calling this function, devices are
|
||||||
|
ready to be filtered and interfaces claimed. */
|
||||||
|
void fxlink_device_list_refresh(struct fxlink_device_list *list);
|
||||||
|
|
||||||
|
/* Interrupt transfers on all devices in the list. This is non-blocking;
|
||||||
|
returns true if some transfers are still running. Call this in a loop until
|
||||||
|
it returns false, while handling libusb events at each iteration. */
|
||||||
|
bool fxlink_device_list_interrupt(struct fxlink_device_list *list);
|
||||||
|
|
||||||
|
/* Stop tracking connected devices, free the device list and disable the
|
||||||
|
hotplug notification. Make sure all devices are closed and communications
|
||||||
|
stopped before calling this function. */
|
||||||
|
void fxlink_device_list_stop(struct fxlink_device_list *list);
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Simplified device enumeration
|
||||||
|
//
|
||||||
|
// These functions provides a simpler way to find devices. Given a filter they
|
||||||
|
// will try to find a device that matches, and build a device structure when
|
||||||
|
// they find one. These are only suitable to open a single device since they
|
||||||
|
// don't track which fxlink devices have been created.
|
||||||
|
//---
|
||||||
|
|
||||||
|
/* Finds a libusb device that matches the provided filter.
|
||||||
|
|
||||||
|
If one is found, returns a newly-allocated fxlink device. The device will be
|
||||||
|
opened and have its device handler in the `comm->dh` field. Additionally, if
|
||||||
|
there is an fxlink interface, that interface will be claimed and
|
||||||
|
communication tools from this header will be initialized.
|
||||||
|
|
||||||
|
If no matching device is found, NULL is returned; this is a non-blocking
|
||||||
|
function. Note that this function will interact with devices to determine
|
||||||
|
their property (ie. serial number) even when it returns NULL. */
|
||||||
|
struct fxlink_device *fxlink_device_find(libusb_context *ctx,
|
||||||
|
struct fxlink_filter const *filter);
|
||||||
|
|
||||||
|
/* Same as fxlink_device_find(), but waits for the specified delay. */
|
||||||
|
// TODO: fxlink_device_find_wait(): Use a struct timespec, get rid of delay_t?
|
||||||
|
struct fxlink_device *fxlink_device_find_wait(libusb_context *ctx,
|
||||||
|
struct fxlink_filter const *filter, delay_t *delay);
|
84
fxlink/include/fxlink/filter.h
Normal file
84
fxlink/include/fxlink/filter.h
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// fxlink.filter: Backend-agnostic device filters
|
||||||
|
//
|
||||||
|
// Most automatic functions in fxlink only connect with a single calculator.
|
||||||
|
// When several calculators are connected it is useful to narrow down the
|
||||||
|
// targeted machine programmatically, which is done with a device filter.
|
||||||
|
//
|
||||||
|
// A device filter is a simple conjunction of properties detected with the
|
||||||
|
// generic metadata of the device, as provided by the backend's API. Depending
|
||||||
|
// on the backend not all properties can be detected; for instance, fx-CG and
|
||||||
|
// G-III calculators have the same idVendor/idProduct pair and cannot be
|
||||||
|
// distinguished on the device descriptor alone, however they report different
|
||||||
|
// identities in SCSI queries and can be differentiated with UDisks2.
|
||||||
|
//
|
||||||
|
// The same type `struct fxlink_properties` is used both to represents devices'
|
||||||
|
// properties and filters. Every property has a "total information order" where
|
||||||
|
// values can be compared for how specific they are. A device matches a filter
|
||||||
|
// if its detected properties are more specific than the filter's requirements.
|
||||||
|
// So far the order is:
|
||||||
|
// - For booleans: true more specific than false
|
||||||
|
// - For serial number: any string more specific than NULL
|
||||||
|
//---
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <fxlink/defs.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* Detected devices' properties; also used for filters. */
|
||||||
|
struct fxlink_filter {
|
||||||
|
/* The calculator uses Protocol 7/CESG502. Detected with idProduct 0x6101.
|
||||||
|
These calcs don't support SCSI, so this is always false in UDisks2. */
|
||||||
|
bool p7;
|
||||||
|
/* The calculator supports Mass Storage/SCSI. Detected with idProduct
|
||||||
|
0x6102. Always true in UDisks2. */
|
||||||
|
bool mass_storage;
|
||||||
|
/* The calculator has an fxlink interface. Always false in UDisks2. */
|
||||||
|
bool intf_fxlink;
|
||||||
|
/* The calculator has a CESG502 interface. Always false in UDisks2. */
|
||||||
|
bool intf_cesg502;
|
||||||
|
/* The calculator is from the fx-CG/G-III series. Detected with the SCSI
|
||||||
|
drive `model` metadata. Only available in UDisks2. */
|
||||||
|
bool series_cg;
|
||||||
|
bool series_g3;
|
||||||
|
/* Serial number. Requires write access to obtain in libusb (with a STRING
|
||||||
|
descriptor request) because it's not cached. free() after use. */
|
||||||
|
char *serial;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Return values for back-end specific matching functions. */
|
||||||
|
enum {
|
||||||
|
/* A unique calculator matching the filter was found */
|
||||||
|
FXLINK_FILTER_UNIQUE,
|
||||||
|
/* No calculator matching the filter was found */
|
||||||
|
FXLINK_FILTER_NONE,
|
||||||
|
/* Multiple calculators matching the filter were found */
|
||||||
|
FXLINK_FILTER_MULTIPLE,
|
||||||
|
/* An error occurred while trying to enumerate devices */
|
||||||
|
FXLINK_FILTER_ERROR,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Parse a filter string into a structure */
|
||||||
|
struct fxlink_filter *fxlink_filter_parse(char const *specification);
|
||||||
|
|
||||||
|
/* Disable filter properties not supported by each backend (with a warning
|
||||||
|
emitted for each such ignored property) */
|
||||||
|
void fxlink_filter_clean_libusb(struct fxlink_filter *filter);
|
||||||
|
void fxlink_filter_clean_udisks2(struct fxlink_filter *filter);
|
||||||
|
|
||||||
|
/* Determines whether a set of concrete properties matches a filter. Returns
|
||||||
|
whether all the fields of `props` are more specific than their counterparts
|
||||||
|
in `filter`. */
|
||||||
|
bool fxlink_filter_match(
|
||||||
|
struct fxlink_filter const *props,
|
||||||
|
struct fxlink_filter const *filter);
|
||||||
|
|
||||||
|
/* Print a set of properties to a stream. Outputs a single line. */
|
||||||
|
void fxlink_filter_print(FILE *fp, struct fxlink_filter const *props);
|
||||||
|
|
||||||
|
/* Free a filter structure and its fields */
|
||||||
|
void fxlink_filter_free(struct fxlink_filter *filter);
|
49
fxlink/include/fxlink/logging.h
Normal file
49
fxlink/include/fxlink/logging.h
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// fxlink.logging: Logging interface and log redirection
|
||||||
|
//
|
||||||
|
// This header provides basic logging utilities. You've seen these a thousand
|
||||||
|
// times over. The only features are supporting colors from <fxlink/defs.h>,
|
||||||
|
// redirecting libusb logs to this interface automatically, and redirecting
|
||||||
|
// this interface to either the terminal or the ncurses TUI.
|
||||||
|
//---
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <fxlink/defs.h>
|
||||||
|
#include <libusb.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* Type of the log handling function out of this interface */
|
||||||
|
typedef void fxlink_log_handler_t(int display_fmt, char const *str);
|
||||||
|
|
||||||
|
/* Default handler; prints to stderr with ANSI escapes. */
|
||||||
|
fxlink_log_handler_t fxlink_log_stderr;
|
||||||
|
|
||||||
|
/* Set the logging function. Default is fxlink_log_stderr. */
|
||||||
|
void fxlink_log_set_handler(fxlink_log_handler_t *handler);
|
||||||
|
|
||||||
|
/* Redirect libusb log to this module's log hander. */
|
||||||
|
void fxlink_log_grab_libusb_logs(void);
|
||||||
|
|
||||||
|
/* Log a message. All of the logging functions accept a printf()-style format
|
||||||
|
with corresponding arguments, and return 1. */
|
||||||
|
int log_(char const *fmt, ...);
|
||||||
|
/* Log a message with a certain display format (color/bold/italic). */
|
||||||
|
int flog(int display_fmt, char const *fmt, ...);
|
||||||
|
/* Like log(), but adds a red "error:" in front. */
|
||||||
|
int elog(char const *fmt, ...);
|
||||||
|
/* Like log(), but adds a yellow "warning:" in front. */
|
||||||
|
int wlog(char const *fmt, ...);
|
||||||
|
/* Like log(), but for headers (with current time in gray in square brackets,
|
||||||
|
and provided text in yellow followed by ":"). */
|
||||||
|
int hlog(char const *fmt, ...);
|
||||||
|
|
||||||
|
/* Warning that includes a libusb error message. */
|
||||||
|
#define wlog_libusb(RC, FMT, ...) \
|
||||||
|
wlog(FMT ": %s\n", ##__VA_ARGS__, libusb_strerror(RC))
|
||||||
|
/* Error that includes a libusb error message. */
|
||||||
|
#define elog_libusb(RC, FMT, ...) \
|
||||||
|
elog(FMT ": %s\n", ##__VA_ARGS__, libusb_strerror(RC))
|
140
fxlink/include/fxlink/protocol.h
Normal file
140
fxlink/include/fxlink/protocol.h
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// fxlink.protocol: Basic fxlink-specific communications procotol
|
||||||
|
//
|
||||||
|
// This header implements fxlink's run-of-the-mill communications protocol.
|
||||||
|
// Messages are typed with an application/type pair and their length must be
|
||||||
|
// announced in the header. In most modes when a message with an application
|
||||||
|
// field other than "fxlink" is specified, an external executable is invoked,
|
||||||
|
// which provides a basic form of automation.
|
||||||
|
//
|
||||||
|
// Message headers are transferred in little-endian format but some message
|
||||||
|
// types are defined to have big-endian contents to avoid excessive amounts of
|
||||||
|
// work on the calculator side.
|
||||||
|
//---
|
||||||
|
|
||||||
|
/* TODO: fxlink protocol: avoid having to specify message length in header */
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <fxlink/defs.h>
|
||||||
|
|
||||||
|
/* Message. The header consists of every field but the `data` pointer, and it
|
||||||
|
must arrive entirely within the first transaction for a message to be
|
||||||
|
recognized. */
|
||||||
|
struct fxlink_message {
|
||||||
|
/* Protocol version, in format 0x0000MMmm */
|
||||||
|
uint32_t version;
|
||||||
|
/* Total size of message, in bytes (excluding this header) */
|
||||||
|
uint32_t size;
|
||||||
|
/* Size of individual transfers (usually 2048 bytes) */
|
||||||
|
/* TODO: fxlink protocol: get rid of transfer size field (blank it out) */
|
||||||
|
uint32_t transfer_size;
|
||||||
|
|
||||||
|
/* Application name (NUL-padded but might not be NUL-terminated) */
|
||||||
|
char application[16];
|
||||||
|
/* Message type (NUL-padded but might not be NUL-terminated) */
|
||||||
|
char type[16];
|
||||||
|
|
||||||
|
/** End of actual header in protocol **/
|
||||||
|
|
||||||
|
/* Padding for alignment */
|
||||||
|
int _padding;
|
||||||
|
/* Pointer to message data, with `size` bytes */
|
||||||
|
void *data;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define FXLINK_MESSAGE_HEADER_SIZE (offsetof(struct fxlink_message, _padding))
|
||||||
|
|
||||||
|
/* Subheader for the built-in image message type. */
|
||||||
|
struct fxlink_message_image_header {
|
||||||
|
/* Image width and height, in pixels */
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
/* Pixel format, one of the FXLINK_MESSAGE_IMAGE_* values below */
|
||||||
|
int pixel_format;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
/* Image is an array of big-endian uint16_t values with RGB565 format */
|
||||||
|
FXLINK_MESSAGE_IMAGE_RGB565,
|
||||||
|
/* Image is an array of bits in black-and-white format */
|
||||||
|
FXLINK_MESSAGE_IMAGE_MONO,
|
||||||
|
/* Image is two consecutive mono arrays, one for light, one for dark */
|
||||||
|
FXLINK_MESSAGE_IMAGE_GRAY,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Format for raw decoded images to be used with other APIs. */
|
||||||
|
struct fxlink_message_image_raw {
|
||||||
|
/* Width and height in pixels */
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
/* Allocated array of `height` pointers, each pointing to `3*width` bytes
|
||||||
|
of memory containing the RGB data of pixels from left to right. */
|
||||||
|
uint8_t **data;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Check whether a message has a certain application and type. If type is NULL,
|
||||||
|
checks the application only (any type is accepted). */
|
||||||
|
bool fxlink_message_is_apptype(struct fxlink_message const *message,
|
||||||
|
char const *application, char const *type);
|
||||||
|
|
||||||
|
/* Check if a message is one of common built-in fxlink messages. */
|
||||||
|
#define fxlink_message_is_fxlink_text(MESSAGE) \
|
||||||
|
fxlink_message_is_apptype(MESSAGE, "fxlink", "text")
|
||||||
|
#define fxlink_message_is_fxlink_image(MESSAGE) \
|
||||||
|
fxlink_message_is_apptype(MESSAGE, "fxlink", "image")
|
||||||
|
#define fxlink_message_is_fxlink_video(MESSAGE) \
|
||||||
|
fxlink_message_is_apptype(MESSAGE, "fxlink", "video")
|
||||||
|
|
||||||
|
/* Decode an image message into a raw image structure. */
|
||||||
|
struct fxlink_message_image_raw *fxlink_message_image_decode(
|
||||||
|
struct fxlink_message const *msg);
|
||||||
|
|
||||||
|
/* Free a raw image structure. */
|
||||||
|
void fxlink_message_image_raw_free(struct fxlink_message_image_raw *raw);
|
||||||
|
|
||||||
|
/* Free memory associated with a message. If free_data is true, also frees the
|
||||||
|
internal data buffer. You should set the flag for messages you received. */
|
||||||
|
void fxlink_message_free(struct fxlink_message *message, bool free_data);
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Tools for crafting and receiving messages
|
||||||
|
//---
|
||||||
|
|
||||||
|
/* Data for an inbound or outbound transfer in progress. This structure can be
|
||||||
|
created as soon as a header has been received or filled. */
|
||||||
|
struct fxlink_transfer {
|
||||||
|
/* Message header and data buffer */
|
||||||
|
struct fxlink_message msg;
|
||||||
|
/* Transfer direction (FXLINK_TRANSFER_{IN,OUT}) */
|
||||||
|
uint8_t direction;
|
||||||
|
/* Size of data sent or received so far */
|
||||||
|
uint32_t processed_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
/* Transfer is inbound (calculator -> fxlink) */
|
||||||
|
FXLINK_TRANSFER_IN,
|
||||||
|
/* Transfer is outbound (fxlink -> calculator) */
|
||||||
|
FXLINK_TRANSFER_OUT,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Make an inbound transfer structure starting with the provided data (the data
|
||||||
|
obtained in the first IN transaction to be decoded). This function grabs the
|
||||||
|
header from the data, allocates a buffer of a suitable size and starts
|
||||||
|
saving the message's contents. Returns NULL on error (invalid or incomplete
|
||||||
|
header, out of memory, etc). */
|
||||||
|
struct fxlink_transfer *fxlink_transfer_make_IN(void *data, int size);
|
||||||
|
|
||||||
|
/* If the provided IN transfer is finished, extract the message and free the
|
||||||
|
transfer pointer. Otherwise, return NULL. */
|
||||||
|
struct fxlink_message *fxlink_transfer_finish_IN(struct fxlink_transfer *tr);
|
||||||
|
|
||||||
|
/* Append data to a previously-initialized inbound transfer. */
|
||||||
|
void fxlink_transfer_receive(struct fxlink_transfer *tr, void *data, int size);
|
||||||
|
|
||||||
|
/* Check whether a transfer is complete. */
|
||||||
|
bool fxlink_transfer_complete(struct fxlink_transfer const *tr);
|
15
fxlink/include/fxlink/tooling/libpng.h
Normal file
15
fxlink/include/fxlink/tooling/libpng.h
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// fxlink.tooling.libpng: Utilities based on libpng
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <fxlink/protocol.h>
|
||||||
|
#include <png.h>
|
||||||
|
|
||||||
|
/* Save a raw image decoded from an fxlink message to a PNG file. Returns
|
||||||
|
zero on success. */
|
||||||
|
int fxlink_libpng_save_raw(struct fxlink_message_image_raw *raw,
|
||||||
|
char const *path);
|
24
fxlink/include/fxlink/tooling/sdl2.h
Normal file
24
fxlink/include/fxlink/tooling/sdl2.h
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// fxlink.tooling.sdl2: Utilities based on the SDL2 library
|
||||||
|
//
|
||||||
|
// Note: all the functions in this file are "neutralized" if the compile-time
|
||||||
|
// option FXLINK_DISABLE_SDL2 is set. See <fxlink/config.h.in>.
|
||||||
|
//---
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <fxlink/protocol.h>
|
||||||
|
|
||||||
|
#ifndef FXLINK_DISABLE_SDL2
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Display a raw image on the window. If now window has been opened yet, one is
|
||||||
|
created automatically. */
|
||||||
|
void fxlink_sdl2_display_raw(struct fxlink_message_image_raw const *raw);
|
||||||
|
|
||||||
|
/* Handle SDL events. This should be called regularly from the main thread. */
|
||||||
|
void fxlink_sdl2_handle_events(void);
|
|
@ -1,33 +1,39 @@
|
||||||
//---
|
//---------------------------------------------------------------------------//
|
||||||
// fxlink:ud2 - UDisks2 functions
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
//---
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
#ifndef FXLINK_UD2_H
|
//---------------------------------------------------------------------------//
|
||||||
#define FXLINK_UD2_H
|
// fxlink.tooling.udisks2: Utilities based on the UDisks2 library
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <fxlink/config.h>
|
||||||
#ifndef FXLINK_DISABLE_UDISKS2
|
#ifndef FXLINK_DISABLE_UDISKS2
|
||||||
|
|
||||||
#include <udisks/udisks.h>
|
#include <udisks/udisks.h>
|
||||||
#include "config.h"
|
#include <fxlink/filter.h>
|
||||||
#include "properties.h"
|
|
||||||
#include "filter.h"
|
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
/* ud2_properties(): Determine properties of a UDisks2 USB drive */
|
/* ud2_properties(): Determine properties of a UDisks2 USB drive */
|
||||||
properties_t ud2_properties(UDisksDrive *drive);
|
struct fxlink_filter ud2_properties(UDisksDrive *drive);
|
||||||
|
|
||||||
|
/* Initialize a connection to the UDisks2 service via D-Bus. */
|
||||||
|
int ud2_start(UDisksClient **udc_ptr, UDisksManager **udm_ptr);
|
||||||
|
|
||||||
|
/* Close the connection to the UDisks2 service. */
|
||||||
|
void ud2_end(UDisksClient *udc, UDisksManager *udm);
|
||||||
|
|
||||||
/* ud2_unique_matching(): Device matching the provided filter, if unique
|
/* ud2_unique_matching(): Device matching the provided filter, if unique
|
||||||
Similar to usb_unique_matching(), please refer to "usb.h" for details.
|
Similar to usb_unique_matching(), please refer to "usb.h" for details.
|
||||||
There are just many more inputs and outputs. */
|
There are just many more inputs and outputs. */
|
||||||
int ud2_unique_matching(filter_t const *filter, UDisksClient *udc,
|
int ud2_unique_matching(struct fxlink_filter const *filter, UDisksClient *udc,
|
||||||
UDisksManager *udm, UDisksBlock **block, UDisksDrive **drive,
|
UDisksManager *udm, UDisksBlock **block, UDisksDrive **drive,
|
||||||
UDisksFilesystem **fs);
|
UDisksFilesystem **fs);
|
||||||
|
|
||||||
/* ud2_unique_wait(): Wait for a device matching the provided filter to connect
|
/* ud2_unique_wait(): Wait for a device matching the provided filter to connect
|
||||||
Like usb_unique_wait(), please see "usb.h" for details. */
|
Returns an FXLINK_FILTER_* code. If a unique device is found, sets *udc,
|
||||||
int ud2_unique_wait(filter_t const *filter, delay_t *delay, UDisksClient *udc,
|
*udm, *block, *drive and *fs accordingly. */
|
||||||
UDisksManager *udm, UDisksBlock **block, UDisksDrive **drive,
|
int ud2_unique_wait(struct fxlink_filter const *filter, delay_t *delay,
|
||||||
UDisksFilesystem **fs);
|
UDisksClient *udc, UDisksManager *udm, UDisksBlock **block,
|
||||||
|
UDisksDrive **drive, UDisksFilesystem **fs);
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// Iteration on UDisks2 devices
|
// Iteration on UDisks2 devices
|
||||||
|
@ -39,7 +45,7 @@ typedef struct {
|
||||||
UDisksDrive *drive;
|
UDisksDrive *drive;
|
||||||
UDisksFilesystem *fs;
|
UDisksFilesystem *fs;
|
||||||
/* Device properties */
|
/* Device properties */
|
||||||
properties_t props;
|
struct fxlink_filter props;
|
||||||
/* Whether the iteration has finished */
|
/* Whether the iteration has finished */
|
||||||
bool done;
|
bool done;
|
||||||
|
|
||||||
|
@ -66,5 +72,3 @@ void ud2_iter_next(ud2_iterator_t *it);
|
||||||
!NAME.done; ud2_iter_next(&NAME)) if(!NAME.done)
|
!NAME.done; ud2_iter_next(&NAME)) if(!NAME.done)
|
||||||
|
|
||||||
#endif /* FXLINK_DISABLE_UDISKS2 */
|
#endif /* FXLINK_DISABLE_UDISKS2 */
|
||||||
|
|
||||||
#endif /* FXLINK_UD2_H */
|
|
71
fxlink/include/fxlink/tui/input.h
Normal file
71
fxlink/include/fxlink/tui/input.h
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// fxlink.tui.input: Asynchronous readline-style console input
|
||||||
|
//
|
||||||
|
// This header provides a basic asynchronous line edition mechanic attached to
|
||||||
|
// an ncurses window. It might be possible to use readline directly, but this
|
||||||
|
// is also a good exercise.
|
||||||
|
//---
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <fxlink/defs.h>
|
||||||
|
#include <ncurses.h>
|
||||||
|
|
||||||
|
/* Input field attached to a window. This object only needs to be instantiated
|
||||||
|
once for multiple inputs. */
|
||||||
|
struct fxlink_TUI_input {
|
||||||
|
/* Line contents, NUL-terminated. The buffer might be larger. */
|
||||||
|
char *data;
|
||||||
|
/* Size of contents (not counting the NUL) */
|
||||||
|
int size;
|
||||||
|
/* Allocated size (always ≥ size+1) */
|
||||||
|
int alloc_size;
|
||||||
|
/* Cursor position within string */
|
||||||
|
int cursor;
|
||||||
|
/* Attached ncurses window */
|
||||||
|
WINDOW *win;
|
||||||
|
/* Original cursor position within window at start of input */
|
||||||
|
uint16_t wx, wy;
|
||||||
|
};
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Text manipulation functions
|
||||||
|
//---
|
||||||
|
|
||||||
|
/* Initialize the input at least init_chars characters of content available.
|
||||||
|
Returns false on error. Previous contents are not freed! */
|
||||||
|
bool fxlink_TUI_input_init(struct fxlink_TUI_input *in, WINDOW *win,
|
||||||
|
int init_chars);
|
||||||
|
|
||||||
|
/* Clean up a line and free its contents. */
|
||||||
|
void fxlink_TUI_input_free(struct fxlink_TUI_input *in);
|
||||||
|
|
||||||
|
/* Realloc the line to ensure n characters plus a NUL can be written. */
|
||||||
|
bool fxlink_TUI_input_alloc(struct fxlink_TUI_input *in, int n);
|
||||||
|
|
||||||
|
/* Insert n characters at position p. */
|
||||||
|
bool fxlink_TUI_input_insert(struct fxlink_TUI_input *in, int p,
|
||||||
|
char const *str, int n);
|
||||||
|
|
||||||
|
/* Remove n characters at position p. Returns the number of characters
|
||||||
|
actually removed after bounds checking. */
|
||||||
|
int fxlink_TUI_input_delete(struct fxlink_TUI_input *in, int p, int n);
|
||||||
|
|
||||||
|
//--
|
||||||
|
// Rendering functions
|
||||||
|
//---
|
||||||
|
|
||||||
|
/* Clear the input up to the original cursor position */
|
||||||
|
void fxlink_TUI_input_clear(struct fxlink_TUI_input *in);
|
||||||
|
|
||||||
|
/* Redraw the input (needed after non-appending edits) */
|
||||||
|
void fxlink_TUI_input_redraw(struct fxlink_TUI_input *in);
|
||||||
|
|
||||||
|
/* Clear the screen as with C-l */
|
||||||
|
void fxlink_TUI_input_clearscreen(struct fxlink_TUI_input *in);
|
||||||
|
|
||||||
|
/* getch() for an input (usually called when there *is* input */
|
||||||
|
bool fxlink_TUI_input_getch(struct fxlink_TUI_input *in, WINDOW *logWindow);
|
113
fxlink/include/fxlink/tui/layout.h
Normal file
113
fxlink/include/fxlink/tui/layout.h
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// fxlink.tui.layout: Utility for laying out TUI windows in a flexbox treee
|
||||||
|
//
|
||||||
|
// This module can be used to set up a flexbox tree for window layout in the
|
||||||
|
// TUI. A `struct fxlink_TUI_box` is either an ncurses window, or a flexbox
|
||||||
|
// which arranges its children horizontally or vertically.
|
||||||
|
//
|
||||||
|
// Each box has some geometry settings:
|
||||||
|
// - w and h are initially set to the natural content size (can be 0)
|
||||||
|
// - min_w, min_h, max_w, max_h constrain the range of acceptable sizes
|
||||||
|
// - stretch_x and stretch_y indicate the box's tendency to grow
|
||||||
|
// - strech_force allows stretching beyond max_w/max_h (rarely needed)
|
||||||
|
//
|
||||||
|
// Most of the module is free-standing. Boxes should be created bottom to top,
|
||||||
|
// ie. windows first and then progressively larger groups. Specifying a WINDOW
|
||||||
|
// pointer in windows is optional; it is only used by fxlink_TUI_apply_layout()
|
||||||
|
// to actually configure ncurses windows. The root box of the tree should be
|
||||||
|
// kept in memory as it is used for rendering windows borders on the background
|
||||||
|
// window.
|
||||||
|
//
|
||||||
|
// Space distribution is initiated by a call to box_layout_root() after all the
|
||||||
|
// boxes have been created to form the tree. The root box receives the provided
|
||||||
|
// screen space, and splits it recursively between children. Boxes get their
|
||||||
|
// requested content size (clamped to minimum/maximum size) and any space left
|
||||||
|
// is distributed in proportion with stretch factors. Overflows are possible if
|
||||||
|
// the screen is too small to accommodate for everyone's content size, so the
|
||||||
|
// application of the layout should account for that and clamp to the screen
|
||||||
|
// size again (fxlink_TUI_apply_layout() does that).
|
||||||
|
//---
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <fxlink/defs.h>
|
||||||
|
#include <ncurses.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define FXLINK_TUI_BOX_MAXSIZE 8
|
||||||
|
|
||||||
|
/* Box either holding an ncurses WINDOW or arranging children in a flexbox. */
|
||||||
|
struct fxlink_TUI_box {
|
||||||
|
/* Position and size, excluding the border (which is shared). After layout,
|
||||||
|
x/y is the absolute position and w/h the allocated size. Other geometric
|
||||||
|
settings are only relevant during layout. */
|
||||||
|
uint16_t x, y, w, h;
|
||||||
|
/* Size constraints */
|
||||||
|
uint16_t min_w, min_h;
|
||||||
|
uint16_t max_w, max_h;
|
||||||
|
/* Stretch factor */
|
||||||
|
uint8_t stretch_x, stretch_y;
|
||||||
|
/* Stretch beyond limits */
|
||||||
|
bool stretch_force;
|
||||||
|
/* Box subdivision type: BOX_WINDOW, BOX_HORIZ and BOX_VERT */
|
||||||
|
short type;
|
||||||
|
|
||||||
|
union {
|
||||||
|
/* Valid for type == FXLINK_TUI_BOX_WINDOW */
|
||||||
|
struct {
|
||||||
|
char const *title;
|
||||||
|
WINDOW **win;
|
||||||
|
} window;
|
||||||
|
/* Valid for type == FXLINK_TUI_BOX_{HORIZONTAL,VERTICAL} */
|
||||||
|
struct fxlink_TUI_box *children[FXLINK_TUI_BOX_MAXSIZE];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
/* Box is an ncurses windows. Before layout, the natural size of the
|
||||||
|
content is set in w and h (default 0), size constraints are set in
|
||||||
|
{min,max}_{w,h} (default 0/65535) and stretch rates in stretch_x,
|
||||||
|
stretch_y (default 1) and strech_force (default false). */
|
||||||
|
FXLINK_TUI_BOX_WINDOW,
|
||||||
|
/* Box is a horizontal of vertical flexbox. Before layout, children are
|
||||||
|
specified and they induce a natural content size. Size constraints are
|
||||||
|
stretch rates are specified as for windows. */
|
||||||
|
FXLINK_TUI_BOX_HORIZONTAL,
|
||||||
|
FXLINK_TUI_BOX_VERTICAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Make a window box. The title is used for the border rendering function in
|
||||||
|
the TUI rendering utils. The window pointer is optional and only needed for
|
||||||
|
fxlink_TUI_apply_layout(). */
|
||||||
|
struct fxlink_TUI_box *fxlink_TUI_box_mk_window(char const *title, WINDOW **w);
|
||||||
|
/* Make a vertical box with a fixed list of children */
|
||||||
|
struct fxlink_TUI_box *fxlink_TUI_box_mk_vertical(
|
||||||
|
struct fxlink_TUI_box *child1, ... /*, NULL */);
|
||||||
|
/* Make a horizontal box with a fixed list of children */
|
||||||
|
struct fxlink_TUI_box *fxlink_TUI_box_mk_horizontal(
|
||||||
|
struct fxlink_TUI_box *child1, ... /*, NULL */);
|
||||||
|
|
||||||
|
/* Specify the minimum size, maximum size and stretch rate of a box */
|
||||||
|
void fxlink_TUI_box_minsize(struct fxlink_TUI_box *box, int min_w, int min_h);
|
||||||
|
void fxlink_TUI_box_maxsize(struct fxlink_TUI_box *box, int max_w, int max_h);
|
||||||
|
void fxlink_TUI_box_stretch(struct fxlink_TUI_box *box,
|
||||||
|
int stretch_x, int stretch_y, bool force);
|
||||||
|
|
||||||
|
/* Recursively print box and children starting at specified indent level */
|
||||||
|
void fxlink_TUI_box_print(FILE *fp, struct fxlink_TUI_box const *b, int level);
|
||||||
|
|
||||||
|
/* Layout a root box for the specified available screen space. This accounts
|
||||||
|
for 1-unit borders around and between windows. For a full-screen window tree
|
||||||
|
x/y would be set to 0 and w/h to the screen size, but the tree can also be
|
||||||
|
laid out to occupy only a subset of screen space. */
|
||||||
|
void fxlink_TUI_box_layout(struct fxlink_TUI_box *root_box,
|
||||||
|
int x, int y, int w, int h);
|
||||||
|
|
||||||
|
/* Recursively apply the layout. This function resizes and moves ncurses
|
||||||
|
windows to fit the space allocated in the boxes. Returns false if an ncurses
|
||||||
|
error causes one of the windows to become NULL. */
|
||||||
|
bool fxlink_TUI_apply_layout(struct fxlink_TUI_box *root_box);
|
||||||
|
|
32
fxlink/include/fxlink/tui/render.h
Normal file
32
fxlink/include/fxlink/tui/render.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// fxlink.tui.render: TUI rendering utilities
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <fxlink/tui/layout.h>
|
||||||
|
#include <ncurses.h>
|
||||||
|
|
||||||
|
/* printf to a window. */
|
||||||
|
#define print wprintw
|
||||||
|
/* printf with an ncurses attribute for the whole string. */
|
||||||
|
void aprint(WINDOW *win, int attr, char const *format, ...);
|
||||||
|
/* printf with an <fxlink/defs.h> format for the whole string. */
|
||||||
|
void fprint(WINDOW *win, int display_fmt, char const *format, ...);
|
||||||
|
|
||||||
|
/* Hard-coded color scheme */
|
||||||
|
#define FMT_FILENAME FMT_CYAN
|
||||||
|
#define FMT_SIZE FMT_MAGENTA
|
||||||
|
#define FMT_HEADER (FMT_CYAN | FMT_ITALIC)
|
||||||
|
#define FMT_BGSELECTED (FMT_BGWHITE | FMT_BLACK)
|
||||||
|
|
||||||
|
/* Translate <fxlink/defs.h> text format into ncurses attributes. */
|
||||||
|
int fmt_to_ncurses_attr(int display_fmt);
|
||||||
|
|
||||||
|
/* Recursively render borders around a box and its children. */
|
||||||
|
void fxlink_TUI_render_borders(struct fxlink_TUI_box const *box);
|
||||||
|
|
||||||
|
/* Render the window titles of all windows in the tree. */
|
||||||
|
void fxlink_TUI_render_titles(struct fxlink_TUI_box const *box);
|
|
@ -1,254 +0,0 @@
|
||||||
#include "config.h"
|
|
||||||
#include "fxlink.h"
|
|
||||||
#include "util.h"
|
|
||||||
#include "properties.h"
|
|
||||||
#include "filter.h"
|
|
||||||
#include "protocol.h"
|
|
||||||
#include "usb.h"
|
|
||||||
#include "png.h"
|
|
||||||
#include "sdl2.h"
|
|
||||||
|
|
||||||
#include <libusb.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
/* Video capture trackers, to avoid spamming terminal with messages */
|
|
||||||
static int last_message_was_video = 0;
|
|
||||||
static int video_frame_count = 0;
|
|
||||||
|
|
||||||
static bool message_new(message_t *msg, usb_fxlink_header_t const *h)
|
|
||||||
{
|
|
||||||
int version_major = (h->version >> 8) & 0xff;
|
|
||||||
int version_minor = (h->version) & 0xff;
|
|
||||||
|
|
||||||
if(!strncmp(h->application,"fxlink",16) && !strncmp(h->type,"video",16)) {
|
|
||||||
if(last_message_was_video)
|
|
||||||
fprintf(stderr, "\r");
|
|
||||||
last_message_was_video = 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if(last_message_was_video)
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
last_message_was_video = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!options.quiet) {
|
|
||||||
fprintf(stderr, "New message (v%d.%d): application '%.16s', type "
|
|
||||||
"'%.16s', size %d bytes", version_major, version_minor,
|
|
||||||
h->application, h->type, h->size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(last_message_was_video)
|
|
||||||
fprintf(stderr, " [video frame #%d]", ++video_frame_count);
|
|
||||||
else if(!options.quiet)
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
|
|
||||||
msg->output = malloc(h->size);
|
|
||||||
if(!msg->output) {
|
|
||||||
err("cannot allocate memory for message of %d bytes", h->size);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
msg->header = *h;
|
|
||||||
msg->size_read = 0;
|
|
||||||
msg->valid = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void message_finish(message_t *msg)
|
|
||||||
{
|
|
||||||
char const *path = ".";
|
|
||||||
|
|
||||||
if(!strncmp(msg->header.application, "fxlink", 16)) {
|
|
||||||
if(!strncmp(msg->header.type, "image", 16)) {
|
|
||||||
usb_fxlink_image_t *img = (void *)msg->output;
|
|
||||||
char *filename = gen_file_name(path, msg->header.type, "png");
|
|
||||||
|
|
||||||
uint8_t **row_pointers = fxlink_protocol_decode_image(msg);
|
|
||||||
fxlink_png_save(row_pointers, img->width, img->height, filename);
|
|
||||||
|
|
||||||
printf("Saved image (%dx%d, format=%d) to '%s'\n",
|
|
||||||
img->width, img->height, img->pixel_format, filename);
|
|
||||||
free(row_pointers);
|
|
||||||
free(filename);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!strncmp(msg->header.type, "text", 16)) {
|
|
||||||
if(!options.quiet)
|
|
||||||
printf("------------------\n");
|
|
||||||
fwrite(msg->output, 1, msg->header.size, stdout);
|
|
||||||
if(!options.quiet) {
|
|
||||||
if(msg->output[msg->header.size - 1] != '\n') printf("\n");
|
|
||||||
printf("------------------\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(options.log_file)
|
|
||||||
fwrite(msg->output, 1, msg->header.size, options.log_file);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!strncmp(msg->header.type, "video", 16)) {
|
|
||||||
usb_fxlink_image_t *img = (void *)msg->output;
|
|
||||||
uint8_t **row_pointers = fxlink_protocol_decode_image(msg);
|
|
||||||
|
|
||||||
#ifndef FXLINK_DISABLE_SDL2
|
|
||||||
sdl2_stream(row_pointers, img->width, img->height);
|
|
||||||
#else
|
|
||||||
warn("SDL2 support disabled, skipping video frame!");
|
|
||||||
#endif
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static char combined_type[33];
|
|
||||||
snprintf(combined_type, 33, "%.16s-%.16s", msg->header.application,
|
|
||||||
msg->header.type);
|
|
||||||
/* Default to saving to a blob */
|
|
||||||
char *filename = gen_file_name(path, combined_type, "bin");
|
|
||||||
FILE *fp = fopen(filename, "wb");
|
|
||||||
if(!fp) {
|
|
||||||
err("could not save to '%s': %m", filename);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fwrite(msg->output, 1, msg->header.size, fp);
|
|
||||||
fclose(fp);
|
|
||||||
fprintf(stderr, "Saved as blob to '%s'\n", filename);
|
|
||||||
free(filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void message_output(message_t *msg, void *buffer, int size)
|
|
||||||
{
|
|
||||||
int data_left = msg->header.size - msg->size_read;
|
|
||||||
|
|
||||||
if(size > data_left) {
|
|
||||||
err("Too much data in message, dropping %d bytes", size - data_left);
|
|
||||||
size = data_left;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(msg->output + msg->size_read, buffer, size);
|
|
||||||
|
|
||||||
msg->size_read += size;
|
|
||||||
if(msg->size_read >= msg->header.size) {
|
|
||||||
bool is_video = !strncmp(msg->header.application, "fxlink", 16) &&
|
|
||||||
!strncmp(msg->header.type, "video", 16);
|
|
||||||
if(!is_video && !options.quiet)
|
|
||||||
fprintf(stderr, "Successfully read %d bytes\n", msg->size_read);
|
|
||||||
message_finish(msg);
|
|
||||||
msg->valid = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main_interactive(filter_t *filter, delay_t *delay, libusb_context *context)
|
|
||||||
{
|
|
||||||
libusb_device *dev = NULL;
|
|
||||||
libusb_device_handle *dh = NULL;
|
|
||||||
|
|
||||||
/* Wait for a device to be connected */
|
|
||||||
filter_clean_libusb(filter);
|
|
||||||
int rc = usb_unique_wait(filter, delay, context, &dev);
|
|
||||||
|
|
||||||
if(rc == FILTER_NONE) {
|
|
||||||
printf("No device found.\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else if(rc == FILTER_MULTIPLE) {
|
|
||||||
printf("Multiple devices found, ambiguous!\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if((rc = libusb_open(dev, &dh))) {
|
|
||||||
rc = libusb_err(rc, "cannot open device %s", usb_id(dev));
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Don't detach kernel drivers to avoid breaking the Mass Storage
|
|
||||||
communications if fxlink is ever started while the native LINK
|
|
||||||
application is running! */
|
|
||||||
libusb_set_auto_detach_kernel_driver(dh, false);
|
|
||||||
|
|
||||||
if((rc = libusb_claim_interface(dh, 0))) {
|
|
||||||
rc = libusb_err(rc, "cannot claim interface on %s", usb_id(dev));
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Connected to %s, starting test.\n", usb_id(dev));
|
|
||||||
|
|
||||||
/* This buffer is used to receive messages; if the header is not complete
|
|
||||||
it is left in the buffer, hence the extra room */
|
|
||||||
__attribute__((aligned(4)))
|
|
||||||
static uint8_t buffer[2048 + sizeof(usb_fxlink_header_t)] = { 0 };
|
|
||||||
/* Amount of data in the buffer */
|
|
||||||
int buffer_size = 0;
|
|
||||||
|
|
||||||
/* Current message */
|
|
||||||
message_t msg = { 0 };
|
|
||||||
|
|
||||||
while(1)
|
|
||||||
{
|
|
||||||
#ifndef FXLINK_DISABLE_SDL2
|
|
||||||
sdl2_tick();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int transferred = -1;
|
|
||||||
rc = libusb_bulk_transfer(dh, 0x81, buffer + buffer_size, 2048,
|
|
||||||
&transferred, 500);
|
|
||||||
|
|
||||||
if(rc == LIBUSB_ERROR_NO_DEVICE) {
|
|
||||||
if(last_message_was_video)
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
printf("Disconnected, leaving.\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if(rc && rc != LIBUSB_ERROR_TIMEOUT) {
|
|
||||||
rc = libusb_err(rc, "bulk transfer failed on %s", usb_id(dev));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(transferred <= 0) continue;
|
|
||||||
|
|
||||||
buffer_size += transferred;
|
|
||||||
|
|
||||||
/* If there is an unfinished message, continue working on it */
|
|
||||||
if(msg.valid) {
|
|
||||||
message_output(&msg, buffer, buffer_size);
|
|
||||||
buffer_size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If the header is not yet fully transmitted, wait */
|
|
||||||
usb_fxlink_header_t *h = (void *)buffer;
|
|
||||||
if(buffer_size < (int)sizeof *h) continue;
|
|
||||||
|
|
||||||
/* Handle a new message */
|
|
||||||
if(h->version == 0x00000100) {
|
|
||||||
int data_size = buffer_size - sizeof *h;
|
|
||||||
|
|
||||||
if(!message_new(&msg, h))
|
|
||||||
printf("dropping %d bytes\n", data_size);
|
|
||||||
else
|
|
||||||
message_output(&msg, buffer + sizeof *h, data_size);
|
|
||||||
|
|
||||||
buffer_size = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
err("invalid header, dropping %d bytes", transferred);
|
|
||||||
buffer_size = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Save last unfinished message */
|
|
||||||
if(buffer_size > 0) {
|
|
||||||
printf("%d bytes not collected dropped\n", buffer_size);
|
|
||||||
}
|
|
||||||
rc = 0;
|
|
||||||
|
|
||||||
end:
|
|
||||||
if(dh) {
|
|
||||||
libusb_release_interface(dh, 0);
|
|
||||||
libusb_close(dh);
|
|
||||||
}
|
|
||||||
if(dev) libusb_unref_device(dev);
|
|
||||||
return rc;
|
|
||||||
}
|
|
93
fxlink/logging.c
Normal file
93
fxlink/logging.c
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
|
||||||
|
#include <fxlink/logging.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
static fxlink_log_handler_t *log_handler = fxlink_log_stderr;
|
||||||
|
|
||||||
|
void fxlink_log_stderr(int display_fmt, char const *str)
|
||||||
|
{
|
||||||
|
fputs(fmt_to_ANSI(display_fmt), stderr);
|
||||||
|
fputs(str, stderr);
|
||||||
|
fputs(fmt_to_ANSI(0), stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_log_set_handler(fxlink_log_handler_t *handler)
|
||||||
|
{
|
||||||
|
log_handler = handler ? handler : fxlink_log_stderr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_libusb_log(libusb_context *ctx, enum libusb_log_level level,
|
||||||
|
char const *str)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
(void)level;
|
||||||
|
log_handler(0, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_log_grab_libusb_logs(void)
|
||||||
|
{
|
||||||
|
libusb_set_log_cb(NULL, handle_libusb_log, LIBUSB_LOG_CB_GLOBAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int flogv(int display_fmt, char const *fmt, va_list args)
|
||||||
|
{
|
||||||
|
char *str = NULL;
|
||||||
|
vasprintf(&str, fmt, args);
|
||||||
|
if(str) {
|
||||||
|
log_handler(display_fmt, str);
|
||||||
|
free(str);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define LOG_VA_ARGS(FMT, STMTS) do { \
|
||||||
|
va_list _args; \
|
||||||
|
va_start(_args, FMT); \
|
||||||
|
STMTS; \
|
||||||
|
va_end(_args); \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
int log_(char const *fmt, ...)
|
||||||
|
{
|
||||||
|
LOG_VA_ARGS(fmt, flogv(0, fmt, _args));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int flog(int display_fmt, char const *fmt, ...)
|
||||||
|
{
|
||||||
|
LOG_VA_ARGS(fmt, flogv(display_fmt, fmt, _args));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int elog(char const *fmt, ...)
|
||||||
|
{
|
||||||
|
flog(FMT_RED, "error: ");
|
||||||
|
LOG_VA_ARGS(fmt, flogv(0, fmt, _args));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int wlog(char const *fmt, ...)
|
||||||
|
{
|
||||||
|
flog(FMT_YELLOW, "warning: ");
|
||||||
|
LOG_VA_ARGS(fmt, flogv(0, fmt, _args));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int hlog(char const *fmt, ...)
|
||||||
|
{
|
||||||
|
time_t t = time(NULL);
|
||||||
|
struct tm tm;
|
||||||
|
localtime_r(&t, &tm);
|
||||||
|
|
||||||
|
flog(FMT_WHITE | FMT_DIM, "[%02d:%02d] ", tm.tm_hour, tm.tm_min);
|
||||||
|
LOG_VA_ARGS(fmt, flogv(FMT_YELLOW | FMT_DIM, fmt, _args));
|
||||||
|
flog(FMT_YELLOW | FMT_DIM, ": ");
|
||||||
|
return 1;
|
||||||
|
}
|
235
fxlink/main.c
235
fxlink/main.c
|
@ -1,9 +1,12 @@
|
||||||
#include "config.h"
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
|
||||||
#include "fxlink.h"
|
#include "fxlink.h"
|
||||||
#include "util.h"
|
#include <fxlink/filter.h>
|
||||||
#include "properties.h"
|
#include <fxlink/logging.h>
|
||||||
#include "filter.h"
|
|
||||||
#include "usb.h"
|
|
||||||
|
|
||||||
#include <libusb.h>
|
#include <libusb.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
|
@ -11,71 +14,64 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <locale.h>
|
||||||
int main_test(libusb_device *device, libusb_context *context);
|
|
||||||
|
|
||||||
static const char *help_string =
|
static const char *help_string =
|
||||||
"usage: %1$s -l [options...]\n"
|
"usage: %1$s (-l|-b|-t) [General options]\n"
|
||||||
" %1$s -b [options...]\n"
|
" %1$s -i [-r] [--fxlink-log[=<FILE>]] [General options]\n"
|
||||||
" %1$s -s [options...] <FILES...>\n"
|
" %1$s -p <FILE> [General options]\n"
|
||||||
" %1$s --test\n"
|
" %1$s -s <FILES>... [General options]\n"
|
||||||
"\n"
|
"\n"
|
||||||
"fxlink interacts with CASIO calculators of the fx-9860G and fx-CG 50 series\n"
|
"fxlink interacts with CASIO calculators of the fx and fx-CG families over\n"
|
||||||
"over the USB port, through mass storage and custom USB protocols. Depending\n"
|
"the USB port, using libusb. It can also transfer files for Mass Storage\n"
|
||||||
"on the mode, fxlink uses libusb (for discovery and USB communication)or\n"
|
"calculators using UDisks2.\n"
|
||||||
"the UDisks2 library (to mount and use Mass Storage devices).\n"
|
|
||||||
"\n"
|
"\n"
|
||||||
"Operating modes:\n"
|
"Standard (libusb) modes:\n"
|
||||||
" -l, --list List detected calculators on the USB ports (libusb)\n"
|
" -l, --list List detected calculators on the USB ports\n"
|
||||||
" -b, --blocks List detected Mass Storage filesystems (udisks2)\n"
|
" -i, --interactive Messaging with a gint add-in (calc -> PC only)\n"
|
||||||
" -s, --send Send a file to a Mass Storage calculator (udisks2)\n"
|
" -t, --tui TUI interactive mode\n"
|
||||||
" -i, --interactive Interactive messaging with a gint add-in (libusb)\n"
|
" -p, --push Push a .bin file to the Add-In Push app\n"
|
||||||
" -p, --push Push a .bin file to the Add-In Push app (libusb)\n"
|
"\n"
|
||||||
|
"Mass Storage (UDisks2) modes:\n"
|
||||||
|
" -b, --blocks List detected Mass Storage calculators\n"
|
||||||
|
" -s, --send Send a file to a Mass Storage calc and unmount it\n"
|
||||||
"\n"
|
"\n"
|
||||||
"General options:\n"
|
"General options:\n"
|
||||||
" -w DELAY Wait up to this many seconds for a calculator to\n"
|
" -v, --verbose Increased verbosity (mostly in -i/-t)\n"
|
||||||
" connect. If DELAY is unspecified, wait indefinitely.\n"
|
" -w <SECONDS> Wait this many seconds for a calculator to connect\n"
|
||||||
" -f FILTER Filter which calculators can be detected and used\n"
|
" -w Wait indefinitely for a calculator to connect\n"
|
||||||
" --libusb-log=LEVEL libusb log level: NONE, ERROR, WARNING, INFO, DEBUG\n"
|
" -f <FILTER> Filter which calculators we connect to (see below)\n"
|
||||||
" --fxlink-log[=FILE] Log fxlink text messages into FILE (append mode). If\n"
|
" --libusb-log=LEVEL libusb log level (NONE, ERROR, WARNING, INFO, DEBUG)\n"
|
||||||
" FILE is missing, a timestamp-name will be generated.\n"
|
"\n"
|
||||||
" -q, --quiet Quite mode (minimum verbosity)\n"
|
"Mode-specific options:\n"
|
||||||
" -u, --unmount In -s mode, always unmount the disk, even if it was\n"
|
" --fxlink-log[=FILE] -i: Append fxlink text messages to FILE. Without\n"
|
||||||
" mounted by someone else\n"
|
" argument, a unique name is generated.\n"
|
||||||
" -r, --repeat In interactive mode, reconnect infinitely if the\n"
|
" -r, --repeat -i: Reconnect if the calc disconnects (implies -w)\n"
|
||||||
" calculator disconnects (implies -w)\n"
|
|
||||||
"\n"
|
"\n"
|
||||||
"Device filters:\n"
|
"Device filters:\n"
|
||||||
" A device filter is a comma-separated list of properties that a device has\n"
|
" A device filter narrows down what devices we list or connect to by\n"
|
||||||
" to match in order to be listed or used, such as 'p7,serial=00000001'.\n"
|
" requiring a list of properties, such as \"p7,serial=00000001\". The\n"
|
||||||
" Several filters can be separated with a semicolon, in which case a device\n"
|
" following properties can be tested:\n"
|
||||||
" will be considered as long as it matches one of the filters. For example,\n"
|
" - p7 Protocol 7 calcs (all fx models that use FA-124)\n"
|
||||||
" 'p7 ; mass_storage,serial=IGQcGRe9'.\n"
|
" - mass_storage Mass Storage calcs (fx-CG models and the G-III)\n"
|
||||||
"\n"
|
" - series_cg fx-CG models [udisks2 only]\n"
|
||||||
" The following properties are defined; the libraries in which each can be\n"
|
" - series_g3 G-III models [udisks2 only]\n"
|
||||||
" detected and used is indicated in brackets.\n"
|
" - serial=<SERIAL> This serial number (needs write access in libusb)\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";
|
|
||||||
|
|
||||||
/* Global options */
|
/* Global options */
|
||||||
struct fxlink_options options;
|
struct fxlink_options options;
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int rc=1, mode=0, error=0, option=0, loglevel=LIBUSB_LOG_LEVEL_ERROR;
|
int rc=1, mode=0, error=0, option=0, loglevel=LIBUSB_LOG_LEVEL_WARNING;
|
||||||
delay_t delay = delay_seconds(0);
|
delay_t delay = delay_seconds(0);
|
||||||
filter_t *filter = NULL;
|
struct fxlink_filter *filter = NULL;
|
||||||
bool repeat = false;
|
bool repeat = false;
|
||||||
|
|
||||||
options.quiet = false;
|
|
||||||
options.force_unmount = false;
|
|
||||||
options.log_file = NULL;
|
options.log_file = NULL;
|
||||||
|
options.verbose = false;
|
||||||
|
|
||||||
|
setlocale(LC_ALL, "");
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// Command-line argument parsing
|
// Command-line argument parsing
|
||||||
|
@ -88,16 +84,20 @@ int main(int argc, char **argv)
|
||||||
{ "blocks", no_argument, NULL, 'b' },
|
{ "blocks", no_argument, NULL, 'b' },
|
||||||
{ "send", no_argument, NULL, 's' },
|
{ "send", no_argument, NULL, 's' },
|
||||||
{ "interactive", no_argument, NULL, 'i' },
|
{ "interactive", no_argument, NULL, 'i' },
|
||||||
|
{ "tui", no_argument, NULL, 't' },
|
||||||
{ "push", no_argument, NULL, 'p' },
|
{ "push", no_argument, NULL, 'p' },
|
||||||
{ "libusb-log", required_argument, NULL, LIBUSB_LOG },
|
{ "libusb-log", required_argument, NULL, LIBUSB_LOG },
|
||||||
{ "quiet", no_argument, NULL, 'q' },
|
|
||||||
{ "fxlink-log", optional_argument, NULL, LOG_TO_FILE },
|
{ "fxlink-log", optional_argument, NULL, LOG_TO_FILE },
|
||||||
{ "unmount", no_argument, NULL, 'u' },
|
|
||||||
{ "repeat", no_argument, NULL, 'r' },
|
{ "repeat", no_argument, NULL, 'r' },
|
||||||
|
{ "verbose", no_argument, NULL, 'v' },
|
||||||
|
/* Deprecated options ignored for compatibility: */
|
||||||
|
{ "quiet", no_argument, NULL, 'q' },
|
||||||
|
{ "unmount", no_argument, NULL, 'u' },
|
||||||
|
{ NULL },
|
||||||
};
|
};
|
||||||
|
|
||||||
while(option >= 0 && option != '?')
|
while(option >= 0 && option != '?')
|
||||||
switch((option = getopt_long(argc, argv, "hlbsipquf:w::r", longs, NULL)))
|
switch((option = getopt_long(argc, argv, "hlbsitpquf:w::rv", longs, NULL)))
|
||||||
{
|
{
|
||||||
case 'h':
|
case 'h':
|
||||||
fprintf(stderr, help_string, argv[0]);
|
fprintf(stderr, help_string, argv[0]);
|
||||||
|
@ -106,6 +106,7 @@ int main(int argc, char **argv)
|
||||||
case 'b':
|
case 'b':
|
||||||
case 's':
|
case 's':
|
||||||
case 'i':
|
case 'i':
|
||||||
|
case 't':
|
||||||
case 'p':
|
case 'p':
|
||||||
mode = option;
|
mode = option;
|
||||||
break;
|
break;
|
||||||
|
@ -124,20 +125,25 @@ int main(int argc, char **argv)
|
||||||
"NONE, ERROR, WARNING, INFO or DEBUG\n", optarg);
|
"NONE, ERROR, WARNING, INFO or DEBUG\n", optarg);
|
||||||
break;
|
break;
|
||||||
case 'q':
|
case 'q':
|
||||||
options.quiet = true;
|
/* Ignored: -q, --quiet used to control some messages but is now
|
||||||
|
supplanted by not setting -v */
|
||||||
break;
|
break;
|
||||||
case 'u':
|
case 'u':
|
||||||
options.force_unmount = true;
|
/* Ignored: -u, --unmount used to force unmounting filesystems after -s
|
||||||
|
(which is now the only behavior) */
|
||||||
break;
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
repeat = true;
|
repeat = true;
|
||||||
delay = delay_infinite();
|
delay = delay_infinite();
|
||||||
break;
|
break;
|
||||||
|
case 'v':
|
||||||
|
options.verbose = true;
|
||||||
|
break;
|
||||||
case LOG_TO_FILE:
|
case LOG_TO_FILE:
|
||||||
if(optarg)
|
if(optarg)
|
||||||
options.log_file = fopen(optarg, "a");
|
options.log_file = fopen(optarg, "a");
|
||||||
else {
|
else {
|
||||||
char *name = gen_file_name(".", "logfile", "log");
|
char *name = fxlink_gen_file_name(".", "logfile", ".log");
|
||||||
printf("--fxlink-log will output in '%s'\n", name);
|
printf("--fxlink-log will output in '%s'\n", name);
|
||||||
options.log_file = fopen(name, "a");
|
options.log_file = fopen(name, "a");
|
||||||
free(name);
|
free(name);
|
||||||
|
@ -151,25 +157,25 @@ int main(int argc, char **argv)
|
||||||
char *end;
|
char *end;
|
||||||
int seconds = strtol(optarg, &end, 10);
|
int seconds = strtol(optarg, &end, 10);
|
||||||
if(seconds < 0 || *end != 0) {
|
if(seconds < 0 || *end != 0) {
|
||||||
error = err("invalid delay '%s'\n", optarg);
|
error = elog("invalid delay '%s'\n", optarg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
delay = delay_seconds(seconds);
|
delay = delay_seconds(seconds);
|
||||||
break;
|
break;
|
||||||
case 'f':
|
case 'f':
|
||||||
filter = filter_parse(optarg);
|
filter = fxlink_filter_parse(optarg);
|
||||||
break;
|
break;
|
||||||
case '?':
|
case '?':
|
||||||
error = 1;
|
error = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mode == 's' && optind == argc)
|
if(mode == 's' && optind == argc)
|
||||||
error = err("send mode requires additional arguments (file names)");
|
error = elog("send mode requires additional arguments (file names)\n");
|
||||||
|
|
||||||
if(mode == 'p' && optind == argc)
|
if(mode == 'p' && optind == argc)
|
||||||
error = err("push mode requires a file name");
|
error = elog("push mode requires a file name\n");
|
||||||
if(mode == 'p' && optind < argc-1)
|
if(mode == 'p' && optind < argc-1)
|
||||||
error = err("push mode only accepts one file name");
|
error = elog("push mode only accepts one file name\n");
|
||||||
|
|
||||||
/* No arguments or bad arguments */
|
/* No arguments or bad arguments */
|
||||||
if(error)
|
if(error)
|
||||||
|
@ -179,6 +185,10 @@ int main(int argc, char **argv)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Default filter */
|
||||||
|
if(filter == NULL)
|
||||||
|
filter = calloc(1, sizeof *filter);
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// libusb initialization
|
// libusb initialization
|
||||||
//---
|
//---
|
||||||
|
@ -186,10 +196,11 @@ int main(int argc, char **argv)
|
||||||
libusb_context *context = NULL;
|
libusb_context *context = NULL;
|
||||||
|
|
||||||
/* Initialize libusb for corresponding modes */
|
/* Initialize libusb for corresponding modes */
|
||||||
if(mode == 'l' || mode == 'i' || mode == 'p') {
|
if(mode == 'l' || mode == 'i' || mode == 't' || mode == 'p') {
|
||||||
if((rc = libusb_init(&context)))
|
if((rc = libusb_init(&context)))
|
||||||
return libusb_err(rc, "error initializing libusb");
|
return elog_libusb(rc, "error initializing libusb");
|
||||||
libusb_set_option(context, LIBUSB_OPTION_LOG_LEVEL, loglevel);
|
libusb_set_option(context, LIBUSB_OPTION_LOG_LEVEL, loglevel);
|
||||||
|
fxlink_log_grab_libusb_logs();
|
||||||
}
|
}
|
||||||
|
|
||||||
//---
|
//---
|
||||||
|
@ -203,14 +214,14 @@ int main(int argc, char **argv)
|
||||||
#ifndef FXLINK_DISABLE_UDISKS2
|
#ifndef FXLINK_DISABLE_UDISKS2
|
||||||
rc = main_blocks(filter, &delay);
|
rc = main_blocks(filter, &delay);
|
||||||
#else
|
#else
|
||||||
rc = err("this fxlink was built without UDisks2; -b is disabled");
|
rc = elog("this fxlink was built without UDisks2; -b is disabled");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else if(mode == 's') {
|
else if(mode == 's') {
|
||||||
#ifndef FXLINK_DISABLE_UDISKS2
|
#ifndef FXLINK_DISABLE_UDISKS2
|
||||||
rc = main_send(filter, &delay, argv + optind);
|
rc = main_send(filter, &delay, argv + optind);
|
||||||
#else
|
#else
|
||||||
rc = err("this fxlink was built without UDisks2; -s is disabled");
|
rc = elog("this fxlink was built without UDisks2; -s is disabled");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else if(mode == 'i') {
|
else if(mode == 'i') {
|
||||||
|
@ -219,99 +230,17 @@ int main(int argc, char **argv)
|
||||||
}
|
}
|
||||||
while(repeat);
|
while(repeat);
|
||||||
}
|
}
|
||||||
|
else if(mode == 't') {
|
||||||
|
rc = main_tui_interactive(context);
|
||||||
|
}
|
||||||
else if(mode == 'p') {
|
else if(mode == 'p') {
|
||||||
rc = main_push(filter, &delay, context, argv + optind);
|
rc = main_push(filter, &delay, context, argv + optind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fxlink_filter_free(filter);
|
||||||
if(context)
|
if(context)
|
||||||
libusb_exit(context);
|
libusb_exit(context);
|
||||||
if(options.log_file)
|
if(options.log_file)
|
||||||
fclose(options.log_file);
|
fclose(options.log_file);
|
||||||
return rc;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
//---
|
|
||||||
// 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
|
|
||||||
|
|
168
fxlink/modes/interactive.c
Normal file
168
fxlink/modes/interactive.c
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
|
||||||
|
#include "../fxlink.h"
|
||||||
|
#include <fxlink/filter.h>
|
||||||
|
#include <fxlink/logging.h>
|
||||||
|
#include <fxlink/protocol.h>
|
||||||
|
#include <fxlink/devices.h>
|
||||||
|
#include <fxlink/tooling/libpng.h>
|
||||||
|
#include <fxlink/tooling/sdl2.h>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static void handle_new_message(struct fxlink_device *fdev,
|
||||||
|
struct fxlink_message *msg)
|
||||||
|
{
|
||||||
|
char const *path = ".";
|
||||||
|
|
||||||
|
if(fxlink_message_is_fxlink_image(msg)) {
|
||||||
|
struct fxlink_message_image_header *img = msg->data;
|
||||||
|
char *filename = fxlink_gen_file_name(path, msg->type, ".png");
|
||||||
|
|
||||||
|
struct fxlink_message_image_raw *raw =
|
||||||
|
fxlink_message_image_decode(msg);
|
||||||
|
if(raw) {
|
||||||
|
fxlink_libpng_save_raw(raw, filename);
|
||||||
|
fxlink_message_image_raw_free(raw);
|
||||||
|
hlog("calculators %s", fxlink_device_id(fdev));
|
||||||
|
log_("saved image (%dx%d, format=%d) to '%s'\n",
|
||||||
|
img->width, img->height, img->pixel_format, filename);
|
||||||
|
}
|
||||||
|
free(filename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fxlink_message_is_fxlink_text(msg)) {
|
||||||
|
char const *str = msg->data;
|
||||||
|
|
||||||
|
if(options.verbose)
|
||||||
|
printf("------------------\n");
|
||||||
|
fwrite(str, 1, msg->size, stdout);
|
||||||
|
if(str[msg->size - 1] != '\n') {
|
||||||
|
if(!options.verbose)
|
||||||
|
printf("\e[30;47m%%\e[0m");
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
if(options.verbose) {
|
||||||
|
printf("------------------\n");
|
||||||
|
}
|
||||||
|
if(options.verbose) {
|
||||||
|
for(size_t i = 0; i < msg->size; i++) {
|
||||||
|
printf(" %02x", str[i]);
|
||||||
|
if((i & 15) == 15 || i == msg->size - 1)
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(options.log_file)
|
||||||
|
fwrite(str, 1, msg->size, options.log_file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fxlink_message_is_fxlink_video(msg)) {
|
||||||
|
struct fxlink_message_image_raw *raw =
|
||||||
|
fxlink_message_image_decode(msg);
|
||||||
|
if(raw) {
|
||||||
|
fxlink_sdl2_display_raw(raw);
|
||||||
|
fxlink_message_image_raw_free(raw);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Default to saving to a blob */
|
||||||
|
static char combined_type[48];
|
||||||
|
sprintf(combined_type, "%.16s-%.16s", msg->application, msg->type);
|
||||||
|
|
||||||
|
char *filename = fxlink_gen_file_name(path, combined_type, ".bin");
|
||||||
|
FILE *fp = fopen(filename, "wb");
|
||||||
|
if(!fp) {
|
||||||
|
elog("could not save to '%s': %m\n", filename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite(msg->data, 1, msg->size, fp);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
log_("saved blob to '%s'\n", filename);
|
||||||
|
free(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main_interactive(struct fxlink_filter *filter, delay_t *delay,
|
||||||
|
libusb_context *ctx)
|
||||||
|
{
|
||||||
|
/* Wait for a device to be connected */
|
||||||
|
fxlink_filter_clean_libusb(filter);
|
||||||
|
filter->intf_fxlink = true;
|
||||||
|
|
||||||
|
struct fxlink_device *fdev = fxlink_device_find_wait(ctx, filter, delay);
|
||||||
|
if(!fdev) {
|
||||||
|
printf("No device found.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if(!fxlink_device_claim_fxlink(fdev)) {
|
||||||
|
fxlink_device_cleanup(fdev);
|
||||||
|
free(fdev);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
hlog("interactive");
|
||||||
|
log_("connected to %s\n", fxlink_device_id(fdev));
|
||||||
|
|
||||||
|
/* Buffer used to receive messages */
|
||||||
|
static uint8_t buffer[2048];
|
||||||
|
/* Current message */
|
||||||
|
struct fxlink_transfer *tr = NULL;
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
fxlink_sdl2_handle_events();
|
||||||
|
|
||||||
|
int transferred = -1;
|
||||||
|
int rc = libusb_bulk_transfer(fdev->dh, fdev->comm->ep_bulk_IN, buffer,
|
||||||
|
sizeof buffer, &transferred, 500 /* ms */);
|
||||||
|
|
||||||
|
if(rc == LIBUSB_ERROR_NO_DEVICE) {
|
||||||
|
hlog("interactive");
|
||||||
|
log_("disconnected, leaving\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if(rc && rc != LIBUSB_ERROR_TIMEOUT) {
|
||||||
|
elog_libusb(rc, "bulk transfer failed on %s",
|
||||||
|
fxlink_device_id(fdev));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(transferred <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Either start a new message or continue an unfinished one */
|
||||||
|
if(tr == NULL)
|
||||||
|
tr = fxlink_transfer_make_IN(buffer, transferred);
|
||||||
|
else
|
||||||
|
fxlink_transfer_receive(tr, buffer, transferred);
|
||||||
|
|
||||||
|
if(tr && fxlink_transfer_complete(tr)) {
|
||||||
|
struct fxlink_message *msg = fxlink_transfer_finish_IN(tr);
|
||||||
|
if(msg) {
|
||||||
|
handle_new_message(fdev, msg);
|
||||||
|
fxlink_message_free(msg, true);
|
||||||
|
}
|
||||||
|
tr = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Warning for unfinished transfer */
|
||||||
|
if(tr) {
|
||||||
|
wlog("unfinished transfer interrupted by disconnection\n");
|
||||||
|
// TODO: Proper way to free a transfer without finishing it
|
||||||
|
free(tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fxlink_device_cleanup(fdev);
|
||||||
|
free(fdev);
|
||||||
|
return 0;
|
||||||
|
}
|
104
fxlink/modes/list.c
Normal file
104
fxlink/modes/list.c
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
|
||||||
|
#include "../fxlink.h"
|
||||||
|
#include <fxlink/devices.h>
|
||||||
|
#include <fxlink/filter.h>
|
||||||
|
#include <fxlink/logging.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static int print_devices(struct fxlink_device_list const *list,
|
||||||
|
struct fxlink_filter const *filter)
|
||||||
|
{
|
||||||
|
int total_devices = 0;
|
||||||
|
|
||||||
|
for(int i = 0; i < list->count; i++) {
|
||||||
|
struct fxlink_device const *fdev = &list->devices[i];
|
||||||
|
if(!fdev->calc)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
struct fxlink_filter properties;
|
||||||
|
fxlink_device_get_properties(fdev, &properties);
|
||||||
|
if(!fxlink_filter_match(&properties, filter))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(total_devices > 0)
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
if(fdev->idProduct == 0x6101)
|
||||||
|
printf("fx-9860G series (Protocol 7) calculator\n");
|
||||||
|
else if(fdev->idProduct == 0x6102)
|
||||||
|
printf("fx-CG or G-III series (USB Mass Storage) calculator\n");
|
||||||
|
else
|
||||||
|
printf("Unknown calculator (idProduct: %04x)\n", fdev->idProduct);
|
||||||
|
|
||||||
|
printf(" Device location: Bus %d, Port %d, Device %d\n",
|
||||||
|
libusb_get_bus_number(fdev->dp),
|
||||||
|
libusb_get_port_number(fdev->dp),
|
||||||
|
libusb_get_device_address(fdev->dp));
|
||||||
|
printf(" Identification: idVendor: %04x, idProduct: %04x\n",
|
||||||
|
fdev->idVendor, fdev->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(fdev->dp),
|
||||||
|
libusb_get_port_number(fdev->dp));
|
||||||
|
|
||||||
|
if(fdev->calc->serial)
|
||||||
|
printf(" Serial number: %s\n", fdev->calc->serial);
|
||||||
|
|
||||||
|
printf(" System: %s (%s)\n",
|
||||||
|
fxlink_device_system_string(fdev),
|
||||||
|
fxlink_device_status_string(fdev));
|
||||||
|
|
||||||
|
printf(" Interfaces: ");
|
||||||
|
for(int i = 0; i < fdev->calc->interface_count; i++)
|
||||||
|
printf(" %02x.%02x",
|
||||||
|
fdev->calc->classes[i] >> 8, fdev->calc->classes[i] & 0xff);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
printf(" Properties: ");
|
||||||
|
fxlink_filter_print(stdout, &properties);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
total_devices++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total_devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void discard_logs(int display_fmt, char const *str)
|
||||||
|
{
|
||||||
|
(void)display_fmt;
|
||||||
|
(void)str;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main_list(struct fxlink_filter *filter, delay_t *delay,
|
||||||
|
libusb_context *ctx)
|
||||||
|
{
|
||||||
|
/* Silence all logs for this mode */
|
||||||
|
fxlink_log_set_handler(discard_logs);
|
||||||
|
|
||||||
|
struct fxlink_device_list list;
|
||||||
|
struct timeval zero_tv = { 0 };
|
||||||
|
fxlink_device_list_track(&list, ctx);
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
libusb_handle_events_timeout(ctx, &zero_tv);
|
||||||
|
fxlink_device_list_refresh(&list);
|
||||||
|
|
||||||
|
int n = print_devices(&list, filter);
|
||||||
|
if(n > 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if(delay_cycle(delay)) {
|
||||||
|
printf("No%s device found.\n", filter ? " matching" : "");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fxlink_device_list_stop(&list);
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -1,21 +1,25 @@
|
||||||
#include "config.h"
|
//---------------------------------------------------------------------------//
|
||||||
#include "fxlink.h"
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
#include "util.h"
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
#include "properties.h"
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
#include "filter.h"
|
//---------------------------------------------------------------------------//
|
||||||
#include "usb.h"
|
|
||||||
|
#include "../fxlink.h"
|
||||||
|
#include <fxlink/filter.h>
|
||||||
|
#include <fxlink/logging.h>
|
||||||
|
#include <fxlink/devices.h>
|
||||||
|
|
||||||
#include <libusb.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
int main_push(filter_t *filter, delay_t *delay, libusb_context *context, char** files)
|
int main_push(struct fxlink_filter *filter, delay_t *delay,
|
||||||
|
libusb_context *ctx, char **files)
|
||||||
{
|
{
|
||||||
int rc = 1;
|
int rc = 1;
|
||||||
libusb_device *dev = NULL;
|
struct fxlink_device *fdev = NULL;
|
||||||
libusb_device_handle *dh = NULL;
|
|
||||||
|
|
||||||
|
/* Load binary file */
|
||||||
FILE *fp = fopen(files[0], "rb");
|
FILE *fp = fopen(files[0], "rb");
|
||||||
if (!fp) {
|
if (!fp) {
|
||||||
printf("error: Unable to open file %s\n", files[0]);
|
printf("error: Unable to open file %s\n", files[0]);
|
||||||
|
@ -35,45 +39,36 @@ int main_push(filter_t *filter, delay_t *delay, libusb_context *context, char**
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
|
||||||
/* Wait for a device to be connected */
|
/* Wait for a device to be connected */
|
||||||
filter_clean_libusb(filter);
|
fxlink_filter_clean_libusb(filter);
|
||||||
rc = usb_unique_wait(filter, delay, context, &dev);
|
filter->intf_cesg502 = true;
|
||||||
|
|
||||||
if(rc == FILTER_NONE) {
|
fdev = fxlink_device_find_wait(ctx, filter, delay);
|
||||||
|
if(!fdev) {
|
||||||
printf("No device found.\n");
|
printf("No device found.\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
else if(rc == FILTER_MULTIPLE) {
|
|
||||||
printf("Multiple devices found, ambiguous!\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if((rc = libusb_open(dev, &dh))) {
|
/* The device uses CESG502, so drive the interface manually */
|
||||||
rc = libusb_err(rc, "cannot open device %s", usb_id(dev));
|
if((rc = libusb_claim_interface(fdev->dh, 0))) {
|
||||||
|
hlog("calculators %s", fxlink_device_id(fdev));
|
||||||
|
rc = elog_libusb(rc, "cannot claim interface 0");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Don't detach kernel drivers to avoid breaking the Mass Storage
|
hlog("push");
|
||||||
communications if fxlink is ever started while the native LINK
|
log_("connected to %s\n", fxlink_device_id(fdev));
|
||||||
application is running! */
|
|
||||||
libusb_set_auto_detach_kernel_driver(dh, false);
|
|
||||||
|
|
||||||
if((rc = libusb_claim_interface(dh, 0))) {
|
|
||||||
rc = libusb_err(rc, "cannot claim interface on %s", usb_id(dev));
|
|
||||||
goto end;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Connected to %s\n", usb_id(dev));
|
|
||||||
|
|
||||||
// Wait to receive "USB loader ready" over USB bulk transfer
|
// Wait to receive "USB loader ready" over USB bulk transfer
|
||||||
uint8_t buf[18];
|
uint8_t buf[18];
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
int actual_length;
|
int actual_length;
|
||||||
rc = libusb_bulk_transfer(dh, 0x82, buf, sizeof(buf) - 1, &actual_length, 0);
|
rc = libusb_bulk_transfer(fdev->dh, 0x82, buf, sizeof(buf) - 1,
|
||||||
|
&actual_length, 0);
|
||||||
buf[sizeof(buf) - 1] = 0;
|
buf[sizeof(buf) - 1] = 0;
|
||||||
// if (rc == LIBUSB_ERROR_TIMEOUT) continue;
|
// if (rc == LIBUSB_ERROR_TIMEOUT) continue;
|
||||||
if (rc) {
|
if (rc) {
|
||||||
rc = libusb_err(rc, "cannot receive data: %s", usb_id(dev));
|
rc = elog_libusb(rc, "cannot receive data");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
if (actual_length == 0) continue;
|
if (actual_length == 0) continue;
|
||||||
|
@ -99,9 +94,10 @@ int main_push(filter_t *filter, delay_t *delay, libusb_context *context, char**
|
||||||
sizebuf[1] = (fsize >> 16) & 0xFF;
|
sizebuf[1] = (fsize >> 16) & 0xFF;
|
||||||
sizebuf[2] = (fsize >> 8) & 0xFF;
|
sizebuf[2] = (fsize >> 8) & 0xFF;
|
||||||
sizebuf[3] = fsize & 0xFF;
|
sizebuf[3] = fsize & 0xFF;
|
||||||
rc = libusb_bulk_transfer(dh, 0x01, sizebuf, sizeof(sizebuf), NULL, 0);
|
rc = libusb_bulk_transfer(fdev->dh, 0x01, sizebuf, sizeof(sizebuf), NULL,
|
||||||
|
0);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
rc = libusb_err(rc, "cannot send size: %s", usb_id(dev));
|
rc = elog_libusb(rc, "cannot send size");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,9 +106,10 @@ int main_push(filter_t *filter, delay_t *delay, libusb_context *context, char**
|
||||||
int sent = 0;
|
int sent = 0;
|
||||||
while (sent < fsize) {
|
while (sent < fsize) {
|
||||||
int actual_length;
|
int actual_length;
|
||||||
rc = libusb_bulk_transfer(dh, 0x01, filebuf + sent, fsize - sent, &actual_length, 0);
|
rc = libusb_bulk_transfer(fdev->dh, 0x01, filebuf + sent, fsize - sent,
|
||||||
|
&actual_length, 0);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
rc = libusb_err(rc, "cannot send data: %s", usb_id(dev));
|
rc = elog_libusb(rc, "cannot send data");
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
sent += actual_length;
|
sent += actual_length;
|
||||||
|
@ -120,10 +117,10 @@ int main_push(filter_t *filter, delay_t *delay, libusb_context *context, char**
|
||||||
printf("Sent %d bytes\n", sent);
|
printf("Sent %d bytes\n", sent);
|
||||||
|
|
||||||
end:
|
end:
|
||||||
if(dh) {
|
if(fdev) {
|
||||||
libusb_release_interface(dh, 0);
|
libusb_release_interface(fdev->dh, 0);
|
||||||
libusb_close(dh);
|
fxlink_device_cleanup(fdev);
|
||||||
|
free(fdev);
|
||||||
}
|
}
|
||||||
if(dev) libusb_unref_device(dev);
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
532
fxlink/modes/tui-interactive.c
Normal file
532
fxlink/modes/tui-interactive.c
Normal file
|
@ -0,0 +1,532 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
|
||||||
|
#include "../fxlink.h"
|
||||||
|
#include <fxlink/tui/layout.h>
|
||||||
|
#include <fxlink/tui/render.h>
|
||||||
|
#include <fxlink/tui/input.h>
|
||||||
|
#include <fxlink/devices.h>
|
||||||
|
#include <fxlink/logging.h>
|
||||||
|
#include <fxlink/tooling/libpng.h>
|
||||||
|
#include <fxlink/tooling/sdl2.h>
|
||||||
|
|
||||||
|
#include <libusb.h>
|
||||||
|
#include <ncurses.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
static struct TUIData {
|
||||||
|
/* SIGWINCH flag */
|
||||||
|
bool resize_needed;
|
||||||
|
/* ncurses window panels */
|
||||||
|
WINDOW *wStatus;
|
||||||
|
WINDOW *wLogs;
|
||||||
|
WINDOW *wTransfers;
|
||||||
|
WINDOW *wTextOutput;
|
||||||
|
WINDOW *wConsole;
|
||||||
|
/* Root box */
|
||||||
|
struct fxlink_TUI_box *bRoot;
|
||||||
|
/* Application data */
|
||||||
|
struct fxlink_pollfds polled_fds;
|
||||||
|
struct fxlink_device_list devices;
|
||||||
|
|
||||||
|
} TUI = { 0 };
|
||||||
|
|
||||||
|
//---
|
||||||
|
// TUI management and rendering
|
||||||
|
//---
|
||||||
|
|
||||||
|
static bool TUI_setup_windows(void)
|
||||||
|
{
|
||||||
|
struct fxlink_TUI_box
|
||||||
|
*bTransfers, *bConsole, *bStatus, *bLogs, *bTextOutput,
|
||||||
|
*bLeft, *bRight;
|
||||||
|
|
||||||
|
bTransfers = fxlink_TUI_box_mk_window("Transfers", &TUI.wTransfers);
|
||||||
|
bConsole = fxlink_TUI_box_mk_window("Console", &TUI.wConsole);
|
||||||
|
bStatus = fxlink_TUI_box_mk_window("Status", &TUI.wStatus);
|
||||||
|
bLogs = fxlink_TUI_box_mk_window("Logs", &TUI.wLogs);
|
||||||
|
bTextOutput = fxlink_TUI_box_mk_window("Text output from calculators",
|
||||||
|
&TUI.wTextOutput);
|
||||||
|
fxlink_TUI_box_stretch(bLogs, 1, 2, false);
|
||||||
|
|
||||||
|
bLeft = fxlink_TUI_box_mk_vertical(bTextOutput, bConsole, NULL);
|
||||||
|
bRight = fxlink_TUI_box_mk_vertical(bStatus, bLogs, bTransfers, NULL);
|
||||||
|
fxlink_TUI_box_stretch(bLeft, 2, 1, false);
|
||||||
|
fxlink_TUI_box_stretch(bRight, 3, 1, false);
|
||||||
|
|
||||||
|
TUI.bRoot = fxlink_TUI_box_mk_horizontal(bLeft, bRight, NULL);
|
||||||
|
fxlink_TUI_box_layout(TUI.bRoot, 0, 1, getmaxx(stdscr), getmaxy(stdscr)-1);
|
||||||
|
return fxlink_TUI_apply_layout(TUI.bRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TUI_free_windows(void)
|
||||||
|
{
|
||||||
|
if(TUI.wStatus) delwin(TUI.wStatus);
|
||||||
|
if(TUI.wLogs) delwin(TUI.wLogs);
|
||||||
|
if(TUI.wTransfers) delwin(TUI.wTransfers);
|
||||||
|
if(TUI.wTextOutput) delwin(TUI.wTextOutput);
|
||||||
|
if(TUI.wConsole) delwin(TUI.wConsole);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TUI_refresh_all(bool refresh_bg)
|
||||||
|
{
|
||||||
|
if(refresh_bg)
|
||||||
|
wrefresh(stdscr);
|
||||||
|
wrefresh(TUI.wStatus);
|
||||||
|
wrefresh(TUI.wLogs);
|
||||||
|
wrefresh(TUI.wTransfers);
|
||||||
|
wrefresh(TUI.wTextOutput);
|
||||||
|
wrefresh(TUI.wConsole);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TUI_refresh_console(void)
|
||||||
|
{
|
||||||
|
wrefresh(TUI.wLogs);
|
||||||
|
wrefresh(TUI.wConsole);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TUI_render_status(void)
|
||||||
|
{
|
||||||
|
WINDOW *win = TUI.wStatus;
|
||||||
|
werase(win);
|
||||||
|
|
||||||
|
int w, h, y = 1;
|
||||||
|
getmaxyx(win, h, w);
|
||||||
|
|
||||||
|
wmove(win, 0, 1);
|
||||||
|
fprint(win, FMT_HEADER,
|
||||||
|
"Device Status ID System Classes");
|
||||||
|
|
||||||
|
if(TUI.devices.count == 0) {
|
||||||
|
mvwaddstr(win, 1, 1, "(no devices)");
|
||||||
|
y++;
|
||||||
|
}
|
||||||
|
for(int i = 0; i < TUI.devices.count; i++) {
|
||||||
|
struct fxlink_device *fdev = &TUI.devices.devices[i];
|
||||||
|
struct fxlink_calc *calc = fdev->calc;
|
||||||
|
|
||||||
|
if(fdev->status == FXLINK_FDEV_STATUS_CONNECTED) {
|
||||||
|
wattron(win, fmt_to_ncurses_attr(FMT_BGSELECTED));
|
||||||
|
mvwhline(win, y, 0, ' ', w);
|
||||||
|
}
|
||||||
|
|
||||||
|
mvwaddstr(win, y, 1, fxlink_device_id(fdev));
|
||||||
|
mvwaddstr(win, y, 10, fxlink_device_status_string(fdev));
|
||||||
|
mvwprintw(win, y, 21, "%04x:%04x", fdev->idVendor, fdev->idProduct);
|
||||||
|
|
||||||
|
if(calc) {
|
||||||
|
mvwaddstr(win, y, 32, fxlink_device_system_string(fdev));
|
||||||
|
wmove(win, y, 41);
|
||||||
|
|
||||||
|
for(int i = 0; i < calc->interface_count; i++)
|
||||||
|
wprintw(win, " %02x.%02x", calc->classes[i] >> 8,
|
||||||
|
calc->classes[i] & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
wattroff(win, fmt_to_ncurses_attr(FMT_BGSELECTED));
|
||||||
|
y++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_comms = false;
|
||||||
|
for(int i = 0; i < TUI.devices.count; i++)
|
||||||
|
has_comms = has_comms || (TUI.devices.devices[i].comm != NULL);
|
||||||
|
|
||||||
|
wmove(win, y+1, 1);
|
||||||
|
fprint(win, FMT_HEADER, "Device Status Serial IN OUT");
|
||||||
|
y += 2;
|
||||||
|
|
||||||
|
if(!has_comms) {
|
||||||
|
mvwaddstr(win, y, 1, "(no communications)");
|
||||||
|
}
|
||||||
|
for(int i = 0; i < TUI.devices.count; i++) {
|
||||||
|
struct fxlink_device *fdev = &TUI.devices.devices[i];
|
||||||
|
struct fxlink_calc *calc = fdev->calc;
|
||||||
|
struct fxlink_comm *comm = fdev->comm;
|
||||||
|
if(!comm)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(y >= h) {
|
||||||
|
y++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mvwaddstr(win, y, 1, fxlink_device_id(fdev));
|
||||||
|
mvwaddstr(win, y, 10, fxlink_device_status_string(fdev));
|
||||||
|
mvwaddstr(win, y, 21, calc->serial ? calc->serial : "(null)");
|
||||||
|
mvwprintw(win, y, 31, "%02x%c", comm->ep_bulk_IN,
|
||||||
|
comm->tr_bulk_IN != NULL ? '*' : '.');
|
||||||
|
mvwprintw(win, y, 36, "%02x%c", comm->ep_bulk_OUT,
|
||||||
|
comm->tr_bulk_OUT != NULL ? '*' : '.');
|
||||||
|
y++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(y > h) {
|
||||||
|
wmove(win, h-1, w-6);
|
||||||
|
fprint(win, FMT_BGSELECTED, "(more)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void progress_bar(WINDOW *win, int width, int done, int total)
|
||||||
|
{
|
||||||
|
char const *ramp[9] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" };
|
||||||
|
int progress = ((int64_t)done * width * 8) / total;
|
||||||
|
int percent = (int64_t)done * 100 / total;
|
||||||
|
|
||||||
|
wprintw(win, "%3d%% │", percent);
|
||||||
|
for(int i = 0; i < width; i++) {
|
||||||
|
int block_width = min(progress, 8);
|
||||||
|
waddstr(win, ramp[block_width]);
|
||||||
|
progress = max(progress-8, 0);
|
||||||
|
}
|
||||||
|
waddstr(win, "│");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TUI_render_transfers(void)
|
||||||
|
{
|
||||||
|
WINDOW *win = TUI.wTransfers;
|
||||||
|
int y = 1;
|
||||||
|
|
||||||
|
werase(win);
|
||||||
|
wmove(win, 0, 1);
|
||||||
|
fprint(win, FMT_HEADER, "Device Dir. Size Progress");
|
||||||
|
bool has_transfers = false;
|
||||||
|
|
||||||
|
for(int i = 0; i < TUI.devices.count; i++) {
|
||||||
|
struct fxlink_device *fdev = &TUI.devices.devices[i];
|
||||||
|
struct fxlink_comm *comm = fdev->comm;
|
||||||
|
if(!comm)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
struct fxlink_transfer *IN = comm->ftransfer_IN;
|
||||||
|
struct fxlink_transfer *OUT = comm->ftransfer_OUT;
|
||||||
|
|
||||||
|
if(IN) {
|
||||||
|
mvwaddstr(win, y, 1, fxlink_device_id(fdev));
|
||||||
|
mvwaddstr(win, y, 10, "IN");
|
||||||
|
mvwaddstr(win, y, 16, fxlink_size_string(IN->msg.size));
|
||||||
|
|
||||||
|
wmove(win, y, 26);
|
||||||
|
progress_bar(win, 32, IN->processed_size, IN->msg.size);
|
||||||
|
|
||||||
|
has_transfers = true;
|
||||||
|
y++;
|
||||||
|
}
|
||||||
|
if(OUT) {
|
||||||
|
mvwaddstr(win, y, 1, fxlink_device_id(fdev));
|
||||||
|
mvwaddstr(win, y, 10, "OUT");
|
||||||
|
mvwaddstr(win, y, 16, fxlink_size_string(IN->msg.size));
|
||||||
|
has_transfers = true;
|
||||||
|
y++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!has_transfers)
|
||||||
|
mvwaddstr(win, 1, 1, "(no transfers)");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TUI_render_all(bool with_borders)
|
||||||
|
{
|
||||||
|
if(with_borders) {
|
||||||
|
erase();
|
||||||
|
fxlink_TUI_render_borders(TUI.bRoot);
|
||||||
|
fxlink_TUI_render_titles(TUI.bRoot);
|
||||||
|
|
||||||
|
/* Title bar */
|
||||||
|
int w = getmaxx(stdscr);
|
||||||
|
attron(fmt_to_ncurses_attr(FMT_BGSELECTED));
|
||||||
|
mvhline(0, 0, ' ', w);
|
||||||
|
char const *str = "fxlink " FXLINK_VERSION " (TUI interactive mode)";
|
||||||
|
mvaddstr(0, w/2 - strlen(str)/2, str);
|
||||||
|
attroff(fmt_to_ncurses_attr(FMT_BGSELECTED));
|
||||||
|
}
|
||||||
|
TUI_render_status();
|
||||||
|
TUI_render_transfers();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TUI_SIGWINCH_handler(int sig)
|
||||||
|
{
|
||||||
|
(void)sig;
|
||||||
|
TUI.resize_needed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool TUI_setup(void)
|
||||||
|
{
|
||||||
|
memset(&TUI, 0, sizeof TUI);
|
||||||
|
|
||||||
|
/* Set up the SINGWINCH handler */
|
||||||
|
struct sigaction WINCH;
|
||||||
|
sigaction(SIGWINCH, NULL, &WINCH);
|
||||||
|
WINCH.sa_handler = TUI_SIGWINCH_handler;
|
||||||
|
sigaction(SIGWINCH, &WINCH, NULL);
|
||||||
|
|
||||||
|
/* Initialize the main screen */
|
||||||
|
initscr();
|
||||||
|
start_color();
|
||||||
|
use_default_colors();
|
||||||
|
|
||||||
|
/* Set up our color pairs. These are FG/BG combinations from the FMT_*
|
||||||
|
enumerated colors in <fxlink/defs.h>, ordered BG-major. */
|
||||||
|
for(int bg = 0; bg < 9; bg++) {
|
||||||
|
for(int fg = 0; fg < 9; fg++)
|
||||||
|
init_pair(9*bg + fg, fg - 1, bg - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!TUI_setup_windows())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Allow ncurses to scroll text-based windows*/
|
||||||
|
scrollok(TUI.wConsole, 1);
|
||||||
|
scrollok(TUI.wLogs, 1);
|
||||||
|
scrollok(TUI.wTextOutput, 1);
|
||||||
|
|
||||||
|
wmove(TUI.wConsole, 0, 0);
|
||||||
|
wmove(TUI.wLogs, 0, 0);
|
||||||
|
wmove(TUI.wTextOutput, 0, 0);
|
||||||
|
|
||||||
|
/* Make getch() non-blocking (though it also doesn't interpret escape
|
||||||
|
sequences anymore!) */
|
||||||
|
cbreak();
|
||||||
|
wtimeout(TUI.wLogs, 0);
|
||||||
|
wtimeout(TUI.wTextOutput, 0);
|
||||||
|
wtimeout(TUI.wConsole, 0);
|
||||||
|
|
||||||
|
/* Disable echo so we can edit input as it's being typed */
|
||||||
|
noecho();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TUI_quit(void)
|
||||||
|
{
|
||||||
|
TUI_free_windows();
|
||||||
|
endwin();
|
||||||
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Interactive TUI
|
||||||
|
//---
|
||||||
|
|
||||||
|
static void handle_image(struct fxlink_message *msg, char const *path)
|
||||||
|
{
|
||||||
|
struct fxlink_message_image_header *img = msg->data;
|
||||||
|
char *filename = fxlink_gen_file_name(path, msg->type, ".png");
|
||||||
|
|
||||||
|
struct fxlink_message_image_raw *raw = fxlink_message_image_decode(msg);
|
||||||
|
if(raw) {
|
||||||
|
fxlink_libpng_save_raw(raw, filename);
|
||||||
|
fxlink_message_image_raw_free(raw);
|
||||||
|
log_("saved image (%dx%d, format=%d) to '%s'\n",
|
||||||
|
img->width, img->height, img->pixel_format, filename);
|
||||||
|
}
|
||||||
|
free(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_text(struct fxlink_message *msg)
|
||||||
|
{
|
||||||
|
char const *str = msg->data;
|
||||||
|
WINDOW *win = TUI.wTextOutput;
|
||||||
|
|
||||||
|
if(options.verbose)
|
||||||
|
waddstr(win, "------------------\n");
|
||||||
|
waddnstr(win, str, msg->size);
|
||||||
|
if(options.verbose) {
|
||||||
|
if(str[msg->size - 1] != '\n')
|
||||||
|
waddch(win, '\n');
|
||||||
|
waddstr(win, "------------------\n");
|
||||||
|
}
|
||||||
|
if(options.verbose) {
|
||||||
|
for(size_t i = 0; i < msg->size; i++) {
|
||||||
|
print(win, " %02x", str[i]);
|
||||||
|
if((i & 15) == 15 || i == msg->size - 1)
|
||||||
|
print(win, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(options.log_file)
|
||||||
|
fwrite(str, 1, msg->size, options.log_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_video(struct fxlink_message *msg)
|
||||||
|
{
|
||||||
|
struct fxlink_message_image_raw *raw = fxlink_message_image_decode(msg);
|
||||||
|
if(!raw)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fxlink_sdl2_display_raw(raw);
|
||||||
|
fxlink_message_image_raw_free(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fxlink_interactive_handle_message(struct fxlink_message *msg)
|
||||||
|
{
|
||||||
|
char const *path = ".";
|
||||||
|
|
||||||
|
if(fxlink_message_is_fxlink_image(msg))
|
||||||
|
return handle_image(msg, path);
|
||||||
|
|
||||||
|
if(fxlink_message_is_fxlink_text(msg))
|
||||||
|
return handle_text(msg);
|
||||||
|
|
||||||
|
if(fxlink_message_is_fxlink_video(msg))
|
||||||
|
return handle_video(msg);
|
||||||
|
|
||||||
|
/* Default to saving to a blob */
|
||||||
|
static char combined_type[48];
|
||||||
|
sprintf(combined_type, "%.16s-%.16s", msg->application, msg->type);
|
||||||
|
|
||||||
|
char *filename = fxlink_gen_file_name(path, combined_type, ".bin");
|
||||||
|
FILE *fp = fopen(filename, "wb");
|
||||||
|
if(!fp) {
|
||||||
|
elog("could not save to '%s': %m\n", filename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite(msg->data, 1, msg->size, fp);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
log_("saved blob to '%s'\n", filename);
|
||||||
|
free(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_fxlink_log(int display_fmt, char const *str)
|
||||||
|
{
|
||||||
|
int attr = fmt_to_ncurses_attr(display_fmt);
|
||||||
|
wattron(TUI.wLogs, attr);
|
||||||
|
waddstr(TUI.wLogs, str);
|
||||||
|
wattroff(TUI.wLogs, attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main_tui_interactive(libusb_context *ctx)
|
||||||
|
{
|
||||||
|
if(!TUI_setup())
|
||||||
|
return elog("error: failed to setup ncurses TUI o(x_x)o\n");
|
||||||
|
|
||||||
|
/* Redirect fxlink logs to the logging window in the TUI */
|
||||||
|
fxlink_log_set_handler(handle_fxlink_log);
|
||||||
|
/* Set up hotplug notification */
|
||||||
|
fxlink_device_list_track(&TUI.devices, ctx);
|
||||||
|
/* Set up file descriptor tracking */
|
||||||
|
fxlink_pollfds_track(&TUI.polled_fds, ctx);
|
||||||
|
|
||||||
|
struct timeval zero_tv = { 0 };
|
||||||
|
struct timeval usb_timeout;
|
||||||
|
struct pollfd stdinfd = { .fd = STDIN_FILENO, .events = POLLIN };
|
||||||
|
|
||||||
|
/* Initial render */
|
||||||
|
print(TUI.wConsole, "fxlink version %s (libusb/TUI interactive mode)\n",
|
||||||
|
FXLINK_VERSION);
|
||||||
|
char const *prompt = "> ";
|
||||||
|
|
||||||
|
print(TUI.wConsole, "%s", prompt);
|
||||||
|
TUI_render_all(true);
|
||||||
|
TUI_refresh_all(true);
|
||||||
|
|
||||||
|
struct fxlink_TUI_input input;
|
||||||
|
fxlink_TUI_input_init(&input, TUI.wConsole, 16);
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
int rc = libusb_get_next_timeout(ctx, &usb_timeout);
|
||||||
|
int timeout = -1;
|
||||||
|
if(rc > 0)
|
||||||
|
timeout = usb_timeout.tv_sec * 1000 + usb_timeout.tv_usec / 1000;
|
||||||
|
bool timeout_is_libusb = true;
|
||||||
|
/* Time out at least every 100 ms so we can handle SDL events */
|
||||||
|
if(timeout < 0 || timeout > 100) {
|
||||||
|
timeout = 100;
|
||||||
|
timeout_is_libusb = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = fxlink_multipoll(timeout,
|
||||||
|
&stdinfd, 1, TUI.polled_fds.fds, TUI.polled_fds.count, NULL);
|
||||||
|
|
||||||
|
if(rc < 0 && errno != EINTR)
|
||||||
|
elog("poll: %s\n", strerror(errno));
|
||||||
|
|
||||||
|
/* Handle SIGWINCH */
|
||||||
|
if(TUI.resize_needed) {
|
||||||
|
endwin();
|
||||||
|
refresh();
|
||||||
|
TUI_setup_windows();
|
||||||
|
TUI.resize_needed = false;
|
||||||
|
TUI_render_all(true);
|
||||||
|
TUI_refresh_all(true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Determine which even source was activated */
|
||||||
|
bool stdin_activity = (stdinfd.revents & POLLIN) != 0;
|
||||||
|
bool usb_activity = false;
|
||||||
|
for(int i = 0; i < TUI.polled_fds.count; i++)
|
||||||
|
usb_activity |= (TUI.polled_fds.fds[i].revents != 0);
|
||||||
|
|
||||||
|
/* Determine what to do. We update the console on stdin activity. We
|
||||||
|
update libusb on USB activity or appropriate timeout. We update SDL
|
||||||
|
events on any timeout. */
|
||||||
|
bool update_console = stdin_activity;
|
||||||
|
bool update_usb = usb_activity || (rc == 0 && timeout_is_libusb);
|
||||||
|
bool update_sdl = (rc == 0);
|
||||||
|
|
||||||
|
if(update_console) {
|
||||||
|
bool finished = fxlink_TUI_input_getch(&input, TUI.wLogs);
|
||||||
|
TUI_refresh_console();
|
||||||
|
|
||||||
|
if(finished) {
|
||||||
|
char *command = input.data;
|
||||||
|
if(command[0] != 0)
|
||||||
|
log_("command: '%s'\n", command);
|
||||||
|
if(!strcmp(command, "q"))
|
||||||
|
break;
|
||||||
|
fxlink_TUI_input_free(&input);
|
||||||
|
print(TUI.wConsole, "%s", prompt);
|
||||||
|
fxlink_TUI_input_init(&input, TUI.wConsole, 16);
|
||||||
|
TUI_refresh_console();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(update_usb) {
|
||||||
|
libusb_handle_events_timeout(ctx, &zero_tv);
|
||||||
|
fxlink_device_list_refresh(&TUI.devices);
|
||||||
|
|
||||||
|
for(int i = 0; i < TUI.devices.count; i++) {
|
||||||
|
struct fxlink_device *fdev = &TUI.devices.devices[i];
|
||||||
|
|
||||||
|
/* Check for devices ready to connect to */
|
||||||
|
if(fdev->status == FXLINK_FDEV_STATUS_IDLE && fdev->comm
|
||||||
|
&& fdev->comm->ep_bulk_IN != 0xff) {
|
||||||
|
if(fxlink_device_claim_fxlink(fdev))
|
||||||
|
fxlink_device_start_bulk_IN(fdev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for devices with finished transfers */
|
||||||
|
struct fxlink_message *msg=fxlink_device_finish_bulk_IN(fdev);
|
||||||
|
if(msg) {
|
||||||
|
fxlink_interactive_handle_message(msg);
|
||||||
|
fxlink_message_free(msg, true);
|
||||||
|
fxlink_device_start_bulk_IN(fdev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TUI_render_all(false);
|
||||||
|
TUI_refresh_all(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(update_sdl) {
|
||||||
|
fxlink_sdl2_handle_events();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while(fxlink_device_list_interrupt(&TUI.devices))
|
||||||
|
libusb_handle_events(ctx);
|
||||||
|
|
||||||
|
fxlink_device_list_stop(&TUI.devices);
|
||||||
|
fxlink_pollfds_stop(&TUI.polled_fds);
|
||||||
|
fxlink_log_set_handler(NULL);
|
||||||
|
TUI_quit();
|
||||||
|
return 0;
|
||||||
|
}
|
125
fxlink/modes/udisks2.c
Normal file
125
fxlink/modes/udisks2.c
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
|
||||||
|
#include <fxlink/config.h>
|
||||||
|
#ifndef FXLINK_DISABLE_UDISKS2
|
||||||
|
|
||||||
|
#include "../fxlink.h"
|
||||||
|
#include <fxlink/tooling/udisks2.h>
|
||||||
|
#include <fxlink/filter.h>
|
||||||
|
#include <fxlink/logging.h>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
int main_send(struct fxlink_filter *filter, delay_t *delay, char **files)
|
||||||
|
{
|
||||||
|
fxlink_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 != FXLINK_FILTER_UNIQUE) {
|
||||||
|
rc = 1;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Determine a mount folder, mounting the volume if needed */
|
||||||
|
gchar *folder = NULL;
|
||||||
|
|
||||||
|
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 = elog("cannot mount %s: %s\n", dev, error->message);
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
printf("Mounted %s to %s.\n", dev, folder);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
folder = strdup(mount_points[0]);
|
||||||
|
printf("Already mounted at %s.\n", folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 = elog("cannot allocate argv array for cp(1)\n");
|
||||||
|
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 = elog("failed to fork to invoke cp\n");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int wstatus;
|
||||||
|
waitpid(pid, &wstatus, 0);
|
||||||
|
|
||||||
|
if(!WIFEXITED(wstatus))
|
||||||
|
elog("process did not terminate normally\n");
|
||||||
|
else if(WEXITSTATUS(wstatus) != 0) {
|
||||||
|
elog("process terminated with error %d\n", WEXITSTATUS(wstatus));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unmount the filesystem and eject the device */
|
||||||
|
GVariant *args = g_variant_new("a{sv}", NULL);
|
||||||
|
udisks_filesystem_call_unmount_sync(fs, args, NULL, &error);
|
||||||
|
if(error)
|
||||||
|
elog("while unmounting %s: %s\n", 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)
|
||||||
|
elog("while ejecting %s: %s\n", 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 */
|
42
fxlink/png.c
42
fxlink/png.c
|
@ -1,42 +0,0 @@
|
||||||
#include "png.h"
|
|
||||||
#include "util.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
/* fxlink_png_save(): Save a bitmap into a PNG file */
|
|
||||||
int fxlink_png_save(png_byte **row_pointers, int width, int height,
|
|
||||||
char const *path)
|
|
||||||
{
|
|
||||||
png_struct *png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
|
|
||||||
NULL, NULL, NULL);
|
|
||||||
if(!png)
|
|
||||||
return err("failed to write PNG: png_create_write_struct");
|
|
||||||
|
|
||||||
png_infop info = png_create_info_struct(png);
|
|
||||||
if(!info)
|
|
||||||
return err("failed to write PNG: png_create_info_struct");
|
|
||||||
|
|
||||||
FILE *fp = fopen(path, "wb");
|
|
||||||
if(!fp) {
|
|
||||||
png_destroy_write_struct(&png, &info);
|
|
||||||
return err("failed to open '%s' to write PNG: %m", path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(setjmp(png_jmpbuf(png))) {
|
|
||||||
fclose(fp);
|
|
||||||
png_destroy_write_struct(&png, &info);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
png_init_io(png, fp);
|
|
||||||
png_set_IHDR(png, info,
|
|
||||||
width, height, 8,
|
|
||||||
PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
|
|
||||||
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
|
||||||
|
|
||||||
png_write_info(png, info);
|
|
||||||
png_write_image(png, row_pointers);
|
|
||||||
png_write_end(png, NULL);
|
|
||||||
png_destroy_write_struct(&png, &info);
|
|
||||||
fclose(fp);
|
|
||||||
return 0;
|
|
||||||
}
|
|
14
fxlink/png.h
14
fxlink/png.h
|
@ -1,14 +0,0 @@
|
||||||
//---
|
|
||||||
// fxlink:png - Tools to output PNG images with libpng
|
|
||||||
//---
|
|
||||||
|
|
||||||
#ifndef FXLINK_PNG_H
|
|
||||||
#define FXLINK_PNG_H
|
|
||||||
|
|
||||||
#include <png.h>
|
|
||||||
|
|
||||||
/* fxlink_png_save(): Save a bitmap into a PNG file */
|
|
||||||
int fxlink_png_save(png_byte **row_pointers, int width, int height,
|
|
||||||
char const *path);
|
|
||||||
|
|
||||||
#endif /* FXLINK_PNG_H */
|
|
|
@ -1,42 +0,0 @@
|
||||||
#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);
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
//---
|
|
||||||
// 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 */
|
|
|
@ -1,140 +1,288 @@
|
||||||
#include "protocol.h"
|
//---------------------------------------------------------------------------//
|
||||||
#include "util.h"
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <fxlink/protocol.h>
|
||||||
#include <stdio.h>
|
#include <fxlink/logging.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include "fxlink.h"
|
||||||
|
|
||||||
|
bool fxlink_message_is_apptype(struct fxlink_message const *msg,
|
||||||
|
char const *application, char const *type)
|
||||||
|
{
|
||||||
|
return memchr(application, '\0', 17) != NULL
|
||||||
|
&& memchr(type, '\0', 17) != NULL
|
||||||
|
&& !strncmp(msg->application, application, 16)
|
||||||
|
&& !strncmp(msg->type, type, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_message_free(struct fxlink_message *message, bool free_data)
|
||||||
|
{
|
||||||
|
if(free_data)
|
||||||
|
free(message->data);
|
||||||
|
free(message);
|
||||||
|
}
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// Image format
|
// Image decoding
|
||||||
//---
|
//---
|
||||||
|
|
||||||
static int img_bytes_per_row(int format, int width)
|
static int img_bytes_per_row(int format, int width)
|
||||||
{
|
{
|
||||||
if(format == USB_FXLINK_IMAGE_RGB565)
|
if(format == FXLINK_MESSAGE_IMAGE_RGB565)
|
||||||
return 2 * width;
|
return 2 * width;
|
||||||
if(format == USB_FXLINK_IMAGE_MONO)
|
if(format == FXLINK_MESSAGE_IMAGE_MONO)
|
||||||
return (width + 7) >> 3;
|
return (width + 7) >> 3;
|
||||||
if(format == USB_FXLINK_IMAGE_GRAY)
|
if(format == FXLINK_MESSAGE_IMAGE_GRAY)
|
||||||
return 2 * ((width + 7) >> 3);
|
return 2 * ((width + 7) >> 3);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void decode_rgb565(void *pixels, int width, int height, int size,
|
static struct fxlink_message_image_raw *decode_rgb565(void const *pixels,
|
||||||
uint8_t **row_pointers)
|
int size, struct fxlink_message_image_raw *raw)
|
||||||
{
|
{
|
||||||
int bpr = img_bytes_per_row(USB_FXLINK_IMAGE_RGB565, width);
|
int bpr = img_bytes_per_row(FXLINK_MESSAGE_IMAGE_RGB565, raw->width);
|
||||||
|
|
||||||
for(int y = 0; y < height; y++) {
|
for(int y = 0; y < raw->height; y++) {
|
||||||
void *row = pixels + y * bpr;
|
void const *row = pixels + y * bpr;
|
||||||
|
|
||||||
for(int x = 0; x < width; x++) {
|
for(int x = 0; x < raw->width; x++) {
|
||||||
/* Don't read past the read buffer if the image is incomplete */
|
/* Don't read past the read buffer if the image is incomplete */
|
||||||
void *input = row + 2 * x;
|
void const *input = row + 2 * x;
|
||||||
uint16_t color = 0;
|
uint16_t color = 0;
|
||||||
if(input - pixels + 2 <= size) color = *(uint16_t *)input;
|
if(input - pixels + 2 <= size) color = *(uint16_t *)input;
|
||||||
|
|
||||||
color = ((color & 0xff00) >> 8) | ((color & 0x00ff) << 8);
|
color = ((color & 0xff00) >> 8) | ((color & 0x00ff) << 8);
|
||||||
|
|
||||||
row_pointers[y][3*x+0] = (color >> 11) << 3;
|
raw->data[y][3*x+0] = (color >> 11) << 3;
|
||||||
row_pointers[y][3*x+1] = ((color >> 5) & 0x3f) << 2;
|
raw->data[y][3*x+1] = ((color >> 5) & 0x3f) << 2;
|
||||||
row_pointers[y][3*x+2] = (color & 0x1f) << 3;
|
raw->data[y][3*x+2] = (color & 0x1f) << 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void decode_mono(void *pixels, int width, int height, int size,
|
static struct fxlink_message_image_raw *decode_mono(void const *pixels,
|
||||||
uint8_t **row_pointers)
|
int size, struct fxlink_message_image_raw *raw)
|
||||||
{
|
{
|
||||||
int bpr = img_bytes_per_row(USB_FXLINK_IMAGE_MONO, width);
|
int bpr = img_bytes_per_row(FXLINK_MESSAGE_IMAGE_MONO, raw->width);
|
||||||
|
|
||||||
for(int y = 0; y < height; y++) {
|
for(int y = 0; y < raw->height; y++) {
|
||||||
void *row = pixels + y * bpr;
|
void const *row = pixels + y * bpr;
|
||||||
|
|
||||||
for(int x = 0; x < width; x++) {
|
for(int x = 0; x < raw->width; x++) {
|
||||||
/* Don't read past the read buffer if the image is incomplete */
|
/* Don't read past the read buffer if the image is incomplete */
|
||||||
void *input = row + (x >> 3);
|
void const *input = row + (x >> 3);
|
||||||
int byte = 0;
|
int byte = 0;
|
||||||
if(input - pixels + 1 <= size) byte = *(uint8_t *)input;
|
if(input - pixels + 1 <= size) byte = *(uint8_t *)input;
|
||||||
int color = (byte & (0x80 >> (x & 7))) ? 0 : 255;
|
int color = (byte & (0x80 >> (x & 7))) ? 0 : 255;
|
||||||
|
|
||||||
row_pointers[y][3*x+0] = color;
|
raw->data[y][3*x+0] = color;
|
||||||
row_pointers[y][3*x+1] = color;
|
raw->data[y][3*x+1] = color;
|
||||||
row_pointers[y][3*x+2] = color;
|
raw->data[y][3*x+2] = color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void decode_gray(void *pixels, int width, int height, int size,
|
static struct fxlink_message_image_raw *decode_gray(void const *pixels,
|
||||||
uint8_t **row_pointers)
|
int size, struct fxlink_message_image_raw *raw)
|
||||||
{
|
{
|
||||||
int bpr = img_bytes_per_row(USB_FXLINK_IMAGE_MONO, width);
|
int bpr = img_bytes_per_row(FXLINK_MESSAGE_IMAGE_MONO, raw->width);
|
||||||
|
|
||||||
for(int k = 0; k < 2 * height; k++) {
|
for(int k = 0; k < 2 * raw->height; k++) {
|
||||||
void *row = pixels + k * bpr;
|
void const *row = pixels + k * bpr;
|
||||||
int y = k % height;
|
int y = k % raw->height;
|
||||||
|
|
||||||
for(int x = 0; x < width; x++) {
|
for(int x = 0; x < raw->width; x++) {
|
||||||
/* Don't read past the read buffer if the image is incomplete */
|
/* Don't read past the read buffer if the image is incomplete */
|
||||||
void *input = row + (x >> 3);
|
void const *input = row + (x >> 3);
|
||||||
int byte = 0;
|
int byte = 0;
|
||||||
if(input - pixels + 1 <= size) byte = *(uint8_t *)input;
|
if(input - pixels + 1 <= size) byte = *(uint8_t *)input;
|
||||||
|
|
||||||
int color = (byte & (0x80 >> (x & 7)));
|
int color = (byte & (0x80 >> (x & 7)));
|
||||||
/* Everything is inverted */
|
/* Everything is inverted */
|
||||||
if(!color) color = (k >= height) ? 0xaa : 0x55;
|
if(!color) color = (k >= raw->height) ? 0xaa : 0x55;
|
||||||
else color = 0x00;
|
else color = 0x00;
|
||||||
|
|
||||||
row_pointers[y][3*x+0] += color;
|
raw->data[y][3*x+0] += color;
|
||||||
row_pointers[y][3*x+1] += color;
|
raw->data[y][3*x+1] += color;
|
||||||
row_pointers[y][3*x+2] += color;
|
raw->data[y][3*x+2] += color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t **fxlink_protocol_decode_image(message_t *msg)
|
struct fxlink_message_image_raw *fxlink_message_image_decode(
|
||||||
|
struct fxlink_message const *msg)
|
||||||
{
|
{
|
||||||
usb_fxlink_image_t *img = (void *)msg->output;
|
if(!fxlink_message_is_fxlink_image(msg)
|
||||||
void *pixels = msg->output + sizeof *img;
|
&& !fxlink_message_is_fxlink_video(msg))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
/* Compute the amount of data for the specified image format and size */
|
struct fxlink_message_image_header *img = msg->data;
|
||||||
int bytes_per_row = img_bytes_per_row(img->pixel_format, img->width);
|
void *pixels = msg->data + sizeof *img;
|
||||||
int expected_size = img->height * bytes_per_row;
|
int pixels_size = msg->size - sizeof *img;
|
||||||
|
|
||||||
/* Check that the correct amount of data was sent */
|
/* Determine the expected data size */
|
||||||
int size = msg->size_read - sizeof *img;
|
int bytes_per_row = img_bytes_per_row(img->pixel_format, img->width);
|
||||||
if(size < expected_size)
|
int expected_size = img->height * bytes_per_row;
|
||||||
printf("warning: got %d bytes but needed %d, image will be "
|
|
||||||
"incomplete\n", size, expected_size);
|
|
||||||
if(size > expected_size)
|
|
||||||
printf("warning: got %d bytes but needed %d for image, dropping extra"
|
|
||||||
"\n", size, expected_size);
|
|
||||||
|
|
||||||
/* Allocate row pointers */
|
if(pixels_size < expected_size)
|
||||||
uint8_t **row_pointers = malloc(img->height*sizeof *row_pointers);
|
wlog("image has %d bytes but needs %d, it will be incomplete\n",
|
||||||
if(!row_pointers) {
|
pixels_size, expected_size);
|
||||||
err("failed to write allocate memory to decode image");
|
if(pixels_size > expected_size)
|
||||||
return NULL;
|
wlog("image has %d bytes but only needs %d, dropping extra\n",
|
||||||
}
|
pixels_size, expected_size);
|
||||||
|
|
||||||
for(size_t y = 0; y < img->height; y++) {
|
/* Allocate memory for the decoded pixels */
|
||||||
row_pointers[y] = calloc(img->width, 3);
|
struct fxlink_message_image_raw *raw = malloc(sizeof *raw);
|
||||||
if(!row_pointers[y]) {
|
uint8_t **data = calloc(img->height, sizeof(uint8_t *));
|
||||||
err("failed to write allocate memory to decode image");
|
if(!raw || !data)
|
||||||
for(size_t i = 0 ; i < y; i++) free(row_pointers[i]);
|
goto alloc_error;
|
||||||
free(row_pointers);
|
for(size_t i = 0; i < img->height; i++) {
|
||||||
return NULL;
|
data[i] = calloc(img->width, 3);
|
||||||
}
|
if(!data[i])
|
||||||
}
|
goto alloc_error;
|
||||||
|
}
|
||||||
|
|
||||||
/* Decode image */
|
raw->width = img->width;
|
||||||
if(img->pixel_format == USB_FXLINK_IMAGE_RGB565)
|
raw->height = img->height;
|
||||||
decode_rgb565(pixels, img->width, img->height, size, row_pointers);
|
raw->data = data;
|
||||||
if(img->pixel_format == USB_FXLINK_IMAGE_MONO)
|
|
||||||
decode_mono(pixels, img->width, img->height, size, row_pointers);
|
|
||||||
if(img->pixel_format == USB_FXLINK_IMAGE_GRAY)
|
|
||||||
decode_gray(pixels, img->width, img->height, size, row_pointers);
|
|
||||||
|
|
||||||
return row_pointers;
|
if(img->pixel_format == FXLINK_MESSAGE_IMAGE_RGB565)
|
||||||
|
return decode_rgb565(pixels, pixels_size, raw);
|
||||||
|
if(img->pixel_format == FXLINK_MESSAGE_IMAGE_MONO)
|
||||||
|
return decode_mono(pixels, pixels_size, raw);
|
||||||
|
if(img->pixel_format == FXLINK_MESSAGE_IMAGE_GRAY)
|
||||||
|
return decode_gray(pixels, pixels_size, raw);
|
||||||
|
|
||||||
|
elog("unknown pixel format: %d\n", img->pixel_format);
|
||||||
|
fxlink_message_image_raw_free(raw);
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
alloc_error:
|
||||||
|
elog("cannot allocate memory for decoded image: %m\n");
|
||||||
|
fxlink_message_image_raw_free(raw);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_message_image_raw_free(struct fxlink_message_image_raw *raw)
|
||||||
|
{
|
||||||
|
if(raw->data){
|
||||||
|
for(int i = 0; i < raw->height; i++)
|
||||||
|
free(raw->data[i]);
|
||||||
|
}
|
||||||
|
free(raw->data);
|
||||||
|
free(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Tools for crafting and receiving messages
|
||||||
|
//---
|
||||||
|
|
||||||
|
/* Check whether a header is valid. It's important for this function to be as
|
||||||
|
strict as possible so that picking up communication in the wrong spot
|
||||||
|
doesn't get out of control by recognizing messages everywhere. */
|
||||||
|
static bool header_valid(struct fxlink_message const *msg)
|
||||||
|
{
|
||||||
|
if(msg->version == 0x00000100) {
|
||||||
|
/* Transfer size larger than 16 MB: impossible for so many reasons */
|
||||||
|
if(msg->transfer_size > 0xffffff)
|
||||||
|
return false;
|
||||||
|
/* Non-printable characters in application/type names */
|
||||||
|
for(size_t i = 0; i < sizeof msg->application; i++) {
|
||||||
|
if(msg->application[i] && !isprint(msg->application[i]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for(size_t i = 0; i < sizeof msg->type; i++) {
|
||||||
|
if(msg->type[i] && !isprint(msg->type[i]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fxlink_transfer *fxlink_transfer_make_IN(void *data, int size)
|
||||||
|
{
|
||||||
|
int header_size = FXLINK_MESSAGE_HEADER_SIZE;
|
||||||
|
|
||||||
|
if(size < header_size) {
|
||||||
|
elog("cannot read message: header too short (%d < %zu bytes)\n",
|
||||||
|
size, header_size);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fxlink_transfer *tr = calloc(1, sizeof *tr);
|
||||||
|
if(!tr) {
|
||||||
|
elog("cannot allocate transfer structure: %m\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&tr->msg, data, header_size);
|
||||||
|
if(!header_valid(&tr->msg)) {
|
||||||
|
elog("cannot read message: invalid header (%d bytes dropped)\n",
|
||||||
|
size);
|
||||||
|
free(tr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr->msg.data = malloc(tr->msg.size);
|
||||||
|
tr->direction = FXLINK_TRANSFER_IN;
|
||||||
|
tr->processed_size = 0;
|
||||||
|
|
||||||
|
if(!tr->msg.data) {
|
||||||
|
elog("cannot allocate buffer for %d bytes\n", tr->msg.size);
|
||||||
|
free(tr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
fxlink_transfer_receive(tr, data + header_size, size - header_size);
|
||||||
|
return tr;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fxlink_message *fxlink_transfer_finish_IN(struct fxlink_transfer *tr)
|
||||||
|
{
|
||||||
|
if(tr->direction != FXLINK_TRANSFER_IN || !fxlink_transfer_complete(tr))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
struct fxlink_message *msg = malloc(sizeof *msg);
|
||||||
|
if(!msg) {
|
||||||
|
elog("cannot allocate message structure: %m\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(options.verbose)
|
||||||
|
log_("extracting completed message (%d bytes)\n", tr->msg.size);
|
||||||
|
|
||||||
|
/* Shallow copy the malloc()'d data pointer */
|
||||||
|
memcpy(msg, &tr->msg, sizeof *msg);
|
||||||
|
free(tr);
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_transfer_receive(struct fxlink_transfer *tr, void *data, int size)
|
||||||
|
{
|
||||||
|
int remaining_size = tr->msg.size - tr->processed_size;
|
||||||
|
if(size > remaining_size) {
|
||||||
|
elog("message too long, dropping %d bytes\n", size - remaining_size);
|
||||||
|
size = remaining_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(options.verbose)
|
||||||
|
log_("got %d bytes (out of %d left)\n", size, remaining_size);
|
||||||
|
|
||||||
|
memcpy(tr->msg.data + tr->processed_size, data, size);
|
||||||
|
tr->processed_size += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fxlink_transfer_complete(struct fxlink_transfer const *tr)
|
||||||
|
{
|
||||||
|
return tr->processed_size >= tr->msg.size;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
//---
|
|
||||||
// fxlink:protocol - Custom fxlink protocol
|
|
||||||
//---
|
|
||||||
|
|
||||||
#ifndef FXLINK_PROTOCOL_H
|
|
||||||
#define FXLINK_PROTOCOL_H
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
/* See the gint source for details on the protocol */
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
uint32_t version;
|
|
||||||
uint32_t size;
|
|
||||||
uint32_t transfer_size;
|
|
||||||
|
|
||||||
char application[16];
|
|
||||||
char type[16];
|
|
||||||
|
|
||||||
} usb_fxlink_header_t;
|
|
||||||
|
|
||||||
/* Subheader for the fxlink built-in "image" type */
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
uint32_t width;
|
|
||||||
uint32_t height;
|
|
||||||
int pixel_format;
|
|
||||||
|
|
||||||
} usb_fxlink_image_t;
|
|
||||||
|
|
||||||
/* Pixel formats */
|
|
||||||
typedef enum
|
|
||||||
{
|
|
||||||
/* Image is an array of *big-endian* uint16_t with RGB565 format */
|
|
||||||
USB_FXLINK_IMAGE_RGB565 = 0,
|
|
||||||
/* Image is an array of bits in mono format */
|
|
||||||
USB_FXLINK_IMAGE_MONO,
|
|
||||||
/* Image is two consecutive mono arrays, one for light, one for dark */
|
|
||||||
USB_FXLINK_IMAGE_GRAY,
|
|
||||||
|
|
||||||
} usb_fxlink_image_format_t;
|
|
||||||
|
|
||||||
//---
|
|
||||||
// Utilities in this implementation
|
|
||||||
//---
|
|
||||||
|
|
||||||
/* Message currently being transferred */
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
usb_fxlink_header_t header;
|
|
||||||
/* Valid when we are reading a message */
|
|
||||||
bool valid;
|
|
||||||
/* Data already read in this message */
|
|
||||||
uint32_t size_read;
|
|
||||||
/* Data buffer */
|
|
||||||
char *output;
|
|
||||||
|
|
||||||
} message_t;
|
|
||||||
|
|
||||||
/* fxlink_protocol_decode_image(): Decode an image into RGB888 format
|
|
||||||
|
|
||||||
This function decodes the message into an RGB888 image and returns an array
|
|
||||||
of row pointers with the image data (free the array and each element of the
|
|
||||||
array after use).
|
|
||||||
|
|
||||||
If there are not enough bytes in the input, it pads with zeros, and if there
|
|
||||||
are extra bytes, they are dropped; in both cases a warning is printed. */
|
|
||||||
uint8_t **fxlink_protocol_decode_image(message_t *msg);
|
|
||||||
|
|
||||||
#endif /* FXLINK_PROTOCOL_H */
|
|
|
@ -1,82 +0,0 @@
|
||||||
#include "config.h"
|
|
||||||
#ifndef FXLINK_DISABLE_SDL2
|
|
||||||
|
|
||||||
#include "sdl2.h"
|
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
static SDL_Window *window = NULL;
|
|
||||||
|
|
||||||
static int init(size_t width, size_t height)
|
|
||||||
{
|
|
||||||
if(!SDL_WasInit(SDL_INIT_VIDEO)) {
|
|
||||||
int rc = SDL_Init(SDL_INIT_VIDEO);
|
|
||||||
if(rc < 0)
|
|
||||||
return err("Cannot initialize SDL: %s\n", SDL_GetError());
|
|
||||||
}
|
|
||||||
|
|
||||||
window = SDL_CreateWindow("fxlink", SDL_WINDOWPOS_CENTERED,
|
|
||||||
SDL_WINDOWPOS_CENTERED, width, height, 0);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
__attribute__((destructor))
|
|
||||||
static void quit(void)
|
|
||||||
{
|
|
||||||
if(!window)
|
|
||||||
return;
|
|
||||||
SDL_DestroyWindow(window);
|
|
||||||
window = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Generate an RGB888 surface from image data. */
|
|
||||||
static SDL_Surface *surface_for_image(uint8_t **RGB888_rows, int width,
|
|
||||||
int height)
|
|
||||||
{
|
|
||||||
/* Little endian setup for RGB */
|
|
||||||
SDL_Surface *s = SDL_CreateRGBSurface(0, width, height, 24,
|
|
||||||
0x000000ff, 0x0000ff00, 0x0000ff00, 0);
|
|
||||||
if(!s) {
|
|
||||||
err("Cannot create surface for image");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int i = 0; i < height; i++)
|
|
||||||
memcpy(s->pixels + i * s->pitch, RGB888_rows[i], width * 3);
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sdl2_stream(uint8_t **RGB888_rows, int width, int height)
|
|
||||||
{
|
|
||||||
if(!window && init(width, height))
|
|
||||||
return;
|
|
||||||
|
|
||||||
int current_w, current_h;
|
|
||||||
SDL_GetWindowSize(window, ¤t_w, ¤t_h);
|
|
||||||
if(current_w != width || current_h != height)
|
|
||||||
SDL_SetWindowSize(window, width, height);
|
|
||||||
|
|
||||||
SDL_Surface *src = surface_for_image(RGB888_rows, width, height);
|
|
||||||
if(!src)
|
|
||||||
return;
|
|
||||||
|
|
||||||
SDL_Surface *dst = SDL_GetWindowSurface(window);
|
|
||||||
SDL_BlitSurface(src, NULL, dst, NULL);
|
|
||||||
SDL_FreeSurface(src);
|
|
||||||
|
|
||||||
SDL_UpdateWindowSurface(window);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sdl2_tick(void)
|
|
||||||
{
|
|
||||||
if(!window)
|
|
||||||
return;
|
|
||||||
|
|
||||||
SDL_Event e;
|
|
||||||
while(SDL_PollEvent(&e)) {
|
|
||||||
if(e.type == SDL_QUIT)
|
|
||||||
quit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif /* FXLINK_DISABLE_SDL2 */
|
|
|
@ -1,23 +0,0 @@
|
||||||
//---
|
|
||||||
// fxlink:sdl2 - SDL2 functions
|
|
||||||
//---
|
|
||||||
|
|
||||||
#ifndef FXLINK_SDL2_H
|
|
||||||
#define FXLINK_SDL2_H
|
|
||||||
|
|
||||||
#ifndef FXLINK_DISABLE_SDL2
|
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
|
||||||
|
|
||||||
/* sdl2_stream(): Display a streaming image on the window
|
|
||||||
This function opens or reuses an SDL2 window to display an image. */
|
|
||||||
void sdl2_stream(uint8_t **RGB888_rows, int width, int height);
|
|
||||||
|
|
||||||
/* sdl2_tick(): Handle SDL events
|
|
||||||
This just needs to be called regularly from the main thread, to respond to
|
|
||||||
events on the window. */
|
|
||||||
void sdl2_tick(void);
|
|
||||||
|
|
||||||
#endif /* FXLINK_DISABLE_SDL2 */
|
|
||||||
|
|
||||||
#endif /* FXLINK_SDL2_H */
|
|
46
fxlink/tooling/libpng.c
Normal file
46
fxlink/tooling/libpng.c
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
|
||||||
|
#include <fxlink/tooling/libpng.h>
|
||||||
|
#include <fxlink/logging.h>
|
||||||
|
|
||||||
|
int fxlink_libpng_save_raw(struct fxlink_message_image_raw *raw,
|
||||||
|
char const *path)
|
||||||
|
{
|
||||||
|
png_struct *png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
|
||||||
|
NULL, NULL, NULL);
|
||||||
|
if(!png)
|
||||||
|
return elog("failed to write PNG: png_create_write_struct\n");
|
||||||
|
|
||||||
|
png_infop info = png_create_info_struct(png);
|
||||||
|
if(!info)
|
||||||
|
return elog("failed to write PNG: png_create_info_struct\n");
|
||||||
|
|
||||||
|
FILE *fp = fopen(path, "wb");
|
||||||
|
if(!fp) {
|
||||||
|
png_destroy_write_struct(&png, &info);
|
||||||
|
return elog("failed to open '%s' to write PNG: %m\n", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(setjmp(png_jmpbuf(png))) {
|
||||||
|
fclose(fp);
|
||||||
|
png_destroy_write_struct(&png, &info);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
png_init_io(png, fp);
|
||||||
|
png_set_IHDR(png, info,
|
||||||
|
raw->width, raw->height, 8,
|
||||||
|
PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
|
||||||
|
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||||
|
|
||||||
|
png_write_info(png, info);
|
||||||
|
png_write_image(png, raw->data);
|
||||||
|
png_write_end(png, NULL);
|
||||||
|
png_destroy_write_struct(&png, &info);
|
||||||
|
fclose(fp);
|
||||||
|
return 0;
|
||||||
|
}
|
98
fxlink/tooling/sdl2.c
Normal file
98
fxlink/tooling/sdl2.c
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
|
||||||
|
#include <fxlink/tooling/sdl2.h>
|
||||||
|
#include <fxlink/logging.h>
|
||||||
|
|
||||||
|
#ifdef FXLINK_DISABLE_SDL2
|
||||||
|
|
||||||
|
void fxlink_sdl2_display_raw(struct fxlink_message_image_raw const *raw)
|
||||||
|
{
|
||||||
|
log_("SDL2 disabled at compiled-time, skipping frame");
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_sdl2_handle_events(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#else /* FXLINK_DISABLE_SDL2 */
|
||||||
|
|
||||||
|
static SDL_Window *window = NULL;
|
||||||
|
|
||||||
|
static int init(size_t width, size_t height)
|
||||||
|
{
|
||||||
|
if(!SDL_WasInit(SDL_INIT_VIDEO)) {
|
||||||
|
int rc = SDL_Init(SDL_INIT_VIDEO);
|
||||||
|
if(rc < 0)
|
||||||
|
return elog("cannot initialize SDL: %s\n", SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
window = SDL_CreateWindow("fxlink", SDL_WINDOWPOS_CENTERED,
|
||||||
|
SDL_WINDOWPOS_CENTERED, width, height, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((destructor))
|
||||||
|
static void quit(void)
|
||||||
|
{
|
||||||
|
if(!window)
|
||||||
|
return;
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
window = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generate an RGB888 surface from image data. */
|
||||||
|
static SDL_Surface *surface_for_image(uint8_t **RGB888_rows, int width,
|
||||||
|
int height)
|
||||||
|
{
|
||||||
|
/* Little endian setup for RGB */
|
||||||
|
SDL_Surface *s = SDL_CreateRGBSurface(0, width, height, 24,
|
||||||
|
0x000000ff, 0x0000ff00, 0x0000ff00, 0);
|
||||||
|
if(!s) {
|
||||||
|
elog("cannot create surface for image\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < height; i++)
|
||||||
|
memcpy(s->pixels + i * s->pitch, RGB888_rows[i], width * 3);
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_sdl2_display_raw(struct fxlink_message_image_raw const *raw)
|
||||||
|
{
|
||||||
|
if(!window && init(raw->width, raw->height))
|
||||||
|
return;
|
||||||
|
|
||||||
|
int current_w, current_h;
|
||||||
|
SDL_GetWindowSize(window, ¤t_w, ¤t_h);
|
||||||
|
if(current_w != raw->width || current_h != raw->height)
|
||||||
|
SDL_SetWindowSize(window, raw->width, raw->height);
|
||||||
|
|
||||||
|
SDL_Surface *src = surface_for_image(raw->data, raw->width, raw->height);
|
||||||
|
if(!src)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SDL_Surface *dst = SDL_GetWindowSurface(window);
|
||||||
|
SDL_BlitSurface(src, NULL, dst, NULL);
|
||||||
|
SDL_FreeSurface(src);
|
||||||
|
|
||||||
|
SDL_UpdateWindowSurface(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_sdl2_handle_events(void)
|
||||||
|
{
|
||||||
|
if(!window)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SDL_Event e;
|
||||||
|
while(SDL_PollEvent(&e)) {
|
||||||
|
if(e.type == SDL_QUIT)
|
||||||
|
quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* FXLINK_DISABLE_SDL2 */
|
|
@ -1,20 +1,15 @@
|
||||||
#include "config.h"
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
|
||||||
|
#include <fxlink/config.h>
|
||||||
#ifndef FXLINK_DISABLE_UDISKS2
|
#ifndef FXLINK_DISABLE_UDISKS2
|
||||||
|
|
||||||
#include "ud2.h"
|
#include <fxlink/tooling/udisks2.h>
|
||||||
#include "fxlink.h"
|
#include <fxlink/filter.h>
|
||||||
#include "util.h"
|
#include <fxlink/logging.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)
|
int ud2_start(UDisksClient **udc_ptr, UDisksManager **udm_ptr)
|
||||||
{
|
{
|
||||||
|
@ -22,12 +17,12 @@ int ud2_start(UDisksClient **udc_ptr, UDisksManager **udm_ptr)
|
||||||
|
|
||||||
UDisksClient *udc = udisks_client_new_sync(NULL, &error);
|
UDisksClient *udc = udisks_client_new_sync(NULL, &error);
|
||||||
if(error)
|
if(error)
|
||||||
return err("cannot open udisks2 client: %s", error->message);
|
return elog("cannot open udisks2 client: %s\n", error->message);
|
||||||
|
|
||||||
UDisksManager *udm = udisks_client_get_manager(udc);
|
UDisksManager *udm = udisks_client_get_manager(udc);
|
||||||
if(!udm) {
|
if(!udm) {
|
||||||
g_object_unref(udc);
|
g_object_unref(udc);
|
||||||
return err("udisks2 daemon does not seem to be running");
|
return elog("udisks2 daemon does not seem to be running\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
*udc_ptr = udc;
|
*udc_ptr = udc;
|
||||||
|
@ -35,8 +30,9 @@ int ud2_start(UDisksClient **udc_ptr, UDisksManager **udm_ptr)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ud2_end(UDisksClient *udc, __attribute__((unused)) UDisksManager *udm)
|
void ud2_end(UDisksClient *udc, UDisksManager *udm)
|
||||||
{
|
{
|
||||||
|
(void)udm;
|
||||||
g_object_unref(udc);
|
g_object_unref(udc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,9 +43,8 @@ gchar **ud2_block_devices(UDisksManager *udm)
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
|
|
||||||
udisks_manager_call_get_block_devices_sync(udm,args,&blocks,NULL,&error);
|
udisks_manager_call_get_block_devices_sync(udm,args,&blocks,NULL,&error);
|
||||||
if(error) {
|
if(error)
|
||||||
err("cannot list udisks2 block devices: %s", error->message);
|
elog("cannot list udisks2 block devices: %s\n", error->message);
|
||||||
}
|
|
||||||
|
|
||||||
return blocks;
|
return blocks;
|
||||||
}
|
}
|
||||||
|
@ -81,9 +76,9 @@ bool is_casio_drive(UDisksDrive *drive)
|
||||||
return strstr(udisks_drive_get_vendor(drive), "CASIO") != NULL;
|
return strstr(udisks_drive_get_vendor(drive), "CASIO") != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
properties_t ud2_properties(UDisksDrive *drive)
|
struct fxlink_filter ud2_properties(UDisksDrive *drive)
|
||||||
{
|
{
|
||||||
properties_t props = { 0 };
|
struct fxlink_filter props = { 0 };
|
||||||
props.p7 = false;
|
props.p7 = false;
|
||||||
props.mass_storage = true;
|
props.mass_storage = true;
|
||||||
|
|
||||||
|
@ -94,17 +89,17 @@ properties_t ud2_properties(UDisksDrive *drive)
|
||||||
|
|
||||||
gchar const *s = udisks_drive_get_serial(drive);
|
gchar const *s = udisks_drive_get_serial(drive);
|
||||||
/* LINK sends a 12-byte serial number with four leading 0. Remove them */
|
/* LINK sends a 12-byte serial number with four leading 0. Remove them */
|
||||||
if(s && strlen(s) == 12 && !strncmp(s, "0000", 4)) s+= 4;
|
if(s && strlen(s) == 12 && !strncmp(s, "0000", 4)) s += 4;
|
||||||
props.serial_number = (char *)s;
|
props.serial = strdup(s);
|
||||||
|
|
||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ud2_unique_matching(filter_t const *filter, UDisksClient *udc,
|
int ud2_unique_matching(struct fxlink_filter const *filter, UDisksClient *udc,
|
||||||
UDisksManager *udm, UDisksBlock **block_ptr, UDisksDrive **drive_ptr,
|
UDisksManager *udm, UDisksBlock **block_ptr, UDisksDrive **drive_ptr,
|
||||||
UDisksFilesystem **fs_ptr)
|
UDisksFilesystem **fs_ptr)
|
||||||
{
|
{
|
||||||
int status = FILTER_NONE;
|
int status = FXLINK_FILTER_NONE;
|
||||||
bool error;
|
bool error;
|
||||||
|
|
||||||
UDisksBlock *block = NULL;
|
UDisksBlock *block = NULL;
|
||||||
|
@ -112,11 +107,12 @@ int ud2_unique_matching(filter_t const *filter, UDisksClient *udc,
|
||||||
UDisksFilesystem *fs = NULL;
|
UDisksFilesystem *fs = NULL;
|
||||||
|
|
||||||
for_udisks2_devices(it, udc, udm, &error) {
|
for_udisks2_devices(it, udc, udm, &error) {
|
||||||
if(!filter_match(&it.props, filter)) continue;
|
if(!fxlink_filter_match(&it.props, filter))
|
||||||
|
continue;
|
||||||
|
|
||||||
/* Already found a device before */
|
/* Already found a device before */
|
||||||
if(status == FILTER_UNIQUE) {
|
if(status == FXLINK_FILTER_UNIQUE) {
|
||||||
status = FILTER_MULTIPLE;
|
status = FXLINK_FILTER_MULTIPLE;
|
||||||
g_object_unref(fs);
|
g_object_unref(fs);
|
||||||
g_object_unref(drive);
|
g_object_unref(drive);
|
||||||
g_object_unref(block);
|
g_object_unref(block);
|
||||||
|
@ -130,31 +126,37 @@ int ud2_unique_matching(filter_t const *filter, UDisksClient *udc,
|
||||||
block = g_object_ref(it.block);
|
block = g_object_ref(it.block);
|
||||||
drive = g_object_ref(it.drive);
|
drive = g_object_ref(it.drive);
|
||||||
fs = g_object_ref(it.fs);
|
fs = g_object_ref(it.fs);
|
||||||
status = FILTER_UNIQUE;
|
status = FXLINK_FILTER_UNIQUE;
|
||||||
}
|
}
|
||||||
if(error)
|
if(error)
|
||||||
return FILTER_ERROR;
|
return FXLINK_FILTER_ERROR;
|
||||||
|
|
||||||
if(block_ptr) *block_ptr = block;
|
if(block_ptr)
|
||||||
else g_object_unref(block);
|
*block_ptr = block;
|
||||||
|
else if(block)
|
||||||
|
g_object_unref(block);
|
||||||
|
|
||||||
if(drive_ptr) *drive_ptr = drive;
|
if(drive_ptr)
|
||||||
else g_object_unref(drive);
|
*drive_ptr = drive;
|
||||||
|
else if(drive)
|
||||||
|
g_object_unref(drive);
|
||||||
|
|
||||||
if(fs_ptr) *fs_ptr = fs;
|
if(fs_ptr)
|
||||||
else g_object_unref(fs);
|
*fs_ptr = fs;
|
||||||
|
else if(fs)
|
||||||
|
g_object_unref(fs);
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ud2_unique_wait(filter_t const *filter, delay_t *delay, UDisksClient *udc,
|
int ud2_unique_wait(struct fxlink_filter const *filter, delay_t *delay,
|
||||||
UDisksManager *udm, UDisksBlock **block, UDisksDrive **drive,
|
UDisksClient *udc, UDisksManager *udm, UDisksBlock **block,
|
||||||
UDisksFilesystem **fs)
|
UDisksDrive **drive, UDisksFilesystem **fs)
|
||||||
{
|
{
|
||||||
while(true) {
|
while(true) {
|
||||||
int rc = ud2_unique_matching(filter, udc, udm, block, drive, fs);
|
int rc = ud2_unique_matching(filter, udc, udm, block, drive, fs);
|
||||||
if(rc != FILTER_NONE) return rc;
|
if(rc != FXLINK_FILTER_NONE) return rc;
|
||||||
if(delay_cycle(delay)) return FILTER_NONE;
|
if(delay_cycle(delay)) return FXLINK_FILTER_NONE;
|
||||||
udisks_client_settle(udc);
|
udisks_client_settle(udc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,9 +225,9 @@ void ud2_iter_next(ud2_iterator_t *it)
|
||||||
// Main functions
|
// Main functions
|
||||||
//---
|
//---
|
||||||
|
|
||||||
int main_blocks(filter_t *filter, delay_t *delay)
|
int main_blocks(struct fxlink_filter *filter, delay_t *delay)
|
||||||
{
|
{
|
||||||
filter_clean_udisks2(filter);
|
fxlink_filter_clean_udisks2(filter);
|
||||||
|
|
||||||
UDisksClient *udc = NULL;
|
UDisksClient *udc = NULL;
|
||||||
UDisksManager *udm = NULL;
|
UDisksManager *udm = NULL;
|
||||||
|
@ -237,7 +239,8 @@ int main_blocks(filter_t *filter, delay_t *delay)
|
||||||
bool error;
|
bool error;
|
||||||
|
|
||||||
for_udisks2_devices(it, udc, udm, &error) {
|
for_udisks2_devices(it, udc, udm, &error) {
|
||||||
if(!filter_match(&it.props, filter)) continue;
|
if(!fxlink_filter_match(&it.props, filter))
|
||||||
|
continue;
|
||||||
|
|
||||||
if(total_devices > 0) printf("\n");
|
if(total_devices > 0) printf("\n");
|
||||||
|
|
||||||
|
@ -254,8 +257,8 @@ int main_blocks(filter_t *filter, delay_t *delay)
|
||||||
udisks_drive_get_vendor(it.drive),
|
udisks_drive_get_vendor(it.drive),
|
||||||
udisks_drive_get_model(it.drive));
|
udisks_drive_get_model(it.drive));
|
||||||
|
|
||||||
if(it.props.serial_number)
|
if(it.props.serial)
|
||||||
printf(" Serial number: %s\n", it.props.serial_number);
|
printf(" Serial number: %s\n", it.props.serial);
|
||||||
|
|
||||||
gchar const * const * mount_points =
|
gchar const * const * mount_points =
|
||||||
udisks_filesystem_get_mount_points(it.fs);
|
udisks_filesystem_get_mount_points(it.fs);
|
||||||
|
@ -267,7 +270,7 @@ int main_blocks(filter_t *filter, delay_t *delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
printf(" Properties: ");
|
printf(" Properties: ");
|
||||||
properties_print(stdout, &it.props);
|
fxlink_filter_print(stdout, &it.props);
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
total_devices++;
|
total_devices++;
|
||||||
|
@ -280,112 +283,4 @@ int main_blocks(filter_t *filter, delay_t *delay)
|
||||||
return 0;
|
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 {
|
|
||||||
int wstatus;
|
|
||||||
waitpid(pid, &wstatus, 0);
|
|
||||||
|
|
||||||
if(!WIFEXITED(wstatus))
|
|
||||||
err("process did not terminate normally");
|
|
||||||
else if(WEXITSTATUS(wstatus) != 0) {
|
|
||||||
err("process terminated with error %d",
|
|
||||||
WEXITSTATUS(wstatus));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Unmount the filesystem and eject the device if we mounted it */
|
|
||||||
if(mounted_here || options.force_unmount) {
|
|
||||||
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 */
|
#endif /* FXLINK_DISABLE_UDISKS2 */
|
338
fxlink/tui/input.c
Normal file
338
fxlink/tui/input.c
Normal file
|
@ -0,0 +1,338 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
|
||||||
|
#include <fxlink/tui/input.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Text manipulation functions
|
||||||
|
//---
|
||||||
|
|
||||||
|
bool fxlink_TUI_input_init(struct fxlink_TUI_input *in, WINDOW *win,
|
||||||
|
int prealloc_size)
|
||||||
|
{
|
||||||
|
char *data = malloc(prealloc_size + 1);
|
||||||
|
if(!data)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
data[0] = 0;
|
||||||
|
in->data = data;
|
||||||
|
in->size = 0;
|
||||||
|
in->alloc_size = prealloc_size + 1;
|
||||||
|
in->cursor = 0;
|
||||||
|
in->win = win;
|
||||||
|
|
||||||
|
getyx(win, in->wy, in->wx);
|
||||||
|
scrollok(win, false);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_TUI_input_free(struct fxlink_TUI_input *in)
|
||||||
|
{
|
||||||
|
free(in->data);
|
||||||
|
memset(in, 0, sizeof *in);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fxlink_TUI_input_alloc(struct fxlink_TUI_input *in, int n)
|
||||||
|
{
|
||||||
|
if(in->alloc_size >= n + 1)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
/* Always increase the size by at least 16 so we can insert many times in a
|
||||||
|
row without worrying about excessive allocations. */
|
||||||
|
int newsize = (in->alloc_size + 16 > n + 1) ? in->alloc_size + 16 : n + 1;
|
||||||
|
char *newdata = realloc(in->data, newsize);
|
||||||
|
if(!newdata)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
in->data = newdata;
|
||||||
|
in->alloc_size = newsize;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fxlink_TUI_input_insert(struct fxlink_TUI_input *in, int p,
|
||||||
|
char const *str, int n)
|
||||||
|
{
|
||||||
|
if(p < 0 || p > in->size)
|
||||||
|
return false;
|
||||||
|
if(!fxlink_TUI_input_alloc(in, in->size + n))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Move the end of the string (plus the NUL) n bytes forward */
|
||||||
|
memmove(in->data + p + n, in->data + p, in->size - p + 1);
|
||||||
|
memcpy(in->data + p, str, n);
|
||||||
|
in->size += n;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fxlink_TUI_input_delete(struct fxlink_TUI_input *in, int p, int n)
|
||||||
|
{
|
||||||
|
if(n >= in->size - p)
|
||||||
|
n = in->size - p;
|
||||||
|
|
||||||
|
if(n < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Move the end of the string (plus the NUL) n bytes backwards */
|
||||||
|
memmove(in->data + p, in->data + p + n, in->size - n - p + 1);
|
||||||
|
in->size -= n;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--
|
||||||
|
// Rendering functions
|
||||||
|
//---
|
||||||
|
|
||||||
|
bool fxlink_TUI_input_echo(struct fxlink_TUI_input *in, chtype ch)
|
||||||
|
{
|
||||||
|
int x, y, w, h;
|
||||||
|
getyx(in->win, y, x);
|
||||||
|
getmaxyx(in->win, h, w);
|
||||||
|
|
||||||
|
bool needs_scroll = (x == w-1 && y == h-1);
|
||||||
|
bool can_scroll = in->wy > 0;
|
||||||
|
|
||||||
|
if(!needs_scroll) {
|
||||||
|
waddch(in->win, ch);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if(can_scroll) {
|
||||||
|
waddch(in->win, ch);
|
||||||
|
scrollok(in->win, true);
|
||||||
|
scroll(in->win);
|
||||||
|
scrollok(in->win, false);
|
||||||
|
wmove(in->win, y, 0);
|
||||||
|
in->wy--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_TUI_input_clear(struct fxlink_TUI_input *in)
|
||||||
|
{
|
||||||
|
int w, h;
|
||||||
|
getmaxyx(in->win, h, w);
|
||||||
|
|
||||||
|
/* Clear from the end of screen to the start of the input */
|
||||||
|
int current_y = h - 1;
|
||||||
|
|
||||||
|
while(current_y > in->wy) {
|
||||||
|
mvwhline(in->win, current_y, 0, ' ', w);
|
||||||
|
current_y--;
|
||||||
|
}
|
||||||
|
mvwhline(in->win, in->wy, in->wx, ' ', w - in->wx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_TUI_input_redraw(struct fxlink_TUI_input *in)
|
||||||
|
{
|
||||||
|
fxlink_TUI_input_clear(in);
|
||||||
|
|
||||||
|
int cursor_x, cursor_y;
|
||||||
|
|
||||||
|
waddnstr(in->win, in->data, in->cursor);
|
||||||
|
getyx(in->win, cursor_y, cursor_x);
|
||||||
|
waddnstr(in->win, in->data + in->cursor, in->size - in->cursor);
|
||||||
|
|
||||||
|
wmove(in->win, cursor_y, cursor_x);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_TUI_input_clearscreen(struct fxlink_TUI_input *in)
|
||||||
|
{
|
||||||
|
int current_y, current_x;
|
||||||
|
getyx(in->win, current_y, current_x);
|
||||||
|
|
||||||
|
scrollok(in->win, true);
|
||||||
|
wscrl(in->win, in->wy);
|
||||||
|
scrollok(in->win, false);
|
||||||
|
|
||||||
|
wmove(in->win, current_y - in->wy, current_x);
|
||||||
|
in->wy = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CSI {
|
||||||
|
int param1;
|
||||||
|
int param2;
|
||||||
|
char cmd;
|
||||||
|
char record[32];
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct CSI parse_CSI(WINDOW *win, WINDOW *logWindow)
|
||||||
|
{
|
||||||
|
struct CSI csi = { 0 };
|
||||||
|
int i = 0;
|
||||||
|
size_t j = 0;
|
||||||
|
|
||||||
|
csi.param1 = -1;
|
||||||
|
csi.param2 = -1;
|
||||||
|
|
||||||
|
while(!csi.cmd && j < sizeof csi.record) {
|
||||||
|
chtype ch = wgetch(win);
|
||||||
|
if((int)ch == ERR) {
|
||||||
|
wprintw(logWindow, "error: invalid CSI escape: ");
|
||||||
|
waddnstr(logWindow, csi.record, j);
|
||||||
|
waddch(logWindow, '\n');
|
||||||
|
return (struct CSI){ 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
int c = ch & A_CHARTEXT;
|
||||||
|
if(isdigit(c)) {
|
||||||
|
if(i == 0) {
|
||||||
|
csi.param1 = csi.param1 * 10 + (c - '0');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
csi.param2 = csi.param2 * 10 + (c - '0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(c == ';' && i == 0)
|
||||||
|
i = 1;
|
||||||
|
else if(strchr("ABCD", c))
|
||||||
|
csi.cmd = c;
|
||||||
|
else {
|
||||||
|
wprintw(logWindow, "unknow CSI escape\n");
|
||||||
|
return (struct CSI){ 0 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return csi;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fxlink_TUI_input_getch(struct fxlink_TUI_input *in, WINDOW *logWindow)
|
||||||
|
{
|
||||||
|
chtype ch = wgetch(in->win);
|
||||||
|
int c = ch & A_CHARTEXT;
|
||||||
|
struct CSI csi = { 0 };
|
||||||
|
|
||||||
|
/* Parse CSI escapes */
|
||||||
|
if(c == '\e') {
|
||||||
|
chtype next = wgetch(in->win);
|
||||||
|
if((int)next == ERR)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int n = next & A_CHARTEXT;
|
||||||
|
/* CSI */
|
||||||
|
if(n == '[')
|
||||||
|
csi = parse_CSI(in->win, logWindow);
|
||||||
|
/* Scrolling (ignored) */
|
||||||
|
else if(n == 'O' || n == 'P')
|
||||||
|
wgetch(in->win);
|
||||||
|
else
|
||||||
|
wprintw(logWindow, "error: invalid escape start ^[%c\n", n);
|
||||||
|
|
||||||
|
c = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Event left after a SIGWINCH */
|
||||||
|
if(ch == KEY_RESIZE)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* <Backspace>: Remove last letter */
|
||||||
|
if(c == 0x7f) {
|
||||||
|
if(in->cursor > 0) {
|
||||||
|
fxlink_TUI_input_delete(in, in->cursor - 1, 1);
|
||||||
|
in->cursor--;
|
||||||
|
fxlink_TUI_input_redraw(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* C-d: Delete to the right */
|
||||||
|
else if(c == 'D'-64) {
|
||||||
|
if(in->cursor < in->size) {
|
||||||
|
fxlink_TUI_input_delete(in, in->cursor, 1);
|
||||||
|
fxlink_TUI_input_redraw(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* C-b: Back one character */
|
||||||
|
else if(c == 'B'-64) {
|
||||||
|
if(in->cursor > 0) {
|
||||||
|
in->cursor--;
|
||||||
|
fxlink_TUI_input_redraw(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* C-f: Forward one character */
|
||||||
|
else if(c == 'F'-64) {
|
||||||
|
if(in->cursor < in->size) {
|
||||||
|
in->cursor++;
|
||||||
|
fxlink_TUI_input_redraw(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* C-a: Move cursor to start of line */
|
||||||
|
else if(c == 'A'-64) {
|
||||||
|
if(in->cursor != 0) {
|
||||||
|
in->cursor = 0;
|
||||||
|
fxlink_TUI_input_redraw(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* C-e: Move cursor to end of line */
|
||||||
|
else if(c == 'E'-64) {
|
||||||
|
if(in->cursor != in->size) {
|
||||||
|
in->cursor = in->size;
|
||||||
|
fxlink_TUI_input_redraw(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* C-l: Clear the screen (moves prompt to first line) */
|
||||||
|
else if(c == 'L'-64) {
|
||||||
|
fxlink_TUI_input_clearscreen(in);
|
||||||
|
}
|
||||||
|
/* C-k: Kill to end of line */
|
||||||
|
else if(c == 'K'-64) {
|
||||||
|
fxlink_TUI_input_delete(in, in->cursor, in->size - in->cursor);
|
||||||
|
fxlink_TUI_input_redraw(in);
|
||||||
|
}
|
||||||
|
/* <LEFT>: Move cursor to the left */
|
||||||
|
else if(csi.cmd == 'D') {
|
||||||
|
int distance = (csi.param1 >= 0) ? csi.param1 : 1;
|
||||||
|
if(in->cursor > 0) {
|
||||||
|
in->cursor = max(0, in->cursor - distance);
|
||||||
|
fxlink_TUI_input_redraw(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* <RIGHT>: Move cursor to the right */
|
||||||
|
else if(csi.cmd == 'C') {
|
||||||
|
int distance = (csi.param1 >= 0) ? csi.param1 : 1;
|
||||||
|
if(in->cursor < in->size) {
|
||||||
|
in->cursor = min(in->size, in->cursor + distance);
|
||||||
|
fxlink_TUI_input_redraw(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// <DEL>: Delete to the right
|
||||||
|
// Meta-F: Move forward a word
|
||||||
|
// Meta-B: Move backward a word
|
||||||
|
// Meta-<DEL> or Meta-<D>: Kill to end of current word or eat next word
|
||||||
|
// Ctrl-W: Kill to previous whitespace
|
||||||
|
|
||||||
|
else if(c == '\n') {
|
||||||
|
in->cursor = in->size;
|
||||||
|
fxlink_TUI_input_redraw(in);
|
||||||
|
scrollok(in->win, true);
|
||||||
|
waddch(in->win, '\n');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if(c != 0) {
|
||||||
|
if(fxlink_TUI_input_echo(in, ch)) {
|
||||||
|
char c_char = c;
|
||||||
|
fxlink_TUI_input_insert(in, in->cursor, &c_char, 1);
|
||||||
|
in->cursor++;
|
||||||
|
fxlink_TUI_input_redraw(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(csi.cmd) {
|
||||||
|
wprintw(logWindow, "unhandled escape: %c (%d,%d)\n",
|
||||||
|
csi.cmd, csi.param1, csi.param2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Debug input as it is being typed
|
||||||
|
if(c != 0) {
|
||||||
|
wprintw(logWindow, "ch:%04x [%.*s|%.*s]\n",
|
||||||
|
ch, in->cursor, in->data,
|
||||||
|
in->size - in->cursor, in->data + in->cursor);
|
||||||
|
} */
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
452
fxlink/tui/layout.c
Normal file
452
fxlink/tui/layout.c
Normal file
|
@ -0,0 +1,452 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
|
||||||
|
#include <fxlink/tui/layout.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
typedef unsigned int uint;
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Flexbox-like allocation algorithm (copy/pasted from JustUI)
|
||||||
|
//
|
||||||
|
// The basic idea of space redistribution is to give each widget extra space
|
||||||
|
// proportional to their stretch rates in the relevant direction. However, the
|
||||||
|
// addition of maximum size constraints means that widgets can decline some of
|
||||||
|
// the extra space being allocated.
|
||||||
|
//
|
||||||
|
// This system defines the result of expansion as a function of the "expansion
|
||||||
|
// factor". As the expansion factor increases, every widget stretches at a
|
||||||
|
// speed proportional to its stretch rate, until it reaches its maximum size.
|
||||||
|
//
|
||||||
|
// Extra widget size
|
||||||
|
// |
|
||||||
|
// + .-------- Maximum size
|
||||||
|
// | .`
|
||||||
|
// | .` <- Slope: widget stretch rate
|
||||||
|
// |.`
|
||||||
|
// 0 +-------+------> Expansion factor
|
||||||
|
// 0 ^
|
||||||
|
// Breaking point
|
||||||
|
//
|
||||||
|
// The extra space allocated to widgets is the sum of this function for every
|
||||||
|
// widget considered for expansion. Since every widget has a possibly different
|
||||||
|
// breaking point, a maximal interval of expansion factor that has no breaking
|
||||||
|
// point is called a "run". During each run, the slope for the total space
|
||||||
|
// remains constant, and a unit of expansion factor corresponds to one pixel
|
||||||
|
// being allocated in the container. Thus, whenever the expansion factor
|
||||||
|
// increases of (slope), every widget (w) gets (w->stretch) new pixels.
|
||||||
|
//
|
||||||
|
// The functions below simulate the expansion by determining the breaking
|
||||||
|
// points of the widgets and allocating extra space during each run. Once the
|
||||||
|
// total extra space allocated reaches the available space, simulation stops
|
||||||
|
// and the allocation is recorded by assigning actual size to widgets.
|
||||||
|
//---
|
||||||
|
|
||||||
|
/* This "expansion" structure tracks information relating to a single child
|
||||||
|
widget during the space distribution process. */
|
||||||
|
typedef struct {
|
||||||
|
/* Child index */
|
||||||
|
uint8_t id;
|
||||||
|
/* Stretch rate, sum of stretch rates is the "slope" */
|
||||||
|
uint8_t stretch;
|
||||||
|
/* Maximum size augmentation */
|
||||||
|
int16_t max;
|
||||||
|
/* Extra space allocate in the previous runs, in pixels */
|
||||||
|
float allocated;
|
||||||
|
/* Breaking point for the current run, as a number of pixels to distribute
|
||||||
|
to the whole system */
|
||||||
|
float breaking_point;
|
||||||
|
} exp_t;
|
||||||
|
|
||||||
|
/* Determine whether a widget can expand any further. */
|
||||||
|
static bool can_expand(exp_t *e)
|
||||||
|
{
|
||||||
|
return (e->stretch > 0 && e->allocated < e->max);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compute the slope for the current run. */
|
||||||
|
static uint compute_slope(exp_t elements[], size_t n)
|
||||||
|
{
|
||||||
|
uint slope = 0;
|
||||||
|
for(size_t i = 0; i < n; i++) {
|
||||||
|
if(can_expand(&elements[i])) slope += elements[i].stretch;
|
||||||
|
}
|
||||||
|
return slope;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compute the breaking point for every expanding widget. Returns the amount of
|
||||||
|
pixels to allocate in order to reach the next breaking point. */
|
||||||
|
static float compute_breaking_points(exp_t elements[], size_t n, uint slope)
|
||||||
|
{
|
||||||
|
float closest = HUGE_VALF;
|
||||||
|
|
||||||
|
for(size_t i = 0; i < n; i++) {
|
||||||
|
exp_t *e = &elements[i];
|
||||||
|
if(!can_expand(e)) continue;
|
||||||
|
|
||||||
|
/* Up to (e->max - e->allocated) pixels can be added to this widget.
|
||||||
|
With the factor of (slope / e->stretch), we get the number of pixels
|
||||||
|
to add to the container in order to reach the threshold. */
|
||||||
|
e->breaking_point = (e->max - e->allocated) * (slope / e->stretch);
|
||||||
|
closest = fminf(e->breaking_point, closest);
|
||||||
|
}
|
||||||
|
|
||||||
|
return closest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate floating-point space to widgets. This is the core of the
|
||||||
|
distribution system, it produces (e->allocated) for every element. */
|
||||||
|
static void allocate_space(exp_t elements[], size_t n, float available)
|
||||||
|
{
|
||||||
|
/* One iteration per run */
|
||||||
|
while(available > 0) {
|
||||||
|
/* Slope for this run; if zero, no more widget can grow */
|
||||||
|
uint slope = compute_slope(elements, n);
|
||||||
|
if(!slope) break;
|
||||||
|
|
||||||
|
/* Closest breaking point, amount of space to distribute this run */
|
||||||
|
float breaking = compute_breaking_points(elements, n, slope);
|
||||||
|
float run_budget = fminf(breaking, available);
|
||||||
|
|
||||||
|
/* Give everyone their share of run_budget */
|
||||||
|
for(size_t i = 0; i < n; i++) {
|
||||||
|
exp_t *e = &elements[i];
|
||||||
|
if(!can_expand(e)) continue;
|
||||||
|
|
||||||
|
e->allocated += (run_budget * e->stretch) / slope;
|
||||||
|
}
|
||||||
|
|
||||||
|
available -= run_budget;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stable insertion sort: order children by decreasing fractional allocation */
|
||||||
|
static void sort_by_fractional_allocation(exp_t elements[], size_t n)
|
||||||
|
{
|
||||||
|
for(size_t spot = 0; spot < n - 1; spot++) {
|
||||||
|
/* Find the element with the max fractional value in [spot..size] */
|
||||||
|
float max_frac = 0;
|
||||||
|
int max_frac_who = -1;
|
||||||
|
|
||||||
|
for(size_t i = spot; i < n; i++) {
|
||||||
|
exp_t *e = &elements[i];
|
||||||
|
|
||||||
|
float frac = e->allocated - floorf(e->allocated);
|
||||||
|
|
||||||
|
if(max_frac_who < 0 || frac > max_frac) {
|
||||||
|
max_frac = frac;
|
||||||
|
max_frac_who = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Give that element the spot */
|
||||||
|
exp_t temp = elements[spot];
|
||||||
|
elements[spot] = elements[max_frac_who];
|
||||||
|
elements[max_frac_who] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int compare_ids(void const *ptr1, void const *ptr2)
|
||||||
|
{
|
||||||
|
exp_t const *e1 = ptr1;
|
||||||
|
exp_t const *e2 = ptr2;
|
||||||
|
return e1->id - e2->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Round allocations so that they add up to the available space */
|
||||||
|
static void round_allocations(exp_t elements[], size_t n, int available_space)
|
||||||
|
{
|
||||||
|
/* Prepare to give everyone the floor of their allocation */
|
||||||
|
for(size_t i = 0; i < n; i++) {
|
||||||
|
exp_t *e = &elements[i];
|
||||||
|
available_space -= floorf(e->allocated);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sort by decreasing fractional allocation then add one extra pixel to
|
||||||
|
the (available_space) children with highest fractional allocation */
|
||||||
|
sort_by_fractional_allocation(elements, n);
|
||||||
|
|
||||||
|
for(size_t i = 0; i < n; i++) {
|
||||||
|
exp_t *e = &elements[i];
|
||||||
|
e->allocated = floorf(e->allocated);
|
||||||
|
|
||||||
|
if(can_expand(e) && (int)i < available_space) e->allocated += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sort back by IDs for final ordering */
|
||||||
|
qsort(elements, n, sizeof *elements, compare_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
// TUI layout
|
||||||
|
//---
|
||||||
|
|
||||||
|
static struct fxlink_TUI_box *mkbox(void)
|
||||||
|
{
|
||||||
|
struct fxlink_TUI_box *b = calloc(1, sizeof *b);
|
||||||
|
if(b) {
|
||||||
|
b->max_w = 0xffff;
|
||||||
|
b->max_h = 0xffff;
|
||||||
|
b->stretch_x = 1;
|
||||||
|
b->stretch_y = 1;
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fxlink_TUI_box *fxlink_TUI_box_mk_window(char const *title, WINDOW **w)
|
||||||
|
{
|
||||||
|
struct fxlink_TUI_box *b = mkbox();
|
||||||
|
if(b) {
|
||||||
|
b->type = FXLINK_TUI_BOX_WINDOW;
|
||||||
|
b->window.title = title;
|
||||||
|
b->window.win = w;
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct fxlink_TUI_box *mkcontainer(int type,
|
||||||
|
struct fxlink_TUI_box *child, va_list args)
|
||||||
|
{
|
||||||
|
struct fxlink_TUI_box *b = mkbox();
|
||||||
|
if(b) {
|
||||||
|
b->type = type;
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while(child && i < FXLINK_TUI_BOX_MAXSIZE) {
|
||||||
|
b->children[i++] = child;
|
||||||
|
child = va_arg(args, struct fxlink_TUI_box *);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fxlink_TUI_box *fxlink_TUI_box_mk_vertical(
|
||||||
|
struct fxlink_TUI_box *child1, ...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
va_start(args, child1);
|
||||||
|
return mkcontainer(FXLINK_TUI_BOX_VERTICAL, child1, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fxlink_TUI_box *fxlink_TUI_box_mk_horizontal(
|
||||||
|
struct fxlink_TUI_box *child1, ...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
va_start(args, child1);
|
||||||
|
return mkcontainer(FXLINK_TUI_BOX_HORIZONTAL, child1, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_TUI_box_minsize(struct fxlink_TUI_box *box, int min_w, int min_h)
|
||||||
|
{
|
||||||
|
box->min_w = min_w;
|
||||||
|
box->min_h = min_h;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_TUI_box_maxsize(struct fxlink_TUI_box *box, int max_w, int max_h)
|
||||||
|
{
|
||||||
|
box->max_w = max_w;
|
||||||
|
box->max_h = max_h;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_TUI_box_stretch(struct fxlink_TUI_box *box, int stretch_x,
|
||||||
|
int stretch_y, bool force)
|
||||||
|
{
|
||||||
|
box->stretch_x = stretch_x;
|
||||||
|
box->stretch_y = stretch_y;
|
||||||
|
box->stretch_force = force;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_TUI_box_print(FILE *fp, struct fxlink_TUI_box const *b, int level)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < level * 4; i++)
|
||||||
|
fputc(' ', fp);
|
||||||
|
|
||||||
|
fprintf(fp, "type=");
|
||||||
|
if(b->type == FXLINK_TUI_BOX_WINDOW)
|
||||||
|
fprintf(fp, "WINDOW '%s'", b->window.title);
|
||||||
|
if(b->type == FXLINK_TUI_BOX_VERTICAL)
|
||||||
|
fprintf(fp, "VERTICAL");
|
||||||
|
if(b->type == FXLINK_TUI_BOX_HORIZONTAL)
|
||||||
|
fprintf(fp, "HORIZONTAL");
|
||||||
|
|
||||||
|
fprintf(fp, " x=%d y=%d w=", b->x, b->y);
|
||||||
|
|
||||||
|
if(b->min_w > 0)
|
||||||
|
fprintf(fp, "(%d)<", b->min_w);
|
||||||
|
fprintf(fp, "%d", b->w);
|
||||||
|
if(b->max_w < 0xffff)
|
||||||
|
fprintf(fp, "<(%d)", b->max_w);
|
||||||
|
|
||||||
|
fprintf(fp, " h=");
|
||||||
|
|
||||||
|
if(b->min_h > 0)
|
||||||
|
fprintf(fp, "(%d)<", b->min_h);
|
||||||
|
fprintf(fp, "%d", b->h);
|
||||||
|
if(b->max_h < 0xffff)
|
||||||
|
fprintf(fp, "<(%d)", b->max_h);
|
||||||
|
|
||||||
|
fprintf(fp, "\n");
|
||||||
|
|
||||||
|
if(b->type != FXLINK_TUI_BOX_WINDOW) {
|
||||||
|
for(int i = 0; i < FXLINK_TUI_BOX_MAXSIZE && b->children[i]; i++)
|
||||||
|
fxlink_TUI_box_print(fp, b->children[i], level+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void box_do_layout(struct fxlink_TUI_box *box)
|
||||||
|
{
|
||||||
|
if(box->type == FXLINK_TUI_BOX_WINDOW)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int horiz = (box->type == FXLINK_TUI_BOX_HORIZONTAL);
|
||||||
|
size_t child_count = 0;
|
||||||
|
while(child_count < FXLINK_TUI_BOX_MAXSIZE && box->children[child_count])
|
||||||
|
child_count++;
|
||||||
|
int spacing = 1;
|
||||||
|
|
||||||
|
/* Content width and height */
|
||||||
|
int cw = box->w;
|
||||||
|
int ch = box->h;
|
||||||
|
/* Allocatable width and height (which excludes spacing) */
|
||||||
|
int total_spacing = (child_count - 1) * spacing;
|
||||||
|
int aw = cw - (horiz ? total_spacing : 0);
|
||||||
|
int ah = ch - (horiz ? 0 : total_spacing);
|
||||||
|
/* Length along the main axis, including spacing */
|
||||||
|
int length = 0;
|
||||||
|
|
||||||
|
/* Expanding widgets' information for extra space distribution */
|
||||||
|
size_t n = child_count;
|
||||||
|
exp_t elements[n];
|
||||||
|
|
||||||
|
for(size_t i = 0; i < child_count; i++) {
|
||||||
|
struct fxlink_TUI_box *child = box->children[i];
|
||||||
|
|
||||||
|
/* Maximum size to enforce: this is the acceptable size closest to our
|
||||||
|
allocatable size */
|
||||||
|
int max_w = clamp(aw, child->min_w, child->max_w);
|
||||||
|
int max_h = clamp(ah, child->min_h, child->max_h);
|
||||||
|
|
||||||
|
/* Start by setting every child to an acceptable size */
|
||||||
|
child->w = clamp(child->w, child->min_w, max_w);
|
||||||
|
child->h = clamp(child->h, child->min_h, max_h);
|
||||||
|
|
||||||
|
/* Initialize expanding widgets' information */
|
||||||
|
elements[i].id = i;
|
||||||
|
elements[i].allocated = 0.0f;
|
||||||
|
elements[i].breaking_point = -1.0f;
|
||||||
|
|
||||||
|
/* Determine natural length along the container, and stretch child
|
||||||
|
along the perpendicular direction if possible */
|
||||||
|
|
||||||
|
if(i > 0)
|
||||||
|
length += spacing;
|
||||||
|
if(horiz) {
|
||||||
|
length += child->w;
|
||||||
|
if(child->stretch_y > 0) child->h = max_h;
|
||||||
|
|
||||||
|
elements[i].stretch = child->stretch_x;
|
||||||
|
elements[i].max = max(max_w - child->w, 0);
|
||||||
|
|
||||||
|
if(child->stretch_force && child->stretch_x > 0)
|
||||||
|
elements[i].max = max(aw - child->w, 0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
length += child->h;
|
||||||
|
if(child->stretch_x > 0) child->w = max_w;
|
||||||
|
|
||||||
|
elements[i].stretch = child->stretch_y;
|
||||||
|
elements[i].max = max(max_h - child->h, 0);
|
||||||
|
|
||||||
|
if(child->stretch_force && child->stretch_y > 0)
|
||||||
|
elements[i].max = max(ah - child->h, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Distribute extra space along the line */
|
||||||
|
int extra_space = (horiz ? cw : ch) - length;
|
||||||
|
allocate_space(elements, n, extra_space);
|
||||||
|
round_allocations(elements, n, extra_space);
|
||||||
|
|
||||||
|
/* Update widgets for extra space */
|
||||||
|
for(size_t i = 0; i < n; i++) {
|
||||||
|
exp_t *e = &elements[i];
|
||||||
|
struct fxlink_TUI_box *child = box->children[e->id];
|
||||||
|
|
||||||
|
if(horiz)
|
||||||
|
child->w += e->allocated;
|
||||||
|
else
|
||||||
|
child->h += e->allocated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Position everyone */
|
||||||
|
int position = 0;
|
||||||
|
|
||||||
|
for(size_t i = 0; i < n; i++) {
|
||||||
|
exp_t *e = &elements[i];
|
||||||
|
struct fxlink_TUI_box *child = box->children[e->id];
|
||||||
|
|
||||||
|
if(horiz) {
|
||||||
|
child->x = box->x + position;
|
||||||
|
child->y = box->y + (ch - child->h) / 2;
|
||||||
|
position += child->w + spacing;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
child->x = box->x + (cw - child->w) / 2;
|
||||||
|
child->y = box->y + position;
|
||||||
|
position += child->h + spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < FXLINK_TUI_BOX_MAXSIZE && box->children[i]; i++)
|
||||||
|
box_do_layout(box->children[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_TUI_box_layout(struct fxlink_TUI_box *b,
|
||||||
|
int x, int y, int w, int h)
|
||||||
|
{
|
||||||
|
b->x = x + 1;
|
||||||
|
b->y = y + 1;
|
||||||
|
b->w = w - 2;
|
||||||
|
b->h = h - 2;
|
||||||
|
return box_do_layout(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool box_apply(struct fxlink_TUI_box *box,
|
||||||
|
int rx, int ry, int rw, int rh)
|
||||||
|
{
|
||||||
|
if(box->type == FXLINK_TUI_BOX_WINDOW) {
|
||||||
|
if(!box->window.win)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
/* Ensure window is of non-zero size and in-bounds */
|
||||||
|
int x = clamp(box->x, rx, rx + rw - 1);
|
||||||
|
int y = clamp(box->y, ry, ry + rh - 1);
|
||||||
|
int w = clamp(box->w, 1, rw - (x - rx));
|
||||||
|
int h = clamp(box->h, 1, rh - (y - ry));
|
||||||
|
|
||||||
|
if(!*box->window.win) {
|
||||||
|
*box->window.win = newwin(h, w, y, x);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
wresize(*box->window.win, h, w);
|
||||||
|
mvwin(*box->window.win, y, x);
|
||||||
|
}
|
||||||
|
return (*box->window.win != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
for(int i = 0; i < FXLINK_TUI_BOX_MAXSIZE && box->children[i]; i++)
|
||||||
|
success = success && box_apply(box->children[i], rx, ry, rw, rh);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fxlink_TUI_apply_layout(struct fxlink_TUI_box *root)
|
||||||
|
{
|
||||||
|
return box_apply(root, root->x, root->y, root->w, root->h);
|
||||||
|
}
|
108
fxlink/tui/render.c
Normal file
108
fxlink/tui/render.c
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
||||||
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
||||||
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
||||||
|
//---------------------------------------------------------------------------//
|
||||||
|
|
||||||
|
#include <fxlink/tui/render.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
void aprint(WINDOW *win, int attr, char const *format, ...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
wattron(win, attr);
|
||||||
|
vw_printw(win, format, args);
|
||||||
|
wattroff(win, attr);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fprint(WINDOW *win, int display_fmt, char const *format, ...)
|
||||||
|
{
|
||||||
|
int attr = fmt_to_ncurses_attr(display_fmt);
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
wattron(win, attr);
|
||||||
|
vw_printw(win, format, args);
|
||||||
|
wattroff(win, attr);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
int fmt_to_ncurses_attr(int format)
|
||||||
|
{
|
||||||
|
/* Get the color pair for FG/BG following our custom encoding (which is
|
||||||
|
compatible with the default for colors without a background) */
|
||||||
|
int FG = fmt_FG(format);
|
||||||
|
int BG = fmt_BG(format);
|
||||||
|
int attr = COLOR_PAIR(9*BG + FG);
|
||||||
|
|
||||||
|
if(fmt_BOLD(format))
|
||||||
|
attr |= A_BOLD;
|
||||||
|
if(fmt_DIM(format))
|
||||||
|
attr |= A_DIM;
|
||||||
|
if(fmt_ITALIC(format))
|
||||||
|
attr |= A_ITALIC;
|
||||||
|
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum { TOP=1, RIGHT=2, BOTTOM=4, LEFT=8 };
|
||||||
|
|
||||||
|
static void TUI_add_borders(int x, int y, int directions)
|
||||||
|
{
|
||||||
|
chtype borders[16] = {
|
||||||
|
' ', '\0', '\0', ACS_LLCORNER,
|
||||||
|
'\0', ACS_VLINE, ACS_ULCORNER, ACS_LTEE,
|
||||||
|
'\0', ACS_LRCORNER, ACS_HLINE, ACS_BTEE,
|
||||||
|
ACS_URCORNER, ACS_RTEE, ACS_TTEE, ACS_PLUS,
|
||||||
|
};
|
||||||
|
|
||||||
|
chtype ch = mvinch(y, x);
|
||||||
|
|
||||||
|
int dirs = 0;
|
||||||
|
for(int i = 0; i < 16; i++) {
|
||||||
|
if(borders[i] == ch)
|
||||||
|
dirs = i;
|
||||||
|
}
|
||||||
|
mvaddch(y, x, borders[dirs | directions]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_TUI_render_borders(struct fxlink_TUI_box const *b)
|
||||||
|
{
|
||||||
|
int x = b->x, y = b->y;
|
||||||
|
|
||||||
|
if(b->type == FXLINK_TUI_BOX_WINDOW) {
|
||||||
|
for(int dx = 0; dx < b->w; dx++) {
|
||||||
|
TUI_add_borders(x+dx, y-1, LEFT | RIGHT);
|
||||||
|
TUI_add_borders(x+dx, y+b->h, LEFT | RIGHT);
|
||||||
|
}
|
||||||
|
for(int dy = 0; dy < b->h; dy++) {
|
||||||
|
TUI_add_borders(x-1, y+dy, TOP | BOTTOM);
|
||||||
|
TUI_add_borders(x+b->w, y+dy, TOP | BOTTOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
TUI_add_borders(x-1, y-1, RIGHT | BOTTOM);
|
||||||
|
TUI_add_borders(x+b->w, y-1, LEFT | BOTTOM);
|
||||||
|
TUI_add_borders(x-1, y+b->h, RIGHT | TOP);
|
||||||
|
TUI_add_borders(x+b->w, y+b->h, LEFT | TOP);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for(int i = 0; i < FXLINK_TUI_BOX_MAXSIZE && b->children[i]; i++)
|
||||||
|
fxlink_TUI_render_borders(b->children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fxlink_TUI_render_titles(struct fxlink_TUI_box const *box)
|
||||||
|
{
|
||||||
|
if(box->type == FXLINK_TUI_BOX_WINDOW) {
|
||||||
|
attron(A_BOLD);
|
||||||
|
mvaddch(box->y-1, box->x, ' ');
|
||||||
|
addstr(box->window.title);
|
||||||
|
addch(' ');
|
||||||
|
attroff(A_BOLD);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(int i = 0; i < FXLINK_TUI_BOX_MAXSIZE && box->children[i]; i++)
|
||||||
|
fxlink_TUI_render_titles(box->children[i]);
|
||||||
|
}
|
159
fxlink/usb.c
159
fxlink/usb.c
|
@ -1,159 +0,0 @@
|
||||||
#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
137
fxlink/usb.h
|
@ -1,137 +0,0 @@
|
||||||
//---
|
|
||||||
// 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 */
|
|
|
@ -1,61 +0,0 @@
|
||||||
#include "util.h"
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
char *gen_file_name(char const *path, char const *name, char const *suffix)
|
|
||||||
{
|
|
||||||
char *filename = NULL;
|
|
||||||
int counter = 1;
|
|
||||||
|
|
||||||
time_t time_raw;
|
|
||||||
struct tm time_bd;
|
|
||||||
time(&time_raw);
|
|
||||||
localtime_r(&time_raw, &time_bd);
|
|
||||||
|
|
||||||
while(1) {
|
|
||||||
asprintf(&filename, "%s/fxlink-%.16s-%04d.%02d.%02d-%02dh%02d-%d.%s",
|
|
||||||
path, name, time_bd.tm_year + 1900, time_bd.tm_mon + 1,
|
|
||||||
time_bd.tm_mday, time_bd.tm_hour, time_bd.tm_min, counter, suffix);
|
|
||||||
if(!filename) continue;
|
|
||||||
|
|
||||||
/* Try to find a name for a file that doesn't exist */
|
|
||||||
if(access(filename, F_OK) == -1) break;
|
|
||||||
|
|
||||||
free(filename);
|
|
||||||
counter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
//---
|
|
||||||
// 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_ARGS__); \
|
|
||||||
1; \
|
|
||||||
})
|
|
||||||
/* Fatal error that includes a libusb error message */
|
|
||||||
#define libusb_err(rc, fmt, ...) ({ \
|
|
||||||
fprintf(stderr, "error: " fmt ": %s\n", ##__VA_ARGS__, \
|
|
||||||
libusb_strerror(rc)); \
|
|
||||||
1; \
|
|
||||||
})
|
|
||||||
/* Warning message */
|
|
||||||
#define wrn(fmt, ...) \
|
|
||||||
fprintf(stderr, "warning: " fmt "\n", ##__VA_ARGS__)
|
|
||||||
|
|
||||||
/* Warning that includes a libusb error message */
|
|
||||||
#define libusb_wrn(rc, fmt, ...) \
|
|
||||||
fprintf(stderr, "error: " fmt ": %s\n", ##__VA_ARGS__, \
|
|
||||||
libusb_strerror(rc))
|
|
||||||
|
|
||||||
//---
|
|
||||||
// File name generation
|
|
||||||
//---
|
|
||||||
|
|
||||||
/* gen_file_name(): Generate a unique timestamp-based file name
|
|
||||||
|
|
||||||
This function generates a unique name for a file to be stored in [path],
|
|
||||||
with the specified [name] component, and the provided [suffix]. The
|
|
||||||
generated path looks like
|
|
||||||
|
|
||||||
<path>/fxlink-<name>-2021.05.09-19h23-1.<suffix>
|
|
||||||
|
|
||||||
with the [-1] suffix being chosen as to avoid overriding extisting files.
|
|
||||||
Returns a newly-allocated string to be freed after use. */
|
|
||||||
char *gen_file_name(char const *path, char const *name, char const *suffix);
|
|
||||||
|
|
||||||
//---
|
|
||||||
// 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