/* * ===================================================================================== * * 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 #include #include #include #include #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; }