code: plan9front

Download patch

ref: afc5d2b7f964522e35ad9023eff4c6bb213114bd
parent: fec45947f05be694706f3d4d25e9c46a42bbbb93
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Wed Sep 14 19:17:47 EDT 2022

bar: a status bar program

--- /dev/null
+++ b/sys/man/1/bar
@@ -1,0 +1,93 @@
+.TH BAR 1
+.SH NAME
+bar \- display a bar
+.SH SYNOPSIS
+.B bar
+[
+.I -b
+]
+[
+.I -d dateformat
+]
+[
+.I -p position
+]
+[
+.I -s separator
+]
+.SH DESCRIPTION
+.I bar
+displays a small window with current battery charge, date and time.
+Additional data may be displayed by writing lines of text to its
+standard input.
+.PP
+.I Bar
+reacts to mouse clicks by writing the "clicked" portion of the text
+(enclosed by the separator) to standard output.  This can be used to
+perform additional actions by a custom script. See
+.I Examples
+section.
+.PP
+By default,
+.I bar
+is placed at the bottom right corner of the screen.  This can be
+changed by speicifying option
+.I -p
+with first letters of the required placement:
+.I l
+for "left",
+.I r
+for "right",
+.I t
+for "top" and
+.I b
+for "bottom".  If neither left nor right is chosen, the window will be
+placed in either top or bottom center.
+.PP
+.I Bar
+keeps itself as a top window. To make it stay at the bottom, use
+.I -b .
+.PP
+Separator can be changed with
+.I -s
+option.
+.PP
+Date and time format may be set using
+.I -d
+option, see
+.I tmdate(2).
+.SH EXAMPLES
+An example of how
+.I bar
+can be used along with
+.I riow
+to control
+.I zuke
+playback via mouse clicks:
+.PP
+.EX
+#!/bin/rc
+rfork ne
+fn mybar {
+	sed -u 's/$/ │ ⏮ │ ⏯ │ ⏭/g' \\
+	| bar \\
+	| awk -v 'c=plumb -d audio ''key ' '
+		/⏮/{system(c"<''")}
+		/⏯/{system(c"p''")}
+		/⏭/{system(c">''")}
+		' >[2]/dev/null
+}
+riow </dev/kbdtap >/dev/kbdtap |[3] mybar
+.EE
+.PP
+The script is used instead of executing
+.I bar
+directly.
+.SH SOURCE
+/sys/src/cmd/bar.c
+.SH SEE ALSO
+.IR riow (1)
+.SH HISTORY
+Bar first appeared in 9front (September, 2022).
+.SH BUGS
+Impossible.
--- /dev/null
+++ b/sys/src/cmd/bar.c
@@ -1,0 +1,384 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <keyboard.h>
+#include <mouse.h>
+#include <thread.h>
+#include <tos.h>
+
+#define MAX(a,b) ((a)>=(b)?(a):(b))
+
+enum {
+	Off = 3,
+};
+
+static int wctl = -1, owidth, width, twidth, bottom, bat, minheight, seplen, sepw, hlitem;
+static char sep[16], bats[16], *aux;
+static char *pos = "rb", *dfmt = "YYYY/MM/DD WW hh:mm:ss", *items[64];
+static int itemw[64], nitems;
+static Image *cback, *ctext;
+static Tzone *local;
+static Font *f;
+
+#pragma varargck type "|" char*
+static int
+sepfmt(Fmt *f)
+{
+	return fmtstrcpy(f, va_arg(f->args, char*)[0] ? sep : "");
+}
+
+/*
+ * nsec() is wallclock and can be adjusted by timesync
+ * so need to use cycles() instead, but fall back to
+ * nsec() in case we can't
+ */
+static uvlong
+nanosec(void)
+{
+	static uvlong fasthz, xstart;
+	uvlong x, div;
+
+	if(fasthz == ~0ULL)
+		return nsec() - xstart;
+
+	if(fasthz == 0){
+		fasthz = _tos->cyclefreq;
+		if(fasthz == 0){
+			fasthz = ~0ULL;
+			xstart = nsec();
+			return 0;
+		}else{
+			cycles(&xstart);
+		}
+	}
+	cycles(&x);
+	x -= xstart;
+
+	/* this is ugly */
+	for(div = 1000000000ULL; x < 0x1999999999999999ULL && div > 1 ; div /= 10ULL, x *= 10ULL);
+
+	return x / (fasthz / div);
+}
+
+static void
+place(void)
+{
+	int w, h, minx, miny, maxx, maxy;
+	char t[64];
+	static int ow, oh;
+
+	if(wctl < 0 && (wctl = open("/dev/wctl", OWRITE)) < 0)
+		return;
+
+	fprint(wctl, bottom ? "bottom" : "top");
+	w = Dx(display->image->r);
+	h = Dy(display->image->r);
+
+	if(ow != w || oh != h || owidth < width){
+		if(pos[0] == 't' || pos[1] == 't'){
+			miny = 0;
+			maxy = minheight;
+		}else{
+			miny = h - minheight;
+			maxy = h;
+		}
+		if(pos[0] == 'l' || pos[1] == 'l'){
+			minx = 0;
+			maxx = MAX(100, Borderwidth+Off+width+Off+Borderwidth);
+		}else if(pos[0] == 'r' || pos[1] == 'r'){
+			minx = MAX(100, w-(Borderwidth+Off+width+Off+Borderwidth));
+			maxx = w;
+		}else{
+			minx = (w-MAX(100, Borderwidth+Off+width+Off+Borderwidth))/2;
+			maxx = (w+MAX(100, Borderwidth+Off+width+Off+Borderwidth))/2;
+		}
+		snprint(t, sizeof(t), "resize -r %d %d %d %d", minx, miny, maxx, maxy);
+		write(wctl, "current", 7);
+		if(fprint(wctl, "%s", t) < 0)
+			fprint(2, "%s: %r\n", t);
+		ow = w;
+		oh = h;
+		owidth = width;
+	}
+}
+
+static void
+split(char *s)
+{
+	char *i;
+
+	for(nitems = 0, i = s; nitems < nelem(items); s += seplen, i = s){
+		if((s = strstr(s, sep)) != nil)
+			*s = 0;
+		if(*i == 0)
+			continue;
+		items[nitems] = i;
+		itemw[nitems++] = stringwidth(f, i) + sepw;
+		if(s == nil)
+			break;
+	}
+	
+}
+
+static void
+redraw(void)
+{
+	static char s[128];
+	char tmp[128];
+	Rectangle r;
+	Tmfmt tf;
+	Point p;
+	Tm tm;
+	int i;
+
+	r = screen->r;
+
+	tf = tmfmt(tmnow(&tm, local), dfmt);
+	p.x = r.min.x + Off;
+	p.y = (pos[0] == 't' || pos[1] == 't') ? r.max.y - (f->height + Off) : r.min.y + Off;
+	if(pos[0] == 'l' || pos[1] == 'l'){
+		snprint(s, sizeof(s), "%τ%|%s%|%s", tf, bats, bats, aux, aux);
+	}else{
+		snprint(s, sizeof(s), "%s%|%s%|%τ", aux, aux, bats, bats, tf);
+		if(pos[0] == 'r' || pos[1] == 'r')
+			p.x = r.max.x - (stringwidth(f, s) + Off);
+	}
+	draw(screen, r, cback, nil, ZP);
+	string(screen, p, ctext, ZP, f, s);
+	if(hlitem >= 0){
+		for(i = 0; i < hlitem; i++)
+			r.min.x += itemw[i];
+		r.max.x = r.min.x + itemw[i];
+		replclipr(screen, 0, r);
+		stringbg(screen, p, cback, ZP, f, s, ctext, ZP);
+		replclipr(screen, 0, screen->r);
+	}
+	split(s);
+
+	flushimage(display, 1);
+
+	snprint(tmp, sizeof(tmp), "%τ", tf);
+	twidth = MAX(twidth, stringwidth(f, tmp));
+	snprint(tmp, sizeof(tmp), "%|%s%|%s", bats, bats[0] ? "100%" : "", aux, aux);
+	width = twidth + stringwidth(f, tmp);
+	if(owidth != width)
+		place();
+}
+
+static void
+readbattery(void)
+{
+	char *s, tmp[16];
+
+	s = bat < 0 || pread(bat, tmp, 4, 0) < 4 ? nil : strchr(tmp, ' ');
+	if(s != nil){
+		*s = 0;
+		snprint(bats, sizeof(bats), "%s%%", tmp);
+	}else{
+		bats[0] = 0;
+	}
+}
+
+static void
+timerproc(void *c)
+{
+	threadsetname("timer");
+	for(;;){
+		sleep(990);
+		sendul(c, 0);
+	}
+}
+
+static void
+auxproc(void *c)
+{
+	Biobuf b;
+	char *s;
+
+	threadsetname("aux");
+	Binit(&b, 0, OREAD);
+	for(;;){
+		s = Brdstr(&b, '\n', 1);
+		sendp(c, s ? s : strdup(""));
+		if(s == nil)
+			break;
+	}
+	Bterm(&b);
+
+	threadexits(nil);
+}
+
+static void
+usage(void)
+{
+	fprint(2, "usage: %s [-b] [-d dateformat] [-p lt|t|rt|lb|b|rb] [-s separator]\n", argv0);
+	threadexitsall("usage");
+}
+
+static void
+clicked(int x, int buttons)
+{
+	int i, ix;
+
+	if(hlitem >= 0){
+		hlitem = -1;
+		return;
+	}
+
+	for(i = ix = 0; i < nitems; i++){
+		ix += itemw[i];
+		if(x <= ix){
+			fprint(1, "%d\t%s\n", buttons, items[i]);
+			hlitem = i;
+			break;
+		}
+	}
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	Keyboardctl *kctl;
+	Mousectl *mctl;
+	uvlong t, oldt;
+	int oldbuttons;
+	char *s, *v[3];
+	u32int brgb;
+	Biobuf *b;
+	Rune key;
+	Mouse m;
+	enum {
+		Emouse,
+		Eresize,
+		Ekeyboard,
+		Eaux,
+		Etimer,
+		Eend,
+	};
+	Alt a[] = {
+		[Emouse] = { nil, &m, CHANRCV },
+		[Eresize] = { nil, nil, CHANRCV },
+		[Ekeyboard] = { nil, &key, CHANRCV },
+		[Eaux] = { nil, &s, CHANRCV },
+		[Etimer] = { nil, nil, CHANRCV },
+		[Eend] = { nil, nil, CHANEND },
+	};
+
+	strcpy(sep, " │ ");
+	ARGBEGIN{
+	case 'b':
+		bottom = 1;
+		break;
+	case 'd':
+		dfmt = EARGF(usage());
+		break;
+	case 'p':
+		pos = EARGF(usage());
+		break;
+	case 's':
+		snprint(sep, sizeof(sep), "%s", EARGF(usage()));
+		break;
+	default:
+		usage();
+	}ARGEND
+	seplen = strlen(sep);
+
+	fmtinstall('|', sepfmt);
+	tmfmtinstall();
+	if((local = tzload("local")) == nil)
+		sysfatal("zone: %r");
+
+	brgb = DPalegreygreen;
+	if((b = Bopen("/dev/theme", OREAD)) != nil){
+		while((s = Brdline(b, '\n')) != nil){
+			s[Blinelen(b)-1] = 0;
+			if(tokenize(s, v, nelem(v)) > 1 && strcmp(v[0], "ltitle") == 0){
+				brgb = strtoul(v[1], nil, 16)<<8 | 0xff;
+				break;
+			}
+		}
+		Bterm(b);
+	}
+
+	if((bat = open("/mnt/pm/battery", OREAD)) < 0)
+		bat = open("/dev/battery", OREAD);
+	if(initdraw(nil, nil, "bar") < 0)
+		sysfatal("initdraw: %r");
+	f = display->defaultfont;
+	minheight = 2*(Borderwidth+1) + f->height;
+	sepw = stringwidth(f, sep);
+	if((mctl = initmouse(nil, screen)) == nil)
+		sysfatal("initmouse: %r");
+	if((kctl = initkeyboard(nil)) == nil)
+		sysfatal("initkeyboard: %r");
+
+	cback = allocimage(display, Rect(0,0,1,1), RGB24, 1, brgb);
+	if(brgb == DPalegreygreen)
+		brgb = DBlack;
+	else{ /* dunno, just invert */
+		brgb = ~(brgb>>8 | brgb>>16 | brgb>>24);
+		brgb = brgb<<8 | brgb<<16 | brgb<<24 | 0xff;
+	}
+	ctext = allocimage(display, Rect(0,0,1,1), RGB24, 1, brgb);
+
+	a[Emouse].c = mctl->c;
+	a[Eresize].c = mctl->resizec;
+	a[Ekeyboard].c = kctl->c;
+	a[Eaux].c = chancreate(sizeof(s), 0);
+	a[Etimer].c = chancreate(sizeof(ulong), 0);
+
+	hlitem = -1;
+	aux = strdup("");
+	readbattery();
+	redraw();
+	proccreate(timerproc, a[Etimer].c, 4096);
+	proccreate(auxproc, a[Eaux].c, 16384);
+
+	m.buttons = 0;
+	oldt = nanosec();
+	for(;;){
+		oldbuttons = m.buttons;
+
+		switch(alt(a)){
+		case Ekeyboard:
+			if(key == Kdel){
+				close(wctl);
+				threadexitsall(nil);
+			}
+			break;
+
+		case Emouse:	
+			if(m.buttons == oldbuttons)
+				break;
+			clicked(m.xy.x-screen->r.min.x, m.buttons);
+			/* wet floor */
+
+		if(0){
+		case Eresize:
+			if(getwindow(display, Refnone) < 0)
+				threadexitsall(nil);
+			/* wet floor */
+		}
+
+		if(0){
+		case Eaux:
+			free(aux);
+			aux = s;
+			/* wet floor */
+		}
+
+		if(0){
+		case Etimer:
+			t = nanosec();
+			if(t - oldt >= 30000000000ULL){
+				readbattery();
+				oldt = t;
+			}
+		}
+			redraw();
+			place();
+			break;
+		}
+	}
+}