spaceball button handling, and button number guessing
[sball] / src / sball.c
index e51ee3e..627fc1b 100644 (file)
@@ -1,8 +1,10 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <ctype.h>
 #include <time.h>
 #include <errno.h>
+#include <alloca.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <termios.h>
 
 #define INP_BUF_SZ     256
 
+enum {
+       SB4000  = 1,
+       FLIPXY  = 2
+};
+
 struct sball {
        int fd;
+       unsigned int flags;
+       int nbuttons;
 
        char buf[INP_BUF_SZ];
        int len;
 
-       int (*proc_input)(struct sball *sb);
+       int (*parse)(struct sball*, int, char*, int);
 };
 
 static int stty_sball(int fd);
 static int stty_mag(int fd);
 
-static int proc_sball(struct sball *sb);
-static int proc_mag(struct sball *sb);
+static int proc_input(struct sball *sb);
 
-static int mag_parsepkt(int id, char *data, int len);
+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 guess_num_buttons(const char *verstr);
 
 static void make_printable(char *buf, int len);
 static int read_timeout(int fd, char *buf, int bufsz, long tm_usec);
@@ -47,18 +58,25 @@ struct sball *sball_open(const char *dev)
                goto err;
        }
        sb->fd = fd;
+       sb->flags = 0;
+       sb->len = 0;
 
        if(stty_sball(fd) == -1) {
                goto err;
        }
 
-       if((sz = read_timeout(fd, buf, sizeof buf - 1, 2000000)) > 0) {
+       if((sz = read_timeout(fd, buf, sizeof buf - 1, 2000000)) > 0 && memcmp(buf, "\r@1", 3) == 0) {
                /* we got a response, so it's a spaceball */
                make_printable(buf, sz);
                printf("Spaceball detected: %s\n", buf);
-               /* TODO: improve detection, verify it's a correct reset response */
 
-               sb->proc_input = proc_sball;
+               sb->nbuttons = guess_num_buttons(buf);
+               printf("%d buttons\n", sb->nbuttons);
+
+               /* set binary mode and enable automatic data packet sending */
+               write(fd, "\rCB\rMSS\r", 8);
+
+               sb->parse = sball_parsepkt;
                return sb;
        }
 
@@ -68,15 +86,17 @@ struct sball *sball_open(const char *dev)
        }
        write(fd, "vQ\r", 3);
 
