[
  {
    "path": ".gitignore",
    "content": "*.o\nxbanish\n"
  },
  {
    "path": "LICENSE",
    "content": "xbanish\nCopyright (c) 2013-2021 joshua stein <jcs@jcs.org>\n\nPermission to use, copy, modify, and distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "# vim:ts=8\n\nCC\t?= cc\nCFLAGS\t?= -O2\nCFLAGS\t+= -Wall -Wunused -Wmissing-prototypes -Wstrict-prototypes\n\nPREFIX\t?= /usr/local\nBINDIR\t?= $(PREFIX)/bin\nMANDIR\t?= $(PREFIX)/man/man1\n\nINSTALL_PROGRAM ?= install -s\nINSTALL_DATA ?= install\n\nLIBS\t?= x11 xfixes xi xext\nINCLUDES?= `pkg-config --cflags $(LIBS)`\nLDFLAGS\t+= `pkg-config --libs $(LIBS)`\n\nPROG\t= xbanish\nOBJS\t= xbanish.o\n\nall: $(PROG)\n\n$(PROG): $(OBJS)\n\t$(CC) $(OBJS) $(LDFLAGS) -o $@\n\n$(OBJS): *.c\n\t$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@\n\ninstall: all\n\tmkdir -p $(DESTDIR)$(BINDIR)\n\t$(INSTALL_PROGRAM) $(PROG) $(DESTDIR)$(BINDIR)\n\tmkdir -p $(DESTDIR)$(MANDIR)\n\t$(INSTALL_DATA) -m 644 xbanish.1 $(DESTDIR)$(MANDIR)/xbanish.1\n\nclean:\n\trm -f $(PROG) $(OBJS)\n\n.PHONY: all install clean\n"
  },
  {
    "path": "README.md",
    "content": "## xbanish\n\nxbanish hides the mouse cursor when you start typing, and shows it again when\nthe mouse cursor moves or a mouse button is pressed.\nThis is similar to xterm's `pointerMode` setting, but xbanish works globally in\nthe X11 session.\n\nunclutter's -keystroke mode is supposed to do this, but it's\n[broken](https://bugs.launchpad.net/ubuntu/+source/unclutter/+bug/54148).\nI looked into fixing it, but unclutter was abandoned so I wrote xbanish.\n\nThe name comes from\n[ratpoison's](https://www.nongnu.org/ratpoison/)\n\"banish\" command that sends the cursor to the corner of the screen.\n\n### Implementation\n\nIf the XInput extension is supported, xbanish uses it to request input from all\nattached keyboards and mice.\nIf XInput 2.2 is supported, raw mouse movement and button press inputs are\nrequested which helps detect cursor movement while in certain applications such\nas Chromium.\n\nIf Xinput is not available, xbanish recurses through the list of windows\nstarting at the root, and calls `XSelectInput()` on each window to receive\nnotification of mouse motion, button presses, and key presses.\n\nIn response to any available keyboard input events, the cursor is hidden.\nOn mouse movement or button events, the cursor is shown.\n\nxbanish initially hid the cursor by calling `XGrabPointer()` with a blank\ncursor image, similar to unclutter's -grab mode, but this had problematic\ninteractions with certain X applications.\nFor example, xlock could not grab the pointer and sometimes didn't lock,\nxwininfo wouldn't work at all, Firefox would quickly hide the Awesome Bar\ndropdown as soon as a key was pressed, and xterm required two middle-clicks to\npaste the clipboard contents.\n\nTo avoid these problems and simplify the implementation, xbanish now uses the\nmodern\n[`Xfixes` extension](http://cgit.freedesktop.org/xorg/proto/fixesproto/plain/fixesproto.txt)\nto easily hide and show the cursor with `XFixesHideCursor()` and\n`XFixesShowCursor()`.\n"
  },
  {
    "path": "xbanish.1",
    "content": ".Dd $Mdocdate: March 13 2020$\n.Dt XBANISH 1\n.Os\n.Sh NAME\n.Nm xbanish\n.Nd hide the X11 mouse cursor when a key is pressed\n.Sh SYNOPSIS\n.Nm\n.Op Fl a\n.Op Fl d\n.Op Fl i Ar modifier\n.Op Fl m Oo Ar w Oc Ns Ar nw|ne|sw|se|\\(+-x\\(+-y\n.Op Fl t Ar seconds\n.Op Fl s\n.Sh DESCRIPTION\n.Nm\nhides the X11 mouse cursor when a key is pressed.\nThe cursor is shown again when it is moved or a mouse button is pressed.\nThis is similar to the\n.Xr xterm 1\nsetting\n.Ic pointerMode\nbut the effect is global in the X11 session.\n.Sh OPTIONS\n.Bl -tag -width Ds\n.It Fl a\nAlways keep mouse cursor hidden while\n.Nm\nis running.\n.It Fl d\nPrint debugging messages to stdout.\n.It Fl i Ar modifier\nIgnore pressed key if\n.Ar modifier\nis used.\nModifiers are:\n.Ic shift ,\n.Ic lock\n(CapsLock),\n.Ic control ,\n.Ic mod1\n(Alt or Meta),\n.Ic mod2\n(NumLock),\n.Ic mod3\n(Hyper),\n.Ic mod4\n(Super, Windows, or Command),\n.Ic mod5\n(ISO Level 3 Shift), and\n.Ic all\n.It Fl m Oo Ar w Oc Ns Ar nw|ne|sw|se|\\(+-x\\(+-y\nWhen hiding the mouse cursor, move it to this corner of the screen\nor current window, then move it back when showing the cursor.\nAlso accepts absolute positioning, for example `+50-100' will be\npositioned 50 pixels from the left and 100 pixels from the bottom.\nSee GEOMETRY SPECIFICATIONS of X(7) for more info.\n.It Fl t Ar seconds\nHide the mouse cursor after\n.Ic seconds\nhave passed without mouse movement.\n.It Fl s\nIgnore scrolling events.\n.El\n.Sh SEE ALSO\n.Xr XFixes 3\n.Xr X 7\n.Sh AUTHORS\n.Nm\nwas written by\n.An joshua stein Aq Mt jcs@jcs.org .\n"
  },
  {
    "path": "xbanish.c",
    "content": "/*\n * xbanish\n * Copyright (c) 2013-2021 joshua stein <jcs@jcs.org>\n *\n * Permission to use, copy, modify, and distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include <err.h>\n#include <signal.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n\n#include <X11/X.h>\n#include <X11/Xlib.h>\n#include <X11/extensions/sync.h>\n#include <X11/extensions/Xfixes.h>\n#include <X11/extensions/XInput.h>\n#include <X11/extensions/XInput2.h>\n#include <X11/Xutil.h>\n\nvoid hide_cursor(void);\nvoid show_cursor(void);\nvoid snoop_root(void);\nint snoop_xinput(Window);\nvoid snoop_legacy(Window);\nvoid set_alarm(XSyncAlarm *, XSyncTestType);\nvoid usage(char *);\nint swallow_error(Display *, XErrorEvent *);\nint parse_geometry(const char *s);\n\n/* xinput event type ids to be filled in later */\nstatic int button_press_type = -1;\nstatic int button_release_type = -1;\nstatic int key_press_type = -1;\nstatic int key_release_type = -1;\nstatic int motion_type = -1;\nstatic int device_change_type = -1;\nstatic long last_device_change = -1;\n\nstatic Display *dpy;\nstatic int hiding = 0, legacy = 0, always_hide = 0, ignore_scroll = 0;\nstatic unsigned timeout = 0;\nstatic unsigned char ignored;\nstatic XSyncCounter idler_counter = 0;\nstatic XSyncAlarm idle_alarm = None;\n\nstatic int debug = 0;\n#define DPRINTF(x) { if (debug) { printf x; } };\n\nstatic int move = 0, move_x, move_y, move_custom_x, move_custom_y, move_custom_mask;\nenum move_types {\n\tMOVE_NW = 1,\n\tMOVE_NE,\n\tMOVE_SW,\n\tMOVE_SE,\n\tMOVE_WIN_NW,\n\tMOVE_WIN_NE,\n\tMOVE_WIN_SW,\n\tMOVE_WIN_SE,\n\tMOVE_CUSTOM,\n};\n\nint\nmain(int argc, char *argv[])\n{\n\tint ch, i;\n\tXEvent e;\n\tXSyncAlarmNotifyEvent *alarm_e;\n\tXGenericEventCookie *cookie;\n\tXSyncSystemCounter *counters;\n\tint sync_event, error;\n\tint major, minor, ncounters;\n\n\tstruct mod_lookup {\n\t\tchar *name;\n\t\tint mask;\n\t} mods[] = {\n\t\t{\"shift\", ShiftMask}, {\"lock\", LockMask},\n\t\t{\"control\", ControlMask}, {\"mod1\", Mod1Mask},\n\t\t{\"mod2\", Mod2Mask}, {\"mod3\", Mod3Mask},\n\t\t{\"mod4\", Mod4Mask}, {\"mod5\", Mod5Mask},\n\t\t{\"all\", -1},\n\t};\n\n\twhile ((ch = getopt(argc, argv, \"adi:m:t:s\")) != -1)\n\t\tswitch (ch) {\n\t\tcase 'a':\n\t\t\talways_hide = 1;\n\t\t\tbreak;\n\t\tcase 'd':\n\t\t\tdebug = 1;\n\t\t\tbreak;\n\t\tcase 'i':\n\t\t\tfor (i = 0;\n\t\t\t    i < sizeof(mods) / sizeof(struct mod_lookup); i++)\n\t\t\t\tif (strcasecmp(optarg, mods[i].name) == 0)\n\t\t\t\t\tignored |= mods[i].mask;\n\n\t\t\tbreak;\n\t\tcase 'm':\n\t\t\tif (strcmp(optarg, \"nw\") == 0)\n\t\t\t\tmove = MOVE_NW;\n\t\t\telse if (strcmp(optarg, \"ne\") == 0)\n\t\t\t\tmove = MOVE_NE;\n\t\t\telse if (strcmp(optarg, \"sw\") == 0)\n\t\t\t\tmove = MOVE_SW;\n\t\t\telse if (strcmp(optarg, \"se\") == 0)\n\t\t\t\tmove = MOVE_SE;\n\t\t\telse if (strcmp(optarg, \"wnw\") == 0)\n\t\t\t\tmove = MOVE_WIN_NW;\n\t\t\telse if (strcmp(optarg, \"wne\") == 0)\n\t\t\t\tmove = MOVE_WIN_NE;\n\t\t\telse if (strcmp(optarg, \"wsw\") == 0)\n\t\t\t\tmove = MOVE_WIN_SW;\n\t\t\telse if (strcmp(optarg, \"wse\") == 0)\n\t\t\t\tmove = MOVE_WIN_SE;\n\t\t\telse if (parse_geometry(optarg))\n\t\t\t\tmove = MOVE_CUSTOM;\n\t\t\telse {\n\t\t\t\twarnx(\"invalid '-m' argument\");\n\t\t\t\tusage(argv[0]);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 't':\n\t\t\ttimeout = strtoul(optarg, NULL, 0);\n\t\t\tbreak;\n\t\tcase 's':\n\t\t\tignore_scroll = 1;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tusage(argv[0]);\n\t\t}\n\n\targc -= optind;\n\targv += optind;\n\n\tif (!(dpy = XOpenDisplay(NULL)))\n\t\terrx(1, \"can't open display %s\", XDisplayName(NULL));\n\n#ifdef __OpenBSD__\n\tif (pledge(\"stdio\", NULL) == -1)\n\t\terr(1, \"pledge\");\n#endif\n\n\tXSetErrorHandler(swallow_error);\n\n\tsnoop_root();\n\n\tif (always_hide)\n\t\thide_cursor();\n\n\t/* required setup for the xsync alarms used by timeout */\n\tif (timeout) {\n\t\tif (XSyncQueryExtension(dpy, &sync_event, &error) != True)\n\t\t\terrx(1, \"no sync extension available\");\n\n\t\tXSyncInitialize(dpy, &major, &minor);\n\n\t\tcounters = XSyncListSystemCounters(dpy, &ncounters);\n\t\tfor (i = 0; i < ncounters; i++) {\n\t\t\tif (!strcmp(counters[i].name, \"IDLETIME\")) {\n\t\t\t\tidler_counter = counters[i].counter;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tXSyncFreeSystemCounterList(counters);\n\n\t\tif (!idler_counter)\n\t\t\terrx(1, \"no idle counter\");\n\t}\n\n\tfor (;;) {\n\t\tcookie = &e.xcookie;\n\t\tXNextEvent(dpy, &e);\n\n\t\tint etype = e.type;\n\t\tif (e.type == motion_type)\n\t\t\tetype = MotionNotify;\n\t\telse if (e.type == key_press_type ||\n\t\t    e.type == key_release_type)\n\t\t\tetype = KeyRelease;\n\t\telse if (e.type == button_press_type ||\n\t\t    e.type == button_release_type)\n\t\t\tetype = ButtonRelease;\n\t\telse if (e.type == device_change_type) {\n\t\t\tXDevicePresenceNotifyEvent *xdpe =\n\t\t\t    (XDevicePresenceNotifyEvent *)&e;\n\t\t\tif (last_device_change == xdpe->serial)\n\t\t\t\tcontinue;\n\t\t\tsnoop_root();\n\t\t\tlast_device_change = xdpe->serial;\n\t\t\tcontinue;\n\t\t}\n\n\t\tswitch (etype) {\n\t\tcase KeyRelease:\n\t\t\tif (ignored) {\n\t\t\t\tunsigned int state = 0;\n\n\t\t\t\t/* masks are only set on key release, if\n\t\t\t\t * ignore is set we must throw out non-release\n\t\t\t\t * events here */\n\t\t\t\tif (e.type == key_press_type) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t/* extract modifier state */\n\t\t\t\tif (e.type == key_release_type) {\n\t\t\t\t\t/* xinput device event */\n\t\t\t\t\tXDeviceKeyEvent *key =\n\t\t\t\t\t    (XDeviceKeyEvent *) &e;\n\t\t\t\t\tstate = key->state;\n\t\t\t\t} else if (e.type == KeyRelease) {\n\t\t\t\t\t/* legacy event */\n\t\t\t\t\tstate = e.xkey.state;\n\t\t\t\t}\n\n\t\t\t\tif (state & ignored) {\n\t\t\t\t\tDPRINTF((\"ignoring key %d\\n\", state));\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\thide_cursor();\n\t\t\tbreak;\n\n\t\tcase ButtonRelease:\n\t\tcase MotionNotify:\n\t\t\tif (!always_hide)\n\t\t\t\tshow_cursor();\n\t\t\tbreak;\n\n\t\tcase CreateNotify:\n\t\t\tif (legacy) {\n\t\t\t\tDPRINTF((\"new window, snooping on it\\n\"));\n\n\t\t\t\t/* not sure why snooping directly on the window\n\t\t\t\t * doesn't work, so snoop on all windows from\n\t\t\t\t * its parent (probably root) */\n\t\t\t\tsnoop_legacy(e.xcreatewindow.parent);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase GenericEvent:\n\t\t\t/* xi2 raw event */\n\t\t\tXGetEventData(dpy, cookie);\n\t\t\tXIDeviceEvent *xie = (XIDeviceEvent *)cookie->data;\n\n\t\t\tswitch (xie->evtype) {\n\t\t\tcase XI_RawMotion:\n\t\t\tcase XI_RawButtonPress:\n\t\t\t\tif (ignore_scroll && ((xie->detail >= 4 && xie->detail <= 7) ||\n\t\t\t\t\t\txie->event_x == xie->event_y))\n\t\t\t\t\tbreak;\n\t\t\t\tif (!always_hide)\n\t\t\t\t\tshow_cursor();\n\t\t\t\tbreak;\n\n\t\t\tcase XI_RawButtonRelease:\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tDPRINTF((\"unknown XI event type %d\\n\",\n\t\t\t\t    xie->evtype));\n\t\t\t}\n\n\t\t\tXFreeEventData(dpy, cookie);\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tif (!timeout ||\n\t\t\t    e.type != (sync_event + XSyncAlarmNotify)) {\n\t\t\t\tDPRINTF((\"unknown event type %d\\n\", e.type));\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\talarm_e = (XSyncAlarmNotifyEvent *)&e;\n\t\t\tif (alarm_e->alarm == idle_alarm) {\n\t\t\t\tDPRINTF((\"idle counter reached %dms, hiding \"\n\t\t\t\t    \"cursor\\n\",\n\t\t\t\t    XSyncValueLow32(alarm_e->counter_value)));\n\t\t\t\thide_cursor();\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid\nhide_cursor(void)\n{\n\tWindow win;\n\tXWindowAttributes attrs;\n\tint x, y, h, w, junk;\n\tunsigned int ujunk;\n\n\tDPRINTF((\"keystroke, %shiding cursor\\n\", (hiding ? \"already \" : \"\")));\n\n\tif (hiding)\n\t\treturn;\n\n\tif (move) {\n\t\tif (XQueryPointer(dpy, DefaultRootWindow(dpy),\n\t\t    &win, &win, &x, &y, &junk, &junk, &ujunk)) {\n\t\t\tmove_x = x;\n\t\t\tmove_y = y;\n\n\t\t\tXGetWindowAttributes(dpy, win, &attrs);\n\n\t\t\th = XHeightOfScreen(DefaultScreenOfDisplay(dpy));\n\t\t\tw = XWidthOfScreen(DefaultScreenOfDisplay(dpy));\n\n\t\t\tswitch (move) {\n\t\t\tcase MOVE_NW:\n\t\t\t\tx = 0;\n\t\t\t\ty = 0;\n\t\t\t\tbreak;\n\t\t\tcase MOVE_NE:\n\t\t\t\tx = w;\n\t\t\t\ty = 0;\n\t\t\t\tbreak;\n\t\t\tcase MOVE_SW:\n\t\t\t\tx = 0;\n\t\t\t\ty = h;\n\t\t\t\tbreak;\n\t\t\tcase MOVE_SE:\n\t\t\t\tx = w;\n\t\t\t\ty = h;\n\t\t\t\tbreak;\n\t\t\tcase MOVE_WIN_NW:\n\t\t\t\tx = attrs.x;\n\t\t\t\ty = attrs.y;\n\t\t\t\tbreak;\n\t\t\tcase MOVE_WIN_NE:\n\t\t\t\tx = attrs.x + attrs.width;\n\t\t\t\ty = attrs.y;\n\t\t\t\tbreak;\n\t\t\tcase MOVE_WIN_SW:\n\t\t\t\tx = attrs.x;\n\t\t\t\ty = attrs.x + attrs.height;\n\t\t\t\tbreak;\n\t\t\tcase MOVE_WIN_SE:\n\t\t\t\tx = attrs.x + attrs.width;\n\t\t\t\ty = attrs.x + attrs.height;\n\t\t\t\tbreak;\n\t\t\tcase MOVE_CUSTOM:\n\t\t\t\tx = (move_custom_mask & XNegative ? w : 0) + move_custom_x;\n\t\t\t\ty = (move_custom_mask & YNegative ? h : 0) + move_custom_y;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tXWarpPointer(dpy, None, DefaultRootWindow(dpy),\n\t\t\t    0, 0, 0, 0, x, y);\n\t\t} else {\n\t\t\tmove_x = -1;\n\t\t\tmove_y = -1;\n\t\t\twarn(\"failed finding cursor coordinates\");\n\t\t}\n\t}\n\n\tXFixesHideCursor(dpy, DefaultRootWindow(dpy));\n\thiding = 1;\n}\n\nvoid\nshow_cursor(void)\n{\n\tDPRINTF((\"mouse moved, %sunhiding cursor\\n\",\n\t    (hiding ? \"\" : \"already \")));\n\n\tif (timeout) {\n\t\tDPRINTF((\"(re)setting timeout of %us\\n\", timeout));\n\t\tset_alarm(&idle_alarm, XSyncPositiveComparison);\n\t}\n\n\tif (!hiding)\n\t\treturn;\n\n\tif (move && move_x != -1 && move_y != -1)\n\t\tXWarpPointer(dpy, None, DefaultRootWindow(dpy), 0, 0, 0, 0,\n\t\t    move_x, move_y);\n\n\tXFixesShowCursor(dpy, DefaultRootWindow(dpy));\n\thiding = 0;\n}\n\nvoid\nsnoop_root(void)\n{\n\tif (snoop_xinput(DefaultRootWindow(dpy)) == 0) {\n\t\tDPRINTF((\"no XInput devices found, using legacy snooping\"));\n\t\tlegacy = 1;\n\t\tsnoop_legacy(DefaultRootWindow(dpy));\n\t}\n}\n\nint\nsnoop_xinput(Window win)\n{\n\tint opcode, event, error, numdevs, i, j;\n\tint major, minor, rc, rawmotion = 0;\n\tint ev = 0;\n\tunsigned char mask[(XI_LASTEVENT + 7)/8];\n\tXDeviceInfo *devinfo = NULL;\n\tXInputClassInfo *ici;\n\tXDevice *device;\n\tXIEventMask evmasks[1];\n\tXEventClass class_presence;\n\n\tif (!XQueryExtension(dpy, \"XInputExtension\", &opcode, &event, &error)) {\n\t\tDPRINTF((\"XInput extension not available\"));\n\t\treturn 0;\n\t}\n\n\t/*\n\t * If we support xinput 2, use that for raw motion and button events to\n\t * get pointer data when the cursor is over a Chromium window.  We\n\t * could also use this to get raw key input and avoid the other XInput\n\t * stuff, but we may need to be able to examine the key value later to\n\t * filter out ignored keys.\n\t */\n\tmajor = minor = 2;\n\trc = XIQueryVersion(dpy, &major, &minor);\n\tif (rc != BadRequest) {\n\t\tmemset(mask, 0, sizeof(mask));\n\n\t\tXISetMask(mask, XI_RawMotion);\n\t\tXISetMask(mask, XI_RawButtonPress);\n\t\tevmasks[0].deviceid = XIAllMasterDevices;\n\t\tevmasks[0].mask_len = sizeof(mask);\n\t\tevmasks[0].mask = mask;\n\n\t\tXISelectEvents(dpy, win, evmasks, 1);\n\t\tXFlush(dpy);\n\n\t\trawmotion = 1;\n\n\t\tDPRINTF((\"using xinput2 raw motion events\\n\"));\n\t}\n\n\tdevinfo = XListInputDevices(dpy, &numdevs);\n\tXEventClass event_list[numdevs * 2];\n\tfor (i = 0; i < numdevs; i++) {\n\t\tif (devinfo[i].use != IsXExtensionKeyboard &&\n\t\t    devinfo[i].use != IsXExtensionPointer)\n\t\t\tcontinue;\n\n\t\tif (!(device = XOpenDevice(dpy, devinfo[i].id)))\n\t\t\tbreak;\n\n\t\tfor (ici = device->classes, j = 0; j < devinfo[i].num_classes;\n\t\tici++, j++) {\n\t\t\tswitch (ici->input_class) {\n\t\t\tcase KeyClass:\n\t\t\t\tDPRINTF((\"attaching to keyboard device %s \"\n\t\t\t\t    \"(use %d)\\n\", devinfo[i].name,\n\t\t\t\t    devinfo[i].use));\n\n\t\t\t\tDeviceKeyPress(device, key_press_type,\n\t\t\t\t    event_list[ev]); ev++;\n\t\t\t\tDeviceKeyRelease(device, key_release_type,\n\t\t\t\t    event_list[ev]); ev++;\n\t\t\t\tbreak;\n\n\t\t\tcase ButtonClass:\n\t\t\t\tif (rawmotion)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tDPRINTF((\"attaching to buttoned device %s \"\n\t\t\t\t    \"(use %d)\\n\", devinfo[i].name,\n\t\t\t\t    devinfo[i].use));\n\n\t\t\t\tDeviceButtonPress(device, button_press_type,\n\t\t\t\t    event_list[ev]); ev++;\n\t\t\t\tDeviceButtonRelease(device,\n\t\t\t\t    button_release_type, event_list[ev]); ev++;\n\t\t\t\tbreak;\n\n\t\t\tcase ValuatorClass:\n\t\t\t\tif (rawmotion)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tDPRINTF((\"attaching to pointing device %s \"\n\t\t\t\t    \"(use %d)\\n\", devinfo[i].name,\n\t\t\t\t    devinfo[i].use));\n\n\t\t\t\tDeviceMotionNotify(device, motion_type,\n\t\t\t\t    event_list[ev]); ev++;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tXCloseDevice(dpy, device);\n\n\t\tif (XSelectExtensionEvent(dpy, win, event_list, ev)) {\n\t\t\twarn(\"error selecting extension events\");\n\t\t\tev = 0;\n\t\t\tgoto done;\n\t\t}\n\t}\n\n\tDevicePresence(dpy, device_change_type, class_presence);\n\tif (XSelectExtensionEvent(dpy, win, &class_presence, 1)) {\n\t\twarn(\"error selecting extension events\");\n\t\tev = 0;\n\t\tgoto done;\n\t}\n\ndone:\n\tif (devinfo != NULL)\n\t   XFreeDeviceList(devinfo);\n\n\treturn ev;\n}\n\nvoid\nsnoop_legacy(Window win)\n{\n\tWindow parent, root, *kids = NULL;\n\tXSetWindowAttributes sattrs;\n\tunsigned int nkids = 0, i;\n\n\t/*\n\t * Firefox stops responding to keys when KeyPressMask is used, so\n\t * settle for KeyReleaseMask\n\t */\n\tint type = PointerMotionMask | KeyReleaseMask | Button1MotionMask |\n\t\tButton2MotionMask | Button3MotionMask | Button4MotionMask |\n\t\tButton5MotionMask | ButtonMotionMask;\n\n\tif (XQueryTree(dpy, win, &root, &parent, &kids, &nkids) == 0) {\n\t\twarn(\"can't query window tree\\n\");\n\t\tgoto done;\n\t}\n\n\tXSelectInput(dpy, root, type);\n\n\t/* listen for newly mapped windows */\n\tsattrs.event_mask = SubstructureNotifyMask;\n\tXChangeWindowAttributes(dpy, root, CWEventMask, &sattrs);\n\n\tfor (i = 0; i < nkids; i++) {\n\t\tXSelectInput(dpy, kids[i], type);\n\t\tsnoop_legacy(kids[i]);\n\t}\n\ndone:\n\tif (kids != NULL)\n\t\tXFree(kids); /* hide yo kids */\n}\n\nvoid\nset_alarm(XSyncAlarm *alarm, XSyncTestType test)\n{\n\tXSyncAlarmAttributes attr;\n\tXSyncValue value;\n\tunsigned int flags;\n\n\tXSyncQueryCounter(dpy, idler_counter, &value);\n\n\tattr.trigger.counter = idler_counter;\n\tattr.trigger.test_type = test;\n\tattr.trigger.value_type = XSyncRelative;\n\tXSyncIntsToValue(&attr.trigger.wait_value, timeout * 1000,\n\t    (unsigned long)(timeout * 1000) >> 32);\n\tXSyncIntToValue(&attr.delta, 0);\n\n\tflags = XSyncCACounter | XSyncCATestType | XSyncCAValue | XSyncCADelta;\n\n\tif (*alarm)\n\t\tXSyncDestroyAlarm(dpy, *alarm);\n\n\t*alarm = XSyncCreateAlarm(dpy, flags, &attr);\n}\n\nvoid\nusage(char *progname)\n{\n\tfprintf(stderr, \"usage: %s [-a] [-d] [-i mod] [-m [w]nw|ne|sw|se|+/-xy] \"\n\t    \"[-t seconds] [-s]\\n\", progname);\n\texit(1);\n}\n\nint\nswallow_error(Display *d, XErrorEvent *e)\n{\n\tif (e->error_code == BadWindow)\n\t\t/* no biggie */\n\t\treturn 0;\n\n\tif (e->error_code & FirstExtensionError)\n\t\t/* error requesting input on a particular xinput device */\n\t\treturn 0;\n\n\terrx(1, \"got X error %d\", e->error_code);\n}\n\nint\nparse_geometry(const char *s)\n{\n\tint x, y;\n\tunsigned int junk;\n\tint ret = XParseGeometry(s, &x, &y, &junk, &junk);\n\tif (((ret & XValue) || (ret & XNegative)) &&\n\t    ((ret & YValue) || (ret & YNegative))) {\n\t\tmove_custom_x = x;\n\t\tmove_custom_y = y;\n\t\tmove_custom_mask = ret;\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n"
  }
]