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