Putting in the "glutFullScreen" support for Gnome and other X window managers per...
[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 #if HAVE_ERRNO
40 #include <errno.h>
41 #endif
42 #include <sys/ioctl.h>
43 #include <sys/time.h>
44 #include <time.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <unistd.h>
48 #include <string.h>
49 #include <termios.h>
50 #include <fcntl.h>
51 #include <sys/types.h>
52
53 typedef struct {
54    int fd;
55    struct termios termio, termio_save;
56 } SERIALPORT;
57
58 #elif TARGET_HOST_MS_WINDOWS
59 #include <sys/types.h>
60 #include <winbase.h>
61 typedef struct {
62    HANDLE fh;
63    COMMTIMEOUTS timeouts_save;
64    DCB dcb_save;
65 } SERIALPORT;
66
67 #endif
68
69 /********************* Dialbox definitions ***********************/
70
71 #define DIAL_NUM_VALUATORS 8
72
73 /* dial parser state machine states */
74 #define DIAL_NEW                (-1)
75 #define DIAL_WHICH_DEVICE       0
76 #define DIAL_VALUE_HIGH         1
77 #define DIAL_VALUE_LOW          2
78
79 /* dial/button box commands */
80 #define DIAL_INITIALIZE                 0x20
81 #define DIAL_SET_LEDS                   0x75
82 #define DIAL_SET_TEXT                   0x61
83 #define DIAL_SET_AUTO_DIALS             0x50
84 #define DIAL_SET_AUTO_DELTA_DIALS       0x51
85 #define DIAL_SET_FILTER                 0x53
86 #define DIAL_SET_BUTTONS_MOM_TYPE       0x71
87 #define DIAL_SET_AUTO_MOM_BUTTONS       0x73
88 #define DIAL_SET_ALL_LEDS               0x4b
89 #define DIAL_CLEAR_ALL_LEDS             0x4c
90
91 /* dial/button box replies and events */
92 #define DIAL_INITIALIZED        0x20
93 #define DIAL_BASE               0x30
94 #define DIAL_DELTA_BASE         0x40
95 #define DIAL_PRESS_BASE         0xc0
96 #define DIAL_RELEASE_BASE       0xe0
97
98 /* macros to determine reply type */
99 #define IS_DIAL_EVENT(ch)       (((ch)>=DIAL_BASE)&&((ch)<DIAL_BASE+DIAL_NUM_VALUATORS))
100 #define IS_KEY_PRESS(ch)        (((ch)>=DIAL_PRESS_BASE)&&((ch)<DIAL_PRESS_BASE+DIAL_NUM_BUTTONS))
101 #define IS_KEY_RELEASE(ch)      (((ch)>=DIAL_RELEASE_BASE)&&((ch)<DIAL_RELEASE_BASE+DIAL_NUM_BUTTONS))
102 #define IS_INIT_EVENT(ch)       ((ch)==DIAL_INITIALIZED)
103
104 /*****************************************************************/
105
106 static SERIALPORT *serial_open ( const char *device );
107 static void serial_close ( SERIALPORT *port );
108 static int serial_getchar ( SERIALPORT *port );
109 static int serial_putchar ( SERIALPORT *port, unsigned char ch );
110 static void serial_flush ( SERIALPORT *port );
111
112 static void send_dial_event(int dial, int value);
113 static void poll_dials(int id);
114
115 /* local variables */
116 static SERIALPORT *dialbox_port=NULL;
117
118 /*****************************************************************/
119
120 /*
121  * Implementation for glutDeviceGet(GLUT_HAS_DIAL_AND_BUTTON_BOX)
122  */
123 int fgInputDeviceDetect( void )
124 {
125     fgInitialiseInputDevices ();
126
127     if ( !dialbox_port )
128         return 0;
129
130     if ( !fgState.InputDevsInitialised )
131         return 0;
132
133     return 1;
134 }
135
136 /*
137  * Try initializing the input device(s)
138  */
139 void fgInitialiseInputDevices ( void )
140 {
141     if( !fgState.InputDevsInitialised )
142     {
143       /* will return true for VC8 (VC2005) and higher */
144 #if TARGET_HOST_MS_WINDOWS && ( _MSC_VER >= 1400 ) && HAVE_ERRNO
145         char *dial_device=NULL;
146         size_t sLen;
147         errno_t err = _dupenv_s( &dial_device, &sLen, "GLUT_DIALS_SERIAL" );
148         if (err)
149             fgError("Error getting GLUT_DIALS_SERIAL environment variable");
150 #else
151         const char *dial_device=NULL;
152         dial_device = getenv ( "GLUT_DIALS_SERIAL" );
153 #endif
154 #if TARGET_HOST_MS_WINDOWS
155         if (!dial_device){
156             static char devname[256];
157             DWORD size=sizeof(devname);
158             DWORD type = REG_SZ;
159             HKEY key;
160             if (RegOpenKeyA(HKEY_LOCAL_MACHINE,"SOFTWARE\\FreeGLUT",&key)==ERROR_SUCCESS) {
161                 if (RegQueryValueExA(key,"DialboxSerialPort",NULL,&type,(LPBYTE)devname,&size)==ERROR_SUCCESS){
162                     dial_device=devname;
163                 }
164                 RegCloseKey(key);
165             }
166         }
167 #endif
168         if ( !dial_device ) return;
169         if ( !( dialbox_port = serial_open ( dial_device ) ) ) return;
170       /* will return true for VC8 (VC2005) and higher */
171 #if TARGET_HOST_MS_WINDOWS && ( _MSC_VER >= 1400 ) && HAVE_ERRNO
172         free ( dial_device );  dial_device = NULL;  /* dupenv_s allocates a string that we must free */
173 #endif
174         serial_putchar(dialbox_port,DIAL_INITIALIZE);
175         glutTimerFunc ( 10, poll_dials, 0 );
176         fgState.InputDevsInitialised = GL_TRUE;
177     }
178 }
179
180 /*
181  *
182  */
183 void fgInputDeviceClose( void )
184 {
185     if ( fgState.InputDevsInitialised )
186     {
187         serial_close ( dialbox_port );
188         dialbox_port = NULL;
189         fgState.InputDevsInitialised = GL_FALSE;
190     }
191 }
192
193 /********************************************************************/
194
195 /* Check all windows for dialbox callbacks */
196 static void fghcbEnumDialCallbacks ( SFG_Window *window, SFG_Enumerator *enumerator )
197 {
198     /* Built-in to INVOKE_WCB():  if window->Callbacks[CB_Dials] */
199     INVOKE_WCB ( *window,Dials, ( ((int*)enumerator->data)[0], ((int*)enumerator->data)[1]) );
200     fgEnumSubWindows ( window, fghcbEnumDialCallbacks, enumerator );
201 }
202
203 static void send_dial_event ( int num, int value )
204 {
205     SFG_Enumerator enumerator;
206     int data[2];
207     data[0] = num;
208     data[1] = value;
209     enumerator.found = GL_FALSE;
210     enumerator.data  =  data;
211     fgEnumWindows ( fghcbEnumDialCallbacks, &enumerator );
212 }
213
214 /********************************************************************/
215 static void poll_dials ( int id )
216 {
217     int data;
218     static int dial_state = DIAL_NEW;
219     static int dial_which;
220     static int dial_value;
221     static int dials[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
222
223     if ( !dialbox_port ) return;
224
225     while ( (data=serial_getchar(dialbox_port)) != EOF )
226     {
227         if ( ( dial_state > DIAL_WHICH_DEVICE ) || IS_DIAL_EVENT ( data ) )
228         {
229             switch ( dial_state )
230             {
231             case DIAL_WHICH_DEVICE:
232                 dial_which = data - DIAL_BASE;
233                 dial_state++;
234                 break;
235             case DIAL_VALUE_HIGH:
236                 dial_value = ( data << 8 );
237                 dial_state++;
238                 break;
239             case DIAL_VALUE_LOW:
240                 dial_value |= data;
241                 if ( dial_value & 0x8000 ) dial_value -= 0x10000;
242                 dials[dial_which] = dial_value;
243                 send_dial_event ( dial_which + 1, dial_value * 360 / 256 );
244                 dial_state = DIAL_WHICH_DEVICE;
245                 break;
246             default:
247                 /* error: Impossible state value! */
248                 break;
249             }
250         }
251         else if ( data == DIAL_INITIALIZED )
252         {
253             fgState.InputDevsInitialised = GL_TRUE;
254             dial_state = DIAL_WHICH_DEVICE;
255             serial_putchar(dialbox_port,DIAL_SET_AUTO_DIALS);
256             serial_putchar(dialbox_port,0xff);
257             serial_putchar(dialbox_port,0xff);
258         }
259         else  /* Unknown data; try flushing. */
260             serial_flush(dialbox_port);
261     }
262
263     glutTimerFunc ( 2, poll_dials, 0 );
264 }
265
266
267 /******** OS Specific Serial I/O routines *******/
268 #if TARGET_HOST_POSIX_X11 /* ==> Linux/BSD/UNIX POSIX serial I/O */
269 static SERIALPORT *serial_open ( const char *device )
270 {
271     int fd;
272     struct termios termio;
273     SERIALPORT *port;
274
275     fd = open(device, O_RDWR | O_NONBLOCK );
276     if (fd <0) {
277         perror(device);
278         return NULL;
279     }
280
281     port = malloc(sizeof(SERIALPORT));
282     memset(port, 0, sizeof(SERIALPORT));
283     port->fd = fd;
284
285     /* save current port settings */
286     tcgetattr(fd,&port->termio_save);
287
288     memset(&termio, 0, sizeof(termio));
289     termio.c_cflag = CS8 | CREAD | HUPCL ;
290     termio.c_iflag = IGNPAR | IGNBRK ;
291     termio.c_cc[VTIME]    = 0;   /* inter-character timer */
292     termio.c_cc[VMIN]     = 1;   /* block read until 1 chars received, when blocking I/O */
293
294     cfsetispeed(&termio, B9600);
295     cfsetospeed(&termio, B9600);
296     tcsetattr(fd,TCSANOW,&termio);
297
298     serial_flush(port);
299     return port;
300 }
301
302 static void serial_close(SERIALPORT *port)
303 {
304     if (port)
305     {
306         /* restore old port settings */
307         tcsetattr(port->fd,TCSANOW,&port->termio_save);
308         close(port->fd);
309         free(port);
310     }
311 }
312
313 static int serial_getchar(SERIALPORT *port)
314 {
315     unsigned char ch;
316     if (!port) return EOF;
317     if (read(port->fd,&ch,1)) return ch;
318     return EOF;
319 }
320
321 static int serial_putchar(SERIALPORT *port, unsigned char ch){
322     if (!port) return 0;
323     return write(port->fd,&ch,1);
324 }
325
326 static void serial_flush ( SERIALPORT *port )
327 {
328     tcflush ( port->fd, TCIOFLUSH );
329 }
330
331 #elif TARGET_HOST_MS_WINDOWS
332
333 static SERIALPORT *serial_open(const char *device){
334     HANDLE fh;
335     DCB dcb={sizeof(DCB)};
336     COMMTIMEOUTS timeouts;
337     SERIALPORT *port;
338
339     fh = CreateFile(device,GENERIC_READ|GENERIC_WRITE,0,NULL,
340       OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
341     if (!fh) return NULL;
342
343     port = malloc(sizeof(SERIALPORT));
344     ZeroMemory(port, sizeof(SERIALPORT));
345     port->fh = fh;
346
347     /* save current port settings */
348     GetCommState(fh,&port->dcb_save);
349     GetCommTimeouts(fh,&port->timeouts_save);
350
351     dcb.DCBlength=sizeof(DCB);
352     BuildCommDCB("96,n,8,1",&dcb);
353     SetCommState(fh,&dcb);
354
355     ZeroMemory(&timeouts,sizeof(timeouts));
356     timeouts.ReadTotalTimeoutConstant=1;
357     timeouts.WriteTotalTimeoutConstant=1;
358     SetCommTimeouts(fh,&timeouts);
359
360     serial_flush(port);
361
362     return port;
363 }
364
365 static void serial_close(SERIALPORT *port){
366     if (port){
367         /* restore old port settings */
368         SetCommState(port->fh,&port->dcb_save);
369         SetCommTimeouts(port->fh,&port->timeouts_save);
370         CloseHandle(port->fh);
371         free(port);
372     }
373 }
374
375 static int serial_getchar(SERIALPORT *port){
376     DWORD n;
377     unsigned char ch;
378     if (!port) return EOF;
379     if (!ReadFile(port->fh,&ch,1,&n,NULL)) return EOF;
380     if (n==1) return ch;
381     return EOF;
382 }
383
384 static int serial_putchar(SERIALPORT *port, unsigned char ch){
385     DWORD n;
386     if (!port) return 0;
387     return WriteFile(port->fh,&ch,1,&n,NULL);
388 }
389
390 static void serial_flush ( SERIALPORT *port )
391 {
392     FlushFileBuffers(port->fh);
393 }
394
395 #endif