hanvon-linux/hanvon-libusb.c
scuti ee3b7b2182 scuti/hotplug (#11)
Added hotplugging support.
2021-09-28 20:34:21 -07:00

555 lines
19 KiB
C

/*
* =====================================================================================
*
* Filename: hanvon-libusb.c
*
* Description: libusb Hanvon tablet driver
*
* Version: 0.1
* Created: 08/17/2020 04:05:14 PM
* Revision: none
* Compiler: gcc
*
* Maintained by: scuti@teknik.io
* surkeh@protonmail.com
*
* =====================================================================================
*/
#define DEBUG(msg,...) fprintf(stderr,"%s(%d): " msg , __FILE__,__LINE__,__VA_ARGS__)
#include <stdlib.h>
#include <stdio.h>
#include <libusb-1.0/libusb.h>
#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>
#define STATE_SUCCESS 0
#define STATE_NOT_FOUND 1
#define VENDOR_ID_HANVON 0x0b57
#define PRODUCT_ID_AM3M 0x8528
#define PRODUCT_ID_AM0806 0x8502
#define PRODUCT_ID_AM0605 0x8503
#define PRODUCT_ID_AM1107 0x8505
#define PRODUCT_ID_AM1209 0x8501
#define PRODUCT_ID_RL0604 0x851f
#define PRODUCT_ID_RL0504 0x851d
#define PRODUCT_ID_GP0806 0x8039
#define PRODUCT_ID_GP0806B 0x8511
#define PRODUCT_ID_GP0605 0x8512
#define PRODUCT_ID_GP0605A 0x803a
#define PRODUCT_ID_GP0504 0x8037
#define PRODUCT_ID_NXS1513 0x8030
#define PRODUCT_ID_GP0906 0x8521
#define PRODUCT_ID_APPIV0906 0x8532
#define AM_PACKET_LEN 10
#define AM_RESOLUTION 40
#define AM_WHEEL_THRESHOLD 4
#define AM_MAX_ABS_X 0x27DE
#define AM_MAX_ABS_Y 0x1CFE
#define AM_MAX_TILT_X 0x3F
#define AM_MAX_TILT_Y 0x7F
#define AM_MAX_PRESSURE 0x400
#define APPIV_MAX_ABS_X 0x5750
#define APPIV_MAX_ABS_Y 0x5750
#define BUTTON_EVENT_GP 0x01
#define PEN_EVENT 0x02
#define BUTTON_EVENT_0906 0x0C
static int lbuttons[]={BTN_0,BTN_1,BTN_2,BTN_3}; /* reported on all AMs */
static int rbuttons[]={BTN_4,BTN_5,BTN_6,BTN_7}; /* reported on AM1107+ */
struct hanvon_message {
unsigned char msgtype;
unsigned char is_move;
unsigned short x_movement;
unsigned short y_movement;
unsigned char pressure;
unsigned char x_tilt;
unsigned char y_tilt;
};
// GLOBAL
int wheel_position;
int find_device(libusb_device **list, unsigned int count) {
if (count < 0) {
return -1;
}
int found = -1;
struct libusb_device_descriptor desc;
for (unsigned int i = 0; i < count; i++) {
libusb_device *t = list[i];
libusb_get_device_descriptor(list[i], &desc);
if (desc.idVendor == VENDOR_ID_HANVON) {
switch(desc.idProduct) {
default:
break;
case PRODUCT_ID_AM0806:
case PRODUCT_ID_AM0605:
case PRODUCT_ID_AM1107:
case PRODUCT_ID_AM1209:
case PRODUCT_ID_RL0604:
case PRODUCT_ID_RL0504:
case PRODUCT_ID_GP0806:
case PRODUCT_ID_GP0806B:
case PRODUCT_ID_GP0605:
case PRODUCT_ID_GP0605A:
case PRODUCT_ID_GP0504:
case PRODUCT_ID_NXS1513:
case PRODUCT_ID_GP0906:
case PRODUCT_ID_APPIV0906:
return i;
} // end switch
} // end if
} // end for
return found;
}
void display_packets(const unsigned char* buf) {
for(int i = 0; i < AM_PACKET_LEN; i++) {
fprintf(stderr,"0x%x, ", buf[i]);
}
fprintf(stderr,"\r");
}
void callback(struct libusb_transfer *transfer) {
unsigned char *data = transfer -> buffer;
display_packets(data);
}
static inline void report_buttons( struct libevdev_uinput *ud,
int buttons[],
unsigned char data)
{
int err = 0;
if((data & 0xf0) == 0xa0) {
// TODO test that these are the correct buttons and all buttons are covered
err = libevdev_uinput_write_event(ud, EV_KEY, buttons[1], (data & 0x02));
if(err) { DEBUG("err: %d\n",err); }
err = libevdev_uinput_write_event(ud, EV_KEY, buttons[2], (data & 0x04));
if(err) { DEBUG("err: %d\n",err); }
err = libevdev_uinput_write_event(ud, EV_KEY, buttons[3], (data & 0x08));
if(err) { DEBUG("err: %d\n",err); }
} else if(data <= 0x3f) { /* slider area active */
int delta = data - wheel_position;
if(abs(delta) < AM_WHEEL_THRESHOLD) {
err = libevdev_uinput_write_event(ud, EV_REL, REL_WHEEL, delta); // TODO test delta as input
if(err) { DEBUG("err: %d\n",err); }
wheel_position = data;
}
}
}
// NOTE:
// Judging by the original driver, this should work for all but may not work
// for the APPIV0906. Possibly needs little endian for APPIV0906 x and y data
// but we don't have any means of testing this without that tablet.
// NOTE:
// Left and right mouse click should work for all but additional buttons are
// not supported by the default handler.
void callback_default (struct libusb_transfer *tx) { // for callback
unsigned char *data = tx -> buffer;
struct hanvon_message *msg = (struct hanvon_message *)tx -> buffer;
int err = 0;
struct libevdev_uinput *ud = tx -> user_data;
switch(msg->msgtype) {
case BUTTON_EVENT_GP:
if(data[1] == 0x55) { // left side buttons
report_buttons(ud, lbuttons, msg->x_movement); // button pressed data in same place as position data
}
if(data[3] == 0xAA) { // right side buttons (am1107, am1209
report_buttons(ud, rbuttons, msg->y_movement); // button pressed data in same place as position data
}
break;
case PEN_EVENT:
/* is_move values:
0x80: near, 0x02: button press
0x10: floating, 0x01: touching */
err = libevdev_uinput_write_event(
ud, EV_KEY, BTN_TOOL_PEN, msg->is_move & (0x80|0x10)
);
if(err) { DEBUG("err: %d\n",err); }
if(msg->is_move & (0x80|0x10)) {
msg->x_movement = htobe16(msg->x_movement);
//DEBUG("Set X to %x\n",msg->x_movement);
err = libevdev_uinput_write_event(
ud, EV_ABS, ABS_X, msg->x_movement
);
if(err) { DEBUG("err: %d\n",err); }
msg->y_movement = htobe16(msg->y_movement);
//DEBUG("Set Y to %x\n",msg->y_movement);
err = libevdev_uinput_write_event(
ud, EV_ABS, ABS_Y, msg->y_movement
);
if(err) { DEBUG("err: %d\n",err); }
}
err = libevdev_uinput_write_event(
ud, EV_ABS, ABS_PRESSURE, msg->pressure * 8 // reference original driver
);
if(err) { DEBUG("err: %d\n",err); }
err = libevdev_uinput_write_event(
ud, EV_ABS, ABS_TILT_X, msg->x_tilt
);
if(err) { DEBUG("err: %d\n",err); }
err = libevdev_uinput_write_event(
ud, EV_ABS, ABS_TILT_Y, msg->y_tilt
);
if(err) { DEBUG("err: %d\n",err); }
err = libevdev_uinput_write_event(
ud, EV_KEY, BTN_LEFT, msg->is_move & 0x01
);
if(err) { DEBUG("err: %d\n",err); }
err = libevdev_uinput_write_event(
ud, EV_KEY, BTN_RIGHT, (msg->is_move & 0x02) / 2
);
if(err) { DEBUG("err: %d\n",err); }
// data[1]:
// 0x10 = lift, 0x90 = close, 0x91 = press
// 0x12 = btn (lift), 0x92 = btn (close), 0x93 = btn (press)
break;
case BUTTON_EVENT_0906:
// TODO confirm this is the byte that contains button flags
err = libevdev_uinput_write_event(
ud, EV_KEY, BTN_0, (msg->is_move & 0x0100) / 2
);
if(err) { DEBUG("err: %d\n",err); }
err = libevdev_uinput_write_event(
ud, EV_KEY, BTN_1, (msg->is_move & 0x0200) / 2
);
if(err) { DEBUG("err: %d\n",err); }
err = libevdev_uinput_write_event(
ud, EV_KEY, BTN_2, (msg->is_move & 0x0400) / 2
);
if(err) { DEBUG("err: %d\n",err); }
err = libevdev_uinput_write_event(
ud, EV_KEY, BTN_3, (msg->is_move & 0x0800) / 2
);
if(err) { DEBUG("err: %d\n",err); }
err = libevdev_uinput_write_event(
ud, EV_KEY, BTN_4, (msg->is_move & 0x1000) / 2
);
if(err) { DEBUG("err: %d\n",err); }
err = libevdev_uinput_write_event(
ud, EV_KEY, BTN_5, (msg->is_move & 0x2000) / 2
);
if(err) { DEBUG("err: %d\n",err); }
err = libevdev_uinput_write_event(
ud, EV_KEY, BTN_6, (msg->is_move & 0x4000) / 2
);
if(err) { DEBUG("err: %d\n",err); }
err = libevdev_uinput_write_event(
ud, EV_KEY, BTN_7, (msg->is_move & 0x8000) / 2
);
if(err) { DEBUG("err: %d\n",err); }
default:
// do nothing
break;
}
// always display packets
display_packets(data);
err += libevdev_uinput_write_event(ud, EV_SYN, SYN_REPORT, 0);
if (err != 0) {
printf("error : gp0504, %i\n", err);
}
return;
}
// https://www.freedesktop.org/software/libevdev/doc/latest/group__kernel.html
int init_ctrl(struct libusb_device * const d,
struct libevdev **evdev,
struct libevdev_uinput **uidev) {
struct input_absinfo *abs;
printf("init_ctrl: %x\n", uidev);
wheel_position = AM_WHEEL_THRESHOLD - 1; // init global
if (d == NULL) {
return -1;
}
int is_ok = 0;
struct libusb_device_descriptor desc;
libusb_get_device_descriptor(d, &desc);
(*evdev) = libevdev_new();
// set up inputs all devices have
libevdev_enable_property((*evdev), INPUT_PROP_DIRECT);
libevdev_enable_event_type((*evdev), EV_SYN);
libevdev_enable_event_code((*evdev), EV_SYN, SYN_REPORT, NULL);
libevdev_enable_event_type((*evdev), EV_KEY); // enable pen button
libevdev_enable_event_code((*evdev), EV_KEY, BTN_TOOL_PEN, NULL);
libevdev_enable_event_code((*evdev), EV_KEY, BTN_LEFT, NULL); // pen tap
libevdev_enable_event_code((*evdev), EV_KEY, BTN_RIGHT, NULL); // pen button
// enable absolute position, pressure, tilt
libevdev_enable_event_type((*evdev), EV_ABS);
abs = malloc(sizeof(struct input_absinfo));
// set up absolute x coordinate input
abs->value = 0x1000;
abs->minimum = 0;
switch(desc.idProduct) {
case PRODUCT_ID_APPIV0906:
abs->maximum = APPIV_MAX_ABS_X;
break;
default:
abs->maximum = AM_MAX_ABS_X;
break;
}
abs->fuzz = 0;
abs->flat = 0;
abs->resolution = AM_RESOLUTION;
if(libevdev_enable_event_code((*evdev), EV_ABS, ABS_X, abs)<0) {
DEBUG("%s","failed to register absolute x\n");
is_ok = -1;
}
// set up absolute y coordinate input
abs->value = 0x1000;
abs->minimum = 0;
switch(desc.idProduct) {
case PRODUCT_ID_APPIV0906:
abs->maximum = APPIV_MAX_ABS_Y;
break;
default:
abs->maximum = AM_MAX_ABS_Y;
break;
}
abs->resolution = AM_RESOLUTION;
if(libevdev_enable_event_code((*evdev), EV_ABS, ABS_Y, abs)<0) {
DEBUG("%s","failed to register absolute y\n");
is_ok = -1;
}
// set up pressure input
abs -> value = 0;
abs -> minimum = 0;
abs -> maximum = AM_MAX_PRESSURE;
abs -> resolution = 0;
if(libevdev_enable_event_code((*evdev), EV_ABS, ABS_PRESSURE, abs)<0) {
DEBUG("%s","failed to register pressure\n");
is_ok = -1;
}
// set up tilt x input
abs -> value = 0;
abs -> minimum = 0;
abs -> maximum = AM_MAX_TILT_X;
abs -> resolution = 0;
if(libevdev_enable_event_code((*evdev), EV_ABS, ABS_TILT_X, abs)<0) {
DEBUG("%s","failed to register x tilt\n");
is_ok = -1;
}
// set up tilt y input
abs -> value = 0;
abs -> minimum = 0;
abs -> maximum = AM_MAX_TILT_Y;
abs -> resolution = 0;
if(libevdev_enable_event_code((*evdev), EV_ABS, ABS_TILT_Y, abs)<0) {
DEBUG("%s","failed to register y tilt\n");
is_ok = -1;
}
// Scroll wheel is NOT universal
if(libevdev_enable_event_code((*evdev), EV_REL, REL_WHEEL, NULL)<0) {
DEBUG("%s","failed to register scroll wheel\n");
is_ok = -1;
}
// set up device-specific inputs
switch(desc.idProduct) {
case PRODUCT_ID_AM3M:
case PRODUCT_ID_AM0806:
case PRODUCT_ID_AM0605:
libevdev_enable_event_code((*evdev), EV_KEY, BTN_0, NULL);
libevdev_enable_event_code((*evdev), EV_KEY, BTN_1, NULL);
libevdev_enable_event_code((*evdev), EV_KEY, BTN_2, NULL);
libevdev_enable_event_code((*evdev), EV_KEY, BTN_3, NULL);
break;
case PRODUCT_ID_AM1107:
case PRODUCT_ID_AM1209:
libevdev_enable_event_code((*evdev), EV_KEY, BTN_0, NULL);
libevdev_enable_event_code((*evdev), EV_KEY, BTN_1, NULL);
libevdev_enable_event_code((*evdev), EV_KEY, BTN_2, NULL);
libevdev_enable_event_code((*evdev), EV_KEY, BTN_3, NULL);
libevdev_enable_event_code((*evdev), EV_KEY, BTN_4, NULL);
libevdev_enable_event_code((*evdev), EV_KEY, BTN_5, NULL);
libevdev_enable_event_code((*evdev), EV_KEY, BTN_6, NULL);
libevdev_enable_event_code((*evdev), EV_KEY, BTN_7, NULL);
break;
default:
// do nothing
break;
}
// set up libevdev device name strings
switch(desc.idProduct) {
// cases are in ID order
case PRODUCT_ID_AM3M:
// space between Art and Master intentional
libevdev_set_name((*evdev), "Hanvon Art Master III");
break;
case PRODUCT_ID_AM0806:
libevdev_set_name((*evdev), "Hanvon ArtMaster AM0806");
break;
case PRODUCT_ID_AM0605:
libevdev_set_name((*evdev), "Hanvon ArtMaster AM0605");
break;
case PRODUCT_ID_AM1107:
// space between Art and Master intentional
libevdev_set_name((*evdev), "Hanvon Art Master AM1107");
break;
case PRODUCT_ID_AM1209:
libevdev_set_name((*evdev), "Hanvon ArtMaster AM1209");
break;
case PRODUCT_ID_RL0604:
libevdev_set_name((*evdev), "Hanvon Rollick 0604");
break;
case PRODUCT_ID_RL0504:
libevdev_set_name((*evdev), "Hanvon Rollick 0504");
break;
case PRODUCT_ID_GP0806:
libevdev_set_name((*evdev), "Hanvon Graphicpal 0806");
break;
case PRODUCT_ID_GP0806B:
libevdev_set_name((*evdev), "Hanvon Graphicpal 0806B");
break;
case PRODUCT_ID_GP0605:
libevdev_set_name((*evdev), "Hanvon Graphicpal 0605");
break;
case PRODUCT_ID_GP0605A:
libevdev_set_name((*evdev), "Hanvon Graphicpal 0605A");
break;
case PRODUCT_ID_GP0504:
libevdev_set_name((*evdev), "Hanvon Graphicpal 0504");
break;
case PRODUCT_ID_NXS1513:
libevdev_set_name((*evdev), "Hanvon Nilox NXS1513");
break;
case PRODUCT_ID_GP0906:
libevdev_set_name((*evdev), "Hanvon Graphicpal 0906");
break;
case PRODUCT_ID_APPIV0906:
libevdev_set_name((*evdev), "Hanvon Art Painter Pro APPIV0906");
break;
}
int err = libevdev_uinput_create_from_device(
(*evdev), LIBEVDEV_UINPUT_OPEN_MANAGED, uidev
);
printf("Initializing controls status: %x, \n", uidev);
free(abs);
return is_ok;
}
// GLOBAL: for main and hotplug_callback
static int HOT_COUNT = 0;
static struct libusb_transfer *TX = NULL;
void loop() {
int status = 0;
int LAST_HOT_COUNT = 0;
while (1) {
if (TX != NULL && TX->dev_handle != 0) {
status = libusb_submit_transfer(TX);
}
if (status < 0 && LAST_HOT_COUNT != HOT_COUNT) {
printf("\nwarning: usb transfer status = %i\n", status);
//continue;
}
LAST_HOT_COUNT = HOT_COUNT;
libusb_handle_events_completed(NULL, NULL);
}
}
// TODO: make scuti explain why this is called twice
int hotplug_callback(struct libusb_context *ctx, struct libusb_device *dev,
libusb_hotplug_event event, void *user_data) {
static libusb_device_handle *dev_handle;
static struct libevdev_uinput *uidev = NULL;
static struct libevdev *evdev = NULL;
struct libusb_device_descriptor desc;
int rc;
libusb_get_device_descriptor(dev, &desc);
if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event) {
// check if the device supported
libusb_device *devs[1] = {dev};
int index = find_device(devs, 1);
if (index < 0) {
printf("INFO: Detected Hanvon device, but it is not supported.\n");
} else {
// libusb functions that accept libusb_device = SAFE
// submit asynchronous transfers = SAFE
// other functions that accept device handle = NOT SAFE
libusb_open(dev, &dev_handle);
if (init_ctrl(dev, &evdev, &uidev) < 0) {
printf("Error: Could not initialize controls.\n");
exit(EXIT_FAILURE);
}
const int ENDPOINT_ADDR = 0x81; // bEndpointAddress from lsusb -v
//AM_PACKET_LEN = 10; // wMaxPacketSize from lsusb -v
unsigned char buffer[AM_PACKET_LEN];
TX = libusb_alloc_transfer(0);
libusb_fill_interrupt_transfer(
TX,
dev_handle,
ENDPOINT_ADDR,
buffer,
AM_PACKET_LEN,
callback_default,
uidev, // extra data to send in tx
130 // timeout in milliseconds
);
}
} else if (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) {
if (dev_handle) {
libusb_close(dev_handle);
dev_handle = NULL;
libevdev_uinput_destroy(uidev);
libusb_free_transfer(TX);
TX = NULL;
}
} else {
printf ("INFO: Device left, but handle did not exist.\n");
}
HOT_COUNT++;
return 0;
}
int main() {
#define UNREF_DEVICE 1
#define KEEP_DEVICE_REF 0
int r = libusb_init(NULL);
if (r < 0) {
return r;
}
libusb_hotplug_callback_handle callback_handle;
int rc;
rc = libusb_hotplug_register_callback(
NULL,
LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |
LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
0,
VENDOR_ID_HANVON, // VENDOR
LIBUSB_HOTPLUG_MATCH_ANY, // PRODUCT
LIBUSB_HOTPLUG_MATCH_ANY, // DEV CLASS
hotplug_callback,
NULL,
&callback_handle
);
if (rc != LIBUSB_SUCCESS) {
printf("Error creating a hotplug callback\n");
libusb_exit(NULL);
return EXIT_FAILURE;
}
loop();
return 0;
}