/* 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 #include #include #include #include #include #include #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, *corner; #define GEOM_LEN 64 char geom[GEOM_LEN+1]; 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 " "[-b | -w ] [ -n ] [ -nw | -ne | -sw | -se ] [geometry]\n\n" " is WxH with -nw/-ne/-sw/-se, or\n WxH[+-]xpos[+-]ypos\n", exe_name); exit(ret); } void errmsg(const char *msg) { fprintf(stderr, "%s: %s\n\n", exe_name, msg); usage(1); } int streq(const char *s1, const char *s2) { return !strcmp(s1, s2); } /* return true if control + any of the Mod[1-5] are both pressed. */ int ctrl_and_mod(unsigned int state) { if(state & ControlMask) if(state & (Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask)) return 1; return 0; } void set_corner(const char *geom) { if(corner) errmsg("multiple modes given, only one of -ne -nw -se -sw is allowed"); corner = geom; } /* preparse geometry, because XParseGeometry doesn't do enough error checking. It'll allow stuff like 100x100x100, or 100x100+foo, or 64x-64-64, with no complaint. */ void preparse_geom(const char *geom) { int x_count = 0, plus_minus_count = 0, bogus_count = 0, len = 0; const char *p = geom; while(*p) { len++; if(*p == 'x') x_count++; else if(*p == '+' || *p == '-') plus_minus_count++; else if(!isdigit(*p)) bogus_count++; p++; } if(!x_count) errmsg("bad geometry: no 'x' character"); else if(len < 3) errmsg("bad geometry: must be at least 3 characters"); else if(len > 28) /* allows for 6-digit x/y/w/h */ errmsg("bad geometry: string too long"); else if(x_count > 1) errmsg("bad geometry: multiple 'x' characters"); else if(plus_minus_count == 1) errmsg("bad geometry: missing +/- character in position"); else if(plus_minus_count > 2) errmsg("bad geometry: too many +/- characters"); else if(bogus_count) errmsg("bad geometry: invalid characters, only digits, +, -, x allowed"); } int main(int argc, char **argv) { Display *d; Window w; XEvent ev; Atom dock_atom; XWindowAttributes w_attr, root_attr; XSetWindowAttributes setattr; XSizeHints hints; int x = -1, y = -1, gflags = -1, visible = 0, black = 0, normal_window = 0; unsigned int width, height; int new_x, new_y; set_exe_name(argv[0]); while(*++argv) { const char *a = *argv; DBG(printf("arg: %s\n", a)); if(streq(a, "--help")) { usage(0); } else if(streq(a, "--version")) { banner(); exit(0); } else if(streq(a, "-b")) { visible = 1; black = 1; } else if(streq(a, "-w")) { visible = 1; black = 0; } else if(streq(a, "-n")) { visible = 1; normal_window = 1; } else if(streq(a, "-ne")) { set_corner("-0+0"); } else if(streq(a, "-nw")) { set_corner("+0+0"); } else if(streq(a, "-se")) { set_corner("-0-0"); } else if(streq(a, "-sw")) { set_corner("+0-0"); } else if(a[0] == '-') { fprintf(stderr, "%s: invalid option: %s\n\n", exe_name, a); usage(1); } else { /* no dash, must be geometry */ if(geom[0]) errmsg("multiple geometry arguments given"); else strncpy(geom, a, GEOM_LEN); } } if(!geom[0]) errmsg("missing required geometry argument"); if(corner) { if(strchr(geom, '+') || strchr(geom, '-')) errmsg("bad geometry: X and Y positions not allowed with -ne -nw -se -sw"); else strncat(geom, corner, GEOM_LEN); } preparse_geom(geom); gflags = XParseGeometry(geom, &x, &y, &width, &height); if(gflags == -1) errmsg("bad geometry (failed to parse)"); else DBG(printf("XParseGeometry got %d %d %u %u\n", x, y, width, height)); if(width == 0 || height == 0) errmsg("bad geometry: width and height must be non-zero"); if((int)width < 0 || (int)height < 0) errmsg("bad geometry: width and height may not be negative"); if(!corner && !(gflags & XValue) && !(gflags & YValue)) errmsg("bad geometry: X and Y positions must be given, with absolute positioning"); if(!(d = XOpenDisplay(NULL))) errmsg("can't open X display"); XGetWindowAttributes(d, DefaultRootWindow(d), &root_attr); if(gflags & XNegative) x = (root_attr.width + x) - width; if(gflags & YNegative) y = (root_attr.height + y) - height; DBG(printf("X size %d x %d, x %d, y %d, width %d, height %d\n", root_attr.width, root_attr.height, x, y, width, height)); /* without -n, create window with override_redirect enabled, to tell the window manager not to decorate it with a titlebar or resize frame. */ setattr.override_redirect = normal_window ? False : True; if(visible) { setattr.background_pixel = black ? BlackPixel(d, DefaultScreen(d)) : WhitePixel(d, DefaultScreen(d)); } setattr.event_mask = EnterWindowMask | VisibilityChangeMask | StructureNotifyMask; w = XCreateWindow(d, DefaultRootWindow(d), x, y, width, height, 0, CopyFromParent, (visible ? InputOutput : InputOnly), CopyFromParent, (visible ? CWBackPixel : 0) | CWOverrideRedirect | CWEventMask, &setattr); DBG(printf("window created\n")); if(!normal_window) { hints.min_width = hints.max_width = hints.width = width; hints.min_height = hints.max_height = hints.height = height; hints.flags = PSize | PMinSize | PMaxSize; XSetWMNormalHints(d, w, &hints); } 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 */ if(!normal_window) { 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); DBG(printf("awaiting events...\n")); while(1) { XNextEvent(d, &ev); switch(ev.type) { case VisibilityNotify: /* always on top (even in normal window mode) */ XRaiseWindow(d, w); XFlush(d); break; case ConfigureNotify: if(!normal_window) { /* this shouldn't be needed, but some WMs might allow moving/resizing the window even though it's override_redirect */ XMoveResizeWindow(d, w, x, y, width, height); XFlush(d); } break; case EnterNotify: /* meat and potatoes here */ if(!ctrl_and_mod(ev.xcrossing.state)) { Window dummy; int real_x, real_y; XGetWindowAttributes(d, w, &w_attr); XTranslateCoordinates(d, w, DefaultRootWindow(d), 0, 0, &real_x, &real_y, &dummy); DBG(printf("Got EnterNotify, real_x %d, real_y %d, height %d, width %d\n", real_x, real_y, w_attr.height, w_attr.width)); /* Where do we warp the pointer to? It would be better if we had a way to find out the previous pointer position, so we could know which direction it's moving. But, in the specific (but usual) case of deadzones in the corner of the screen, the dumb logic below is good enough. */ new_x = ev.xcrossing.x_root; new_y = ev.xcrossing.y_root; if(w_attr.height < root_attr.height) { /* window is less than full height of the screen, warp vertically */ if(w_attr.y == 0) new_y = real_y + w_attr.height; /* it's at the top, warp down. */ else new_y = real_y - 1; /* it's not at the top, warp up */ } else { /* window *is* full height of the screen, have to warp horizonally */ if(w_attr.x == 0) new_x = real_x + w_attr.width; /* it's at the left edge, warp right */ else new_x = real_x - 1; /* it's not at the left edge, warp left */ } DBG(printf(" old X/Y were %d, %d; new X/Y are %d, %d\n", ev.xcrossing.x_root, ev.xcrossing.y_root, new_x, new_y)); XWarpPointer(d, None, DefaultRootWindow(d), 0, 0, 0, 0, new_x, new_y); } break; case MapNotify: case ReparentNotify: /* ignore these */ break; default: DBG(printf("got an unexpected XEvent type %d\n", ev.type)); break; } } /* 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; }