pax_global_header00006660000000000000000000000064136554157730014532gustar00rootroot0000000000000052 comment=e54bdd873f51d8b87a66a781f7bfeecf6e054143 xbanish-1.7/000077500000000000000000000000001365541577300130355ustar00rootroot00000000000000xbanish-1.7/.gitignore000066400000000000000000000000141365541577300150200ustar00rootroot00000000000000*.o xbanish xbanish-1.7/Makefile000066400000000000000000000013611365541577300144760ustar00rootroot00000000000000# vim:ts=8 CC ?= cc CFLAGS ?= -O2 CFLAGS += -Wall -Wunused -Wmissing-prototypes -Wstrict-prototypes -Wunused PREFIX ?= /usr/local BINDIR ?= $(PREFIX)/bin MANDIR ?= $(PREFIX)/man/man1 INSTALL_PROGRAM ?= install -s INSTALL_DATA ?= install X11BASE ?= /usr/X11R6 INCLUDES?= -I$(X11BASE)/include LDPATH ?= -L$(X11BASE)/lib LIBS += -lX11 -lXfixes -lXi PROG = xbanish OBJS = xbanish.o all: $(PROG) $(PROG): $(OBJS) $(CC) $(OBJS) $(LDPATH) $(LIBS) -o $@ $(OBJS): *.c $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ install: all mkdir -p $(DESTDIR)$(BINDIR) $(INSTALL_PROGRAM) $(PROG) $(DESTDIR)$(BINDIR) mkdir -p $(DESTDIR)$(MANDIR) $(INSTALL_DATA) -m 644 xbanish.1 $(DESTDIR)$(MANDIR)/xbanish.1 clean: rm -f $(PROG) $(OBJS) .PHONY: all install clean xbanish-1.7/README.md000066400000000000000000000036451365541577300143240ustar00rootroot00000000000000## xbanish xbanish hides the mouse cursor when you start typing, and shows it again when the mouse cursor moves or a mouse button is pressed. This is similar to xterm's `pointerMode` setting, but xbanish works globally in the X11 session. unclutter's -keystroke mode is supposed to do this, but it's [broken](https://bugs.launchpad.net/ubuntu/+source/unclutter/+bug/54148). I looked into fixing it, but the unclutter source code is terrible, so I wrote xbanish. The name comes from [ratpoison's](https://www.nongnu.org/ratpoison/) "banish" command that sends the cursor to the corner of the screen. ### Implementation If the XInput extension is supported, xbanish uses it to request input from all attached keyboards and mice. If XInput 2.2 is supported, raw mouse movement and button press inputs are requested which helps detect cursor movement while in certain applications such as Chromium. If Xinput is not available, xbanish recurses through the list of windows starting at the root, and calls `XSelectInput()` on each window to receive notification of mouse motion, button presses, and key presses. In response to any available keyboard input events, the cursor is hidden. On mouse movement or button events, the cursor is shown. xbanish initially hid the cursor by calling `XGrabPointer()` with a blank cursor image, similar to unclutter's -grab mode, but this had problematic interactions with certain X applications. For example, xlock could not grab the pointer and sometimes didn't lock, xwininfo wouldn't work at all, Firefox would quickly hide the Awesome Bar dropdown as soon as a key was pressed, and xterm required two middle-clicks to paste the clipboard contents. To avoid these problems and simplify the implementation, xbanish now uses the modern [`Xfixes` extension](http://cgit.freedesktop.org/xorg/proto/fixesproto/plain/fixesproto.txt) to easily hide and show the cursor with `XFixesHideCursor()` and `XFixesShowCursor()`. xbanish-1.7/xbanish.1000066400000000000000000000020761365541577300145600ustar00rootroot00000000000000.Dd $Mdocdate: September 16 2019$ .Dt XBANISH 1 .Os .Sh NAME .Nm xbanish .Nd hide the X11 mouse cursor when a key is pressed .Sh SYNOPSIS .Nm .Op Fl a .Op Fl d .Op Fl i Ar modifier .Op Fl m Ar nw|ne|sw|se .Sh DESCRIPTION .Nm hides the X11 mouse cursor when a key is pressed. The cursor is shown again when it is moved or a mouse button is pressed. This is similar to the .Xr xterm 1 setting .Ic pointerMode but the effect is global in the X11 session. .Sh OPTIONS .Bl -tag -width Ds .It Fl a Always keep mouse cursor hidden while .Nm is running. .It Fl d Print debugging messages to stdout. .It Fl i Ar modifier Ignore pressed key if .Ar modifier is used. Modifiers are: .Ic shift , .Ic lock (CapsLock), .Ic control , .Ic mod1 (Alt or Meta), .Ic mod2 (NumLock), .Ic mod3 (Hyper), .Ic mod4 (Super, Windows, or Command), and .Ic mod5 (ISO Level 3 Shift). .It Fl m Ar nw|ne|sw|se When hiding the mouse cursor, move it to this corner of the screen, then move it back when showing the cursor. .El .Sh SEE ALSO .Xr XFixes 3 .Sh AUTHORS .Nm was written by .An joshua stein Aq Mt jcs@jcs.org . xbanish-1.7/xbanish.c000066400000000000000000000264321365541577300146440ustar00rootroot00000000000000/* * xbanish * Copyright (c) 2013-2015 joshua stein * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include void hide_cursor(void); void show_cursor(void); void snoop_root(void); int snoop_xinput(Window); void snoop_legacy(Window); void usage(char *); int swallow_error(Display *, XErrorEvent *); /* xinput event type ids to be filled in later */ static int button_press_type = -1; static int button_release_type = -1; static int key_press_type = -1; static int key_release_type = -1; static int motion_type = -1; static int device_change_type = -1; static long last_device_change = -1; static Display *dpy; static int hiding = 0, legacy = 0, always_hide = 0; static unsigned char ignored; static int debug = 0; #define DPRINTF(x) { if (debug) { printf x; } }; static int move = 0, move_x, move_y; enum move_types { MOVE_NW = 1, MOVE_NE, MOVE_SW, MOVE_SE, }; int main(int argc, char *argv[]) { int ch, i; XEvent e; XGenericEventCookie *cookie; struct mod_lookup { char *name; int mask; } mods[] = { {"shift", ShiftMask}, {"lock", LockMask}, {"control", ControlMask}, {"mod1", Mod1Mask}, {"mod2", Mod2Mask}, {"mod3", Mod3Mask}, {"mod4", Mod4Mask}, {"mod5", Mod5Mask} }; while ((ch = getopt(argc, argv, "adi:m:")) != -1) switch (ch) { case 'a': always_hide = 1; break; case 'd': debug = 1; break; case 'i': for (i = 0; i < sizeof(mods) / sizeof(struct mod_lookup); i++) if (strcasecmp(optarg, mods[i].name) == 0) ignored |= mods[i].mask; break; case 'm': if (strcmp(optarg, "nw") == 0) move = MOVE_NW; else if (strcmp(optarg, "ne") == 0) move = MOVE_NE; else if (strcmp(optarg, "sw") == 0) move = MOVE_SW; else if (strcmp(optarg, "se") == 0) move = MOVE_SE; else { warnx("invalid '-m' argument"); usage(argv[0]); } break; default: usage(argv[0]); } argc -= optind; argv += optind; if (!(dpy = XOpenDisplay(NULL))) errx(1, "can't open display %s", XDisplayName(NULL)); #ifdef __OpenBSD__ if (pledge("stdio", NULL) == -1) err(1, "pledge"); #endif XSetErrorHandler(swallow_error); snoop_root(); if (always_hide) hide_cursor(); for (;;) { cookie = &e.xcookie; XNextEvent(dpy, &e); int etype = e.type; if (e.type == motion_type) etype = MotionNotify; else if (e.type == key_press_type || e.type == key_release_type) etype = KeyRelease; else if (e.type == button_press_type || e.type == button_release_type) etype = ButtonRelease; else if (e.type == device_change_type) { XDevicePresenceNotifyEvent *xdpe = (XDevicePresenceNotifyEvent *)&e; if (last_device_change == xdpe->serial) continue; snoop_root(); last_device_change = xdpe->serial; continue; } switch (etype) { case KeyRelease: if (ignored) { unsigned int state = 0; /* masks are only set on key release, if * ignore is set we must throw out non-release * events here */ if (e.type == key_press_type) { break; } /* extract modifier state */ if (e.type == key_release_type) { /* xinput device event */ XDeviceKeyEvent *key = (XDeviceKeyEvent *) &e; state = key->state; } else if (e.type == KeyRelease) { /* legacy event */ state = e.xkey.state; } if (state & ignored) { DPRINTF(("ignoring key %d\n", state)); break; } } hide_cursor(); break; case ButtonRelease: case MotionNotify: if (!always_hide) show_cursor(); break; case CreateNotify: if (legacy) { DPRINTF(("new window, snooping on it\n")); /* not sure why snooping directly on the window * doesn't work, so snoop on all windows from * its parent (probably root) */ snoop_legacy(e.xcreatewindow.parent); } break; case GenericEvent: /* xi2 raw event */ XGetEventData(dpy, cookie); XIDeviceEvent *xie = (XIDeviceEvent *)cookie->data; switch (xie->evtype) { case XI_RawMotion: case XI_RawButtonPress: if (!always_hide) show_cursor(); break; case XI_RawButtonRelease: break; default: DPRINTF(("unknown XI event type %d\n", xie->evtype)); } XFreeEventData(dpy, cookie); break; default: DPRINTF(("unknown event type %d\n", e.type)); } } } void hide_cursor(void) { Window win; int x, y, h, w, junk; unsigned int ujunk; DPRINTF(("keystroke, %shiding cursor\n", (hiding ? "already " : ""))); if (hiding) return; if (move) { if (XQueryPointer(dpy, DefaultRootWindow(dpy), &win, &win, &x, &y, &junk, &junk, &ujunk)) { move_x = x; move_y = y; h = XHeightOfScreen(DefaultScreenOfDisplay(dpy)); w = XWidthOfScreen(DefaultScreenOfDisplay(dpy)); switch (move) { case MOVE_NW: x = 0; y = 0; break; case MOVE_NE: x = w; y = 0; break; case MOVE_SW: x = 0; y = h; break; case MOVE_SE: x = w; y = h; break; } XWarpPointer(dpy, None, DefaultRootWindow(dpy), 0, 0, 0, 0, x, y); } else { move_x = -1; move_y = -1; warn("failed finding cursor coordinates"); } } XFixesHideCursor(dpy, DefaultRootWindow(dpy)); hiding = 1; } void show_cursor(void) { DPRINTF(("mouse moved, %sunhiding cursor\n", (hiding ? "" : "already "))); if (!hiding) return; if (move && move_x != -1 && move_y != -1) XWarpPointer(dpy, None, DefaultRootWindow(dpy), 0, 0, 0, 0, move_x, move_y); XFixesShowCursor(dpy, DefaultRootWindow(dpy)); hiding = 0; } void snoop_root(void) { if (snoop_xinput(DefaultRootWindow(dpy)) == 0) { DPRINTF(("no XInput devices found, using legacy snooping")); legacy = 1; snoop_legacy(DefaultRootWindow(dpy)); } } int snoop_xinput(Window win) { int opcode, event, error, numdevs, i, j; int major, minor, rc, rawmotion = 0; int ev = 0; unsigned char mask[(XI_LASTEVENT + 7)/8]; XDeviceInfo *devinfo = NULL; XInputClassInfo *ici; XDevice *device; XIEventMask evmasks[1]; XEventClass class_presence; if (!XQueryExtension(dpy, "XInputExtension", &opcode, &event, &error)) { DPRINTF(("XInput extension not available")); return 0; } /* * If we support xinput 2, use that for raw motion and button events to * get pointer data when the cursor is over a Chromium window. We * could also use this to get raw key input and avoid the other XInput * stuff, but we may need to be able to examine the key value later to * filter out ignored keys. */ major = minor = 2; rc = XIQueryVersion(dpy, &major, &minor); if (rc != BadRequest) { memset(mask, 0, sizeof(mask)); XISetMask(mask, XI_RawMotion); XISetMask(mask, XI_RawButtonPress); evmasks[0].deviceid = XIAllMasterDevices; evmasks[0].mask_len = sizeof(mask); evmasks[0].mask = mask; XISelectEvents(dpy, win, evmasks, 1); XFlush(dpy); rawmotion = 1; DPRINTF(("using xinput2 raw motion events\n")); } devinfo = XListInputDevices(dpy, &numdevs); XEventClass event_list[numdevs * 2]; for (i = 0; i < numdevs; i++) { if (devinfo[i].use != IsXExtensionKeyboard && devinfo[i].use != IsXExtensionPointer) continue; if (!(device = XOpenDevice(dpy, devinfo[i].id))) break; for (ici = device->classes, j = 0; j < devinfo[i].num_classes; ici++, j++) { switch (ici->input_class) { case KeyClass: DPRINTF(("attaching to keyboard device %s " "(use %d)\n", devinfo[i].name, devinfo[i].use)); DeviceKeyPress(device, key_press_type, event_list[ev]); ev++; DeviceKeyRelease(device, key_release_type, event_list[ev]); ev++; break; case ButtonClass: if (rawmotion) continue; DPRINTF(("attaching to buttoned device %s " "(use %d)\n", devinfo[i].name, devinfo[i].use)); DeviceButtonPress(device, button_press_type, event_list[ev]); ev++; DeviceButtonRelease(device, button_release_type, event_list[ev]); ev++; break; case ValuatorClass: if (rawmotion) continue; DPRINTF(("attaching to pointing device %s " "(use %d)\n", devinfo[i].name, devinfo[i].use)); DeviceMotionNotify(device, motion_type, event_list[ev]); ev++; break; } } XCloseDevice(dpy, device); if (XSelectExtensionEvent(dpy, win, event_list, ev)) { warn("error selecting extension events"); ev = 0; goto done; } } DevicePresence(dpy, device_change_type, class_presence); if (XSelectExtensionEvent(dpy, win, &class_presence, 1)) { warn("error selecting extension events"); ev = 0; goto done; } done: if (devinfo != NULL) XFreeDeviceList(devinfo); return ev; } void snoop_legacy(Window win) { Window parent, root, *kids = NULL; XSetWindowAttributes sattrs; unsigned int nkids = 0, i; /* * Firefox stops responding to keys when KeyPressMask is used, so * settle for KeyReleaseMask */ int type = PointerMotionMask | KeyReleaseMask | Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask | ButtonMotionMask; if (XQueryTree(dpy, win, &root, &parent, &kids, &nkids) == FALSE) { warn("can't query window tree\n"); goto done; } XSelectInput(dpy, root, type); /* listen for newly mapped windows */ sattrs.event_mask = SubstructureNotifyMask; XChangeWindowAttributes(dpy, root, CWEventMask, &sattrs); for (i = 0; i < nkids; i++) { XSelectInput(dpy, kids[i], type); snoop_legacy(kids[i]); } done: if (kids != NULL) XFree(kids); /* hide yo kids */ } void usage(char *progname) { fprintf(stderr, "usage: %s [-a] [-d] [-i mod] [-m nw|ne|sw|se]\n", progname); exit(1); } int swallow_error(Display *d, XErrorEvent *e) { if (e->error_code == BadWindow) /* no biggie */ return 0; else if (e->error_code & FirstExtensionError) /* error requesting input on a particular xinput device */ return 0; else errx(1, "got X error %d", e->error_code); }