1 /*
2  * unclutter: remove idle cursor image from screen so that it doesnt
3  * obstruct the area you are looking at.
4  * doesn't do it if cursor is in root window or a button is down.
5  * polls mouse to see if is stationary, or waits for a keyup event on the
6  * screen.  These will only arrive in windows of applications that dont
7  * wait for keyup themselves.  We could only do better by using the XTest
8  * extensions and so getting all keystroke events.
9  * Tries to cope with jitter if you have a mouse that twitches.
10  * Unfortunately, clients like emacs set different text cursor
11  * shapes depending on whether they have pointer focus or not.
12  * Try to kid them with a synthetic EnterNotify event.
13  * Whereas version 1 did a grab cursor, version 2 creates a small subwindow.
14  * This may work better with some window managers.
15  * Some servers return a Visibility event when the subwindow is mapped.
16  * Sometimes this is Unobscured, or even FullyObscured. Ignore these and
17  * rely on LeaveNotify events. (An InputOnly window is not supposed to get
18  * visibility events.)
19  * Mark M Martin. cetia feb 1994  mmm@cetia.fr
20  * keystroke code from Bill Trost trost@cloud.rain.com
21  * 11May2011 idlecommand added.
22  */
23 #include <X11/Xos.h>
24 #include <X11/Xlib.h>
25 #include <X11/Xutil.h>
26 #include <X11/Xproto.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include "vroot.h"
30 
31 char *progname;
pexit(str)32 pexit(str)char *str;{
33     fprintf(stderr,"%s: %s\n",progname,str);
34     exit(1);
35 }
usage()36 usage(){
37     pexit("usage:\n\
38 	-display <display>\n\
39 	-idle <seconds>		time between polls to detect idleness.\n\
40 	-keystroke		wait for keystroke before idling.\n\
41 	-jitter <pixels>	pixels mouse can twitch without moving\n\
42 	-exec <idlecommand>	execute idlecommand on idle.\n\
43 	-grab			use grabpointer method not createwindow\n\
44 	-reset			reset the timer whenever cursor becomes\n\
45 					visible even if it hasn't moved\n\
46  	-root	       		apply to cursor on root window too\n\
47 	-onescreen		apply only to given screen of display\n\
48  	-visible       		ignore visibility events\n\
49  	-noevents      		dont send pseudo events\n\
50 	-not names...		dont apply to windows whose wm-name begins.\n\
51 				(must be last argument)");
52 }
53 
54 #define ALMOSTEQUAL(a,b) (abs(a-b)<=jitter)
55 #define ANYBUTTON (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask)
56 
57 /* Since the small window we create is a child of the window the pointer is
58  * in, it can be destroyed by its adoptive parent.  Hence our destroywindow()
59  * can return an error, saying it no longer exists.  Similarly, the parent
60  * window can disappear while we are trying to create the child. Trap and
61  * ignore these errors.
62  */
63 int (*defaulthandler)();
errorhandler(display,error)64 int errorhandler(display,error)
65 Display *display;
66 XErrorEvent *error;
67 {
68     if(error->error_code!=BadWindow)
69 	(*defaulthandler)(display,error);
70 }
71 
72 char **names;	/* -> argv list of names to avoid */
73 
74 /*
75  * return true if window has a wm_name and the start of it matches
76  * one of the given names to avoid
77  */
nameinlist(display,window)78 nameinlist(display,window)
79 Display *display;
80 Window window;
81 {
82     char **cpp;
83     char *name;
84 
85     if(names==0)return 0;
86     if(XFetchName (display, window, &name)){
87 	for(cpp = names;*cpp!=0;cpp++){
88 	    if(strncmp(*cpp,name,strlen(*cpp))==0)
89 		break;
90 	}
91 	XFree(name);
92 	return(*cpp!=0);
93     }
94     return 0;
95 }
96 /*
97  * create a small 1x1 curssor with all pixels masked out on the given screen.
98  */
createnullcursor(display,root)99 createnullcursor(display,root)
100 Display *display;
101 Window root;
102 {
103     Pixmap cursormask;
104     XGCValues xgc;
105     GC gc;
106     XColor dummycolour;
107     Cursor cursor;
108 
109     cursormask = XCreatePixmap(display, root, 1, 1, 1/*depth*/);
110     xgc.function = GXclear;
111     gc =  XCreateGC(display, cursormask, GCFunction, &xgc);
112     XFillRectangle(display, cursormask, gc, 0, 0, 1, 1);
113     dummycolour.pixel = 0;
114     dummycolour.red = 0;
115     dummycolour.flags = 04;
116     cursor = XCreatePixmapCursor(display, cursormask, cursormask,
117 	      &dummycolour,&dummycolour, 0,0);
118     XFreePixmap(display,cursormask);
119     XFreeGC(display,gc);
120     return cursor;
121 }
122 
main(argc,argv)123 main(argc,argv)char **argv;{
124     Display *display;
125     int screen,oldx = -99,oldy = -99,numscreens;
126     int doroot = 0, jitter = 0, idletime = 5, usegrabmethod = 0, waitagain = 0,
127 	dovisible = 1, doevents = 1, onescreen = 0;
128     Cursor *cursor;
129     Window *realroot;
130     Window root;
131     char *displayname = 0;
132     char *idlecommand = 0;
133 
134     progname = *argv;
135     argc--;
136     while(argv++,argc-->0){
137 	if(strcmp(*argv,"-idle")==0){
138 	    argc--,argv++;
139 	    if(argc<0)usage();
140 	    idletime = atoi(*argv);
141 	}else if(strcmp(*argv,"-exec")==0){
142 	    argc--,argv++;
143 	    if(argc<0)usage();
144 	    idlecommand = *argv;
145 	}else if(strcmp(*argv,"-keystroke")==0){
146 	    idletime = -1;
147 	}else if(strcmp(*argv,"-jitter")==0){
148 	    argc--,argv++;
149 	    if(argc<0)usage();
150 	    jitter = atoi(*argv);
151 	}else if(strcmp(*argv,"-noevents")==0){
152 	    doevents = 0;
153 	}else if(strcmp(*argv,"-root")==0){
154 	    doroot = 1;
155 	}else if(strcmp(*argv,"-grab")==0){
156 	    usegrabmethod = 1;
157 	}else if(strcmp(*argv,"-reset")==0){
158 	    waitagain = 1;
159 	}else if(strcmp(*argv,"-onescreen")==0){
160 	    onescreen = 1;
161 	}else if(strcmp(*argv,"-visible")==0){
162 	    dovisible = 0;
163 	}else if(strcmp(*argv,"-not")==0){
164 	    /* take rest of srg list */
165 	    names = ++argv;
166 	    if(*names==0)names = 0;	/* no args follow */
167 	    argc = 0;
168 	}else if(strcmp(*argv,"-display")==0 || strcmp(*argv,"-d")==0){
169 	    argc--,argv++;
170 	    if(argc<0)usage();
171 	    displayname = *argv;
172 	}else usage();
173     }
174     display = XOpenDisplay(displayname);
175     if(display==0)pexit("could not open display");
176     numscreens = ScreenCount(display);
177     cursor = (Cursor*) malloc(numscreens*sizeof(Cursor));
178     realroot = (Window*) malloc(numscreens*sizeof(Window));
179 
180     /* each screen needs its own empty cursor.
181      * note each real root id so can find which screen we are on
182      */
183     for(screen = 0;screen<numscreens;screen++)
184 	if(onescreen && screen!=DefaultScreen(display)){
185 	    realroot[screen] = -1;
186 	    cursor[screen] = -1;
187 	}else{
188 	    realroot[screen] = XRootWindow(display,screen);
189 	    cursor[screen] = createnullcursor(display,realroot[screen]);
190 	    if(idletime<0)
191 		XSelectInput(display,realroot[screen],KeyReleaseMask);
192 	}
193     screen = DefaultScreen(display);
194     root = VirtualRootWindow(display,screen);
195 
196     if(!usegrabmethod)
197 	defaulthandler = XSetErrorHandler(errorhandler);
198     /*
199      * create a small unmapped window on a screen just so xdm can use
200      * it as a handle on which to killclient() us.
201      */
202     XCreateWindow(display, realroot[screen], 0,0,1,1, 0, CopyFromParent,
203 		 InputOutput, CopyFromParent, 0, (XSetWindowAttributes*)0);
204 
205     while(1){
206 	Window dummywin,windowin,newroot;
207 	int rootx,rooty,winx,winy;
208 	unsigned int modifs;
209 	Window lastwindowavoided = None;
210 
211 	/*
212 	 * wait for pointer to not move and no buttons down
213 	 * or if triggered by keystroke check no buttons down
214 	 */
215 	while(1){
216 	    if(idletime<0){		/* wait for keystroke trigger */
217 		XEvent event;
218 		do{
219 		    XNextEvent(display,&event);
220 		}while(event.type != KeyRelease ||
221 		       (event.xkey.state & ANYBUTTON));
222 		oldx = event.xkey.x_root;
223 		oldy = event.xkey.y_root;
224 	    }
225 	    if(!XQueryPointer(display, root, &newroot, &windowin,
226 			 &rootx, &rooty, &winx, &winy, &modifs)){
227 		/* window manager with virtual root may have restarted
228 		 * or we have changed screens */
229 		if(!onescreen){
230 		    for(screen = 0;screen<numscreens;screen++)
231 			if(newroot==realroot[screen])break;
232 		    if(screen>=numscreens)
233 			pexit("not on a known screen");
234 		}
235 		root = VirtualRootWindow(display,screen);
236 	    }else if((!doroot && windowin==None) || (modifs & ANYBUTTON) ||
237 		     !(ALMOSTEQUAL(rootx,oldx) && ALMOSTEQUAL(rooty,oldy))){
238 		oldx = rootx, oldy = rooty;
239 	    }else if(windowin==None){
240 		windowin = root;
241 		break;
242 	    }else if(windowin!=lastwindowavoided){
243 		/* descend tree of windows under cursor to bottommost */
244 		Window childin;
245 		int toavoid = xFalse;
246 		lastwindowavoided = childin = windowin;
247 		do{
248 		    windowin = childin;
249 		    if(nameinlist (display, windowin)){
250 			toavoid = xTrue;
251 			break;
252 		    }
253 		}while(XQueryPointer(display, windowin, &dummywin,
254 		     &childin, &rootx, &rooty, &winx, &winy, &modifs)
255 		       && childin!=None);
256 		if(!toavoid){
257 		    lastwindowavoided = None;
258 		    break;
259 		}
260 	    }
261 	    if(idletime>=0)
262 		sleep(idletime);
263 	}
264 	if(idlecommand!=0)
265 	    system(idlecommand);
266 	/* wait again next time */
267 	if(waitagain)
268 	    oldx = -1-jitter;
269 	if(usegrabmethod){
270 	    if(XGrabPointer(display, root, 0,
271 		    PointerMotionMask|ButtonPressMask|ButtonReleaseMask,
272 		    GrabModeAsync, GrabModeAsync, None, cursor[screen],
273 		    CurrentTime)==GrabSuccess){
274 		/* wait for a button event or large cursor motion */
275 		XEvent event;
276 		do{
277 		    XNextEvent(display,&event);
278 		}while(event.type==KeyRelease ||
279 		       (event.type==MotionNotify &&
280 			ALMOSTEQUAL(rootx,event.xmotion.x) &&
281 			ALMOSTEQUAL(rooty,event.xmotion.y)));
282 		XUngrabPointer(display, CurrentTime);
283 	    }
284 	}else{
285 	    XSetWindowAttributes attributes;
286 	    XEvent event;
287 	    Window cursorwindow;
288 
289 	    /* create small input-only window under cursor
290 	     * as a sub window of the window currently under the cursor
291 	     */
292 	    attributes.event_mask = LeaveWindowMask |
293 			EnterWindowMask |
294 			StructureNotifyMask |
295 			FocusChangeMask;
296 	    if(dovisible)
297 		attributes.event_mask |= VisibilityChangeMask;
298 	    attributes.override_redirect = True;
299 	    attributes.cursor = cursor[screen];
300 	    cursorwindow = XCreateWindow
301 		(display, windowin,
302 		 winx-jitter, winy-jitter,
303 		 jitter*2+1, jitter*2+1, 0, CopyFromParent,
304 		 InputOnly, CopyFromParent,
305 		 CWOverrideRedirect | CWEventMask | CWCursor,
306 		 &attributes);
307 	    /* discard old events for previously created windows */
308 	    XSync(display,True);
309 	    XMapWindow(display,cursorwindow);
310 	    /*
311 	     * Dont wait for expose/map cos override and inputonly(?).
312 	     * Check that created window captured the pointer by looking
313 	     * for inevitable EnterNotify event that must follow MapNotify.
314 	     * [Bug fix thanks to Charles Hannum <mycroft@ai.mit.edu>]
315 	     */
316 	    XSync(display,False);
317 	    if(!XCheckTypedWindowEvent(display, cursorwindow, EnterNotify,
318 				      &event))
319 		oldx = -1-jitter;	/* slow down retry */
320 	    else{
321 		if(doevents){
322 		    /*
323 		     * send a pseudo EnterNotify event to the parent window
324 		     * to try to convince application that we didnt really leave it
325 		     */
326 		    event.xcrossing.type = EnterNotify;
327 		    event.xcrossing.display = display;
328 		    event.xcrossing.window = windowin;
329 		    event.xcrossing.root = root;
330 		    event.xcrossing.subwindow = None;
331 		    event.xcrossing.time = CurrentTime;
332 		    event.xcrossing.x = winx;
333 		    event.xcrossing.y = winy;
334 		    event.xcrossing.x_root = rootx;
335 		    event.xcrossing.y_root = rooty;
336 		    event.xcrossing.mode = NotifyNormal;
337 		    event.xcrossing.same_screen = True;
338 		    event.xcrossing.focus = True;
339 		    event.xcrossing.state = modifs;
340 		    (void)XSendEvent(display,windowin,
341 				     True/*propagate*/,EnterWindowMask,&event);
342 		}
343 		/* wait till pointer leaves window */
344 		do{
345 		    XNextEvent(display,&event);
346 		}while(event.type!=LeaveNotify &&
347 		       event.type!=FocusOut &&
348 		       event.type!=UnmapNotify &&
349 		       event.type!=ConfigureNotify &&
350 		       event.type!=CirculateNotify &&
351 		       event.type!=ReparentNotify &&
352 		       event.type!=DestroyNotify &&
353 		       (event.type!=VisibilityNotify ||
354 			event.xvisibility.state==VisibilityUnobscured)
355 		       );
356 		/* check if a second unclutter is running cos they thrash */
357 		if(event.type==LeaveNotify &&
358 		   event.xcrossing.window==cursorwindow &&
359 		   event.xcrossing.detail==NotifyInferior)
360 		    pexit("someone created a sub-window to my sub-window! giving up");
361 	    }
362 	    XDestroyWindow(display, cursorwindow);
363 	}
364     }
365 }
366