commit a9b462061c05f8cd4e1f85b05522770293c8a468 from: markvanatten via: Dan Cross date: Wed Jan 15 13:43:01 2020 UTC winwatch: port based Plan 9 winwatch Port of Plan 9's winwatch(1). commit - dc24d309d591eb59168a84f233bb8dfb1795c5a2 commit + a9b462061c05f8cd4e1f85b05522770293c8a468 blob - /dev/null blob + afbf541daa67973756e41247875134fd1e3dda22 (mode 644) --- /dev/null +++ man/man1/winwatch.1 @@ -0,0 +1,57 @@ +.TH WINWATCH 1 +.SH NAME +winwatch \- monitor rio windows +.SH SYNOPSIS +.B winwatch +[ +.B -e +.I exclude +] [ +.B -f +.I font +] [ +.B -n +] [ +.B -s +] +.SH DESCRIPTION +.I Winwatch +displays the labels of all current +.IR rio (1) +windows, refreshing the display every second. +Right clicking a window's label unhides, raises and gives focus to that window. +Typing +.B q +or +DEL +quits +.IR winwatch . +.PP +If the +.B -e +flag +is given, +windows matching the regular expression +.I exclude +are not shown. +With the +.B -n +option, +the +label is defined as the window’s name instead of its class, +and with +.B -s +the labels are sorted by alphabet (case insensitive) +instead of by order of appearance. +Winwatch is unicode aware. +.SH EXAMPLE +Excluding winwatch and stats from being shown. +.IP +.EX +% winwatch -e '^(winwatch|stats)$' +.EE +.SH SOURCE +.B \*9/src/cmd/winwatch.c +.SH SEE ALSO +.IR rio (1), +.IR regexp (7). blob - 8b8ea46a7ba36996909246b1cf85c4b7bd995842 blob + 20202e22d0d16128ebea59becd7b4d9a7457c804 --- src/cmd/rio/mkfile +++ src/cmd/rio/mkfile @@ -16,7 +16,7 @@ RIOFILES=\ CFLAGS=$CFLAGS -DDEBUG HFILES=dat.h fns.h -TARG=rio xshove +TARG=rio winwatch xshove # need to add lib64 when it exists (on x86-64), but # Darwin complains about the nonexistant directory blob - /dev/null blob + 66ec8cbedfe78901cc1130fa2834a29ba83c2383 (mode 644) --- /dev/null +++ src/cmd/rio/winwatch.c @@ -0,0 +1,538 @@ +/* slightly modified from +https://github.com/fhs/misc/blob/master/cmd/winwatch/winwatch.c +so as to deal with memory leaks and certain X errors */ + +#include +#include +#include +#include +#include +#include +#include "../devdraw/x11-inc.h" + +AUTOLIB(X11); + +typedef struct Win Win; +struct Win { + XWindow n; + int dirty; + char *label; + Rectangle r; +}; + +XDisplay *dpy; +XWindow root; +Atom net_active_window; +Reprog *exclude = nil; +Win *win; +int nwin; +int mwin; +int onwin; +int rows, cols; +int sortlabels; +int showwmnames; +Font *font; +Image *lightblue; + +XErrorHandler oldxerrorhandler; + +enum { + PAD = 3, + MARGIN = 5 +}; + +static jmp_buf savebuf; + +int +winwatchxerrorhandler(XDisplay *disp, XErrorEvent *xe) +{ + char buf[100]; + + XGetErrorText(disp, xe->error_code, buf, 100); + fprintf(stderr, "winwatch: X error %s, request code %d\n", buf, + xe->request_code); + XFlush(disp); + XSync(disp, False); + XSetErrorHandler(oldxerrorhandler); + longjmp(savebuf, 1); +} + +void* +erealloc(void *v, ulong n) +{ + v = realloc(v, n); + if (v == nil) + sysfatal("out of memory reallocating"); + return v; +} + +char* +estrdup(char *s) +{ + s = strdup(s); + if (s == nil) + sysfatal("out of memory allocating"); + return s; +} + +char* +getproperty(XWindow w, Atom a) +{ + uchar *p; + int fmt; + Atom type; + ulong n, dummy; + int s; + + n = 100; + p = nil; + + oldxerrorhandler = XSetErrorHandler(winwatchxerrorhandler); + s = XGetWindowProperty(dpy, w, a, 0, 100L, 0, + AnyPropertyType, &type, &fmt, &n, &dummy, &p); + XFlush(dpy); + XSync(dpy, False); + XSetErrorHandler(oldxerrorhandler); + + + if (s == 0) + return (char *) p; + else { + free(p); + return nil; + } +} + +XWindow +findname(XWindow w) +{ + int i; + uint nxwin; + XWindow dw1, dw2, *xwin; + char *p; + int s; + Atom net_wm_name; + + p = getproperty(w, XA_WM_NAME); + if (p) { + free(p); + return w; + } + + net_wm_name = XInternAtom (dpy, "_NET_WM_NAME", FALSE); + p = getproperty(w, net_wm_name); + if (p) { + free(p); + return w; + } + + oldxerrorhandler = XSetErrorHandler(winwatchxerrorhandler); + s = XQueryTree(dpy, w, &dw1, &dw2, &xwin, &nxwin); + XFlush(dpy); + XSync(dpy, False); + XSetErrorHandler(oldxerrorhandler); + + if (s == 0) { + if (xwin != NULL) + XFree(xwin); + return 0; + } + + for (i = 0; i < nxwin; i++) { + w = findname(xwin[i]); + if (w != 0) { + XFree(xwin); + return w; + } + } + + XFree(xwin); + + return 0; +} + +int +wcmp(const void *w1, const void *w2) +{ + return *(XWindow *) w1 - *(XWindow *) w2; +} + +/* unicode-aware case-insensitive strcmp, taken from golang’s gc/subr.c */ + +int +_cistrcmp(char *p, char *q) +{ + Rune rp, rq; + + while(*p || *q) { + if(*p == 0) + return +1; + if(*q == 0) + return -1; + p += chartorune(&rp, p); + q += chartorune(&rq, q); + rp = tolowerrune(rp); + rq = tolowerrune(rq); + if(rp < rq) + return -1; + if(rp > rq) + return +1; + } + return 0; +} + +int +winlabelcmp(const void *w1, const void *w2) +{ + const Win *p1 = (Win *) w1; + const Win *p2 = (Win *) w2; + return _cistrcmp(p1->label, p2->label); +} + +void +refreshwin(void) +{ + XWindow dw1, dw2, *xwin; + XClassHint class; + XWindowAttributes attr; + char *label; + char *wmname; + int i, nw; + uint nxwin; + Status s; + Atom net_wm_name; + + + oldxerrorhandler = XSetErrorHandler(winwatchxerrorhandler); + s = XQueryTree(dpy, root, &dw1, &dw2, &xwin, &nxwin); + XFlush(dpy); + XSync(dpy, False); + XSetErrorHandler(oldxerrorhandler); + + if (s == 0) { + if (xwin != NULL) + XFree(xwin); + return; + } + qsort(xwin, nxwin, sizeof(xwin[0]), wcmp); + + nw = 0; + for (i = 0; i < nxwin; i++) { + memset(&attr, 0, sizeof attr); + xwin[i] = findname(xwin[i]); + if (xwin[i] == 0) + continue; + + oldxerrorhandler = XSetErrorHandler(winwatchxerrorhandler); + s = XGetWindowAttributes(dpy, xwin[i], &attr); + XFlush(dpy); + XSync(dpy, False); + XSetErrorHandler(oldxerrorhandler); + + if (s == 0) + continue; + if (attr.width <= 0 || attr.override_redirect + || attr.map_state != IsViewable) + continue; + + oldxerrorhandler = XSetErrorHandler(winwatchxerrorhandler); + s = XGetClassHint(dpy, xwin[i], &class); + XFlush(dpy); + XSync(dpy, False); + XSetErrorHandler(oldxerrorhandler); + + if (s == 0) + continue; + + if (exclude != nil && regexec(exclude, class.res_name, nil, 0)) { + free(class.res_name); + free(class.res_class); + continue; + } + + net_wm_name = XInternAtom (dpy, "_NET_WM_NAME", FALSE); + wmname = getproperty(xwin[i], net_wm_name); + + if (wmname == nil) { + wmname = getproperty(xwin[i], XA_WM_NAME); + if (wmname == nil) { + free(class.res_name); + free(class.res_class); + continue; + } + } + + if (showwmnames == 1) + label = wmname; + else + label = class.res_name; + + if (nw < nwin && win[nw].n == xwin[i] + && strcmp(win[nw].label, label) == 0) { + nw++; + free(wmname); + free(class.res_name); + free(class.res_class); + continue; + } + + if (nw < nwin) { + free(win[nw].label); + win[nw].label = nil; + } + + if (nw >= mwin) { + mwin += 8; + win = erealloc(win, mwin * sizeof(win[0])); + } + win[nw].n = xwin[i]; + win[nw].label = estrdup(label); + win[nw].dirty = 1; + win[nw].r = Rect(0, 0, 0, 0); + free(wmname); + free(class.res_name); + free(class.res_class); + nw++; + } + + oldxerrorhandler = XSetErrorHandler(winwatchxerrorhandler); + XFree(xwin); + XFlush(dpy); + XSync(dpy, False); + XSetErrorHandler(oldxerrorhandler); + + while (nwin > nw) + free(win[--nwin].label); + nwin = nw; + + if (sortlabels == 1) + qsort(win, nwin, sizeof(struct Win), winlabelcmp); + + return; +} + +void +drawnowin(int i) +{ + Rectangle r; + + r = Rect(0, 0, (Dx(screen->r) - 2 * MARGIN + PAD) / cols - PAD, + font->height); + r = rectaddpt(rectaddpt + (r, + Pt(MARGIN + (PAD + Dx(r)) * (i / rows), + MARGIN + (PAD + Dy(r)) * (i % rows))), + screen->r.min); + draw(screen, insetrect(r, -1), lightblue, nil, ZP); +} + +void +drawwin(int i) +{ + draw(screen, win[i].r, lightblue, nil, ZP); + _string(screen, addpt(win[i].r.min, Pt(2, 0)), display->black, ZP, + font, win[i].label, nil, strlen(win[i].label), + win[i].r, nil, ZP, SoverD); + border(screen, win[i].r, 1, display->black, ZP); + win[i].dirty = 0; +} + +int +geometry(void) +{ + int i, ncols, z; + Rectangle r; + + z = 0; + rows = (Dy(screen->r) - 2 * MARGIN + PAD) / (font->height + PAD); + if (rows * cols < nwin || rows * cols >= nwin * 2) { + ncols = nwin <= 0 ? 1 : (nwin + rows - 1) / rows; + if (ncols != cols) { + cols = ncols; + z = 1; + } + } + + r = Rect(0, 0, (Dx(screen->r) - 2 * MARGIN + PAD) / cols - PAD, + font->height); + for (i = 0; i < nwin; i++) + win[i].r = + rectaddpt(rectaddpt + (r, + Pt(MARGIN + (PAD + Dx(r)) * (i / rows), + MARGIN + (PAD + Dy(r)) * (i % rows))), + screen->r.min); + + return z; +} + +void +redraw(Image *screen, int all) +{ + int i; + + all |= geometry(); + if (all) + draw(screen, screen->r, lightblue, nil, ZP); + for (i = 0; i < nwin; i++) + if (all || win[i].dirty) + drawwin(i); + if (!all) + for (; i < onwin; i++) + drawnowin(i); + + onwin = nwin; +} + +void +eresized(int new) +{ + if (new && getwindow(display, Refmesg) < 0) + fprint(2, "can't reattach to window"); + geometry(); + redraw(screen, 1); +} + + +void +selectwin(XWindow win) +{ + XEvent ev; + long mask; + + memset(&ev, 0, sizeof ev); + ev.xclient.type = ClientMessage; + ev.xclient.serial = 0; + ev.xclient.send_event = True; + ev.xclient.message_type = net_active_window; + ev.xclient.window = win; + ev.xclient.format = 32; + mask = SubstructureRedirectMask | SubstructureNotifyMask; + + XSendEvent(dpy, root, False, mask, &ev); + XMapRaised(dpy, win); + XSync(dpy, False); +} + + +void +click(Mouse m) +{ + int i, j; + + if (m.buttons == 0 || (m.buttons & ~4)) + return; + + for (i = 0; i < nwin; i++) + if (ptinrect(m.xy, win[i].r)) + break; + if (i == nwin) + return; + + do + m = emouse(); + while (m.buttons == 4); + + if (m.buttons != 0) { + do + m = emouse(); + while (m.buttons); + return; + } + + for (j = 0; j < nwin; j++) + if (ptinrect(m.xy, win[j].r)) + break; + if (j != i) + return; + + selectwin(win[i].n); +} + +void +usage(void) +{ + fprint(2, + "usage: winwatch [-e exclude] [-W winsize] [-f font] [-n] [-s]\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + char *fontname; + int Etimer; + Event e; + + sortlabels = 0; + showwmnames = 0; + + fontname = "/lib/font/bit/lucsans/unicode.8.font"; + + ARGBEGIN { + case 'W': + winsize = EARGF(usage()); + break; + case 'f': + fontname = EARGF(usage()); + break; + case 'e': + exclude = regcomp(EARGF(usage())); + if (exclude == nil) + sysfatal("Bad regexp"); + break; + case 's': + sortlabels = 1; + break; + case 'n': + showwmnames = 1; + break; + default: + usage(); + } + ARGEND if (argc) + usage(); + + /* moved up from original winwatch.c for p9p because there can be only one but we want to restart when needed */ + einit(Emouse | Ekeyboard); + Etimer = etimer(0, 1000); + + dpy = XOpenDisplay(""); + + if (dpy == nil) + sysfatal("open display: %r"); + + root = DefaultRootWindow(dpy); + net_active_window = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); + + initdraw(0, 0, "winwatch"); + lightblue = allocimagemix(display, DPalebluegreen, DWhite); + if (lightblue == nil) + sysfatal("allocimagemix: %r"); + if ((font = openfont(display, fontname)) == nil) + sysfatal("font '%s' not found", fontname); + + + /* reentry point upon X server errors */ + setjmp(savebuf); + + refreshwin(); + redraw(screen, 1); + + for (;;) { + switch (eread(Emouse | Ekeyboard | Etimer, &e)) { + case Ekeyboard: + if (e.kbdc == 0x7F || e.kbdc == 'q') + exits(0); + break; + case Emouse: + if (e.mouse.buttons) + click(e.mouse); + /* fall through */ + default: /* Etimer */ + refreshwin(); + redraw(screen, 0); + break; + } + } +}