/* spacenavd - a free software replacement driver for 6dof space-mice. Copyright (C) 2007-2019 John Tsiombikas 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 . */ #include "config.h" #ifdef USE_X11 #include #include #include #include #include #ifdef HAVE_ALLOCA_H #include #endif #ifdef HAVE_MALLOC_H #include #endif #include #include #include "proto_x11.h" #include "client.h" #include "spnavd.h" #include "dev.h" #include "xdetect.h" #include "kbemu.h" #ifdef HAVE_XINPUT2_H #include #include #endif enum cmd_msg { CMD_NONE, CMD_APP_WINDOW = 27695, /* set client window */ CMD_APP_SENS /* set app sensitivity */ }; static int xerr(Display *dpy, XErrorEvent *err); static int xioerr(Display *dpy); static Display *dpy; static Window win; static Atom xa_event_motion, xa_event_bpress, xa_event_brelease; static Atom xa_event_devdisc, xa_event_cmd; /* 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; static jmp_buf jbuf; 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"); } /* ... also there won't be an XAUTHORITY env var, so set one up */ if(!getenv("XAUTHORITY")) { struct passwd *p = getpwuid(getuid()); char *home, *buf; if(!p || !p->pw_dir) { if(!p) { logmsg(LOG_ERR, "getpwuid failed: %s\n", strerror(errno)); } logmsg(LOG_WARNING, "falling back to getting the home directory from the HOME env var...\n"); if(!(home = getenv("HOME"))) { logmsg(LOG_WARNING, "HOME env var not found, using /tmp as a home directory...\n"); home = "/tmp"; } } else { home = p->pw_dir; } buf = alloca(strlen("XAUTHORITY=") + strlen(home) + strlen("/.Xauthority") + 1); sprintf(buf, "XAUTHORITY=%s/.Xauthority", home); putenv(buf); } if(verbose) { logmsg(LOG_INFO, "trying to open X11 display \"%s\"\n", getenv("DISPLAY")); logmsg(LOG_INFO, " XAUTHORITY=%s\n", getenv("XAUTHORITY")); } if(!(dpy = XOpenDisplay(0))) { logmsg(LOG_ERR, "failed to open X11 display \"%s\"\n", getenv("DISPLAY")); xdet_start(); return -1; } XSetErrorHandler(xerr); XSetIOErrorHandler(xioerr); if(setjmp(jbuf)) { return -1; } 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); xa_event_devdisc = XInternAtom(dpy, "DeviceDisconnectEvent", False); 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; itype) { 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: return; } XSendEvent(dpy, get_client_window(c), False, 0, &xevent); XFlush(dpy); } int handle_xevents(fd_set *rset) { if(!dpy) { if(xdet_get_fd() != -1) { handle_xdet_events(rset); } return -1; } /* process any pending X events */ if(FD_ISSET(ConnectionNumber(dpy), rset)) { if(setjmp(jbuf)) { return 0; } 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; iname, xidevs[i].name) == 0 && xidevs[i].enabled) { unsigned char zero = 0; logmsg(LOG_INFO, "Removing device \"%s\" from X server\n", dev->name); XIChangeProperty(dpy, xidevs[i].deviceid, atom_enabled, XA_INTEGER, 8, PropModeReplace, &zero, 1); break; } dev = dev->next; } } XIFreeDeviceInfo(xidevs); #endif /* HAVE_XINPUT2_H */ } /* X11 error handler */ static int xerr(Display *dpy, XErrorEvent *err) { char buf[512]; if(err->error_code == BadWindow) { if(verbose) { logmsg(LOG_INFO, "Caught BadWindow, dropping client with window: %x\n", (unsigned int)err->resourceid); } /* we may get a BadWindow error when trying to send events to * clients that have disconnected in the meanwhile. */ remove_client_window((Window)err->resourceid); } else { XGetErrorText(dpy, err->error_code, buf, sizeof buf); 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); } return 0; } /* X11 I/O error handler * This function must not return or xlib will abort. */ static int xioerr(Display *display) { logmsg(LOG_ERR, "Lost the X server!\n"); dpy = 0; close_x11(); xdet_start(); longjmp(jbuf, 1); return 0; } #else int spacenavd_proto_x11_shut_up_empty_source_warning; #endif /* USE_X11 */