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