Fixing a 'gamemode at_exit' bug courtesy of Diederick Niehorster per e-mail dated...
[freeglut] / src / freeglut_input_devices.c
1 /*
2  * freeglut_input_devices.c
3  *
4  * Handles miscellaneous input devices via direct serial-port access.
5  * Proper X11 XInput device support is not yet supported.
6  * Also lacks Mac support.
7  *
8  * Written by Joe Krahn <krahn@niehs.nih.gov> 2005
9  *
10  * Copyright (c) 2005 Stephen J. Baker. All Rights Reserved.
11  *
12  * Permission is hereby granted, free of charge, to any person obtaining a
13  * copy of this software and associated documentation files (the "Software"),
14  * to deal in the Software without restriction, including without limitation
15  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16  * and/or sell copies of the Software, and to permit persons to whom the
17  * Software is furnished to do so, subject to the following conditions:
18  *
19  * The above copyright notice and this permission notice shall be included
20  * in all copies or substantial portions of the Software.
21  *
22  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
23  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
25  * PAWEL W. OLSZTA OR STEPHEN J. BAKER BE LIABLE FOR ANY CLAIM, DAMAGES OR
26  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
27  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28  * DEALINGS IN THE SOFTWARE.
29  */
30
31 #ifdef HAVE_CONFIG_H
32 #    include "config.h"
33 #endif
34
35 #include <GL/freeglut.h>
36 #include "freeglut_internal.h"
37
38 #if TARGET_HOST_POSIX_X11
39 #ifdef HAVE_ERRNO_H
40 #include <errno.h>
41 #endif
42 #include <sys/ioctl.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <termios.h>
47 #include <fcntl.h>
48
49 typedef struct {
50    int fd;
51    struct termios termio, termio_save;
52 } SERIALPORT;
53
54 #elif TARGET_HOST_MS_WINDOWS
55 #include <sys/types.h>
56 #include <winbase.h>
57 typedef struct {
58    HANDLE fh;
59    COMMTIMEOUTS timeouts_save;
60    DCB dcb_save;
61 } SERIALPORT;
62
63 #endif
64
65 /********************* Dialbox definitions ***********************/
66
67 #define DIAL_NUM_VALUATORS 8
68
69 /* dial parser state machine states */
70 #define DIAL_NEW                (-1)
71 #define DIAL_WHICH_DEVICE       0
72 #define DIAL_VALUE_HIGH         1
73 #define DIAL_VALUE_LOW          2
74
75 /* dial/button box commands */
76 #define DIAL_INITIALIZE                 0x20
77 #define DIAL_SET_LEDS                   0x75
78 #define DIAL_SET_TEXT                   0x61
79 #define DIAL_SET_AUTO_DIALS             0x50
80 #define DIAL_SET_AUTO_DELTA_DIALS       0x51
81 #define DIAL_SET_FILTER                 0x53
82 #define DIAL_SET_BUTTONS_MOM_TYPE       0x71
83 #define DIAL_SET_AUTO_MOM_BUTTONS       0x73
84 #define DIAL_SET_ALL_LEDS               0x4b
85 #define DIAL_CLEAR_ALL_LEDS             0x4c
86
87 /* dial/button box replies and events */
88 #define DIAL_INITIALIZED        0x20
89 #define DIAL_BASE               0x30
90 #define DIAL_DELTA_BASE         0x40
91 #define DIAL_PRESS_BASE         0xc0
92 #define DIAL_RELEASE_BASE       0xe0
93
94 /* macros to determine reply type */
95 #define IS_DIAL_EVENT(ch)       (((ch)>=DIAL_BASE)&&((ch)<DIAL_BASE+DIAL_NUM_VALUATORS))
96 #define IS_KEY_PRESS(ch)        (((ch)>=DIAL_PRESS_BASE)&&((ch)<DIAL_PRESS_BASE+DIAL_NUM_BUTTONS))
97 #define IS_KEY_RELEASE(ch)      (((ch)>=DIAL_RELEASE_BASE)&&((ch)<DIAL_RELEASE_BASE+DIAL_NUM_BUTTONS))
98 #define IS_INIT_EVENT(ch)       ((ch)==DIAL_INITIALIZED)
99
100 /*****************************************************************/
101
102 static SERIALPORT *serial_open ( const char *device );
103 static void serial_close ( SERIALPORT *port );
104 static int serial_getchar ( SERIALPORT *port );
105 static int serial_putchar ( SERIALPORT *port, unsigned char ch );
106 static void serial_flush ( SERIALPORT *port );
107
108 static void send_dial_event(int dial, int value);
109 static void poll_dials(int id);
110
111 /* local variables */
112 static SERIALPORT *dialbox_port=NULL;
113
114 /*****************************************************************/
115
116 /*
117  * Implementation for glutDeviceGet(GLUT_HAS_DIAL_AND_BUTTON_BOX)
118  */
119 int fgInputDeviceDetect( void )
120 {
121     fgInitialiseInputDevices ();
122
123     if ( !dialbox_port )
124         return 0;
125
126     if ( !fgState.InputDevsInitialised )
127         return 0;
128
129     return 1;
130 }
131
132 /*
133  * Try initializing the input device(s)
134  */
135 void fgInitialiseInputDevices ( void )
136 {
137     if( !fgState.InputDevsInitialised )
138     {
139         const char *dial_device=NULL;
140         dial_device = getenv ( "GLUT_DIALS_SERIAL" );
141 #if TARGET_HOST_MS_WINDOWS
142         if (!dial_device){
143             static char devname[256];
144             DWORD size=sizeof(devname);
145             DWORD type = REG_SZ;
146             HKEY key;
147             if (RegOpenKeyA(HKEY_LOCAL_MACHINE,"SOFTWARE\\FreeGLUT",&key)==ERROR_SUCCESS) {
148                 if (RegQueryValueExA(key,"DialboxSerialPort",NULL,&type,(LPBYTE)devname,&size)==ERROR_SUCCESS){
149                     dial_device=devname;
150                 }
151                 RegCloseKey(key);
152             }
153         }
154 #endif
155         if ( !dial_device ) return;
156         if ( !( dialbox_port = serial_open ( dial_device ) ) ) return;
157         serial_putchar(dialbox_port,DIAL_INITIALIZE);
158         glutTimerFunc ( 10, poll_dials, 0 );
159         fgState.InputDevsInitialised = GL_TRUE;
160     }
161 }
162
163 /*
164  *
165  */
166 void fgInputDeviceClose( void )
167 {
168     if ( fgState.InputDevsInitialised )
169     {
170         serial_close ( dialbox_port );
171         dialbox_port = NULL;
172         fgState.InputDevsInitialised = GL_FALSE;
173     }
174 }
175
176 /********************************************************************/
177
178 /* Check all windows for dialbox callbacks */
179 static void fghcbEnumDialCallbacks ( SFG_Window *window, SFG_Enumerator *enumerator )
180 {
181     /* Built-in to INVOKE_WCB():  if window->Callbacks[CB_Dials] */
182     INVOKE_WCB ( *window,Dials, ( ((int*)enumerator->data)[0], ((int*)enumerator->data)[1]) );
183     fgEnumSubWindows ( window, fghcbEnumDialCallbacks, enumerator );
184 }
185
186 static void send_dial_event ( int num, int value )
187 {
188     SFG_Enumerator enumerator;
189     int data[2];
190     data[0] = num;
191     data[1] = value;
192     enumerator.found = GL_FALSE;
193     enumerator.data  =  data;
194     fgEnumWindows ( fghcbEnumDialCallbacks, &enumerator );
195 }
196
197 /********************************************************************/
198 static void poll_dials ( int id )
199 {
200     int data;
201     static int dial_state = DIAL_NEW;
202     static int dial_which;
203     static int dial_value;
204     static int dials[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
205
206     if ( !dialbox_port ) return;
207
208     while ( (data=serial_getchar(dialbox_port)) != EOF )
209     {
210         if ( ( dial_state > DIAL_WHICH_DEVICE ) || IS_DIAL_EVENT ( data ) )
211         {
212             switch ( dial_state )
213             {
214             case DIAL_WHICH_DEVICE:
215                 dial_which = data - DIAL_BASE;
216                 dial_state++;
217                 break;
218             case DIAL_VALUE_HIGH:
219                 dial_value = ( data << 8 );
220                 dial_state++;
221                 break;
222             case DIAL_VALUE_LOW:
223                 dial_value |= data;
224                 if ( dial_value & 0x8000 ) dial_value -= 0x10000;
225                 dials[dial_which] = dial_value;
226                 send_dial_event ( dial_which + 1, dial_value * 360 / 256 );
227                 dial_state = DIAL_WHICH_DEVICE;
228                 break;
229             default:
230                 /* error: Impossible state value! */
231                 break;
232             }
233         }
234         else if ( data == DIAL_INITIALIZED )
235         {
236             fgState.InputDevsInitialised = GL_TRUE;
237             dial_state = DIAL_WHICH_DEVICE;
238             serial_putchar(dialbox_port,DIAL_SET_AUTO_DIALS);
239             serial_putchar(dialbox_port,0xff);
240             serial_putchar(dialbox_port,0xff);
241         }
242         else  /* Unknown data; try flushing. */
243             serial_flush(dialbox_port);
244     }
245
246     glutTimerFunc ( 2, poll_dials, 0 );
247 }
248
249
250 /******** OS Specific Serial I/O routines *******/
251 #if TARGET_HOST_POSIX_X11 /* ==> Linux/BSD/UNIX POSIX serial I/O */
252 static SERIALPORT *serial_open ( const char *device )
253 {
254     int fd;
255     struct termios termio;
256     SERIALPORT *port;
257
258     fd = open(device, O_RDWR | O_NONBLOCK );
259     if (fd <0) {
260         perror(device);
261         return NULL;
262     }
263
264     port = malloc(sizeof(SERIALPORT));
265     memset(port, 0, sizeof(SERIALPORT));
266     port->fd = fd;
267
268     /* save current port settings */
269     tcgetattr(fd,&port->termio_save);
270
271     memset(&termio, 0, sizeof(termio));
272     termio.c_cflag = CS8 | CREAD | HUPCL ;
273     termio.c_iflag = IGNPAR | IGNBRK ;
274     termio.c_cc[VTIME]    = 0;   /* inter-character timer */
275     termio.c_cc[VMIN]     = 1;   /* block read until 1 chars received, when blocking I/O */
276
277     cfsetispeed(&termio, B9600);
278     cfsetospeed(&termio, B9600);
279     tcsetattr(fd,TCSANOW,&termio);
280
281     serial_flush(port);
282     return port;
283 }
284
285 static void serial_close(SERIALPORT *port)
286 {
287     if (port)
288     {
289         /* restore old port settings */
290         tcsetattr(port->fd,TCSANOW,&port->termio_save);
291         close(port->fd);
292         free(port);
293     }
294 }
295
296 static int serial_getchar(SERIALPORT *port)
297 {
298     unsigned char ch;
299     if (!port) return EOF;
300     if (read(port->fd,&ch,1)) return ch;
301     return EOF;
302 }
303
304 static int serial_putchar(SERIALPORT *port, unsigned char ch){
305     if (!port) return 0;
306     return write(port->fd,&ch,1);
307 }
308
309 static void serial_flush ( SERIALPORT *port )
310 {
311     tcflush ( port->fd, TCIOFLUSH );
312 }
313
314 #elif TARGET_HOST_MS_WINDOWS
315
316 static SERIALPORT *serial_open(const char *device){
317     HANDLE fh;
318     DCB dcb={sizeof(DCB)};
319     COMMTIMEOUTS timeouts;
320     SERIALPORT *port;
321
322     fh = CreateFile(device,GENERIC_READ|GENERIC_WRITE,0,NULL,
323       OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
324     if (!fh) return NULL;
325
326     port = malloc(sizeof(SERIALPORT));
327     ZeroMemory(port, sizeof(SERIALPORT));
328     port->fh = fh;
329
330     /* save current port settings */
331     GetCommState(fh,&port->dcb_save);
332     GetCommTimeouts(fh,&port->timeouts_save);
333
334     dcb.DCBlength=sizeof(DCB);
335     BuildCommDCB("96,n,8,1",&dcb);
336     SetCommState(fh,&dcb);
337
338     ZeroMemory(&timeouts,sizeof(timeouts));
339     timeouts.ReadTotalTimeoutConstant=1;
340     timeouts.WriteTotalTimeoutConstant=1;
341     SetCommTimeouts(fh,&timeouts);
342
343     serial_flush(port);
344
345     return port;
346 }
347
348 static void serial_close(SERIALPORT *port){
349     if (port){
350         /* restore old port settings */
351         SetCommState(port->fh,&port->dcb_save);
352         SetCommTimeouts(port->fh,&port->timeouts_save);
353         CloseHandle(port->fh);
354         free(port);
355     }
356 }
357
358 static int serial_getchar(SERIALPORT *port){
359     DWORD n;
360     unsigned char ch;
361     if (!port) return EOF;
362     if (!ReadFile(port->fh,&ch,1,&n,NULL)) return EOF;
363     if (n==1) return ch;
364     return EOF;
365 }
366
367 static int serial_putchar(SERIALPORT *port, unsigned char ch){
368     DWORD n;
369     if (!port) return 0;
370     return WriteFile(port->fh,&ch,1,&n,NULL);
371 }
372
373 static void serial_flush ( SERIALPORT *port )
374 {
375     FlushFileBuffers(port->fh);
376 }
377
378 #endif