Commit Diff


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 <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <regexp.h>
+#include <stdio.h>
+#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;
+        }
+    }
+}