Blob


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 */
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"
15 AUTOLIB(X11);
17 typedef struct Win Win;
18 struct Win {
19 XWindow n;
20 int dirty;
21 char *label;
22 Rectangle r;
23 };
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;
40 enum {
41 PAD = 3,
42 MARGIN = 5
43 };
46 int
47 winwatchxerrorhandler(XDisplay *disp, XErrorEvent *xe)
48 {
49 char buf[100];
51 XGetErrorText(disp, xe->error_code, buf, 100);
52 fprint(2, "winwatch: X error %s, request code %d\n",
53 buf, xe->request_code);
54 return 0;
55 }
57 void*
58 erealloc(void *v, ulong n)
59 {
60 v = realloc(v, n);
61 if(v==nil)
62 sysfatal("out of memory reallocating");
63 return v;
64 }
66 char*
67 estrdup(char *s)
68 {
69 s = strdup(s);
70 if(s==nil)
71 sysfatal("out of memory allocating");
72 return(s);
73 }
75 char*
76 getproperty(XWindow w, Atom a)
77 {
78 uchar *p;
79 int fmt;
80 Atom type;
81 ulong n, dummy;
82 int s;
84 n = 100;
85 p = nil;
86 s = XGetWindowProperty(dpy, w, a, 0, 100L, 0,
87 AnyPropertyType, &type, &fmt, &n, &dummy, &p);
89 if(s!=0){
90 XFree(p);
91 return(nil);
92 }
94 return((char*)p);
95 }
97 XWindow
98 findname(XWindow w)
99 {
100 int i;
101 uint nxwin;
102 XWindow dw1, dw2, *xwin, rwin;
103 char *p;
104 int s;
105 Atom net_wm_name;
107 p = getproperty(w, XA_WM_NAME);
108 if(p){
109 free(p);
110 return(w);
113 net_wm_name = XInternAtom(dpy, "_NET_WM_NAME", FALSE);
114 p = getproperty(w, net_wm_name);
115 if(p){
116 free(p);
117 return(w);
120 rwin = 0;
122 s = XQueryTree(dpy, w, &dw1, &dw2, &xwin, &nxwin);
124 if(s!=0){
125 for (i = 0; i < nxwin; i++){
126 w = findname(xwin[i]);
127 if(w != 0){
128 rwin = w;
129 break ;
132 XFree(xwin);
135 return rwin;
138 int
139 wcmp(const void *w1, const void *w2)
141 return *(XWindow *) w1 - *(XWindow *) w2;
144 /* unicode-aware case-insensitive strcmp, taken from golang’s gc/subr.c */
146 int
147 _cistrcmp(char *p, char *q)
149 Rune rp, rq;
151 while(*p || *q) {
152 if(*p == 0)
153 return +1;
154 if(*q == 0)
155 return -1;
156 p += chartorune(&rp, p);
157 q += chartorune(&rq, q);
158 rp = tolowerrune(rp);
159 rq = tolowerrune(rq);
160 if(rp < rq)
161 return -1;
162 if(rp > rq)
163 return +1;
165 return 0;
168 int
169 winlabelcmp(const void *w1, const void *w2)
171 const Win *p1 = (Win *) w1;
172 const Win *p2 = (Win *) w2;
173 return _cistrcmp(p1->label, p2->label);
176 void
177 refreshwin(void)
179 XWindow dw1, dw2, *xwin;
180 XClassHint class;
181 XWindowAttributes attr;
182 char *label;
183 char *wmname;
184 int i, nw;
185 uint nxwin;
186 Status s;
187 Atom net_wm_name;
189 s = XQueryTree(dpy, root, &dw1, &dw2, &xwin, &nxwin);
191 if(s==0){
192 if(xwin!=NULL)
193 XFree(xwin);
194 return;
196 qsort(xwin, nxwin, sizeof(xwin[0]), wcmp);
198 nw = 0;
199 for(i=0; i<nxwin; i++){
200 memset(&attr, 0, sizeof attr);
201 xwin[i] = findname(xwin[i]);
202 if(xwin[i]==0)
203 continue;
205 s = XGetWindowAttributes(dpy, xwin[i], &attr);
207 if(s==0)
208 continue;
209 if (attr.width <= 0 ||
210 attr.override_redirect ||
211 attr.map_state != IsViewable)
212 continue;
214 s = XGetClassHint(dpy, xwin[i], &class);
216 if(s==0)
217 continue;
219 if (exclude!=nil && regexec(exclude, class.res_name, nil, 0)) {
220 free(class.res_name);
221 free(class.res_class);
222 continue;
225 net_wm_name = XInternAtom(dpy, "_NET_WM_NAME", FALSE);
226 wmname = getproperty(xwin[i], net_wm_name);
228 if(wmname==nil){
229 wmname = getproperty(xwin[i], XA_WM_NAME);
230 if(wmname==nil){
231 free(class.res_name);
232 free(class.res_class);
233 continue;
237 label = class.res_name;
238 if(showwmnames==1)
239 label = wmname;
241 if(nw<nwin && win[nw].n==xwin[i] && strcmp(win[nw].label, label)==0) {
242 nw++;
243 free(wmname);
244 free(class.res_name);
245 free(class.res_class);
246 continue;
249 if(nw<nwin){
250 free(win[nw].label);
251 win[nw].label = nil;
254 if(nw>=mwin){
255 mwin += 8;
256 win = erealloc(win, mwin * sizeof(win[0]));
258 win[nw].n = xwin[i];
259 win[nw].label = estrdup(label);
260 win[nw].dirty = 1;
261 win[nw].r = Rect(0, 0, 0, 0);
262 free(wmname);
263 free(class.res_name);
264 free(class.res_class);
265 nw++;
268 XFree(xwin);
270 while(nwin>nw)
271 free(win[--nwin].label);
272 nwin = nw;
274 if(sortlabels==1)
275 qsort(win, nwin, sizeof(struct Win), winlabelcmp);
278 void
279 drawnowin(int i)
281 Rectangle r;
283 r = Rect(0, 0, (Dx(screen->r) - 2 * MARGIN + PAD) / cols - PAD, font->height);
284 r = rectaddpt(
285 rectaddpt(r,
286 Pt(MARGIN + (PAD + Dx(r)) * (i / rows),
287 MARGIN + (PAD + Dy(r)) * (i % rows))),
288 screen->r.min);
289 draw(screen, insetrect(r, -1), lightblue, nil, ZP);
292 void
293 drawwin(int i)
295 draw(screen, win[i].r, lightblue, nil, ZP);
296 _string(screen, addpt(win[i].r.min, Pt(2, 0)), display->black, ZP,
297 font, win[i].label, nil, strlen(win[i].label),
298 win[i].r, nil, ZP, SoverD);
299 border(screen, win[i].r, 1, display->black, ZP);
300 win[i].dirty = 0;
303 int
304 geometry(void)
306 int i, ncols, z;
307 Rectangle r;
309 z = 0;
310 rows = (Dy(screen->r) - 2 * MARGIN + PAD) / (font->height + PAD);
311 if(rows*cols<nwin || rows*cols>=nwin*2){
312 ncols = 1;
313 if(nwin>0)
314 ncols = (nwin + rows - 1) / rows;
315 if(ncols!=cols){
316 cols = ncols;
317 z = 1;
321 r = Rect(0, 0, (Dx(screen->r) - 2 * MARGIN + PAD) / cols - PAD, font->height);
322 for(i=0; i<nwin; i++)
323 win[i].r =
324 rectaddpt(
325 rectaddpt(r,
326 Pt(MARGIN + (PAD + Dx(r)) * (i / rows),
327 MARGIN + (PAD + Dy(r)) * (i % rows))),
328 screen->r.min);
330 return z;
333 void
334 redraw(Image *screen, int all)
336 int i;
338 all |= geometry();
339 if(all)
340 draw(screen, screen->r, lightblue, nil, ZP);
341 for(i=0; i<nwin; i++)
342 if(all || win[i].dirty)
343 drawwin(i);
344 if(!all)
345 for (; i<onwin; i++)
346 drawnowin(i);
347 onwin = nwin;
350 void
351 eresized(int new)
353 if(new && getwindow(display, Refmesg)<0)
354 fprint(2, "can't reattach to window");
355 geometry();
356 redraw(screen, 1);
360 void
361 selectwin(XWindow win)
363 XEvent ev;
364 long mask;
366 memset(&ev, 0, sizeof ev);
367 ev.xclient.type = ClientMessage;
368 ev.xclient.serial = 0;
369 ev.xclient.send_event = True;
370 ev.xclient.message_type = net_active_window;
371 ev.xclient.window = win;
372 ev.xclient.format = 32;
373 mask = SubstructureRedirectMask | SubstructureNotifyMask;
375 XSendEvent(dpy, root, False, mask, &ev);
376 XMapRaised(dpy, win);
377 XSync(dpy, False);
380 void
381 click(Mouse m)
383 int i, j;
385 if(m.buttons==0 || (m.buttons&~4))
386 return;
388 for(i=0; i<nwin; i++)
389 if(ptinrect(m.xy, win[i].r))
390 break;
391 if(i==nwin)
392 return;
394 do
395 m = emouse();
396 while(m.buttons==4);
398 if(m.buttons!=0){
399 do
400 m = emouse();
401 while(m.buttons);
402 return;
404 for(j=0; j<nwin; j++)
405 if(ptinrect(m.xy, win[j].r))
406 break;
407 if(j==i)
408 selectwin(win[i].n);
411 void
412 usage(void)
414 fprint(2,
415 "usage: winwatch [-e exclude] [-W winsize] [-f font] [-n] [-s]\n");
416 exits("usage");
419 void
420 main(int argc, char **argv)
422 char *fontname;
423 int Etimer;
424 Event e;
426 sortlabels = 0;
427 showwmnames = 0;
428 fontname = "/lib/font/bit/lucsans/unicode.8.font";
430 ARGBEGIN {
431 case 'W':
432 winsize = EARGF(usage());
433 break;
434 case 'f':
435 fontname = EARGF(usage());
436 break;
437 case 'e':
438 exclude = regcomp(EARGF(usage()));
439 if(exclude==nil)
440 sysfatal("Bad regexp");
441 break;
442 case 's':
443 sortlabels = 1;
444 break;
445 case 'n':
446 showwmnames = 1;
447 break;
448 default:
449 usage();
450 } ARGEND;
452 if(argc)
453 usage();
455 einit(Emouse | Ekeyboard);
456 Etimer = etimer(0, 1000);
458 dpy = XOpenDisplay("");
459 if(dpy==nil)
460 sysfatal("open display: %r");
462 root = DefaultRootWindow(dpy);
463 net_active_window = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False);
465 initdraw(0, 0, "winwatch");
466 lightblue = allocimagemix(display, DPalebluegreen, DWhite);
467 if(lightblue==nil)
468 sysfatal("allocimagemix: %r");
469 font = openfont(display, fontname);
470 if(font==nil)
471 sysfatal("font '%s' not found", fontname);
473 XSetErrorHandler(winwatchxerrorhandler);
475 refreshwin();
476 redraw(screen, 1);
477 for(;;){
478 switch(eread(Emouse|Ekeyboard|Etimer, &e)){
479 case Ekeyboard:
480 if(e.kbdc==0x7F || e.kbdc=='q')
481 exits(0);
482 break;
483 case Emouse:
484 if(e.mouse.buttons)
485 click(e.mouse);
486 /* fall through */
487 default: /* Etimer */
488 refreshwin();
489 redraw(screen, 0);
490 break;