twinwatch: port based Plan 9 winwatch - plan9port - [fork] Plan 9 from user spa… | |
git clone git://src.adamsgaard.dk/plan9port | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit a9b462061c05f8cd4e1f85b05522770293c8a468 | |
parent dc24d309d591eb59168a84f233bb8dfb1795c5a2 | |
Author: markvanatten <[email protected]> | |
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(-) | |
--- | |
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). | |
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 | |
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… | |
+ 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; | |
+ } | |
+ } | |
+} |