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