twinwatch: port based Plan 9 winwatch - plan9port - [fork] Plan 9 from user space
 (HTM) git clone git://src.adamsgaard.dk/plan9port
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit a9b462061c05f8cd4e1f85b05522770293c8a468
 (DIR) parent dc24d309d591eb59168a84f233bb8dfb1795c5a2
 (HTM) Author: markvanatten <vanattenmark@gmail.com>
       Date:   Wed, 15 Jan 2020 14:43:01 +0100
       
       winwatch: port based Plan 9 winwatch
       
       Port of Plan 9's winwatch(1).
       Diffstat:
         A man/man1/winwatch.1                 |      57 +++++++++++++++++++++++++++++++
         M src/cmd/rio/mkfile                  |       2 +-
         A src/cmd/rio/winwatch.c              |     538 +++++++++++++++++++++++++++++++
       
       3 files changed, 596 insertions(+), 1 deletion(-)
       ---
 (DIR) diff --git a/man/man1/winwatch.1 b/man/man1/winwatch.1
       t@@ -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).
 (DIR) diff --git a/src/cmd/rio/mkfile b/src/cmd/rio/mkfile
       t@@ -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
 (DIR) diff --git a/src/cmd/rio/winwatch.c b/src/cmd/rio/winwatch.c
       t@@ -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;
       +        }
       +    }
       +}