2009-07-24 01:04:12 +00:00
|
|
|
/*
|
|
|
|
spacenavd - a free software replacement driver for 6dof space-mice.
|
2019-01-28 03:41:06 +00:00
|
|
|
Copyright (C) 2007-2019 John Tsiombikas <nuclear@member.fsf.org>
|
2009-07-24 01:04:12 +00:00
|
|
|
|
|
|
|
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/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
2009-10-09 09:09:56 +00:00
|
|
|
|
|
|
|
#ifdef USE_X11
|
2009-07-20 22:47:08 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
2010-07-27 04:46:14 +00:00
|
|
|
#include <string.h>
|
2013-05-22 15:15:48 +00:00
|
|
|
#include <errno.h>
|
2010-02-27 23:49:56 +00:00
|
|
|
#include <setjmp.h>
|
2011-10-05 02:05:18 +00:00
|
|
|
#ifdef HAVE_ALLOCA_H
|
2010-07-27 04:46:14 +00:00
|
|
|
#include <alloca.h>
|
2011-10-05 02:05:18 +00:00
|
|
|
#endif
|
|
|
|
#ifdef HAVE_MALLOC_H
|
|
|
|
#include <malloc.h>
|
|
|
|
#endif
|
2010-07-27 04:46:14 +00:00
|
|
|
#include <unistd.h>
|
|
|
|
#include <pwd.h>
|
2009-07-20 22:47:08 +00:00
|
|
|
#include "proto_x11.h"
|
|
|
|
#include "client.h"
|
|
|
|
#include "spnavd.h"
|
2015-02-12 04:17:13 +00:00
|
|
|
#include "dev.h"
|
2009-07-24 01:04:12 +00:00
|
|
|
#include "xdetect.h"
|
2012-05-05 05:12:49 +00:00
|
|
|
#include "kbemu.h"
|
2009-07-20 22:47:08 +00:00
|
|
|
|
2015-02-12 04:17:13 +00:00
|
|
|
#ifdef HAVE_XINPUT2_H
|
|
|
|
#include <X11/Xatom.h>
|
|
|
|
#include <X11/extensions/XInput2.h>
|
|
|
|
#endif
|
|
|
|
|
2009-07-20 22:47:08 +00:00
|
|
|
|
|
|
|
enum cmd_msg {
|
|
|
|
CMD_NONE,
|
|
|
|
CMD_APP_WINDOW = 27695, /* set client window */
|
|
|
|
CMD_APP_SENS /* set app sensitivity */
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2010-02-27 23:49:56 +00:00
|
|
|
static int xerr(Display *dpy, XErrorEvent *err);
|
|
|
|
static int xioerr(Display *dpy);
|
2009-07-20 22:47:08 +00:00
|
|
|
|
|
|
|
static Display *dpy;
|
|
|
|
static Window win;
|
2022-04-09 03:51:22 +00:00
|
|
|
static Atom xa_event_motion, xa_event_bpress, xa_event_brelease;
|
|
|
|
static Atom xa_event_devdisc, xa_event_cmd;
|
2009-07-20 22:47:08 +00:00
|
|
|
|
|
|
|
/* XXX This stands in for the client sensitivity. Due to the
|
|
|
|
* bad design of the original magellan protocol, we can't know
|
|
|
|
* which client requested the sensitivity change, so we have
|
|
|
|
* to keep it global for all X clients.
|
|
|
|
*/
|
|
|
|
static float x11_sens = 1.0;
|
|
|
|
|
2010-02-27 23:49:56 +00:00
|
|
|
static jmp_buf jbuf;
|
|
|
|
|
2009-07-20 22:47:08 +00:00
|
|
|
|
|
|
|
int init_x11(void)
|
|
|
|
{
|
|
|
|
int i, screen, scr_count;
|
|
|
|
Window root;
|
|
|
|
XSetWindowAttributes xattr;
|
|
|
|
Atom wm_delete, cmd_type;
|
|
|
|
XTextProperty tp_wname;
|
|
|
|
XClassHint class_hint;
|
|
|
|
char *win_title = "Magellan Window";
|
|
|
|
|
|
|
|
if(dpy) return 0;
|
|
|
|
|
|
|
|
/* if the server started from init, it probably won't have a DISPLAY env var
|
|
|
|
* so let's add a default one.
|
|
|
|
*/
|
|
|
|
if(!getenv("DISPLAY")) {
|
|
|
|
putenv("DISPLAY=:0.0");
|
|
|
|
}
|
|
|
|
|
2010-07-27 04:46:14 +00:00
|
|
|
/* ... also there won't be an XAUTHORITY env var, so set one up */
|
|
|
|
if(!getenv("XAUTHORITY")) {
|
|
|
|
struct passwd *p = getpwuid(getuid());
|
2013-05-22 15:15:48 +00:00
|
|
|
char *home, *buf;
|
|
|
|
if(!p || !p->pw_dir) {
|
|
|
|
if(!p) {
|
2019-01-28 03:41:06 +00:00
|
|
|
logmsg(LOG_ERR, "getpwuid failed: %s\n", strerror(errno));
|
2013-05-22 15:15:48 +00:00
|
|
|
}
|
2019-01-28 03:41:06 +00:00
|
|
|
logmsg(LOG_WARNING, "falling back to getting the home directory from the HOME env var...\n");
|
2013-05-22 15:15:48 +00:00
|
|
|
if(!(home = getenv("HOME"))) {
|
2019-01-28 03:41:06 +00:00
|
|
|
logmsg(LOG_WARNING, "HOME env var not found, using /tmp as a home directory...\n");
|
2013-05-22 15:15:48 +00:00
|
|
|
home = "/tmp";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
home = p->pw_dir;
|
|
|
|
}
|
|
|
|
|
|
|
|
buf = alloca(strlen("XAUTHORITY=") + strlen(home) + strlen("/.Xauthority") + 1);
|
2010-07-27 04:46:14 +00:00
|
|
|
sprintf(buf, "XAUTHORITY=%s/.Xauthority", home);
|
|
|
|
putenv(buf);
|
|
|
|
}
|
|
|
|
|
2009-07-20 22:47:08 +00:00
|
|
|
if(verbose) {
|
2019-01-28 03:41:06 +00:00
|
|
|
logmsg(LOG_INFO, "trying to open X11 display \"%s\"\n", getenv("DISPLAY"));
|
|
|
|
logmsg(LOG_INFO, " XAUTHORITY=%s\n", getenv("XAUTHORITY"));
|
2009-07-20 22:47:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if(!(dpy = XOpenDisplay(0))) {
|
2019-01-28 03:41:06 +00:00
|
|
|
logmsg(LOG_ERR, "failed to open X11 display \"%s\"\n", getenv("DISPLAY"));
|
2009-07-24 01:04:12 +00:00
|
|
|
|
|
|
|
xdet_start();
|
2009-07-20 22:47:08 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2010-02-27 23:49:56 +00:00
|
|
|
|
|
|
|
XSetErrorHandler(xerr);
|
|
|
|
XSetIOErrorHandler(xioerr);
|
|
|
|
|
|
|
|
if(setjmp(jbuf)) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2009-07-20 22:47:08 +00:00
|
|
|
scr_count = ScreenCount(dpy);
|
|
|
|
screen = DefaultScreen(dpy);
|
|
|
|
root = RootWindow(dpy, screen);
|
|
|
|
|
|
|
|
/* intern the various atoms used for communicating with the magellan clients */
|
|
|
|
xa_event_motion = XInternAtom(dpy, "MotionEvent", False);
|
|
|
|
xa_event_bpress = XInternAtom(dpy, "ButtonPressEvent", False);
|
|
|
|
xa_event_brelease = XInternAtom(dpy, "ButtonReleaseEvent", False);
|
2022-04-09 03:51:22 +00:00
|
|
|
xa_event_devdisc = XInternAtom(dpy, "DeviceDisconnectEvent", False);
|
2009-07-20 22:47:08 +00:00
|
|
|
xa_event_cmd = XInternAtom(dpy, "CommandEvent", False);
|
|
|
|
|
|
|
|
/* Create a dummy window, so that clients are able to send us events
|
|
|
|
* through the magellan API. No need to map the window.
|
|
|
|
*/
|
|
|
|
xattr.background_pixel = xattr.border_pixel = BlackPixel(dpy, screen);
|
|
|
|
xattr.colormap = DefaultColormap(dpy, screen);
|
|
|
|
|
|
|
|
win = XCreateWindow(dpy, root, 0, 0, 10, 10, 0, CopyFromParent, InputOutput,
|
|
|
|
DefaultVisual(dpy, screen), CWColormap | CWBackPixel | CWBorderPixel, &xattr);
|
|
|
|
|
|
|
|
wm_delete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
|
|
|
|
XSetWMProtocols(dpy, win, &wm_delete, 1);
|
|
|
|
|
|
|
|
XStringListToTextProperty(&win_title, 1, &tp_wname);
|
|
|
|
XSetWMName(dpy, win, &tp_wname);
|
|
|
|
XFree(tp_wname.value);
|
|
|
|
|
|
|
|
class_hint.res_name = "magellan";
|
|
|
|
class_hint.res_class = "magellan_win";
|
|
|
|
XSetClassHint(dpy, win, &class_hint);
|
|
|
|
|
|
|
|
/* I believe this is a bit hackish, but the magellan API expects to find the CommandEvent
|
|
|
|
* property on the root window, containing our window id.
|
|
|
|
* The API doesn't look for a specific property type, so I made one up here (MagellanCmdType).
|
|
|
|
*/
|
|
|
|
cmd_type = XInternAtom(dpy, "MagellanCmdType", False);
|
|
|
|
for(i=0; i<scr_count; i++) {
|
|
|
|
Window root = RootWindow(dpy, i);
|
|
|
|
XChangeProperty(dpy, root, xa_event_cmd, cmd_type, 32, PropModeReplace, (unsigned char*)&win, 1);
|
|
|
|
}
|
|
|
|
XFlush(dpy);
|
|
|
|
|
2012-05-05 05:12:49 +00:00
|
|
|
/* pass the display connection to the keyboard emulation module */
|
|
|
|
kbemu_set_display(dpy);
|
|
|
|
|
2009-07-24 01:04:12 +00:00
|
|
|
xdet_stop(); /* stop X server detection if it was running */
|
2015-02-12 04:17:13 +00:00
|
|
|
|
|
|
|
drop_xinput();
|
2009-07-20 22:47:08 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void close_x11(void)
|
|
|
|
{
|
|
|
|
int i, scr_count;
|
|
|
|
struct client *cnode;
|
|
|
|
|
2010-02-27 23:49:56 +00:00
|
|
|
if(dpy && setjmp(jbuf) == 0) {
|
|
|
|
if(verbose) {
|
2019-01-28 03:41:06 +00:00
|
|
|
logmsg(LOG_INFO, "closing X11 connection to display \"%s\"\n", getenv("DISPLAY"));
|
2010-02-27 23:49:56 +00:00
|
|
|
}
|
2009-07-20 22:47:08 +00:00
|
|
|
|
2010-02-27 23:49:56 +00:00
|
|
|
/* first delete all the CommandEvent properties from all root windows */
|
|
|
|
scr_count = ScreenCount(dpy);
|
|
|
|
for(i=0; i<scr_count; i++) {
|
|
|
|
Window root = RootWindow(dpy, i);
|
|
|
|
XDeleteProperty(dpy, root, xa_event_cmd);
|
|
|
|
}
|
2009-07-20 22:47:08 +00:00
|
|
|
|
2010-02-27 23:49:56 +00:00
|
|
|
XDestroyWindow(dpy, win);
|
|
|
|
XCloseDisplay(dpy);
|
|
|
|
dpy = 0;
|
2012-05-05 05:12:49 +00:00
|
|
|
|
|
|
|
/* stop the kbemu module from using an invalid Display* */
|
|
|
|
kbemu_set_display(0);
|
2009-07-20 22:47:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* also remove all x11 clients from the client list */
|
|
|
|
cnode = first_client();
|
|
|
|
while(cnode) {
|
|
|
|
struct client *c = cnode;
|
|
|
|
cnode = next_client();
|
|
|
|
|
|
|
|
if(get_client_type(c) == CLIENT_X11) {
|
|
|
|
remove_client(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int get_x11_socket(void)
|
|
|
|
{
|
2009-07-24 01:04:12 +00:00
|
|
|
return dpy ? ConnectionNumber(dpy) : xdet_get_fd();
|
2009-07-20 22:47:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void send_xevent(spnav_event *ev, struct client *c)
|
|
|
|
{
|
|
|
|
int i;
|
2022-03-27 15:56:24 +00:00
|
|
|
XEvent xevent;
|
2009-07-20 22:47:08 +00:00
|
|
|
|
|
|
|
if(!dpy) return;
|
|
|
|
|
2010-02-27 23:49:56 +00:00
|
|
|
if(setjmp(jbuf)) {
|
|
|
|
return;
|
|
|
|
}
|
2009-07-20 22:47:08 +00:00
|
|
|
|
|
|
|
xevent.type = ClientMessage;
|
|
|
|
xevent.xclient.send_event = False;
|
|
|
|
xevent.xclient.display = dpy;
|
|
|
|
xevent.xclient.window = get_client_window(c);
|
|
|
|
|
|
|
|
switch(ev->type) {
|
|
|
|
case 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 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:
|
2022-03-27 15:56:24 +00:00
|
|
|
return;
|
2009-07-20 22:47:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
XSendEvent(dpy, get_client_window(c), False, 0, &xevent);
|
2010-02-27 23:49:56 +00:00
|
|
|
XFlush(dpy);
|
2009-07-20 22:47:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int handle_xevents(fd_set *rset)
|
|
|
|
{
|
|
|
|
if(!dpy) {
|
2009-07-24 01:04:12 +00:00
|
|
|
if(xdet_get_fd() != -1) {
|
|
|
|
handle_xdet_events(rset);
|
|
|
|
}
|
2009-07-20 22:47:08 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* process any pending X events */
|
|
|
|
if(FD_ISSET(ConnectionNumber(dpy), rset)) {
|
2010-02-27 23:49:56 +00:00
|
|
|
if(setjmp(jbuf)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-07-20 22:47:08 +00:00
|
|
|
while(XPending(dpy)) {
|
|
|
|
XEvent xev;
|
|
|
|
XNextEvent(dpy, &xev);
|
|
|
|
|
|
|
|
if(xev.type == ClientMessage && xev.xclient.message_type == xa_event_cmd) {
|
|
|
|
unsigned int win_id;
|
|
|
|
|
|
|
|
switch(xev.xclient.data.s[2]) {
|
|
|
|
case CMD_APP_WINDOW:
|
|
|
|
win_id = xev.xclient.data.s[1];
|
|
|
|
win_id |= (unsigned int)xev.xclient.data.s[0] << 16;
|
|
|
|
|
|
|
|
set_client_window((Window)win_id);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CMD_APP_SENS:
|
|
|
|
x11_sens = *(float*)xev.xclient.data.s; /* see decl of x11_sens for details */
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* adds a new X11 client to the list, IF it does not already exist */
|
|
|
|
void set_client_window(Window win)
|
|
|
|
{
|
|
|
|
int i, scr_count;
|
|
|
|
struct client *cnode;
|
|
|
|
|
|
|
|
/* When a magellan application exits, the SDK sets another window to avoid
|
|
|
|
* crashing the original proprietary daemon. The new free SDK will set
|
|
|
|
* consistently the root window for that purpose, which we can ignore here
|
|
|
|
* easily.
|
|
|
|
*/
|
|
|
|
scr_count = ScreenCount(dpy);
|
|
|
|
for(i=0; i<scr_count; i++) {
|
|
|
|
if(win == RootWindow(dpy, i)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* make sure we don't already have that client */
|
|
|
|
cnode = first_client();
|
|
|
|
while(cnode) {
|
|
|
|
if(get_client_window(cnode) == win) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
cnode = next_client();
|
|
|
|
}
|
|
|
|
|
|
|
|
add_client(CLIENT_X11, &win);
|
|
|
|
}
|
|
|
|
|
|
|
|
void remove_client_window(Window win)
|
|
|
|
{
|
|
|
|
struct client *c, *cnode;
|
|
|
|
|
|
|
|
cnode = first_client();
|
|
|
|
while(cnode) {
|
|
|
|
c = cnode;
|
|
|
|
cnode = next_client();
|
|
|
|
|
|
|
|
if(get_client_window(c) == win) {
|
|
|
|
remove_client(c);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-12 04:17:13 +00:00
|
|
|
void drop_xinput(void)
|
|
|
|
{
|
|
|
|
#ifdef HAVE_XINPUT2_H
|
|
|
|
XIDeviceInfo *xidevs;
|
|
|
|
int i, num_devs;
|
|
|
|
static Atom atom_enabled;
|
|
|
|
|
|
|
|
if(!dpy) return;
|
|
|
|
|
|
|
|
if(!atom_enabled) {
|
|
|
|
atom_enabled = XInternAtom(dpy, "Device Enabled", False);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!(xidevs = XIQueryDevice(dpy, XIAllDevices, &num_devs))) {
|
2019-01-28 03:41:06 +00:00
|
|
|
logmsg(LOG_ERR, "failed to query XInput2 devices\n");
|
2015-02-12 04:17:13 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(i=0; i<num_devs; i++) {
|
|
|
|
struct device *dev = get_devices();
|
|
|
|
while(dev) {
|
|
|
|
if(strcmp(dev->name, xidevs[i].name) == 0 && xidevs[i].enabled) {
|
|
|
|
unsigned char zero = 0;
|
2019-01-28 03:41:06 +00:00
|
|
|
logmsg(LOG_INFO, "Removing device \"%s\" from X server\n", dev->name);
|
2015-02-12 04:17:13 +00:00
|
|
|
|
|
|
|
XIChangeProperty(dpy, xidevs[i].deviceid, atom_enabled, XA_INTEGER, 8, PropModeReplace, &zero, 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
dev = dev->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
XIFreeDeviceInfo(xidevs);
|
|
|
|
|
|
|
|
#endif /* HAVE_XINPUT2_H */
|
|
|
|
}
|
|
|
|
|
2009-07-20 22:47:08 +00:00
|
|
|
|
2010-02-27 23:49:56 +00:00
|
|
|
/* X11 error handler */
|
|
|
|
static int xerr(Display *dpy, XErrorEvent *err)
|
2009-07-20 22:47:08 +00:00
|
|
|
{
|
2010-02-27 23:49:56 +00:00
|
|
|
char buf[512];
|
|
|
|
|
2009-07-20 22:47:08 +00:00
|
|
|
if(err->error_code == BadWindow) {
|
2022-03-27 10:15:59 +00:00
|
|
|
if(verbose) {
|
|
|
|
logmsg(LOG_INFO, "Caught BadWindow, dropping client with window: %x\n",
|
|
|
|
(unsigned int)err->resourceid);
|
|
|
|
}
|
2010-02-27 23:49:56 +00:00
|
|
|
/* we may get a BadWindow error when trying to send events to
|
|
|
|
* clients that have disconnected in the meanwhile.
|
|
|
|
*/
|
2009-07-20 22:47:08 +00:00
|
|
|
remove_client_window((Window)err->resourceid);
|
|
|
|
} else {
|
|
|
|
XGetErrorText(dpy, err->error_code, buf, sizeof buf);
|
2022-03-27 10:15:59 +00:00
|
|
|
logmsg(LOG_ERR, "Caught unexpected X error: %s [op: %d,%d, res: %u]\n", buf,
|
|
|
|
(int)err->request_code, (int)err->minor_code, (unsigned int)err->resourceid);
|
2009-07-20 22:47:08 +00:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-02-27 23:49:56 +00:00
|
|
|
/* X11 I/O error handler
|
|
|
|
* This function must not return or xlib will abort.
|
|
|
|
*/
|
|
|
|
static int xioerr(Display *display)
|
|
|
|
{
|
2019-01-28 03:41:06 +00:00
|
|
|
logmsg(LOG_ERR, "Lost the X server!\n");
|
2010-02-27 23:49:56 +00:00
|
|
|
dpy = 0;
|
|
|
|
close_x11();
|
|
|
|
xdet_start();
|
|
|
|
|
|
|
|
longjmp(jbuf, 1);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-10-09 09:09:56 +00:00
|
|
|
#else
|
|
|
|
int spacenavd_proto_x11_shut_up_empty_source_warning;
|
|
|
|
#endif /* USE_X11 */
|