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