winwatch.c (8283B)
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 40 enum { 41 PAD = 3, 42 MARGIN = 5 43 }; 44 45 46 int 47 winwatchxerrorhandler(XDisplay *disp, XErrorEvent *xe) 48 { 49 char buf[100]; 50 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 } 56 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 } 65 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 } 74 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; 83 84 n = 100; 85 p = nil; 86 s = XGetWindowProperty(dpy, w, a, 0, 100L, 0, 87 AnyPropertyType, &type, &fmt, &n, &dummy, &p); 88 89 if(s!=0){ 90 XFree(p); 91 return(nil); 92 } 93 94 return((char*)p); 95 } 96 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; 106 107 p = getproperty(w, XA_WM_NAME); 108 if(p){ 109 free(p); 110 return(w); 111 } 112 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); 118 } 119 120 rwin = 0; 121 122 s = XQueryTree(dpy, w, &dw1, &dw2, &xwin, &nxwin); 123 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 ; 130 } 131 } 132 XFree(xwin); 133 } 134 135 return rwin; 136 } 137 138 int 139 wcmp(const void *w1, const void *w2) 140 { 141 return *(XWindow *) w1 - *(XWindow *) w2; 142 } 143 144 /* unicode-aware case-insensitive strcmp, taken from golang’s gc/subr.c */ 145 146 int 147 _cistrcmp(char *p, char *q) 148 { 149 Rune rp, rq; 150 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; 164 } 165 return 0; 166 } 167 168 int 169 winlabelcmp(const void *w1, const void *w2) 170 { 171 const Win *p1 = (Win *) w1; 172 const Win *p2 = (Win *) w2; 173 return _cistrcmp(p1->label, p2->label); 174 } 175 176 void 177 refreshwin(void) 178 { 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; 188 189 s = XQueryTree(dpy, root, &dw1, &dw2, &xwin, &nxwin); 190 191 if(s==0){ 192 if(xwin!=NULL) 193 XFree(xwin); 194 return; 195 } 196 qsort(xwin, nxwin, sizeof(xwin[0]), wcmp); 197 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; 204 205 s = XGetWindowAttributes(dpy, xwin[i], &attr); 206 207 if(s==0) 208 continue; 209 if (attr.width <= 0 || 210 attr.override_redirect || 211 attr.map_state != IsViewable) 212 continue; 213 214 s = XGetClassHint(dpy, xwin[i], &class); 215 216 if(s==0) 217 continue; 218 219 if (exclude!=nil && regexec(exclude, class.res_name, nil, 0)) { 220 free(class.res_name); 221 free(class.res_class); 222 continue; 223 } 224 225 net_wm_name = XInternAtom(dpy, "_NET_WM_NAME", FALSE); 226 wmname = getproperty(xwin[i], net_wm_name); 227 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; 234 } 235 } 236 237 label = class.res_name; 238 if(showwmnames==1) 239 label = wmname; 240 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; 247 } 248 249 if(nw<nwin){ 250 free(win[nw].label); 251 win[nw].label = nil; 252 } 253 254 if(nw>=mwin){ 255 mwin += 8; 256 win = erealloc(win, mwin * sizeof(win[0])); 257 } 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++; 266 } 267 268 XFree(xwin); 269 270 while(nwin>nw) 271 free(win[--nwin].label); 272 nwin = nw; 273 274 if(sortlabels==1) 275 qsort(win, nwin, sizeof(struct Win), winlabelcmp); 276 } 277 278 void 279 drawnowin(int i) 280 { 281 Rectangle r; 282 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); 290 } 291 292 void 293 drawwin(int i) 294 { 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; 301 } 302 303 int 304 geometry(void) 305 { 306 int i, ncols, z; 307 Rectangle r; 308 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; 318 } 319 } 320 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); 329 330 return z; 331 } 332 333 void 334 redraw(Image *screen, int all) 335 { 336 int i; 337 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; 348 } 349 350 void 351 eresized(int new) 352 { 353 if(new && getwindow(display, Refmesg)<0) 354 fprint(2, "can't reattach to window"); 355 geometry(); 356 redraw(screen, 1); 357 } 358 359 360 void 361 selectwin(XWindow win) 362 { 363 XEvent ev; 364 long mask; 365 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; 374 375 XSendEvent(dpy, root, False, mask, &ev); 376 XMapRaised(dpy, win); 377 XSync(dpy, False); 378 } 379 380 void 381 click(Mouse m) 382 { 383 int i, j; 384 385 if(m.buttons==0 || (m.buttons&~4)) 386 return; 387 388 for(i=0; i<nwin; i++) 389 if(ptinrect(m.xy, win[i].r)) 390 break; 391 if(i==nwin) 392 return; 393 394 do 395 m = emouse(); 396 while(m.buttons==4); 397 398 if(m.buttons!=0){ 399 do 400 m = emouse(); 401 while(m.buttons); 402 return; 403 } 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); 409 } 410 411 void 412 usage(void) 413 { 414 fprint(2, 415 "usage: winwatch [-e exclude] [-W winsize] [-f font] [-n] [-s]\n"); 416 exits("usage"); 417 } 418 419 void 420 main(int argc, char **argv) 421 { 422 char *fontname; 423 int Etimer; 424 Event e; 425 426 sortlabels = 0; 427 showwmnames = 0; 428 fontname = "/lib/font/bit/lucsans/unicode.8.font"; 429 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; 451 452 if(argc) 453 usage(); 454 455 einit(Emouse | Ekeyboard); 456 Etimer = etimer(0, 1000); 457 458 dpy = XOpenDisplay(""); 459 if(dpy==nil) 460 sysfatal("open display: %r"); 461 462 root = DefaultRootWindow(dpy); 463 net_active_window = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); 464 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); 472 473 XSetErrorHandler(winwatchxerrorhandler); 474 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; 491 } 492 } 493 }