src/spacenav-lib-hack.c
author František Kučera <franta-hg@frantovo.cz>
Sat, 09 Mar 2019 12:30:52 +0100
branchv_0
changeset 5 26e9fb705faf
parent 4 650e0a578dab
child 8 1dab2b99f51d
permissions -rw-r--r--
back to the pthread

/**
 * Spacenav lib hack
 * Copyright © 2019 František Kučera (Frantovo.cz, GlobalCode.info)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
#define _GNU_SOURCE

#include <stdio.h>
#include <stdint.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#include <X11/Xlib.h>
#include <spnav.h>

/**
 * The purpose of this hack is to enable 3D mouse in applications that use X11 protocol
 * while our daemon is providing only domain socket and no X11.
 * 
 * Usage:
 * LD_PRELOAD=./spacenav-lib-hack/build/Debug/src/libspnav-lib-hack.so freecad
 * 
 * This hack:
 * 1) Pretends that spnav_x11_open() was successful.
 * 2) Starts a background process (TODO: also terminate it) which reads from the domain socket
 *    and translates spacenav events to X11 events and sends them to the application window.
 * 3) reimplements spnav_x11_event() which is needed, because Atom variables in original library
 *    were not initialized spnav_x11_open() and thus the library funcion is not working.
 * 
 * This solution works and allows using e.g. FreeCAD with domain socket instead of X11, but:
 *  - is very hackish and should be reimplemented as a separate process which will work partly
 *    as a libspnav client and partly emulate spacenavd daemon and will bridge the domain socket
 *    and th X11. Other solution is to emulate the hardware and keep spacenavd and libspnav untouched.
 */

static pthread_t spacenav_hack_thread;
static Window spacenav_hack_win;

// from proto_x11.c (spacenavd)
static Atom xa_event_motion, xa_event_bpress, xa_event_brelease, xa_event_cmd;
static float x11_sens = 1.0;

/**
 * This function is based on proto_x11.c (spacenavd) written by:
 * Copyright (C) 2007-2019 John Tsiombikas <nuclear@member.fsf.org>
 * and published under GNU GPLv3+
 * 
 * @param ev
 * @param dpy
 * @param win
 */
static void send_xevent(spnav_event *ev, Display* dpy, Window win) {
	int i;
	XEvent xevent;

	if (!dpy) return;

	// if(setjmp(jbuf)) return;

	xevent.type = ClientMessage;
	xevent.xclient.send_event = False;
	xevent.xclient.display = dpy;
	xevent.xclient.window = win;

	switch (ev->type) {
		case SPNAV_EVENT_MOTION:
			xevent.xclient.message_type = xa_event_motion;
			xevent.xclient.format = 16;

			for (i = 0; i < 6; i++) {
				float val = (float) ev->motion.data[i] * x11_sens;
				xevent.xclient.data.s[i + 2] = (short) val;
			}
			xevent.xclient.data.s[0] = xevent.xclient.data.s[1] = 0;
			xevent.xclient.data.s[8] = ev->motion.period;
			break;

		case SPNAV_EVENT_BUTTON:
			xevent.xclient.message_type = ev->button.press ? xa_event_bpress : xa_event_brelease;
			xevent.xclient.format = 16;
			xevent.xclient.data.s[2] = ev->button.bnum;
			break;

		default:
			break;
	}

	XSendEvent(dpy, win, False, 0, &xevent);
	XFlush(dpy);
}

static void* spacenav_hack_translate_events(void* arg) {
	fprintf(stderr, "spnav-lib-hack: pthread running, PID=%d\n", getpid());
	spnav_event event;
	Display* dpy = XOpenDisplay(0);

	while (1) {
		if (spnav_wait_event(&event)) {
			send_xevent(&event, dpy, spacenav_hack_win);
		}
	}
}

int spnav_x11_open(Display* dpy, Window win) {
	int result = spnav_open();
	fprintf(stderr, "spnav-lib-hack: instead of spnav_x11_open(%p, 0x%lx) calling spnav_open() = %d\n", dpy, win, result);

	xa_event_motion = XInternAtom(dpy, "MotionEvent", False);
	xa_event_bpress = XInternAtom(dpy, "ButtonPressEvent", False);
	xa_event_brelease = XInternAtom(dpy, "ButtonReleaseEvent", False);
	xa_event_cmd = XInternAtom(dpy, "CommandEvent", False);

	if (result == 0) {
		spacenav_hack_win = win;
		pthread_create(&spacenav_hack_thread, NULL, spacenav_hack_translate_events, NULL);
	}

	return result;
}

/**
 * This function is based on proto_x11.c (spacenavd) written by:
 * Copyright (C) 2007-2019 John Tsiombikas <nuclear@member.fsf.org>
 * and published under GNU GPLv3+
 * 
 * @param dpy
 * @param win
 * @return 
 */
int spnav_x11_event(const XEvent* xev, spnav_event* event) {
	/*
	 * Because real spnav_x11_open() was not called, the library has not initialized xa_event_*
	 * variables and thus will return 0 (= not a spacenav event).
	 */

	int i;
	int xmsg_type;

	if (xev->type != ClientMessage) {
		return 0;
	}

	xmsg_type = xev->xclient.message_type;

	if (xmsg_type != xa_event_motion && xmsg_type != xa_event_bpress &&
			xmsg_type != xa_event_brelease) {
		return 0;
	}

	if (xmsg_type == xa_event_motion) {
		event->type = SPNAV_EVENT_MOTION;
		event->motion.data = &event->motion.x;

		for (i = 0; i < 6; i++) {
			event->motion.data[i] = xev->xclient.data.s[i + 2];
		}
		event->motion.period = xev->xclient.data.s[8];
	} else {
		event->type = SPNAV_EVENT_BUTTON;
		event->button.press = xmsg_type == xa_event_bpress ? 1 : 0;
		event->button.bnum = xev->xclient.data.s[2];
	}
	return event->type;

	// real_spnav_x11_event() is not working (see comment above):

	/*
	static int (*real_spnav_x11_event)(const XEvent*, spnav_event*) = NULL;
	if (!real_spnav_x11_event) real_spnav_x11_event = dlsym(RTLD_NEXT, "spnav_x11_event");
	int result = real_spnav_x11_event(xev, event);
	if (result == SPNAV_EVENT_MOTION || result == SPNAV_EVENT_BUTTON) fprintf(stderr, "spnav-lib-hack: spnav_x11_event() = %d\n", result);
	return result;
	 */
}


/*
int spnav_x11_open(Display* dpy, Window win) {
	static int (*real_spnav_x11_open)(Display*, Window) = NULL;
	if (!real_spnav_x11_open) real_spnav_x11_open = dlsym(RTLD_NEXT, "spnav_x11_open");
	int result = real_spnav_x11_open(dpy, win);
	fprintf(stderr, "spnav-lib-hack: spnav_x11_open() = %d\n", result);
	return result;
}
 */

/*
int spnav_open() {
	static int (*real_spnav_open)() = NULL;
	if (!real_spnav_open) real_spnav_open = dlsym(RTLD_NEXT, "spnav_open");
	int result = real_spnav_open();
	// fprintf(stderr, "spnav-lib-hack: spnav_open() = %d\n", result);
	return result;
}
 */