1 /*
2  * xbanish
3  * Copyright (c) 2013-2015 joshua stein <jcs@jcs.org>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. The name of the author may not be used to endorse or promote products
15  *    derived from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include <err.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <string.h>
33 #include <unistd.h>
34 
35 #include <X11/X.h>
36 #include <X11/Xlib.h>
37 #include <X11/Intrinsic.h>
38 #include <X11/extensions/Xfixes.h>
39 #include <X11/extensions/XInput.h>
40 #include <X11/extensions/XInput2.h>
41 
42 void hide_cursor(void);
43 void show_cursor(void);
44 void snoop_root(void);
45 int snoop_xinput(Window);
46 void snoop_legacy(Window);
47 void usage(char *);
48 int swallow_error(Display *, XErrorEvent *);
49 
50 /* xinput event type ids to be filled in later */
51 static int button_press_type = -1;
52 static int button_release_type = -1;
53 static int key_press_type = -1;
54 static int key_release_type = -1;
55 static int motion_type = -1;
56 static int device_change_type = -1;
57 static long last_device_change = -1;
58 
59 static Display *dpy;
60 static int hiding = 0, legacy = 0, always_hide = 0;
61 static unsigned char ignored;
62 
63 static int debug = 0;
64 #define DPRINTF(x) { if (debug) { printf x; } };
65 
66 static int move = 0, move_x, move_y;
67 enum move_types {
68 	MOVE_NW = 1,
69 	MOVE_NE,
70 	MOVE_SW,
71 	MOVE_SE,
72 };
73 
74 int
main(int argc,char * argv[])75 main(int argc, char *argv[])
76 {
77 	int ch, i;
78 	XEvent e;
79 	XGenericEventCookie *cookie;
80 
81 	struct mod_lookup {
82 		char *name;
83 		int mask;
84 	} mods[] = {
85 		{"shift", ShiftMask}, {"lock", LockMask},
86 		{"control", ControlMask}, {"mod1", Mod1Mask},
87 		{"mod2", Mod2Mask}, {"mod3", Mod3Mask},
88 		{"mod4", Mod4Mask}, {"mod5", Mod5Mask}
89 	};
90 
91 	while ((ch = getopt(argc, argv, "adi:m:")) != -1)
92 		switch (ch) {
93 		case 'a':
94 			always_hide = 1;
95 			break;
96 		case 'd':
97 			debug = 1;
98 			break;
99 		case 'i':
100 			for (i = 0;
101 			    i < sizeof(mods) / sizeof(struct mod_lookup); i++)
102 				if (strcasecmp(optarg, mods[i].name) == 0)
103 					ignored |= mods[i].mask;
104 
105 			break;
106 		case 'm':
107 			if (strcmp(optarg, "nw") == 0)
108 				move = MOVE_NW;
109 			else if (strcmp(optarg, "ne") == 0)
110 				move = MOVE_NE;
111 			else if (strcmp(optarg, "sw") == 0)
112 				move = MOVE_SW;
113 			else if (strcmp(optarg, "se") == 0)
114 				move = MOVE_SE;
115 			else {
116 				warnx("invalid '-m' argument");
117 				usage(argv[0]);
118 			}
119 			break;
120 		default:
121 			usage(argv[0]);
122 		}
123 
124 	argc -= optind;
125 	argv += optind;
126 
127 	if (!(dpy = XOpenDisplay(NULL)))
128 		errx(1, "can't open display %s", XDisplayName(NULL));
129 
130 #ifdef __OpenBSD__
131 	if (pledge("stdio", NULL) == -1)
132 		err(1, "pledge");
133 #endif
134 
135 	XSetErrorHandler(swallow_error);
136 
137 	snoop_root();
138 
139 	if (always_hide)
140 		hide_cursor();
141 
142 	for (;;) {
143 		cookie = &e.xcookie;
144 		XNextEvent(dpy, &e);
145 
146 		int etype = e.type;
147 		if (e.type == motion_type)
148 			etype = MotionNotify;
149 		else if (e.type == key_press_type ||
150 		    e.type == key_release_type)
151 			etype = KeyRelease;
152 		else if (e.type == button_press_type ||
153 		    e.type == button_release_type)
154 			etype = ButtonRelease;
155 		else if (e.type == device_change_type) {
156 			XDevicePresenceNotifyEvent *xdpe =
157 			    (XDevicePresenceNotifyEvent *)&e;
158 			if (last_device_change == xdpe->serial)
159 				continue;
160 			snoop_root();
161 			last_device_change = xdpe->serial;
162 			continue;
163 		}
164 
165 		switch (etype) {
166 		case KeyRelease:
167 			if (ignored) {
168 				unsigned int state = 0;
169 
170 				/* masks are only set on key release, if
171 				 * ignore is set we must throw out non-release
172 				 * events here */
173 				if (e.type == key_press_type) {
174 					break;
175 				}
176 
177 				/* extract modifier state */
178 				if (e.type == key_release_type) {
179 					/* xinput device event */
180 					XDeviceKeyEvent *key =
181 					    (XDeviceKeyEvent *) &e;
182 					state = key->state;
183 				} else if (e.type == KeyRelease) {
184 					/* legacy event */
185 					state = e.xkey.state;
186 				}
187 
188 				if (state & ignored) {
189 					DPRINTF(("ignoring key %d\n", state));
190 					break;
191 				}
192 			}
193 
194 			hide_cursor();
195 			break;
196 
197 		case ButtonRelease:
198 		case MotionNotify:
199 			if (!always_hide)
200 				show_cursor();
201 			break;
202 
203 		case CreateNotify:
204 			if (legacy) {
205 				DPRINTF(("new window, snooping on it\n"));
206 
207 				/* not sure why snooping directly on the window
208 				 * doesn't work, so snoop on all windows from
209 				 * its parent (probably root) */
210 				snoop_legacy(e.xcreatewindow.parent);
211 			}
212 			break;
213 
214 		case GenericEvent:
215 			/* xi2 raw event */
216 			XGetEventData(dpy, cookie);
217 			XIDeviceEvent *xie = (XIDeviceEvent *)cookie->data;
218 
219 			switch (xie->evtype) {
220 			case XI_RawMotion:
221 			case XI_RawButtonPress:
222 				if (!always_hide)
223 					show_cursor();
224 				break;
225 
226 			case XI_RawButtonRelease:
227 				break;
228 
229 			default:
230 				DPRINTF(("unknown XI event type %d\n",
231 				    xie->evtype));
232 			}
233 
234 			XFreeEventData(dpy, cookie);
235 			break;
236 
237 		default:
238 			DPRINTF(("unknown event type %d\n", e.type));
239 		}
240 	}
241 }
242 
243 void
hide_cursor(void)244 hide_cursor(void)
245 {
246 	Window win;
247 	int x, y, h, w, junk;
248 	unsigned int ujunk;
249 
250 	DPRINTF(("keystroke, %shiding cursor\n", (hiding ? "already " : "")));
251 
252 	if (hiding)
253 		return;
254 
255 	if (move) {
256 		if (XQueryPointer(dpy, DefaultRootWindow(dpy),
257 		    &win, &win, &x, &y, &junk, &junk, &ujunk)) {
258 			move_x = x;
259 			move_y = y;
260 
261 			h = XHeightOfScreen(DefaultScreenOfDisplay(dpy));
262 			w = XWidthOfScreen(DefaultScreenOfDisplay(dpy));
263 
264 			switch (move) {
265 			case MOVE_NW:
266 				x = 0;
267 				y = 0;
268 				break;
269 			case MOVE_NE:
270 				x = w;
271 				y = 0;
272 				break;
273 			case MOVE_SW:
274 				x = 0;
275 				y = h;
276 				break;
277 			case MOVE_SE:
278 				x = w;
279 				y = h;
280 				break;
281 			}
282 
283 			XWarpPointer(dpy, None, DefaultRootWindow(dpy),
284 			    0, 0, 0, 0, x, y);
285 		} else {
286 			move_x = -1;
287 			move_y = -1;
288 			warn("failed finding cursor coordinates");
289 		}
290 	}
291 
292 	XFixesHideCursor(dpy, DefaultRootWindow(dpy));
293 	hiding = 1;
294 }
295 
296 void
show_cursor(void)297 show_cursor(void)
298 {
299 	DPRINTF(("mouse moved, %sunhiding cursor\n",
300 	    (hiding ? "" : "already ")));
301 
302 	if (!hiding)
303 		return;
304 
305 	if (move && move_x != -1 && move_y != -1)
306 		XWarpPointer(dpy, None, DefaultRootWindow(dpy), 0, 0, 0, 0,
307 		    move_x, move_y);
308 
309 	XFixesShowCursor(dpy, DefaultRootWindow(dpy));
310 	hiding = 0;
311 }
312 
313 void
snoop_root(void)314 snoop_root(void)
315 {
316 	if (snoop_xinput(DefaultRootWindow(dpy)) == 0) {
317 		DPRINTF(("no XInput devices found, using legacy snooping"));
318 		legacy = 1;
319 		snoop_legacy(DefaultRootWindow(dpy));
320 	}
321 }
322 
323 int
snoop_xinput(Window win)324 snoop_xinput(Window win)
325 {
326 	int opcode, event, error, numdevs, i, j;
327 	int major, minor, rc, rawmotion = 0;
328 	int ev = 0;
329 	unsigned char mask[(XI_LASTEVENT + 7)/8];
330 	XDeviceInfo *devinfo = NULL;
331 	XInputClassInfo *ici;
332 	XDevice *device;
333 	XIEventMask evmasks[1];
334 	XEventClass class_presence;
335 
336 	if (!XQueryExtension(dpy, "XInputExtension", &opcode, &event, &error)) {
337 		DPRINTF(("XInput extension not available"));
338 		return 0;
339 	}
340 
341 	/*
342 	 * If we support xinput 2, use that for raw motion and button events to
343 	 * get pointer data when the cursor is over a Chromium window.  We
344 	 * could also use this to get raw key input and avoid the other XInput
345 	 * stuff, but we may need to be able to examine the key value later to
346 	 * filter out ignored keys.
347 	 */
348 	major = minor = 2;
349 	rc = XIQueryVersion(dpy, &major, &minor);
350 	if (rc != BadRequest) {
351 		memset(mask, 0, sizeof(mask));
352 
353 		XISetMask(mask, XI_RawMotion);
354 		XISetMask(mask, XI_RawButtonPress);
355 		evmasks[0].deviceid = XIAllMasterDevices;
356 		evmasks[0].mask_len = sizeof(mask);
357 		evmasks[0].mask = mask;
358 
359 		XISelectEvents(dpy, win, evmasks, 1);
360 		XFlush(dpy);
361 
362 		rawmotion = 1;
363 
364 		DPRINTF(("using xinput2 raw motion events\n"));
365 	}
366 
367 	devinfo = XListInputDevices(dpy, &numdevs);
368 	XEventClass event_list[numdevs * 2];
369 	for (i = 0; i < numdevs; i++) {
370 		if (devinfo[i].use != IsXExtensionKeyboard &&
371 		    devinfo[i].use != IsXExtensionPointer)
372 			continue;
373 
374 		if (!(device = XOpenDevice(dpy, devinfo[i].id)))
375 			break;
376 
377 		for (ici = device->classes, j = 0; j < devinfo[i].num_classes;
378 		ici++, j++) {
379 			switch (ici->input_class) {
380 			case KeyClass:
381 				DPRINTF(("attaching to keyboard device %s "
382 				    "(use %d)\n", devinfo[i].name,
383 				    devinfo[i].use));
384 
385 				DeviceKeyPress(device, key_press_type,
386 				    event_list[ev]); ev++;
387 				DeviceKeyRelease(device, key_release_type,
388 				    event_list[ev]); ev++;
389 				break;
390 
391 			case ButtonClass:
392 				if (rawmotion)
393 					continue;
394 
395 				DPRINTF(("attaching to buttoned device %s "
396 				    "(use %d)\n", devinfo[i].name,
397 				    devinfo[i].use));
398 
399 				DeviceButtonPress(device, button_press_type,
400 				    event_list[ev]); ev++;
401 				DeviceButtonRelease(device,
402 				    button_release_type, event_list[ev]); ev++;
403 				break;
404 
405 			case ValuatorClass:
406 				if (rawmotion)
407 					continue;
408 
409 				DPRINTF(("attaching to pointing device %s "
410 				    "(use %d)\n", devinfo[i].name,
411 				    devinfo[i].use));
412 
413 				DeviceMotionNotify(device, motion_type,
414 				    event_list[ev]); ev++;
415 				break;
416 			}
417 		}
418 
419 		XCloseDevice(dpy, device);
420 
421 		if (XSelectExtensionEvent(dpy, win, event_list, ev)) {
422 			warn("error selecting extension events");
423 			ev = 0;
424 			goto done;
425 		}
426 	}
427 
428 	DevicePresence(dpy, device_change_type, class_presence);
429 	if (XSelectExtensionEvent(dpy, win, &class_presence, 1)) {
430 		warn("error selecting extension events");
431 		ev = 0;
432 		goto done;
433 	}
434 
435 done:
436 	if (devinfo != NULL)
437 	   XFreeDeviceList(devinfo);
438 
439 	return ev;
440 }
441 
442 void
snoop_legacy(Window win)443 snoop_legacy(Window win)
444 {
445 	Window parent, root, *kids = NULL;
446 	XSetWindowAttributes sattrs;
447 	unsigned int nkids = 0, i;
448 
449 	/*
450 	 * Firefox stops responding to keys when KeyPressMask is used, so
451 	 * settle for KeyReleaseMask
452 	 */
453 	int type = PointerMotionMask | KeyReleaseMask | Button1MotionMask |
454 		Button2MotionMask | Button3MotionMask | Button4MotionMask |
455 		Button5MotionMask | ButtonMotionMask;
456 
457 	if (XQueryTree(dpy, win, &root, &parent, &kids, &nkids) == FALSE) {
458 		warn("can't query window tree\n");
459 		goto done;
460 	}
461 
462 	XSelectInput(dpy, root, type);
463 
464 	/* listen for newly mapped windows */
465 	sattrs.event_mask = SubstructureNotifyMask;
466 	XChangeWindowAttributes(dpy, root, CWEventMask, &sattrs);
467 
468 	for (i = 0; i < nkids; i++) {
469 		XSelectInput(dpy, kids[i], type);
470 		snoop_legacy(kids[i]);
471 	}
472 
473 done:
474 	if (kids != NULL)
475 		XFree(kids); /* hide yo kids */
476 }
477 
478 void
usage(char * progname)479 usage(char *progname)
480 {
481 	fprintf(stderr, "usage: %s [-a] [-d] [-i mod] [-m nw|ne|sw|se]\n",
482 	    progname);
483 	exit(1);
484 }
485 
486 int
swallow_error(Display * d,XErrorEvent * e)487 swallow_error(Display *d, XErrorEvent *e)
488 {
489 	if (e->error_code == BadWindow)
490 		/* no biggie */
491 		return 0;
492 	else if (e->error_code & FirstExtensionError)
493 		/* error requesting input on a particular xinput device */
494 		return 0;
495 	else
496 		errx(1, "got X error %d", e->error_code);
497 }
498