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
17extern int sball_initialized;
18
19enum {
20 SPNAV_EVENT_ANY, /* used by spnav_remove_events() */
21 SPNAV_EVENT_MOTION,
22 SPNAV_EVENT_BUTTON /* includes both press and release */
23};
24
25struct 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
33struct spnav_event_button {
34 int type;
35 int press;
36 int bnum;
37};
38
39typedef union spnav_event {
40 int type;
41 struct spnav_event_motion motion;
42 struct spnav_event_button button;
43} spnav_event;
44
45
46static int spnav_x11_open(Display *dpy, Window win);
47static int spnav_x11_window(Window win);
48static int spnav_x11_event(const XEvent *xev, spnav_event *event);
49static int spnav_close(void);
50static int spnav_fd(void);
51static int spnav_remove_events(int type);
52
53static SFG_Window *spnav_win;
54
55void 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
74void fgPlatformSpaceballClose(void)
75{
76 spnav_close();
77}
78
79int 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
87int fgPlatformSpaceballNumButtons(void) {
88 return 2;
89}
90
91void fgPlatformSpaceballSetWindow(SFG_Window *window)
92{
93 if(spnav_win != window) {
94 spnav_x11_window(window->Window.Handle);
95 spnav_win = window;
96 }
97}
98
99int 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
115void 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/*
149The following code is part of libspnav, part of the spacenav project (spacenav.sf.net)
150Copyright (C) 2007-2009 John Tsiombikas <nuclear@member.fsf.org>
151
152Redistribution and use in source and binary forms, with or without
153modification, are permitted provided that the following conditions are met:
154
1551. Redistributions of source code must retain the above copyright notice, this
156 list of conditions and the following disclaimer.
1572. 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.
1603. 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
163THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
164WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
165MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
166EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
167EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
168OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
169INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
170CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
171IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
172OF 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
182static Window get_daemon_window(Display *dpy);
183static int catch_badwin(Display *dpy, XErrorEvent *err);
184
185static Display *dpy;
186static Window app_win;
187static Atom motion_event, button_press_event, button_release_event, command_event;
188
189enum {
190 CMD_APP_WINDOW = 27695,
191 CMD_APP_SENS
192};
193
194#define IS_OPEN dpy
195
196struct event_node {
197 spnav_event event;
198 struct event_node *next;
199};
200
201static 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
228static 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
239static 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
272static 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
295static 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
308static 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
326static 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
341static 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
374static 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
400static 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
413