Clarify why check for XInput
[freeglut] / src / x11 / fg_spaceball_x11.c
1 /* Spaceball support for Linux.
2  * Written by John Tsiombikas <nuclear@member.fsf.org>
3  * Copied for Platform code by Evan Felix <karcaw at gmail.com>
4  * Creation date: Thur Feb 2 2012
5  *
6  * This code supports 3Dconnexion's 6-dof space-whatever devices.
7  * It can communicate with either the proprietary 3Dconnexion daemon (3dxsrv)
8  * free spacenavd (http://spacenav.sourceforge.net), through the "standard"
9  * magellan X-based protocol.
10  */
11
12 #include <GL/freeglut.h>
13 #include "../fg_internal.h"
14
15 #include <X11/Xlib.h>
16
17 extern int sball_initialized;
18
19 enum {
20     SPNAV_EVENT_ANY,  /* used by spnav_remove_events() */
21     SPNAV_EVENT_MOTION,
22     SPNAV_EVENT_BUTTON  /* includes both press and release */
23 };
24
25 struct spnav_event_motion {
26     int type;
27     int x, y, z;
28     int rx, ry, rz;
29     unsigned int period;
30     int *data;
31 };
32
33 struct spnav_event_button {
34     int type;
35     int press;
36     int bnum;
37 };
38
39 typedef union spnav_event {
40     int type;
41     struct spnav_event_motion motion;
42     struct spnav_event_button button;
43 } spnav_event;
44
45
46 static int spnav_x11_open(Display *dpy, Window win);
47 static int spnav_x11_window(Window win);
48 static int spnav_x11_event(const XEvent *xev, spnav_event *event);
49 static int spnav_close(void);
50 static int spnav_fd(void);
51 static int spnav_remove_events(int type);
52
53 static SFG_Window *spnav_win;
54
55 void fgPlatformInitializeSpaceball(void)
56 {
57     Window w;
58
59     sball_initialized = 1;
60     if(!fgStructure.CurrentWindow)
61     {
62         sball_initialized = -1;
63         return;
64     }
65
66     w = fgStructure.CurrentWindow->Window.Handle;
67     if(spnav_x11_open(fgDisplay.pDisplay.Display, w) == -1)
68     {
69         sball_initialized = -1;
70         return;
71     }
72 }
73
74 void fgPlatformSpaceballClose(void) 
75 {
76     spnav_close();
77 }
78
79 int fgPlatformHasSpaceball(void) 
80 {
81     /* XXX this function should somehow query the driver if there's a device
82      * plugged in, as opposed to just checking if there's a driver to talk to.
83      */
84     return spnav_fd() == -1 ? 0 : 1;
85 }
86
87 int fgPlatformSpaceballNumButtons(void) {
88     return 2;
89 }
90
91 void fgPlatformSpaceballSetWindow(SFG_Window *window) 
92 {
93        if(spnav_win != window) {
94         spnav_x11_window(window->Window.Handle);
95         spnav_win = window;
96     }
97 }
98
99 int fgIsSpaceballXEvent(const XEvent *xev)
100 {
101     spnav_event sev;
102
103     if(spnav_win != fgStructure.CurrentWindow) {
104         /* this will also initialize spaceball if needed (first call) */
105         fgSpaceballSetWindow(fgStructure.CurrentWindow);
106     }
107
108     if(sball_initialized != 1) {
109         return 0;
110     }
111
112     return spnav_x11_event(xev, &sev);
113 }
114
115 void fgSpaceballHandleXEvent(const XEvent *xev)
116 {
117     spnav_event sev;
118
119     if(sball_initialized == 0) {
120         fgInitialiseSpaceball();
121         if(sball_initialized != 1) {
122             return;
123         }
124     }
125
126     if(spnav_x11_event(xev, &sev)) {
127         switch(sev.type) {
128         case SPNAV_EVENT_MOTION:
129             if(sev.motion.x | sev.motion.y | sev.motion.z) {
130                 INVOKE_WCB(*spnav_win, SpaceMotion, (sev.motion.x, sev.motion.y, sev.motion.z));
131             }
132             if(sev.motion.rx | sev.motion.ry | sev.motion.rz) {
133                 INVOKE_WCB(*spnav_win, SpaceRotation, (sev.motion.rx, sev.motion.ry, sev.motion.rz));
134             }
135             spnav_remove_events(SPNAV_EVENT_MOTION);
136             break;
137
138         case SPNAV_EVENT_BUTTON:
139             INVOKE_WCB(*spnav_win, SpaceButton, (sev.button.bnum, sev.button.press ? GLUT_DOWN : GLUT_UP));
140             break;
141
142         default:
143             break;
144         }
145     }
146 }
147
148 /*
149 The following code is part of libspnav, part of the spacenav project (spacenav.sf.net)
150 Copyright (C) 2007-2009 John Tsiombikas <nuclear@member.fsf.org>
151
152 Redistribution and use in source and binary forms, with or without
153 modification, are permitted provided that the following conditions are met:
154
155 1. Redistributions of source code must retain the above copyright notice, this
156    list of conditions and the following disclaimer.
157 2. Redistributions in binary form must reproduce the above copyright notice,
158    this list of conditions and the following disclaimer in the documentation
159    and/or other materials provided with the distribution.
160 3. The name of the author may not be used to endorse or promote products
161    derived from this software without specific prior written permission.
162
163 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
164 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
165 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
166 EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
167 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
168 OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
169 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
170 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
171 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
172 OF SUCH DAMAGE.
173 */
174 #include <stdio.h>
175 #include <stdlib.h>
176 #include <string.h>
177
178 #ifdef HAVE_ERRNO_H
179 #include <errno.h>
180 #endif
181
182 #include <X11/Xlib.h>
183 #include <X11/Xutil.h>
184
185 static Window get_daemon_window(Display *dpy);
186 static int catch_badwin(Display *dpy, XErrorEvent *err);
187
188 static Display *dpy;
189 static Window app_win;
190 static Atom motion_event, button_press_event, button_release_event, command_event;
191
192 enum {
193   CMD_APP_WINDOW = 27695,
194   CMD_APP_SENS
195 };
196
197 #define IS_OPEN    dpy
198
199 struct event_node {
200   spnav_event event;
201   struct event_node *next;
202 };
203
204 static int spnav_x11_open(Display *display, Window win)
205 {
206   if(IS_OPEN) {
207     return -1;
208   }
209
210   dpy = display;
211
212   motion_event = XInternAtom(dpy, "MotionEvent", True);
213   button_press_event = XInternAtom(dpy, "ButtonPressEvent", True);
214   button_release_event = XInternAtom(dpy, "ButtonReleaseEvent", True);
215   command_event = XInternAtom(dpy, "CommandEvent", True);
216
217   if(!motion_event || !button_press_event || !button_release_event || !command_event) {
218     dpy = 0;
219     return -1;  /* daemon not started */
220   }
221
222   if(spnav_x11_window(win) == -1) {
223     dpy = 0;
224     return -1;  /* daemon not started */
225   }
226
227   app_win = win;
228   return 0;
229 }
230
231 static int spnav_close(void)
232 {
233   if(dpy) {
234     spnav_x11_window(DefaultRootWindow(dpy));
235     app_win = 0;
236     dpy = 0;
237     return 0;
238   }
239   return -1;
240 }
241
242 static int spnav_x11_window(Window win)
243 {
244   int (*prev_xerr_handler)(Display*, XErrorEvent*);
245   XEvent xev;
246   Window daemon_win;
247
248   if(!IS_OPEN) {
249     return -1;
250   }
251
252   if(!(daemon_win = get_daemon_window(dpy))) {
253     return -1;
254   }
255
256   prev_xerr_handler = XSetErrorHandler(catch_badwin);
257
258   xev.type = ClientMessage;
259   xev.xclient.send_event = False;
260   xev.xclient.display = dpy;
261   xev.xclient.window = win;
262   xev.xclient.message_type = command_event;
263   xev.xclient.format = 16;
264   xev.xclient.data.s[0] = ((unsigned int)win & 0xffff0000) >> 16;
265   xev.xclient.data.s[1] = (unsigned int)win & 0xffff;
266   xev.xclient.data.s[2] = CMD_APP_WINDOW;
267
268   XSendEvent(dpy, daemon_win, False, 0, &xev);
269   XSync(dpy, False);
270
271   XSetErrorHandler(prev_xerr_handler);
272   return 0;
273 }
274
275 static int spnav_fd(void)
276 {
277   if(dpy) {
278     return ConnectionNumber(dpy);
279   }
280   return -1;
281 }
282
283 /*static int spnav_wait_event(spnav_event *event)
284 {
285   if(dpy) {
286     for(;;) {
287       XEvent xev;
288       XNextEvent(dpy, &xev);
289
290       if(spnav_x11_event(&xev, event) > 0) {
291         return event->type;
292       }
293     }
294   }
295   return 0;
296 }
297
298 static int spnav_poll_event(spnav_event *event)
299 {
300   if(dpy) {
301     if(XPending(dpy)) {
302       XEvent xev;
303       XNextEvent(dpy, &xev);
304
305       return spnav_x11_event(&xev, event);
306     }
307   }
308   return 0;
309 }*/
310
311 static Bool match_events(Display *dpy, XEvent *xev, char *arg)
312 {
313   int evtype = *(int*)arg;
314
315   if(xev->type != ClientMessage) {
316     return False;
317   }
318
319   if(xev->xclient.message_type == motion_event) {
320     return !evtype || evtype == SPNAV_EVENT_MOTION ? True : False;
321   }
322   if(xev->xclient.message_type == button_press_event ||
323       xev->xclient.message_type == button_release_event) {
324     return !evtype || evtype == SPNAV_EVENT_BUTTON ? True : False;
325   }
326   return False;
327 }
328
329 static int spnav_remove_events(int type)
330 {
331   int rm_count = 0;
332
333   if(dpy) {
334     XEvent xev;
335
336     while(XCheckIfEvent(dpy, &xev, match_events, (char*)&type)) {
337       rm_count++;
338     }
339     return rm_count;
340   }
341   return 0;
342 }
343
344 static int spnav_x11_event(const XEvent *xev, spnav_event *event)
345 {
346   int i;
347   int xmsg_type;
348
349   if(xev->type != ClientMessage) {
350     return 0;
351   }
352
353   xmsg_type = xev->xclient.message_type;
354
355   if(xmsg_type != motion_event && xmsg_type != button_press_event &&
356       xmsg_type != button_release_event) {
357     return 0;
358   }
359
360   if(xmsg_type == motion_event) {
361     event->type = SPNAV_EVENT_MOTION;
362     event->motion.data = &event->motion.x;
363
364     for(i=0; i<6; i++) {
365       event->motion.data[i] = xev->xclient.data.s[i + 2];
366     }
367     event->motion.period = xev->xclient.data.s[8];
368   } else {
369     event->type = SPNAV_EVENT_BUTTON;
370     event->button.press = xmsg_type == button_press_event ? 1 : 0;
371     event->button.bnum = xev->xclient.data.s[2];
372   }
373   return event->type;
374 }
375
376
377 static Window get_daemon_window(Display *dpy)
378 {
379   Window win, root_win;
380   XTextProperty wname;
381   Atom type;
382   int fmt;
383   unsigned long nitems, bytes_after;
384   unsigned char *prop;
385
386   root_win = DefaultRootWindow(dpy);
387
388   XGetWindowProperty(dpy, root_win, command_event, 0, 1, False, AnyPropertyType, &type, &fmt, &nitems, &bytes_after, &prop);
389   if(!prop) {
390     return 0;
391   }
392
393   win = *(Window*)prop;
394   XFree(prop);
395
396   if(!XGetWMName(dpy, win, &wname) || strcmp("Magellan Window", (char*)wname.value) != 0) {
397     return 0;
398   }
399
400   return win;
401 }
402
403 static int catch_badwin(Display *dpy, XErrorEvent *err)
404 {
405   char buf[256];
406
407   if(err->error_code == BadWindow) {
408     /* do nothing? */
409   } else {
410     XGetErrorText(dpy, err->error_code, buf, sizeof buf);
411     fprintf(stderr, "Caught unexpected X error: %s\n", buf);
412   }
413   return 0;
414 }
415