diff --git a/.gitignore b/.gitignore index a856fad..a4cfcb3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ libspnavdev.a libspnavdev.so* libspnavdev.dll test +build*/ +.vscode/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b2c0430 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,52 @@ +# libspnavdev - direct 6dof device handling library +# Copyright (C) 2021 Collabora, Ltd. +# +# SPDX-License-Identifier: GPL-3-or-later +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +cmake_minimum_required(VERSION 3.1...3.19) +project( + libspnavdev + VERSION 0.1 + LANGUAGES C) + +find_package(HIDAPI) + + +add_library(spnavdev + src/serdev.c + src/spnavdev.c + src/usbdev.c +) + +if(HIDAPI_FOUND) + set(HAVE_HIDAPI TRUE) + target_link_libraries(spnavdev PRIVATE ${HIDAPI_LIBRARY}) + target_include_directories(spnavdev PRIVATE ${HIDAPI_INCLUDE_DIR}) +endif() +target_include_directories(spnavdev PUBLIC $) + +configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) +target_include_directories(spnavdev PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +target_compile_definitions(spnavdev PRIVATE HAVE_CONFIG_H) + +if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) + # We are our own project, not a subproject + if(WIN32) + add_subdirectory(examples/win32) + else() + add_subdirectory(examples/unix) + endif() +endif() diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..25ee887 --- /dev/null +++ b/config.h.in @@ -0,0 +1,21 @@ +/* +libspnavdev - direct 6dof device handling library +Copyright (C) 2020 John Tsiombikas + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#ifndef SPNAVDEV_CONFIG_H_ +#define SPNAVDEV_CONFIG_H_ +#cmakedefine HAVE_HIDAPI +#endif \ No newline at end of file diff --git a/examples/unix/CMakeLists.txt b/examples/unix/CMakeLists.txt new file mode 100644 index 0000000..8661306 --- /dev/null +++ b/examples/unix/CMakeLists.txt @@ -0,0 +1,20 @@ +# libspnavdev - direct 6dof device handling library +# Copyright (C) 2021 Collabora, Ltd. +# +# SPDX-License-Identifier: GPL-3-or-later +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +add_executable(test test.c) +target_link_libraries(test PRIVATE spnavdev) \ No newline at end of file diff --git a/examples/unix/test.c b/examples/unix/test.c index 2ec57ac..b2dd38d 100644 --- a/examples/unix/test.c +++ b/examples/unix/test.c @@ -26,6 +26,7 @@ int main(int argc, char **argv) fd_set rdset; union spndev_event ev; const char *s; + int led=0; signal(SIGINT, sighandler); @@ -53,11 +54,17 @@ int main(int argc, char **argv) case SPNDEV_BUTTON: if((s = spndev_button_name(dev, ev.bn.num))) { - printf("button %d (%s) ", ev.bn.num, s); + printf("button %d (\"%s\") ", ev.bn.num, s); } else { printf("button %d ", ev.bn.num); } puts(ev.bn.press ? "pressed" : "released"); + + if (ev.bn.press) { + spndev_set_led(dev, led); + led = !led; + } + break; default: diff --git a/examples/win32/CMakeLists.txt b/examples/win32/CMakeLists.txt new file mode 100644 index 0000000..8661306 --- /dev/null +++ b/examples/win32/CMakeLists.txt @@ -0,0 +1,20 @@ +# libspnavdev - direct 6dof device handling library +# Copyright (C) 2021 Collabora, Ltd. +# +# SPDX-License-Identifier: GPL-3-or-later +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +add_executable(test test.c) +target_link_libraries(test PRIVATE spnavdev) \ No newline at end of file diff --git a/examples/win32/test.c b/examples/win32/test.c new file mode 100644 index 0000000..e0fce36 --- /dev/null +++ b/examples/win32/test.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include "spnavdev.h" + +static struct spndev *dev; +static int quit = 0; + +int main(int argc, char **argv) +{ + int fd; + union spndev_event ev; + const char *s; + int led=0; + + if(!(dev = spndev_open(argv[1]))) { + fprintf(stderr, "Failed to open 6dof device %s\n", argv[1] ? argv[1] : ""); + return 1; + } + fd = spndev_fd(dev); + + printf("Monitoring device, ctrl-c to quit\n"); + + while(!quit) { + + if (spndev_process(dev, &ev)) { + switch (ev.type) { + case SPNDEV_MOTION: + printf("motion: T[%+6d %+6d %+6d] R[%+6d %+6d %+6d]\n", + ev.mot.v[0], ev.mot.v[1], ev.mot.v[2], ev.mot.v[3], + ev.mot.v[4], ev.mot.v[5]); + break; + + case SPNDEV_BUTTON: + if ((s = spndev_button_name(dev, ev.bn.num))) { + printf("button %d (\"%s\") ", ev.bn.num, s); + } else { + printf("button %d ", ev.bn.num); + } + puts(ev.bn.press ? "pressed" : "released"); + + if (ev.bn.press) { + spndev_set_led(dev, led); + spndev_write_lcd(dev, 0xAA); + spndev_set_lcd_bl(dev, led); + led = !led; + } + + break; + + default: + break; + } + } + + } + + spndev_close(dev); + return 0; +} diff --git a/src/dev.h b/src/dev.h index 9730471..33af30c 100644 --- a/src/dev.h +++ b/src/dev.h @@ -21,21 +21,23 @@ along with this program. If not, see . #include "spnavdev.h" struct axisprop { - char *name; + const char *name; int minval, maxval, deadz; }; struct spndev { char *name, *path; - int usb_vendor, usb_product; + uint16_t usb_vendor, usb_product; + unsigned internal_id; int fd; /* UNIX file descriptor */ void *handle; /* Win32 handle */ int num_axes, num_buttons; struct axisprop *aprop; - char **bn_name; + const char **bn_name; int led; + int lcdbl; void *uptr, *drvdata; @@ -44,10 +46,15 @@ struct spndev { void (*setled)(struct spndev*, int led); int (*getled)(struct spndev*); + + void (*setlcdbl)(struct spndev *, int bl); + int (*getlcdbl)(struct spndev *); + + void (*writelcd)(struct spndev *, int state); }; -int spndev_usb_open(struct spndev *dev, const char *devstr, int vend, int prod); +int spndev_usb_open(struct spndev *dev, const char *devstr, uint16_t vend, uint16_t prod); int spndev_ser_open(struct spndev *dev, const char *devstr); diff --git a/src/serdev.c b/src/serdev.c index 1b07c80..02d580b 100644 --- a/src/serdev.c +++ b/src/serdev.c @@ -1,7 +1,18 @@ + +#if defined(_WIN32) +#define _CRT_NONSTDC_NO_WARNINGS /* Avoid error with strdup on Win32 */ +#endif #include +#include +#include +#include +#if defined(_WIN32) +#undef _CRT_NONSTDC_NO_WARNINGS +#endif #include "dev.h" +#include "serio.h" -#if defined(__i386__) || defined(__ia64__) || defined(WIN32) || \ +#if defined(__i386__) || defined(__ia64__) || defined(_WIN32) || \ (defined(__alpha__) || defined(__alpha)) || \ defined(__arm__) || \ (defined(__mips__) && defined(__MIPSEL__)) || \ @@ -15,6 +26,7 @@ #define INP_BUF_SZ 256 +#define TURBO_MAGELLAN_COMPRESS enum { SB4000 = 1, @@ -30,10 +42,12 @@ struct sball { unsigned int keystate, keymask; +#ifndef _WIN32 struct termios saved_term; +#endif int saved_mstat; - int (*parse)(struct sball*, int, char*, int); + int (*parse)(struct spndev*, union spndev_event*, int, char*, int); }; enum { @@ -58,13 +72,15 @@ static struct { {"Spaceball 2003C", 8, {"1", "2", "3", "4", "5", "6", "7", "8"}}, {"Spaceball 3003/3003C", 2, {"R", "L"}}, {"Spaceball 4000FLX/5000FLX-A", 12, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C"}}, - {"Magellan SpaceMouse", 9, {"1", "2", "3", "4", "5", "6", "7", "8", "*"}}, + {"Magellan SpaceMouse", 11, {"1", "2", "3", "4", "5", "6", "7", "8", "*", "+", "-"}}, {"Spaceball 5000FLX", 12, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C"}}, {"CadMan", 4, {"1", "2", "3", "4"}}, {"Space Explorer", 14, {"1", "2", "T", "L", "R", "F", "ALT", "ESC", "SHIFT", "CTRL", "Fit", "Panel", "+", "-", "2D"}}, {0, 0, {0}} }; +static void close_dev_serial(struct spndev* dev); +static int read_dev_serial(struct spndev* dev, union spndev_event* evt); static int init_dev(struct spndev *dev, int type); static int stty_sball(int fd, struct sball *sb); @@ -72,16 +88,16 @@ static int stty_mag(int fd, struct sball *sb); static void stty_save(int fd, struct sball *sb); static void stty_restore(int fd, struct sball *sb); -static int proc_input(struct sball *sb); +static int proc_input(struct spndev* dev, union spndev_event* evt); -static int mag_parsepkt(struct sball *sb, int id, char *data, int len); -static int sball_parsepkt(struct sball *sb, int id, char *data, int len); +static int mag_parsepkt(struct spndev* dev, union spndev_event* evt, int id, char* data, int len); +static int sball_parsepkt(struct spndev* dev, union spndev_event* evt, int id, char* data, int len); static int guess_device(const char *verstr); static void make_printable(char *buf, int len); static int read_timeout(int fd, char *buf, int bufsz, long tm_usec); - +static void gen_button_events(struct sball* sb, unsigned int prev, union spndev_event* evt); int spndev_ser_open(struct spndev *dev, const char *devstr) @@ -90,13 +106,13 @@ int spndev_ser_open(struct spndev *dev, const char *devstr) char buf[128]; struct sball *sb = 0; - if((fd = open(devstr, O_RDWR | O_NOCTTY | O_NONBLOCK)) == -1) { - fprintf(stderr, "spndev_open: failed to open device: %s: %s\n", dev, strerror(errno)); + if(-1 == (fd = seropen(devstr))) { + fprintf(stderr, "spndev_open: failed to open device: %s: %s\n", devstr, strerror(errno)); return -1; } dev->fd = fd; dev->path = strdup(devstr); - dev->usb_vendor = dev->usb_product = -1; + dev->usb_vendor = dev->usb_product = 0xFFFF; dev->handle = 0; if(!(sb = calloc(1, sizeof *sb))) { @@ -104,19 +120,26 @@ int spndev_ser_open(struct spndev *dev, const char *devstr) goto err; } dev->drvdata = sb; + dev->close = close_dev_serial; + dev->read = read_dev_serial; + dev->setled = 0; + dev->getled = 0; + dev->setlcdbl = 0; + dev->getlcdbl = 0; + dev->writelcd = 0; stty_save(fd, sb); if(stty_sball(fd, sb) == -1) { goto err; } - write(fd, "\r@RESET\r", 8); + serwrite(fd, "\r@RESET\r", 8); if((sz = read_timeout(fd, buf, sizeof buf - 1, 2000000)) > 0 && strstr(buf, "\r@1")) { /* we got a response, so it's a spaceball */ make_printable(buf, sz); if(init_dev(dev, guess_device(buf)) == -1) { - fprintf(stderr, "spndev_open: failed to initialized device structure\n"); + fprintf(stderr, "spndev_open: failed to initialize device structure\n"); goto err; } @@ -126,17 +149,17 @@ int spndev_ser_open(struct spndev *dev, const char *devstr) * a key event to find out as soon as possible if this is a 4000flx with * 12 buttons */ - write(fd, "\rCB\rMSSV\rk\r", 11); + serwrite(fd, "\rCB\rMSSV\rk\r", 11); sb->parse = sball_parsepkt; - return dev; + return 0; } /* try as a magellan spacemouse */ if(stty_mag(fd, sb) == -1) { goto err; } - write(fd, "vQ\r", 3); + serwrite(fd, "vQ\r", 3); if((sz = read_timeout(fd, buf, sizeof buf - 1, 250000)) > 0 && buf[0] == 'v') { make_printable(buf, sz); @@ -148,19 +171,64 @@ int spndev_ser_open(struct spndev *dev, const char *devstr) printf("Magellan SpaceMouse detected: %s\n", dev->name); /* set 3D mode, not-dominant-axis, pass through motion and button packets */ - write(fd, "m3\r", 3); +// serwrite(fd, "m3\r", 3); +#ifdef TURBO_MAGELLAN_COMPRESS + /* set 3D mode, not-dominant-axis, pass through motion and button packets, extended key + mode, turbo/compressed Magellan mode */ + serwrite(fd, "c33\r", 4); +#else + /* set 3D mode, not-dominant-axis, pass through motion and button packets, extended key + mode */ + serwrite(fd, "c32\r", 4); +#endif + sz = read_timeout(fd, buf, sizeof buf - 1, 250000); sb->parse = mag_parsepkt; - return dev; + return 0; } err: - stty_restore(fd, sb); - close(fd); - free(sb); + close_dev_serial(dev); return -1; } +static void close_dev_serial(struct spndev *dev) +{ + if(dev->drvdata) { + stty_restore(dev->fd, dev->drvdata); + serclose(dev->fd); + free(dev->drvdata); + } + dev->drvdata = 0; +} + +static int read_dev_serial(struct spndev* dev, union spndev_event* evt) +{ + int sz; + struct sball* sb = (struct sball*)dev->drvdata; + + if (!sb) return -1; + + evt->type = SPNDEV_NONE; + +// while ((sz = serread(dev->fd, sb->buf + sb->len, INP_BUF_SZ - sb->len - 1)) > 0) { + { + sz = serread(dev->fd, sb->buf + sb->len, (16 > INP_BUF_SZ - sb->len - 1) ? INP_BUF_SZ - sb->len - 1 : 16); + sb->len += sz; + proc_input(dev, evt); + } + + /* if we fill the input buffer, make a last attempt to parse it, and discard + * it so we can receive more + */ + if (sb->len >= INP_BUF_SZ) { + proc_input(dev, evt); + sb->len = 0; + } + + return evt->type; +} + static int init_dev(struct spndev *dev, int type) { int i; @@ -172,6 +240,7 @@ static int init_dev(struct spndev *dev, int type) } dev->num_axes = 6; dev->num_buttons = devinfo[type].nbuttons; + sb->keymask = 0xffff >> (16 - dev->num_buttons); if(!(dev->aprop = malloc(dev->num_axes * sizeof *dev->aprop))) { free(dev->name); @@ -182,7 +251,7 @@ static int init_dev(struct spndev *dev, int type) free(dev->name); } - for(i=0; i<6; i++) { + for(i = 0; i < dev->num_axes; i++) { dev->aprop[i].name = axnames[i]; /* TODO min/max/deadz */ } @@ -191,6 +260,7 @@ static int init_dev(struct spndev *dev, int type) } /* TODO setup callbacks */ + return 0; } static int guess_device(const char *verstr) @@ -249,3 +319,288 @@ static int guess_device(const char *verstr) fprintf(stderr, "Please include the following version string in your bug report: \"%s\"\n", verstr); return DEV_UNKNOWN; } + +static int stty_sball(int fd, struct sball* sb) { return 0; } +static int stty_mag(int fd, struct sball* sb) { return 0; } +static void stty_save(int fd, struct sball* sb) {} +static void stty_restore(int fd, struct sball* sb) {} + +static int proc_input(struct spndev* dev, union spndev_event* evt) +{ + struct sball* sb = (struct sball*)dev->drvdata; + int sz; + char* bptr = sb->buf; + char* start = sb->buf; + char* end = sb->buf + sb->len; + + /* see if we have a CR in the buffer */ + while (bptr < end) { + if (*bptr == '\r') { + *bptr = 0; + sb->parse(dev, evt, *start, start + 1, bptr - start - 1); + start = ++bptr; + } else { + bptr++; + } + } + + sz = start - sb->buf; + if (sz > 0) { + memmove(sb->buf, start, sz); // PAR@@@ hmmmm.... reading from memory beyond buf ? + sb->len -= sz; + } + return 0; +} + +static int mag_parsepkt(struct spndev* dev, union spndev_event* evt, int id, char *data, int len) +{ + struct sball* sb = (struct sball*)dev->drvdata; + int i, prev, motion_pending = 0; + unsigned int prev_key; + unsigned check_sum=0; + + /*printf("magellan packet: %c - %s (%d bytes)\n", (char)id, data, len);*/ + + switch(id) { + case 'd': +#ifdef TURBO_MAGELLAN_COMPRESS + if(len != 14) { +#else + if(len != 24) { +#endif + fprintf(stderr, "magellan: invalid data packet, expected 24 bytes, got: %d\n", len); + return -1; + } +// evt->type = SPNDEV_MOTION; + for(i = 0; i < dev->num_axes; i++) { + prev = evt->mot.v[i]; + +#ifdef TURBO_MAGELLAN_COMPRESS + evt->mot.v[i] = ((((int)data[0] & 0x3f) << 6) | (data[1] & 0x3f)) - 0x800; + check_sum += (unsigned char)(data[0]); + check_sum += (unsigned char)(data[1]); + data += 2; +#else + evt->mot.v[i] = ((((int)data[0] & 0xf) << 12) | (((int)data[1] & 0xf) << 8) | + (((int)data[2] & 0xf) << 4) | (data[3] & 0xf)) - 0x8000; + data += 4; +#endif + + if(evt->mot.v[i] != prev) { +// PAR@@@ enqueue_motion(sb, i, evt->mot.v[i]); + motion_pending++; + } + } +#ifdef TURBO_MAGELLAN_COMPRESS + { + /* Verify checksum */ + unsigned checsum_rec = ((((unsigned)data[0] & 0x3f) << 6) | (data[1] & 0x3f)); + if(check_sum != checsum_rec) { + fprintf(stderr, "magellan: invalid check sum, expected %u, got: %u\n", + checsum_rec, check_sum); + }; + data += 2; + } +#endif + + if(motion_pending) { +// PAR@@@ enqueue_motion(sb, -1, 0); + evt->type = SPNDEV_MOTION; + } + break; + + case 'k': + if(len < 3) { + fprintf(stderr, "magellan: invalid keyboard pakcet, expected 3 bytes, got: %d\n", len); + return -1; + } + prev_key = sb->keystate; + sb->keystate = (data[0] & 0xf) | ((data[1] & 0xf) << 4) | (((unsigned int)data[2] & 0xf) << 8); + if(len > 3) { + sb->keystate |= ((unsigned int)data[3] & 0xf) << 12; + } + + if(sb->keystate != prev_key) { + gen_button_events(sb, prev_key, evt); + } + break; + + case 'e': + if(data[0] == 1) { + fprintf(stderr, "magellan error: illegal command: %c%c\n", data[1], data[2]); + } else if(data[0] == 2) { + fprintf(stderr, "magellan error: framing error\n"); + } else { + fprintf(stderr, "magellan error: unknown device error\n"); + } + return -1; + + default: + break; + } + return 0; +} + +static int sball_parsepkt(struct spndev* dev, union spndev_event* evt, int id, char *data, int len) +{ + struct sball* sb = (struct sball*)dev->drvdata; + int i, prev, motion_pending = 0; + char c, *rd, *wr; + unsigned int prev_key; + + /* decode data packet, replacing escaped values with the correct ones */ + rd = wr = data; + while(rd < data + len) { + if((c = *rd++) == '^') { + switch(*rd++) { + case 'Q': + *wr++ = 0x11; /* XON */ + break; + case 'S': + *wr++ = 0x13; /* XOFF */ + break; + case 'M': + *wr++ = 13; /* CR */ + break; + case '^': + *wr++ = '^'; + break; + default: + fprintf(stderr, "sball decode: ignoring invalid escape code: %xh\n", (unsigned int)c); + } + } else { + *wr++ = c; + } + } + len = wr - data; /* update the decoded length */ + + switch(id) { + case 'D': + if(len != 14) { + fprintf(stderr, "sball: invalid data packet, expected 14 bytes, got: %d\n", len); + return -1; + } + + for(i = 0; i < dev->num_axes; i++) { + data += 2; + prev = evt->mot.v[i]; +#ifdef SBALL_BIG_ENDIAN + evt->mot.v[i] = data[0] | data[1] << 8; +#else + evt->mot.v[i] = data[1] | data[0] << 8; +#endif + + if(evt->mot.v[i] != prev) { +// PAR@@@ enqueue_motion(sb, i, evt->mot.v[i]); + motion_pending++; + } + } + if(motion_pending) { +// PAR@@@ enqueue_motion(sb, -1, 0); + evt->type = SPNDEV_MOTION; + } + break; + + case 'K': + if(len != 2) { + fprintf(stderr, "sball: invalid key packet, expected 2 bytes, got: %d\n", len); + return -1; + } + if(sb->flags & SB4000) break; /* ignore K packets from spaceball 4000 devices */ + + prev_key = sb->keystate; + /* data[1] bits 0-3 -> buttons 0,1,2,3 + * data[1] bits 4,5 (3003 L/R) -> buttons 0, 1 + * data[0] bits 0-2 -> buttons 4,5,6 + * data[0] bit 4 is (2003 pick) -> button 7 + */ + sb->keystate = ((data[1] & 0xf) | ((data[1] >> 4) & 3) | ((data[0] & 7) << 4) | + ((data[0] & 0x10) << 3)) & sb->keymask; + + if(sb->keystate != prev_key) { + gen_button_events(sb, prev_key, evt); + } + break; + + case '.': + if(len != 2) { + fprintf(stderr, "sball: invalid sb4k key packet, expected 2 bytes, got: %d\n", len); + return -1; + } + /* spaceball 4000 key packet */ + if(!(sb->flags & SB4000)) { + printf("Switching to spaceball 4000flx/5000flx-a mode (12 buttons) \n"); + sb->flags |= SB4000; + dev->num_buttons = 12; /* might have guessed 8 before */ // PAR@@@@@@ FIIIIX reallocate the button names array at the very least! + sb->keymask = 0xffff >> (16 - dev->num_buttons); + } + /* update orientation flag (actually don't bother) */ + /* + if(data[0] & 0x20) { + sb->flags |= FLIPXY; + } else { + sb->flags &= ~FLIPXY; + } + */ + + prev_key = sb->keystate; + /* data[1] bits 0-5 -> buttons 0,1,2,3,4,5 + * data[1] bit 7 -> button 6 + * data[0] bits 0-4 -> buttons 7,8,9,10,11 + */ + sb->keystate = (data[1] & 0x3f) | ((data[1] & 0x80) >> 1) | ((data[0] & 0x1f) << 7); + if(sb->keystate != prev_key) { + gen_button_events(sb, prev_key, evt); + } + break; + + case 'E': + fprintf(stderr, "sball: error:"); + for(i=0; ikeystate ^ prev; + + for(i = 0; i < 16; i++) { + if(diff & bit) { + evt->type = SPNDEV_BUTTON; + evt->bn.press = sb->keystate & bit ? 1 : 0; + evt->bn.num = i; + } + bit <<= 1; + } +} diff --git a/src/serio.h b/src/serio.h new file mode 100644 index 0000000..c6f15b5 --- /dev/null +++ b/src/serio.h @@ -0,0 +1,35 @@ +/* +libspnavdev - RS-232 serial port HAL +Copyright (C) 2021 Peter Ruevski + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef SERIO_H_ +#define SERIO_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +int seropen(char const* devstr); +int serread(int h, void* buf, unsigned int len); +int serwrite(int h, void const* buf, unsigned int len); +int serclose(int h); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SERIO_H_ */ diff --git a/src/serio_posix.c b/src/serio_posix.c new file mode 100644 index 0000000..1bef13c --- /dev/null +++ b/src/serio_posix.c @@ -0,0 +1,50 @@ +/* +libspnavdev - RS-232 access with standard POSIX file IO +Copyright (C) 2021 Peter Ruevski + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef _WIN32 + +#if defined(_WIN32) +# define _CRT_NONSTDC_NO_WARNINGS /* Avoid errors with open, read, write, close on Win32*/ +#endif + +#include +#include + +#if defined(_WIN32) +# undef _CRT_NONSTDC_NO_WARNINGS +#endif + +// http://unixwiz.net/techtips/termios-vmin-vtime.html + +int seropen(char const* devstr) { + return open(devstr, _O_RDWR /* | O_NOCTTY | O_NONBLOCK */); +} + +int serread(int h, void* buf, unsigned int len) { + return read(h, buf, len); +} + +int serwrite(int h, void const* buf, unsigned int len) { + return write(h, buf, len); +} + +int serclose(int h) { + return close(h); +} + +#endif \ No newline at end of file diff --git a/src/serio_win32.c b/src/serio_win32.c new file mode 100644 index 0000000..1b40787 --- /dev/null +++ b/src/serio_win32.c @@ -0,0 +1,122 @@ +/* +libspnavdev - RS-232 access with Win32 API +Copyright (C) 2021 Peter Ruevski + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifdef _WIN32 + +#define WINVER 0x0501 +#define _WIN32_WINNT 0x0501 +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include "serio.h" + +#define checkerror(funccall, msg) \ + if(!funccall) { \ + print_error(msg); \ + serclose((int)h); \ + return -1; \ + } + +static void print_error(const char* context); + +int seropen(char const* devstr) { + HANDLE h; + COMMTIMEOUTS CommTimeouts; + DCB dcb; + + h = CreateFile(devstr /* lpFileName */, GENERIC_READ | GENERIC_WRITE /* dwDesiredAccess */, + 0 /* dwShareMode */, NULL /* lpSecurityAttributes */, + OPEN_EXISTING /* dwCreationDisposition */, + FILE_ATTRIBUTE_NORMAL /* dwFlagsAndAttributes */, NULL /* hTemplateFile */ + ); + + if(INVALID_HANDLE_VALUE == h) { + print_error("Could not open serial device"); + return -1; + } + + // Flush away any bytes previously read or written. + checkerror(FlushFileBuffers(h), "Failed to flush serial port"); + + // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddser/ns-ntddser-_serial_timeouts + // https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-commtimeouts + // https://docs.microsoft.com/en-us/windows-hardware/drivers/serports/setting-read-and-write-timeouts-for-a-serial-device + CommTimeouts.ReadIntervalTimeout = 2 * 1000 * (1+8+1) / 9600; // Inter byte time out in ms. 2 for margin * (1 start 8 data 1 stop bits) / 9600bps /* = MAXDWORD; for non blocking */ + CommTimeouts.ReadTotalTimeoutMultiplier = 0; + CommTimeouts.ReadTotalTimeoutConstant = 0; + + CommTimeouts.WriteTotalTimeoutMultiplier = 2 * 1000 * (1+8+1)/ 9600; + CommTimeouts.WriteTotalTimeoutConstant = 0; + checkerror(SetCommTimeouts(h, &CommTimeouts), "Failed to set serial timeouts"); + + // https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/cc732236(v=ws.11) + // BuildCommDCBAndTimeouts("baud=9600 parity=N data=8 stop=1 xon=on to=on", &dcb, &CommTimeouts); // does not do what I need + dcb.DCBlength = sizeof(DCB); + checkerror(GetCommState(h, &dcb), "Failed to get serial state"); + BuildCommDCB("baud=9600 parity=N data=8 stop=1 xon=on dtr=on rts=on", &dcb); // RTS_CONTROL_ENABLE + checkerror(SetCommState(h, &dcb), "Failed to set serial state"); + + return (int)h; +} + +int serread(int h, void* buf, unsigned int len) { + DWORD read; + if(ReadFile((void*)h, buf, len, &read, NULL)) { + return read; + } else { + print_error("Read failed"); + return -1; + }; +} + +int serwrite(int h, void const* buf, unsigned int len) { + DWORD written; + if(WriteFile((void*)h, buf, len, &written, NULL)) { + if(written != len) { + print_error("Not all written"); + } + return written; + } else { + print_error("Write failed"); + return -1; + }; +} + +int serclose(int h) { + if(CloseHandle((void*)h)) { + return 0; + } else { + print_error("Close failed"); + return -1; + } +} + +static void print_error(const char* context) { + DWORD error_code = GetLastError(); + char buffer[256]; + DWORD size = + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, error_code, + MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), buffer, sizeof(buffer), NULL); + if(size == 0) { + buffer[0] = 0; + } + fprintf(stderr, "%s: %s\n", context, buffer); +} + +#endif \ No newline at end of file diff --git a/src/spnavdev.c b/src/spnavdev.c index d4fe632..3531771 100644 --- a/src/spnavdev.c +++ b/src/spnavdev.c @@ -23,14 +23,14 @@ along with this program. If not, see . struct spndev *spndev_open(const char *devstr) { struct spndev *dev; - unsigned int vendor = 0xffffffff, product = 0xffffffff; + uint16_t vendor = 0xffff, product = 0xffff; if(!(dev = malloc(sizeof *dev))) { perror("spndev_open: failed to allocate device structure"); return 0; } - if(!devstr || sscanf(devstr, "%x:%x", &vendor, &product) == 2) { + if(!devstr || sscanf(devstr, "%hx:%hx", &vendor, &product) == 2) { if(spndev_usb_open(dev, devstr, vendor, product) == -1) { free(dev); return 0; @@ -73,9 +73,9 @@ const char *spndev_path(struct spndev *dev) return dev->path; } -int spndev_usbid(struct spndev *dev, int *vend, int *prod) +int spndev_usbid(struct spndev *dev, uint16_t*vend, uint16_t*prod) { - if(dev->usb_vendor == -1) return -1; + if( (dev->usb_vendor == 0) || (dev->usb_vendor == 0xFFFF) ) return -1; *vend = dev->usb_vendor; *prod = dev->usb_product; return 0; @@ -92,7 +92,7 @@ const char *spndev_axis_name(struct spndev *dev, int axis) if(axis < 0 || axis >= dev->num_axes) { return 0; } - return dev->axis_name[axis]; + return dev->aprop[axis].name; } int spndev_axis_min(struct spndev *dev, int axis) @@ -100,7 +100,7 @@ int spndev_axis_min(struct spndev *dev, int axis) if(axis < 0 || axis >= dev->num_axes) { return 0; } - return dev->minval[axis]; + return dev->aprop[axis].minval; } int spndev_axis_max(struct spndev *dev, int axis) @@ -108,7 +108,7 @@ int spndev_axis_max(struct spndev *dev, int axis) if(axis < 0 || axis >= dev->num_axes) { return 0; } - return dev->maxval[axis]; + return dev->aprop[axis].maxval; } int spndev_num_buttons(struct spndev *dev) @@ -167,3 +167,27 @@ int spndev_get_deadzone(struct spndev *dev, int axis) { return -1; /* TODO */ } + +int spndev_set_lcd_bl(struct spndev *dev, int bl) +{ + if(!dev->setlcdbl) { + return -1; + } + dev->setlcdbl(dev, bl); + return 0; +} + +int spndev_get_lcd_bl(struct spndev *dev) { + if(!dev->getlcdbl) { + return 0; + } + return dev->getlcdbl(dev); +} + +int spndev_write_lcd(struct spndev *dev, int state) { + if(!dev->writelcd) { + return -1; + } + dev->writelcd(dev, state); + return 0; +} diff --git a/src/spnavdev.h b/src/spnavdev.h index bbd4d59..b8e5f65 100644 --- a/src/spnavdev.h +++ b/src/spnavdev.h @@ -18,6 +18,12 @@ along with this program. If not, see . #ifndef SPNAVDEV_H_ #define SPNAVDEV_H_ +#include + +#ifdef __cplusplus +extern "C" { +#endif + struct spndev; enum { @@ -57,7 +63,7 @@ void *spndev_get_userptr(struct spndev *dev); /* device information */ const char *spndev_name(struct spndev *dev); const char *spndev_path(struct spndev *dev); -int spndev_usbid(struct spndev *dev, int *vend, int *prod); +int spndev_usbid(struct spndev *dev, uint16_t *vend, uint16_t *prod); int spndev_num_axes(struct spndev *dev); const char *spndev_axis_name(struct spndev *dev, int axis); @@ -83,5 +89,12 @@ int spndev_get_led(struct spndev *dev); int spndev_set_deadzone(struct spndev *dev, int axis, int dead); int spndev_get_deadzone(struct spndev *dev, int axis); +int spndev_set_lcd_bl(struct spndev *dev, int state); +int spndev_get_lcd_bl(struct spndev *dev); +int spndev_write_lcd(struct spndev *dev, int state); + +#ifdef __cplusplus +} /* extern "C" */ +#endif #endif /* SPNAVDEV_H_ */ diff --git a/src/usbdev.c b/src/usbdev.c index 9dbbf49..a2afb04 100644 --- a/src/usbdev.c +++ b/src/usbdev.c @@ -1,8 +1,580 @@ +/* +libspnavdev - USB HID 6dof device handling (through hidapi) +Copyright (C) 2021 Peter Ruevski + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#else +/* assume hidapi */ +#define HAVE_HIDAPI +#endif + #include +#include +#include +#include + +#ifdef HAVE_HIDAPI +#include "hidapi.h" #include "dev.h" -int spndev_usb_open(struct spndev *dev, const char *devstr, int vend, int prod) +#define max_num_btn 48 +#define max_buf_size 80 + +static const size_t oldposrotreportsize = 7; +static const size_t newposrotreportsize = 13; +static const int axis_max = 511; +static const int axis_min = -512; +static const int axis_deadz = 0; + +typedef struct spnav_hid { + unsigned char btn[max_num_btn]; + unsigned char buf[max_buf_size]; +} tspnav_hid; + +static const struct { + uint16_t vid; + uint16_t pid; + enum {O=0, N=1} posrotreport; // Old (two reports) or New (single report) positon and rotation data + const char* name; + const char* pn; // "P/N:" part number "Part No." from the label or box + int nbuttons; + const char* const bnames[32]; +} +devinfo[] = { + {0x046d, 0xc603, O, "SpaceMouse Plus XT USB" , "", 10, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}}, // Manual says 11 is the * reported? Buttons order? Side button names? "L" "R"? + {0x046d, 0xc605, O, "CADman", "", 4, {"1", "2", "3", "4"}}, // Buttons order? Names? + {0x046d, 0xc606, O, "SpaceMouse Classic USB", "", 8, {"1", "2", "3", "4", "5", "6", "7", "8"}}, // Manual says 11 is the * reported? + {0x046d, 0xc621, O, "Spaceball 5000 USB", "5000 USB", 12, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C"}}, // Tested working + {0x046d, 0xc623, O, "SpaceTraveler", "", 8, {"1", "2", "3", "4", "5", "6", "7", "8"}}, // Tested working + {0x046d, 0xc625, O, "SpacePilot", "SP1 USB", 21, {"1", "2", "3", "4", "5", "6", "T", "L", "R", "F", "ESC", "ALT", "SHIFT", "CTRL", "FIT", "PANEL", "+", "-", "Dom", "3D Lock", "Config"}}, // IBM "(P) P/N: 60K9206", "(P) FRU P/N: 4K9204" Tested working + {0x046d, 0xc626, O, "SpaceNavigator", "3DX-700028", 2, {"MENU", "FIT"} }, // also "3DX-600027" "3DX-600028" "3DX-600029"? "SpaceNavigator USB" on the label + {0x046d, 0xc627, O, "SpaceExplorer", "3DX-700026", 15, {"1", "2", "T", "L", "R", "F", "ESC", "ALT", "SHIFT", "CTRL", "FIT", "PANEL", "+", "-", "2D"}}, // Also "3DX-600029"? "3DX-600025" both "SpaceExplorer USB" "DLZ-3DX-700026 (B)" on the other side, "Part No. 3DX-700026" on the box // Tested working + {0x046d, 0xc628, O, "SpaceNavigator for Notebooks", "3DX-700034", 2, {"MENU", "FIT"}}, + {0x046d, 0xc629, O, "SpacePilot Pro", "3DX-700036", 31, {"MENU", "FIT", "T", "L", "R", "F", "B", "BK", "Roll +", "Roll -", "ISO1", "ISO2", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "ESC", "ALT", "SHIFT", "CTRL", "Rot", "Pan/Zoom", "Dom", "+", "-"}}, + {0x046d, 0xc62b, O, "SpaceMouse Pro", "3DX-700040", 27/*15*/, {"Menu", "FIT", "T", "", "R", "F", "", "", "Roll +", "", "", "", "1", "2", "3", "4", "", "", "", "", "", "", "ESC", "ALT", "SHIFT", "CTRL", "Rot"}}, // < ReportID>16 < Mask>00001000 LongPressButton_13 LongPressButton_14 LongPressButton_15 00008000 LongPressButton_16 + {0x046d, 0xc640, O, "NuLOOQ", "", 5, {""}}, // Logitech Amazon First Available December 19, 2005 From https://github.com/microdee/UE4-SpaceMouse/blob/3584fab85147a7806c15040b5625ddb07414bbcb/Source/SpaceMouseReader/Private/SpaceMouseReader.cpp#L26 + {0x256f, 0xc62c, N, "LIPARI", "", 22, {"MENU", "FIT", "T", "L", "R", "F", "B", "BK", "Roll +", "Roll -", "ISO1", "ISO2", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}}, + {0x256f, 0xc62e, N, "SpaceMouse Wireless (cabled)", "3DX-700043", 2, {"MENU", "FIT"}}, // 3DX-700066 3DX-600044? + {0x256f, 0xc62f, N, "SpaceMouse Wireless Receiver", "3DX-700043", 2, {"MENU", "FIT"}}, // 3DX-700066 3DX-600044? + {0x256f, 0xc631, N, "SpaceMouse Pro Wireless (cabled)", "3DX-700075", 27/*15*/, {"MENU", "FIT", "T", "", "R", "F", "", "", "Roll +", "", "", "", "1", "2", "3", "4", "", "", "", "", "", "", "ESC", "ALT", "SHIFT", "CTRL", "Rot"}}, // < ReportID>16 < Mask > 00001000 LongPressButton_13 LongPressButton_14 LongPressButton_15 00008000 LongPressButton_16 3DX-600047 + {0x256f, 0xc632, N, "SpaceMouse Pro Wireless Receiver", "3DX-700075", 27/*15*/, {"MENU", "FIT", "T", "", "R", "F", "", "", "Roll +", "", "", "", "1", "2", "3", "4", "", "", "", "", "", "", "ESC", "ALT", "SHIFT", "CTRL", "Rot"}}, // < ReportID>16 < Mask > 00001000 LongPressButton_13 LongPressButton_14 LongPressButton_15 00008000 LongPressButton_16 3DX-600047 + {0x256f, 0xc633, N, "SpaceMouse Enterprise", "3DX-700056", 31/*31*/, {"MENU", "FIT", "T", "", "R", "F", "", "", "Roll +", "", "ISO1", "", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "ESC", "ALT", "SHIFT", "CTRL", "Rot"}}, // report1C ENTER, DELETE, 11, 12, V1, V2, V3, TAB, SPACE < ReportID>16 < Mask>00001000 LongPressButton_13 LongPressButton_14 LongPressButton_15 00008000 LongPressButton_16 // 3DX-600051 + {0x256f, 0xc635, O, "SpaceMouse Compact", "3DX-700059", 2, {"MENU", "FIT"}}, + {0x256f, 0xc636, O, "SpaceMouse Module", "", 0, {""}}, // ?? + {0x256f, 0xc652, N, "SpaceMouse Universal Receiver", "3DX-700069", 0, {""}}, +}; + +static const unsigned num_known_devs = sizeof(devinfo)/sizeof(devinfo[0]); + +static int usbdev_init(struct spndev* dev, const unsigned type); +static void usbdev_close(struct spndev* dev); +static int usbdev_read_O(struct spndev* dev, union spndev_event* evt); +static int usbdev_read_N(struct spndev* dev, union spndev_event* evt); +static void usbdev_setled(struct spndev* dev, int led); +static int usbdev_getled(struct spndev* dev); +static inline void usbdev_parsebuttons(const struct spndev* dev, union spndev_event* evt, const unsigned char* report); +static inline void checkrange(const struct spndev* dev, const int val); +static void SpacePilotLCDSetBl(struct spndev* dev, int state); +static int SpacePilotLCDGetBl(struct spndev* dev); +static void SpacePilotLCDWrite(struct spndev* dev, int state); + +#ifdef _WIN32 +#define VID_PID_FORMAT_STR "%#0.4hx:%#0.4hx" +#else +#define VID_PID_FORMAT_STR "%#0.4hx:%#0.4hx" +#endif + +int spndev_usb_open(struct spndev *dev, const char *devstr, uint16_t vend, uint16_t prod) +{ + int err = -1; + struct hid_device_info* deviceinfos; + + memset(dev, 0, sizeof(*dev)); + + if (devstr) { + /* Make a list of specific devices */ + deviceinfos = hid_enumerate(vend, prod); + if (!deviceinfos) { + fprintf(stderr, "USB HID device " VID_PID_FORMAT_STR " not found\n", vend, prod); + return -1; + } + } else { + /* Make a list of all HID devices */ + deviceinfos = hid_enumerate(0, 0); + if (!deviceinfos) { + fprintf(stderr, "No USB HID devices found\n"); + return -1; + } + } + + struct hid_device_info* cinfo = deviceinfos; + + unsigned devidx = 0; + unsigned hididx = 0; + char opened = 0; + + /* Try to open the first known device */ + while (cinfo && !opened) + { + char pidmatch = 0; + char vidmatch = 0; + /* Loop through the found HID devices */ + for (devidx = 0; devidx < num_known_devs; ++devidx) + { + /* Searching for a match with one of our known devices */ + if (devinfo[devidx].vid == cinfo->vendor_id) { + vidmatch = 1; + if (devinfo[devidx].pid == cinfo->product_id) + { + pidmatch = 1; + hid_device* hiddev = hid_open_path(cinfo->path); + if (hiddev) { + if (!(dev->path = _strdup(cinfo->path))) { + fprintf(stderr, "spndev_open: Failed to allocate device path\n"); + goto cleanup; + } + if (!(dev->name = (char*)_wcsdup(cinfo->product_string))) { + fprintf(stderr, "spndev_open: Failed to allocate device name\n"); + goto cleanup; + } + if (-1 == hid_set_nonblocking(hiddev, 1)) { + fprintf(stderr, "spndev_open: Failed to set non-blocking HID mode.\n"); + goto cleanup; + + } + dev->internal_id = hididx; + dev->handle = (void*)hiddev; + dev->usb_vendor = cinfo->vendor_id; + dev->usb_product = cinfo->product_id; + opened = 1; + err = 0; // Success + fwprintf(stderr, L"Opened USB device %ws " VID_PID_FORMAT_STR " %hs\n", cinfo->product_string, cinfo->vendor_id, cinfo->product_id, cinfo->path); + break; + } + else { + fwprintf(stderr, L"Could not open USB device %s " VID_PID_FORMAT_STR " %hs\n", cinfo->product_string, cinfo->vendor_id, cinfo->product_id, cinfo->path); + } + } + } + } + + if (vidmatch && !pidmatch) { + fwprintf(stderr, L"Found unsupported USB device %s %s " VID_PID_FORMAT_STR " %hs\n", + cinfo->manufacturer_string, cinfo->product_string, cinfo->vendor_id, cinfo->product_id, cinfo->path); + fprintf(stderr, "Usage Page: %#.2hx, Usage ID: %#.2hx\n", cinfo->usage_page, cinfo->usage); + if (1 == cinfo->usage_page && 8 == cinfo->usage) { + fprintf(stderr, "The device seems to be a \"Multi - axis Controller\". Please report it.\n"); + } + } + + cinfo = cinfo->next; + hididx++; + } + + +cleanup: + hid_free_enumeration(deviceinfos); + + if (-1 != err) { + if (-1 == (err = usbdev_init(dev, devidx))) { + fprintf(stderr, "spndev_open: failed to initialize device structure\n"); + } + } + + if (-1 == err) { + usbdev_close(dev); + } + + return err; +} + +static int usbdev_init(struct spndev* dev, const unsigned type) +{ + int i; + static const char* axnames[] = { "Tx", "Ty", "Tz", "Rx", "Ry", "Rz" }; + + dev->num_axes = 6; + dev->num_buttons = devinfo[type].nbuttons; + + if (!(dev->drvdata = calloc(1, sizeof(tspnav_hid)))) { + fprintf(stderr, "spndev_open: failed to allocate HID buffer\n"); + return -1; + } + + if (!(dev->aprop = malloc(dev->num_axes * sizeof * dev->aprop))) { + return -1; + } + if (!(dev->bn_name = malloc(dev->num_buttons * sizeof * dev->bn_name))) { + return -1; + } + + for (i = 0; i < 6; i++) { + dev->aprop[i].name = axnames[i]; + dev->aprop->maxval = axis_max; + dev->aprop->minval = axis_min; + dev->aprop->deadz = axis_deadz; + } + for (i = 0; i < dev->num_buttons; i++) { + dev->bn_name[i] = devinfo[type].bnames[i]; + } + + dev->close = usbdev_close; + if (O == devinfo[type].posrotreport) { + dev->read = usbdev_read_O; + } else if (N == devinfo[type].posrotreport) { + dev->read = usbdev_read_N; + } + else { + // WTF? + } + dev->getled = usbdev_getled; + dev->setled = usbdev_setled; + if((dev->usb_vendor == devinfo[5].vid) && (dev->usb_product == devinfo[5].pid)) { // ToDo: The constant 4 here is an ugly hack + /* The device is a SpacePilot USB. Connect the LCD support functions. */ + dev->setlcdbl = SpacePilotLCDSetBl; + dev->getlcdbl = SpacePilotLCDGetBl; + dev->writelcd = SpacePilotLCDWrite; + } + return 0; +} + +static void usbdev_close(struct spndev *dev) { + if (dev) { + hid_close((hid_device*)dev->handle); + free(dev->name); + free(dev->path); + free(dev->drvdata); + free(dev->aprop); + free((void *)dev->bn_name); + } + +} + + +// Read and parse "Old" (two reports) style positon and rotation data +static int usbdev_read_O(struct spndev *dev, union spndev_event *evt) { - fprintf(stderr, "spndev_open: USB devices not supported yet\n"); - return -1; + evt->type = SPNDEV_NONE; + unsigned char *buffer = ((tspnav_hid*)dev->drvdata)->buf; + + if (hid_read((hid_device*)dev->handle, buffer, oldposrotreportsize) > 0) { + switch (buffer[0]) { + case 0: + for (size_t i = 0; i < oldposrotreportsize; ++i) { + printf("%x", buffer[i]); + } + break; + + case 1: // Translation + evt->type = SPNDEV_MOTION; + for (int i = 0; i < 3; ++i) { + evt->mot.v[i] = *(int16_t*)(buffer + 1 + 2 * i); + checkrange(dev, evt->mot.v[i]); + } + break; + + case 2: // Rotation + evt->type = SPNDEV_MOTION; + for (int i = 0; i < 3; ++i) { + evt->mot.v[i + 3] = *(int16_t*)(buffer + 1 + 2 * i); + checkrange(dev, evt->mot.v[i + 3]); + } + break; + case 3: // Buttons + usbdev_parsebuttons(dev, evt, buffer); + break; + + default: + for (size_t i = 0; i < newposrotreportsize; ++i) { + printf("%x", buffer[i]); + } + break; + } + } + return evt->type; } + +// Read and parse "New" (single report) style positon and rotation data +static int usbdev_read_N(struct spndev* dev, union spndev_event* evt) +{ + evt->type = SPNDEV_NONE; + unsigned char* buffer = ((tspnav_hid*)dev->drvdata)->buf; + + if (hid_read((hid_device*)dev->handle, buffer, newposrotreportsize) > 0) { + switch (buffer[0]) { + case 0: + for (size_t i = 0; i < newposrotreportsize; ++i) { + printf("%x", buffer[i]); + } + break; + + case 1: // Translation & Rotation + evt->type = SPNDEV_MOTION; + for (int i = 0; i < 6; ++i) { + evt->mot.v[i] = *(int16_t*)(buffer + 1 + 2 * i); + checkrange(dev, evt->mot.v[i]); + } + break; + + case 3: // Buttons + usbdev_parsebuttons(dev, evt, buffer); + break; + + default: + for (size_t i = 0; i < newposrotreportsize; ++i) { + printf("%x", buffer[i]); + } + break; + } + } + return evt->type; +} + +static void usbdev_setled(struct spndev *dev, int led) +{ + const unsigned char led_report[2] = {4, (unsigned char)led}; + hid_write((hid_device*)dev->handle, led_report, sizeof(led_report)); + dev->led = led; +} + +static int usbdev_getled(struct spndev *dev) +{ + return dev->led; +} + +static inline void usbdev_parsebuttons(const struct spndev* dev, union spndev_event* evt, const unsigned char *report) { + unsigned char* btn = ((tspnav_hid*)dev->drvdata)->btn; + int ii = 0; + for (int j = 0; j < 6; j++) + { + for (int k = 0; k < 8; k++) + { + if ((1 << k & (unsigned char)*(report + 1 + j)) > 0) { + if (!btn[ii]) { + evt->type = SPNDEV_BUTTON; + evt->bn.press = 1; + evt->bn.num = ii; + btn[ii] = 1; + } + } + else { + if (btn[ii]) { + evt->type = SPNDEV_BUTTON; + evt->bn.press = 0; + evt->bn.num = ii; + btn[ii] = 0; + } + } + ii++; + } + } +} + +static inline void checkrange(const struct spndev* dev, const int val) { + if (dev->aprop->maxval < val) { + fwprintf(stderr, L"Too high %i\n", val); + } else if (dev->aprop->minval > val) { + fwprintf(stderr, L"Too low %i\n", val); + } +} + +/*****************************************/ +/* SpacePilot SP1 USB LCD screen support */ +/* The function names are intentionally */ +/* in a different style. */ +#define SP_RPT_ID_LCD_POS 0x0c +#define SP_RPT_ID_LCD_DATA 0x0d +#define SP_RPT_ID_LCD_DATA_PACK 0x0e +#define SP_RPT_ID_LCD_BACKLIGHT 0x10 +#define SP_X_RES 240u +#define SP_Y_RES 8u // 64 rows but packed in vertical bytes + +/* + * Sets the position at which subsequent bits will be filled. + * Note that row is one of 8 rows of 8 bit tall columns. + * There is a problem if you try to start after column 120. + */ +static void SpacePilotLCDStartPos(struct spndev* dev, unsigned char column, unsigned char row) { + /* https://forum.3dconnexion.com/viewtopic.php?f=19&t=1095 + Post by jwick » Wed Aug 29, 2007 8 : 14 am + Hello mew, + + There was a small problem in an earlier version of the SP firmware where requests to + start drawing at any column after 120 resulted in starting at the next column to the right. + That was fixed quite a while ago.I wouldn't worry about it. + + Ruevs: + My SpacePilot has this problem. Essentially positioning to colum 120 does not work. + The writing starts on column 121. It is possible to write to column 120 by starting at + e.g. 119 and writing a packet, which can write up to 7 columns properly. + + The bug seems to come from the fact that the LCD seems to be constructed from 4 segments + in a 2*2 grid. + [ ][ ] + [ ][ ] + Column 120 is the first column of the second segments of the LCD and perhaps there is an + off by one error in the firmware code that handles "LCD_POS". + + If the problem is to be avoided the driver needs to know the firmware version - I do not + know how to read it. In addition it needs to know the version before which the problem + exists and apply a workaround conditionally. + + Perhaps I will install an older version of the official driver and sniff the USB bus to + see if and how it reads the firmware version. + */ + unsigned char lcd_pos_report[4]; + + // Set start position + lcd_pos_report[0] = SP_RPT_ID_LCD_POS; + lcd_pos_report[1] = row; // 0-7 + lcd_pos_report[2] = column; // 0-239 + lcd_pos_report[3] = 0; + + // Send the buffer to the device + hid_send_feature_report((hid_device*)dev->handle, lcd_pos_report, 3/*sizeof(lcd_pos_report)*/); +} + +/* + * Write packed data to the screen. This is faster than the "normal" writing below. + */ +static void SpacePilotLCDWritePacked(struct spndev* dev, + unsigned char count1, unsigned char pattern1, + unsigned char count2, unsigned char pattern2, + unsigned char count3, unsigned char pattern3) { + /* + The packed structure is a count, value structure. You can have up to 3 values that can be + repeated up to 255 times. Of course, there are only 240 columns per row. This can fill patterns + or empty space quickly. We make extensive use of this packet, testing all data to see if it is + more efficient to send it packed before sending it unpacked. This is the relevant data + structure. + typedef struct PackedDataState + { + int count[3]; // Count of each pattern in the data array + unsigned char bits[3]; // Up to 3 patterns of data bytes + } PackedDataState; + + ruevs: The structure is not quite right. The counts and patterns are interleaved. See the code. + */ + unsigned char buffer[7]; + + buffer[0] = SP_RPT_ID_LCD_DATA_PACK; + buffer[1] = count1; + buffer[2] = pattern1; + buffer[3] = count2; + buffer[4] = pattern2; + buffer[5] = count3; + buffer[6] = pattern3; + hid_send_feature_report((hid_device*)dev->handle, buffer, 7); +} + +/* + * Fill/clear screen quickly using packed data. + * pattern could be different from 0x00 or 0xFF +*/ +static void SpacePilotLCDFill(struct spndev* dev, unsigned char pattern) { + unsigned char row; + for(row = 0; row < 8; ++row) { + SpacePilotLCDStartPos(dev, 0, row); + SpacePilotLCDWritePacked(dev, SP_X_RES, pattern, 0, 0, 0, 0); + } +} + +/* + * ToDo: + * This will be the function for writing to the LCD. + * Right now it is just test code to excercise the low levels. + * The signature will change. + */ +static void SpacePilotLCDWrite(struct spndev* dev, int state) { + unsigned char col, row; + unsigned char buffer[8]; + + SpacePilotLCDFill(dev, state); + + for(row = 0; row < 8; ++row) { + for(col = 1; col < 240; col += 7) { + unsigned char c; + SpacePilotLCDStartPos(dev, col, row); + + buffer[0] = SP_RPT_ID_LCD_DATA; + if(dev->led) { + buffer[1] = 1; + for(c = 1; c < 7; ++c) { + buffer[c + 1] = buffer[c] | (1 << (c)); + } + } else { + for(c = 0; c < 7; ++c) { + buffer[c + 1] = col + c; + } + } + hid_send_feature_report((hid_device*)dev->handle, buffer, 8); + } + } + + /* Test for the column 120 writing problem described in SpacePilotLCDStartPos */ + buffer[0] = SP_RPT_ID_LCD_DATA; + buffer[1] = 0x80; + for(int c = 1; c < 7; ++c) { + buffer[c + 1] = buffer[c] | (0x80 >> (c)); + } + + SpacePilotLCDStartPos(dev, 119, 0); + hid_send_feature_report((hid_device*)dev->handle, buffer, 8); + + SpacePilotLCDStartPos(dev, 120, 0); + hid_send_feature_report((hid_device*)dev->handle, buffer, 8); + + SpacePilotLCDStartPos(dev, 121, 0); + hid_send_feature_report((hid_device*)dev->handle, buffer, 8); +} + +/* + * Turn the backlight on or off. + */ +static void SpacePilotLCDSetBl(struct spndev* dev, int state) { + /* The LCD backlight packet is : 0x10, 0x02 + buffer[0] = 0x10; + buffer[1] = 0x00; // On + buffer[1] = 0x02; // Off + */ + unsigned char buffer[2]; + buffer[0] = SP_RPT_ID_LCD_BACKLIGHT; + buffer[1] = (state) ? 0x00 : 0x02; + hid_send_feature_report((hid_device*)dev->handle, buffer, 2); + + dev->lcdbl = state; +} + +/* + * Get the backlight state. + */ +static int SpacePilotLCDGetBl(struct spndev* dev) { + return dev->lcdbl; +} + +/* SpacePilot SP1 USB LCD screen support */ +/*****************************************/ + +#else + +int spndev_usb_open(struct spndev *dev, const char *devstr, uint16_t vend, uint16_t prod) { + fprintf(stderr, "HIDAPI support not compiled in.\n"); + return -1; +} + +#endif