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