From 13e8217cd4ef76dbf8be02481340a71cc3dc4a99 Mon Sep 17 00:00:00 2001 From: Drew Fisher Date: Sat, 29 Oct 2011 12:44:06 -0700 Subject: [PATCH] Pull apart library and example. An API now exists, as does a separate program that performs the same demonstration task for a limited number of updates. A shared library will be built. Timeout behavior needs to be implemented. Forward progress. Signed-off-by: Drew Fisher --- CMakeLists.txt | 22 ++- examples/consoledemo.c | 116 ++++++++++++ libtouchmouse/libtouchmouse.h | 57 ++++++ src/touchmouse-internal.h | 27 +++ touchmouse.c => src/touchmouse.c | 300 ++++++++++++++++++------------- 5 files changed, 385 insertions(+), 137 deletions(-) create mode 100644 examples/consoledemo.c create mode 100644 libtouchmouse/libtouchmouse.h create mode 100644 src/touchmouse-internal.h rename touchmouse.c => src/touchmouse.c (63%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9039226..d1ff373 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,31 +8,35 @@ project(libtouchmouse) set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) # Add include path to hidapi.h -include_directories (${CMAKE_CURRENT_SOURCE_DIR}/hidapi/hidapi) +include_directories (${CMAKE_CURRENT_SOURCE_DIR}/hidapi/hidapi ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}) +# Set library sources if(WIN32) # Win32-specific options go here. message(STATUS "Detected Win32 system") - list(APPEND SRC hidapi/windows/hid.c) + list(APPEND LIBSRC hidapi/windows/hid.c) elseif(APPLE) # Apple-specific options go here. message(STATUS "Detected Apple system") - list(APPEND SRC hidapi/mac/hid.c) + list(APPEND LIBSRC hidapi/mac/hid.c) set(CMAKE_EXE_LINKER_FLAGS "-framework IOKit -framework CoreFoundation") else() # Linux-specific options go here. message(STATUS "Detected non-windows, non-apple system; assuming some Linux variant") include_directories(/usr/include/libusb-1.0) - list(APPEND SRC hidapi/linux/hid-libusb.c) + list(APPEND LIBSRC hidapi/linux/hid-libusb.c) endif() +list(APPEND LIBSRC src/touchmouse.c) -# Build +set(CMAKE_C_FLAGS "-Wall -ggdb") -set(CMAKE_C_FLAGS "-Wall") +# Build library +add_library(touchmouse SHARED ${LIBSRC}) -add_executable(touchmouse touchmouse.c ${SRC}) +# Build demo +add_executable(consoledemo examples/consoledemo.c) if(WIN32) - target_link_libraries(touchmouse setupapi) + target_link_libraries(consoledemo touchmouse setupapi) elseif(NOT APPLE) - target_link_libraries(touchmouse usb-1.0 pthread rt) + target_link_libraries(consoledemo touchmouse usb-1.0 pthread rt) endif() diff --git a/examples/consoledemo.c b/examples/consoledemo.c new file mode 100644 index 0000000..78fc0dc --- /dev/null +++ b/examples/consoledemo.c @@ -0,0 +1,116 @@ +/* + * Copyright 2011 Drew Fisher (drew.m.fisher@gmail.com). + * + * The contents of this file may be used by anyone for any reason without any + * conditions and may be used as a starting point for your own applications + * which use libtouchmouse. +*/ +#include +#include +#include +#include + +#include + +void callback(touchmouse_callback_info *cbdata) { + printf("Callback triggered, timestamp %d\n", cbdata->timestamp); + int row; + for(row = 0; row < 13 ; row++) { + int col; + for(col = 0; col < 15; col++) { + printf("%02X ", cbdata->image[row * 15 + col]); + } + printf("\n"); + } +} + +int main(void) { + int res; + // Initialize library. + printf("Initializing libtouchmouse...\n"); + res = touchmouse_init(); + if (res != 0) { + fprintf(stderr, "Failed to initialize libtouchmouse, aborting\n"); + return 1; + } + + // Count devices. + printf("Enumerating touchmouse devices...\n"); + int devs_found = 0; + touchmouse_device_info* devs = touchmouse_enumerate_devices(); + printf("touchmouse_enumerate_devices returned %p\n", devs); + touchmouse_device_info* d = devs; + while(d) { + d = d->next; + devs_found++; + } + printf("Found %d touchmouse devices.\n", devs_found); + + // If we didn't see any, we can't possibly open a device. + if (devs_found == 0) { + fprintf(stderr, "No touchmouse found, aborting\n"); + return 1; + } + + // Open the first device. + printf("Attempting to open device 0...\n"); + touchmouse_device *dev; + res = touchmouse_open(&dev, devs); + touchmouse_free_enumeration(devs); + if (res != 0) { + fprintf(stderr, "Failed to open device 0, aborting\n"); + return -1; + } + printf("Opened device 0 successfully.\n"); + + // Enable full image updates on the opened device + printf("Setting device to send full image updates...\n"); + res = touchmouse_set_device_mode(dev, TOUCHMOUSE_RAW_IMAGE); + if (res != 0) { + fprintf(stderr, "Failed to enable full image updates, aborting\n"); + return -1; + } + printf("Device set to send full image updates successfully.\n"); + + // Set user callback function + printf("Setting callback function for image updates...\n"); + res = touchmouse_set_image_update_callback(dev, callback); + if (res != 0) { + fprintf(stderr, "Failed to set callback function, aborting\n"); + return -1; + } + + // Poll device for image updates. + int i; + while(i < 100) { + res = touchmouse_process_events_timeout(dev, -1); // -1 means infinite timeout + i++; + } + + // Disable touch image updates (and reenable automatic two-finger scrolling, etc.) + printf("Restoring device to default mode...\n"); + res = touchmouse_set_device_mode(dev, TOUCHMOUSE_DEFAULT); + if (res != 0) { + fprintf(stderr, "Failed to set device back to default mode, aborting\n"); + return -1; + } + + // Close device handle + printf("Closing device...\n"); + res = touchmouse_close(dev); + dev = NULL; + if (res != 0) { + fprintf(stderr, "Failed to close device, aborting\n"); + return -1; + } + + // Free static library data + printf("Cleaning up libtouchmouse...\n"); + touchmouse_shutdown(); + if (res != 0) { + fprintf(stderr, "Failed to clean up libtouchmouse, aborting\n"); + return -1; + } + printf("Done!\n"); + return 0; +} diff --git a/libtouchmouse/libtouchmouse.h b/libtouchmouse/libtouchmouse.h new file mode 100644 index 0000000..dc57fcf --- /dev/null +++ b/libtouchmouse/libtouchmouse.h @@ -0,0 +1,57 @@ +#ifndef __LIBTOUCHMOUSE_H__ +#define __LIBTOUCHMOUSE_H__ +#include + +// Types: +struct touchmouse_device_; +typedef struct touchmouse_device_ touchmouse_device; + +struct touchmouse_device_info; // For enumeration. +typedef struct touchmouse_device_info { + struct touchmouse_device_info *next; + void* opaque; // Internally-used unique handle for the device in question + // We'll add any other interesting info, like serial number, version ID, etc. if we can fetch it reliably. +} touchmouse_device_info; + + +typedef struct touchmouse_callback_info { + void* userdata; + uint8_t* image; + uint8_t timestamp; +} touchmouse_callback_info; + +// Callback declaration: void function that takes a void pointer, +typedef void (*touchmouse_image_callback)(touchmouse_callback_info *cbinfo); + +typedef enum { + TOUCHMOUSE_DEFAULT = 0, // Default mode when you plug the mouse in. + TOUCHMOUSE_RAW_IMAGE = 1, // Disables gestures, calls touchmouse_image_callback whenever a new image arrives + // Other modes may exist, I haven't played with the mouse enough yet. +} touchmouse_mode; + +// Library initialization/destruction +int touchmouse_init(void); +int touchmouse_shutdown(void); + +// Device enumeration/open/close/free +touchmouse_device_info* touchmouse_enumerate_devices(void); +void touchmouse_free_enumeration(touchmouse_device_info *devs); +int touchmouse_open(touchmouse_device **dev, touchmouse_device_info *dev_info); +int touchmouse_close(touchmouse_device *dev); + +// Set mouse mode. +int touchmouse_set_device_mode(touchmouse_device *dev, touchmouse_mode mode); + +// Register callback for touch image updates +int touchmouse_set_image_update_callback(touchmouse_device *dev, touchmouse_image_callback callback); + +// Allow for setting a piece of user-defined data to be provided in the callback. +int touchmouse_set_device_userdata(touchmouse_device *dev, void *userdata); + +// Process events for a device. +// milliseconds < 0 means "block until you have new data and trigger the callback, then return." +// milliseconds = 0 means "trigger the callback if you have the data waiting, otherwise request data async and return immediately" +// milliseconds > 0 means "fetch data. Trigger a callback if the data arrives within milliseconds, otherwise return." +int touchmouse_process_events_timeout(touchmouse_device *dev, int milliseconds); + +#endif /* __LIBTOUCHMOUSE_H__ */ diff --git a/src/touchmouse-internal.h b/src/touchmouse-internal.h new file mode 100644 index 0000000..ebadaec --- /dev/null +++ b/src/touchmouse-internal.h @@ -0,0 +1,27 @@ +#ifndef __TOUCHMOUSE_INTERNAL__ +#define __TOUCHMOUSE_INTERNAL__ + +#include + +struct touchmouse_device_ { + // HIDAPI handle + hid_device* dev; + // Callback information + void* userdata; + touchmouse_image_callback cb; + // Image decoder/reassembler state + uint8_t timestamp_last_completed; + int buf_index; + int next_is_run_encoded; + uint8_t partial_image[181]; + uint8_t image[195]; +}; + +enum { + DECODER_BEGIN, + DECODER_IN_PROGRESS, + DECODER_COMPLETE, + DECODER_ERROR, +} decoder_state; + +#endif // __TOUCHMOUSE_INTERNAL__ diff --git a/touchmouse.c b/src/touchmouse.c similarity index 63% rename from touchmouse.c rename to src/touchmouse.c index 081ccc8..17108bb 100644 --- a/touchmouse.c +++ b/src/touchmouse.c @@ -25,19 +25,12 @@ * or implied, of the copyright holder. */ #include -#include #include #include #include "hidapi.h" #include -int quit = 0; - -// A signal handler so we can ^C cleanly -void handler(int signum) { - printf("Caught signal, quitting\n"); - quit = 1; -} +#include "touchmouse-internal.h" #pragma pack(1) // The USB HID reports that contain our data are always 32 bytes, with the @@ -47,7 +40,7 @@ typedef struct { // ones that have report_id 0x27. uint8_t length; // Length of the useful data in this transfer, including // both timestamp and data[] buffer. - uint32_t magic; // Four magic bytes. These are always the same: + uint8_t magic[4]; // Four magic bytes. These are always the same: // 0x14 0x01 0x00 0x51 uint8_t timestamp; // Measured in milliseconds since the last series of // touch events began, but wraps at 256. If two or @@ -95,50 +88,20 @@ typedef struct { // Note that the usable touch area on the mouse may be even smaller than this 181 // pixel arrangement - some of even these pixels may always give a value of 0. -typedef void (*touchmouse_callback)(void* dev, uint8_t* image, uint8_t timestamp); - -// Tracks internal state of the decoder -typedef struct { - int buf_index; - int next_is_run_encoded; - uint8_t partial_image[181]; - uint8_t image[195]; - touchmouse_callback cb; -} decoder; - -void print_table(void* placeholder, uint8_t* image, uint8_t timestamp) { - // Sample callback - simply print out the table. - int row; - printf("Current touch state:\n"); - for(row = 0; row < 13 ; row++) { - int col; - for(col = 0; col < 15; col++) { - printf("%02X ", image[row * 15 + col]); - } - printf("\n"); - } -} - -void reset_decoder(decoder* state) { - // Don't set the callback to NULL. +static void reset_decoder(touchmouse_device *state) +{ state->buf_index = 0; state->next_is_run_encoded = 0; - memset(state->partial_image, 0, 181); - memset(state->image, 0, 195); + memset(state->partial_image, 0, sizeof(state->partial_image)); + memset(state->image, 0, sizeof(state->image)); } // There are 15 possible values that each pixel can take on, but we'd like to // scale them up to the full range of a uint8_t for convenience. -uint8_t decoder_table[15] = {0, 18, 36, 55, 73, 91, 109, 128, 146, 164, 182, 200, 219, 237, 255 }; - -enum { - DECODER_BEGIN, - DECODER_IN_PROGRESS, - DECODER_COMPLETE, - DECODER_ERROR, -} decoder_state; +static uint8_t decoder_table[15] = {0, 18, 36, 55, 73, 91, 109, 128, 146, 164, 182, 200, 219, 237, 255 }; -int process_nybble(decoder* state, uint8_t nybble) { +static int process_nybble(touchmouse_device *state, uint8_t nybble) +{ //printf("process_nybble: buf_index = %d\t%01x\n", state->buf_index, nybble); if (nybble >= 16) { fprintf(stderr, "process_nybble: got nybble >= 16, wtf: %d\n", nybble); @@ -203,50 +166,28 @@ int process_nybble(decoder* state, uint8_t nybble) { return DECODER_IN_PROGRESS; } -int enable_mouse_image_mode(hid_device* dev) { - // We need to set two bits in a particular Feature report. We first fetch - // the current state of the feature report, set the interesting bits, and - // write that feature report back to the device. - printf("Reading current config flags\n"); - unsigned char data[27] = {0x22}; - int transferred = 0; - transferred = hid_get_feature_report(dev, data, 27); - if (transferred > 0) { - printf("%d bytes received:\n", transferred); - int i; - for(i = 0; i < transferred; i++) { - printf("%02X ", data[i]); - } - printf("\n"); - } - if (transferred != 0x1B) { - fprintf(stderr, "Failed to read Feature 0x22 correctly; expected 27 bytes, got %d\n", transferred); - return -1; - } - - // This particular byte/setting appears to control the - // "send all the raw input" flag. - data[4] = 0x06; - - printf("Trying to enable full touch updates...\n"); - transferred = hid_send_feature_report(dev, data, 27); - printf("Wrote %d bytes\n", transferred); - if (transferred == 0x1B) { - printf("Successfully enabled full touch updates.\n"); - return 0; - } - fprintf(stderr, "Failed to enable full touch updates.\n"); - return -1; +// Initialize libtouchmouse. Which mostly consists of calling hid_init(); +int touchmouse_init(void) +{ + return hid_init(); } -int main(void) { - signal(SIGINT, handler); +// Same thing - clean up +int touchmouse_shutdown(void) +{ + // TODO: add some checking to see if all device handles have been closed, + // and try to close them all? This would involve keeping a list of + // currently-open devices. Not hard. + return hid_exit(); +} - hid_device *dev; +// Enumeration. +touchmouse_device_info* touchmouse_enumerate_devices(void) +{ + touchmouse_device_info* retval = NULL; + touchmouse_device_info** prev_next_pointer = &retval; struct hid_device_info *devs, *cur_dev; - - // Open HID device. - char* path = NULL; + // Get list of HID devices that match VendorID/ProductID devs = hid_enumerate(0x045e, 0x0773); // 0x045e = Microsoft, 0x0773 = TouchMouse cur_dev = devs; while(cur_dev) { @@ -270,43 +211,136 @@ int main(void) { #endif { printf("Found TouchMouse: %s\n", cur_dev->path); - path = cur_dev->path; - break; + *prev_next_pointer = (touchmouse_device_info*)malloc(sizeof(touchmouse_device_info)); + memset(*prev_next_pointer, 0, sizeof(**prev_next_pointer)); + printf("Allocated a touchmouse_device_info at address %p\n", *prev_next_pointer); + // We need to save both the pointer to this particular hid_device_info + // as well as the one from which it was initially allocated, so we can + // free it. + // Perhaps this would be better placed in a statically allocated list... + struct hid_device_info** pair = (struct hid_device_info**)malloc(2*sizeof(struct hid_device_info*)); + printf("Allocated two hid_device_info* at address %p\n", pair); + (*prev_next_pointer)->opaque = (void*)pair; + pair[0] = cur_dev; + pair[1] = devs; + prev_next_pointer = &((*prev_next_pointer)->next); } cur_dev = cur_dev->next; } - if (!path) { - fprintf(stderr, "Couldn't find TouchMouse, aborting\n"); - return -1; + // If we're about to return NULL, then we'd better free the HID enumeration + // handles now, since we'll get no data from the user when they call + // touchmouse_free_enumeration(NULL) + if (!retval) { + printf("Found no devices, so calling hid_free_enumeration()\n"); + hid_free_enumeration(devs); + } + return retval; +} + +void touchmouse_free_enumeration(touchmouse_device_info *devs) +{ + touchmouse_device_info* prev; + if (devs) { + hid_free_enumeration(((struct hid_device_info**)devs->opaque)[1]); + } + while (devs) { + prev = devs; + devs = devs->next; + free(prev->opaque); + free(prev); } - dev = hid_open_path(path); - if (!dev) { - fprintf(stderr, "Failed to open device %s, aborting\n", path); +} + + +int touchmouse_open(touchmouse_device **dev, touchmouse_device_info *dev_info) +{ + touchmouse_device* t_dev = (touchmouse_device*)malloc(sizeof(touchmouse_device)); + memset(t_dev, 0, sizeof(touchmouse_device)); + char* path = ((struct hid_device_info**)dev_info->opaque)[0]->path; + t_dev->dev = hid_open_path(path); + if (!t_dev->dev) { + fprintf(stderr, "hid_open() failed for device with path %s\n", path); + free(t_dev); return -1; } - hid_free_enumeration(devs); + hid_set_nonblocking(t_dev->dev, 1); // Enable nonblocking reads + *dev = t_dev; + return 0; +} + +int touchmouse_close(touchmouse_device *dev) +{ + hid_close(dev->dev); + free(dev); + return 0; +} - // Enable image updates - int res = 0; - res = enable_mouse_image_mode(dev); - if (res != 0) { - fprintf(stderr, "Failed to enable full touch updates, aborting\n"); +int touchmouse_set_device_mode(touchmouse_device *dev, touchmouse_mode mode) +{ + // We need to set two bits in a particular Feature report. We first fetch + // the current state of the feature report, set the interesting bits, and + // write that feature report back to the device. + printf("Reading current config flags\n"); + unsigned char data[27] = {0x22}; + int transferred = 0; + transferred = hid_get_feature_report(dev->dev, data, 27); + if (transferred > 0) { + printf("%d bytes received:\n", transferred); + int i; + for(i = 0; i < transferred; i++) { + printf("%02X ", data[i]); + } + printf("\n"); + } + if (transferred != 0x1B) { + fprintf(stderr, "Failed to read Feature 0x22 correctly; expected 27 bytes, got %d\n", transferred); return -1; } - // Initialize decoder - decoder* state = (decoder*)malloc(sizeof(decoder)); - memset(state, 0, sizeof(*state)); - state->cb = print_table; + // This particular byte/setting appears to control the + // "send all the raw input" flag. + switch (mode) { + case TOUCHMOUSE_DEFAULT: + data[4] = 0x00; + printf("Trying to disable full touch updates...\n"); + break; + case TOUCHMOUSE_RAW_IMAGE: + data[4] = 0x06; + printf("Trying to enable full touch updates...\n"); + break; + } - uint8_t last_timestamp = 0; + transferred = hid_send_feature_report(dev->dev, data, 27); + printf("Wrote %d bytes\n", transferred); + if (transferred == 0x1B) { + printf("Successfully set device mode.\n"); + return 0; + } + fprintf(stderr, "Failed to set device mode.\n"); + return -1; +} + +int touchmouse_set_image_update_callback(touchmouse_device *dev, touchmouse_image_callback callback) +{ + dev->cb = callback; + return 0; +} - // Poll for updates. +int touchmouse_set_device_userdata(touchmouse_device *dev, void *userdata) +{ + dev->userdata = userdata; + return 0; +} + +int touchmouse_process_events_timeout(touchmouse_device *dev, int milliseconds) { unsigned char data[256] = {}; - printf("polling for image updates...\n"); - hid_set_nonblocking(dev, 1); // Enable nonblocking reads - while(!quit) { - res = hid_read_timeout(dev, data, 255, 100); // 100 msec is hardly noticable, but keeps us from pegging a CPU core + int res; + int millisleft = milliseconds; + uint8_t first_timestamp_read = 0; + uint8_t last_timestamp = 0; + while(millisleft) { // TODO: make this TIME_NOT_YET_EXPIRED + res = hid_read_timeout(dev->dev, data, 255, millisleft); // TODO: fix this to be + // the right number of milliseconds if (res < 0 ) { fprintf(stderr, "hid_read() failed: %d\n", res); return -1; @@ -328,40 +362,50 @@ int main(void) { printf(" %02X", r->data[t]); } printf("\n"); - if (r->timestamp != last_timestamp) { - reset_decoder(state); // Reset decoder for next transfer - last_timestamp = r->timestamp; + // Reset the decoder if we've seen one timestamp already from earlier + // transfers, and this one doesn't match. + if (first_timestamp_read && r->timestamp != last_timestamp) { + reset_decoder(dev); // Reset decoder for next transfer } - for(t = 0; t < r->length - 1; t++) { // Note that we subtract one byte because the length includes the timestamp byte. + first_timestamp_read = 1; + last_timestamp = r->timestamp; + for(t = 0; t < r->length - 1; t++) { // We subtract one byte because the length includes the timestamp byte. int res; // Yes, we process the low nybble first. Embedded systems are funny like that. - res = process_nybble(state, r->data[t] & 0xf); + res = process_nybble(dev, r->data[t] & 0xf); if (res == DECODER_COMPLETE) { - state->cb(state, state->image, r->timestamp); - reset_decoder(state); // Reset decoder for next transfer - break; + dev->timestamp_last_completed = r->timestamp; + touchmouse_callback_info cbinfo; + cbinfo.userdata = dev->userdata; + cbinfo.image = dev->image; + cbinfo.timestamp = dev->timestamp_last_completed; + dev->cb(&cbinfo); + reset_decoder(dev); // Reset decoder for next transfer + return 0; } if (res == DECODER_ERROR) { fprintf(stderr, "Caught error in decoder, aborting!\n"); - goto cleanup; + return -1; } - res = process_nybble(state, (r->data[t] & 0xf0) >> 4); + res = process_nybble(dev, (r->data[t] & 0xf0) >> 4); if (res == DECODER_COMPLETE) { - state->cb(state, state->image, r->timestamp); - reset_decoder(state); // Reset decoder for next transfer - break; + dev->timestamp_last_completed = r->timestamp; + touchmouse_callback_info cbinfo; + cbinfo.userdata = dev->userdata; + cbinfo.image = dev->image; + cbinfo.timestamp = dev->timestamp_last_completed; + dev->cb(&cbinfo); + reset_decoder(dev); // Reset decoder for next transfer + return 0; } if (res == DECODER_ERROR) { fprintf(stderr, "Caught error in decoder, aborting!\n"); - goto cleanup; + return -1; } } } } } -cleanup: - hid_close(dev); - dev = NULL; - hid_exit(); return 0; } + -- 2.39.5