Another copy of my dotfiles. Because I don't completely trust GitHub.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

487 lines
11 KiB

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