7816f66e5dc9603fc7b7d731a69415947344e495
[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 #include <errno.h>\r
219 \r
220 #include <X11/Xlib.h>\r
221 #include <X11/Xutil.h>\r
222 \r
223 static Window get_daemon_window(Display *dpy);\r
224 static int catch_badwin(Display *dpy, XErrorEvent *err);\r
225 \r
226 static Display *dpy;\r
227 static Window app_win;\r
228 static Atom motion_event, button_press_event, button_release_event, command_event;\r
229 \r
230 enum {\r
231         CMD_APP_WINDOW = 27695,\r
232         CMD_APP_SENS\r
233 };\r
234 \r
235 #define IS_OPEN         dpy\r
236 \r
237 struct event_node {\r
238         spnav_event event;\r
239         struct event_node *next;\r
240 };\r
241 \r
242 static int spnav_x11_open(Display *display, Window win)\r
243 {\r
244         if(IS_OPEN) {\r
245                 return -1;\r
246         }\r
247 \r
248         dpy = display;\r
249 \r
250         motion_event = XInternAtom(dpy, "MotionEvent", True);\r
251         button_press_event = XInternAtom(dpy, "ButtonPressEvent", True);\r
252         button_release_event = XInternAtom(dpy, "ButtonReleaseEvent", True);\r
253         command_event = XInternAtom(dpy, "CommandEvent", True);\r
254 \r
255         if(!motion_event || !button_press_event || !button_release_event || !command_event) {\r
256                 dpy = 0;\r
257                 return -1;      /* daemon not started */\r
258         }\r
259 \r
260         if(spnav_x11_window(win) == -1) {\r
261                 dpy = 0;\r
262                 return -1;      /* daemon not started */\r
263         }\r
264 \r
265         app_win = win;\r
266         return 0;\r
267 }\r
268 \r
269 static int spnav_close(void)\r
270 {\r
271         if(dpy) {\r
272                 spnav_x11_window(DefaultRootWindow(dpy));\r
273                 app_win = 0;\r
274                 dpy = 0;\r
275                 return 0;\r
276         }\r
277         return -1;\r
278 }\r
279 \r
280 static int spnav_x11_window(Window win)\r
281 {\r
282         int (*prev_xerr_handler)(Display*, XErrorEvent*);\r
283         XEvent xev;\r
284         Window daemon_win;\r
285 \r
286         if(!IS_OPEN) {\r
287                 return -1;\r
288         }\r
289 \r
290         if(!(daemon_win = get_daemon_window(dpy))) {\r
291                 return -1;\r
292         }\r
293 \r
294         prev_xerr_handler = XSetErrorHandler(catch_badwin);\r
295 \r
296         xev.type = ClientMessage;\r
297         xev.xclient.send_event = False;\r
298         xev.xclient.display = dpy;\r
299         xev.xclient.window = win;\r
300         xev.xclient.message_type = command_event;\r
301         xev.xclient.format = 16;\r
302         xev.xclient.data.s[0] = ((unsigned int)win & 0xffff0000) >> 16;\r
303         xev.xclient.data.s[1] = (unsigned int)win & 0xffff;\r
304         xev.xclient.data.s[2] = CMD_APP_WINDOW;\r
305 \r
306         XSendEvent(dpy, daemon_win, False, 0, &xev);\r
307         XSync(dpy, False);\r
308 \r
309         XSetErrorHandler(prev_xerr_handler);\r
310         return 0;\r
311 }\r
312 \r
313 static int spnav_fd(void)\r
314 {\r
315         if(dpy) {\r
316                 return ConnectionNumber(dpy);\r
317         }\r
318         return -1;\r
319 }\r
320 \r
321 /*static int spnav_wait_event(spnav_event *event)\r
322 {\r
323         if(dpy) {\r
324                 for(;;) {\r
325                         XEvent xev;\r
326                         XNextEvent(dpy, &xev);\r
327 \r
328                         if(spnav_x11_event(&xev, event) > 0) {\r
329                                 return event->type;\r
330                         }\r
331                 }\r
332         }\r
333         return 0;\r
334 }\r
335 \r
336 static int spnav_poll_event(spnav_event *event)\r
337 {\r
338         if(dpy) {\r
339                 if(XPending(dpy)) {\r
340                         XEvent xev;\r
341                         XNextEvent(dpy, &xev);\r
342 \r
343                         return spnav_x11_event(&xev, event);\r
344                 }\r
345         }\r
346         return 0;\r
347 }*/\r
348 \r
349 static Bool match_events(Display *dpy, XEvent *xev, char *arg)\r
350 {\r
351         int evtype = *(int*)arg;\r
352 \r
353         if(xev->type != ClientMessage) {\r
354                 return False;\r
355         }\r
356 \r
357         if(xev->xclient.message_type == motion_event) {\r
358                 return !evtype || evtype == SPNAV_EVENT_MOTION ? True : False;\r
359         }\r
360         if(xev->xclient.message_type == button_press_event ||\r
361                         xev->xclient.message_type == button_release_event) {\r
362                 return !evtype || evtype == SPNAV_EVENT_BUTTON ? True : False;\r
363         }\r
364         return False;\r
365 }\r
366 \r
367 static int spnav_remove_events(int type)\r
368 {\r
369         int rm_count = 0;\r
370 \r
371         if(dpy) {\r
372                 XEvent xev;\r
373 \r
374                 while(XCheckIfEvent(dpy, &xev, match_events, (char*)&type)) {\r
375                         rm_count++;\r
376                 }\r
377                 return rm_count;\r
378         }\r
379         return 0;\r
380 }\r
381 \r
382 static int spnav_x11_event(const XEvent *xev, spnav_event *event)\r
383 {\r
384         int i;\r
385         int xmsg_type;\r
386 \r
387         if(xev->type != ClientMessage) {\r
388                 return 0;\r
389         }\r
390 \r
391         xmsg_type = xev->xclient.message_type;\r
392 \r
393         if(xmsg_type != motion_event && xmsg_type != button_press_event &&\r
394                         xmsg_type != button_release_event) {\r
395                 return 0;\r
396         }\r
397 \r
398         if(xmsg_type == motion_event) {\r
399                 event->type = SPNAV_EVENT_MOTION;\r
400                 event->motion.data = &event->motion.x;\r
401 \r
402                 for(i=0; i<6; i++) {\r
403                         event->motion.data[i] = xev->xclient.data.s[i + 2];\r
404                 }\r
405                 event->motion.period = xev->xclient.data.s[8];\r
406         } else {\r
407                 event->type = SPNAV_EVENT_BUTTON;\r
408                 event->button.press = xmsg_type == button_press_event ? 1 : 0;\r
409                 event->button.bnum = xev->xclient.data.s[2];\r
410         }\r
411         return event->type;\r
412 }\r
413 \r
414 \r
415 static Window get_daemon_window(Display *dpy)\r
416 {\r
417         Window win, root_win;\r
418         XTextProperty wname;\r
419         Atom type;\r
420         int fmt;\r
421         unsigned long nitems, bytes_after;\r
422         unsigned char *prop;\r
423 \r
424         root_win = DefaultRootWindow(dpy);\r
425 \r
426         XGetWindowProperty(dpy, root_win, command_event, 0, 1, False, AnyPropertyType, &type, &fmt, &nitems, &bytes_after, &prop);\r
427         if(!prop) {\r
428                 return 0;\r
429         }\r
430 \r
431         win = *(Window*)prop;\r
432         XFree(prop);\r
433 \r
434         if(!XGetWMName(dpy, win, &wname) || strcmp("Magellan Window", (char*)wname.value) != 0) {\r
435                 return 0;\r
436         }\r
437 \r
438         return win;\r
439 }\r
440 \r
441 static int catch_badwin(Display *dpy, XErrorEvent *err)\r
442 {\r
443         char buf[256];\r
444 \r
445         if(err->error_code == BadWindow) {\r
446                 /* do nothing? */\r
447         } else {\r
448                 XGetErrorText(dpy, err->error_code, buf, sizeof buf);\r
449                 fprintf(stderr, "Caught unexpected X error: %s\n", buf);\r
450         }\r
451         return 0;\r
452 }\r
453 \r
454 #endif  /* TARGET_HOST_POSIX_X11 */\r