mirror of
https://github.com/9fans/plan9port.git
synced 2025-01-12 11:10:07 +00:00
winwatch: port based Plan 9 winwatch
Port of Plan 9's winwatch(1).
This commit is contained in:
parent
dc24d309d5
commit
a9b462061c
3 changed files with 596 additions and 1 deletions
57
man/man1/winwatch.1
Normal file
57
man/man1/winwatch.1
Normal file
|
@ -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).
|
|
@ -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
|
||||
|
|
538
src/cmd/rio/winwatch.c
Normal file
538
src/cmd/rio/winwatch.c
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue