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:
Lephenixnoir 2023-03-03 00:29:00 +01:00
parent d6ed47b133
commit 9d30377d90
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
45 changed files with 4608 additions and 1707 deletions

View file

@ -15,6 +15,7 @@ endif()
if(NOT FXLINK_DISABLE_SDL2)
pkg_check_modules(sdl2 REQUIRED sdl2 IMPORTED_TARGET)
endif()
pkg_check_modules(ncurses REQUIRED ncurses IMPORTED_TARGET)
set(CMAKE_INSTALL_MESSAGE LAZY)
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_link_libraries(fxgxa PkgConfig::libpng)
# fxg1a as a symlink (for compatibility=
# fxg1a as a symlink (for compatibility)
add_custom_target(fxg1a ALL
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")
# fxlink
configure_file(fxlink/config.h.in "${BIN}/include/fxlink/config.h")
add_executable(fxlink fxlink/usb.c fxlink/filter.c fxlink/interactive.c fxlink/push.c
fxlink/main.c fxlink/png.c fxlink/properties.c fxlink/ud2.c fxlink/util.c
fxlink/protocol.c fxlink/sdl2.c)
target_link_libraries(fxlink PkgConfig::libpng PkgConfig::libusb)
target_include_directories(fxlink PRIVATE "${BIN}/include/fxlink")
configure_file(fxlink/include/fxlink/config.h.in
"${BIN}/include/fxlink/config.h")
add_executable(fxlink
fxlink/defs.c
fxlink/devices.c
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)
target_link_libraries(fxlink PkgConfig::udisks2)
endif()

View file

@ -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
View 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
View 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;
}
}

View file

@ -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 <ctype.h>
#include <stdlib.h>
//---
// Property parser
// Filter parser
//---
/* skip_spaces(): Skip spaces, returns true if end of string is reached */
bool skip_spaces(char const **input)
/* Identify property separating characters to be skipped */
static bool issep(int c)
{
while(isspace(**input)) (*input)++;
return (**input == 0);
return (c == ' ' || c == '\t' || c == '\n' || c == ',');
}
/* isword(): Identify valid word characters for the filter */
bool isword(int c)
/* Identify valid word characters for the filter */
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 */
char *read_word(char const **input)
/* Copy the next word in the string, assumes word is non-empty */
static char *read_word(char const **input)
{
char const *str = *input;
while(**input && isword(**input)) (*input)++;
return strndup(str, *input - str);
}
enum {
T_END, /* End of string */
T_PROP, /* Property; (*name) and (*value) are set */
T_COMMA = ',', /* Comma character (property separator) */
T_SEMI = ';', /* Semicolon character (option separator) */
};
/* lex(): Read a token from the input source
Returns the token type, updates (*input) and sets (*name) and (*value) to
either NULL or some freshly-allocated copies of the name and (optional)
value of the property (always NULL unless T_PROP is returned). The caller
should free() both. */
static int lex(char const **input, char **name, char **value)
/* Reads a property from the input source. Advances *input and sets *name and
*value to newly-allocated copies of the name and (optional) value of the
property (T_PROP). Both should be free()'d after use. At the end of the
input, returns false and sets *name = *value = NULL. */
static bool read_property(char const **input, char **name, char **value)
{
*name = *value = NULL;
if(skip_spaces(input)) return T_END;
if(**input == ',' || **input == ';') {
while(issep(**input))
(*input)++;
return (*input)[-1];
}
if(!**input)
return false;
if(!isword(**input)) {
wrn("expected property name in filter, skipping '%c'", **input);
(*input)++;
return lex(input, name, value);
elog("expected property name in filter, found '%c'; stopping\n",
**input);
return false;
}
*name = read_word(input);
if(skip_spaces(input) || **input != '=')
return T_PROP;
(*input)++;
if(skip_spaces(input))
wrn("no value after '=' in filter property '%s'", *name);
else if(!isword(**input))
wrn("ignoring invalid value for filter property '%s'", *name);
else
if(**input == '=') {
(*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;
int t;
/* Create an initial filter with a single otion */
filter_t *filter = malloc(sizeof *filter);
if(!filter) return NULL;
filter->options = calloc(1, sizeof(properties_t));
if(!filter->options) {
free(filter);
struct fxlink_filter *filter = calloc(1, sizeof *filter);
if(!filter)
return NULL;
}
filter->length = 1;
/* Current option */
properties_t *option = &filter->options[0];
while((t = lex(&input, &name, &value)) != T_END) {
/* Ignore property separators (tokens are already separated) */
if(t == ',') continue;
/* Add a new option in the filter */
if(t == ';') {
size_t new_size = (filter->length + 1) * sizeof(properties_t);
properties_t *new_options = realloc(filter->options, new_size);
if(!new_options) continue;
filter->options = new_options;
option = &filter->options[filter->length++];
*option = (properties_t){ 0 };
continue;
}
while(read_property(&input, &name, &value)) {
/* Add a new property to the current option */
if(!strcmp(name, "p7") && !value)
option->p7 = true;
filter->p7 = true;
else if(!strcmp(name, "mass_storage") && !value)
option->mass_storage = true;
filter->mass_storage = true;
else if(!strcmp(name, "series_cg") && !value)
option->series_cg = true;
filter->series_cg = true;
else if(!strcmp(name, "series_g3") && !value)
option->series_g3 = true;
else if(!strcmp(name, "serial_number") && value) {
option->serial_number = strdup(value);
}
else wrn("ignoring invalid filter property: '%s' %s value", name,
value ? "with" : "without");
filter->series_g3 = true;
else if(!strcmp(name, "intf_fxlink") && !value)
filter->intf_fxlink = true;
else if(!strcmp(name, "intf_cesg502") && !value)
filter->intf_cesg502 = true;
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(value);
}
return filter;
}
void filter_free(filter_t *filter)
{
if(!filter) return;
for(size_t i = 0; i < filter->length; i++)
free(filter->options[i].serial_number);
free(filter->options);
free(filter);
}
//---
// Filtering API
//---
void filter_clean_libusb(filter_t *filter)
void fxlink_filter_clean_libusb(struct fxlink_filter *filter)
{
if(!filter) return;
if(!filter)
return;
for(size_t i = 0; i < filter->length; i++) {
properties_t *prop = &filter->options[i];
/* Suppress series_cg and series_g3, which are based off the USB Mass
Storage metadata provided only by UDisks2 */
if(prop->series_cg) {
wrn("ignoring series_cg in libusb filter (cannot be detected)");
prop->series_cg = false;
}
if(prop->series_g3) {
wrn("ignoring series_g3 in libusb filter (cannot be detected)");
prop->series_g3 = false;
}
/* Suppress series_cg and series_g3, which are based off the SCSI metadata
provided only to UDisks2 */
if(filter->series_cg) {
wlog("ignoring series_cg in libusb filter (cannot be detected)\n");
filter->series_cg = false;
}
if(filter->series_g3) {
wlog("ignoring series_g3 in libusb filter (cannot be detected)\n");
filter->series_g3 = false;
}
}
void filter_clean_udisks2(filter_t *filter)
void fxlink_filter_clean_udisks2(struct fxlink_filter *filter)
{
/* Every property can be used */
(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 */
if(!filter || !filter->length)
if(!filter)
return true;
for(size_t i = 0; i < filter->length; i++) {
if(properties_match(props, &filter->options[i]))
return true;
}
return false;
if(filter->p7 && !props->p7)
return false;
if(filter->mass_storage && !props->mass_storage)
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(...) { \
if(sep) fprintf(fp, ", "); \
@ -186,9 +155,26 @@ void filter_print(FILE *fp, filter_t const *filter)
sep = true; \
}
for(size_t i = 0; i < filter->length; i++) {
if(i > 0) printf("; ");
properties_t *prop = &filter->options[i];
properties_print(fp, prop);
}
bool sep = false;
if(filter->p7)
output("p7");
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);
}

View file

@ -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 */

View file

@ -1,36 +1,42 @@
//---
// fxlink:fxlink - Application logic
//---
#ifndef FXLINK_FXLINK_H
#define FXLINK_FXLINK_H
//---------------------------------------------------------------------------//
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
// |:::| Made by Lephe' as part of the fxSDK. //
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
//---------------------------------------------------------------------------//
// fxlink.fxlink: Options and mode functions
#pragma once
#include <fxlink/filter.h>
#include <libusb.h>
#include "filter.h"
#include "util.h"
struct fxlink_options
{
bool quiet;
bool force_unmount;
/* Global and command-line options. */
struct fxlink_options {
/* If not NULL, gets a copy of all text messages received in either
interactive mode */
FILE *log_file;
/* Extra details (mainly interactive messages) */
bool verbose;
};
extern struct fxlink_options options;
/* 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 */
int main_blocks(filter_t *filter, delay_t *delay);
int main_blocks(struct fxlink_filter *filter, delay_t *delay);
/* 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 */
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 */
int main_push(filter_t *filter, delay_t *delay, libusb_context *context, char **files);
#endif /* FXLINK_FXLINK_H */
int main_push(struct fxlink_filter *filter, delay_t *delay,
libusb_context *context, char **files);

View 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@"

View 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);

View 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);

View 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);

View 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))

View 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);

View 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);

View 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);

View file

@ -1,33 +1,39 @@
//---
// fxlink:ud2 - UDisks2 functions
//---
#ifndef FXLINK_UD2_H
#define FXLINK_UD2_H
//---------------------------------------------------------------------------//
// ==>/[_]\ 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.udisks2: Utilities based on the UDisks2 library
#pragma once
#include <fxlink/config.h>
#ifndef FXLINK_DISABLE_UDISKS2
#include <udisks/udisks.h>
#include "config.h"
#include "properties.h"
#include "filter.h"
#include "util.h"
#include <fxlink/filter.h>
/* 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
Similar to usb_unique_matching(), please refer to "usb.h" for details.
There are just many more inputs and outputs. */
int ud2_unique_matching(filter_t const *filter, UDisksClient *udc,
int ud2_unique_matching(struct fxlink_filter const *filter, UDisksClient *udc,
UDisksManager *udm, UDisksBlock **block, UDisksDrive **drive,
UDisksFilesystem **fs);
/* ud2_unique_wait(): Wait for a device matching the provided filter to connect
Like usb_unique_wait(), please see "usb.h" for details. */
int ud2_unique_wait(filter_t const *filter, delay_t *delay, UDisksClient *udc,
UDisksManager *udm, UDisksBlock **block, UDisksDrive **drive,
UDisksFilesystem **fs);
Returns an FXLINK_FILTER_* code. If a unique device is found, sets *udc,
*udm, *block, *drive and *fs accordingly. */
int ud2_unique_wait(struct fxlink_filter const *filter, delay_t *delay,
UDisksClient *udc, UDisksManager *udm, UDisksBlock **block,
UDisksDrive **drive, UDisksFilesystem **fs);
//---
// Iteration on UDisks2 devices
@ -39,7 +45,7 @@ typedef struct {
UDisksDrive *drive;
UDisksFilesystem *fs;
/* Device properties */
properties_t props;
struct fxlink_filter props;
/* Whether the iteration has finished */
bool done;
@ -66,5 +72,3 @@ void ud2_iter_next(ud2_iterator_t *it);
!NAME.done; ud2_iter_next(&NAME)) if(!NAME.done)
#endif /* FXLINK_DISABLE_UDISKS2 */
#endif /* FXLINK_UD2_H */

View 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);

View 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);

View 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);

View file

@ -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
View 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;
}

View file

@ -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 "util.h"
#include "properties.h"
#include "filter.h"
#include "usb.h"
#include <fxlink/filter.h>
#include <fxlink/logging.h>
#include <libusb.h>
#include <getopt.h>
@ -11,71 +14,64 @@
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
int main_test(libusb_device *device, libusb_context *context);
#include <locale.h>
static const char *help_string =
"usage: %1$s -l [options...]\n"
" %1$s -b [options...]\n"
" %1$s -s [options...] <FILES...>\n"
" %1$s --test\n"
"usage: %1$s (-l|-b|-t) [General options]\n"
" %1$s -i [-r] [--fxlink-log[=<FILE>]] [General options]\n"
" %1$s -p <FILE> [General options]\n"
" %1$s -s <FILES>... [General options]\n"
"\n"
"fxlink interacts with CASIO calculators of the fx-9860G and fx-CG 50 series\n"
"over the USB port, through mass storage and custom USB protocols. Depending\n"
"on the mode, fxlink uses libusb (for discovery and USB communication)or\n"
"the UDisks2 library (to mount and use Mass Storage devices).\n"
"fxlink interacts with CASIO calculators of the fx and fx-CG families over\n"
"the USB port, using libusb. It can also transfer files for Mass Storage\n"
"calculators using UDisks2.\n"
"\n"
"Operating modes:\n"
" -l, --list List detected calculators on the USB ports (libusb)\n"
" -b, --blocks List detected Mass Storage filesystems (udisks2)\n"
" -s, --send Send a file to a Mass Storage calculator (udisks2)\n"
" -i, --interactive Interactive messaging with a gint add-in (libusb)\n"
" -p, --push Push a .bin file to the Add-In Push app (libusb)\n"
"Standard (libusb) modes:\n"
" -l, --list List detected calculators on the USB ports\n"
" -i, --interactive Messaging with a gint add-in (calc -> PC only)\n"
" -t, --tui TUI interactive mode\n"
" -p, --push Push a .bin file to the Add-In Push app\n"
"\n"
"Mass Storage (UDisks2) modes:\n"
" -b, --blocks List detected Mass Storage calculators\n"
" -s, --send Send a file to a Mass Storage calc and unmount it\n"
"\n"
"General options:\n"
" -w DELAY Wait up to this many seconds for a calculator to\n"
" connect. If DELAY is unspecified, wait indefinitely.\n"
" -f FILTER Filter which calculators can be detected and used\n"
" --libusb-log=LEVEL libusb log level: NONE, ERROR, WARNING, INFO, DEBUG\n"
" --fxlink-log[=FILE] Log fxlink text messages into FILE (append mode). If\n"
" FILE is missing, a timestamp-name will be generated.\n"
" -q, --quiet Quite mode (minimum verbosity)\n"
" -u, --unmount In -s mode, always unmount the disk, even if it was\n"
" mounted by someone else\n"
" -r, --repeat In interactive mode, reconnect infinitely if the\n"
" calculator disconnects (implies -w)\n"
" -v, --verbose Increased verbosity (mostly in -i/-t)\n"
" -w <SECONDS> Wait this many seconds for a calculator to connect\n"
" -w Wait indefinitely for a calculator to connect\n"
" -f <FILTER> Filter which calculators we connect to (see below)\n"
" --libusb-log=LEVEL libusb log level (NONE, ERROR, WARNING, INFO, DEBUG)\n"
"\n"
"Mode-specific options:\n"
" --fxlink-log[=FILE] -i: Append fxlink text messages to FILE. Without\n"
" argument, a unique name is generated.\n"
" -r, --repeat -i: Reconnect if the calc disconnects (implies -w)\n"
"\n"
"Device filters:\n"
" A device filter is a comma-separated list of properties that a device has\n"
" to match in order to be listed or used, such as 'p7,serial=00000001'.\n"
" Several filters can be separated with a semicolon, in which case a device\n"
" will be considered as long as it matches one of the filters. For example,\n"
" 'p7 ; mass_storage,serial=IGQcGRe9'.\n"
"\n"
" The following properties are defined; the libraries in which each can be\n"
" detected and used is indicated in brackets.\n"
" p7 Matches Protocol 7 calculators (all the FX models\n"
" except the G-III). [libusb, udisks2]\n"
" mass_storage Matches Mass Storage calculators (the CG series and\n"
" the G-III). [libusb, udisks2]\n"
" series_cg Matches CG-series calculators. [udisks2]\n"
" series_g3 Matches G-III series calculators. [udisks2]\n"
" serial_number=ID Matches this specific serial number. Requires write\n"
" access to the device in libusb. [libusb, udisks2]\n";
" A device filter narrows down what devices we list or connect to by\n"
" requiring a list of properties, such as \"p7,serial=00000001\". The\n"
" following properties can be tested:\n"
" - p7 Protocol 7 calcs (all fx models that use FA-124)\n"
" - mass_storage Mass Storage calcs (fx-CG models and the G-III)\n"
" - series_cg fx-CG models [udisks2 only]\n"
" - series_g3 G-III models [udisks2 only]\n"
" - serial=<SERIAL> This serial number (needs write access in libusb)\n";
/* Global options */
struct fxlink_options options;
int main(int argc, char **argv)
{
int rc=1, mode=0, error=0, option=0, loglevel=LIBUSB_LOG_LEVEL_ERROR;
int rc=1, mode=0, error=0, option=0, loglevel=LIBUSB_LOG_LEVEL_WARNING;
delay_t delay = delay_seconds(0);
filter_t *filter = NULL;
struct fxlink_filter *filter = NULL;
bool repeat = false;
options.quiet = false;
options.force_unmount = false;
options.log_file = NULL;
options.verbose = false;
setlocale(LC_ALL, "");
//---
// Command-line argument parsing
@ -88,16 +84,20 @@ int main(int argc, char **argv)
{ "blocks", no_argument, NULL, 'b' },
{ "send", no_argument, NULL, 's' },
{ "interactive", no_argument, NULL, 'i' },
{ "tui", no_argument, NULL, 't' },
{ "push", no_argument, NULL, 'p' },
{ "libusb-log", required_argument, NULL, LIBUSB_LOG },
{ "quiet", no_argument, NULL, 'q' },
{ "fxlink-log", optional_argument, NULL, LOG_TO_FILE },
{ "unmount", no_argument, NULL, 'u' },
{ "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 != '?')
switch((option = getopt_long(argc, argv, "hlbsipquf:w::r", longs, NULL)))
switch((option = getopt_long(argc, argv, "hlbsitpquf:w::rv", longs, NULL)))
{
case 'h':
fprintf(stderr, help_string, argv[0]);
@ -106,6 +106,7 @@ int main(int argc, char **argv)
case 'b':
case 's':
case 'i':
case 't':
case 'p':
mode = option;
break;
@ -124,20 +125,25 @@ int main(int argc, char **argv)
"NONE, ERROR, WARNING, INFO or DEBUG\n", optarg);
break;
case 'q':
options.quiet = true;
/* Ignored: -q, --quiet used to control some messages but is now
supplanted by not setting -v */
break;
case 'u':
options.force_unmount = true;
/* Ignored: -u, --unmount used to force unmounting filesystems after -s
(which is now the only behavior) */
break;
case 'r':
repeat = true;
delay = delay_infinite();
break;
case 'v':
options.verbose = true;
break;
case LOG_TO_FILE:
if(optarg)
options.log_file = fopen(optarg, "a");
else {
char *name = gen_file_name(".", "logfile", "log");
char *name = fxlink_gen_file_name(".", "logfile", ".log");
printf("--fxlink-log will output in '%s'\n", name);
options.log_file = fopen(name, "a");
free(name);
@ -151,25 +157,25 @@ int main(int argc, char **argv)
char *end;
int seconds = strtol(optarg, &end, 10);
if(seconds < 0 || *end != 0) {
error = err("invalid delay '%s'\n", optarg);
error = elog("invalid delay '%s'\n", optarg);
break;
}
delay = delay_seconds(seconds);
break;
case 'f':
filter = filter_parse(optarg);
filter = fxlink_filter_parse(optarg);
break;
case '?':
error = 1;
}
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)
error = err("push mode requires a file name");
error = elog("push mode requires a file name\n");
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 */
if(error)
@ -179,6 +185,10 @@ int main(int argc, char **argv)
return 1;
}
/* Default filter */
if(filter == NULL)
filter = calloc(1, sizeof *filter);
//---
// libusb initialization
//---
@ -186,10 +196,11 @@ int main(int argc, char **argv)
libusb_context *context = NULL;
/* 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)))
return libusb_err(rc, "error initializing libusb");
return elog_libusb(rc, "error initializing libusb");
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
rc = main_blocks(filter, &delay);
#else
rc = err("this fxlink was built without UDisks2; -b is disabled");
rc = elog("this fxlink was built without UDisks2; -b is disabled");
#endif
}
else if(mode == 's') {
#ifndef FXLINK_DISABLE_UDISKS2
rc = main_send(filter, &delay, argv + optind);
#else
rc = err("this fxlink was built without UDisks2; -s is disabled");
rc = elog("this fxlink was built without UDisks2; -s is disabled");
#endif
}
else if(mode == 'i') {
@ -219,99 +230,17 @@ int main(int argc, char **argv)
}
while(repeat);
}
else if(mode == 't') {
rc = main_tui_interactive(context);
}
else if(mode == 'p') {
rc = main_push(filter, &delay, context, argv + optind);
}
fxlink_filter_free(filter);
if(context)
libusb_exit(context);
if(options.log_file)
fclose(options.log_file);
return rc;
}
//---
// 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
View 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
View 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;
}

View file

@ -1,21 +1,25 @@
#include "config.h"
#include "fxlink.h"
#include "util.h"
#include "properties.h"
#include "filter.h"
#include "usb.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/filter.h>
#include <fxlink/logging.h>
#include <fxlink/devices.h>
#include <libusb.h>
#include <string.h>
#include <stdlib.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;
libusb_device *dev = NULL;
libusb_device_handle *dh = NULL;
struct fxlink_device *fdev = NULL;
/* Load binary file */
FILE *fp = fopen(files[0], "rb");
if (!fp) {
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);
/* Wait for a device to be connected */
filter_clean_libusb(filter);
rc = usb_unique_wait(filter, delay, context, &dev);
fxlink_filter_clean_libusb(filter);
filter->intf_cesg502 = true;
if(rc == FILTER_NONE) {
fdev = fxlink_device_find_wait(ctx, filter, delay);
if(!fdev) {
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));
/* The device uses CESG502, so drive the interface manually */
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;
}
/* 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\n", usb_id(dev));
hlog("push");
log_("connected to %s\n", fxlink_device_id(fdev));
// Wait to receive "USB loader ready" over USB bulk transfer
uint8_t buf[18];
while (1)
{
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;
// if (rc == LIBUSB_ERROR_TIMEOUT) continue;
if (rc) {
rc = libusb_err(rc, "cannot receive data: %s", usb_id(dev));
rc = elog_libusb(rc, "cannot receive data");
goto end;
}
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[2] = (fsize >> 8) & 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) {
rc = libusb_err(rc, "cannot send size: %s", usb_id(dev));
rc = elog_libusb(rc, "cannot send size");
goto end;
}
@ -110,9 +106,10 @@ int main_push(filter_t *filter, delay_t *delay, libusb_context *context, char**
int sent = 0;
while (sent < fsize) {
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) {
rc = libusb_err(rc, "cannot send data: %s", usb_id(dev));
rc = elog_libusb(rc, "cannot send data");
goto end;
}
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);
end:
if(dh) {
libusb_release_interface(dh, 0);
libusb_close(dh);
if(fdev) {
libusb_release_interface(fdev->dh, 0);
fxlink_device_cleanup(fdev);
free(fdev);
}
if(dev) libusb_unref_device(dev);
return rc;
}

View 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
View 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 */

