1 /*
2  * slightly modified from
3  * https://github.com/fhs/misc/blob/master/cmd/winwatch/winwatch.c
4  * so as to deal with memory leaks and certain X errors
5  */
6 
7 #include <u.h>
8 #include <libc.h>
9 #include <draw.h>
10 #include <event.h>
11 #include <regexp.h>
12 #include <fmt.h>
13 #include "../devdraw/x11-inc.h"
14 
15 AUTOLIB(X11);
16 
17 typedef struct Win Win;
18 struct Win {
19 	XWindow n;
20 	int dirty;
21 	char *label;
22 	Rectangle r;
23 };
24 
25 XDisplay *dpy;
26 XWindow root;
27 Atom net_active_window;
28 Reprog *exclude = nil;
29 Win *win;
30 int nwin;
31 int mwin;
32 int onwin;
33 int rows, cols;
34 int sortlabels;
35 int showwmnames;
36 Font *font;
37 Image *lightblue;
38 
39 XErrorHandler oldxerrorhandler;
40 
41 enum {
42 	PAD = 3,
43 	MARGIN = 5
44 };
45 
46 static jmp_buf savebuf;
47 
48 int
winwatchxerrorhandler(XDisplay * disp,XErrorEvent * xe)49 winwatchxerrorhandler(XDisplay *disp, XErrorEvent *xe)
50 {
51 	char buf[100];
52 
53 	XGetErrorText(disp, xe->error_code, buf, 100);
54 	fprint(2, "winwatch: X error %s, request code %d\n",
55 	    buf, xe->request_code);
56 	XFlush(disp);
57 	XSync(disp, False);
58 	XSetErrorHandler(oldxerrorhandler);
59 	longjmp(savebuf, 1);
60 	return(0);  /* Not reached */
61 }
62 
63 void*
erealloc(void * v,ulong n)64 erealloc(void *v, ulong n)
65 {
66 	v = realloc(v, n);
67 	if(v==nil)
68 		sysfatal("out of memory reallocating");
69 	return v;
70 }
71 
72 char*
estrdup(char * s)73 estrdup(char *s)
74 {
75 	s = strdup(s);
76 	if(s==nil)
77 		sysfatal("out of memory allocating");
78 	return(s);
79 }
80 
81 char*
getproperty(XWindow w,Atom a)82 getproperty(XWindow w, Atom a)
83 {
84 	uchar *p;
85 	int fmt;
86 	Atom type;
87 	ulong n, dummy;
88 	int s;
89 
90 	n = 100;
91 	p = nil;
92 	oldxerrorhandler = XSetErrorHandler(winwatchxerrorhandler);
93 	s = XGetWindowProperty(dpy, w, a, 0, 100L, 0,
94 	    AnyPropertyType, &type, &fmt, &n, &dummy, &p);
95 	XFlush(dpy);
96 	XSync(dpy, False);
97 	XSetErrorHandler(oldxerrorhandler);
98 	if(s!=0){
99 		XFree(p);
100 		return(nil);
101 	}
102 
103 	return((char*)p);
104 }
105 
106 XWindow
findname(XWindow w)107 findname(XWindow w)
108 {
109 	int i;
110 	uint nxwin;
111 	XWindow dw1, dw2, *xwin;
112 	char *p;
113 	int s;
114 	Atom net_wm_name;
115 
116 	p = getproperty(w, XA_WM_NAME);
117 	if(p){
118 		free(p);
119 		return(w);
120 	}
121 
122 	net_wm_name = XInternAtom(dpy, "_NET_WM_NAME", FALSE);
123 	p = getproperty(w, net_wm_name);
124 	if(p){
125 		free(p);
126 		return(w);
127 	}
128 
129 	oldxerrorhandler = XSetErrorHandler(winwatchxerrorhandler);
130 	s = XQueryTree(dpy, w, &dw1, &dw2, &xwin, &nxwin);
131 	XFlush(dpy);
132 	XSync(dpy, False);
133 	XSetErrorHandler(oldxerrorhandler);
134 	if(s == 0) {
135 		if (xwin != NULL)
136 			XFree(xwin);
137 		return 0;
138 	}
139 
140 	for (i = 0; i < nxwin; i++) {
141 		w = findname(xwin[i]);
142 		if (w != 0) {
143 			XFree(xwin);
144 			return w;
145 		}
146 	}
147 	XFree(xwin);
148 
149 	return 0;
150 }
151 
152 int
wcmp(const void * w1,const void * w2)153 wcmp(const void *w1, const void *w2)
154 {
155 	return *(XWindow *) w1 - *(XWindow *) w2;
156 }
157 
158 /* unicode-aware case-insensitive strcmp,  taken from golang’s gc/subr.c */
159 
160 int
_cistrcmp(char * p,char * q)161 _cistrcmp(char *p, char *q)
162 {
163 	Rune rp, rq;
164 
165 	while(*p || *q) {
166 		if(*p == 0)
167 			return +1;
168 		if(*q == 0)
169 			return -1;
170 		p += chartorune(&rp, p);
171 		q += chartorune(&rq, q);
172 		rp = tolowerrune(rp);
173 		rq = tolowerrune(rq);
174 		if(rp < rq)
175 			return -1;
176 		if(rp > rq)
177 			return +1;
178 	}
179 	return 0;
180 }
181 
182 int
winlabelcmp(const void * w1,const void * w2)183 winlabelcmp(const void *w1, const void *w2)
184 {
185 	const Win *p1 = (Win *) w1;
186 	const Win *p2 = (Win *) w2;
187 	return _cistrcmp(p1->label, p2->label);
188 }
189 
190 void
refreshwin(void)191 refreshwin(void)
192 {
193 	XWindow dw1, dw2, *xwin;
194 	XClassHint class;
195 	XWindowAttributes attr;
196 	char *label;
197 	char *wmname;
198 	int i, nw;
199 	uint nxwin;
200 	Status s;
201 	Atom net_wm_name;
202 
203 
204 	oldxerrorhandler = XSetErrorHandler(winwatchxerrorhandler);
205 	s = XQueryTree(dpy, root, &dw1, &dw2, &xwin, &nxwin);
206 	XFlush(dpy);
207 	XSync(dpy, False);
208 	XSetErrorHandler(oldxerrorhandler);
209 	if(s==0){
210 		if(xwin!=NULL)
211 			XFree(xwin);
212 		return;
213 	}
214 	qsort(xwin, nxwin, sizeof(xwin[0]), wcmp);
215 
216 	nw = 0;
217 	for(i=0; i<nxwin; i++){
218 		memset(&attr, 0, sizeof attr);
219 		xwin[i] = findname(xwin[i]);
220 		if(xwin[i]==0)
221 			continue;
222 
223 		oldxerrorhandler = XSetErrorHandler(winwatchxerrorhandler);
224 		s = XGetWindowAttributes(dpy, xwin[i], &attr);
225 		XFlush(dpy);
226 		XSync(dpy, False);
227 		XSetErrorHandler(oldxerrorhandler);
228 		if(s==0)
229 			continue;
230 		if (attr.width <= 0 ||
231 		    attr.override_redirect ||
232 		    attr.map_state != IsViewable)
233 			continue;
234 
235 		oldxerrorhandler = XSetErrorHandler(winwatchxerrorhandler);
236 		s = XGetClassHint(dpy, xwin[i], &class);
237 		XFlush(dpy);
238 		XSync(dpy, False);
239 		XSetErrorHandler(oldxerrorhandler);
240 
241 		if(s==0)
242 			continue;
243 
244 		if (exclude!=nil && regexec(exclude, class.res_name, nil, 0)) {
245 			free(class.res_name);
246 			free(class.res_class);
247 			continue;
248 		}
249 
250 		net_wm_name = XInternAtom(dpy, "_NET_WM_NAME", FALSE);
251 		wmname = getproperty(xwin[i], net_wm_name);
252 
253 		if(wmname==nil){
254 			wmname = getproperty(xwin[i], XA_WM_NAME);
255 			if(wmname==nil){
256 				free(class.res_name);
257 				free(class.res_class);
258 				continue;
259 			}
260 		}
261 
262 		label = class.res_name;
263 		if(showwmnames==1)
264 			label = wmname;
265 
266 		if(nw<nwin && win[nw].n==xwin[i] && strcmp(win[nw].label, label)==0) {
267 			nw++;
268 			free(wmname);
269 			free(class.res_name);
270 			free(class.res_class);
271 			continue;
272 		}
273 
274 		if(nw<nwin){
275 			free(win[nw].label);
276 			win[nw].label = nil;
277 		}
278 
279 		if(nw>=mwin){
280 			mwin += 8;
281 			win = erealloc(win, mwin * sizeof(win[0]));
282 		}
283 		win[nw].n = xwin[i];
284 		win[nw].label = estrdup(label);
285 		win[nw].dirty = 1;
286 		win[nw].r = Rect(0, 0, 0, 0);
287 		free(wmname);
288 		free(class.res_name);
289 		free(class.res_class);
290 		nw++;
291 	}
292 
293 	oldxerrorhandler = XSetErrorHandler(winwatchxerrorhandler);
294 	XFree(xwin);
295 	XFlush(dpy);
296 	XSync(dpy, False);
297 	XSetErrorHandler(oldxerrorhandler);
298 
299 	while(nwin>nw)
300 		free(win[--nwin].label);
301 	nwin = nw;
302 
303 	if(sortlabels==1)
304 		qsort(win, nwin, sizeof(struct Win), winlabelcmp);
305 }
306 
307 void
drawnowin(int i)308 drawnowin(int i)
309 {
310 	Rectangle r;
311 
312 	r = Rect(0, 0, (Dx(screen->r) - 2 * MARGIN + PAD) / cols - PAD, font->height);
313 	r = rectaddpt(
314 	        rectaddpt(r,
315 	            Pt(MARGIN + (PAD + Dx(r)) * (i / rows),
316  	                MARGIN + (PAD + Dy(r)) * (i % rows))),
317 	        screen->r.min);
318 	draw(screen, insetrect(r, -1), lightblue, nil, ZP);
319 }
320 
321 void
drawwin(int i)322 drawwin(int i)
323 {
324 	draw(screen, win[i].r, lightblue, nil, ZP);
325 	_string(screen, addpt(win[i].r.min, Pt(2, 0)), display->black, ZP,
326 	    font, win[i].label, nil, strlen(win[i].label),
327 	    win[i].r, nil, ZP, SoverD);
328 	border(screen, win[i].r, 1, display->black, ZP);
329 	win[i].dirty = 0;
330 }
331 
332 int
geometry(void)333 geometry(void)
334 {
335 	int i, ncols, z;
336 	Rectangle r;
337 
338 	z = 0;
339 	rows = (Dy(screen->r) - 2 * MARGIN + PAD) / (font->height + PAD);
340 	if(rows*cols<nwin || rows*cols>=nwin*2){
341 		ncols = 1;
342 		if(nwin>0)
343 			ncols = (nwin + rows - 1) / rows;
344 		if(ncols!=cols){
345 			cols = ncols;
346 			z = 1;
347 		}
348 	}
349 
350 	r = Rect(0, 0, (Dx(screen->r) - 2 * MARGIN + PAD) / cols - PAD, font->height);
351 	for(i=0; i<nwin; i++)
352 		win[i].r =
353 		    rectaddpt(
354 		        rectaddpt(r,
355 		            Pt(MARGIN + (PAD + Dx(r)) * (i / rows),
356 		                MARGIN + (PAD + Dy(r)) * (i % rows))),
357 		        screen->r.min);
358 
359 	return z;
360 }
361 
362 void
redraw(Image * screen,int all)363 redraw(Image *screen, int all)
364 {
365 	int i;
366 
367 	all |= geometry();
368 	if(all)
369 		draw(screen, screen->r, lightblue, nil, ZP);
370 	for(i=0; i<nwin; i++)
371 		if(all || win[i].dirty)
372 			drawwin(i);
373 	if(!all)
374 		for (; i<onwin; i++)
375 			drawnowin(i);
376 	onwin = nwin;
377 }
378 
379 void
eresized(int new)380 eresized(int new)
381 {
382 	if(new && getwindow(display, Refmesg)<0)
383 		fprint(2, "can't reattach to window");
384 	geometry();
385 	redraw(screen, 1);
386 }
387 
388 
389 void
selectwin(XWindow win)390 selectwin(XWindow win)
391 {
392 	XEvent ev;
393 	long mask;
394 
395 	memset(&ev, 0, sizeof ev);
396 	ev.xclient.type = ClientMessage;
397 	ev.xclient.serial = 0;
398 	ev.xclient.send_event = True;
399 	ev.xclient.message_type = net_active_window;
400 	ev.xclient.window = win;
401 	ev.xclient.format = 32;
402 	mask = SubstructureRedirectMask | SubstructureNotifyMask;
403 
404 	XSendEvent(dpy, root, False, mask, &ev);
405 	XMapRaised(dpy, win);
406 	XSync(dpy, False);
407 }
408 
409 void
click(Mouse m)410 click(Mouse m)
411 {
412 	int i, j;
413 
414 	if(m.buttons==0 || (m.buttons&~4))
415 		return;
416 
417 	for(i=0; i<nwin; i++)
418 		if(ptinrect(m.xy, win[i].r))
419 			break;
420 	if(i==nwin)
421 		return;
422 
423 	do
424 		m = emouse();
425 	while(m.buttons==4);
426 
427 	if(m.buttons!=0){
428 		do
429 			m = emouse();
430 		while(m.buttons);
431 		return;
432 	}
433 	for(j=0; j<nwin; j++)
434 		if(ptinrect(m.xy, win[j].r))
435 			break;
436 	if(j==i)
437 		selectwin(win[i].n);
438 }
439 
440 void
usage(void)441 usage(void)
442 {
443 	fprint(2,
444 	    "usage: winwatch [-e exclude] [-W winsize] [-f font] [-n] [-s]\n");
445 	exits("usage");
446 }
447 
448 void
main(int argc,char ** argv)449 main(int argc, char **argv)
450 {
451 	char *fontname;
452 	int Etimer;
453 	Event e;
454 
455 	sortlabels = 0;
456 	showwmnames = 0;
457 	fontname = "/lib/font/bit/lucsans/unicode.8.font";
458 
459 	ARGBEGIN {
460 	case 'W':
461 		winsize = EARGF(usage());
462 		break;
463 	case 'f':
464 		fontname = EARGF(usage());
465 		break;
466 	case 'e':
467 		exclude = regcomp(EARGF(usage()));
468 		if(exclude==nil)
469 			sysfatal("Bad regexp");
470 		break;
471 	case 's':
472 		sortlabels = 1;
473 		break;
474 	case 'n':
475 		showwmnames = 1;
476 		break;
477 	default:
478 		usage();
479 	} ARGEND;
480 
481 	if(argc)
482 	    usage();
483 
484 	/* moved up from original winwatch.c for p9p because there can be only one but we want to restart when needed */
485 	einit(Emouse | Ekeyboard);
486 	Etimer = etimer(0, 1000);
487 
488 	dpy = XOpenDisplay("");
489 	if(dpy==nil)
490 		sysfatal("open display: %r");
491 
492 	root = DefaultRootWindow(dpy);
493 	net_active_window = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False);
494 
495 	initdraw(0, 0, "winwatch");
496 	lightblue = allocimagemix(display, DPalebluegreen, DWhite);
497 	if(lightblue==nil)
498 		sysfatal("allocimagemix: %r");
499 	font = openfont(display, fontname);
500 	if(font==nil)
501 		sysfatal("font '%s' not found", fontname);
502 
503 	/* reentry point upon X server errors */
504 	setjmp(savebuf);
505 
506 	refreshwin();
507 	redraw(screen, 1);
508 	for(;;){
509 		switch(eread(Emouse|Ekeyboard|Etimer, &e)){
510 		case Ekeyboard:
511 			if(e.kbdc==0x7F || e.kbdc=='q')
512 				exits(0);
513 			break;
514 		case Emouse:
515 			if(e.mouse.buttons)
516 				click(e.mouse);
517 			/* fall through  */
518 		default:		/* Etimer */
519 			refreshwin();
520 			redraw(screen, 0);
521 			break;
522 		}
523 	}
524 }
525