diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | 1 | 166 | ||||
-rw-r--r-- | Makefile | 29 | ||||
-rw-r--r-- | README | 13 | ||||
-rw-r--r-- | version.h | 1 | ||||
-rw-r--r-- | version.rst | 1 | ||||
-rw-r--r-- | xdeadzone.1 | 160 | ||||
-rw-r--r-- | xdeadzone.c | 197 | ||||
-rw-r--r-- | xdeadzone.rst | 136 |
9 files changed, 706 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c0ebefd --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.o +core +xdeadzone @@ -0,0 +1,166 @@ +/* + xdeadzone - create a window and don't allow the mouse pointer to enter it. + + compile me with: + + gcc -Wall -o xdeadzone xdeadzone.c -lX11 + + run me with ./xdeadzone, and be prepared to hit ^C or use kill to exit, + since it doesn't have any other way to exit (currently). +*/ + +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#ifndef VERSION +# define VERSION "UnknownVersion" +#endif + +#define NAME "XDeadZone" + +const char *exename; + +void set_exename(const char *p) { + exename = p; + while(*p) { + if(*p == '/') exename = p + 1; + p++; + } + printf("exename: %s\n", exename); +} + +void usage(char *name, int ret) { + printf( + NAME " " VERSION "\n" + "Usage:\n %s " + "[-nw | -ne | -sw | -se | -abs xpos ypos] [width] [height]" + "\n", + name); + exit(ret); +} + +void errmsg(char *name, char *msg) { + fprintf(stderr, "%s: %s\n", name, msg); + usage(name, 1); +} + +int main(int argc, char **argv) { + Display *d; + Window w; + XEvent ev; + Atom dock_atom; + XWindowAttributes attr; + XSetWindowAttributes setattr; + int screen; + + int done = 0; + int x = 0, y = 0, width = 0, height = 0; + + if(argc == 2 && strcmp(argv[1], "--help") == 0) + usage(argv[0], 0); + + if(strcmp(argv[1], "-abs") == 0) { + if(argc != 6) + errmsg(argv[0], "wrong number of arguments for -abs"); + x = atoi(argv[2]); + y = atoi(argv[3]); + width = atoi(argv[4]); + height = atoi(argv[5]); + } else { + if(argc != 4) + errmsg(argv[0], "wrong number of arguments for -nw/-ne/-sw/-se"); + width = atoi(argv[2]); + height = atoi(argv[3]); + if(strcmp(argv[1], "-nw") == 0) { + x = 0; y = 0; + } else if(strcmp(argv[1], "-ne") == 0) { + x = -width; y = 0; + } else if(strcmp(argv[1], "-sw") == 0) { + x = 0; y = -height; + } else if(strcmp(argv[1], "-se") == 0) { + x = -width; y = -height; + } else { + errmsg(argv[0], "invalid 1st argument (not -abs/-nw/-ne/-sw/-se)"); + } + } + + if(width <= 0 || height <= 0) + errmsg(argv[0], "width and height must be positive and non-zero"); + + if(!(d = XOpenDisplay(NULL))) + errmsg(argv[0], "can't open X display"); + + XGetWindowAttributes(d, DefaultRootWindow(d), &attr); + if(x < 0) x = attr.width + x; + if(y < 0) y = attr.height + y; + + printf("X size %d x %d, x %d, y %d, width %d, height %d\n", attr.width, attr.height, x, y, width, height); + + /* Create window with override_redirect enabled, to tell the window + manager not to decorate it with a titlebar or resize frame. + Also, we really only care about EnterNotify events. + Set the window to solid white, to make it easy to see during testing. + In actual use, the window won't be visible, it'll be in the dead zone, + right? + */ + screen = DefaultScreen(d); + setattr.override_redirect = True; + setattr.background_pixel = WhitePixel(d, screen); + setattr.event_mask = EnterWindowMask; + + w = XCreateWindow(d, + RootWindow(d, screen), + x, y, width, height, 0, + DefaultDepth(d, screen), + InputOutput, + DefaultVisual(d, screen), + CWBackPixel | CWOverrideRedirect | CWEventMask, + &setattr); + + XStoreName(d, w, NAME); + + /* On most window managers, this makes the window stay on top and + be present on all virtual desktops. Tested, works with WindowMaker, Fvwm2, + KDE5, XFCE4, BlackBox, and FluxBox. On all but Fvwm2, it also + disappears the titlebar and resize frame... but we already took + care of that with override_redirect, above. + ref: http://standards.freedesktop.org/wm-spec/wm-spec-latest.html + */ + dock_atom = XInternAtom(d, "_NET_WM_WINDOW_TYPE_DOCK", False); + XChangeProperty(d, + w, + XInternAtom(d, "_NET_WM_WINDOW_TYPE", False), + XA_ATOM, + 32, + PropModeReplace, + (unsigned char *) &dock_atom, + 1); + + XMapWindow(d, w); + + /* have to do this after it's mapped, since the window manager might have + ignored the x and y in the XCreateWindow(). */ + XMoveWindow(d, w, x, y); + + while(!done) { + XNextEvent(d, &ev); + if(ev.type == EnterNotify) { + // printf("entered at %d, %d\n", ev.xcrossing.x_root, ev.xcrossing.y_root); + XWarpPointer(d, + None, + DefaultRootWindow(d), + 0, 0, 0, 0, + ev.xcrossing.x_root, + (y == 0 ? y + height : y - 1)); + } + } + + XUnmapWindow(d, w); + XDestroyWindow(d, w); + XCloseDisplay(d); + + return 0; +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b0ccfc2 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +# GNU Makefile for xdeadzone(1). May also work with BSD make, since I +# don't use any GNU-specific features. + +# Optimization flags for $(CC) +OPTFLAGS=-O2 -fPIC + +# Add any extra libraries you need here. +LIBS= + +# Shouldn't need to override these. +VERSION=0.1 +CFLAGS=-Wall -DVERSION="\"$(VERSION)"\" $(OPTFLAGS) +LDFLAGS=-lX11 $(LIBS) + +all: xdeadzone xdeadzone.1 + +xdeadzone: xdeadzone.c + +xdeadzone.1: xdeadzone.rst + echo ".. |version| replace:: $(VERSION)" > version.rst + rst2man xdeadzone.rst > xdeadzone.1 + +clean: + rm -f xdeadzone *.o core + +realclean: clean + rm -f xdeadzone.1 version.rst + +.PHONY: all clean realclean @@ -0,0 +1,13 @@ +README for XDeadZone +==================== + +xdeadzone's job is to create a window of a specified size, and prevent +the mouse pointer from entering it. + +The intended use for it is to keep the mouse out of the "dead zone" +of a multi-head X display where the monitors don't all have the same +resolution. It could also be useful for covering annoying parts of +the screen, e.g. advertisements in ad-driven software like the Opera +browser, or Adobe Reader. + +See the man page for more information. diff --git a/version.h b/version.h new file mode 100644 index 0000000..1eeff9a --- /dev/null +++ b/version.h @@ -0,0 +1 @@ +#define VERSION "0.1" diff --git a/version.rst b/version.rst new file mode 100644 index 0000000..f502eab --- /dev/null +++ b/version.rst @@ -0,0 +1 @@ +.. |version| replace:: 0.1 diff --git a/xdeadzone.1 b/xdeadzone.1 new file mode 100644 index 0000000..0e4a61d --- /dev/null +++ b/xdeadzone.1 @@ -0,0 +1,160 @@ +.\" Man page generated from reStructuredText. +. +. +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +.TH "XDEADZONE" 1 "2024-01-31" "0.1" "Urchlay's Misc Stuff" +.SH NAME +xdeadzone \- keep the mouse pointer out of the dead zone, on mismatched multihead displays. +.\" RST source for xdeadzone(1) man page. Convert with: +. +.\" rst2man.py xdeadzone.rst > xdeadzone.1 +. +.SH SYNOPSIS +.sp +xdeadzone [\fB\-nw\fP | \fB\-ne\fP | \fB\-sw\fP | \fB\-se\fP | \fB\-abs\fP \fIx\-position\fP \fIy\-position\fP] \fIwidth\fP \fIheight\fP +.sp +xdeadzone \fB\-\-help\fP | \fB\-\-version\fP +.SH DESCRIPTION +.sp +xdeadzone\(aqs job is to create a window of a specified size, and prevent +the mouse pointer from entering it. +.sp +The intended use for it is to keep the mouse out of the "dead zone" +of a multi\-head X display where the monitors don\(aqt all have the same +resolution. +.sp +It could also be useful for covering annoying parts of the screen, +e.g. advertisements in ad\-driven software like the Opera browser, or +Adobe Reader. +.SH OPTIONS +.INDENT 0.0 +.TP +.B \-\-help +Print built\-in help message and exit. +.TP +.B \-\-version +Print the application name and version number, and exit. +.UNINDENT +.INDENT 0.0 +.TP +.B \fB\-nw\fP +Place window at northwest (top left) corner of display. +.TP +.B \fB\-ne\fP +Place window at northeast (top right) corner of display. +.TP +.B \fB\-sw\fP +Place window at southwest (bottom left) corner of display. +.TP +.B \fB\-se\fP +Place window at southeast (bottom right) corner of display. +.TP +.B \fB\-abs\fP \fIx\-position\fP \fIy\-position\fP +Place window at the given coordinates. Negative numbers will be +treated as offsets from the right/bottom of the display. +.TP +.B \fBwidth\fP +Width of the dead zone. Required; must be a positive integer. +.TP +.B \fBheight\fP +Height of the dead zone. Required; must be a positive integer. +.UNINDENT +.SH ENVIRONMENT +.INDENT 0.0 +.TP +.B \fBDISPLAY\fP +As usual for X applications: the X server to connect to. +.UNINDENT +.SH EXIT STATUS +.sp +With \fB\-\-help\fP or \fB\-\-version\fP, exit status is 0 (success). +.sp +If there\(aqs an error in the arguments, exit status is non\-zero (failure). +.sp +In normal operation, \fBxdeadzone\fP never exits. +.SH EXAMPLES +.sp +You have a 1920x1080 LCD monitor on the left, and a 1280x1024 +one on the right. This gives you a nice 3200x1080 X display... but the +mouse can "vanish", because X pretends the right\-hand monitor has +1080 vertical pixels. So there\(aqs a 1280x56 horizontal strip "below" the +bottom of the right\-hand monitor that doesn\(aqt get displayed. If the +mouse moves into this area, the pointer disappears, and it\(aqs not +obvious what happened to it. +.sp +To avoid losing the pointer, you can run this: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +xdeadzone \-se 1280 56 & +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +\&...from your ~/.xinitrc (or whatever you use to run commands at X startup). +.sp +If you instead have the same two monitors in a vertical arrangement, +with the 1280x1024 one on top, you\(aqll have a 640x1024 vertical strip +of \(aqdead zone\(aq beyond the right edge of the top monitor. To avoid +losing the mouse there: +.INDENT 0.0 +.INDENT 3.5 +.sp +.nf +.ft C +xdeadzone \-ne 640 1024 & +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +If the dead zone were on the left of the top monitor, you\(aqd use \fB\-nw\fP instead +of \fB\-ne\fP\&. +.SH BUGS +.sp +There isn\(aqt much error\-checking for the numeric arguments. Anything +non\-numeric will be read as zero. If you include a decimal point, +that should be an error, but instead it\(aqs silently ignored (the value +is truncated). +.sp +Maybe it should background (daemonize) itself. However, it works +fine with & to background it, and this is pretty common practice for +starting X software from ~/.xinitrc. +.SH COPYRIGHT +.sp +WTFPL. Do WTF you want to with this. +.sp +See \fI\%http://www.wtfpl.net/txt/copying/\fP for details. +.SH AUTHORS +.sp +\fBxdeadzone\fP was written by B. Watson (\fI\%urchlay@slackware.uk\fP). +.\" Generated by docutils manpage writer. +. diff --git a/xdeadzone.c b/xdeadzone.c new file mode 100644 index 0000000..ab36f8c --- /dev/null +++ b/xdeadzone.c @@ -0,0 +1,197 @@ +/* + xdeadzone - create a window and don't allow the mouse pointer to enter it. + + Operation is simple: set up our window, wait for events, and if we get + an EnterNotify event, warp the pointer out of our window. +*/ + +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#ifndef VERSION +# define VERSION "UnknownVersion" +#endif + +#define NAME "XDeadZone" + +/* define this to print some debug info at runtime. */ +// #define DEBUG + +#ifdef DEBUG +# define DBG(x) x +#else +# define DBG(x) +#endif + +const char *exe_name; + +void set_exe_name(const char *p) { + exe_name = p; + while(*p) { + if(*p == '/') exe_name = p + 1; + p++; + } +} + +void banner(void) { + printf(NAME " " VERSION "\n"); +} + +void usage(const int ret) { + banner(); + printf( + "Usage:\n %s " + "[-nw | -ne | -sw | -se | -abs xpos ypos] [width] [height]" + "\n", + exe_name); + exit(ret); +} + +void errmsg(const char *msg) { + fprintf(stderr, "%s: %s\n", exe_name, msg); + usage(1); +} + +int streq(const char *s1, const char *s2) { + return !strcmp(s1, s2); +} + +int main(int argc, char **argv) { + Display *d; + Window w; + XEvent ev; + Atom dock_atom; + XWindowAttributes attr; + XSetWindowAttributes setattr; + + int x = 0, y = 0, width = 0, height = 0; + + set_exe_name(argv[0]); + + if(argc < 2) + usage(1); + + if(argc == 2) { + if(streq(argv[1], "--help")) { + usage(0); + } else if(streq(argv[1], "--version")) { + banner(); + exit(0); + } else { + usage(1); + } + } + + if(streq(argv[1], "-abs")) { + if(argc != 6) + errmsg("wrong number of arguments for -abs"); + x = atoi(argv[2]); + y = atoi(argv[3]); + width = atoi(argv[4]); + height = atoi(argv[5]); + } else { + if(argc != 4) + errmsg("wrong number of arguments for non-abs mode"); + width = atoi(argv[2]); + height = atoi(argv[3]); + if(streq(argv[1], "-nw")) { + x = 0; y = 0; + } else if(streq(argv[1], "-ne")) { + x = -width; y = 0; + } else if(streq(argv[1], "-sw")) { + x = 0; y = -height; + } else if(streq(argv[1], "-se")) { + x = -width; y = -height; + } else { + errmsg("invalid first argument (not -abs/-nw/-ne/-sw/-se)"); + } + } + + if(width <= 0 || height <= 0) + errmsg("width and height must be positive and non-zero"); + + if(!(d = XOpenDisplay(NULL))) + errmsg("can't open X display"); + + XGetWindowAttributes(d, DefaultRootWindow(d), &attr); + if(x < 0) x = attr.width + x; + if(y < 0) y = attr.height + y; + + DBG(printf("X size %d x %d, x %d, y %d, width %d, height %d\n", + attr.width, attr.height, x, y, width, height)); + + /* Create window with override_redirect enabled, to tell the window + manager not to decorate it with a titlebar or resize frame. + Also, we really only care about EnterNotify events. + Set the window to solid white, to make it easy to see during testing. + In actual use, the window won't be visible, it'll be in the dead zone, + right? + */ + setattr.override_redirect = True; + setattr.background_pixel = WhitePixel(d, DefaultScreen(d)); + setattr.event_mask = EnterWindowMask; + + w = XCreateWindow(d, + DefaultRootWindow(d), + x, y, width, height, 0, + CopyFromParent, + InputOutput, + CopyFromParent, + CWBackPixel | CWOverrideRedirect | CWEventMask, + &setattr); + + DBG(printf("window created\n")); + + XStoreName(d, w, NAME); + + /* On most window managers, this makes the window stay on top and + be present on all virtual desktops. Tested, works with WindowMaker, Fvwm2, + KDE5, XFCE4, BlackBox, and FluxBox. On all but Fvwm2, it also + disappears the titlebar and resize frame... but we already took + care of that with override_redirect, above. + ref: http://standards.freedesktop.org/wm-spec/wm-spec-latest.html + */ + dock_atom = XInternAtom(d, "_NET_WM_WINDOW_TYPE_DOCK", False); + XChangeProperty(d, + w, + XInternAtom(d, "_NET_WM_WINDOW_TYPE", False), + XA_ATOM, + 32, + PropModeReplace, + (unsigned char *) &dock_atom, + 1); + + XMapWindow(d, w); + + /* Have to do this after it's mapped, since the window manager might have + ignored the x and y in the XCreateWindow(). */ + XMoveWindow(d, w, x, y); + + while(1) { + XNextEvent(d, &ev); + if(ev.type == EnterNotify) { + /* warp down if at top of screen */ + int new_y = (y == 0 ? y + height : y - 1); + + DBG(printf("Got EnterNotify, coords %d, %d, new Y is %d\n", + ev.xcrossing.x_root, ev.xcrossing.y_root, new_y)); + XWarpPointer(d, + None, + DefaultRootWindow(d), + 0, 0, 0, 0, + ev.xcrossing.x_root, + new_y); + } + } + + /* We should never get here (the loop above never exits). + The only way out is for us or the X server to be killed. */ + XUnmapWindow(d, w); + XDestroyWindow(d, w); + XCloseDisplay(d); + + return 0; +} diff --git a/xdeadzone.rst b/xdeadzone.rst new file mode 100644 index 0000000..ff12bd7 --- /dev/null +++ b/xdeadzone.rst @@ -0,0 +1,136 @@ +.. RST source for xdeadzone(1) man page. Convert with: +.. rst2man.py xdeadzone.rst > xdeadzone.1 + +.. include:: version.rst +.. |date| date:: + +========= +xdeadzone +========= + +------------------------------------------------------------------------------ +keep the mouse pointer out of the dead zone, on mismatched multihead displays. +------------------------------------------------------------------------------ + +:Manual section: 1 +:Manual group: Urchlay's Misc Stuff +:Date: |date| +:Version: |version| + +SYNOPSIS +======== + +xdeadzone [**-nw** | **-ne** | **-sw** | **-se** | **-abs** *x-position* *y-position*] *width* *height* + +xdeadzone **--help** | **--version** + +DESCRIPTION +=========== + +xdeadzone's job is to create a window of a specified size, and prevent +the mouse pointer from entering it. + +The intended use for it is to keep the mouse out of the "dead zone" +of a multi-head X display where the monitors don't all have the same +resolution. + +It could also be useful for covering annoying parts of the screen, +e.g. advertisements in ad-driven software like the Opera browser, or +Adobe Reader. + +OPTIONS +======= + +--help + Print built-in help message and exit. + +--version + Print the application name and version number, and exit. + +**-nw** + Place window at northwest (top left) corner of display. + +**-ne** + Place window at northeast (top right) corner of display. + +**-sw** + Place window at southwest (bottom left) corner of display. + +**-se** + Place window at southeast (bottom right) corner of display. + +**-abs** *x-position* *y-position* + Place window at the given coordinates. Negative numbers will be + treated as offsets from the right/bottom of the display. + +**width** + Width of the dead zone. Required; must be a positive integer. + +**height** + Height of the dead zone. Required; must be a positive integer. + +ENVIRONMENT +=========== + +**DISPLAY** + As usual for X applications: the X server to connect to. + +EXIT STATUS +=========== + +With **--help** or **--version**, exit status is 0 (success). + +If there's an error in the arguments, exit status is non-zero (failure). + +In normal operation, **xdeadzone** never exits. + +EXAMPLES +======== + +You have a 1920x1080 LCD monitor on the left, and a 1280x1024 +one on the right. This gives you a nice 3200x1080 X display... but the +mouse can "vanish", because X pretends the right-hand monitor has +1080 vertical pixels. So there's a 1280x56 horizontal strip "below" the +bottom of the right-hand monitor that doesn't get displayed. If the +mouse moves into this area, the pointer disappears, and it's not +obvious what happened to it. + +To avoid losing the pointer, you can run this:: + + xdeadzone -se 1280 56 & + +...from your ~/.xinitrc (or whatever you use to run commands at X startup). + +If you instead have the same two monitors in a vertical arrangement, +with the 1280x1024 one on top, you'll have a 640x1024 vertical strip +of 'dead zone' beyond the right edge of the top monitor. To avoid +losing the mouse there:: + + xdeadzone -ne 640 1024 & + +If the dead zone were on the left of the top monitor, you'd use **-nw** instead +of **-ne**. + +BUGS +==== + +There isn't much error-checking for the numeric arguments. Anything +non-numeric will be read as zero. If you include a decimal point, +that should be an error, but instead it's silently ignored (the value +is truncated). + +Maybe it should background (daemonize) itself. However, it works +fine with & to background it, and this is pretty common practice for +starting X software from ~/.xinitrc. + +COPYRIGHT +========= + +WTFPL. Do WTF you want to with this. + +See http://www.wtfpl.net/txt/copying/ for details. + +AUTHORS +======= + +**xdeadzone** was written by B. Watson (urchlay@slackware.uk). |