+/* Serial spaceball and space-mouse example driver code for UNIX systems.
+ * Tested with Spaceball 4000FLX and Magellan SpaceMouse on GNU/Linux, FreeBSD,
+ * SGI/IRIX, and SunOS (Solaris/Illumos).
+ *
+ * Author: John Tsiombikas <nuclear@member.fsf.org>
+ * No copyright, public domain.
+ */
#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>
#include <sys/select.h>
#include <sys/time.h>
+#include <sys/ioctl.h>
+
+#if defined(__i386__) || defined(__ia64__) || defined(WIN32) || \
+ (defined(__alpha__) || defined(__alpha)) || \
+ defined(__arm__) || \
+ (defined(__mips__) && defined(__MIPSEL__)) || \
+ defined(__SYMBIAN32__) || \
+ defined(__x86_64__) || \
+ defined(__LITTLE_ENDIAN__)
+#define SBALL_LITTLE_ENDIAN
+#else
+#define SBALL_BIG_ENDIAN
+#endif
#define INP_BUF_SZ 256
char buf[INP_BUF_SZ];
int len;
+ short mot[6];
+ unsigned int keystate, keymask;
+
+ struct termios saved_term;
+ int saved_mstat;
+
int (*parse)(struct sball*, int, char*, int);
};
-static int stty_sball(int fd);
-static int stty_mag(int fd);
+static int stty_sball(struct sball *sb);
+static int stty_mag(struct sball *sb);
+static void stty_save(struct sball *sb);
+static void stty_restore(struct sball *sb);
static int proc_input(struct sball *sb);
static void make_printable(char *buf, int len);
static int read_timeout(int fd, char *buf, int bufsz, long tm_usec);
+static void print_state(struct sball *sb);
+
+
struct sball *sball_open(const char *dev)
{
int fd, sz;
char buf[128];
struct sball *sb = 0;
- if((fd = open(dev, O_RDWR | O_NOCTTY)) == -1) {
+ if((fd = open(dev, O_RDWR | O_NOCTTY | O_NONBLOCK)) == -1) {
fprintf(stderr, "sball_open: failed to open device: %s: %s\n", dev, strerror(errno));
return 0;
}
- if(!(sb = malloc(sizeof *sb))) {
+ if(!(sb = calloc(1, sizeof *sb))) {
fprintf(stderr, "sball_open: failed to allocate sball object\n");
goto err;
}
sb->fd = fd;
- sb->flags = 0;
- sb->len = 0;
- if(stty_sball(fd) == -1) {
+ stty_save(sb);
+
+ if(stty_sball(sb) == -1) {
goto err;
}
+ write(fd, "\r@RESET\r", 8);
- if((sz = read_timeout(fd, buf, sizeof buf - 1, 2000000)) > 0 && memcmp(buf, "\r@1", 3) == 0) {
+ if((sz = read_timeout(fd, buf, sizeof buf - 1, 2000000)) > 0 && strstr(buf, "\r@1")) {
/* we got a response, so it's a spaceball */
make_printable(buf, sz);
printf("Spaceball detected: %s\n", buf);
sb->nbuttons = guess_num_buttons(buf);
+ sb->keymask = 0xffff >> (16 - sb->nbuttons);
printf("%d buttons\n", sb->nbuttons);
- /* set binary mode and enable automatic data packet sending */
- write(fd, "\rCB\rMSS\r", 8);
+ /* set binary mode and enable automatic data packet sending. also request
+ * 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);
sb->parse = sball_parsepkt;
return sb;
}
/* try as a magellan spacemouse */
- if(stty_mag(fd) == -1) {
+ if(stty_mag(sb) == -1) {
goto err;
}
write(fd, "vQ\r", 3);
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);
+ printf("Magellan SpaceMouse detected:\n%s\n", buf);
sb->nbuttons = guess_num_buttons(buf);
+ sb->keymask = 0xffff >> (16 - sb->nbuttons);
printf("%d buttons\n", sb->nbuttons);
/* set 3D mode, not-dominant-axis, pass through motion and button packets */
}
err:
+ stty_restore(sb);
close(fd);
free(sb);
return 0;
void sball_close(struct sball *sb)
{
if(!sb) return;
+
+ stty_restore(sb);
close(sb->fd);
}
return 0;
}
-/* Labtec spaceball: 9600 8n1 XON/XOFF */
-static int stty_sball(int fd)
+int sball_axis(struct sball *sb, int axis)
{
- struct termios term;
+ return sb->mot[axis];
+}
- if(tcgetattr(fd, &term) == -1) {
- perror("sball_open: tcgetattr");
- return -1;
- }
+unsigned int sball_buttons(struct sball *sb)
+{
+ return sb->keystate;
+}
+int sball_num_buttons(struct sball *sb)
+{
+ return sb->nbuttons;
+}
+
+/* Labtec spaceball: 9600 8n1 XON/XOFF
+ * Can't use canonical mode to assemble input into lines for the spaceball,
+ * because binary data received for motion events can include newlines which
+ * would be eaten up by the line discipline. Therefore we'll rely on VTIME=1 to
+ * hopefully get more than 1 byte at a time. Alternatively we could request
+ * printable reports, but I don't feel like implementing that.
+ */
+static int stty_sball(struct sball *sb)
+{
+ int mstat;
+ struct termios term;
+
+ term = sb->saved_term;
term.c_oflag = 0;
term.c_lflag = 0;
term.c_cc[VMIN] = 0;
cfsetispeed(&term, B9600);
cfsetospeed(&term, B9600);
- if(tcsetattr(fd, TCSANOW, &term) == -1) {
+ if(tcsetattr(sb->fd, TCSAFLUSH, &term) == -1) {
perror("sball_open: tcsetattr");
return -1;
}
+ tcflush(sb->fd, TCIOFLUSH);
+ mstat = sb->saved_mstat | TIOCM_DTR | TIOCM_RTS;
+ ioctl(sb->fd, TIOCMGET, &mstat);
return 0;
}
-/* Logicad magellan spacemouse: 9600 8n2 CTS/RTS */
-static int stty_mag(int fd)
+/* Logicad magellan spacemouse: 9600 8n2 CTS/RTS
+ * Since the magellan devices don't seem to send any newlines, we can rely on
+ * canonical mode to feed us nice whole lines at a time.
+ */
+static int stty_mag(struct sball *sb)
{
+ int mstat;
struct termios term;
- if(tcgetattr(fd, &term) == -1) {
- perror("sball_open: tcgetattr");
- return -1;
- }
-
+ term = sb->saved_term;
term.c_oflag = 0;
- term.c_lflag = 0;
+ term.c_lflag = ICANON;
term.c_cc[VMIN] = 0;
- term.c_cc[VTIME] = 1;
-
- term.c_cflag = CLOCAL | CREAD | CS8 | CSTOPB | HUPCL | CRTSCTS;
+ term.c_cc[VTIME] = 0;
+ term.c_cc[VEOF] = 0;
+ term.c_cc[VEOL] = '\r';
+ term.c_cc[VEOL2] = 0;
+ term.c_cc[VERASE] = 0;
+ term.c_cc[VKILL] = 0;
+
+ term.c_cflag = CLOCAL | CREAD | CS8 | CSTOPB | HUPCL;
+#ifdef CCTS_OFLOW
+ term.c_cflag |= CCTS_OFLOW;
+#elif defined(CRTSCTS)
+ term.c_cflag |= CRTSCTS;
+#endif
term.c_iflag = IGNBRK | IGNPAR;
cfsetispeed(&term, B9600);
cfsetospeed(&term, B9600);
- if(tcsetattr(fd, TCSANOW, &term) == -1) {
+ if(tcsetattr(sb->fd, TCSAFLUSH, &term) == -1) {
perror("sball_open: tcsetattr");
return -1;
}
+ tcflush(sb->fd, TCIOFLUSH);
+ mstat = sb->saved_mstat | TIOCM_DTR | TIOCM_RTS;
+ ioctl(sb->fd, TIOCMGET, &mstat);
return 0;
}
+static void stty_save(struct sball *sb)
+{
+ tcgetattr(sb->fd, &sb->saved_term);
+ ioctl(sb->fd, TIOCMGET, &sb->saved_mstat);
+}
+
+static void stty_restore(struct sball *sb)
+{
+ tcsetattr(sb->fd, TCSAFLUSH, &sb->saved_term);
+ tcflush(sb->fd, TCIOFLUSH);
+ ioctl(sb->fd, TIOCMSET, &sb->saved_mstat);
+}
+
static int proc_input(struct sball *sb)
{
return 0;
}
-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;
+ int i;
/*printf("magellan packet: %c - %s (%d bytes)\n", (char)id, data, len);*/
return -1;
}
for(i=0; i<6; i++) {
- mot[i] = ((((int)data[0] & 0xf) << 12) | (((int)data[1] & 0xf) << 8) |
+ sb->mot[i] = ((((int)data[0] & 0xf) << 12) | (((int)data[1] & 0xf) << 8) |
(((int)data[2] & 0xf) << 4) | (data[3] & 0xf)) - 0x8000;
data += 4;
}
- printf("motion: T %+4d %+4d %+4d R %+4d %+4d %+4d\n", mot[0], mot[1], mot[2], mot[3], mot[4], mot[5]);
+ print_state(sb);
break;
case 'k':
- if(len != 3) {
+ if(len < 3) {
fprintf(stderr, "magellan: invalid keyboard pakcet, expected 3 bytes, got: %d\n", len);
return -1;
}
- keystate = (data[0] & 0xf) | ((data[1] & 0xf) << 4) | (((unsigned int)data[2] & 0xf) << 8);
- print_keystate(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;
+ }
+ print_state(sb);
break;
case 'e':
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;
break;
case '^':
*wr++ = '^';
+ break;
default:
fprintf(stderr, "sball decode: ignoring invalid escape code: %xh\n", (unsigned int)c);
}
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]);
+#ifndef SBALL_BIG_ENDIAN
+ rd = data;
+ for(i=0; i<6; i++) {
+ rd += 2;
+ c = rd[0];
+ rd[0] = rd[1];
+ rd[1] = c;
+ }
+#endif
+ memcpy(sb->mot, data + 2, 12);
+ print_state(sb);
break;
case 'K':
* 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);
+ sb->keystate = ((data[1] & 0xf) | ((data[1] >> 4) & 3) | ((data[0] & 7) << 4) |
+ ((data[0] & 0x10) << 3)) & sb->keymask;
+ print_state(sb);
break;
case '.':
return -1;
}
/* spaceball 4000 key packet */
- sb->flags |= SB4000;
- /* update orientation flag */
+ if(!(sb->flags & SB4000)) {
+ printf("Switching to spaceball 4000flx/5000flx-a mode (12 buttons) \n");
+ sb->flags |= SB4000;
+ sb->nbuttons = 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;
}
+ */
/* 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);
+ sb->keystate = (data[1] & 0x3f) | ((data[1] & 0x80) >> 1) | ((data[0] & 0x1f) << 7);
+ print_state(sb);
break;
case 'E':
fprintf(stderr, "sball: error:");
for(i=0; i<len; i++) {
- if(isprint(data[i])) {
+ if(isprint((int)data[i])) {
fprintf(stderr, " %c", data[i]);
} else {
fprintf(stderr, " %02xh", (unsigned int)data[i]);
}
break;
+ case 'M': /* ignore MSS responses */
+ break;
+
default:
/* DEBUG */
fprintf(stderr, "sball: got '%c' packet:", (char)id);
static int guess_num_buttons(const char *verstr)
{
int major, minor;
- const char *s, *model;
+ const char *s;
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
- */
+ if(minor == 43 || minor == 45) {
return 12; /* spaceball 4000flx/5000flx-a */
}
- if(minor == 2 || minor == 13 || minor == 15) {
+ if(minor == 2 || minor == 13 || minor == 15 || minor == 42) {
+ /* 2.42 is also used by spaceball 4000flx. we'll guess 2003c for
+ * now, and change the buttons to 12 first time we get a '.'
+ * packet. I'll also request a key report during init to make
+ * sure this happens as soon as possible, before clients have a
+ * chance to connect.
+ */
return 8; /* spaceball 1003/2003/2003c */
}
}
}
- if(strstr(verstr, "MAGELLAN Version")) {
+ if(strstr(verstr, "MAGELLAN")) {
return 9; /* magellan spacemouse */
}
- if(strstr(verstr, "SPACEBALL Version")) {
+ if(strstr(verstr, "SPACEBALL")) {
return 12; /* spaceball 5000 */
}
- if(strstr(verstr, "CadMan Version")) {
- return 2;
+ if(strstr(verstr, "CadMan")) {
+ return 4;
}
fprintf(stderr, "Can't guess number of buttons, default to 8, report this as a bug!\n");
return sz > 0 ? sz : -1;
}
+static void print_motion(short *mot)
+{
+ printf(" T[%+6d %+6d %+6d] R[%+6d %+6d %+6d]", mot[0], mot[1],
+ mot[2], mot[3], mot[4], mot[5]);
+}
+
+static void print_keystate(unsigned int keystate)
+{
+ int i;
+
+ for(i=0; i<16; i++) {
+ int b = 15 - i;
+ int hex = b < 10 ? b + '0' : b - 10 + 'a';
+ putchar(keystate & (1 << b) ? hex : '-');
+ }
+}
+
+static void print_state(struct sball *sb)
+{
+ print_motion(sb->mot);
+ printf(" B[");
+ print_keystate(sb->keystate);
+ printf("]\r");
+ fflush(stdout);
+}
+
+
+/* test all known button packets for all models */
+enum {
+ BTEST_SB2003,
+ BTEST_SB2003C,
+ BTEST_SB3003C,
+ BTEST_SB4000FLX,
+ BTEST_SMCLASSIC,
+ BTEST_SMPLUS,
+ BTEST_SB5000FLX,
+ BTEST_SPEXP,
+
+ NUM_BTEST_MODELS
+};
+
+static const char *btest_models[NUM_BTEST_MODELS] = {
+ "Spaceball 2003",
+ "Spaceball 2003C",
+ "Spaceball 3003C",
+ "Spaceball 4000FLX",
+ "Spacemouse classic",
+ "Spacemouse plus/xt",
+ "Spaceball 5000FLX",
+ "Space Explorer"
+};
+
+struct btest_data {
+ int device;
+ const char *label;
+ const char *pkt;
+};
+
+struct btest_data btest_data[] = {
+ {BTEST_SB2003, "bn 1", "K@A\r"},
+ {BTEST_SB2003, "bn 2", "K@B\r"},
+ {BTEST_SB2003, "bn 3", "K@D\r"},
+ {BTEST_SB2003, "bn 4", "K@H\r"},
+ {BTEST_SB2003, "bn 5", "KA@\r"},
+ {BTEST_SB2003, "bn 6", "KB@\r"},
+ {BTEST_SB2003, "bn 7", "KD@\r"},
+ {BTEST_SB2003, "bn 8", "KH@\r"},
+ {BTEST_SB2003, "pick", "KP@\r"},
+
+ {BTEST_SB2003C, "bn 1", "K@A\r"},
+ {BTEST_SB2003C, "bn 2", "K@B\r"},
+ {BTEST_SB2003C, "bn 3", "K@D\r"},
+ {BTEST_SB2003C, "bn 4", "K@H\r"},
+ {BTEST_SB2003C, "bn 5", "KA@\r"},
+ {BTEST_SB2003C, "bn 6", "KB@\r"},
+ {BTEST_SB2003C, "bn 7", "KD@\r"},
+ {BTEST_SB2003C, "bn 8", "KP@\r"},
+ {BTEST_SB2003C, "zero", "K`@\r"},
+
+ {BTEST_SB3003C, "bn R", "KPP\r"},
+ {BTEST_SB3003C, "bn L", "K@`\r"},
+ {BTEST_SB3003C, "zero", "K`@\r"},
+
+ {BTEST_SB4000FLX, "bn 1", ".@A\r"},
+ {BTEST_SB4000FLX, "bn 2", ".@B\r"},
+ {BTEST_SB4000FLX, "bn 3", ".@D\r"},
+ {BTEST_SB4000FLX, "bn 4", ".@H\r"},
+ {BTEST_SB4000FLX, "bn 5", ".@P\r"},
+ {BTEST_SB4000FLX, "bn 6", ".@`\r"},
+ {BTEST_SB4000FLX, "bn 7", ".@\xc0\r"},
+ {BTEST_SB4000FLX, "bn 8", ".A@\r"},
+ {BTEST_SB4000FLX, "bn 9", ".B@\r"},
+ {BTEST_SB4000FLX, "bn A", ".D@\r"},
+ {BTEST_SB4000FLX, "bn B", ".H@\r"},
+ {BTEST_SB4000FLX, "bn C", ".P@\r"},
+ {BTEST_SB4000FLX, "r.h.", ".`@\r"},
+
+ {BTEST_SMCLASSIC, "bn 1", "kA00\r"},
+ {BTEST_SMCLASSIC, "bn 2", "kB00\r"},
+ {BTEST_SMCLASSIC, "bn 3", "kD00\r"},
+ {BTEST_SMCLASSIC, "bn 4", "kH00\r"},
+ {BTEST_SMCLASSIC, "bn 5", "k0A0\r"},
+ {BTEST_SMCLASSIC, "bn 6", "k0B0\r"},
+ {BTEST_SMCLASSIC, "bn 7", "k0D0\r"},
+ {BTEST_SMCLASSIC, "bn 8", "k0H0\r"},
+ {BTEST_SMCLASSIC, "bn *", "k00A\r"},
+
+ {BTEST_SMPLUS, "bn 1", "kA00\r"},
+ {BTEST_SMPLUS, "bn 2", "kB00\r"},
+ {BTEST_SMPLUS, "bn 3", "kD00\r"},
+ {BTEST_SMPLUS, "bn 4", "kH00\r"},
+ {BTEST_SMPLUS, "bn 5", "k0A0\r"},
+ {BTEST_SMPLUS, "bn 6", "k0B0\r"},
+ {BTEST_SMPLUS, "bn 7", "k0D0\r"},
+ {BTEST_SMPLUS, "bn 8", "k0H0\r"},
+ {BTEST_SMPLUS, "bn *", "k00A\r"},
+ {BTEST_SMPLUS, "bn A", "k00B\r"},
+ {BTEST_SMPLUS, "bn B", "k00D\r"},
+
+ {BTEST_SB5000FLX, "bn 1", "kA00\r"},
+ {BTEST_SB5000FLX, "bn 2", "kB00\r"},
+ {BTEST_SB5000FLX, "bn 3", "kD00\r"},
+ {BTEST_SB5000FLX, "bn 4", "kH00\r"},
+ {BTEST_SB5000FLX, "bn 5", "k0A0\r"},
+ {BTEST_SB5000FLX, "bn 6", "k0B0\r"},
+ {BTEST_SB5000FLX, "bn 7", "k0D0\r"},
+ {BTEST_SB5000FLX, "bn 8", "k0H0\r"},
+ {BTEST_SB5000FLX, "bn 9", "k00A\r"},
+ {BTEST_SB5000FLX, "bn A", "k00B\r"},
+ {BTEST_SB5000FLX, "bn B", "k00D\r"},
+ {BTEST_SB5000FLX, "bn C", "k00H\r"},
+
+ {BTEST_SPEXP, "bn 1", "kA000\r"},
+ {BTEST_SPEXP, "bn 2", "kB000\r"},
+ {BTEST_SPEXP, "bn T", "kD000\r"},
+ {BTEST_SPEXP, "bn L", "kH000\r"},
+ {BTEST_SPEXP, "bn R", "k0A00\r"},
+ {BTEST_SPEXP, "bn F", "k0B00\r"},
+ {BTEST_SPEXP, " ALT", "k0D00\r"},
+ {BTEST_SPEXP, " ESC", "k0H00\r"},
+ {BTEST_SPEXP, "SHFT", "k00A0\r"},
+ {BTEST_SPEXP, "CTRL", "k00B0\r"},
+ {BTEST_SPEXP, " fit", "k00D0\r"},
+ {BTEST_SPEXP, "panl", "k00H0\r"},
+ {BTEST_SPEXP, "bn +", "k000A\r"},
+ {BTEST_SPEXP, "bn -", "k000B\r"},
+ {BTEST_SPEXP, " 2D", "k000D\r"},
+
+ {-1, 0, 0}
+};
+
+void sball_button_test(void)
+{
+ char pkt[16];
+ int dev = -1;
+ int (*parse)(struct sball*, int, char*, int);
+ struct btest_data *td = btest_data;
+ struct sball sb = {0};
+
+
+ while(td->device >= 0) {
+ if(td->device != dev) {
+ dev = td->device;
+ sb.keymask = dev == BTEST_SB3003C ? 3 : 0xffff;
+ parse = (dev < BTEST_SMCLASSIC) ? sball_parsepkt : mag_parsepkt;
+ printf("\n\nTesting button packets for: %s", btest_models[dev]);
+ }
+
+ strcpy(pkt, td->pkt + 1);
+ sb.flags = dev == BTEST_SB4000FLX ? SB4000 : 0;
+
+ printf("\n %s: ", td->label);
+ parse(&sb, td->pkt[0], pkt, strlen(pkt) - 1);
+ td++;
+ }
+ putchar('\n');
+}