Repository: jcs/xbanish
Branch: master
Commit: 189ce79b1df4
Files: 6
Total size: 18.8 KB
Directory structure:
gitextract_63wvmd0k/
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── xbanish.1
└── xbanish.c
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.o
xbanish
================================================
FILE: LICENSE
================================================
xbanish
Copyright (c) 2013-2021 joshua stein <jcs@jcs.org>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
================================================
FILE: Makefile
================================================
# vim:ts=8
CC ?= cc
CFLAGS ?= -O2
CFLAGS += -Wall -Wunused -Wmissing-prototypes -Wstrict-prototypes
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
MANDIR ?= $(PREFIX)/man/man1
INSTALL_PROGRAM ?= install -s
INSTALL_DATA ?= install
LIBS ?= x11 xfixes xi xext
INCLUDES?= `pkg-config --cflags $(LIBS)`
LDFLAGS += `pkg-config --libs $(LIBS)`
PROG = xbanish
OBJS = xbanish.o
all: $(PROG)
$(PROG): $(OBJS)
$(CC) $(OBJS) $(LDFLAGS) -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
================================================
FILE: README.md
================================================
## 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 unclutter was abandoned 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()`.
================================================
FILE: xbanish.1
================================================
.Dd $Mdocdate: March 13 2020$
.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 Oo Ar w Oc Ns Ar nw|ne|sw|se|\(+-x\(+-y
.Op Fl t Ar seconds
.Op Fl s
.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),
.Ic mod5
(ISO Level 3 Shift), and
.Ic all
.It Fl m Oo Ar w Oc Ns Ar nw|ne|sw|se|\(+-x\(+-y
When hiding the mouse cursor, move it to this corner of the screen
or current window, then move it back when showing the cursor.
Also accepts absolute positioning, for example `+50-100' will be
positioned 50 pixels from the left and 100 pixels from the bottom.
See GEOMETRY SPECIFICATIONS of X(7) for more info.
.It Fl t Ar seconds
Hide the mouse cursor after
.Ic seconds
have passed without mouse movement.
.It Fl s
Ignore scrolling events.
.El
.Sh SEE ALSO
.Xr XFixes 3
.Xr X 7
.Sh AUTHORS
.Nm
was written by
.An joshua stein Aq Mt jcs@jcs.org .
================================================
FILE: xbanish.c
================================================
/*
* xbanish
* Copyright (c) 2013-2021 joshua stein <jcs@jcs.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <err.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/extensions/sync.h>
#include <X11/extensions/Xfixes.h>
#include <X11/extensions/XInput.h>
#include <X11/extensions/XInput2.h>
#include <X11/Xutil.h>
void hide_cursor(void);
void show_cursor(void);
void snoop_root(void);
int snoop_xinput(Window);
void snoop_legacy(Window);
void set_alarm(XSyncAlarm *, XSyncTestType);
void usage(char *);
int swallow_error(Display *, XErrorEvent *);
int parse_geometry(const char *s);
/* 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, ignore_scroll = 0;
static unsigned timeout = 0;
static unsigned char ignored;
static XSyncCounter idler_counter = 0;
static XSyncAlarm idle_alarm = None;
static int debug = 0;
#define DPRINTF(x) { if (debug) { printf x; } };
static int move = 0, move_x, move_y, move_custom_x, move_custom_y, move_custom_mask;
enum move_types {
MOVE_NW = 1,
MOVE_NE,
MOVE_SW,
MOVE_SE,
MOVE_WIN_NW,
MOVE_WIN_NE,
MOVE_WIN_SW,
MOVE_WIN_SE,
MOVE_CUSTOM,
};
int
main(int argc, char *argv[])
{
int ch, i;
XEvent e;
XSyncAlarmNotifyEvent *alarm_e;
XGenericEventCookie *cookie;
XSyncSystemCounter *counters;
int sync_event, error;
int major, minor, ncounters;
struct mod_lookup {
char *name;
int mask;
} mods[] = {
{"shift", ShiftMask}, {"lock", LockMask},
{"control", ControlMask}, {"mod1", Mod1Mask},
{"mod2", Mod2Mask}, {"mod3", Mod3Mask},
{"mod4", Mod4Mask}, {"mod5", Mod5Mask},
{"all", -1},
};
while ((ch = getopt(argc, argv, "adi:m:t:s")) != -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 if (strcmp(optarg, "wnw") == 0)
move = MOVE_WIN_NW;
else if (strcmp(optarg, "wne") == 0)
move = MOVE_WIN_NE;
else if (strcmp(optarg, "wsw") == 0)
move = MOVE_WIN_SW;
else if (strcmp(optarg, "wse") == 0)
move = MOVE_WIN_SE;
else if (parse_geometry(optarg))
move = MOVE_CUSTOM;
else {
warnx("invalid '-m' argument");
usage(argv[0]);
}
break;
case 't':
timeout = strtoul(optarg, NULL, 0);
break;
case 's':
ignore_scroll = 1;
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();
/* required setup for the xsync alarms used by timeout */
if (timeout) {
if (XSyncQueryExtension(dpy, &sync_event, &error) != True)
errx(1, "no sync extension available");
XSyncInitialize(dpy, &major, &minor);
counters = XSyncListSystemCounters(dpy, &ncounters);
for (i = 0; i < ncounters; i++) {
if (!strcmp(counters[i].name, "IDLETIME")) {
idler_counter = counters[i].counter;
break;
}
}
XSyncFreeSystemCounterList(counters);
if (!idler_counter)
errx(1, "no idle counter");
}
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 (ignore_scroll && ((xie->detail >= 4 && xie->detail <= 7) ||
xie->event_x == xie->event_y))
break;
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:
if (!timeout ||
e.type != (sync_event + XSyncAlarmNotify)) {
DPRINTF(("unknown event type %d\n", e.type));
break;
}
alarm_e = (XSyncAlarmNotifyEvent *)&e;
if (alarm_e->alarm == idle_alarm) {
DPRINTF(("idle counter reached %dms, hiding "
"cursor\n",
XSyncValueLow32(alarm_e->counter_value)));
hide_cursor();
}
}
}
}
void
hide_cursor(void)
{
Window win;
XWindowAttributes attrs;
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;
XGetWindowAttributes(dpy, win, &attrs);
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;
case MOVE_WIN_NW:
x = attrs.x;
y = attrs.y;
break;
case MOVE_WIN_NE:
x = attrs.x + attrs.width;
y = attrs.y;
break;
case MOVE_WIN_SW:
x = attrs.x;
y = attrs.x + attrs.height;
break;
case MOVE_WIN_SE:
x = attrs.x + attrs.width;
y = attrs.x + attrs.height;
break;
case MOVE_CUSTOM:
x = (move_custom_mask & XNegative ? w : 0) + move_custom_x;
y = (move_custom_mask & YNegative ? h : 0) + move_custom_y;
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 (timeout) {
DPRINTF(("(re)setting timeout of %us\n", timeout));
set_alarm(&idle_alarm, XSyncPositiveComparison);
}
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) == 0) {
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
set_alarm(XSyncAlarm *alarm, XSyncTestType test)
{
XSyncAlarmAttributes attr;
XSyncValue value;
unsigned int flags;
XSyncQueryCounter(dpy, idler_counter, &value);
attr.trigger.counter = idler_counter;
attr.trigger.test_type = test;
attr.trigger.value_type = XSyncRelative;
XSyncIntsToValue(&attr.trigger.wait_value, timeout * 1000,
(unsigned long)(timeout * 1000) >> 32);
XSyncIntToValue(&attr.delta, 0);
flags = XSyncCACounter | XSyncCATestType | XSyncCAValue | XSyncCADelta;
if (*alarm)
XSyncDestroyAlarm(dpy, *alarm);
*alarm = XSyncCreateAlarm(dpy, flags, &attr);
}
void
usage(char *progname)
{
fprintf(stderr, "usage: %s [-a] [-d] [-i mod] [-m [w]nw|ne|sw|se|+/-xy] "
"[-t seconds] [-s]\n", progname);
exit(1);
}
int
swallow_error(Display *d, XErrorEvent *e)
{
if (e->error_code == BadWindow)
/* no biggie */
return 0;
if (e->error_code & FirstExtensionError)
/* error requesting input on a particular xinput device */
return 0;
errx(1, "got X error %d", e->error_code);
}
int
parse_geometry(const char *s)
{
int x, y;
unsigned int junk;
int ret = XParseGeometry(s, &x, &y, &junk, &junk);
if (((ret & XValue) || (ret & XNegative)) &&
((ret & YValue) || (ret & YNegative))) {
move_custom_x = x;
move_custom_y = y;
move_custom_mask = ret;
return 1;
}
return 0;
}
gitextract_63wvmd0k/ ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── xbanish.1 └── xbanish.c
SYMBOL INDEX (11 symbols across 1 files)
FILE: xbanish.c
type move_types (line 63) | enum move_types {
function main (line 75) | int
function hide_cursor (line 300) | void
function show_cursor (line 376) | void
function snoop_root (line 398) | void
function snoop_xinput (line 408) | int
function snoop_legacy (line 527) | void
function set_alarm (line 563) | void
function usage (line 587) | void
function swallow_error (line 595) | int
function parse_geometry (line 609) | int
Condensed preview — 6 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (22K chars).
[
{
"path": ".gitignore",
"chars": 12,
"preview": "*.o\nxbanish\n"
},
{
"path": "LICENSE",
"chars": 754,
"preview": "xbanish\nCopyright (c) 2013-2021 joshua stein <jcs@jcs.org>\n\nPermission to use, copy, modify, and distribute this softwar"
},
{
"path": "Makefile",
"chars": 735,
"preview": "# vim:ts=8\n\nCC\t?= cc\nCFLAGS\t?= -O2\nCFLAGS\t+= -Wall -Wunused -Wmissing-prototypes -Wstrict-prototypes\n\nPREFIX\t?= /usr/loc"
},
{
"path": "README.md",
"chars": 1942,
"preview": "## xbanish\n\nxbanish hides the mouse cursor when you start typing, and shows it again when\nthe mouse cursor moves or a mo"
},
{
"path": "xbanish.1",
"chars": 1508,
"preview": ".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"
},
{
"path": "xbanish.c",
"chars": 14350,
"preview": "/*\n * xbanish\n * Copyright (c) 2013-2021 joshua stein <jcs@jcs.org>\n *\n * Permission to use, copy, modify, and distribut"
}
]
About this extraction
This page contains the full source code of the jcs/xbanish GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 6 files (18.8 KB), approximately 5.9k tokens, and a symbol index with 11 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.