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