View file

@ -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;
}

View file

@ -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 */

View file

@ -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);
}

View file

@ -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 */

View file

@ -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 <stdio.h>
#include <fxlink/protocol.h>
#include <fxlink/logging.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)
{
if(format == USB_FXLINK_IMAGE_RGB565)
return 2 * width;
if(format == USB_FXLINK_IMAGE_MONO)
return (width + 7) >> 3;
if(format == USB_FXLINK_IMAGE_GRAY)
return 2 * ((width + 7) >> 3);
if(format == FXLINK_MESSAGE_IMAGE_RGB565)
return 2 * width;
if(format == FXLINK_MESSAGE_IMAGE_MONO)
return (width + 7) >> 3;
if(format == FXLINK_MESSAGE_IMAGE_GRAY)
return 2 * ((width + 7) >> 3);
return 0;
return 0;
}
static void decode_rgb565(void *pixels, int width, int height, int size,
uint8_t **row_pointers)
static struct fxlink_message_image_raw *decode_rgb565(void const *pixels,
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++) {
void *row = pixels + y * bpr;
for(int y = 0; y < raw->height; y++) {
void const *row = pixels + y * bpr;
for(int x = 0; x < width; x++) {
/* Don't read past the read buffer if the image is incomplete */
void *input = row + 2 * x;
uint16_t color = 0;
if(input - pixels + 2 <= size) color = *(uint16_t *)input;
for(int x = 0; x < raw->width; x++) {
/* Don't read past the read buffer if the image is incomplete */
void const *input = row + 2 * x;
uint16_t color = 0;
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;
row_pointers[y][3*x+1] = ((color >> 5) & 0x3f) << 2;
row_pointers[y][3*x+2] = (color & 0x1f) << 3;
}
}
raw->data[y][3*x+0] = (color >> 11) << 3;
raw->data[y][3*x+1] = ((color >> 5) & 0x3f) << 2;
raw->data[y][3*x+2] = (color & 0x1f) << 3;
}
}
return raw;
}
static void decode_mono(void *pixels, int width, int height, int size,
uint8_t **row_pointers)
static struct fxlink_message_image_raw *decode_mono(void const *pixels,
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++) {
void *row = pixels + y * bpr;
for(int y = 0; y < raw->height; y++) {
void const *row = pixels + y * bpr;
for(int x = 0; x < width; x++) {
/* Don't read past the read buffer if the image is incomplete */
void *input = row + (x >> 3);
int byte = 0;
if(input - pixels + 1 <= size) byte = *(uint8_t *)input;
int color = (byte & (0x80 >> (x & 7))) ? 0 : 255;
for(int x = 0; x < raw->width; x++) {
/* Don't read past the read buffer if the image is incomplete */
void const *input = row + (x >> 3);
int byte = 0;
if(input - pixels + 1 <= size) byte = *(uint8_t *)input;
int color = (byte & (0x80 >> (x & 7))) ? 0 : 255;
row_pointers[y][3*x+0] = color;
row_pointers[y][3*x+1] = color;
row_pointers[y][3*x+2] = color;
}
}
raw->data[y][3*x+0] = color;
raw->data[y][3*x+1] = color;
raw->data[y][3*x+2] = color;
}
}
return raw;
}
static void decode_gray(void *pixels, int width, int height, int size,
uint8_t **row_pointers)
static struct fxlink_message_image_raw *decode_gray(void const *pixels,
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++) {
void *row = pixels + k * bpr;
int y = k % height;
for(int k = 0; k < 2 * raw->height; k++) {
void const *row = pixels + k * bpr;
int y = k % raw->height;
for(int x = 0; x < width; x++) {
/* Don't read past the read buffer if the image is incomplete */
void *input = row + (x >> 3);
int byte = 0;
if(input - pixels + 1 <= size) byte = *(uint8_t *)input;
for(int x = 0; x < raw->width; x++) {
/* Don't read past the read buffer if the image is incomplete */
void const *input = row + (x >> 3);
int byte = 0;
if(input - pixels + 1 <= size) byte = *(uint8_t *)input;
int color = (byte & (0x80 >> (x & 7)));
/* Everything is inverted */
if(!color) color = (k >= height) ? 0xaa : 0x55;
else color = 0x00;
int color = (byte & (0x80 >> (x & 7)));
/* Everything is inverted */
if(!color) color = (k >= raw->height) ? 0xaa : 0x55;
else color = 0x00;
row_pointers[y][3*x+0] += color;
row_pointers[y][3*x+1] += color;
row_pointers[y][3*x+2] += color;
}
}
raw->data[y][3*x+0] += color;
raw->data[y][3*x+1] += 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;
void *pixels = msg->output + sizeof *img;
if(!fxlink_message_is_fxlink_image(msg)
&& !fxlink_message_is_fxlink_video(msg))
return NULL;
/* Compute the amount of data for the specified image format and size */
int bytes_per_row = img_bytes_per_row(img->pixel_format, img->width);
int expected_size = img->height * bytes_per_row;
struct fxlink_message_image_header *img = msg->data;
void *pixels = msg->data + sizeof *img;
int pixels_size = msg->size - sizeof *img;
/* Check that the correct amount of data was sent */
int size = msg->size_read - sizeof *img;
if(size < expected_size)
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);
/* Determine the expected data size */
int bytes_per_row = img_bytes_per_row(img->pixel_format, img->width);
int expected_size = img->height * bytes_per_row;
/* Allocate row pointers */
uint8_t **row_pointers = malloc(img->height*sizeof *row_pointers);
if(!row_pointers) {
err("failed to write allocate memory to decode image");
return NULL;
}
if(pixels_size < expected_size)
wlog("image has %d bytes but needs %d, it will be incomplete\n",
pixels_size, expected_size);
if(pixels_size > expected_size)
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++) {
row_pointers[y] = calloc(img->width, 3);
if(!row_pointers[y]) {
err("failed to write allocate memory to decode image");
for(size_t i = 0 ; i < y; i++) free(row_pointers[i]);
free(row_pointers);
return NULL;
}
}
/* Allocate memory for the decoded pixels */
struct fxlink_message_image_raw *raw = malloc(sizeof *raw);
uint8_t **data = calloc(img->height, sizeof(uint8_t *));
if(!raw || !data)
goto alloc_error;
for(size_t i = 0; i < img->height; i++) {
data[i] = calloc(img->width, 3);
if(!data[i])
goto alloc_error;
}
/* Decode image */
if(img->pixel_format == USB_FXLINK_IMAGE_RGB565)
decode_rgb565(pixels, img->width, img->height, size, row_pointers);
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);
raw->width = img->width;
raw->height = img->height;
raw->data = data;
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;
}

