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