-       if((sz = read_timeout(fd, buf, sizeof buf - 1, 250000)) > 0) {
-               /* we got a response, it's a magellan spacemouse */
+       if((sz = read_timeout(fd, buf, sizeof buf - 1, 250000)) > 0 && buf[0] == 'v') {
                make_printable(buf, sz);
                printf("Magellan SpaceMouse detected: %s\n", buf);
 
+               sb->nbuttons = guess_num_buttons(buf);
+               printf("%d buttons\n", sb->nbuttons);
+
                /* set 3D mode, not-dominant-axis, pass through motion and button packets */
                write(fd, "m3\r", 3);
 
-               sb->proc_input = proc_mag;
+               sb->parse = mag_parsepkt;
                return sb;
        }
 
@@ -103,14 +123,14 @@ int sball_read(struct sball *sb)
 
        while((sz = read(sb->fd, sb->buf + sb->len,  INP_BUF_SZ - sb->len - 1)) > 0) {
                sb->len += sz;
-               sb->proc_input(sb);
+               proc_input(sb);
        }
 
        /* 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) {
-               sb->proc_input(sb);
+               proc_input(sb);
                sb->len = 0;
        }
 
@@ -176,12 +196,7 @@ static int stty_mag(int fd)
 }
 
 
-static int proc_sball(struct sball *sb)
-{
-       return -1;
-}
-
-static int proc_mag(struct sball *sb)
+static int proc_input(struct sball *sb)
 {
        int sz;
        char *bptr = sb->buf;
@@ -192,7 +207,7 @@ static int proc_mag(struct sball *sb)
        while(bptr < end) {
                if(*bptr == '\r') {
                        *bptr = 0;
-                       mag_parsepkt(*start, start + 1, bptr - start - 1);
+                       sb->parse(sb, *start, start + 1, bptr - start - 1);
                        start = ++bptr;
                } else {
                        bptr++;
@@ -207,7 +222,20 @@ static int proc_mag(struct sball *sb)
        return 0;
 }
 
-static int mag_parsepkt(int id, char *data, int len)
+static void print_keystate(unsigned int keystate)
+{
+       int i;
+
+       printf("keystate: ");
+       for(i=0; i<12; i++) {
+               int b = 11 - i;
+               int hex = b < 10 ? b + '0' : b - 10 + 'a';
+               putchar(keystate & (1 << b) ? hex : '-');
+       }
+       putchar('\n');
+}
+
+static int mag_parsepkt(struct sball *sb, int id, char *data, int len)
 {
        int i, mot[6];
        unsigned int keystate;
@@ -234,13 +262,7 @@ static int mag_parsepkt(int id, char *data, int len)
                        return -1;
                }
                keystate = (data[0] & 0xf) | ((data[1] & 0xf) << 4) | (((unsigned int)data[2] & 0xf) << 8);
-               printf("keystate: ");
-               for(i=0; i<12; i++) {
-                       int b = 11 - i;
-                       int hex = b < 10 ? b + '0' : b - 10 + 'a';
-                       putchar(keystate & (1 << b) ? hex : '-');
-               }
-               putchar('\n');
+               print_keystate(keystate);
                break;
 
        case 'e':
@@ -259,6 +281,168 @@ static int mag_parsepkt(int id, char *data, int len)
        return 0;
 }
 
+static int sball_parsepkt(struct sball *sb, int id, char *data, int len)
+{
+       int i;
+       unsigned int keystate;
+       char c, *rd, *wr;
+       short *mot;
+
+       /* 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++ = '^';
+                       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;
+               }
+
+               mot = (short*)(data + 2);       /* skip the period */
+               printf("motion: T %+6d %+6d %+6d  R %+6d %+6d %+6d\n", mot[0], mot[1], mot[2], mot[3], mot[4], mot[5]);
+               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 */
+
+               /* 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
+                */
+               keystate = (data[1] & 0xf) | ((data[1] >> 4) & 3) | ((data[0] & 7) << 4) |
+                       ((data[0] & 0x10) >> 1);
+               print_keystate(keystate);
+               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 */
+               sb->flags |= SB4000;
+               /* update orientation flag */
+               if(data[0] & 0x20) {
+                       sb->flags |= FLIPXY;
+               } else {
+                       sb->flags &= ~FLIPXY;
+               }
+
+               /* 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
+                */
+               keystate = (data[1] & 0x3f) | ((data[1] & 0x80) >> 1) | ((data[0] & 0x1f) << 7);
+               print_keystate(keystate);
+               break;
+
+       case 'E':
+               fprintf(stderr, "sball: error:");
+               for(i=0; i<len; i++) {
+                       if(isprint(data[i])) {
+                               fprintf(stderr, " %c", data[i]);
+                       } else {
+                               fprintf(stderr, " %02xh", (unsigned int)data[i]);
+                       }
+               }
+               break;
+
+       default:
+               /* DEBUG */
+               fprintf(stderr, "sball: got '%c' packet:", (char)id);
+               for(i=0; i<len; i++) {
+                       fprintf(stderr, " %02x", (unsigned int)data[i]);
+               }
+               fputc('\n', stderr);
+       }
+       return 0;
+}
+
+static int guess_num_buttons(const char *verstr)
+{
+       int major, minor;
+       const char *s, *model;
+
+       if((s = strstr(verstr, "Firmware version"))) {  /* spaceball */
+
+               /* if we got a model number, guess based on that */
+               if((model = strchr(s, '('))) {
+                       if(memcmp(model, "(Model ", 7) == 0) {
+                               model += 7;
+                       } else {
+                               model++;
+                       }
+                       switch(atoi(model)) {
+                       case 2003:
+                               return 8;
+                       case 3003:
+                               return 2;
+                       case 5000:
+                               return 12;
+                       default:
+                               break;
+                       }
+               }
+               /* try to guess based on firmware number */
+               if(sscanf(s + 17, "%d.%d", &major, &minor) == 2 && major == 2) {
+                       if(minor == 35 || minor == 62 || minor == 63) {
+                               return 2;       /* spaceball 3003/3003C */
+                       }
+                       if(minor == 42 || minor == 43 || minor == 45) {
+                               /* 2.42 is also used by spaceball 2003C, but this should be
+                                * caught before we get here by the model number guess
+                                */
+                               return 12;      /* spaceball 4000flx/5000flx-a */
+                       }
+                       if(minor == 2 || minor == 13 || minor == 15) {
+                               return 8;       /* spaceball 1003/2003/2003c */
+                       }
+               }
+       }
+
+       if(strstr(verstr, "MAGELLAN Version")) {
+               return 9; /* magellan spacemouse */
+       }
+
+       if(strstr(verstr, "SPACEBALL Version")) {
+               return 12; /* spaceball 5000 */
+       }
+
+       if(strstr(verstr, "CadMan Version")) {
+               return 2;
+       }
+
+       fprintf(stderr, "Can't guess number of buttons, default to 8, report this as a bug!\n");
+       return 8;
+}
+
 static void make_printable(char *buf, int len)
 {
        int i, c;
@@ -268,7 +452,7 @@ static void make_printable(char *buf, int len)
                c = *buf++;
                if(c == '\r') {
                        *wr++ = '\n';
-                       if(*buf == '\n') buf++;
+                       while(*buf == '\n' || *buf == '\r') buf++;
                } else {
                        *wr++ = c;
                }