View file

@ -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 */

View file

@ -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, &current_w, &current_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 */

View file

@ -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
View 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
View 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, &current_w, &current_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 */

View file

@ -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
#include "ud2.h"
#include "fxlink.h"
#include "util.h"
#include "properties.h"
#include "filter.h"
#include <udisks/udisks.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
//---
// UDisks2 utility functions
//---
#include <fxlink/tooling/udisks2.h>
#include <fxlink/filter.h>
#include <fxlink/logging.h>
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);
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);
if(!udm) {
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;
@ -35,8 +30,9 @@ int ud2_start(UDisksClient **udc_ptr, UDisksManager **udm_ptr)
return 0;
}
void ud2_end(UDisksClient *udc, __attribute__((unused)) UDisksManager *udm)
void ud2_end(UDisksClient *udc, UDisksManager *udm)
{
(void)udm;
g_object_unref(udc);
}
@ -47,9 +43,8 @@ gchar **ud2_block_devices(UDisksManager *udm)
GError *error = NULL;
udisks_manager_call_get_block_devices_sync(udm,args,&blocks,NULL,&error);
if(error) {
err("cannot list udisks2 block devices: %s", error->message);
}
if(error)
elog("cannot list udisks2 block devices: %s\n", error->message);
return blocks;
}
@ -81,9 +76,9 @@ bool is_casio_drive(UDisksDrive *drive)
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.mass_storage = true;
@ -94,17 +89,17 @@ properties_t ud2_properties(UDisksDrive *drive)
gchar const *s = udisks_drive_get_serial(drive);
/* LINK sends a 12-byte serial number with four leading 0. Remove them */
if(s && strlen(s) == 12 && !strncmp(s, "0000", 4)) s+= 4;
props.serial_number = (char *)s;
if(s && strlen(s) == 12 && !strncmp(s, "0000", 4)) s += 4;
props.serial = strdup(s);
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,
UDisksFilesystem **fs_ptr)
{
int status = FILTER_NONE;
int status = FXLINK_FILTER_NONE;
bool error;
UDisksBlock *block = NULL;
@ -112,11 +107,12 @@ int ud2_unique_matching(filter_t const *filter, UDisksClient *udc,
UDisksFilesystem *fs = NULL;
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 */
if(status == FILTER_UNIQUE) {
status = FILTER_MULTIPLE;
if(status == FXLINK_FILTER_UNIQUE) {
status = FXLINK_FILTER_MULTIPLE;
g_object_unref(fs);
g_object_unref(drive);
g_object_unref(block);
@ -130,31 +126,37 @@ int ud2_unique_matching(filter_t const *filter, UDisksClient *udc,
block = g_object_ref(it.block);
drive = g_object_ref(it.drive);
fs = g_object_ref(it.fs);
status = FILTER_UNIQUE;
status = FXLINK_FILTER_UNIQUE;
}
if(error)
return FILTER_ERROR;
return FXLINK_FILTER_ERROR;
if(block_ptr) *block_ptr = block;
else g_object_unref(block);
if(block_ptr)
*block_ptr = block;
else if(block)
g_object_unref(block);
if(drive_ptr) *drive_ptr = drive;
else g_object_unref(drive);
if(drive_ptr)
*drive_ptr = drive;
else if(drive)
g_object_unref(drive);
if(fs_ptr) *fs_ptr = fs;
else g_object_unref(fs);
if(fs_ptr)
*fs_ptr = fs;
else if(fs)
g_object_unref(fs);
return status;
}
int ud2_unique_wait(filter_t const *filter, delay_t *delay, UDisksClient *udc,
UDisksManager *udm, UDisksBlock **block, UDisksDrive **drive,
UDisksFilesystem **fs)
int ud2_unique_wait(struct fxlink_filter const *filter, delay_t *delay,
UDisksClient *udc, UDisksManager *udm, UDisksBlock **block,
UDisksDrive **drive, UDisksFilesystem **fs)
{
while(true) {
int rc = ud2_unique_matching(filter, udc, udm, block, drive, fs);
if(rc != FILTER_NONE) return rc;
if(delay_cycle(delay)) return FILTER_NONE;
if(rc != FXLINK_FILTER_NONE) return rc;
if(delay_cycle(delay)) return FXLINK_FILTER_NONE;
udisks_client_settle(udc);
}
}
@ -223,9 +225,9 @@ void ud2_iter_next(ud2_iterator_t *it)
// 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;
UDisksManager *udm = NULL;
@ -237,7 +239,8 @@ int main_blocks(filter_t *filter, delay_t *delay)
bool 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");
@ -254,8 +257,8 @@ int main_blocks(filter_t *filter, delay_t *delay)
udisks_drive_get_vendor(it.drive),
udisks_drive_get_model(it.drive));
if(it.props.serial_number)
printf(" Serial number: %s\n", it.props.serial_number);
if(it.props.serial)
printf(" Serial number: %s\n", it.props.serial);
gchar const * const * mount_points =
udisks_filesystem_get_mount_points(it.fs);
@ -267,7 +270,7 @@ int main_blocks(filter_t *filter, delay_t *delay)
}
printf(" Properties: ");
properties_print(stdout, &it.props);
fxlink_filter_print(stdout, &it.props);
printf("\n");
total_devices++;
@ -280,112 +283,4 @@ int main_blocks(filter_t *filter, delay_t *delay)
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 */

338
fxlink/tui/input.c Normal file
View 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
View 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
View 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]);
}

View file

@ -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);
}

View file

@ -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 */

View file

@ -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;
}

View file

@ -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 */