Uploading spaceball/set-window patch per e-mail from John Tsiombikas dated 1:34 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 #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(spnav_win != fgStructure.CurrentWindow) {\r
147         /* this will also initialize spaceball if needed (first call) */\r
148         fgSpaceballSetWindow(fgStructure.CurrentWindow);\r
149     }\r
150 \r
151     if(!sball_initialized) {\r
152         return 0;\r
153     }\r
154 \r
155     return spnav_x11_event(xev, &sev);\r
156 }\r
157 \r
158 void fgSpaceballHandleXEvent(const XEvent *xev)\r
159 {\r
160     spnav_event sev;\r
161 \r
162     if(!sball_initialized) {\r
163         fgInitialiseSpaceball();\r
164         if(!sball_initialized) {\r
165             return;\r
166         }\r
167     }\r
168 \r
169     if(spnav_x11_event(xev, &sev)) {\r
170         switch(sev.type) {\r
171         case SPNAV_EVENT_MOTION:\r
172             if(sev.motion.x | sev.motion.y | sev.motion.z) {\r
173                 INVOKE_WCB(*spnav_win, SpaceMotion, (sev.motion.x, sev.motion.y, sev.motion.z));\r
174             }\r
175             if(sev.motion.rx | sev.motion.ry | sev.motion.rz) {\r
176                 INVOKE_WCB(*spnav_win, SpaceRotation, (sev.motion.rx, sev.motion.ry, sev.motion.rz));\r
177             }\r
178             spnav_remove_events(SPNAV_EVENT_MOTION);\r
179             break;\r
180 \r
181         case SPNAV_EVENT_BUTTON:\r
182             INVOKE_WCB(*spnav_win, SpaceButton, (sev.button.bnum, sev.button.press ? GLUT_DOWN : GLUT_UP));\r
183             break;\r
184 \r
185         default:\r
186             break;\r
187         }\r
188     }\r
189 }\r
190 \r
191 /*\r
192 The following code is part of libspnav, part of the spacenav project (spacenav.sf.net)\r
193 Copyright (C) 2007-2009 John Tsiombikas <nuclear@member.fsf.org>\r
194 \r
195 Redistribution and use in source and binary forms, with or without\r
196 modification, are permitted provided that the following conditions are met:\r
197 \r
198 1. Redistributions of source code must retain the above copyright notice, this\r
199    list of conditions and the following disclaimer.\r
200 2. Redistributions in binary form must reproduce the above copyright notice,\r
201    this list of conditions and the following disclaimer in the documentation\r
202    and/or other materials provided with the distribution.\r
203 3. The name of the author may not be used to endorse or promote products\r
204    derived from this software without specific prior written permission.\r
205 \r
206 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED\r
207 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\r
208 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO\r
209 EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
210 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT\r
211 OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
212 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r
213 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING\r
214 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY\r
215 OF SUCH DAMAGE.\r
216 */\r
217 #include <stdio.h>\r
218 #include <stdlib.h>\r
219 #include <string.h>\r
220 \r
221 #if HAVE_ERRNO_H\r
222 #include <errno.h>\r
223 #endif\r
224 \r
225 #include <X11/Xlib.h>\r
226 #include <X11/Xutil.h>\r
227 \r
228 static Window get_daemon_window(Display *dpy);\r
229 static int catch_badwin(Display *dpy, XErrorEvent *err);\r
230 \r
231 static Display *dpy;\r
232 static Window app_win;\r
233 static Atom motion_event, button_press_event, button_release_event, command_event;\r
234 \r
235 enum {\r
236   CMD_APP_WINDOW = 27695,\r
237   CMD_APP_SENS\r
238 };\r
239 \r
240 #define IS_OPEN    dpy\r
241 \r
242 struct event_node {\r
243   spnav_event event;\r
244   struct event_node *next;\r
245 };\r
246 \r
247 static int spnav_x11_open(Display *display, Window win)\r
248 {\r
249   if(IS_OPEN) {\r
250     return -1;\r
251   }\r
252 \r
253   dpy = display;\r
254 \r
255   motion_event = XInternAtom(dpy, "MotionEvent", True);\r
256   button_press_event = XInternAtom(dpy, "ButtonPressEvent", True);\r
257   button_release_event = XInternAtom(dpy, "ButtonReleaseEvent", True);\r
258   command_event = XInternAtom(dpy, "CommandEvent", True);\r
259 \r
260   if(!motion_event || !button_press_event || !button_release_event || !command_event) {\r
261     dpy = 0;\r
262     return -1;  /* daemon not started */\r
263   }\r
264 \r
265   if(spnav_x11_window(win) == -1) {\r
266     dpy = 0;\r
267     return -1;  /* daemon not started */\r
268   }\r
269 \r
270   app_win = win;\r
271   return 0;\r
272 }\r
273 \r
274 static int spnav_close(void)\r
275 {\r
276   if(dpy) {\r
277     spnav_x11_window(DefaultRootWindow(dpy));\r
278     app_win = 0;\r
279     dpy = 0;\r
280     return 0;\r
281   }\r
282   return -1;\r
283 }\r
284 \r
285 static int spnav_x11_window(Window win)\r
286 {\r
287   int (*prev_xerr_handler)(Display*, XErrorEvent*);\r
288   XEvent xev;\r
289   Window daemon_win;\r
290 \r
291   if(!IS_OPEN) {\r
292     return -1;\r
293   }\r
294 \r
295   if(!(daemon_win = get_daemon_window(dpy))) {\r
296     return -1;\r
297   }\r
298 \r
299   prev_xerr_handler = XSetErrorHandler(catch_badwin);\r
300 \r
301   xev.type = ClientMessage;\r
302   xev.xclient.send_event = False;\r
303   xev.xclient.display = dpy;\r
304   xev.xclient.window = win;\r
305   xev.xclient.message_type = command_event;\r
306   xev.xclient.format = 16;\r
307   xev.xclient.data.s[0] = ((unsigned int)win & 0xffff0000) >> 16;\r
308   xev.xclient.data.s[1] = (unsigned int)win & 0xffff;\r
309   xev.xclient.data.s[2] = CMD_APP_WINDOW;\r
310 \r
311   XSendEvent(dpy, daemon_win, False, 0, &xev);\r
312   XSync(dpy, False);\r
313 \r
314   XSetErrorHandler(prev_xerr_handler);\r
315   return 0;\r
316 }\r
317 \r
318 static int spnav_fd(void)\r
319 {\r
320   if(dpy) {\r
321     return ConnectionNumber(dpy);\r
322   }\r
323   return -1;\r
324 }\r
325 \r
326 /*static int spnav_wait_event(spnav_event *event)\r
327 {\r
328   if(dpy) {\r
329     for(;;) {\r
330       XEvent xev;\r
331       XNextEvent(dpy, &xev);\r
332 \r
333       if(spnav_x11_event(&xev, event) > 0) {\r
334         return event->type;\r
335       }\r
336     }\r
337   }\r
338   return 0;\r
339 }\r
340 \r
341 static int spnav_poll_event(spnav_event *event)\r
342 {\r
343   if(dpy) {\r
344     if(XPending(dpy)) {\r
345       XEvent xev;\r
346       XNextEvent(dpy, &xev);\r
347 \r
348       return spnav_x11_event(&xev, event);\r
349     }\r
350   }\r
351   return 0;\r
352 }*/\r
353 \r
354 static Bool match_events(Display *dpy, XEvent *xev, char *arg)\r
355 {\r
356   int evtype = *(int*)arg;\r
357 \r
358   if(xev->type != ClientMessage) {\r
359     return False;\r
360   }\r
361 \r
362   if(xev->xclient.message_type == motion_event) {\r
363     return !evtype || evtype == SPNAV_EVENT_MOTION ? True : False;\r
364   }\r
365   if(xev->xclient.message_type == button_press_event ||\r
366       xev->xclient.message_type == button_release_event) {\r
367     return !evtype || evtype == SPNAV_EVENT_BUTTON ? True : False;\r
368   }\r
369   return False;\r
370 }\r
371 \r
372 static int spnav_remove_events(int type)\r
373 {\r
374   int rm_count = 0;\r
375 \r
376   if(dpy) {\r
377     XEvent xev;\r
378 \r
379     while(XCheckIfEvent(dpy, &xev, match_events, (char*)&type)) {\r
380       rm_count++;\r
381     }\r
382     return rm_count;\r
383   }\r
384   return 0;\r
385 }\r
386 \r
387 static int spnav_x11_event(const XEvent *xev, spnav_event *event)\r
388 {\r
389   int i;\r
390   int xmsg_type;\r
391 \r
392   if(xev->type != ClientMessage) {\r
393     return 0;\r
394   }\r
395 \r
396   xmsg_type = xev->xclient.message_type;\r
397 \r
398   if(xmsg_type != motion_event && xmsg_type != button_press_event &&\r
399       xmsg_type != button_release_event) {\r
400     return 0;\r
401   }\r
402 \r
403   if(xmsg_type == motion_event) {\r
404     event->type = SPNAV_EVENT_MOTION;\r
405     event->motion.data = &event->motion.x;\r
406 \r
407     for(i=0; i<6; i++) {\r
408       event->motion.data[i] = xev->xclient.data.s[i + 2];\r
409     }\r
410     event->motion.period = xev->xclient.data.s[8];\r
411   } else {\r
412     event->type = SPNAV_EVENT_BUTTON;\r
413     event->button.press = xmsg_type == button_press_event ? 1 : 0;\r
414     event->button.bnum = xev->xclient.data.s[2];\r
415   }\r
416   return event->type;\r
417 }\r
418 \r
419 \r
420 static Window get_daemon_window(Display *dpy)\r
421 {\r
422   Window win, root_win;\r
423   XTextProperty wname;\r
424   Atom type;\r
425   int fmt;\r
426   unsigned long nitems, bytes_after;\r
427   unsigned char *prop;\r
428 \r
429   root_win = DefaultRootWindow(dpy);\r
430 \r
431   XGetWindowProperty(dpy, root_win, command_event, 0, 1, False, AnyPropertyType, &type, &fmt, &nitems, &bytes_after, &prop);\r
432   if(!prop) {\r
433     return 0;\r
434   }\r
435 \r
436   win = *(Window*)prop;\r
437   XFree(prop);\r
438 \r
439   if(!XGetWMName(dpy, win, &wname) || strcmp("Magellan Window", (char*)wname.value) != 0) {\r
440     return 0;\r
441   }\r
442 \r
443   return win;\r
444 }\r
445 \r
446 static int catch_badwin(Display *dpy, XErrorEvent *err)\r
447 {\r
448   char buf[256];\r
449 \r
450   if(err->error_code == BadWindow) {\r
451     /* do nothing? */\r
452   } else {\r
453     XGetErrorText(dpy, err->error_code, buf, sizeof buf);\r
454     fprintf(stderr, "Caught unexpected X error: %s\n", buf);\r
455   }\r
456   return 0;\r
457 }\r
458 \r
459 #endif  /* TARGET_HOST_POSIX_X11 */\r