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