default irix device
[sball] / src / sball.c
1 /* Serial spaceball and space-mouse example driver code for UNIX systems.
2  * Tested with Spaceball 4000FLX and Magellan SpaceMouse on GNU/Linux, FreeBSD,
3  * SGI/IRIX, and SunOS (Solaris/Illumos).
4  *
5  * Author: John Tsiombikas <nuclear@member.fsf.org>
6  * No copyright, public domain.
7  */
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <ctype.h>
12 #include <time.h>
13 #include <errno.h>
14 #include <unistd.h>
15 #include <fcntl.h>
16 #include <termios.h>
17 #include <sys/select.h>
18 #include <sys/time.h>
19 #include <sys/ioctl.h>
20
21 #if  defined(__i386__) || defined(__ia64__) || defined(WIN32) || \
22     (defined(__alpha__) || defined(__alpha)) || \
23      defined(__arm__) || \
24     (defined(__mips__) && defined(__MIPSEL__)) || \
25      defined(__SYMBIAN32__) || \
26      defined(__x86_64__) || \
27      defined(__LITTLE_ENDIAN__)
28 #define SBALL_LITTLE_ENDIAN
29 #else
30 #define SBALL_BIG_ENDIAN
31 #endif
32
33 #define INP_BUF_SZ      256
34
35 enum {
36         SB4000  = 1,
37         FLIPXY  = 2
38 };
39
40 struct sball {
41         int fd;
42         unsigned int flags;
43         int nbuttons;
44
45         char buf[INP_BUF_SZ];
46         int len;
47
48         short mot[6];
49         unsigned int keystate, keymask;
50
51         struct termios saved_term;
52         int saved_mstat;
53
54         int (*parse)(struct sball*, int, char*, int);
55 };
56
57 static int stty_sball(struct sball *sb);
58 static int stty_mag(struct sball *sb);
59 static void stty_save(struct sball *sb);
60 static void stty_restore(struct sball *sb);
61
62 static int proc_input(struct sball *sb);
63
64 static int mag_parsepkt(struct sball *sb, int id, char *data, int len);
65 static int sball_parsepkt(struct sball *sb, int id, char *data, int len);
66
67 static int guess_num_buttons(const char *verstr);
68
69 static void make_printable(char *buf, int len);
70 static int read_timeout(int fd, char *buf, int bufsz, long tm_usec);
71
72 static void print_state(struct sball *sb);
73
74
75 struct sball *sball_open(const char *dev)
76 {
77         int fd, sz;
78         char buf[128];
79         struct sball *sb = 0;
80
81         if((fd = open(dev, O_RDWR | O_NOCTTY | O_NONBLOCK)) == -1) {
82                 fprintf(stderr, "sball_open: failed to open device: %s: %s\n", dev, strerror(errno));
83                 return 0;
84         }
85
86         if(!(sb = calloc(1, sizeof *sb))) {
87                 fprintf(stderr, "sball_open: failed to allocate sball object\n");
88                 goto err;
89         }
90         sb->fd = fd;
91
92         stty_save(sb);
93
94         if(stty_sball(sb) == -1) {
95                 goto err;
96         }
97         write(fd, "\r@RESET\r", 8);
98
99         if((sz = read_timeout(fd, buf, sizeof buf - 1, 2000000)) > 0 && strstr(buf, "\r@1")) {
100                 /* we got a response, so it's a spaceball */
101                 make_printable(buf, sz);
102                 printf("Spaceball detected: %s\n", buf);
103
104                 sb->nbuttons = guess_num_buttons(buf);
105                 sb->keymask = 0xffff >> (16 - sb->nbuttons);
106                 printf("%d buttons\n", sb->nbuttons);
107
108                 /* set binary mode and enable automatic data packet sending. also request
109                  * a key event to find out as soon as possible if this is a 4000flx with
110                  * 12 buttons
111                 */
112                 write(fd, "\rCB\rMSSV\rk\r", 11);
113
114                 sb->parse = sball_parsepkt;
115                 return sb;
116         }
117
118         /* try as a magellan spacemouse */
119         if(stty_mag(sb) == -1) {
120                 goto err;
121         }
122         write(fd, "vQ\r", 3);
123
124         if((sz = read_timeout(fd, buf, sizeof buf - 1, 250000)) > 0 && buf[0] == 'v') {
125                 make_printable(buf, sz);
126                 printf("Magellan SpaceMouse detected:\n%s\n", buf);
127
128                 sb->nbuttons = guess_num_buttons(buf);
129                 sb->keymask = 0xffff >> (16 - sb->nbuttons);
130                 printf("%d buttons\n", sb->nbuttons);
131
132                 /* set 3D mode, not-dominant-axis, pass through motion and button packets */
133                 write(fd, "m3\r", 3);
134
135                 sb->parse = mag_parsepkt;
136                 return sb;
137         }
138
139 err:
140         stty_restore(sb);
141         close(fd);
142         free(sb);
143         return 0;
144 }
145
146 void sball_close(struct sball *sb)
147 {
148         if(!sb) return;
149
150         stty_restore(sb);
151         close(sb->fd);
152 }
153
154 int sball_fd(struct sball *sb)
155 {
156         return sb->fd;
157 }
158
159 int sball_read(struct sball *sb)
160 {
161         int sz;
162
163         while((sz = read(sb->fd, sb->buf + sb->len,  INP_BUF_SZ - sb->len - 1)) > 0) {
164                 sb->len += sz;
165                 proc_input(sb);
166         }
167
168         /* if we fill the input buffer, make a last attempt to parse it, and discard
169          * it so we can receive more
170          */
171         if(sb->len >= INP_BUF_SZ) {
172                 proc_input(sb);
173                 sb->len = 0;
174         }
175
176         return 0;
177 }
178
179 int sball_axis(struct sball *sb, int axis)
180 {
181         return sb->mot[axis];
182 }
183
184 unsigned int sball_buttons(struct sball *sb)
185 {
186         return sb->keystate;
187 }
188
189 int sball_num_buttons(struct sball *sb)
190 {
191         return sb->nbuttons;
192 }
193
194 /* Labtec spaceball: 9600 8n1 XON/XOFF
195  * Can't use canonical mode to assemble input into lines for the spaceball,
196  * because binary data received for motion events can include newlines which
197  * would be eaten up by the line discipline. Therefore we'll rely on VTIME=1 to
198  * hopefully get more than 1 byte at a time. Alternatively we could request
199  * printable reports, but I don't feel like implementing that.
200  */
201 static int stty_sball(struct sball *sb)
202 {
203         int mstat;
204         struct termios term;
205
206         term = sb->saved_term;
207         term.c_oflag = 0;
208         term.c_lflag = 0;
209         term.c_cc[VMIN] = 0;
210         term.c_cc[VTIME] = 1;
211
212         term.c_cflag = CLOCAL | CREAD | CS8 | HUPCL;
213         term.c_iflag = IGNBRK | IGNPAR | IXON | IXOFF;
214
215         cfsetispeed(&term, B9600);
216         cfsetospeed(&term, B9600);
217
218         if(tcsetattr(sb->fd, TCSAFLUSH, &term) == -1) {
219                 perror("sball_open: tcsetattr");
220                 return -1;
221         }
222         tcflush(sb->fd, TCIOFLUSH);
223
224         mstat = sb->saved_mstat | TIOCM_DTR | TIOCM_RTS;
225         ioctl(sb->fd, TIOCMGET, &mstat);
226         return 0;
227 }
228
229 /* Logicad magellan spacemouse: 9600 8n2 CTS/RTS
230  * Since the magellan devices don't seem to send any newlines, we can rely on
231  * canonical mode to feed us nice whole lines at a time.
232  */
233 static int stty_mag(struct sball *sb)
234 {
235         int mstat;
236         struct termios term;
237
238         term = sb->saved_term;
239         term.c_oflag = 0;
240         term.c_lflag = ICANON;
241         term.c_cc[VMIN] = 0;
242         term.c_cc[VTIME] = 0;
243         term.c_cc[VEOF] = 0;
244         term.c_cc[VEOL] = '\r';
245         term.c_cc[VEOL2] = 0;
246         term.c_cc[VERASE] = 0;
247         term.c_cc[VKILL] = 0;
248
249         term.c_cflag = CLOCAL | CREAD | CS8 | CSTOPB | HUPCL;
250 #ifdef CCTS_OFLOW
251         term.c_cflag |= CCTS_OFLOW;
252 #elif defined(CRTSCTS)
253         term.c_cflag |= CRTSCTS;
254 #endif
255         term.c_iflag = IGNBRK | IGNPAR;
256
257         cfsetispeed(&term, B9600);
258         cfsetospeed(&term, B9600);
259
260         if(tcsetattr(sb->fd, TCSAFLUSH, &term) == -1) {
261                 perror("sball_open: tcsetattr");
262                 return -1;
263         }
264         tcflush(sb->fd, TCIOFLUSH);
265
266         mstat = sb->saved_mstat | TIOCM_DTR | TIOCM_RTS;
267         ioctl(sb->fd, TIOCMGET, &mstat);
268         return 0;
269 }
270
271 static void stty_save(struct sball *sb)
272 {
273         tcgetattr(sb->fd, &sb->saved_term);
274         ioctl(sb->fd, TIOCMGET, &sb->saved_mstat);
275 }
276
277 static void stty_restore(struct sball *sb)
278 {
279         tcsetattr(sb->fd, TCSAFLUSH, &sb->saved_term);
280         tcflush(sb->fd, TCIOFLUSH);
281         ioctl(sb->fd, TIOCMSET, &sb->saved_mstat);
282 }
283
284
285 static int proc_input(struct sball *sb)
286 {
287         int sz;
288         char *bptr = sb->buf;
289         char *start = sb->buf;
290         char *end = sb->buf + sb->len;
291
292         /* see if we have a CR in the buffer */
293         while(bptr < end) {
294                 if(*bptr == '\r') {
295                         *bptr = 0;
296                         sb->parse(sb, *start, start + 1, bptr - start - 1);
297                         start = ++bptr;
298                 } else {
299                         bptr++;
300                 }
301         }
302
303         sz = start - sb->buf;
304         if(sz > 0) {
305                 memmove(sb->buf, start, sz);
306                 sb->len -= sz;
307         }
308         return 0;
309 }
310
311 static int mag_parsepkt(struct sball *sb, int id, char *data, int len)
312 {
313         int i;
314
315         /*printf("magellan packet: %c - %s (%d bytes)\n", (char)id, data, len);*/
316
317         switch(id) {
318         case 'd':
319                 if(len != 24) {
320                         fprintf(stderr, "magellan: invalid data packet, expected 24 bytes, got: %d\n", len);
321                         return -1;
322                 }
323                 for(i=0; i<6; i++) {
324                         sb->mot[i] = ((((int)data[0] & 0xf) << 12) | (((int)data[1] & 0xf) << 8) |
325                                         (((int)data[2] & 0xf) << 4) | (data[3] & 0xf)) - 0x8000;
326                         data += 4;
327                 }
328                 print_state(sb);
329                 break;
330
331         case 'k':
332                 if(len < 3) {
333                         fprintf(stderr, "magellan: invalid keyboard pakcet, expected 3 bytes, got: %d\n", len);
334                         return -1;
335                 }
336                 sb->keystate = (data[0] & 0xf) | ((data[1] & 0xf) << 4) | (((unsigned int)data[2] & 0xf) << 8);
337                 if(len > 3) {
338                         sb->keystate |= ((unsigned int)data[3] & 0xf) << 12;
339                 }
340                 print_state(sb);
341                 break;
342
343         case 'e':
344                 if(data[0] == 1) {
345                         fprintf(stderr, "magellan error: illegal command: %c%c\n", data[1], data[2]);
346                 } else if(data[0] == 2) {
347                         fprintf(stderr, "magellan error: framing error\n");
348                 } else {
349                         fprintf(stderr, "magellan error: unknown device error\n");
350                 }
351                 return -1;
352
353         default:
354                 break;
355         }
356         return 0;
357 }
358
359 static int sball_parsepkt(struct sball *sb, int id, char *data, int len)
360 {
361         int i;
362         char c, *rd, *wr;
363
364         /* decode data packet, replacing escaped values with the correct ones */
365         rd = wr = data;
366         while(rd < data + len) {
367                 if((c = *rd++) == '^') {
368                         switch(*rd++) {
369                         case 'Q':
370                                 *wr++ = 0x11;   /* XON */
371                                 break;
372                         case 'S':
373                                 *wr++ = 0x13;   /* XOFF */
374                                 break;
375                         case 'M':
376                                 *wr++ = 13;             /* CR */
377                                 break;
378                         case '^':
379                                 *wr++ = '^';
380                                 break;
381                         default:
382                                 fprintf(stderr, "sball decode: ignoring invalid escape code: %xh\n", (unsigned int)c);
383                         }
384                 } else {
385                         *wr++ = c;
386                 }
387         }
388         len = wr - data;        /* update the decoded length */
389
390         switch(id) {
391         case 'D':
392                 if(len != 14) {
393                         fprintf(stderr, "sball: invalid data packet, expected 14 bytes, got: %d\n", len);
394                         return -1;
395                 }
396
397 #ifndef SBALL_BIG_ENDIAN
398                 rd = data;
399                 for(i=0; i<6; i++) {
400                         rd += 2;
401                         c = rd[0];
402                         rd[0] = rd[1];
403                         rd[1] = c;
404                 }
405 #endif
406                 memcpy(sb->mot, data + 2, 12);
407                 print_state(sb);
408                 break;
409
410         case 'K':
411                 if(len != 2) {
412                         fprintf(stderr, "sball: invalid key packet, expected 2 bytes, got: %d\n", len);
413                         return -1;
414                 }
415                 if(sb->flags & SB4000) break;   /* ignore K packets from spaceball 4000 devices */
416
417                 /* data[1] bits 0-3 -> buttons 0,1,2,3
418                  * data[1] bits 4,5 (3003 L/R) -> buttons 0, 1
419                  * data[0] bits 0-2 -> buttons 4,5,6
420                  * data[0] bit 4 is (2003 pick) -> button 7
421                  */
422                 sb->keystate = ((data[1] & 0xf) | ((data[1] >> 4) & 3) | ((data[0] & 7) << 4) |
423                         ((data[0] & 0x10) << 3)) & sb->keymask;
424                 print_state(sb);
425                 break;
426
427         case '.':
428                 if(len != 2) {
429                         fprintf(stderr, "sball: invalid sb4k key packet, expected 2 bytes, got: %d\n", len);
430                         return -1;
431                 }
432                 /* spaceball 4000 key packet */
433                 if(!(sb->flags & SB4000)) {
434                         printf("Switching to spaceball 4000flx/5000flx-a mode (12 buttons)            \n");
435                         sb->flags |= SB4000;
436                         sb->nbuttons = 12;      /* might have guessed 8 before */
437                         sb->keymask = 0xfff;
438                 }
439                 /* update orientation flag (actually don't bother) */
440                 /*
441                 if(data[0] & 0x20) {
442                         sb->flags |= FLIPXY;
443                 } else {
444                         sb->flags &= ~FLIPXY;
445                 }
446                 */
447
448                 /* data[1] bits 0-5 -> buttons 0,1,2,3,4,5
449                  * data[1] bit 7 -> button 6
450                  * data[0] bits 0-4 -> buttons 7,8,9,10,11
451                  */
452                 sb->keystate = (data[1] & 0x3f) | ((data[1] & 0x80) >> 1) | ((data[0] & 0x1f) << 7);
453                 print_state(sb);
454                 break;
455
456         case 'E':
457                 fprintf(stderr, "sball: error:");
458                 for(i=0; i<len; i++) {
459                         if(isprint((int)data[i])) {
460                                 fprintf(stderr, " %c", data[i]);
461                         } else {
462                                 fprintf(stderr, " %02xh", (unsigned int)data[i]);
463                         }
464                 }
465                 break;
466
467         case 'M':       /* ignore MSS responses */
468                 break;
469
470         default:
471                 /* DEBUG */
472                 fprintf(stderr, "sball: got '%c' packet:", (char)id);
473                 for(i=0; i<len; i++) {
474                         fprintf(stderr, " %02x", (unsigned int)data[i]);
475                 }
476                 fputc('\n', stderr);
477         }
478         return 0;
479 }
480
481 static int guess_num_buttons(const char *verstr)
482 {
483         int major, minor;
484         const char *s;
485
486         if((s = strstr(verstr, "Firmware version"))) {  /* spaceball */
487                 /* try to guess based on firmware number */
488                 if(sscanf(s + 17, "%d.%d", &major, &minor) == 2 && major == 2) {
489                         if(minor == 35 || minor == 62 || minor == 63) {
490                                 return 2;       /* spaceball 3003/3003C */
491                         }
492                         if(minor == 43 || minor == 45) {
493                                 return 12;      /* spaceball 4000flx/5000flx-a */
494                         }
495                         if(minor == 2 || minor == 13 || minor == 15 || minor == 42) {
496                                 /* 2.42 is also used by spaceball 4000flx. we'll guess 2003c for
497                                  * now, and change the buttons to 12 first time we get a '.'
498                                  * packet. I'll also request a key report during init to make
499                                  * sure this happens as soon as possible, before clients have a
500                                  * chance to connect.
501                                  */
502                                 return 8;       /* spaceball 1003/2003/2003c */
503                         }
504                 }
505         }
506
507         if(strstr(verstr, "MAGELLAN")) {
508                 return 9; /* magellan spacemouse */
509         }
510
511         if(strstr(verstr, "SPACEBALL")) {
512                 return 12; /* spaceball 5000 */
513         }
514
515         if(strstr(verstr, "CadMan")) {
516                 return 4;
517         }
518
519         fprintf(stderr, "Can't guess number of buttons, default to 8, report this as a bug!\n");
520         return 8;
521 }
522
523 static void make_printable(char *buf, int len)
524 {
525         int i, c;
526         char *wr = buf;
527
528         for(i=0; i<len; i++) {
529                 c = *buf++;
530                 if(c == '\r') {
531                         *wr++ = '\n';
532                         while(*buf == '\n' || *buf == '\r') buf++;
533                 } else {
534                         *wr++ = c;
535                 }
536         }
537         *wr = 0;
538 }
539
540 static int read_timeout(int fd, char *buf, int bufsz, long tm_usec)
541 {
542         int res;
543         long usec, sz = 0;
544         struct timeval tv0, tv;
545         fd_set rdset;
546
547         if(!buf || bufsz <= 0) return -1;
548
549         usec = tm_usec;
550         gettimeofday(&tv0, 0);
551
552         while(sz < bufsz && usec > 0) {
553                 tv.tv_sec = usec / 1000000;
554                 tv.tv_usec = usec % 1000000;
555
556                 FD_ZERO(&rdset);
557                 FD_SET(fd, &rdset);
558                 if((res = select(fd + 1, &rdset, 0, 0, &tv)) > 0 && FD_ISSET(fd, &rdset)) {
559                         sz += read(fd, buf + sz, bufsz - sz);
560                         buf[sz] = 0;
561                         tm_usec = usec = 128000;        /* wait 128ms for the rest of the message to appear */
562                         gettimeofday(&tv0, 0);
563                         continue;
564                 }
565                 if(res == -1 && (errno == EWOULDBLOCK || errno == EAGAIN)) {
566                         break;
567                 }
568                 gettimeofday(&tv, 0);
569                 usec = tm_usec - ((tv.tv_sec - tv0.tv_sec) * 1000000 + (tv.tv_usec - tv0.tv_usec));
570         }
571
572         return sz > 0 ? sz : -1;
573 }
574
575 static void print_motion(short *mot)
576 {
577         printf(" T[%+6d %+6d %+6d]  R[%+6d %+6d %+6d]", mot[0], mot[1],
578                         mot[2], mot[3], mot[4], mot[5]);
579 }
580
581 static void print_keystate(unsigned int keystate)
582 {
583         int i;
584
585         for(i=0; i<16; i++) {
586                 int b = 15 - i;
587                 int hex = b < 10 ? b + '0' : b - 10 + 'a';
588                 putchar(keystate & (1 << b) ? hex : '-');
589         }
590 }
591
592 static void print_state(struct sball *sb)
593 {
594         print_motion(sb->mot);
595         printf("  B[");
596         print_keystate(sb->keystate);
597         printf("]\r");
598         fflush(stdout);
599 }
600
601
602 /* test all known button packets for all models */
603 enum {
604         BTEST_SB2003,
605         BTEST_SB2003C,
606         BTEST_SB3003C,
607         BTEST_SB4000FLX,
608         BTEST_SMCLASSIC,
609         BTEST_SMPLUS,
610         BTEST_SB5000FLX,
611         BTEST_SPEXP,
612
613         NUM_BTEST_MODELS
614 };
615
616 static const char *btest_models[NUM_BTEST_MODELS] = {
617         "Spaceball 2003",
618         "Spaceball 2003C",
619         "Spaceball 3003C",
620         "Spaceball 4000FLX",
621         "Spacemouse classic",
622         "Spacemouse plus/xt",
623         "Spaceball 5000FLX",
624         "Space Explorer"
625 };
626
627 struct btest_data {
628         int device;
629         const char *label;
630         const char *pkt;
631 };
632
633 struct btest_data btest_data[] = {
634         {BTEST_SB2003, "bn 1", "K@A\r"},
635         {BTEST_SB2003, "bn 2", "K@B\r"},
636         {BTEST_SB2003, "bn 3", "K@D\r"},
637         {BTEST_SB2003, "bn 4", "K@H\r"},
638         {BTEST_SB2003, "bn 5", "KA@\r"},
639         {BTEST_SB2003, "bn 6", "KB@\r"},
640         {BTEST_SB2003, "bn 7", "KD@\r"},
641         {BTEST_SB2003, "bn 8", "KH@\r"},
642         {BTEST_SB2003, "pick", "KP@\r"},
643
644         {BTEST_SB2003C, "bn 1", "K@A\r"},
645         {BTEST_SB2003C, "bn 2", "K@B\r"},
646         {BTEST_SB2003C, "bn 3", "K@D\r"},
647         {BTEST_SB2003C, "bn 4", "K@H\r"},
648         {BTEST_SB2003C, "bn 5", "KA@\r"},
649         {BTEST_SB2003C, "bn 6", "KB@\r"},
650         {BTEST_SB2003C, "bn 7", "KD@\r"},
651         {BTEST_SB2003C, "bn 8", "KP@\r"},
652         {BTEST_SB2003C, "zero", "K`@\r"},
653
654         {BTEST_SB3003C, "bn R", "KPP\r"},
655         {BTEST_SB3003C, "bn L", "K@`\r"},
656         {BTEST_SB3003C, "zero", "K`@\r"},
657
658         {BTEST_SB4000FLX, "bn 1", ".@A\r"},
659         {BTEST_SB4000FLX, "bn 2", ".@B\r"},
660         {BTEST_SB4000FLX, "bn 3", ".@D\r"},
661         {BTEST_SB4000FLX, "bn 4", ".@H\r"},
662         {BTEST_SB4000FLX, "bn 5", ".@P\r"},
663         {BTEST_SB4000FLX, "bn 6", ".@`\r"},
664         {BTEST_SB4000FLX, "bn 7", ".@\xc0\r"},
665         {BTEST_SB4000FLX, "bn 8", ".A@\r"},
666         {BTEST_SB4000FLX, "bn 9", ".B@\r"},
667         {BTEST_SB4000FLX, "bn A", ".D@\r"},
668         {BTEST_SB4000FLX, "bn B", ".H@\r"},
669         {BTEST_SB4000FLX, "bn C", ".P@\r"},
670         {BTEST_SB4000FLX, "r.h.", ".`@\r"},
671
672         {BTEST_SMCLASSIC, "bn 1", "kA00\r"},
673         {BTEST_SMCLASSIC, "bn 2", "kB00\r"},
674         {BTEST_SMCLASSIC, "bn 3", "kD00\r"},
675         {BTEST_SMCLASSIC, "bn 4", "kH00\r"},
676         {BTEST_SMCLASSIC, "bn 5", "k0A0\r"},
677         {BTEST_SMCLASSIC, "bn 6", "k0B0\r"},
678         {BTEST_SMCLASSIC, "bn 7", "k0D0\r"},
679         {BTEST_SMCLASSIC, "bn 8", "k0H0\r"},
680         {BTEST_SMCLASSIC, "bn *", "k00A\r"},
681
682         {BTEST_SMPLUS, "bn 1", "kA00\r"},
683         {BTEST_SMPLUS, "bn 2", "kB00\r"},
684         {BTEST_SMPLUS, "bn 3", "kD00\r"},
685         {BTEST_SMPLUS, "bn 4", "kH00\r"},
686         {BTEST_SMPLUS, "bn 5", "k0A0\r"},
687         {BTEST_SMPLUS, "bn 6", "k0B0\r"},
688         {BTEST_SMPLUS, "bn 7", "k0D0\r"},
689         {BTEST_SMPLUS, "bn 8", "k0H0\r"},
690         {BTEST_SMPLUS, "bn *", "k00A\r"},
691         {BTEST_SMPLUS, "bn A", "k00B\r"},
692         {BTEST_SMPLUS, "bn B", "k00D\r"},
693
694         {BTEST_SB5000FLX, "bn 1", "kA00\r"},
695         {BTEST_SB5000FLX, "bn 2", "kB00\r"},
696         {BTEST_SB5000FLX, "bn 3", "kD00\r"},
697         {BTEST_SB5000FLX, "bn 4", "kH00\r"},
698         {BTEST_SB5000FLX, "bn 5", "k0A0\r"},
699         {BTEST_SB5000FLX, "bn 6", "k0B0\r"},
700         {BTEST_SB5000FLX, "bn 7", "k0D0\r"},
701         {BTEST_SB5000FLX, "bn 8", "k0H0\r"},
702         {BTEST_SB5000FLX, "bn 9", "k00A\r"},
703         {BTEST_SB5000FLX, "bn A", "k00B\r"},
704         {BTEST_SB5000FLX, "bn B", "k00D\r"},
705         {BTEST_SB5000FLX, "bn C", "k00H\r"},
706
707         {BTEST_SPEXP, "bn 1", "kA000\r"},
708         {BTEST_SPEXP, "bn 2", "kB000\r"},
709         {BTEST_SPEXP, "bn T", "kD000\r"},
710         {BTEST_SPEXP, "bn L", "kH000\r"},
711         {BTEST_SPEXP, "bn R", "k0A00\r"},
712         {BTEST_SPEXP, "bn F", "k0B00\r"},
713         {BTEST_SPEXP, " ALT", "k0D00\r"},
714         {BTEST_SPEXP, " ESC", "k0H00\r"},
715         {BTEST_SPEXP, "SHFT", "k00A0\r"},
716         {BTEST_SPEXP, "CTRL", "k00B0\r"},
717         {BTEST_SPEXP, " fit", "k00D0\r"},
718         {BTEST_SPEXP, "panl", "k00H0\r"},
719         {BTEST_SPEXP, "bn +", "k000A\r"},
720         {BTEST_SPEXP, "bn -", "k000B\r"},
721         {BTEST_SPEXP, "  2D", "k000D\r"},
722
723         {-1, 0, 0}
724 };
725
726 void sball_button_test(void)
727 {
728         char pkt[16];
729         int dev = -1;
730         int (*parse)(struct sball*, int, char*, int);
731         struct btest_data *td = btest_data;
732         struct sball sb = {0};
733
734
735         while(td->device >= 0) {
736                 if(td->device != dev) {
737                         dev = td->device;
738                         sb.keymask = dev == BTEST_SB3003C ? 3 : 0xffff;
739                         parse = (dev < BTEST_SMCLASSIC) ? sball_parsepkt : mag_parsepkt;
740                         printf("\n\nTesting button packets for: %s", btest_models[dev]);
741                 }
742
743                 strcpy(pkt, td->pkt + 1);
744                 sb.flags = dev == BTEST_SB4000FLX ? SB4000 : 0;
745
746                 printf("\n  %s: ", td->label);
747                 parse(&sb, td->pkt[0], pkt, strlen(pkt) - 1);
748                 td++;
749         }
750         putchar('\n');
751 }