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