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