/* 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 #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 enum { M_UNSET, M_ABS, M_NE, M_NW, M_SE, M_SW }; 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 " "<-b | -w | -i> <-n> [-nw | -ne | -sw | -se | -abs] [geometry]\n\n" " is WxH for all modes but -abs, or\n WxH[+-]xpos[+-]ypos for -abs\n", exe_name); exit(ret); } void errmsg(const char *msg) { fprintf(stderr, "%s: %s\n", exe_name, msg); usage(1); } void check_mode(int mode) { if(mode != M_UNSET) errmsg("multiple modes given, only one of -abs -ne -nw -se -sw is allowed"); } 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 = -1, y = -1, gflags = -1, visible = 0, black = 0, mode = M_UNSET, normal_window = 0; unsigned int width, height; 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, "-i")) { visible = 0; } else if(streq(a, "-n")) { visible = 1; normal_window = 1; } else if(streq(a, "-abs")) { check_mode(mode); mode = M_ABS; } else if(streq(a, "-ne")) { check_mode(mode); mode = M_NE; } else if(streq(a, "-nw")) { check_mode(mode); mode = M_NW; } else if(streq(a, "-se")) { check_mode(mode); mode = M_SE; } else if(streq(a, "-sw")) { check_mode(mode); mode = M_SW; } else if(a[0] == '-') { fprintf(stderr, "%s: invalid option: %s (try --help)\n", exe_name, a); exit(1); } else { /* no dash, must be geometry */ if(gflags == -1) gflags = XParseGeometry(a, &x, &y, &width, &height); else errmsg("multiple geometry arguments given (try --help)"); } } if(gflags == -1) errmsg("missing required geometry argument (try --help)"); else DBG(printf("XParseGeometry got %d %d %d %d\n", x, y, width, height)); if(mode != M_ABS && (gflags & (XValue | YValue))) errmsg("bad geometry: X and Y position not allowed without -abs"); switch(mode) { case M_ABS: if(!(gflags & (XValue | YValue))) errmsg("bad geometry: -abs requires X and Y position"); break; case M_NW: x = 0; y = 0; break; case M_NE: x = -width; y = 0; break; case M_SW: x = 0; y = -height; break; case M_SE: x = -width; y = -height; break; default: errmsg("no mode given, one of -abs -ne -nw -se -sw is required"); } if(width == 0 || height == 0) errmsg("bad geometry: width and height must be non-zero"); if(normal_window && !visible) errmsg("cannot combine -i (invisible) with -n (normal window)"); if(!(d = XOpenDisplay(NULL))) errmsg("can't open X display"); XGetWindowAttributes(d, DefaultRootWindow(d), &attr); if(mode == M_ABS) { if(gflags & XNegative) x = (attr.width + x) - width; if(gflags & YNegative) y = (attr.height + y) - height; } else { 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 = normal_window ? False : True; if(visible) { setattr.background_pixel = black ? BlackPixel(d, DefaultScreen(d)) : WhitePixel(d, DefaultScreen(d)); } setattr.event_mask = EnterWindowMask; 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")); 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 EnterNotify events...\n")); 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; }