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