From 2a16f39a864af4a1d0d863ba91dc5b870b49c053 Mon Sep 17 00:00:00 2001 From: ruevs Date: Sun, 14 Feb 2021 06:18:56 +0200 Subject: [PATCH 01/20] Win32: change serdev.c enough to compile on Windows. Fix warnings by using ANSI C functions instead of obsolete POSIX ones. --- src/serdev.c | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/serdev.c b/src/serdev.c index 1b07c80..b4b0c33 100644 --- a/src/serdev.c +++ b/src/serdev.c @@ -1,4 +1,8 @@ #include +#include +#include +#include +#include #include "dev.h" #if defined(__i386__) || defined(__ia64__) || defined(WIN32) || \ @@ -30,7 +34,9 @@ struct sball { unsigned int keystate, keymask; +#ifndef _WIN32 struct termios saved_term; +#endif int saved_mstat; int (*parse)(struct sball*, int, char*, int); @@ -90,13 +96,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((fd = _open(devstr, _O_RDWR /* | O_NOCTTY | O_NONBLOCK */)) == -1) { + fprintf(stderr, "spndev_open: failed to open device: %s: %s\n", dev->path, strerror(errno)); return -1; } dev->fd = fd; - dev->path = strdup(devstr); - dev->usb_vendor = dev->usb_product = -1; + dev->path = _strdup(devstr); + dev->usb_vendor = dev->usb_product = 0xFFFF; dev->handle = 0; if(!(sb = calloc(1, sizeof *sb))) { @@ -110,7 +116,7 @@ int spndev_ser_open(struct spndev *dev, const char *devstr) if(stty_sball(fd, sb) == -1) { goto err; } - write(fd, "\r@RESET\r", 8); + _write(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 */ @@ -126,17 +132,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); + _write(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); + _write(fd, "vQ\r", 3); if((sz = read_timeout(fd, buf, sizeof buf - 1, 250000)) > 0 && buf[0] == 'v') { make_printable(buf, sz); @@ -148,15 +154,15 @@ 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); + _write(fd, "m3\r", 3); sb->parse = mag_parsepkt; - return dev; + return 0; } err: stty_restore(fd, sb); - close(fd); + _close(fd); free(sb); return -1; } @@ -167,7 +173,7 @@ static int init_dev(struct spndev *dev, int type) struct sball *sb = dev->drvdata; static const char *axnames[] = {"Tx", "Ty", "Tz", "Rx", "Ry", "Rz"}; - if(!(dev->name = strdup(devinfo[type].name))) { + if(!(dev->name = _strdup(devinfo[type].name))) { return -1; } dev->num_axes = 6; @@ -191,6 +197,7 @@ static int init_dev(struct spndev *dev, int type) } /* TODO setup callbacks */ + return 0; } static int guess_device(const char *verstr) @@ -249,3 +256,14 @@ 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 mag_parsepkt(struct sball* sb, int id, char* data, int len) { return 0; } +static int sball_parsepkt(struct sball* sb, int id, char* data, int len) { return 0; } + +static void make_printable(char* buf, int len) {} +static int read_timeout(int fd, char* buf, int bufsz, long tm_usec) { return 0; } From bac8c1a250220762edf84b0ced69b292010b262d Mon Sep 17 00:00:00 2001 From: ruevs Date: Sun, 14 Feb 2021 06:23:37 +0200 Subject: [PATCH 02/20] Win32: Add working Windows example. Works with SpaceExplorer and should mostly work with all USB devices if the usbdev.c implementation is merged. --- examples/win32/test.c | 57 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 examples/win32/test.c diff --git a/examples/win32/test.c b/examples/win32/test.c new file mode 100644 index 0000000..59a44f0 --- /dev/null +++ b/examples/win32/test.c @@ -0,0 +1,57 @@ +#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; + + 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(1 > 0) { + if(1) { + 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"); + break; + + default: + break; + } + } + } + } + + } + + spndev_close(dev); + return 0; +} From bc8b75d34747d43ef3a91432bbab292b39721617 Mon Sep 17 00:00:00 2001 From: ruevs Date: Sun, 14 Feb 2021 05:58:51 +0200 Subject: [PATCH 03/20] Implement support for 3Dconnexion USB 6DOF controllers. SpaceMouse etc. Using `hidapi` as a HID access library. Tested on Windows with SpaceExplorer. Should work on Linux. Needs hid.c and hidapi.h from here: https://github.com/libusb/hidapi --- src/dev.h | 9 +- src/spnavdev.c | 10 +- src/usbdev.c | 345 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 352 insertions(+), 12 deletions(-) diff --git a/src/dev.h b/src/dev.h index 9730471..86bc304 100644 --- a/src/dev.h +++ b/src/dev.h @@ -21,20 +21,21 @@ 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; + unsigned short 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; void *uptr, *drvdata; @@ -47,7 +48,7 @@ struct spndev { }; -int spndev_usb_open(struct spndev *dev, const char *devstr, int vend, int prod); +int spndev_usb_open(struct spndev *dev, const char *devstr, unsigned short vend, unsigned short prod); int spndev_ser_open(struct spndev *dev, const char *devstr); diff --git a/src/spnavdev.c b/src/spnavdev.c index d4fe632..d042c3b 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; + unsigned short 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; @@ -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) diff --git a/src/usbdev.c b/src/usbdev.c index 9dbbf49..4c19446 100644 --- a/src/usbdev.c +++ b/src/usbdev.c @@ -1,8 +1,347 @@ +/* +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 . +*/ + #include +#include +#include +#include +#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" , "", 9, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "*"}}, // Buttons order? + {0x046d, 0xc605, O, "CADman", "", 15, {""}}, + {0x046d, 0xc606, O, "SpaceMouse Classic USB", "", 15, {""}}, + {0x046d, 0xc621, O, "Spaceball 5000 USB", "5000 USB", 12, {""}}, + {0x046d, 0xc623, O, "SpaceTraveler", "", 8, {""}}, + {0x046d, 0xc625, O, "SpacePilot", "SP1 USB", 21, {""}}, // IBM "(P) P/N: 60K9206", "(P) FRU P/N: 4K9204" + {0x046d, 0xc626, O, "SpaceNavigator", "3DX-700028", 2, {"MENU", "FIT"} }, // also "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 + {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", "", 15, {""}}, // 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 +", "RollMinus", "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", 27/*WTF*/, {"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-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); + +int spndev_usb_open(struct spndev *dev, const char *devstr, unsigned short vend, unsigned short prod) +{ + int err = -1; + struct hid_device_info* deviceinfos; + + dev->fd = 0; + + if (devstr) { + /* Make a list of specific devices */ + deviceinfos = hid_enumerate(vend, prod); + if (!deviceinfos) { + fprintf(stderr, "USB HID device %x:%x 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 allocade 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 %x:%x %hs\n", cinfo->product_string, cinfo->vendor_id, cinfo->product_id, cinfo->path); + break; + } + else { + fwprintf(stderr, L"Could not upen USB device %s %x:%x %hs\n", cinfo->product_string, cinfo->vendor_id, cinfo->product_id, cinfo->path); + } + } + } + } + if (vidmatch && !pidmatch) { + fwprintf(stderr, L"Found unsupported 3Dconnexion USB device %s %x:%x %hs\n", cinfo->product_string, cinfo->vendor_id, cinfo->product_id, cinfo->path); + } + 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; + 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); + free(dev); + } + +} + + +// Read and parse "Old" (two reports) style positon and rotation data +static int usbdev_read_O(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, 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); + } + } + 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); + } + } + return evt->type; +} + +static void usbdev_setled(struct spndev *dev, int led) { - fprintf(stderr, "spndev_open: USB devices not supported yet\n"); - return -1; +} + +static int usbdev_getled(struct spndev *dev) +{ + return 0; +} + +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); + } } From 651d7ac1e6a56c73c5f20ccc0f9639ea58c5405a Mon Sep 17 00:00:00 2001 From: ruevs Date: Mon, 15 Feb 2021 22:25:54 +0200 Subject: [PATCH 04/20] Fix crash on closing a USB device Format VID and PID properly in debug messages. Print more information on unknown 3Dconnexion devices. Dump unknown HID reports. --- src/usbdev.c | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/usbdev.c b/src/usbdev.c index 4c19446..fec9ae7 100644 --- a/src/usbdev.c +++ b/src/usbdev.c @@ -58,13 +58,13 @@ devinfo[] = { {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", "", 15, {""}}, // From https://github.com/microdee/UE4-SpaceMouse/blob/3584fab85147a7806c15040b5625ddb07414bbcb/Source/SpaceMouseReader/Private/SpaceMouseReader.cpp#L26 + {0x046d, 0xc640, O, "NuLOOQ", "", 5, {""}}, // Logitech 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 +", "RollMinus", "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", 27/*WTF*/, {"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-600051 + {0x256f, 0xc633, N, "SpaceMouse Enterprise", "3DX-700056", 31/*WTF*/, {"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-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, {""}}, @@ -86,13 +86,13 @@ int spndev_usb_open(struct spndev *dev, const char *devstr, unsigned short vend, int err = -1; struct hid_device_info* deviceinfos; - dev->fd = 0; + 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 %x:%x not found\n", vend, prod); + fprintf(stderr, "USB HID device %#0.4hx:%#0.4hx not found\n", vend, prod); return -1; } } else { @@ -145,18 +145,25 @@ int spndev_usb_open(struct spndev *dev, const char *devstr, unsigned short vend, dev->usb_product = cinfo->product_id; opened = 1; err = 0; // Success - fwprintf(stderr, L"Opened USB device %ws %x:%x %hs\n", cinfo->product_string, cinfo->vendor_id, cinfo->product_id, cinfo->path); + fwprintf(stderr, L"Opened USB device %ws %#0.4hx:%#0.4hx %hs\n", cinfo->product_string, cinfo->vendor_id, cinfo->product_id, cinfo->path); break; } else { - fwprintf(stderr, L"Could not upen USB device %s %x:%x %hs\n", cinfo->product_string, cinfo->vendor_id, cinfo->product_id, cinfo->path); + fwprintf(stderr, L"Could not upen USB device %s %#0.4hx:%#0.4hx %hs\n", cinfo->product_string, cinfo->vendor_id, cinfo->product_id, cinfo->path); } } } } + if (vidmatch && !pidmatch) { - fwprintf(stderr, L"Found unsupported 3Dconnexion USB device %s %x:%x %hs\n", cinfo->product_string, cinfo->vendor_id, cinfo->product_id, cinfo->path); + fwprintf(stderr, L"Found unsupported USB device %s %s %#0.4hx:%#0.4hx %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++; } @@ -230,7 +237,6 @@ static void usbdev_close(struct spndev *dev) { free(dev->drvdata); free(dev->aprop); free((void *)dev->bn_name); - free(dev); } } @@ -267,6 +273,13 @@ static int usbdev_read_O(struct spndev *dev, union spndev_event *evt) 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; @@ -296,6 +309,13 @@ static int usbdev_read_N(struct spndev* dev, union spndev_event* evt) 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; From c4882067c875448977ad1d563de4d740c75f2816 Mon Sep 17 00:00:00 2001 From: ruevs Date: Tue, 16 Feb 2021 00:53:03 +0200 Subject: [PATCH 05/20] Iplement LED control functions. Update button names. --- examples/win32/test.c | 7 +++++++ src/usbdev.c | 19 +++++++++++-------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/examples/win32/test.c b/examples/win32/test.c index 59a44f0..55ca646 100644 --- a/examples/win32/test.c +++ b/examples/win32/test.c @@ -13,6 +13,7 @@ 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] : ""); @@ -41,6 +42,12 @@ int main(int argc, char **argv) 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/src/usbdev.c b/src/usbdev.c index fec9ae7..1ad6d8e 100644 --- a/src/usbdev.c +++ b/src/usbdev.c @@ -56,15 +56,15 @@ devinfo[] = { {0x046d, 0xc626, O, "SpaceNavigator", "3DX-700028", 2, {"MENU", "FIT"} }, // also "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 {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 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 +", "RollMinus", "ISO1", "ISO2", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}}, + {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/*WTF*/, {"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-600051 + {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, {""}}, @@ -323,11 +323,14 @@ static int usbdev_read_N(struct spndev* dev, union spndev_event* evt) static void usbdev_setled(struct spndev *dev, int led) { + const unsigned char led_report[2] = { 4, led }; + hid_write((hid_device*)dev->handle, led_report, sizeof(led_report)); + dev->led = led; } static int usbdev_getled(struct spndev *dev) { - return 0; + return dev->led; } static inline void usbdev_parsebuttons(const struct spndev* dev, union spndev_event* evt, const unsigned char *report) { From 508033fcd1717dbd2dd4c7219c30f4d91a7d8186 Mon Sep 17 00:00:00 2001 From: ruevs Date: Tue, 16 Feb 2021 03:39:18 +0200 Subject: [PATCH 06/20] Add button names for SpacePilot and older devices. The SpacePilot should be correct taken from Base.xml of the official driver (v. 10.4.10). The others just seem right from pictures and user manuals. --- src/usbdev.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/usbdev.c b/src/usbdev.c index 1ad6d8e..c09bc8f 100644 --- a/src/usbdev.c +++ b/src/usbdev.c @@ -47,12 +47,12 @@ static const struct { const char* const bnames[32]; } devinfo[] = { - {0x046d, 0xc603, O, "SpaceMouse Plus XT USB" , "", 9, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "*"}}, // Buttons order? - {0x046d, 0xc605, O, "CADman", "", 15, {""}}, - {0x046d, 0xc606, O, "SpaceMouse Classic USB", "", 15, {""}}, - {0x046d, 0xc621, O, "Spaceball 5000 USB", "5000 USB", 12, {""}}, - {0x046d, 0xc623, O, "SpaceTraveler", "", 8, {""}}, - {0x046d, 0xc625, O, "SpacePilot", "SP1 USB", 21, {""}}, // IBM "(P) P/N: 60K9206", "(P) FRU P/N: 4K9204" + {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"}}, // Buttons order? + {0x046d, 0xc623, O, "SpaceTraveler", "", 8, {"1", "2", "3", "4", "5", "6", "7", "8"}}, + {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" {0x046d, 0xc626, O, "SpaceNavigator", "3DX-700028", 2, {"MENU", "FIT"} }, // also "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 {0x046d, 0xc628, O, "SpaceNavigator for Notebooks", "3DX-700034", 2, {"MENU", "FIT"}}, From 879711dd19cca2d014a8e50ddd776a735e679329 Mon Sep 17 00:00:00 2001 From: ruevs Date: Tue, 16 Feb 2021 00:53:03 +0200 Subject: [PATCH 07/20] Implement LED control functions. Update button names. --- examples/unix/test.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/unix/test.c b/examples/unix/test.c index 2ec57ac..8273c90 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); @@ -58,6 +59,12 @@ int main(int argc, char **argv) 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: From c791c3ccdc75f32fcd1901307b67a6448ea6d8b0 Mon Sep 17 00:00:00 2001 From: Ryan Pavlik Date: Tue, 23 Mar 2021 14:02:40 -0500 Subject: [PATCH 08/20] Gitignore. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) 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/ From 841d46566dd423e441ca671dfcf18ab4c977c753 Mon Sep 17 00:00:00 2001 From: Ryan Pavlik Date: Tue, 23 Mar 2021 14:02:57 -0500 Subject: [PATCH 09/20] Fix endianness on Win32 --- src/serdev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serdev.c b/src/serdev.c index b4b0c33..7b63afd 100644 --- a/src/serdev.c +++ b/src/serdev.c @@ -5,7 +5,7 @@ #include #include "dev.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__)) || \ From 42c6425d4f49b94a36d1730b38a238f27db28139 Mon Sep 17 00:00:00 2001 From: Ryan Pavlik Date: Tue, 23 Mar 2021 14:03:12 -0500 Subject: [PATCH 10/20] Fix format crash on Win32 --- src/usbdev.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/usbdev.c b/src/usbdev.c index c09bc8f..5ee4e58 100644 --- a/src/usbdev.c +++ b/src/usbdev.c @@ -81,6 +81,12 @@ 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); +#ifdef _WIN32 +#define VID_PID_FORMAT_STR "%04x:%04x" +#else +#define VID_PID_FORMAT_STR "%#0.4hx:%#0.4hx" +#endif + int spndev_usb_open(struct spndev *dev, const char *devstr, unsigned short vend, unsigned short prod) { int err = -1; @@ -92,7 +98,7 @@ int spndev_usb_open(struct spndev *dev, const char *devstr, unsigned short vend, /* Make a list of specific devices */ deviceinfos = hid_enumerate(vend, prod); if (!deviceinfos) { - fprintf(stderr, "USB HID device %#0.4hx:%#0.4hx not found\n", vend, prod); + fprintf(stderr, "USB HID device " VID_PID_FORMAT_STR " not found\n", vend, prod); return -1; } } else { @@ -145,18 +151,18 @@ int spndev_usb_open(struct spndev *dev, const char *devstr, unsigned short vend, dev->usb_product = cinfo->product_id; opened = 1; err = 0; // Success - fwprintf(stderr, L"Opened USB device %ws %#0.4hx:%#0.4hx %hs\n", cinfo->product_string, cinfo->vendor_id, cinfo->product_id, cinfo->path); + 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 upen USB device %s %#0.4hx:%#0.4hx %hs\n", cinfo->product_string, cinfo->vendor_id, cinfo->product_id, cinfo->path); + 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 %#0.4hx:%#0.4hx %hs\n", + 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) { From dec3afaf9a829f968dac439a7c1a0ccfeb8c3d97 Mon Sep 17 00:00:00 2001 From: Ryan Pavlik Date: Tue, 23 Mar 2021 14:09:44 -0500 Subject: [PATCH 11/20] Fix message typos --- src/usbdev.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/usbdev.c b/src/usbdev.c index 5ee4e58..6799519 100644 --- a/src/usbdev.c +++ b/src/usbdev.c @@ -133,7 +133,7 @@ int spndev_usb_open(struct spndev *dev, const char *devstr, unsigned short vend, hid_device* hiddev = hid_open_path(cinfo->path); if (hiddev) { if (!(dev->path = _strdup(cinfo->path))) { - fprintf(stderr, "spndev_open: Failed to allocade device path\n"); + fprintf(stderr, "spndev_open: Failed to allocate device path\n"); goto cleanup; } if (!(dev->name = (char*)_wcsdup(cinfo->product_string))) { @@ -200,7 +200,7 @@ static int usbdev_init(struct spndev* dev, const unsigned type) dev->num_buttons = devinfo[type].nbuttons; if (!(dev->drvdata = calloc(1, sizeof(tspnav_hid)))) { - fprintf(stderr, "spndev_open: failed to allocate HId buffer\n"); + fprintf(stderr, "spndev_open: failed to allocate HID buffer\n"); return -1; } From d135289dcaeeb59e58d9f21be20a5f84a9cc6ffb Mon Sep 17 00:00:00 2001 From: Ryan Pavlik Date: Tue, 23 Mar 2021 14:04:24 -0500 Subject: [PATCH 12/20] Add simple modern CMake build system. Builds with MSVS 2019 --- CMakeLists.txt | 41 +++++++++++++++++++++++++++++++++++ examples/unix/CMakeLists.txt | 20 +++++++++++++++++ examples/win32/CMakeLists.txt | 20 +++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 examples/unix/CMakeLists.txt create mode 100644 examples/win32/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3339fbf --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,41 @@ +# 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 REQUIRED) + +add_library(spnavdev + src/serdev.c + src/spnavdev.c + src/usbdev.c +) + +target_link_libraries(spnavdev PRIVATE ${HIDAPI_LIBRARY}) +target_include_directories(spnavdev PRIVATE ${HIDAPI_INCLUDE_DIR}) +target_include_directories(spnavdev PUBLIC $) + +if(WIN32) + add_subdirectory(examples/win32) +else() + add_subdirectory(examples/unix) +endif() 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/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 From ae97951b9eb6801034d238ea8249e418e37b0c55 Mon Sep 17 00:00:00 2001 From: Ryan Pavlik Date: Tue, 23 Mar 2021 14:16:41 -0500 Subject: [PATCH 13/20] Handle HIDAPI-less builds. --- CMakeLists.txt | 14 +++++++++++--- config.h.in | 21 +++++++++++++++++++++ src/usbdev.c | 18 ++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 config.h.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 3339fbf..2a6d553 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,8 @@ project( VERSION 0.1 LANGUAGES C) -find_package(HIDAPI REQUIRED) +find_package(HIDAPI) + add_library(spnavdev src/serdev.c @@ -30,10 +31,17 @@ add_library(spnavdev src/usbdev.c ) -target_link_libraries(spnavdev PRIVATE ${HIDAPI_LIBRARY}) -target_include_directories(spnavdev PRIVATE ${HIDAPI_INCLUDE_DIR}) +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(WIN32) add_subdirectory(examples/win32) else() 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/src/usbdev.c b/src/usbdev.c index 6799519..90daf5b 100644 --- a/src/usbdev.c +++ b/src/usbdev.c @@ -16,10 +16,19 @@ 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" @@ -374,3 +383,12 @@ static inline void checkrange(const struct spndev* dev, const int val) { fwprintf(stderr, L"Too low %i\n", val); } } + +#else + +int spndev_usb_open(struct spndev *dev, const char *devstr, unsigned short vend, unsigned short prod) { + fprintf(stderr, "HIDAPI support not compiled in.\n"); + return -1; +} + +#endif From 8e9fda6e000e075e48ba5d34b18f132580512ee2 Mon Sep 17 00:00:00 2001 From: Ryan Pavlik Date: Tue, 23 Mar 2021 15:37:39 -0500 Subject: [PATCH 14/20] Wrap header in extern "C" --- src/spnavdev.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/spnavdev.h b/src/spnavdev.h index bbd4d59..4fee030 100644 --- a/src/spnavdev.h +++ b/src/spnavdev.h @@ -18,6 +18,10 @@ along with this program. If not, see . #ifndef SPNAVDEV_H_ #define SPNAVDEV_H_ +#ifdef __cplusplus +extern "C" { +#endif + struct spndev; enum { @@ -83,5 +87,8 @@ 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); +#ifdef __cplusplus +} /* extern "C" */ +#endif #endif /* SPNAVDEV_H_ */ From 69f892568b7b69120d52a0a4c49faa02d4ddd830 Mon Sep 17 00:00:00 2001 From: Ryan Pavlik Date: Tue, 23 Mar 2021 16:30:24 -0500 Subject: [PATCH 15/20] Handle being a subproject --- CMakeLists.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a6d553..b2c0430 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,8 +42,11 @@ 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(WIN32) - add_subdirectory(examples/win32) -else() - add_subdirectory(examples/unix) +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() From 5f40e08ff01f56c7e604e5a90c196822c8132211 Mon Sep 17 00:00:00 2001 From: ruevs Date: Fri, 26 Mar 2021 14:26:37 +0200 Subject: [PATCH 16/20] Use uint16_t for USB vendor and device IDs. --- src/dev.h | 4 ++-- src/spnavdev.c | 6 +++--- src/spnavdev.h | 4 +++- src/usbdev.c | 6 +++--- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/dev.h b/src/dev.h index 86bc304..9f3c3c1 100644 --- a/src/dev.h +++ b/src/dev.h @@ -27,7 +27,7 @@ struct axisprop { struct spndev { char *name, *path; - unsigned short usb_vendor, usb_product; + uint16_t usb_vendor, usb_product; unsigned internal_id; int fd; /* UNIX file descriptor */ @@ -48,7 +48,7 @@ struct spndev { }; -int spndev_usb_open(struct spndev *dev, const char *devstr, unsigned short vend, unsigned short 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/spnavdev.c b/src/spnavdev.c index d042c3b..524a176 100644 --- a/src/spnavdev.c +++ b/src/spnavdev.c @@ -23,7 +23,7 @@ along with this program. If not, see . struct spndev *spndev_open(const char *devstr) { struct spndev *dev; - unsigned short vendor = 0xffff, product = 0xffff; + uint16_t vendor = 0xffff, product = 0xffff; if(!(dev = malloc(sizeof *dev))) { perror("spndev_open: failed to allocate device structure"); @@ -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; diff --git a/src/spnavdev.h b/src/spnavdev.h index 4fee030..5880e2a 100644 --- a/src/spnavdev.h +++ b/src/spnavdev.h @@ -18,6 +18,8 @@ along with this program. If not, see . #ifndef SPNAVDEV_H_ #define SPNAVDEV_H_ +#include + #ifdef __cplusplus extern "C" { #endif @@ -61,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); diff --git a/src/usbdev.c b/src/usbdev.c index 90daf5b..8f2b40f 100644 --- a/src/usbdev.c +++ b/src/usbdev.c @@ -62,7 +62,7 @@ devinfo[] = { {0x046d, 0xc621, O, "Spaceball 5000 USB", "5000 USB", 12, {"1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C"}}, // Buttons order? {0x046d, 0xc623, O, "SpaceTraveler", "", 8, {"1", "2", "3", "4", "5", "6", "7", "8"}}, {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" - {0x046d, 0xc626, O, "SpaceNavigator", "3DX-700028", 2, {"MENU", "FIT"} }, // also "3DX-600028" "3DX-600029"? "SpaceNavigator USB" on the label + {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 {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", "+", "-"}}, @@ -96,7 +96,7 @@ static inline void checkrange(const struct spndev* dev, const int val); #define VID_PID_FORMAT_STR "%#0.4hx:%#0.4hx" #endif -int spndev_usb_open(struct spndev *dev, const char *devstr, unsigned short vend, unsigned short prod) +int spndev_usb_open(struct spndev *dev, const char *devstr, uint16_t vend, uint16_t prod) { int err = -1; struct hid_device_info* deviceinfos; @@ -386,7 +386,7 @@ static inline void checkrange(const struct spndev* dev, const int val) { #else -int spndev_usb_open(struct spndev *dev, const char *devstr, unsigned short vend, unsigned short prod) { +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; } From 9c377ac03e3d8fc8b9a993ec97089a0745d16e3e Mon Sep 17 00:00:00 2001 From: ruevs Date: Sat, 27 Mar 2021 01:30:41 +0200 Subject: [PATCH 17/20] Serial (RS-232) device support - first rough implementation Added RS-232 serial port HAL (Hardware Abstraction Layer) for Win32 and POSIX (Linux, UNIX). Implemented rough first serial device support by heavily borrowing code from spacenavd. Tested working on Win32 with a SpaceBall 3003 FLX (apart from a timing problem when opening). The POSIX HAL tested on Windows but is far from ready. --- src/serdev.c | 314 ++++++++++++++++++++++++++++++++++++++++++---- src/serio.h | 35 ++++++ src/serio_posix.c | 48 +++++++ src/serio_win32.c | 119 ++++++++++++++++++ src/usbdev.c | 4 +- 5 files changed, 497 insertions(+), 23 deletions(-) create mode 100644 src/serio.h create mode 100644 src/serio_posix.c create mode 100644 src/serio_win32.c diff --git a/src/serdev.c b/src/serdev.c index 7b63afd..3749c8a 100644 --- a/src/serdev.c +++ b/src/serdev.c @@ -1,9 +1,16 @@ + +#if defined(_WIN32) +#define _CRT_NONSTDC_NO_WARNINGS /* Avoid error with strdup on Win32 */ +#endif #include #include #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) || \ (defined(__alpha__) || defined(__alpha)) || \ @@ -39,7 +46,7 @@ struct sball { #endif int saved_mstat; - int (*parse)(struct sball*, int, char*, int); + int (*parse)(struct spndev*, union spndev_event*, int, char*, int); }; enum { @@ -71,6 +78,8 @@ static struct { {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); @@ -78,10 +87,10 @@ 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); @@ -89,19 +98,18 @@ static void make_printable(char *buf, int len); static int read_timeout(int fd, char *buf, int bufsz, long tm_usec); - int spndev_ser_open(struct spndev *dev, const char *devstr) { int fd, sz; 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->path, 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->path = strdup(devstr); dev->usb_vendor = dev->usb_product = 0xFFFF; dev->handle = 0; @@ -110,13 +118,15 @@ 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; 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 */ @@ -132,7 +142,7 @@ 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 0; @@ -142,7 +152,7 @@ int spndev_ser_open(struct spndev *dev, const char *devstr) 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); @@ -154,26 +164,59 @@ 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); sb->parse = mag_parsepkt; 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) { + 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; struct sball *sb = dev->drvdata; static const char *axnames[] = {"Tx", "Ty", "Tz", "Rx", "Ry", "Rz"}; - if(!(dev->name = _strdup(devinfo[type].name))) { + if(!(dev->name = strdup(devinfo[type].name))) { return -1; } dev->num_axes = 6; @@ -262,8 +305,237 @@ 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 mag_parsepkt(struct sball* sb, int id, char* data, int len) { return 0; } -static int sball_parsepkt(struct sball* sb, int id, char* data, int len) { return 0; } +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); + 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; + + /*printf("magellan packet: %c - %s (%d bytes)\n", (char)id, data, len);*/ + + switch(id) { + case 'd': + if(len != 24) { + fprintf(stderr, "magellan: invalid data packet, expected 24 bytes, got: %d\n", len); + return -1; + } + evt->type = SPNDEV_MOTION; + for(i=0; i<6; i++) { + prev = evt->mot.v[i]; + 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; + + 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); + } + 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) { +// PAR@@@ gen_button_events(sb, prev_key); + } + 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<6; 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) { +// PAR@@@ gen_button_events(sb, prev_key); + evt->type = SPNDEV_BUTTON; + } + 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 */ + sb->keymask = 0xfff; + } + /* 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) { +// PAR@@@ gen_button_events(sb, prev_key); + } + break; + + case 'E': + fprintf(stderr, "sball: error:"); + for(i=0; i + +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..19cdb9d --- /dev/null +++ b/src/serio_posix.c @@ -0,0 +1,48 @@ +/* +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 + +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..505be53 --- /dev/null +++ b/src/serio_win32.c @@ -0,0 +1,119 @@ +/* +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://docs.microsoft.com/en-us/windows-hardware/drivers/serports/setting-read-and-write-timeouts-for-a-serial-device + CommTimeouts.ReadIntervalTimeout = 2 * 1000 * 8 / 9600; // 9600kbps * 2 for margin + CommTimeouts.ReadTotalTimeoutMultiplier = 1; + CommTimeouts.ReadTotalTimeoutConstant = 1; + CommTimeouts.WriteTotalTimeoutMultiplier = 2 * 1000 * 8 / 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/usbdev.c b/src/usbdev.c index 8f2b40f..9fa1863 100644 --- a/src/usbdev.c +++ b/src/usbdev.c @@ -91,7 +91,7 @@ static inline void usbdev_parsebuttons(const struct spndev* dev, union spndev_ev static inline void checkrange(const struct spndev* dev, const int val); #ifdef _WIN32 -#define VID_PID_FORMAT_STR "%04x:%04x" +#define VID_PID_FORMAT_STR "%#0.4hx:%#0.4hx" #else #define VID_PID_FORMAT_STR "%#0.4hx:%#0.4hx" #endif @@ -338,7 +338,7 @@ static int usbdev_read_N(struct spndev* dev, union spndev_event* evt) static void usbdev_setled(struct spndev *dev, int led) { - const unsigned char led_report[2] = { 4, 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; } From 2c43a980e146857ef97b9cfc68cd496dd1d6289f Mon Sep 17 00:00:00 2001 From: ruevs Date: Mon, 3 Apr 2023 22:02:53 +0300 Subject: [PATCH 18/20] Small clean up of the examples Currently tested USB devices: "SpaceTraveler" "SpacePilot" "SpaceExplorer" - 6DOF hat working. - All button press and release events detected. - Buttons correctly identified. - LED On/Off working. --- examples/unix/test.c | 2 +- examples/win32/test.c | 48 ++++++++++++++++++++----------------------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/examples/unix/test.c b/examples/unix/test.c index 8273c90..b2dd38d 100644 --- a/examples/unix/test.c +++ b/examples/unix/test.c @@ -54,7 +54,7 @@ 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); } diff --git a/examples/win32/test.c b/examples/win32/test.c index 55ca646..57cc8ca 100644 --- a/examples/win32/test.c +++ b/examples/win32/test.c @@ -25,35 +25,31 @@ int main(int argc, char **argv) while(!quit) { - if(1 > 0) { - if(1) { - 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 (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); - led = !led; - } + if (ev.bn.press) { + spndev_set_led(dev, led); + led = !led; + } - break; + break; - default: - break; - } - } + default: + break; } } From e17b5289d6f8434c23096eb50dbd636d546f7af6 Mon Sep 17 00:00:00 2001 From: ruevs Date: Tue, 4 Apr 2023 22:51:05 +0300 Subject: [PATCH 19/20] Serial (RS-232) device support improved significantly Tested working on Win32 with a SpaceTec IMC "SpaceBall 3003 FLX" and a 3Dconnexion "Magellan / SpaceMouse" (apart from a checksum error when receiving button and 6DOF data simultaneously). --- src/serdev.c | 100 +++++++++++++++++++++++++++++++++++++--------- src/serio_posix.c | 2 + src/serio_win32.c | 11 +++-- 3 files changed, 90 insertions(+), 23 deletions(-) diff --git a/src/serdev.c b/src/serdev.c index 3749c8a..ff7387d 100644 --- a/src/serdev.c +++ b/src/serdev.c @@ -26,6 +26,7 @@ #define INP_BUF_SZ 256 +#define TURBO_MAGELLAN_COMPRESS enum { SB4000 = 1, @@ -71,7 +72,7 @@ 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"}}, @@ -96,6 +97,7 @@ 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) @@ -120,6 +122,8 @@ int spndev_ser_open(struct spndev *dev, const char *devstr) dev->drvdata = sb; dev->close = close_dev_serial; dev->read = read_dev_serial; + dev->setled = 0; + dev->getled = 0; stty_save(fd, sb); @@ -132,7 +136,7 @@ int spndev_ser_open(struct spndev *dev, const char *devstr) /* 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; } @@ -164,7 +168,17 @@ 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 */ - serwrite(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 0; @@ -192,9 +206,11 @@ static int read_dev_serial(struct spndev* dev, union spndev_event* evt) if (!sb) return -1; - evt->type = SPNDEV_NONE; + evt->type = SPNDEV_NONE; - while ((sz = serread(dev->fd, sb->buf + sb->len, INP_BUF_SZ - sb->len - 1)) > 0) { +// 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); } @@ -221,6 +237,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); @@ -231,7 +248,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 */ } @@ -326,7 +343,7 @@ static int proc_input(struct spndev* dev, union spndev_event* evt) sz = start - sb->buf; if (sz > 0) { - memmove(sb->buf, start, sz); + memmove(sb->buf, start, sz); // PAR@@@ hmmmm.... reading from memory beyond buf ? sb->len -= sz; } return 0; @@ -337,29 +354,55 @@ static int mag_parsepkt(struct spndev* dev, union spndev_event* evt, int id, cha 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<6; i++) { +// 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; @@ -375,7 +418,7 @@ static int mag_parsepkt(struct spndev* dev, union spndev_event* evt, int id, cha } if(sb->keystate != prev_key) { -// PAR@@@ gen_button_events(sb, prev_key); + gen_button_events(sb, prev_key, evt); } break; @@ -435,13 +478,13 @@ static int sball_parsepkt(struct spndev* dev, union spndev_event* evt, int id, c return -1; } - for(i=0; i<6; i++) { + 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; + evt->mot.v[i] = data[0] | data[1] << 8; #else - evt->mot.v[i] = data[1] | data[0] << 8; + evt->mot.v[i] = data[1] | data[0] << 8; #endif if(evt->mot.v[i] != prev) { @@ -472,8 +515,7 @@ static int sball_parsepkt(struct spndev* dev, union spndev_event* evt, int id, c ((data[0] & 0x10) << 3)) & sb->keymask; if(sb->keystate != prev_key) { -// PAR@@@ gen_button_events(sb, prev_key); - evt->type = SPNDEV_BUTTON; + gen_button_events(sb, prev_key, evt); } break; @@ -486,8 +528,8 @@ static int sball_parsepkt(struct spndev* dev, union spndev_event* evt, int id, c 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 */ - sb->keymask = 0xfff; + 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) */ /* @@ -505,7 +547,7 @@ static int sball_parsepkt(struct spndev* dev, union spndev_event* evt, int id, c */ sb->keystate = (data[1] & 0x3f) | ((data[1] & 0x80) >> 1) | ((data[0] & 0x1f) << 7); if(sb->keystate != prev_key) { -// PAR@@@ gen_button_events(sb, prev_key); + gen_button_events(sb, prev_key, evt); } break; @@ -535,7 +577,27 @@ static int sball_parsepkt(struct spndev* dev, union spndev_event* evt, int id, c return 0; } +// Probably not needed used in the original dev_serial.c only to make sure that an unknown message can be printf-ed static void make_printable(char* buf, int len) {} -static int read_timeout(int fd, char* buf, int bufsz, long tm_usec) { + +// Only used in the spndev_ser_open function after opening the serial purt (and thus powerin the device) +// to allow for the first bytes to arrive. Since on Win32 I configure the serial port "blocking" enyway this is not +// really needed. Kept in case in Linux/Unix or some other platform it is harder to achive. +static int read_timeout(int fd, char* buf, int bufsz, long tm_usec) { return serread(fd, buf, bufsz); } + +static void gen_button_events(struct sball* sb, unsigned int prev, union spndev_event* evt) { + int i; + unsigned int bit = 1; + unsigned int diff = sb->keystate ^ 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_posix.c b/src/serio_posix.c index 19cdb9d..1bef13c 100644 --- a/src/serio_posix.c +++ b/src/serio_posix.c @@ -29,6 +29,8 @@ along with this program. If not, see . # 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 */); } diff --git a/src/serio_win32.c b/src/serio_win32.c index 505be53..1b40787 100644 --- a/src/serio_win32.c +++ b/src/serio_win32.c @@ -54,11 +54,14 @@ int seropen(char const* devstr) { // 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 * 8 / 9600; // 9600kbps * 2 for margin - CommTimeouts.ReadTotalTimeoutMultiplier = 1; - CommTimeouts.ReadTotalTimeoutConstant = 1; - CommTimeouts.WriteTotalTimeoutMultiplier = 2 * 1000 * 8 / 9600; + 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"); From 199c35a3181c03beb89b188e94f7c7ba45449f5b Mon Sep 17 00:00:00 2001 From: ruevs Date: Wed, 13 Dec 2023 23:46:18 +0200 Subject: [PATCH 20/20] Support for the SpacePilot SP1 USB LCD display screen Writing to the screen and controlling the backlight works. The API for writing to the screen is not finalized. Also mark the tested and fully working USB devices as such: Spaceball 5000 USB SpaceTraveler SpacePilot SP1 USB SpaceExplorer --- examples/win32/test.c | 2 + src/dev.h | 6 ++ src/serdev.c | 3 + src/spnavdev.c | 24 ++++++ src/spnavdev.h | 4 + src/usbdev.c | 194 +++++++++++++++++++++++++++++++++++++++++- 6 files changed, 229 insertions(+), 4 deletions(-) diff --git a/examples/win32/test.c b/examples/win32/test.c index 57cc8ca..e0fce36 100644 --- a/examples/win32/test.c +++ b/examples/win32/test.c @@ -43,6 +43,8 @@ int main(int argc, char **argv) if (ev.bn.press) { spndev_set_led(dev, led); + spndev_write_lcd(dev, 0xAA); + spndev_set_lcd_bl(dev, led); led = !led; } diff --git a/src/dev.h b/src/dev.h index 9f3c3c1..33af30c 100644 --- a/src/dev.h +++ b/src/dev.h @@ -37,6 +37,7 @@ struct spndev { struct axisprop *aprop; const char **bn_name; int led; + int lcdbl; void *uptr, *drvdata; @@ -45,6 +46,11 @@ 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); }; diff --git a/src/serdev.c b/src/serdev.c index ff7387d..02d580b 100644 --- a/src/serdev.c +++ b/src/serdev.c @@ -124,6 +124,9 @@ int spndev_ser_open(struct spndev *dev, const char *devstr) dev->read = read_dev_serial; dev->setled = 0; dev->getled = 0; + dev->setlcdbl = 0; + dev->getlcdbl = 0; + dev->writelcd = 0; stty_save(fd, sb); diff --git a/src/spnavdev.c b/src/spnavdev.c index 524a176..3531771 100644 --- a/src/spnavdev.c +++ b/src/spnavdev.c @@ -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 5880e2a..b8e5f65 100644 --- a/src/spnavdev.h +++ b/src/spnavdev.h @@ -89,6 +89,10 @@ 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 diff --git a/src/usbdev.c b/src/usbdev.c index 9fa1863..a2afb04 100644 --- a/src/usbdev.c +++ b/src/usbdev.c @@ -59,11 +59,11 @@ 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"}}, // Buttons order? - {0x046d, 0xc623, O, "SpaceTraveler", "", 8, {"1", "2", "3", "4", "5", "6", "7", "8"}}, - {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" + {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 + {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 @@ -89,6 +89,9 @@ 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" @@ -241,6 +244,12 @@ static int usbdev_init(struct spndev* dev, const unsigned type) } 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; } @@ -384,6 +393,183 @@ static inline void checkrange(const struct spndev* dev, const int 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) {