ref: b804f62dceead0d0398f427b8e58cf50ef390f2d
parent: 1270ba4418294c69d04242d13621d24b10a4120f
author: qwx <[email protected]>
date: Sat Aug 19 04:53:26 EDT 2023
more theming
--- /dev/null
+++ b/sys/src/cmd/audio/zuke/zuke.c
@@ -1,0 +1,1659 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <plumb.h>
+#include <ctype.h>
+#include "plist.h"
+#include "icy.h"
+
+#define MAX(a,b) ((a)>=(b)?(a):(b))
+#define MIN(a,b) ((a)<=(b)?(a):(b))
+#define CLAMP(x,min,max) MAX(min, MIN(max, x))
+
+typedef struct Color Color;
+typedef struct Player Player;
+typedef struct Playlist Playlist;
+
+enum
+{
+ Cstart = 1,
+ Cstop,
+ Ctoggle,
+ Cseekrel,
+
+ Rgdisabled = 0,
+ Rgtrack,
+ Rgalbum,
+ Numrg,
+
+ Everror = 1,
+ Evready,
+
+ Seek = 10, /* 10 seconds */
+ Seekfast = 60, /* a minute */
+
+ Bps = 44100*2*2, /* 44100KHz, stereo, s16 for a sample */
+ Relbufsz = Bps/2, /* 0.5s */
+
+ Dback = 0,
+ Dfhigh,
+ Dfmed,
+ Dflow,
+ Dfinv,
+ Dbmed,
+ Dblow,
+ Dbinv,
+ Numcolors,
+
+ Ncol = 10,
+};
+
+struct Color {
+ u32int rgb;
+ Image *im;
+};
+
+struct Player
+{
+ Channel *ctl;
+ Channel *ev;
+ Channel *img;
+ Channel *icytitlec;
+ char *icytitle;
+ double seek;
+ double gain;
+ int pcur;
+};
+
+struct Playlist
+{
+ Meta *m;
+ int n;
+ char *raw;
+ int rawsz;
+};
+
+int mainstacksize = 65536;
+
+static int debug;
+static int audio = -1;
+static int volume, rg;
+static int pnotifies;
+static Playlist *pl;
+static Player *playernext;
+static Player *playercurr;
+static vlong byteswritten;
+static int pcur, pcurplaying;
+static int scroll, scrollsz;
+static Font *f;
+static Image *cover;
+static Channel *playc;
+static Channel *redrawc;
+static Mousectl *mctl;
+static Keyboardctl kctl;
+static int shiftdown;
+static int colwidth[Ncol];
+static int mincolwidth[Ncol];
+static char *cols = "AatD";
+static int colspath;
+static int *shuffle;
+static int repeatone;
+static int stopafter;
+static Rectangle seekbar;
+static int seekmx, newseekmx = -1;
+static double seekoff; /* ms */
+static Lock audiolock;
+static int audioerr = 0;
+static Biobuf out;
+static char *covers[] =
+{
+ "art", "folder", "cover", "Cover", "scans/CD", "Scans/Front", "Covers/Front"
+};
+
+static Color colors[Numcolors] =
+{
+ [Dback] = {0xf0f0f0},
+ [Dfhigh] = {0xffffff},
+ [Dfmed] = {0x343434},
+ [Dflow] = {0xa5a5a5},
+ [Dfinv] = {0x323232},
+ [Dbmed] = {0x72dec2},
+ [Dblow] = {0x404040},
+ [Dbinv] = {0xffb545},
+};
+
+static int Scrollwidth;
+static int Scrollheight;
+static int Seekthicc;
+static int Coversz;
+
+static char *
+matchvname(char **s)
+{
+ char *names[] = {"mix", "master", "pcm out"};
+ int i, l;
+
+ for(i = 0; i < nelem(names); i++){
+ l = strlen(names[i]);
+ if(strncmp(*s, names[i], l) == 0){
+ *s += l;
+ return names[i];
+ }
+ }
+
+ return nil;
+}
+
+static void
+chvolume(int d)
+{
+ int f, x, ox, want, try;
+ char *s, *e;
+ Biobuf b;
+ char *n;
+
+ if((f = open("/dev/volume", ORDWR|OCEXEC)) < 0)
+ return;
+ Binit(&b, f, OREAD);
+
+ want = x = -1;
+ ox = 0;
+ for(try = 0; try < 10; try++){
+ for(n = nil; (s = Brdline(&b, '\n')) != nil;){
+ if((n = matchvname(&s)) != nil && (ox = strtol(s, &e, 10)) >= 0 && s != e)
+ break;
+ n = nil;
+ }
+
+ if(want < 0){
+ want = CLAMP(ox+d, 0, 100);
+ x = ox;
+ }
+ if(n == nil || (d > 0 && ox >= want) || (d < 0 && ox <= want))
+ break;
+ x = CLAMP(x+d, 0, 100);
+ if(fprint(f, "%s %d\n", n, x) < 0)
+ break;
+ /* go to eof and back */
+ while(Brdline(&b, '\n') != nil);
+ Bseek(&b, 0, 0);
+ }
+
+ volume = CLAMP(ox, 0, 100);
+
+ Bterm(&b);
+ close(f);
+}
+
+static void
+audioon(void)
+{
+ lock(&audiolock);
+ if(audio < 0){
+ if((audio = open("/dev/audio", OWRITE|OCEXEC)) < 0 && audioerr == 0){
+ fprint(2, "%r\n");
+ audioerr = 1;
+ }else{
+ chvolume(0);
+ }
+ }
+ unlock(&audiolock);
+}
+
+static void
+audiooff(void)
+{
+ lock(&audiolock);
+ close(audio);
+ audio = -1;
+ audioerr = 0;
+ unlock(&audiolock);
+}
+
+#pragma varargck type "P" uvlong
+static int
+positionfmt(Fmt *f)
+{
+ char *s, tmp[16];
+ u64int sec;
+
+ s = tmp;
+ sec = va_arg(f->args, uvlong);
+ if(sec >= 3600){
+ s = seprint(s, tmp+sizeof(tmp), "%02lld:", sec/3600);
+ sec %= 3600;
+ }
+ s = seprint(s, tmp+sizeof(tmp), "%02lld:", sec/60);
+ sec %= 60;
+ seprint(s, tmp+sizeof(tmp), "%02lld", sec);
+
+ return fmtstrcpy(f, tmp);
+}
+
+static char *
+getcol(Meta *m, int c)
+{
+ static char tmp[32];
+ char *s;
+
+ s = nil;
+ switch(c){
+ case Palbum: s = m->album; break;
+ case Partist: s = m->artist[0]; break;
+ case Pdate: s = m->date; break;
+ case Ptitle: s = (!colspath && (m->title == nil || *m->title == 0)) ? m->basename : m->title; break;
+ case Ptrack: snprint(tmp, sizeof(tmp), "%4s", m->track); s = m->track ? tmp : nil; break;
+ case Ppath: s = m->path; break;
+ case Pduration:
+ tmp[0] = 0;
+ if(m->duration > 0)
+ snprint(tmp, sizeof(tmp), "%8P", m->duration/1000);
+ s = tmp;
+ break;
+ default: sysfatal("invalid column '%c'", c);
+ }
+
+ return s ? s : "";
+}
+
+static Meta *
+getmeta(int i)
+{
+ return &pl->m[shuffle != nil ? shuffle[i] : i];
+}
+
+static void
+updatescrollsz(void)
+{
+ scrollsz = Dy(screen->r)/f->height - 2;
+}
+
+static void
+redraw_(int full)
+{
+ static Image *back, *ocover;
+ static int oscrollcenter, opcur, opcurplaying;
+
+ int x, i, j, scrollcenter, w;
+ uvlong dur, msec;
+ Rectangle sel, r;
+ char tmp[32], *s;
+ Point p, sp, p₀, p₁;
+ Image *col;
+
+ /* seekbar playback/duration text */
+ i = snprint(tmp, sizeof(tmp), "%s%s%s%s",
+ rg ? (rg == Rgalbum ? "ᴬ" : "ᵀ") : "",
+ stopafter ? "ⁿ" : repeatone ? "¹" : "",
+ shuffle != nil ? "∫" : "",
+ (rg || stopafter || repeatone || shuffle != nil) ? " " : ""
+ );
+ msec = 0;
+ dur = 0;
+ w = stringwidth(f, tmp);
+ if(pcurplaying >= 0){
+ msec = byteswritten*1000/Bps;
+ dur = getmeta(pcurplaying)->duration;
+ if(dur > 0){
+ snprint(tmp+i, sizeof(tmp)-i, "%P/%P ", dur/1000, dur/1000);
+ w += stringwidth(f, tmp+i);
+ msec = MIN(msec, dur);
+ i += snprint(tmp+i, sizeof(tmp)-i, "%P/%P ",
+ (uvlong)(newseekmx >= 0 ? seekoff : msec)/1000,
+ dur/1000
+ );
+ }else{
+ j = snprint(tmp+i, sizeof(tmp)-i, "%P ", msec/1000);
+ w += stringwidth(f, tmp+i);
+ i += j;
+ }
+ }
+ snprint(tmp+i, sizeof(tmp)-i, "%d%%", 100);
+ w += stringwidth(f, tmp+i);
+ snprint(tmp+i, sizeof(tmp)-i, "%d%%", volume);
+
+ lockdisplay(display);
+
+ if(back == nil || Dx(screen->r) != Dx(back->r) || Dy(screen->r) != Dy(back->r)){
+ freeimage(back);
+ back = allocimage(display, Rpt(ZP,subpt(screen->r.max, screen->r.min)), XRGB32, 0, DNofill);
+ full = 1;
+ }
+
+ r = screen->r;
+
+ /* scrollbar */
+ p₀ = Pt(r.min.x, r.min.y);
+ p₁ = Pt(r.min.x+Scrollwidth, r.max.y-Seekthicc);
+ if(scroll < 1)
+ scrollcenter = 0;
+ else
+ scrollcenter = (p₁.y-p₀.y-Scrollheight/2 - Seekthicc)*scroll / (pl->n - scrollsz);
+ if(full || oscrollcenter != scrollcenter){
+ draw(screen, Rpt(p₀, Pt(p₁.x, p₁.y)), colors[Dback].im, nil, ZP);
+ line(screen, Pt(p₁.x, p₀.y), p₁, Endsquare, Endsquare, 0, colors[Dflow].im, ZP);
+ r = Rpt(
+ Pt(p₀.x+1, p₀.y + scrollcenter + Scrollheight/4),
+ Pt(p₁.x-1, p₀.y + scrollcenter + Scrollheight/4 + Scrollheight)
+ );
+ /* scrollbar handle */
+ draw(screen, r, colors[Dblow].im, nil, ZP);
+ }
+
+ /* seek bar rectangle */
+ r = Rpt(Pt(p₀.x, p₁.y), Pt(screen->r.max.x-w-4, screen->r.max.y));
+
+ /* playback/duration text */
+ draw(screen, Rpt(Pt(r.max.x, p₁.y), screen->r.max), colors[Dblow].im, nil, ZP);
+ p = addpt(Pt(screen->r.max.x - stringwidth(f, tmp) - 4, p₁.y), Pt(2, 2));
+ string(screen, p, colors[Dfhigh].im, ZP, f, tmp);
+
+ /* seek control */
+ if(pcurplaying >= 0 && dur > 0){
+ border(screen, r, 3, colors[Dblow].im, ZP);
+ r = insetrect(r, 3);
+ seekbar = r;
+ p = r.min;
+ x = p.x + Dx(r) * (double)msec / (double)dur;
+ r.min.x = x;
+ draw(screen, r, colors[Dback].im, nil, ZP);
+ r.min.x = p.x;
+ r.max.x = x;
+ draw(screen, r, colors[Dbmed].im, nil, ZP);
+ }else
+ draw(screen, r, colors[Dblow].im, nil, ZP);
+
+ Rectangle bp[2] = {
+ Rpt(addpt(screen->r.min, Pt(Scrollwidth+1, 0)), subpt(screen->r.max, Pt(0, Seekthicc))),
+ ZR,
+ };
+
+ if(cover != nil){
+ r.min.x = screen->r.max.x - Dx(cover->r) - 8;
+ r.min.y = p₁.y - Dy(cover->r) - 6;
+ r.max.x = screen->r.max.x;
+ r.max.y = p₁.y + 2;
+ if(full || cover != ocover){
+ border(screen, r, 4, colors[Dblow].im, ZP);
+ draw(screen, insetrect(r, 4), cover, nil, ZP);
+ }
+ bp[1] = bp[0];
+ bp[0].max.y = r.min.y;
+ bp[1].max.x = r.min.x;
+ bp[1].min.y = r.min.y;
+ }else if(ocover != nil)
+ full = 1;
+
+ /* playlist */
+ if(full || oscrollcenter != scrollcenter || pcur != opcur || pcurplaying != opcurplaying){
+ draw(back, back->r, colors[Dback].im, nil, ZP);
+
+ p.x = sp.x = Scrollwidth;
+ p.y = 0;
+ sp.y = back->r.max.y;
+ for(i = 0; cols[i+1] != 0; i++){
+ p.x += colwidth[i] + 4;
+ sp.x = p.x;
+ line(back, p, sp, Endsquare, Endsquare, 0, colors[Dflow].im, ZP);
+ p.x += 4;
+ }
+
+ sp.x = sp.y = 0;
+ p.x = Scrollwidth + 2;
+ p.y = back->r.min.y + 2;
+
+ for(i = scroll; i < pl->n; i++, p.y += f->height){
+ if(i < 0)
+ continue;
+ if(p.y > back->r.max.y)
+ break;
+
+ if(pcur == i){
+ sel.min.x = Scrollwidth;
+ sel.min.y = p.y;
+ sel.max.x = back->r.max.x;
+ sel.max.y = p.y + f->height;
+ replclipr(back, 0, back->r);
+ draw(back, sel, colors[Dbinv].im, nil, ZP);
+ col = colors[Dfinv].im;
+ }else{
+ col = colors[Dfmed].im;
+ }
+
+ sel = back->r;
+
+ p.x = Scrollwidth + 2 + 3;
+ for(j = 0; cols[j] != 0; j++){
+ sel.max.x = cols[j+1] ? (p.x + colwidth[j] - 1) : back->r.max.x;
+ replclipr(back, 0, sel);
+ if(playercurr != nil && playercurr->icytitle != nil && pcurplaying == i && cols[j] == Ptitle)
+ s = playercurr->icytitle;
+ else
+ s = getcol(getmeta(i), cols[j]);
+ string(back, p, col, sp, f, s);
+ p.x += colwidth[j] + 8;
+ }
+
+ if(pcurplaying == i){
+ Point rightp, leftp;
+ leftp.y = rightp.y = p.y - 1;
+ leftp.x = Scrollwidth;
+ rightp.x = back->r.max.x;
+ replclipr(back, 0, back->r);
+ line(back, leftp, rightp, 0, 0, 0, colors[Dflow].im, sp);
+ leftp.y = rightp.y = p.y + f->height;
+ line(back, leftp, rightp, 0, 0, 0, colors[Dflow].im, sp);
+ }
+ }
+
+ for(i = 0; bp[i].max.x ; i++)
+ draw(screen, bp[i], back, nil, subpt(bp[i].min, screen->r.min));
+ }
+ oscrollcenter = scrollcenter;
+ opcurplaying = pcurplaying;
+ ocover = cover;
+ opcur = pcur;
+
+ flushimage(display, 1);
+ unlockdisplay(display);
+}
+
+static void
+redrawproc(void *)
+{
+ ulong full, nbfull, another;
+
+ threadsetname("redraw");
+ while(recv(redrawc, &full) == 1){
+Again:
+ redraw_(full);
+ another = 0;
+ full = 0;
+ while(nbrecv(redrawc, &nbfull) > 0){
+ full |= nbfull;
+ another = 1;
+ }
+ if(another)
+ goto Again;
+ }
+
+ threadexits(nil);
+}
+
+static void
+redraw(int full)
+{
+ sendul(redrawc, full);
+}
+
+static void
+coverload(void *player_)
+{
+ int p[2], pid, fd, i;
+ char *prog, *path, *s, tmp[64];
+ Meta *m;
+ Channel *ch;
+ Player *player;
+ Image *newcover;
+
+ threadsetname("cover");
+ player = player_;
+ m = getmeta(player->pcur);
+ pid = -1;
+ ch = player->img;
+ fd = -1;
+ prog = nil;
+
+ if(m->imagefmt != nil)
+ prog = "audio/readtags -i";
+ else{
+ path = strdup(m->path);
+ if(path != nil && (s = utfrrune(path, '/')) != nil){
+ *s = 0;
+
+ for(i = 0; i < nelem(covers) && prog == nil; i++){
+ if((s = smprint("%s/%s.jpg", path, covers[i])) != nil && (fd = open(s, OREAD|OCEXEC)) >= 0)
+ prog = "jpg -9t";
+ free(s);
+ s = nil;
+ if(fd < 0 && (s = smprint("%s/%s.png", path, covers[i])) != nil && (fd = open(s, OREAD|OCEXEC)) >= 0)
+ prog = "png -9t";
+ free(s);
+ }
+ }
+ free(path);
+ }
+
+ if(prog == nil)
+ goto done;
+ if(fd < 0)
+ fd = open(m->path, OREAD|OCEXEC);
+ snprint(tmp, sizeof(tmp), "%s | resample -x%d", prog, Coversz);
+ pipe(p);
+ if((pid = rfork(RFPROC|RFFDG|RFNOTEG|RFCENVG|RFNOWAIT)) == 0){
+ dup(fd, 0); close(fd);
+ dup(p[1], 1); close(p[1]);
+ if(!debug){
+ dup(fd = open("/dev/null", OWRITE), 2);
+ close(fd);
+ }
+ execl("/bin/rc", "rc", "-c", tmp, nil);
+ sysfatal("execl: %r");
+ }
+ close(fd);
+ close(p[1]);
+
+ if(pid > 0){
+ newcover = readimage(display, p[0], 1);
+ /* if readtags fails, readimage will also fail, and we send nil over ch */
+ sendp(ch, newcover);
+ }
+ close(p[0]);
+done:
+ if(pid < 0)
+ sendp(ch, nil);
+ chanclose(ch);
+ chanfree(ch);
+ postnote(PNGROUP, pid, "die");
+ threadexits(nil);
+}
+
+static int
+playerret(Player *player)
+{
+ return recvul(player->ev) == Everror ? -1 : 0;
+}
+
+static void
+pnotify(Player *p)
+{
+ Meta *m;
+ int i;
+
+ if(!pnotifies)
+ return;
+
+ if(p != nil){
+ m = getmeta(p->pcur);
+ for(i = 0; cols[i] != 0; i++)
+ Bprint(&out, "%s\t", getcol(m, cols[i]));
+ }
+ Bprint(&out, "\n");
+ Bflush(&out);
+}
+
+static void
+stop(Player *player)
+{
+ if(player == nil)
+ return;
+
+ if(player == playernext)
+ playernext = nil;
+ if(!getmeta(player->pcur)->filefmt[0])
+ playerret(player);
+ if(player == playercurr)
+ pnotify(nil);
+ sendul(player->ctl, Cstop);
+}
+
+static void
+start(Player *player)
+{
+ if(player == nil)
+ return;
+ if(!getmeta(player->pcur)->filefmt[0])
+ playerret(player);
+ pnotify(player);
+ sendul(player->ctl, Cstart);
+}
+
+static void playerthread(void *player_);
+
+static void
+setgain(Player *player)
+{
+ if(player == nil)
+ return;
+ if(rg == Rgdisabled)
+ player->gain = 0.0;
+ if(rg == Rgtrack)
+ player->gain = getmeta(player->pcur)->rgtrack;
+ else if(rg == Rgalbum)
+ player->gain = getmeta(player->pcur)->rgalbum;
+ player->gain = pow(10.0, player->gain/20.0);
+}
+
+static Player *
+newplayer(int pcur, int loadnext)
+{
+ Player *player;
+
+ if(playernext != nil && loadnext){
+ if(pcur == playernext->pcur){
+ player = playernext;
+ playernext = nil;
+ goto done;
+ }
+ stop(playernext);
+ playernext = nil;
+ }
+
+ player = mallocz(sizeof(*player), 1);
+ player->ctl = chancreate(sizeof(ulong), 0);
+ player->ev = chancreate(sizeof(ulong), 0);
+ player->pcur = pcur;
+ setgain(player);
+
+ threadcreate(playerthread, player, 32768);
+ if(getmeta(pcur)->filefmt[0] && playerret(player) < 0)
+ return nil;
+
+done:
+ if(pcur < pl->n-1 && playernext == nil && loadnext)
+ playernext = newplayer(pcur+1, 0);
+
+ return player;
+}
+
+static long
+iosetname(va_list *)
+{
+ procsetname("player/io");
+ return 0;
+}
+
+static int
+clip16(int v)
+{
+ if(v > 0x7fff)
+ return 0x7fff;
+ if(v < -0x8000)
+ return -0x8000;
+ return v;
+}
+
+static void
+gain(double g, char *buf, long n)
+{
+ s16int *f;
+
+ if(g != 1.0)
+ for(f = (s16int*)buf; n >= 4; n -= 4){
+ *f++ = clip16(*f * g);
+ *f++ = clip16(*f * g);
+ }
+}
+
+static void
+playerthread(void *player_)
+{
+ char *buf, cmd[64], seekpos[12], *fmt, *path, *icytitle;
+ Player *player;
+ Ioproc *io;
+ Image *thiscover;
+ ulong c;
+ int p[2], q[2], fd, pid, noinit, trycoverload;
+ long n, r;
+ vlong boffset, boffsetlast;
+ Meta *cur;
+
+ threadsetname("player");
+ player = player_;
+ noinit = 0;
+ boffset = 0;
+ buf = nil;
+ trycoverload = 1;
+ io = nil;
+
+restart:
+ cur = getmeta(player->pcur);
+ fmt = cur->filefmt;
+ path = cur->path;
+ fd = -1;
+ q[0] = -1;
+ pid = -1;
+ if(*fmt){
+ if((fd = open(cur->path, OREAD)) < 0){
+ fprint(2, "%r\n");
+ sendul(player->ev, Everror);
+ chanclose(player->ev);
+ goto freeplayer;
+ }
+ }else{
+ sendul(player->ev, Evready);
+ chanclose(player->ev);
+ if(strncmp(cur->path, "http://", 7) == 0){ /* try icy */
+ pipe(q);
+ if(icyget(cur, q[0], &player->icytitlec) == 0){
+ fd = q[1];
+ path = nil;
+ }else{
+ close(q[0]); q[0] = -1;
+ close(q[1]);
+ }
+ }
+ }
+
+ pipe(p);
+ if((pid = rfork(RFPROC|RFFDG|RFNOTEG|RFCENVG|RFNOWAIT)) == 0){
+ close(q[0]);
+ close(p[1]);
+ if(fd < 0)
+ fd = open("/dev/null", OREAD);
+ dup(fd, 0); close(fd); /* fd == q[1] when it's Icy */
+ dup(p[0], 1); close(p[0]);
+ if(!debug){
+ dup(fd = open("/dev/null", OWRITE), 2);
+ close(fd);
+ }
+ if(*fmt){
+ snprint(cmd, sizeof(cmd), "/bin/audio/%sdec", fmt);
+ snprint(seekpos, sizeof(seekpos), "%g", (double)boffset/Bps);
+ execl(cmd, cmd, boffset ? "-s" : nil, seekpos, nil);
+ }else{
+ execl("/bin/play", "play", "-o", "/fd/1", path, nil);
+ }
+ exits("%r");
+ }
+ if(pid < 0)
+ sysfatal("rfork: %r");
+ /* fd is q[1] when it's Icy */
+ close(fd);
+ close(p[0]);
+
+ c = 0;
+ if(!noinit){
+ if(*fmt){
+ sendul(player->ev, Evready);
+ chanclose(player->ev);
+ }
+ buf = malloc(Relbufsz);
+ if((io = ioproc()) == nil)
+ sysfatal("player: %r");
+ iocall(io, iosetname);
+ if((n = ioreadn(io, p[1], buf, Relbufsz)) < 0)
+ fprint(2, "player: %r\n");
+ if(recv(player->ctl, &c) < 0 || c != Cstart)
+ goto freeplayer;
+ if(n < 1)
+ goto next;
+ audioon();
+ gain(player->gain, buf, n);
+ boffset = iowrite(io, audio, buf, n);
+ noinit = 1;
+ }
+
+ boffsetlast = boffset;
+ byteswritten = boffset;
+ pcurplaying = player->pcur;
+ redraw(1);
+
+ while(1){
+ n = ioread(io, p[1], buf, Relbufsz);
+ if(n <= 0){
+ if(stopafter){
+ audiooff();
+ goto stop;
+ }else if(repeatone){
+ c = Cseekrel;
+ boffset = 0;
+ }
+ break;
+ }
+ if(player->icytitlec != nil && nbrecv(player->icytitlec, &icytitle) != 0){
+ free(player->icytitle);
+ player->icytitle = icytitle;
+ redraw(1);
+ }
+ thiscover = nil;
+ if(player->img != nil && nbrecv(player->img, &thiscover) != 0){
+ freeimage(cover);
+ cover = thiscover;
+ redraw(0);
+ player->img = nil;
+ }
+ r = nbrecv(player->ctl, &c);
+ if(r < 0){
+ audiooff();
+ goto stop;
+ }else if(r != 0){
+ if(c == Ctoggle){
+ audiooff();
+ if(recv(player->ctl, &c) < 0)
+ goto stop;
+ }
+ if(c == Cseekrel && *fmt){
+ boffset = MAX(0, boffset + player->seek*Bps);
+ n = 0;
+ break;
+ }else if(c == Cstop){
+ audiooff();
+ goto stop;
+ }
+ }
+
+ boffset += n;
+ byteswritten = boffset;
+ audioon();
+ gain(player->gain, buf, n);
+ iowrite(io, audio, buf, n);
+ if(trycoverload){
+ trycoverload = 0;
+ player->img = chancreate(sizeof(Image*), 0);
+ proccreate(coverload, player, 4096);
+ }
+ if(labs(boffset/Relbufsz - boffsetlast/Relbufsz) > 0){
+ boffsetlast = boffset;
+ redraw(0);
+ }
+ }
+
+ if(n < 1){ /* seeking backwards or end of the song */
+ close(p[1]);
+ p[1] = -1;
+ postnote(PNGROUP, pid, "die");
+ if(c != Cseekrel || (getmeta(pcurplaying)->duration && boffset >= getmeta(pcurplaying)->duration/1000*Bps)){
+next:
+ playercurr = nil;
+ playercurr = newplayer((player->pcur+1) % pl->n, 1);
+ start(playercurr);
+ goto stop;
+ }
+ goto restart;
+ }
+
+stop:
+ if(player->img != nil)
+ freeimage(recvp(player->img));
+freeplayer:
+ close(p[1]);
+ closeioproc(io);
+ postnote(PNGROUP, pid, "die");
+ if(player->icytitlec != nil){
+ while((icytitle = recvp(player->icytitlec)) != nil)
+ free(icytitle);
+ chanfree(player->icytitlec);
+ }
+ chanfree(player->ctl);
+ chanfree(player->ev);
+ if(player == playercurr)
+ playercurr = nil;
+ if(player == playernext)
+ playernext = nil;
+ free(buf);
+ free(player->icytitle);
+ free(player);
+ threadexits(nil);
+}
+
+static int
+toggle(Player *player)
+{
+ return (player != nil && sendul(player->ctl, Ctoggle) == 1) ? 0 : -1;
+}
+
+static void
+seekrel(Player *player, double off)
+{
+ if(player != nil){
+ player->seek = off;
+ sendul(player->ctl, Cseekrel);
+ }
+}
+
+static void
+freeplist(Playlist *pl)
+{
+ if(pl != nil){
+ free(pl->m);
+ free(pl->raw);
+ }
+ free(pl);
+}
+
+static char *
+readall(int f)
+{
+ int bufsz, sz, n;
+ char *s;
+
+ bufsz = 1023;
+ s = nil;
+ for(sz = 0;; sz += n){
+ if(bufsz-sz < 1024){
+ bufsz *= 2;
+ s = realloc(s, bufsz);
+ }
+ if((n = readn(f, s+sz, bufsz-sz-1)) < 1)
+ break;
+ }
+ if(n < 0 || sz < 1){
+ if(n == 0)
+ werrstr("empty");
+ free(s);
+ return nil;
+ }
+ s[sz] = 0;
+
+ return s;
+}
+
+static int
+cmpint(void *a, void *b)
+{
+ return *(int*)a - *(int*)b;
+}
+
+static Playlist *
+readplist(int fd, int mincolwidth[Ncol])
+{
+ char *raw, *s, *e, *a[5], *b;
+ int plsz, i, *w;
+ Playlist *pl;
+ Meta *m;
+
+ if((raw = readall(fd)) == nil)
+ return nil;
+
+ plsz = 0;
+ for(s = raw; (s = strchr(s, '\n')) != nil; s++){
+ if(*(++s) == '\n')
+ plsz++;
+ }
+
+ if((pl = calloc(1, sizeof(*pl))) == nil || (pl->m = calloc(plsz+1, sizeof(Meta))) == nil){
+ freeplist(pl);
+ werrstr("no memory");
+ return nil;
+ }
+
+ pl->raw = raw;
+ for(s = pl->raw, m = pl->m, e = s; e != nil; s = e){
+ if((e = strchr(s, '\n')) == nil)
+ goto addit;
+ s += 2;
+ *e++ = 0;
+ switch(s[-2]){
+ case 0:
+addit:
+ if(m->path != nil){
+ if(m->filefmt == nil)
+ m->filefmt = "";
+ pl->n++;
+ m++;
+ }
+ break;
+ case Pimage:
+ if(tokenize(s, a, nelem(a)) >= 4){
+ m->imageoffset = atoi(a[0]);
+ m->imagesize = atoi(a[1]);
+ m->imagereader = atoi(a[2]);
+ m->imagefmt = a[3];
+ }
+ break;
+ case Pduration:
+ m->duration = strtoull(s, nil, 0);
+ break;
+ case Partist:
+ if(m->numartist < Maxartist)
+ m->artist[m->numartist++] = s;
+ break;
+ case Pfilefmt: m->filefmt = s; break;
+ case Palbum: m->album = s; break;
+ case Pdate: m->date = s; break;
+ case Ptitle: m->title = s; break;
+ case Ptrack: m->track = s; break;
+ case Prgtrack: m->rgtrack = atof(s); break;
+ case Prgalbum: m->rgalbum = atof(s); break;
+ case Ppath:
+ m->path = s;
+ m->basename = (b = utfrrune(s, '/')) == nil ? s : b+1;
+ break;
+ }
+ }
+
+ w = malloc(sizeof(int)*pl->n);
+ for(i = 0; cols[i] != 0; i++){
+ for(m = pl->m; m != pl->m + pl->n; m++)
+ w[m - pl->m] = stringwidth(f, getcol(m, cols[i]));
+ qsort(w, pl->n, sizeof(*w), cmpint);
+ mincolwidth[i] = w[93*(pl->n-1)/100];
+ }
+ free(w);
+
+ return pl;
+}
+
+static void
+recenter(void)
+{
+ updatescrollsz();
+ scroll = pcur - scrollsz/2 + 1;
+}
+
+static void
+seekto(char *s)
+{
+ vlong p;
+ char *e;
+
+ for(p = 0; *s; s = e){
+ p += strtoll(s, &e, 10);
+ if(s == e)
+ break;
+ if(*e == ':'){
+ p *= 60;
+ e++;
+ }
+ }
+
+ seekrel(playercurr, p - byteswritten/Bps);
+}
+
+static void
+search(char d)
+{
+ Meta *m;
+ static char buf[64];
+ static int sz;
+ int inc, i, a, cycle;
+
+ inc = (d == '/' || d == 'n') ? 1 : -1;
+ if(d == '/' || d == '?')
+ sz = enter(inc > 0 ? "forward:" : "backward:", buf, sizeof(buf), mctl, &kctl, screen->screen);
+ if(sz < 1){
+ redraw(1);
+ return;
+ }
+
+ cycle = 1;
+ for(i = pcur+inc; i >= 0 && i < pl->n;){
+ m = getmeta(i);
+ for(a = 0; a < m->numartist; a++){
+ if(cistrstr(m->artist[a], buf) != nil)
+ break;
+ }
+ if(a < m->numartist)
+ break;
+ if(m->album != nil && cistrstr(m->album, buf) != nil)
+ break;
+ if(m->title != nil && cistrstr(m->title, buf) != nil)
+ break;
+ if(cistrstr(m->path, buf) != nil)
+ break;
+onemore:
+ i += inc;
+ }
+ if(i >= 0 && i < pl->n){
+ pcur = i;
+ recenter();
+ }else if(cycle && i+inc < 0){
+ cycle = 0;
+ i = pl->n;
+ goto onemore;
+ }else if(cycle && i+inc >= pl->n){
+ cycle = 0;
+ i = -1;
+ goto onemore;
+ }
+ redraw(1);
+}
+
+static void
+toggleshuffle(void)
+{
+ int i, m, xi, a, c, pcurnew, pcurplayingnew;
+
+ if(shuffle == nil){
+ if(pl->n < 2)
+ return;
+
+ m = pl->n;
+ if(pl->n < 4){
+ a = 1;
+ c = 3;
+ m = 7;
+ }else{
+ m += 1;
+ m |= m >> 1;
+ m |= m >> 2;
+ m |= m >> 4;
+ m |= m >> 8;
+ m |= m >> 16;
+ a = 1 + nrand(m/4)*4; /* 1 ≤ a < m && a mod 4 = 1 */
+ c = 3 + nrand((m-2)/2)*2; /* 3 ≤ c < m-1 && c mod 2 = 1 */
+ }
+
+ shuffle = malloc(pl->n*sizeof(*shuffle));
+ xi = pcurplaying < 0 ? pcur : pcurplaying;
+ pcurplayingnew = -1;
+ pcurnew = 0;
+ for(i = 0; i < pl->n;){
+ if(xi < pl->n){
+ if(pcur == xi)
+ pcurnew = i;
+ if(pcurplaying == xi)
+ pcurplayingnew = i;
+ shuffle[i++] = xi;
+ }
+ xi = (a*xi + c) & m;
+ }
+ pcur = pcurnew;
+ pcurplaying = pcurplayingnew;
+ }else{
+ pcur = shuffle[pcur];
+ if(pcurplaying >= 0)
+ pcurplaying = shuffle[pcurplaying];
+ free(shuffle);
+ shuffle = nil;
+ }
+
+ stop(playernext);
+ if(pcur < pl->n-1)
+ playernext = newplayer(pcur+1, 0);
+}
+
+static void
+adjustcolumns(void)
+{
+ int i, n, total, width;
+
+ total = 0;
+ n = 0;
+ width = Dx(screen->r);
+ for(i = 0; cols[i] != 0; i++){
+ if(cols[i] == Pduration || cols[i] == Pdate || cols[i] == Ptrack)
+ width -= mincolwidth[i] + 8;
+ else{
+ total += mincolwidth[i];
+ n++;
+ }
+ }
+ colspath = 0;
+ for(i = 0; cols[i] != 0; i++){
+ if(cols[i] == Ppath || cols[i] == Pbasename)
+ colspath = 1;
+ if(cols[i] == Pduration || cols[i] == Pdate || cols[i] == Ptrack)
+ colwidth[i] = mincolwidth[i];
+ else
+ colwidth[i] = (width - Scrollwidth - n*8) * mincolwidth[i] / total;
+ }
+}
+
+static void
+plumbaudio(void *kbd)
+{
+ int i, f, pf, mcw[Ncol], playing, shuffled;
+ Playlist *p;
+ Plumbmsg *m;
+ char *s, *e;
+ Rune c;
+
+ threadsetname("audio/plumb");
+ if((f = plumbopen("audio", OREAD)) >= 0){
+ while((m = plumbrecv(f)) != nil){
+ s = m->data;
+ if(strncmp(s, "key", 3) == 0 && isspace(s[3])){
+ for(s = s+4; isspace(*s); s++);
+ for(; (i = chartorune(&c, s)) > 0 && c != Runeerror; s += i)
+ sendul(kbd, c);
+ continue;
+ }
+ if(*s != '/' && m->wdir != nil)
+ s = smprint("%s/%.*s", m->wdir, m->ndata, m->data);
+
+ if((e = strrchr(s, '.')) != nil && strcmp(e, ".plist") == 0 && (pf = open(s, OREAD|OCEXEC)) >= 0){
+ p = readplist(pf, mcw);
+ close(pf);
+ if(p == nil)
+ continue;
+ playing = pcurplaying;
+ if(shuffled = (shuffle != nil))
+ sendul(kbd, 's');
+ /* make sure nothing is playing */
+ while(pcurplaying >= 0){
+ sendul(kbd, 'v');
+ sleep(100);
+ }
+ freeplist(pl);
+ pl = p;
+ memmove(mincolwidth, mcw, sizeof(mincolwidth));
+ adjustcolumns();
+ pcur = 0;
+ if(shuffled){
+ pcur = nrand(pl->n);
+ sendul(kbd, 's');
+ }
+ redraw(1);
+ if(playing >= 0)
+ sendul(kbd, '\n');
+ }else{
+ for(i = 0; i < pl->n; i++){
+ if(strcmp(pl->m[i].path, s) == 0){
+ sendul(playc, i);
+ break;
+ }
+ }
+ }
+
+ if(s != m->data)
+ free(s);
+ plumbfree(m);
+ }
+ }
+
+ threadexits(nil);
+}
+
+static void
+kbproc(void *cchan)
+{
+ char *s, buf[128], buf2[128];
+ int kbd, n;
+ Rune r;
+
+ threadsetname("kbproc");
+ if((kbd = open("/dev/kbd", OREAD|OCEXEC)) < 0)
+ sysfatal("/dev/kbd: %r");
+
+ buf2[0] = 0;
+ buf2[1] = 0;
+ buf[0] = 0;
+ for(;;){
+ if(buf[0] != 0){
+ n = strlen(buf)+1;
+ memmove(buf, buf+n, sizeof(buf)-n);
+ }
+ if(buf[0] == 0){
+ n = read(kbd, buf, sizeof(buf)-1);
+ if(n <= 0)
+ break;
+ buf[n-1] = 0;
+ buf[n] = 0;
+ }
+
+ switch(buf[0]){
+ case 'k':
+ for(s = buf+1; *s;){
+ s += chartorune(&r, s);
+ if(utfrune(buf2+1, r) == nil){
+ if(r == Kshift)
+ shiftdown = 1;
+ }
+ }
+ break;
+ case 'K':
+ for(s = buf2+1; *s;){
+ s += chartorune(&r, s);
+ if(utfrune(buf+1, r) == nil){
+ if(r == Kshift)
+ shiftdown = 0;
+ }
+ }
+ break;
+ case 'c':
+ if(chartorune(&r, buf+1) > 0 && r != Runeerror)
+ nbsend(cchan, &r);
+ default:
+ continue;
+ }
+
+ strcpy(buf2, buf);
+ }
+
+ close(kbd);
+ threadexits(nil);
+}
+
+static void
+usage(void)
+{
+ fprint(2, "usage: %s [-s] [-c aAdDtTp]\n", argv0);
+ sysfatal("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ Rune key;
+ Mouse m;
+ ulong ind;
+ enum {
+ Emouse,
+ Eresize,
+ Ekey,
+ Eplay,
+ };
+ Alt a[] = {
+ { nil, &m, CHANRCV },
+ { nil, nil, CHANRCV },
+ { nil, &key, CHANRCV },
+ { nil, &ind, CHANRCV },
+ { nil, nil, CHANEND },
+ };
+ int n, scrolling, oldpcur, oldbuttons, pnew, shuffled;
+ int seekmx, full;
+ char buf[64];
+
+ shuffled = 0;
+ ARGBEGIN{
+ case 'd':
+ debug++;
+ break;
+ case 's':
+ shuffled = 1;
+ break;
+ case 'c':
+ cols = EARGF(usage());
+ if(strlen(cols) >= nelem(colwidth))
+ sysfatal("max %d columns allowed", nelem(colwidth));
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND;
+
+ Binit(&out, 1, OWRITE);
+ pnotifies = fd2path(1, buf, sizeof(buf)) == 0 && strcmp(buf, "/dev/cons") != 0;
+
+ if(initdraw(nil, nil, "zuke") < 0)
+ sysfatal("initdraw: %r");
+ unlockdisplay(display);
+ display->locking = 1;
+ f = display->defaultfont;
+ Scrollwidth = MAX(14, stringwidth(f, "#"));
+ Scrollheight = MAX(16, f->height);
+ Seekthicc = Scrollheight + 2;
+ Coversz = MAX(64, stringwidth(f, "¹∫ 00:00:00/00:00:00 100%"));
+ if((mctl = initmouse(nil, screen)) == nil)
+ sysfatal("initmouse: %r");
+
+ kctl.c = chancreate(sizeof(Rune), 20);
+ proccreate(kbproc, kctl.c, 4096);
+ playc = chancreate(sizeof(ind), 0);
+
+ a[Emouse].c = mctl->c;
+ a[Eresize].c = mctl->resizec;
+ a[Ekey].c = kctl.c;
+ a[Eplay].c = playc;
+
+ redrawc = chancreate(sizeof(ulong), 8);
+ proccreate(redrawproc, nil, 8192);
+
+ Theme th[Numcolors] = {
+ [Dback] { "back", 0xf0f0f0 },
+ [Dfhigh] { "high", 0xffffff },
+ [Dfmed] { "text", 0x343434 },
+ [Dflow] { "border", 0xa5a5a5 },
+ [Dfinv] { "hold", 0x323232 },
+ [Dbmed] { "menubar", 0x72dec2 },
+ [Dblow] { "ltitle", 0x404040 },
+ [Dbinv] { "paletext", 0xffb545 },
+ };
+ readtheme(th, nelem(th), nil);
+ for(n = 0; n < Numcolors; n++){
+ colors[n].rgb = th[n].c >> 8 & ~(0xff<<24);
+ colors[n].im = allocimage(display, Rect(0,0,1,1), RGB24, 1, th[n].c);
+ }
+
+ srand(time(0));
+ pcurplaying = -1;
+ chvolume(0);
+ fmtinstall('P', positionfmt);
+ threadsetname("zuke");
+
+ if((pl = readplist(0, mincolwidth)) == nil){
+ fprint(2, "playlist: %r\n");
+ sysfatal("playlist error");
+ }
+
+ m.buttons = 0;
+ scrolling = 0;
+ seekmx = 0;
+ adjustcolumns();
+
+ proccreate(plumbaudio, kctl.c, 4096);
+
+ if(shuffled){
+ pcur = nrand(pl->n);
+ toggleshuffle();
+ }
+ full = 1;
+
+ for(;;){
+ updatescrollsz();
+ scroll = CLAMP(scroll, 0, pl->n - scrollsz);
+ redraw(full);
+
+ oldpcur = pcur;
+ full = 0;
+ if(seekmx != newseekmx){
+ seekmx = newseekmx;
+ redraw(0);
+ }
+
+ oldbuttons = m.buttons;
+ switch(alt(a)){
+ case Emouse:
+ if(ptinrect(m.xy, seekbar)){
+ seekoff = getmeta(pcurplaying)->duration * (double)(m.xy.x-1-seekbar.min.x) / (double)Dx(seekbar);
+ if(seekoff < 0)
+ seekoff = 0;
+ newseekmx = m.xy.x;
+ }else{
+ newseekmx = -1;
+ }
+ if(oldbuttons == m.buttons && m.buttons == 0)
+ continue;
+
+ if(m.buttons != 2)
+ scrolling = 0;
+ if(m.buttons == 0)
+ break;
+ if(m.buttons == 8){
+ scroll -= (shiftdown ? 0 : scrollsz/4)+1;
+ break;
+ }else if(m.buttons == 16){
+ scroll += (shiftdown ? 0 : scrollsz/4)+1;
+ break;
+ }
+
+ n = (m.xy.y - screen->r.min.y)/f->height;
+
+ if(m.xy.x <= screen->r.min.x+Scrollwidth && m.xy.y <= screen->r.max.y-Seekthicc){
+ if(m.buttons == 1){
+ scroll -= n+1;
+ break;
+ }else if(m.buttons == 4){
+ scroll += n+1;
+ break;
+ }else if(m.buttons == 2){
+ scrolling = 1;
+ }
+ }
+
+ if(!scrolling && ptinrect(m.xy, insetrect(seekbar, -4))){
+ if(ptinrect(m.xy, seekbar))
+ seekrel(playercurr, seekoff/1000.0 - byteswritten/Bps);
+ break;
+ }
+
+ if(scrolling){
+ if(scrollsz >= pl->n)
+ break;
+ scroll = (m.xy.y - screen->r.min.y)*(pl->n-scrollsz) / (Dy(screen->r)-Seekthicc);
+ }else if(m.buttons == 1 || m.buttons == 2){
+ n += scroll;
+ if(n < pl->n){
+ pcur = n;
+ if(m.buttons == 2 && oldbuttons == 0){
+ stop(playercurr);
+ playercurr = newplayer(pcur, 1);
+ start(playercurr);
+ }
+ }
+ }
+ break;
+ case Eresize: /* resize */
+ if(getwindow(display, Refnone) < 0)
+ sysfatal("getwindow: %r");
+ adjustcolumns();
+ redraw(1);
+ break;
+ case Ekey:
+ switch(key){
+ default:
+ if(isdigit(key) && pcurplaying >= 0 && getmeta(pcurplaying)->duration > 0){
+ buf[0] = key;
+ buf[1] = 0;
+ if(enter("seek:", buf, sizeof(buf), mctl, &kctl, screen->screen) < 1)
+ redraw(1);
+ else
+ seekto(buf);
+ }
+ break;
+ case Kleft:
+ seekrel(playercurr, -(double)Seek);
+ break;
+ case Kright:
+ seekrel(playercurr, Seek);
+ break;
+ case ',':
+ seekrel(playercurr, -(double)Seekfast);
+ break;
+ case '.':
+ seekrel(playercurr, Seekfast);
+ break;
+ case Kup:
+ pcur--;
+ break;
+ case Kpgup:
+ pcur -= scrollsz;
+ break;
+ case Kdown:
+ pcur++;
+ break;
+ case Kpgdown:
+ pcur += scrollsz;
+ break;
+ case Kend:
+ pcur = pl->n-1;
+ scroll = pl->n-scrollsz;
+ break;
+ case Khome:
+ pcur = 0;
+ break;
+ case '\n':
+playcur:
+ stop(playercurr);
+ playercurr = newplayer(pcur, 1);
+ start(playercurr);
+ break;
+ case 'q':
+ case Kdel:
+ stop(playercurr);
+ stop(playernext);
+ threadexitsall(nil);
+ case 'i':
+ case 'o':
+ if(pcur == pcurplaying)
+ oldpcur = -1;
+ pcur = pcurplaying;
+ recenter();
+ break;
+ case 'b':
+ case '>':
+ if(playercurr == nil)
+ break;
+ pnew = pcurplaying;
+ if(++pnew >= pl->n)
+ pnew = 0;
+ stop(playercurr);
+ playercurr = newplayer(pnew, 1);
+ start(playercurr);
+ break;
+ case 'z':
+ case '<':
+ if(playercurr == nil)
+ break;
+ pnew = pcurplaying;
+ if(--pnew < 0)
+ pnew = pl->n-1;
+ stop(playercurr);
+ playercurr = newplayer(pnew, 1);
+ start(playercurr);
+ break;
+ case '-':
+ chvolume(-1);
+ redraw(0);
+ continue;
+ case '+':
+ case '=':
+ chvolume(+1);
+ redraw(0);
+ continue;
+ case 'v':
+ stop(playercurr);
+ stop(playernext);
+ playercurr = nil;
+ playernext = nil;
+ pcurplaying = -1;
+ freeimage(cover);
+ cover = nil;
+ full = 1;
+ break;
+ case 'g':
+ rg = (rg+1) % Numrg;
+ setgain(playercurr);
+ setgain(playernext);
+ redraw(0);
+ break;
+ case 's':
+ toggleshuffle();
+ recenter();
+ full = 1;
+ break;
+ case 'r':
+ repeatone ^= 1;
+ redraw(0);
+ break;
+ case 'x':
+ stopafter ^= 1;
+ redraw(0);
+ break;
+ case 'c':
+ case 'p':
+ case ' ':
+ if(toggle(playercurr) != 0)
+ goto playcur;
+ break;
+ case '/':
+ case '?':
+ case 'n':
+ case 'N':
+ search(key);
+ break;
+ }
+ break;
+ case Eplay:
+ pcur = ind;
+ recenter();
+ if(playercurr != nil)
+ goto playcur;
+ break;
+ }
+
+ if(pcur != oldpcur){
+ pcur = CLAMP(pcur, 0, pl->n-1);
+ if(pcur < scroll)
+ scroll = pcur;
+ else if(pcur > scroll + scrollsz)
+ scroll = pcur - scrollsz;
+ }
+ }
+}
--- /dev/null
+++ b/sys/src/cmd/bar.c
@@ -1,0 +1,371 @@
+#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 int lastpx;
+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);
+ if(s == nil)
+ break;
+ }
+
+}
+
+static void
+redraw(void)
+{
+ static char s[1024];
+ char tmp[1024];
+ 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);
+ }
+ lastpx = p.x;
+ draw(screen, r, cback, nil, ZP);
+ string(screen, p, ctext, ZP, f, s);
+ if(hlitem >= 0){
+ r.min.x = lastpx;
+ for(i = 0; i < hlitem; i++)
+ r.min.x += itemw[i] + sepw;
+ 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);
+ if(s == nil)
+ break;
+ sendp(c, s);
+ }
+ 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;
+
+ x -= lastpx;
+ for(i = ix = 0; i < nitems; i++){
+ if(x >= ix && x <= ix+itemw[i]){
+ fprint(1, "%d\t%s\n", buttons, items[i]);
+ hlitem = i;
+ break;
+ }
+ ix += itemw[i] + sepw;
+ }
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ Keyboardctl *kctl;
+ Mousectl *mctl;
+ uvlong t, oldt;
+ int oldbuttons;
+ char *s;
+ 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");
+
+ 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");
+ Theme th[2] = {
+ { "back", DPalegreygreen },
+ { "text", DBlack },
+ };
+ readtheme(th, nelem(th), nil);
+ cback = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[0].c);
+ ctext = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[1].c);
+
+ 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;
+ if(m.buttons == 0)
+ hlitem = -1;
+ else
+ clicked(m.xy.x, m.buttons);
+ /* wet floor */
+
+ if(0){
+ case Eresize:
+ if(getwindow(display, Refnone) < 0)
+ threadexitsall(nil);
+ owidth = 0;
+ /* 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;
+ }
+ }
+ place();
+ redraw();
+ break;
+ }
+ }
+}
--- /dev/null
+++ b/sys/src/cmd/faces/main.c
@@ -1,0 +1,744 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <plumb.h>
+#include <regexp.h>
+#include <event.h> /* for support routines only */
+#include <bio.h>
+#include "faces.h"
+
+int history = 0; /* use old interface, showing history of mailbox rather than current state */
+int initload = 0; /* initialize program with contents of mail box */
+int clickrm = 0; /* allows removing mail faces by left clicking */
+
+enum
+{
+ Facesep = 6, /* must be even to avoid damaging background stipple */
+ Infolines = 9,
+
+ HhmmTime = 18*60*60, /* max age of face to display hh:mm time */
+};
+
+enum
+{
+ Mainp,
+ Timep,
+ Mousep,
+ NPROC
+};
+
+int pids[NPROC];
+char *procnames[] = {
+ "main",
+ "time",
+ "mouse"
+};
+
+Rectangle leftright = {0, 0, 20, 15};
+
+uchar leftdata[] = {
+ 0x00, 0x80, 0x00, 0x01, 0x80, 0x00, 0x03, 0x80,
+ 0x00, 0x07, 0x80, 0x00, 0x0f, 0x00, 0x00, 0x1f,
+ 0xff, 0xf0, 0x3f, 0xff, 0xf0, 0xff, 0xff, 0xf0,
+ 0x3f, 0xff, 0xf0, 0x1f, 0xff, 0xf0, 0x0f, 0x00,
+ 0x00, 0x07, 0x80, 0x00, 0x03, 0x80, 0x00, 0x01,
+ 0x80, 0x00, 0x00, 0x80, 0x00
+};
+
+uchar rightdata[] = {
+ 0x00, 0x10, 0x00, 0x00, 0x18, 0x00, 0x00, 0x1c,
+ 0x00, 0x00, 0x1e, 0x00, 0x00, 0x0f, 0x00, 0xff,
+ 0xff, 0x80, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xf0,
+ 0xff, 0xff, 0xc0, 0xff, 0xff, 0x80, 0x00, 0x0f,
+ 0x00, 0x00, 0x1e, 0x00, 0x00, 0x1c, 0x00, 0x00,
+ 0x18, 0x00, 0x00, 0x10, 0x00
+};
+
+enum{
+ Cbgrnd,
+ Carrow,
+ Csmallfg,
+ Cfacebg,
+ Ctext,
+ Csmalltext,
+ Ncols,
+};
+Image *cols[Ncols];
+Image *left; /* left-pointing cols[Carrow] mask */
+Image *right; /* right-pointing cols[Carrow] mask */
+Font *tinyfont;
+Font *mediumfont;
+Font *datefont;
+int first, last; /* first and last visible face; last is first invisible */
+int nfaces;
+int mousefd;
+int nacross;
+int ndown;
+
+char date[64];
+Face **faces;
+char *maildir = "/mail/fs/mbox";
+ulong now;
+
+Point datep = { 8, 6 };
+Point facep = { 8, 6+0+4 }; /* 0 updated to datefont->height in init() */
+Point enddate; /* where date ends on display; used to place cols[Carrow]s */
+Rectangle leftr; /* location of left cols[Carrow] on display */
+Rectangle rightr; /* location of right cols[Carrow] on display */
+void updatetimes(void);
+
+void
+setdate(void)
+{
+ now = time(nil);
+ strcpy(date, ctime(now));
+ date[4+4+3+5] = '\0'; /* change from Thu Jul 22 14:28:43 EDT 1999\n to Thu Jul 22 14:28 */
+}
+
+void
+init(void)
+{ int i;
+
+ mousefd = open("/dev/mouse", OREAD);
+ if(mousefd < 0){
+ fprint(2, "faces: can't open mouse: %r\n");
+ exits("mouse");
+ }
+ initplumb();
+
+ Theme th[nelem(cols)] = {
+ [Cbgrnd] { "rioback", DBlack },
+ [Carrow] { "palehold", 0x008888FF },
+ [Csmallfg] { "paletext", DBlack },
+ [Cfacebg] { "white", DWhite },
+ [Ctext] { "hold", DWhite },
+ [Csmalltext] { "white", DWhite },
+ };
+ readtheme(th, nelem(th), nil);
+ for(i=0; i<nelem(cols); i++)
+ cols[i] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[i].c);
+ left = allocimage(display, leftright, GREY1, 0, DWhite);
+ right = allocimage(display, leftright, GREY1, 0, DWhite);
+ if(cols[Cbgrnd]==nil || cols[Carrow]==nil || cols[Csmallfg] == nil || cols[Cfacebg] == nil || cols[Ctext] == nil || left==nil || right==nil){
+ fprint(2, "faces: can't create images: %r\n");
+ exits("image");
+ }
+ loadimage(left, leftright, leftdata, sizeof leftdata);
+ loadimage(right, leftright, rightdata, sizeof rightdata);
+
+ /* initialize little fonts */
+ tinyfont = openfont(display, "/lib/font/bit/misc/ascii.5x7.font");
+ if(tinyfont == nil)
+ tinyfont = font;
+ mediumfont = openfont(display, "/lib/font/bit/vga/unicode.font");
+ if(mediumfont == nil)
+ mediumfont = font;
+ datefont = font;
+
+ facep.y += datefont->height;
+ if(datefont->height & 1) /* stipple parity */
+ facep.y++;
+ faces = nil;
+}
+
+void
+drawtime(void)
+{
+ Rectangle r;
+
+ r.min = addpt(screen->r.min, datep);
+ if(eqpt(enddate, ZP)){
+ enddate = r.min;
+ enddate.x += stringwidth(datefont, "Wed May 30 22:54"); /* nice wide string */
+ enddate.x += Facesep; /* for safety */
+ }
+ r.max.x = enddate.x;
+ r.max.y = enddate.y+datefont->height;
+ draw(screen, r, cols[Cbgrnd], nil, ZP);
+ string(screen, r.min, cols[Csmallfg], ZP, datefont, date);
+}
+
+void
+timeproc(void)
+{
+ for(;;){
+ lockdisplay(display);
+ drawtime();
+ updatetimes();
+ flushimage(display, 1);
+ unlockdisplay(display);
+ now = time(nil);
+ sleep(((60 - now%60) + 1)*1000); /* wait for minute to change */
+ setdate();
+ }
+}
+
+int
+alreadyseen(char *digest)
+{
+ int i;
+ Face *f;
+
+ if(!digest)
+ return 0;
+
+ /* can do accurate check */
+ for(i=0; i<nfaces; i++){
+ f = faces[i];
+ if(f->str[Sdigest]!=nil && strcmp(digest, f->str[Sdigest])==0)
+ return 1;
+ }
+ return 0;
+}
+
+int
+torune(Rune *r, char *s, int nr)
+{
+ int i;
+
+ for(i=0; i<nr-1 && *s!='\0'; i++)
+ s += chartorune(r+i, s);
+ r[i] = L'\0';
+ return i;
+}
+
+void
+center(Font *f, Point p, char *s, Image *color)
+{
+ int i, n, dx;
+ Rune rbuf[32];
+ char sbuf[32*UTFmax+1];
+
+ dx = stringwidth(f, s);
+ if(dx > Facesize){
+ n = torune(rbuf, s, nelem(rbuf));
+ for(i=0; i<n; i++){
+ dx = runestringnwidth(f, rbuf, i+1);
+ if(dx > Facesize)
+ break;
+ }
+ sprint(sbuf, "%.*S", i, rbuf);
+ s = sbuf;
+ dx = stringwidth(f, s);
+ }
+ p.x += (Facesize-dx)/2;
+ string(screen, p, color, ZP, f, s);
+}
+
+Rectangle
+facerect(int index) /* index is geometric; 0 is always upper left face */
+{
+ Rectangle r;
+ int x, y;
+
+ x = index % nacross;
+ y = index / nacross;
+ r.min = addpt(screen->r.min, facep);
+ r.min.x += x*(Facesize+Facesep);
+ r.min.y += y*(Facesize+Facesep+2*mediumfont->height);
+ r.max = addpt(r.min, Pt(Facesize, Facesize));
+ r.max.y += 2*mediumfont->height;
+ /* simple fix to avoid drawing off screen, allowing customers to use position */
+ if(index<0 || index>=nacross*ndown)
+ r.max.x = r.min.x;
+ return r;
+}
+
+static char *mon = "JanFebMarAprMayJunJulAugSepOctNovDec";
+char*
+facetime(Face *f, int *recent)
+{
+ static char buf[30];
+
+ if((long)(now - f->time) > HhmmTime){
+ *recent = 0;
+ sprint(buf, "%.3s %2d", mon+3*f->tm.mon, f->tm.mday);
+ return buf;
+ }else{
+ *recent = 1;
+ sprint(buf, "%02d:%02d", f->tm.hour, f->tm.min);
+ return buf;
+ }
+}
+
+void
+drawface(Face *f, int i)
+{
+ char *tstr;
+ Rectangle r;
+ Point p;
+
+ if(f == nil)
+ return;
+ if(i<first || i>=last)
+ return;
+ r = facerect(i-first);
+ draw(screen, r, cols[Cbgrnd], nil, ZP);
+ draw(screen, Rpt(r.min, addpt(r.min, Pt(Facesize, Facesize))), cols[Cfacebg], nil, ZP);
+ draw(screen, r, f->bit, f->mask, ZP);
+ r.min.y += Facesize;
+ center(mediumfont, r.min, f->str[Suser], cols[Ctext]);
+ r.min.y += mediumfont->height;
+ tstr = facetime(f, &f->recent);
+ center(mediumfont, r.min, tstr, cols[Ctext]);
+ if(f->unknown){
+ r.min.y -= mediumfont->height + tinyfont->height + 2;
+ for(p.x=-1; p.x<=1; p.x++)
+ for(p.y=-1; p.y<=1; p.y++)
+ center(tinyfont, addpt(r.min, p), f->str[Sdomain], cols[Cbgrnd]);
+ center(tinyfont, r.min, f->str[Sdomain], cols[Csmalltext]);
+ }
+}
+
+void
+updatetimes(void)
+{
+ int i;
+ Face *f;
+
+ for(i=0; i<nfaces; i++){
+ f = faces[i];
+ if(f == nil)
+ continue;
+ if(((long)(now - f->time) <= HhmmTime) != f->recent)
+ drawface(f, i);
+ }
+}
+
+void
+setlast(void)
+{
+ last = first+nacross*ndown;
+ if(last > nfaces)
+ last = nfaces;
+}
+
+void
+drawarrows(void)
+{
+ Point p;
+
+ p = enddate;
+ p.x += Facesep;
+ if(p.x & 1)
+ p.x++; /* align background texture */
+ leftr = rectaddpt(leftright, p);
+ p.x += Dx(leftright) + Facesep;
+ rightr = rectaddpt(leftright, p);
+ draw(screen, leftr, first>0? cols[Carrow] : cols[Cbgrnd], left, leftright.min);
+ draw(screen, rightr, last<nfaces? cols[Carrow] : cols[Cbgrnd], right, leftright.min);
+}
+
+void
+addface(Face *f) /* always adds at 0 */
+{
+ Face **ofaces;
+ Rectangle r0, r1, r;
+ int y, nx, ny;
+
+ if(f == nil)
+ return;
+ lockdisplay(display);
+ if(first != 0){
+ first = 0;
+ resized();
+ }
+ findbit(f);
+
+ nx = nacross;
+ ny = (nfaces+(nx-1)) / nx;
+
+ for(y=ny; y>=0; y--){
+ /* move them along */
+ r0 = facerect(y*nx+0);
+ r1 = facerect(y*nx+1);
+ r = r1;
+ r.max.x = r.min.x + (nx - 1)*(Facesize+Facesep);
+ draw(screen, r, screen, nil, r0.min);
+ /* copy one down from row above */
+ if(y != 0){
+ r = facerect((y-1)*nx+nx-1);
+ draw(screen, r0, screen, nil, r.min);
+ }
+ }
+
+ ofaces = faces;
+ faces = emalloc((nfaces+1)*sizeof(Face*));
+ memmove(faces+1, ofaces, nfaces*(sizeof(Face*)));
+ free(ofaces);
+ nfaces++;
+ setlast();
+ drawarrows();
+ faces[0] = f;
+ drawface(f, 0);
+ flushimage(display, 1);
+ unlockdisplay(display);
+}
+
+void
+loadmboxfaces(char *maildir)
+{
+ int dirfd;
+ Dir *d;
+ int i, n;
+
+ dirfd = open(maildir, OREAD);
+ if(dirfd >= 0){
+ chdir(maildir);
+ while((n = dirread(dirfd, &d)) > 0){
+ for(i=0; i<n; i++)
+ addface(dirface(maildir, d[i].name));
+ free(d);
+ }
+ close(dirfd);
+ }
+}
+
+void
+freeface(Face *f)
+{
+ int i;
+
+ if(f->file==nil || f->bit!=f->file->image)
+ freeimage(f->bit);
+ freefacefile(f->file);
+ for(i=0; i<Nstring; i++)
+ free(f->str[i]);
+ free(f);
+}
+
+void
+delface(int j)
+{
+ Rectangle r0, r1, r;
+ int nx, ny, x, y;
+
+ if(j < first)
+ first--;
+ else if(j < last){
+ nx = nacross;
+ ny = (nfaces+(nx-1)) / nx;
+ x = (j-first)%nx;
+ for(y=(j-first)/nx; y<ny; y++){
+ if(x != nx-1){
+ /* move them along */
+ r0 = facerect(y*nx+x);
+ r1 = facerect(y*nx+x+1);
+ r = r0;
+ r.max.x = r.min.x + (nx - x - 1)*(Facesize+Facesep);
+ draw(screen, r, screen, nil, r1.min);
+ }
+ if(y != ny-1){
+ /* copy one up from row below */
+ r = facerect((y+1)*nx);
+ draw(screen, facerect(y*nx+nx-1), screen, nil, r.min);
+ }
+ x = 0;
+ }
+ if(last < nfaces) /* first off-screen becomes visible */
+ drawface(faces[last], last-1);
+ else{
+ /* clear final spot */
+ r = facerect(last-first-1);
+ draw(screen, r, cols[Cbgrnd], nil, r.min);
+ }
+ }
+ freeface(faces[j]);
+ memmove(faces+j, faces+j+1, (nfaces-(j+1))*sizeof(Face*));
+ nfaces--;
+ setlast();
+ drawarrows();
+}
+
+void
+dodelete(int i)
+{
+ Face *f;
+
+ f = faces[i];
+ if(history){
+ free(f->str[Sshow]);
+ f->str[Sshow] = estrdup("");
+ }else{
+ delface(i);
+ flushimage(display, 1);
+ }
+}
+
+void
+delete(char *s, char *digest)
+{
+ int i;
+ Face *f;
+
+ lockdisplay(display);
+ for(i=0; i<nfaces; i++){
+ f = faces[i];
+ if(digest != nil){
+ if(f->str[Sdigest]!=nil && strcmp(digest, f->str[Sdigest]) == 0){
+ dodelete(i);
+ break;
+ }
+ }else{
+ if(f->str[Sshow] && strcmp(s, f->str[Sshow]) == 0){
+ dodelete(i);
+ break;
+ }
+ }
+ }
+ unlockdisplay(display);
+}
+
+void
+faceproc(void)
+{
+ for(;;)
+ addface(nextface());
+}
+
+void
+resized(void)
+{
+ int i;
+
+ nacross = (Dx(screen->r)-2*facep.x+Facesep)/(Facesize+Facesep);
+ for(ndown=1; rectinrect(facerect(ndown*nacross), screen->r); ndown++)
+ ;
+ setlast();
+ draw(screen, screen->r, cols[Cbgrnd], nil, ZP);
+ enddate = ZP;
+ drawtime();
+ for(i=0; i<nfaces; i++)
+ drawface(faces[i], i);
+ drawarrows();
+ flushimage(display, 1);
+}
+
+void
+eresized(int new)
+{
+ lockdisplay(display);
+ if(new && getwindow(display, Refnone) < 0) {
+ fprint(2, "can't reattach to window\n");
+ killall("reattach");
+ }
+ resized();
+ unlockdisplay(display);
+}
+
+int
+getmouse(Mouse *m)
+{
+ int n;
+ static int eof;
+ char buf[128];
+
+ if(eof)
+ return 0;
+ for(;;){
+ n = read(mousefd, buf, sizeof(buf));
+ if(n <= 0){
+ /* so callers needn't check return value every time */
+ eof = 1;
+ m->buttons = 0;
+ return 0;
+ }
+ n = eatomouse(m, buf, n);
+ if(n > 0)
+ return 1;
+ }
+}
+
+enum
+{
+ Clicksize = 3, /* pixels */
+};
+
+int
+scroll(int but, Point p)
+{
+ int delta;
+
+ delta = 0;
+ lockdisplay(display);
+ if(ptinrect(p, leftr) && first>0){
+ if(but == 2)
+ delta = -first;
+ else{
+ delta = nacross;
+ if(delta > first)
+ delta = first;
+ delta = -delta;
+ }
+ }else if(ptinrect(p, rightr) && last<nfaces){
+ if(but == 2)
+ delta = (nfaces-nacross*ndown) - first;
+ else{
+ delta = nacross;
+ if(delta > nfaces-last)
+ delta = nfaces-last;
+ }
+ }
+ first += delta;
+ last += delta;
+ unlockdisplay(display);
+ if(delta)
+ eresized(0);
+ return delta;
+}
+
+void
+click(int button, Mouse *m)
+{
+ Point p;
+ int i;
+
+ p = m->xy;
+ while(m->buttons == (1<<(button-1)))
+ getmouse(m);
+ if(m->buttons)
+ return;
+ if(abs(p.x-m->xy.x)>Clicksize || abs(p.y-m->xy.y)>Clicksize)
+ return;
+ switch(button){
+ case 1:
+ if(scroll(1, p))
+ break;
+ if(history){
+ /* click clears display */
+ lockdisplay(display);
+ for(i=0; i<nfaces; i++)
+ freeface(faces[i]);
+ free(faces);
+ faces=nil;
+ nfaces = 0;
+ unlockdisplay(display);
+ eresized(0);
+ return;
+ }else{
+ for(i=first; i<last; i++) /* clear vwhois faces */
+ if(ptinrect(p, facerect(i-first))
+ && (clickrm || strstr(faces[i]->str[Sshow], "/XXXvwhois"))){
+ delface(i);
+ flushimage(display, 1);
+ }
+ }
+ break;
+ case 2:
+ scroll(2, p);
+ break;
+ case 3:
+ scroll(3, p);
+ lockdisplay(display);
+ for(i=first; i<last; i++)
+ if(ptinrect(p, facerect(i-first))){
+ showmail(faces[i]);
+ break;
+ }
+ unlockdisplay(display);
+ break;
+ }
+}
+
+void
+mouseproc(void)
+{
+ Mouse mouse;
+
+ while(getmouse(&mouse)){
+ if(mouse.buttons == 1)
+ click(1, &mouse);
+ else if(mouse.buttons == 2)
+ click(2, &mouse);
+ else if(mouse.buttons == 4)
+ click(3, &mouse);
+
+ while(mouse.buttons)
+ getmouse(&mouse);
+ }
+}
+
+void
+killall(char *s)
+{
+ int i, pid;
+
+ pid = getpid();
+ for(i=0; i<NPROC; i++)
+ if(pids[i] && pids[i]!=pid)
+ postnote(PNPROC, pids[i], "kill");
+ exits(s);
+}
+
+void
+startproc(void (*f)(void), int index)
+{
+ int pid;
+
+ switch(pid = rfork(RFPROC|RFMEM|RFNOWAIT)){
+ case -1:
+ fprint(2, "faces: fork failed: %r\n");
+ killall("fork failed");
+ case 0:
+ f();
+ fprint(2, "faces: %s process exits\n", procnames[index]);
+ if(index >= 0)
+ killall("process died");
+ exits(nil);
+ }
+ if(index >= 0)
+ pids[index] = pid;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: faces [-chi] [-m maildir]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+ int i;
+
+ ARGBEGIN{
+ case 'h':
+ history++;
+ break;
+ case 'i':
+ initload++;
+ break;
+ case 'm':
+ addmaildir(EARGF(usage()));
+ maildir = nil;
+ break;
+ case 'c':
+ clickrm++;
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(initdraw(nil, nil, "faces") < 0){
+ fprint(2, "faces: initdraw failed: %r\n");
+ exits("initdraw");
+ }
+ if(maildir)
+ addmaildir(maildir);
+ init();
+ unlockdisplay(display); /* initdraw leaves it locked */
+ display->locking = 1; /* tell library we're using the display lock */
+ setdate();
+ eresized(0);
+
+ pids[Mainp] = getpid();
+ startproc(timeproc, Timep);
+ startproc(mouseproc, Mousep);
+ if(initload)
+ for(i = 0; i < nmaildirs; i++)
+ loadmboxfaces(maildirs[i]);
+ faceproc();
+ fprint(2, "faces: %s process exits\n", procnames[Mainp]);
+ killall(nil);
+}
--- /dev/null
+++ b/sys/src/cmd/kbmap.c
@@ -1,0 +1,284 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <keyboard.h>
+
+typedef struct KbMap KbMap;
+struct KbMap {
+ char *name;
+ char *file;
+ Rectangle r;
+ int current;
+};
+
+KbMap *map;
+int nmap;
+Image *txt, *sel;
+
+enum {
+ PAD = 3,
+ MARGIN = 5
+};
+
+char *dir = "/sys/lib/kbmap";
+
+void*
+erealloc(void *v, ulong n)
+{
+ v = realloc(v, n);
+ if(v == nil)
+ sysfatal("out of memory reallocating %lud", n);
+ return v;
+}
+
+void*
+emalloc(ulong n)
+{
+ void *v;
+
+ v = malloc(n);
+ if(v == nil)
+ sysfatal("out of memory allocating %lud", n);
+ memset(v, 0, n);
+ return v;
+}
+
+char*
+estrdup(char *s)
+{
+ int l;
+ char *t;
+
+ if (s == nil)
+ return nil;
+ l = strlen(s)+1;
+ t = emalloc(l);
+ memcpy(t, s, l);
+
+ return t;
+}
+
+void
+init(void)
+{
+ int i, fd, nr;
+ Dir *pd;
+
+ if((fd = open(dir, OREAD)) < 0)
+ return;
+
+ nmap = nr = dirreadall(fd, &pd);
+ map = emalloc(nr * sizeof(KbMap));
+ for(i=0; i<nr; i++){
+ map[i].file = emalloc(strlen(dir) + strlen(pd[i].name) + 2);
+ sprint(map[i].file, "%s/%s", dir, pd[i].name);
+ map[i].name = estrdup(pd[i].name);
+ map[i].current = 0;
+ }
+ free(pd);
+
+ close(fd);
+}
+
+void
+drawmap(int i)
+{
+ if(map[i].current)
+ draw(screen, map[i].r, sel, nil, ZP);
+ else
+ draw(screen, map[i].r, display->black, nil, ZP);
+
+ _string(screen, addpt(map[i].r.min, Pt(2,0)), txt, ZP,
+ font, map[i].name, nil, strlen(map[i].name),
+ map[i].r, nil, ZP, SoverD);
+ border(screen, map[i].r, 1, txt, ZP);
+}
+
+void
+geometry(void)
+{
+ int i, rows, cols;
+ Rectangle r;
+
+ rows = (Dy(screen->r)-2*MARGIN+PAD)/(font->height+PAD);
+ if(rows < 1)
+ rows = 1;
+ cols = (nmap+rows-1)/rows;
+ if(cols < 1)
+ cols = 1;
+ r = Rect(0,0,(Dx(screen->r)-2*MARGIN+PAD)/cols-PAD, font->height);
+ for(i=0; i<nmap; i++)
+ map[i].r = rectaddpt(rectaddpt(r, Pt(MARGIN+(PAD+Dx(r))*(i/rows),
+ MARGIN+(PAD+Dy(r))*(i%rows))), screen->r.min);
+
+}
+
+void
+redraw(Image *screen)
+{
+ int i;
+
+ draw(screen, screen->r, display->black, nil, ZP);
+ for(i=0; i<nmap; i++)
+ drawmap(i);
+ flushimage(display, 1);
+}
+
+void
+eresized(int new)
+{
+ if(new && getwindow(display, Refmesg) < 0)
+ fprint(2,"can't reattach to window");
+ geometry();
+ redraw(screen);
+}
+
+int
+writemap(char *file)
+{
+ int i, fd, ofd;
+ char buf[8192];
+ int n;
+ char *p;
+
+ if((fd = open(file, OREAD)) < 0){
+ fprint(2, "cannot open %s: %r\n", file);
+ return -1;
+ }
+ if((ofd = open("/dev/kbmap", OWRITE|OTRUNC)) < 0){
+ fprint(2, "cannot open /dev/kbmap: %r\n");
+ close(fd);
+ return -1;
+ }
+ /* do not write half lines */
+ n = 0;
+ while((i = read(fd, buf + n, sizeof buf - 1 - n)) > 0){
+ n += i;
+ buf[n] = '\0';
+ p = strrchr(buf, '\n');
+ if(p == nil){
+ if(n == sizeof buf - 1){
+ fprint(2, "writing /dev/kbmap: line too long\n");
+ break;
+ }
+ continue;
+ }
+ p++;
+ if(write(ofd, buf, p - buf) != p - buf){
+ fprint(2, "writing /dev/kbmap: %r\n");
+ break;
+ }
+ n -= p - buf;
+ memmove(buf, p, n);
+ }
+
+ close(fd);
+ close(ofd);
+ return 0;
+}
+
+void
+click(Mouse m)
+{
+ int i, j;
+
+ if(m.buttons == 0 || (m.buttons & ~4))
+ return;
+
+ for(i=0; i<nmap; i++)
+ if(ptinrect(m.xy, map[i].r))
+ break;
+ if(i == nmap)
+ return;
+
+ do
+ m = emouse();
+ while(m.buttons == 4);
+
+ if(m.buttons != 0){
+ do
+ m = emouse();
+ while(m.buttons);
+ return;
+ }
+
+ for(j=0; j<nmap; j++)
+ if(ptinrect(m.xy, map[j].r))
+ break;
+ if(j != i)
+ return;
+
+ writemap(map[i].file);
+
+ /* clean the previous current map */
+ for(j=0; j<nmap; j++)
+ map[j].current = 0;
+
+ map[i].current = 1;
+
+ redraw(screen);
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: kbmap [file...]\n");
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ Event e;
+ char *c;
+
+ if(argc > 1) {
+ argv++; argc--;
+ map = emalloc((argc)*sizeof(KbMap));
+ while(argc--) {
+ map[argc].file = estrdup(argv[argc]);
+ c = strrchr(map[argc].file, '/');
+ map[argc].name = (c == nil ? map[argc].file : c+1);
+ map[argc].current = 0;
+ nmap++;
+ }
+ } else
+ init();
+
+ if(initdraw(0, 0, "kbmap") < 0){
+ fprint(2, "kbmap: initdraw failed: %r\n");
+ exits("initdraw");
+ }
+ enum{
+ Ctxt,
+ Csel,
+ Ncols,
+ };
+ Theme th[Ncols] = {
+ [Ctxt] { "text", 0xEAFFFFFF },
+ [Csel] { "hold", DBlue },
+ };
+ readtheme(th, nelem(th), nil);
+ txt = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Ctxt].c);
+ sel = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[Csel].c);
+ if(txt == nil || sel == nil)
+ sysfatal("allocimage: %r");
+
+ eresized(0);
+ einit(Emouse|Ekeyboard);
+
+ for(;;){
+ switch(eread(Emouse|Ekeyboard, &e)){
+ case Ekeyboard:
+ if(e.kbdc==Kdel || e.kbdc=='q')
+ exits(0);
+ break;
+ case Emouse:
+ if(e.mouse.buttons)
+ click(e.mouse);
+ break;
+ }
+ }
+}
+
--- /dev/null
+++ b/sys/src/cmd/spred/cmdw.c
@@ -1,0 +1,342 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include "dat.h"
+#include "fns.h"
+
+static int
+cmdinit(Win *)
+{
+ return 0;
+}
+
+static void
+scrollbar(Win *w)
+{
+ int h, t0, t1;
+
+ h = Dy(w->inner);
+ draw(w->im, rectaddpt(Rect(0, 0, SCRBSIZ+1, h), w->inner.min), w->tab->cols[BORD], nil, ZP);
+ t0 = w->toprune * h;
+ t1 = (w->toprune + w->fr.nchars) * h;
+ if(w->nrunes == 0){
+ t0 = 0;
+ t1 = h;
+ }else{
+ t0 /= w->nrunes;
+ t1 /= w->nrunes;
+ }
+ draw(w->im, rectaddpt(Rect(0, t0, SCRBSIZ, t1), w->inner.min), w->tab->cols[BACK], nil, ZP);
+}
+
+static void
+cmddraw(Win *w)
+{
+ Rectangle r;
+
+ frclear(&w->fr, 0);
+ r = insetrect(w->inner, 1);
+ r.min.x += SCRTSIZ;
+ scrollbar(w);
+ frinit(&w->fr, r, display->defaultfont, w->im, w->tab->cols);
+ frinsert(&w->fr, w->runes + w->toprune, w->runes + w->nrunes, 0);
+}
+
+void
+cmdscroll(Win *w, int l)
+{
+ int r;
+
+ if(l == 0)
+ return;
+ if(l > 0){
+ for(r = w->toprune; r < w->nrunes && l != 0; r++)
+ if(w->runes[r] == '\n')
+ l--;
+ w->toprune = r;
+ }else{
+ for(r = w->toprune; r > 0; r--)
+ if(w->runes[r] == '\n' && ++l == 0){
+ r++;
+ break;
+ }
+ w->toprune = r;
+
+ }
+ frdelete(&w->fr, 0, w->fr.nchars);
+ frinsert(&w->fr, w->runes + w->toprune, w->runes + w->nrunes, 0);
+ scrollbar(w);
+}
+
+static void
+cmdclick(Win *w, Mousectl *mc)
+{
+ if(mc->xy.x <= w->inner.min.x + SCRBSIZ){
+ cmdscroll(w, -5);
+ return;
+ }
+ frselect(&w->fr, mc);
+}
+
+static int
+cmdrmb(Win *w, Mousectl *mc)
+{
+ if(mc->xy.x > w->inner.min.x + SCRBSIZ)
+ return -1;
+ cmdscroll(w, 5);
+ return 0;
+}
+
+int
+cmdinsert(Win *w, Rune *r, int nr, int rp)
+{
+ Rune *s;
+
+ if(nr < 0)
+ for(nr = 0, s = r; *s++ != 0; nr++)
+ ;
+ if(rp < 0 || rp > w->nrunes)
+ rp = w->nrunes;
+ if(w->nrunes + nr > w->arunes){
+ w->runes = realloc(w->runes, w->arunes = w->arunes + (nr + RUNEBLK - 1) & ~(RUNEBLK - 1));
+ if(w->runes == nil)
+ sysfatal("realloc: %r");
+ }
+ if(rp != w->nrunes)
+ memmove(w->runes + rp, w->runes + rp + nr, (w->nrunes - rp) * sizeof(Rune));
+ memmove(w->runes + rp, r, nr * sizeof(Rune));
+ w->nrunes += nr;
+ if(w->toprune > rp)
+ w->toprune += nr;
+ else{
+ frinsert(&w->fr, w->runes + rp, w->runes + rp + nr, rp - w->toprune);
+ if(rp == w->nrunes - nr){
+ if(w->fr.lastlinefull)
+ cmdscroll(w, 1);
+ }
+ }
+ if(w->opoint > rp)
+ w->opoint += nr;
+ return nr;
+}
+
+static void
+cmddel(Win *w, int a, int b)
+{
+ if(a >= b)
+ return;
+ memmove(w->runes + a, w->runes + b, w->nrunes - b);
+ w->nrunes -= b - a;
+ if(w->toprune >= b)
+ w->toprune -= b - a;
+ else{
+ frdelete(&w->fr, a - w->toprune, b - w->toprune);
+ if(w->toprune >= a)
+ w->toprune = a;
+ }
+ if(a <= w->opoint && w->opoint < b)
+ w->opoint = a;
+ else if(w->opoint >= b)
+ w->opoint -= b - a;
+}
+
+static void
+setsel(Win *w, int p0, int p1)
+{
+ frdrawsel(&w->fr, frptofchar(&w->fr, w->fr.p0), w->fr.p0, w->fr.p1, 0);
+ w->fr.p0 = p0;
+ w->fr.p1 = p1;
+ frdrawsel(&w->fr, frptofchar(&w->fr, p0), p0, p1, 1);
+}
+
+static void
+cmdline(Win *w)
+{
+ static char buf[4096];
+ Rune *q;
+ char *p;
+
+ q = w->runes + w->opoint;
+ p = buf;
+ while(q < w->runes + w->nrunes && p < buf + nelem(buf) + 1)
+ p += runetochar(p, q++);
+ *p = 0;
+ w->opoint = w->nrunes;
+ docmd(buf);
+}
+
+static void
+cmdkey(Win *w, Rune r)
+{
+ switch(r){
+ case Kview:
+ cmdscroll(w, 3);
+ return;
+ case Kup:
+ cmdscroll(w, -3);
+ return;
+ case Kleft:
+ if(w->fr.p0 == 0)
+ return;
+ setsel(w, w->fr.p0 - 1, w->fr.p0 - 1);
+ return;
+ case Kright:
+ if(w->toprune + w->fr.p1 == w->nrunes)
+ return;
+ setsel(w, w->fr.p1 + 1, w->fr.p1 + 1);
+ return;
+ }
+ if(w->fr.p0 < w->fr.p1)
+ cmddel(w, w->toprune + w->fr.p0, w->toprune + w->fr.p1);
+ switch(r){
+ case 0x00:
+ case Kesc:
+ break;
+ case '\b':
+ if(w->fr.p0 > 0 && w->toprune + w->fr.p0 != w->opoint)
+ cmddel(w, w->toprune + w->fr.p0 - 1, w->toprune + w->fr.p0);
+ break;
+ case '\n':
+ cmdinsert(w, &r, 1, w->fr.p0 + w->toprune);
+ if(w->toprune + w->fr.p0 == w->nrunes)
+ cmdline(w);
+ break;
+ default:
+ cmdinsert(w, &r, 1, w->fr.p0 + w->toprune);
+ }
+}
+
+static int
+tosnarf(Win *w, int p0, int p1)
+{
+ int fd;
+ static char buf[512];
+ char *c, *ce;
+ Rune *rp, *re;
+
+ if(p0 >= p1)
+ return 0;
+ fd = open("/dev/snarf", OWRITE|OTRUNC);
+ if(fd < 0){
+ cmdprint("tosnarf: %r");
+ return -1;
+ }
+ c = buf;
+ ce = buf + sizeof(buf);
+ rp = w->runes + p0;
+ re = w->runes + p1;
+ for(; rp < re; rp++){
+ if(c + UTFmax > ce){
+ write(fd, buf, c - buf);
+ c = buf;
+ }
+ c += runetochar(c, rp);
+ }
+ if(c > buf)
+ write(fd, buf, c - buf);
+ close(fd);
+ return 0;
+}
+
+static int
+fromsnarf(Win *w, int p0)
+{
+ int fd, rc;
+ char *buf, *p;
+ Rune *rbuf, *r;
+ int nc, end;
+
+ fd = open("/dev/snarf", OREAD);
+ if(fd < 0){
+ cmdprint("fromsnarf: %r");
+ return -1;
+ }
+ buf = nil;
+ nc = 0;
+ for(;;){
+ buf = realloc(buf, nc + 4096);
+ rc = readn(fd, buf + nc, nc + 4096);
+ if(rc <= 0)
+ break;
+ nc += rc;
+ if(rc < 4096)
+ break;
+ }
+ close(fd);
+ rbuf = emalloc(sizeof(Rune) * nc);
+ r = rbuf;
+ for(p = buf; p < buf + nc; r++)
+ p += chartorune(r, p);
+ end = p0 == w->nrunes;
+ cmdinsert(w, rbuf, r - rbuf, p0);
+ if(end && r > rbuf && r[-1] == '\n')
+ cmdline(w);
+ return 0;
+}
+
+static void
+cmdmenu(Win *w, Mousectl *mc)
+{
+ enum {
+ CUT,
+ PASTE,
+ SNARF,
+ };
+ static char *ms[] = {
+ [CUT] "cut",
+ [PASTE] "paste",
+ [SNARF] "snarf",
+ nil,
+ };
+ static Menu m = {ms};
+
+ switch(menuhit(2, mc, &m, nil)){
+ case CUT:
+ if(tosnarf(w, w->toprune + w->fr.p0, w->toprune + w->fr.p1) >= 0)
+ cmddel(w, w->toprune + w->fr.p0, w->toprune + w->fr.p1);
+ break;
+ case SNARF:
+ tosnarf(w, w->toprune + w->fr.p0, w->toprune + w->fr.p1);
+ break;
+ case PASTE:
+ if(w->fr.p0 < w->fr.p1)
+ cmddel(w, w->toprune + w->fr.p0, w->toprune + w->fr.p1);
+ fromsnarf(w, w->toprune + w->fr.p0);
+ break;
+ }
+}
+
+void
+cmdprint(char *fmt, ...)
+{
+ Rune *r;
+ va_list va;
+
+ va_start(va, fmt);
+ r = runevsmprint(fmt, va);
+ va_end(va);
+ if(r != nil)
+ cmdw->opoint += cmdinsert(cmdw, r, -1, cmdw->opoint);
+}
+
+Wintab cmdtab = {
+ .init = cmdinit,
+ .draw = cmddraw,
+ .click = cmdclick,
+ .menu = cmdmenu,
+ .rmb = cmdrmb,
+ .key = cmdkey,
+ .hexcols = {
+ [BORD] 0x440000FF,
+ [TEXT] 0x770000FF,
+ [DISB] 0x111111FF,
+ [BACK] 0x000000FF,
+ [HIGH] 0x111111FF,
+ [HTEXT] 0x770000FF,
+ }
+};
--- /dev/null
+++ b/sys/src/cmd/spred/win.c
@@ -1,0 +1,283 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <cursor.h>
+#include <frame.h>
+#include "dat.h"
+#include "fns.h"
+
+Screen *scr;
+extern Wintab *tabs[];
+Win wlist;
+File flist;
+Win *actw, *actf, *cmdw;
+Image *invcol;
+
+void*
+emalloc(ulong sz)
+{
+ void *v;
+
+ v = malloc(sz);
+ if(v == nil)
+ sysfatal("malloc: %r");
+ memset(v, 0, sz);
+ setmalloctag(v, getcallerpc(&sz));
+ return v;
+}
+
+void
+initwin(void)
+{
+ Rectangle r;
+ int i, j;
+
+ scr = allocscreen(screen, display->white, 0);
+ if(scr == nil)
+ sysfatal("allocscreen: %r");
+ draw(screen, screen->r, display->black, nil, ZP);
+ for(i = 0; i < NTYPES; i++)
+ for(j = 0; j < NCOLS; j++)
+ tabs[i]->cols[j] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, tabs[i]->hexcols[j]);
+ invcol = allocimage(display, Rect(0, 0, 2, 2), screen->chan, 1, 0);
+ draw(invcol, Rect(1, 0, 2, 1), display->white, nil, ZP);
+ draw(invcol, Rect(0, 1, 1, 2), display->white, nil, ZP);
+ wlist.next = wlist.prev = &wlist;
+ flist.next = flist.prev = &flist;
+ r = screen->r;
+ r.max.y = r.min.y + Dy(r) / 5;
+ cmdw = newwin(CMD, r, nil);
+ if(cmdw == nil)
+ sysfatal("newwin: %r");
+}
+
+Win *
+newwin(int t, Rectangle r, File *f)
+{
+ Win *w;
+
+ w = emalloc(sizeof(*w));
+ w->next = &wlist;
+ w->prev = wlist.prev;
+ w->next->prev = w;
+ w->prev->next = w;
+ w->type = t;
+ w->tab = tabs[t];
+ w->entire = r;
+ w->inner = insetrect(r, BORDSIZ);
+ w->im = allocwindow(scr, r, Refbackup, 0);
+ draw(w->im, w->inner, w->tab->cols[BACK], nil, ZP);
+ if(f != nil){
+ incref(f);
+ w->wprev = f->wins.wprev;
+ w->wnext = &f->wins;
+ f->wins.wprev->wnext = w;
+ f->wins.wprev = w;
+ w->f = f;
+ }
+ w->tab->init(w);
+ setfocus(w);
+ w->tab->draw(w);
+ return w;
+}
+
+Win *
+newwinsel(int t, Mousectl *mc, File *f)
+{
+ Rectangle u;
+
+ u = getrect(3, mc);
+ if(Dx(u) < MINSIZ || Dy(u) < MINSIZ)
+ return nil;
+ rectclip(&u, screen->r);
+ return newwin(t, u, f);
+}
+
+void
+winzerox(Win *w, Mousectl *mc)
+{
+ Win *v;
+
+ if(w->tab->zerox == nil){
+ cmdprint("?\n");
+ return;
+ }
+ v = newwinsel(w->type, mc, w->f);
+ if(v == nil)
+ return;
+ w->tab->zerox(w, v);
+ v->tab->draw(v);
+}
+
+void
+winclose(Win *w)
+{
+ if(w->f == nil){
+ cmdprint("?\n");
+ return;
+ }
+ if(!decref(w->f)){
+ if(w->f->change > 0){
+ cmdprint("?\n");
+ incref(w->f);
+ w->f->change = -1;
+ return;
+ }
+ putfil(w->f);
+ w->f = nil;
+ }
+ freeimage(w->im);
+ if(w->f != nil){
+ w->wnext->wprev = w->wprev;
+ w->wprev->wnext = w->wnext;
+ }
+ w->next->prev = w->prev;
+ w->prev->next = w->next;
+ if(w == actw)
+ actw = nil;
+ if(w == actf)
+ actf = nil;
+ free(w);
+}
+
+void
+setfocus(Win *w)
+{
+ if(actw != nil)
+ border(actw->im, actw->entire, BORDSIZ, actw->tab->cols[DISB], ZP);
+ actw = w;
+ if(w != cmdw)
+ actf = w;
+ if(w == nil)
+ return;
+ if(w->im == nil)
+ sysfatal("setfocus: phase error");
+ topwindow(w->im);
+ w->prev->next = w->next;
+ w->next->prev = w->prev;
+ w->prev = wlist.prev;
+ w->next = &wlist;
+ w->prev->next = w;
+ w->next->prev = w;
+ border(w->im, w->entire, BORDSIZ, w->tab->cols[BORD], ZP);
+}
+
+static Win *
+winpoint(Point p)
+{
+ Win *w;
+
+ for(w = wlist.prev; w != &wlist; w = w->prev)
+ if(ptinrect(p, w->entire))
+ return w;
+ return nil;
+}
+
+void
+winclick(Mousectl *mc)
+{
+ Win *w;
+
+ w = winpoint(mc->xy);
+ if(w != nil){
+ if(w != actw)
+ setfocus(w);
+ w->tab->click(w, mc);
+ }
+ while((mc->buttons & 1) != 0)
+ readmouse(mc);
+}
+
+Win *
+winsel(Mousectl *mc, int but)
+{
+ extern Cursor crosscursor;
+ int m;
+ Win *w;
+
+ m = 1 << but - 1;
+ setcursor(mc, &crosscursor);
+ for(;;){
+ readmouse(mc);
+ if((mc->buttons & ~m) != 0){
+ w = nil;
+ goto end;
+ }
+ if((mc->buttons & m) != 0)
+ break;
+ }
+ w = winpoint(mc->xy);
+end:
+ while(readmouse(mc), mc->buttons != 0)
+ ;
+ setcursor(mc, nil);
+ return w;
+}
+
+void
+winresize(Win *w, Mousectl *mc)
+{
+ Rectangle r;
+
+ if(w == nil)
+ return;
+ r = getrect(3, mc);
+ if(Dx(r) < MINSIZ || Dy(r) < MINSIZ)
+ return;
+ rectclip(&r, screen->r);
+ freeimage(w->im);
+ w->entire = r;
+ w->inner = insetrect(r, BORDSIZ);
+ w->im = allocwindow(scr, r, Refbackup, 0);
+ draw(w->im, w->inner, w->tab->cols[BACK], nil, ZP);
+ setfocus(w);
+ w->tab->draw(w);
+}
+
+void
+resize(void)
+{
+ Rectangle old, r;
+ int dxo, dyo, dxn, dyn;
+ Win *w;
+
+ old = screen->r;
+ dxo = Dx(old);
+ dyo = Dy(old);
+ if(getwindow(display, Refnone) < 0)
+ sysfatal("resize failed: %r");
+ dxn = Dx(screen->r);
+ dyn = Dy(screen->r);
+ freescreen(scr);
+ scr = allocscreen(screen, display->white, 0);
+ if(scr == nil)
+ sysfatal("allocscreen: %r");
+ draw(screen, screen->r, display->black, nil, ZP);
+ for(w = wlist.next; w != &wlist; w = w->next){
+ r = rectsubpt(w->entire, old.min);
+ r.min.x = muldiv(r.min.x, dxn, dxo);
+ r.max.x = muldiv(r.max.x, dxn, dxo);
+ r.min.y = muldiv(r.min.y, dyn, dyo);
+ r.max.y = muldiv(r.max.y, dyn, dyo);
+ w->entire = rectaddpt(r, screen->r.min);
+ w->inner = insetrect(w->entire, BORDSIZ);
+ freeimage(w->im);
+ w->im = allocwindow(scr, w->entire, Refbackup, 0);
+ if(w->im == nil)
+ sysfatal("allocwindow: %r");
+ draw(w->im, w->inner, w->tab->cols[BACK], nil, ZP);
+ border(w->im, w->entire, BORDSIZ, w->tab->cols[w == actw ? BORD : DISB], ZP);
+ w->tab->draw(w);
+ }
+}
+
+extern Wintab cmdtab, paltab, sprtab;
+
+Wintab *tabs[] = {
+ [CMD] &cmdtab,
+ [PAL] &paltab,
+ [SPR] &sprtab,
+};
--- /dev/null
+++ b/sys/src/cmd/stats.c
@@ -1,0 +1,1463 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <draw.h>
+#include <event.h>
+#include <keyboard.h>
+
+#define MAXNUM 10 /* maximum number of numbers on data line */
+
+typedef struct Graph Graph;
+typedef struct Machine Machine;
+
+struct Graph
+{
+ int colindex;
+ Rectangle r;
+ int *data;
+ int ndata;
+ char *label;
+ void (*newvalue)(Machine*, uvlong*, uvlong*, int);
+ void (*update)(Graph*, uvlong, uvlong);
+ Machine *mach;
+ int overflow;
+ Image *overtmp;
+};
+
+enum
+{
+ /* /dev/swap */
+ Mem = 0,
+ Maxmem,
+ Swap,
+ Maxswap,
+ Reclaim,
+ Maxreclaim,
+ Kern,
+ Maxkern,
+ Draw,
+ Maxdraw,
+
+ /* /dev/sysstats */
+ Procno = 0,
+ Context,
+ Interrupt,
+ Syscall,
+ Fault,
+ TLBfault,
+ TLBpurge,
+ Load,
+ Idle,
+ InIntr,
+
+ /* /net/ether0/stats */
+ In = 0,
+ Link,
+ Out,
+ Err0,
+};
+
+struct Machine
+{
+ char *name;
+ char *shortname;
+ int remote;
+ int statsfd;
+ int swapfd;
+ int etherfd;
+ int ifstatsfd;
+ int batteryfd;
+ int bitsybatfd;
+ int tempfd;
+ int disable;
+
+ uvlong devswap[10];
+ uvlong devsysstat[10];
+ uvlong prevsysstat[10];
+ int nproc;
+ int lgproc;
+ uvlong netetherstats[8];
+ uvlong prevetherstats[8];
+ uvlong batterystats[2];
+ uvlong netetherifstats[2];
+ uvlong temp[10];
+
+ /* big enough to hold /dev/sysstat even with many processors */
+ char buf[8*1024];
+ char *bufp;
+ char *ebufp;
+};
+
+enum
+{
+ Mainproc,
+ Inputproc,
+ NPROC,
+};
+
+enum
+{
+ Ncolor = 6,
+ Ysqueeze = 2, /* vertical squeezing of label text */
+ Labspace = 2, /* room around label */
+ Dot = 2, /* height of dot */
+ Opwid = 5, /* strlen("add ") or strlen("drop ") */
+ Nlab = 3, /* max number of labels on y axis */
+ Lablen = 16, /* max length of label */
+ Lx = 4, /* label tick length */
+};
+
+enum Menu2
+{
+ Mbattery,
+ Mcontext,
+ Mether,
+ Methererr,
+ Metherin,
+ Metherout,
+ Mfault,
+ Midle,
+ Minintr,
+ Mintr,
+ Mload,
+ Mmem,
+ Mswap,
+ Mreclaim,
+ Mkern,
+ Mdraw,
+ Msyscall,
+ Mtlbmiss,
+ Mtlbpurge,
+ Msignal,
+ Mtemp,
+ Nmenu2,
+};
+
+char *menu2str[Nmenu2+1] = {
+ "add battery ",
+ "add context ",
+ "add ether ",
+ "add ethererr",
+ "add etherin ",
+ "add etherout",
+ "add fault ",
+ "add idle ",
+ "add inintr ",
+ "add intr ",
+ "add load ",
+ "add mem ",
+ "add swap ",
+ "add reclaim ",
+ "add kern ",
+ "add draw ",
+ "add syscall ",
+ "add tlbmiss ",
+ "add tlbpurge",
+ "add 802.11b ",
+ "add temp ",
+ nil,
+};
+
+
+void contextval(Machine*, uvlong*, uvlong*, int),
+ etherval(Machine*, uvlong*, uvlong*, int),
+ ethererrval(Machine*, uvlong*, uvlong*, int),
+ etherinval(Machine*, uvlong*, uvlong*, int),
+ etheroutval(Machine*, uvlong*, uvlong*, int),
+ faultval(Machine*, uvlong*, uvlong*, int),
+ intrval(Machine*, uvlong*, uvlong*, int),
+ inintrval(Machine*, uvlong*, uvlong*, int),
+ loadval(Machine*, uvlong*, uvlong*, int),
+ idleval(Machine*, uvlong*, uvlong*, int),
+ memval(Machine*, uvlong*, uvlong*, int),
+ swapval(Machine*, uvlong*, uvlong*, int),
+ reclaimval(Machine*, uvlong*, uvlong*, int),
+ kernval(Machine*, uvlong*, uvlong*, int),
+ drawval(Machine*, uvlong*, uvlong*, int),
+ syscallval(Machine*, uvlong*, uvlong*, int),
+ tlbmissval(Machine*, uvlong*, uvlong*, int),
+ tlbpurgeval(Machine*, uvlong*, uvlong*, int),
+ batteryval(Machine*, uvlong*, uvlong*, int),
+ signalval(Machine*, uvlong*, uvlong*, int),
+ tempval(Machine*, uvlong*, uvlong*, int);
+
+Menu menu2 = {menu2str, nil};
+int present[Nmenu2];
+void (*newvaluefn[Nmenu2])(Machine*, uvlong*, uvlong*, int init) = {
+ batteryval,
+ contextval,
+ etherval,
+ ethererrval,
+ etherinval,
+ etheroutval,
+ faultval,
+ idleval,
+ inintrval,
+ intrval,
+ loadval,
+ memval,
+ swapval,
+ reclaimval,
+ kernval,
+ drawval,
+ syscallval,
+ tlbmissval,
+ tlbpurgeval,
+ signalval,
+ tempval,
+};
+
+enum{
+ Cback,
+ Cbord,
+ Ctext,
+ Cmix1,
+ Cmix2,
+ Nscolor = Cmix2,
+ Ccol1,
+ Ccol2,
+ Ccol3,
+ Ccol4,
+ Ccol5,
+ Ccol6,
+ Ntcolor,
+};
+
+Image *cols[Ncolor][3];
+Image *tcols[Nscolor];
+Graph *graph;
+Machine *mach;
+char *mysysname;
+char argchars[] = "8bcdeEfiIkmlnprstwz";
+int pids[NPROC];
+int parity; /* toggled to avoid patterns in textured background */
+int nmach;
+int ngraph; /* totaly number is ngraph*nmach */
+double scale = 1.0;
+int logscale = 0;
+int ylabels = 0;
+int sleeptime = 1000;
+int batteryperiod = 1000;
+int tempperiod = 1000;
+
+char *procnames[NPROC] = {"main", "input"};
+
+void
+killall(char *s)
+{
+ int i, pid;
+
+ pid = getpid();
+ for(i=0; i<NPROC; i++)
+ if(pids[i] && pids[i]!=pid)
+ postnote(PNPROC, pids[i], "kill");
+ exits(s);
+}
+
+void*
+emalloc(ulong sz)
+{
+ void *v;
+ v = malloc(sz);
+ if(v == nil) {
+ fprint(2, "stats: out of memory allocating %ld: %r\n", sz);
+ killall("mem");
+ }
+ memset(v, 0, sz);
+ return v;
+}
+
+void*
+erealloc(void *v, ulong sz)
+{
+ v = realloc(v, sz);
+ if(v == nil) {
+ fprint(2, "stats: out of memory reallocating %ld: %r\n", sz);
+ killall("mem");
+ }
+ return v;
+}
+
+char*
+estrdup(char *s)
+{
+ char *t;
+ if((t = strdup(s)) == nil) {
+ fprint(2, "stats: out of memory in strdup(%.10s): %r\n", s);
+ killall("mem");
+ }
+ return t;
+}
+
+void
+mkcol(int i, int mix, int mix2, int c)
+{
+ cols[i][0] = allocimagemix(display, c, mix);
+ cols[i][1] = allocimagemix(display, c, mix2);
+ cols[i][2] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, c);
+}
+
+void
+colinit(Theme th[Ntcolor])
+{
+ mkcol(0, th[Cmix1].c, th[Cmix2].c, th[Ccol1].c);
+ mkcol(1, th[Cmix1].c, th[Cmix2].c, th[Ccol2].c);
+ mkcol(2, th[Cmix1].c, th[Cmix2].c, th[Ccol3].c);
+ mkcol(3, th[Cmix1].c, th[Cmix2].c, th[Ccol4].c);
+ mkcol(4, th[Cmix1].c, th[Cmix2].c, th[Ccol5].c);
+ mkcol(5, th[Cmix1].c, th[Cmix2].c, th[Ccol6].c);
+}
+
+int
+loadbuf(Machine *m, int *fd)
+{
+ int n;
+
+
+ if(*fd < 0)
+ return 0;
+ seek(*fd, 0, 0);
+ n = read(*fd, m->buf, sizeof m->buf-1);
+ if(n <= 0){
+ close(*fd);
+ *fd = -1;
+ return 0;
+ }
+ m->bufp = m->buf;
+ m->ebufp = m->buf+n;
+ m->buf[n] = 0;
+ return 1;
+}
+
+void
+label(Point p, int dy, char *text)
+{
+ char *s;
+ Rune r[2];
+ int w, maxw, maxy;
+
+ p.x += Labspace;
+ maxy = p.y+dy;
+ maxw = 0;
+ r[1] = '\0';
+ for(s=text; *s; ){
+ if(p.y+font->height-Ysqueeze > maxy)
+ break;
+ w = chartorune(r, s);
+ s += w;
+ w = runestringwidth(font, r);
+ if(w > maxw)
+ maxw = w;
+ runestring(screen, p, tcols[Ctext], ZP, font, r);
+ p.y += font->height-Ysqueeze;
+ }
+}
+
+Point
+paritypt(int x)
+{
+ return Pt(x+parity, 0);
+}
+
+Point
+datapoint(Graph *g, int x, uvlong v, uvlong vmax)
+{
+ Point p;
+ double y;
+
+ p.x = x;
+ y = ((double)v)/(vmax*scale);
+ if(logscale){
+ /*
+ * Arrange scale to cover a factor of 1000.
+ * vmax corresponds to the 100 mark.
+ * 10*vmax is the top of the scale.
+ */
+ if(y <= 0.)
+ y = 0;
+ else{
+ y = log10(y);
+ /* 1 now corresponds to the top; -2 to the bottom; rescale */
+ y = (y+2.)/3.;
+ }
+ }
+ if(y >= 1.)
+ y = 1;
+ if(y <= 0.)
+ y = 0;
+ p.y = g->r.max.y - Dy(g->r)*y - Dot;
+ if(p.y < g->r.min.y)
+ p.y = g->r.min.y;
+ if(p.y > g->r.max.y-Dot)
+ p.y = g->r.max.y-Dot;
+ return p;
+}
+
+void
+drawdatum(Graph *g, int x, uvlong prev, uvlong v, uvlong vmax)
+{
+ int c;
+ Point p, q;
+
+ c = g->colindex;
+ p = datapoint(g, x, v, vmax);
+ q = datapoint(g, x, prev, vmax);
+ if(p.y < q.y){
+ draw(screen, Rect(p.x, g->r.min.y, p.x+1, p.y), cols[c][0], nil, paritypt(p.x));
+ draw(screen, Rect(p.x, p.y, p.x+1, q.y+Dot), cols[c][2], nil, ZP);
+ draw(screen, Rect(p.x, q.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP);
+ }else{
+ draw(screen, Rect(p.x, g->r.min.y, p.x+1, q.y), cols[c][0], nil, paritypt(p.x));
+ draw(screen, Rect(p.x, q.y, p.x+1, p.y+Dot), cols[c][2], nil, ZP);
+ draw(screen, Rect(p.x, p.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP);
+ }
+
+}
+
+void
+redraw(Graph *g, uvlong vmax)
+{
+ int i, c;
+
+ c = g->colindex;
+ draw(screen, g->r, cols[c][0], nil, paritypt(g->r.min.x));
+ for(i=1; i<Dx(g->r); i++)
+ drawdatum(g, g->r.max.x-i, g->data[i-1], g->data[i], vmax);
+ drawdatum(g, g->r.min.x, g->data[i], g->data[i], vmax);
+ g->overflow = 0;
+}
+
+void
+update1(Graph *g, uvlong v, uvlong vmax)
+{
+ char buf[48];
+ int overflow;
+
+ if(g->overflow && g->overtmp!=nil)
+ draw(screen, g->overtmp->r, g->overtmp, nil, g->overtmp->r.min);
+ draw(screen, g->r, screen, nil, Pt(g->r.min.x+1, g->r.min.y));
+ drawdatum(g, g->r.max.x-1, g->data[0], v, vmax);
+ memmove(g->data+1, g->data, (g->ndata-1)*sizeof(g->data[0]));
+ g->data[0] = v;
+ g->overflow = 0;
+ if(logscale)
+ overflow = (v>10*vmax*scale);
+ else
+ overflow = (v>vmax*scale);
+ if(overflow && g->overtmp!=nil){
+ g->overflow = 1;
+ draw(g->overtmp, g->overtmp->r, screen, nil, g->overtmp->r.min);
+ sprint(buf, "%llud", v);
+ string(screen, g->overtmp->r.min, tcols[Ctext], ZP, font, buf);
+ }
+}
+
+/* read one line of text from buffer and process integers */
+int
+readnums(Machine *m, int n, uvlong *a, int spanlines)
+{
+ int i;
+ char *p, *ep;
+
+ if(spanlines)
+ ep = m->ebufp;
+ else
+ for(ep=m->bufp; ep<m->ebufp; ep++)
+ if(*ep == '\n')
+ break;
+ p = m->bufp;
+ for(i=0; i<n && p<ep; i++){
+ while(p<ep && (!isascii(*p) || !isdigit(*p)) && *p!='-')
+ p++;
+ if(p == ep)
+ break;
+ a[i] = strtoull(p, &p, 10);
+ }
+ if(ep < m->ebufp)
+ ep++;
+ m->bufp = ep;
+ return i == n;
+}
+
+int
+readswap(Machine *m, uvlong *a)
+{
+ static int xxx = 0;
+
+ if(strstr(m->buf, "memory\n")){
+ /* new /dev/swap - skip first 3 numbers */
+ if(!readnums(m, 7, a, 1))
+ return 0;
+
+ a[Mem] = a[3];
+ a[Maxmem] = a[4];
+ a[Swap] = a[5];
+ a[Maxswap] = a[6];
+
+ a[Reclaim] = 0;
+ a[Maxreclaim] = 0;
+ if(m->bufp = strstr(m->buf, "reclaim")){
+ while(m->bufp > m->buf && m->bufp[-1] != '\n')
+ m->bufp--;
+ a[Reclaim] = strtoull(m->bufp, &m->bufp, 10);
+ while(*m->bufp++ == '/')
+ a[Maxreclaim] = strtoull(m->bufp, &m->bufp, 10);
+ }
+
+ a[Kern] = 0;
+ a[Maxkern] = 0;
+ if(m->bufp = strstr(m->buf, "kernel malloc")){
+ while(m->bufp > m->buf && m->bufp[-1] != '\n')
+ m->bufp--;
+ a[Kern] = strtoull(m->bufp, &m->bufp, 10);
+ while(*m->bufp++ == '/')
+ a[Maxkern] = strtoull(m->bufp, &m->bufp, 10);
+ }
+
+ a[Draw] = 0;
+ a[Maxdraw] = 0;
+ if(m->bufp = strstr(m->buf, "kernel draw")){
+ while(m->bufp > m->buf && m->bufp[-1] != '\n')
+ m->bufp--;
+ a[Draw] = strtoull(m->bufp, &m->bufp, 10);
+ while(*m->bufp++ == '/')
+ a[Maxdraw] = strtoull(m->bufp, &m->bufp, 10);
+ }
+
+ return 1;
+ }
+
+ a[Reclaim] = 0;
+ a[Maxreclaim] = 0;
+ a[Kern] = 0;
+ a[Maxkern] = 0;
+ a[Draw] = 0;
+ a[Maxdraw] = 0;
+
+ return readnums(m, 4, a, 0);
+}
+
+char*
+shortname(char *s)
+{
+ char *p, *e;
+
+ p = estrdup(s);
+ e = strchr(p, '.');
+ if(e)
+ *e = 0;
+ return p;
+}
+
+int
+ilog10(uvlong j)
+{
+ int i;
+
+ for(i = 0; j >= 10; i++)
+ j /= 10;
+ return i;
+}
+
+int
+initmach(Machine *m, char *name)
+{
+ int n;
+ uvlong a[MAXNUM];
+ char *p, mpt[256], buf[256];
+
+ p = strchr(name, '!');
+ if(p)
+ p++;
+ else
+ p = name;
+ m->name = estrdup(p);
+ m->shortname = shortname(p);
+ m->remote = (strcmp(p, mysysname) != 0);
+ if(m->remote == 0)
+ strcpy(mpt, "");
+ else{
+ Waitmsg *w;
+ int pid;
+
+ snprint(mpt, sizeof mpt, "/n/%s", p);
+
+ pid = fork();
+ switch(pid){
+ case -1:
+ fprint(2, "can't fork: %r\n");
+ return 0;
+ case 0:
+ execl("/bin/rimport", "rimport", name, "/", mpt, nil);
+ fprint(2, "can't exec: %r\n");
+ exits("exec");
+ }
+ w = wait();
+ if(w == nil || w->pid != pid || w->msg[0] != '\0'){
+ free(w);
+ return 0;
+ }
+ free(w);
+ }
+
+ snprint(buf, sizeof buf, "%s/dev/swap", mpt);
+ m->swapfd = open(buf, OREAD);
+ if(loadbuf(m, &m->swapfd) && readswap(m, a))
+ memmove(m->devswap, a, sizeof m->devswap);
+
+ snprint(buf, sizeof buf, "%s/dev/sysstat", mpt);
+ m->statsfd = open(buf, OREAD);
+ if(loadbuf(m, &m->statsfd)){
+ for(n=0; readnums(m, nelem(m->devsysstat), a, 0); n++)
+ ;
+ m->nproc = n;
+ }else
+ m->nproc = 1;
+ m->lgproc = ilog10(m->nproc);
+
+ snprint(buf, sizeof buf, "%s/net/ether0/stats", mpt);
+ m->etherfd = open(buf, OREAD);
+ if(loadbuf(m, &m->etherfd) && readnums(m, nelem(m->netetherstats), a, 1))
+ memmove(m->netetherstats, a, sizeof m->netetherstats);
+
+ snprint(buf, sizeof buf, "%s/net/ether0/ifstats", mpt);
+ m->ifstatsfd = open(buf, OREAD);
+ if(loadbuf(m, &m->ifstatsfd)){
+ /* need to check that this is a wavelan interface */
+ if(strncmp(m->buf, "Signal: ", 8) == 0 && readnums(m, nelem(m->netetherifstats), a, 1))
+ memmove(m->netetherifstats, a, sizeof m->netetherifstats);
+ }
+
+ snprint(buf, sizeof buf, "%s/mnt/apm/battery", mpt);
+ m->batteryfd = open(buf, OREAD);
+ if(m->batteryfd < 0){
+ snprint(buf, sizeof buf, "%s/mnt/pm/battery", mpt);
+ m->batteryfd = open(buf, OREAD);
+ }
+ m->bitsybatfd = -1;
+ if(m->batteryfd >= 0){
+ batteryperiod = 10000;
+ if(loadbuf(m, &m->batteryfd) && readnums(m, nelem(m->batterystats), a, 0))
+ memmove(m->batterystats, a, sizeof(m->batterystats));
+ }else{
+ snprint(buf, sizeof buf, "%s/dev/battery", mpt);
+ m->bitsybatfd = open(buf, OREAD);
+ if(loadbuf(m, &m->bitsybatfd) && readnums(m, 1, a, 0))
+ memmove(m->batterystats, a, sizeof(m->batterystats));
+ }
+ snprint(buf, sizeof buf, "%s/dev/cputemp", mpt);
+ m->tempfd = open(buf, OREAD);
+ if(m->tempfd < 0){
+ tempperiod = 5000;
+ snprint(buf, sizeof buf, "%s/mnt/pm/cputemp", mpt);
+ m->tempfd = open(buf, OREAD);
+ }
+ if(loadbuf(m, &m->tempfd))
+ for(n=0; n < nelem(m->temp) && readnums(m, 2, a, 0); n++)
+ m->temp[n] = a[0];
+ return 1;
+}
+
+jmp_buf catchalarm;
+
+int
+alarmed(void *a, char *s)
+{
+ if(strcmp(s, "alarm") == 0)
+ notejmp(a, catchalarm, 1);
+ return 0;
+}
+
+int
+needswap(int init)
+{
+ return init | present[Mmem] | present[Mswap] | present[Mreclaim] | present[Mkern] | present[Mdraw];
+}
+
+
+int
+needstat(int init)
+{
+ return init | present[Mcontext] | present[Mfault] | present[Mintr] | present[Mload] | present[Midle] |
+ present[Minintr] | present[Msyscall] | present[Mtlbmiss] | present[Mtlbpurge];
+}
+
+
+int
+needether(int init)
+{
+ return init | present[Mether] | present[Metherin] | present[Metherout] | present[Methererr];
+}
+
+int
+needbattery(int init)
+{
+ static uint step = 0;
+
+ if(++step*sleeptime >= batteryperiod){
+ step = 0;
+ return init | present[Mbattery];
+ }
+
+ return 0;
+}
+
+int
+needsignal(int init)
+{
+ return init | present[Msignal];
+}
+
+int
+needtemp(int init)
+{
+ static uint step = 0;
+
+ if(++step*sleeptime >= tempperiod){
+ step = 0;
+ return init | present[Mtemp];
+ }
+
+ return 0;
+}
+
+void
+readmach(Machine *m, int init)
+{
+ int n, i;
+ uvlong a[nelem(m->devsysstat)];
+ char buf[32];
+
+ if(m->remote && (m->disable || setjmp(catchalarm))){
+ if (m->disable++ >= 5)
+ m->disable = 0; /* give it another chance */
+ memmove(m->devsysstat, m->prevsysstat, sizeof m->devsysstat);
+ memmove(m->netetherstats, m->prevetherstats, sizeof m->netetherstats);
+ return;
+ }
+ snprint(buf, sizeof buf, "%s", m->name);
+ if (strcmp(m->name, buf) != 0){
+ free(m->name);
+ m->name = estrdup(buf);
+ free(m->shortname);
+ m->shortname = shortname(buf);
+ if(display != nil) /* else we're still initializing */
+ eresized(0);
+ }
+ if(m->remote){
+ atnotify(alarmed, 1);
+ alarm(5000);
+ }
+ if(needswap(init) && loadbuf(m, &m->swapfd) && readswap(m, a))
+ memmove(m->devswap, a, sizeof m->devswap);
+ if(needstat(init) && loadbuf(m, &m->statsfd)){
+ memmove(m->prevsysstat, m->devsysstat, sizeof m->devsysstat);
+ memset(m->devsysstat, 0, sizeof m->devsysstat);
+ for(n=0; n<m->nproc && readnums(m, nelem(m->devsysstat), a, 0); n++)
+ for(i=0; i<nelem(m->devsysstat); i++)
+ m->devsysstat[i] += a[i];
+ }
+ if(needether(init) && loadbuf(m, &m->etherfd) && readnums(m, nelem(m->netetherstats), a, 1)){
+ memmove(m->prevetherstats, m->netetherstats, sizeof m->netetherstats);
+ memmove(m->netetherstats, a, sizeof m->netetherstats);
+ }
+ if(needsignal(init) && loadbuf(m, &m->ifstatsfd) && strncmp(m->buf, "Signal: ", 8)==0 && readnums(m, nelem(m->netetherifstats), a, 1)){
+ memmove(m->netetherifstats, a, sizeof m->netetherifstats);
+ }
+ if(needbattery(init)){
+ if(loadbuf(m, &m->batteryfd) && readnums(m, nelem(m->batterystats), a, 0))
+ memmove(m->batterystats, a, sizeof(m->batterystats));
+ else if(loadbuf(m, &m->bitsybatfd) && readnums(m, 1, a, 0))
+ memmove(m->batterystats, a, sizeof(m->batterystats));
+ }
+ if(needtemp(init) && loadbuf(m, &m->tempfd))
+ for(n=0; n < nelem(m->temp) && readnums(m, 2, a, 0); n++)
+ m->temp[n] = a[0];
+ if(m->remote){
+ alarm(0);
+ atnotify(alarmed, 0);
+ }
+}
+
+void
+memval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+ *v = m->devswap[Mem];
+ *vmax = m->devswap[Maxmem];
+ if(*vmax == 0)
+ *vmax = 1;
+}
+
+void
+swapval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+ *v = m->devswap[Swap];
+ *vmax = m->devswap[Maxswap];
+ if(*vmax == 0)
+ *vmax = 1;
+}
+
+void
+reclaimval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+ *v = m->devswap[Reclaim];
+ *vmax = m->devswap[Maxreclaim];
+ if(*vmax == 0)
+ *vmax = 1;
+}
+
+void
+kernval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+ *v = m->devswap[Kern];
+ *vmax = m->devswap[Maxkern];
+ if(*vmax == 0)
+ *vmax = 1;
+}
+
+void
+drawval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+ *v = m->devswap[Draw];
+ *vmax = m->devswap[Maxdraw];
+ if(*vmax == 0)
+ *vmax = 1;
+}
+
+void
+contextval(Machine *m, uvlong *v, uvlong *vmax, int init)
+{
+ *v = (m->devsysstat[Context]-m->prevsysstat[Context])&0xffffffff;
+ *vmax = sleeptime*m->nproc;
+ if(init)
+ *vmax = sleeptime;
+}
+
+/*
+ * bug: need to factor in HZ
+ */
+void
+intrval(Machine *m, uvlong *v, uvlong *vmax, int init)
+{
+ *v = (m->devsysstat[Interrupt]-m->prevsysstat[Interrupt])&0xffffffff;
+ *vmax = sleeptime*m->nproc*10;
+ if(init)
+ *vmax = sleeptime*10;
+}
+
+void
+syscallval(Machine *m, uvlong *v, uvlong *vmax, int init)
+{
+ *v = (m->devsysstat[Syscall]-m->prevsysstat[Syscall])&0xffffffff;
+ *vmax = sleeptime*m->nproc;
+ if(init)
+ *vmax = sleeptime;
+}
+
+void
+faultval(Machine *m, uvlong *v, uvlong *vmax, int init)
+{
+ *v = (m->devsysstat[Fault]-m->prevsysstat[Fault])&0xffffffff;
+ *vmax = sleeptime*m->nproc;
+ if(init)
+ *vmax = sleeptime;
+}
+
+void
+tlbmissval(Machine *m, uvlong *v, uvlong *vmax, int init)
+{
+ *v = (m->devsysstat[TLBfault]-m->prevsysstat[TLBfault])&0xffffffff;
+ *vmax = (sleeptime/1000)*10*m->nproc;
+ if(init)
+ *vmax = (sleeptime/1000)*10;
+}
+
+void
+tlbpurgeval(Machine *m, uvlong *v, uvlong *vmax, int init)
+{
+ *v = (m->devsysstat[TLBpurge]-m->prevsysstat[TLBpurge])&0xffffffff;
+ *vmax = (sleeptime/1000)*10*m->nproc;
+ if(init)
+ *vmax = (sleeptime/1000)*10;
+}
+
+void
+loadval(Machine *m, uvlong *v, uvlong *vmax, int init)
+{
+ *v = m->devsysstat[Load];
+ *vmax = 1000*m->nproc;
+ if(init)
+ *vmax = 1000;
+}
+
+void
+idleval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+ *v = m->devsysstat[Idle]/m->nproc;
+ *vmax = 100;
+}
+
+void
+inintrval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+ *v = m->devsysstat[InIntr]/m->nproc;
+ *vmax = 100;
+}
+
+void
+etherval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+ *v = m->netetherstats[In]-m->prevetherstats[In] + m->netetherstats[Out]-m->prevetherstats[Out];
+ *vmax = sleeptime;
+}
+
+void
+etherinval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+ *v = m->netetherstats[In]-m->prevetherstats[In];
+ *vmax = sleeptime;
+}
+
+void
+etheroutval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+ *v = m->netetherstats[Out]-m->prevetherstats[Out];
+ *vmax = sleeptime;
+}
+
+void
+ethererrval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+ int i;
+
+ *v = 0;
+ for(i=Err0; i<nelem(m->netetherstats); i++)
+ *v += m->netetherstats[i]-m->prevetherstats[i];
+ *vmax = (sleeptime/1000)*10;
+}
+
+void
+batteryval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+ *v = m->batterystats[0];
+ if(m->bitsybatfd >= 0)
+ *vmax = 184; // at least on my bitsy...
+ else
+ *vmax = 100;
+}
+
+void
+signalval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+ ulong l;
+
+ *vmax = sleeptime;
+ l = m->netetherifstats[0];
+ /*
+ * Range is seen to be from about -45 (strong) to -95 (weak); rescale
+ */
+ if(l == 0){ /* probably not present */
+ *v = 0;
+ return;
+ }
+ *v = 20*(l+95);
+}
+
+void
+tempval(Machine *m, uvlong *v, uvlong *vmax, int)
+{
+ ulong l;
+
+ *vmax = 100;
+ l = m->temp[0];
+ if(l == ~0 || l == 0)
+ *v = 0;
+ else
+ *v = l;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: stats [-O] [-S scale] [-LY] [-%s] [machine...]\n", argchars);
+ exits("usage");
+}
+
+void
+addgraph(int n)
+{
+ Graph *g, *ograph;
+ int i, j;
+ static int nadd;
+
+ if(n > nelem(menu2str))
+ abort();
+ /* avoid two adjacent graphs of same color */
+ if(ngraph>0 && graph[ngraph-1].colindex==nadd%Ncolor)
+ nadd++;
+ ograph = graph;
+ graph = emalloc(nmach*(ngraph+1)*sizeof(Graph));
+ for(i=0; i<nmach; i++)
+ for(j=0; j<ngraph; j++)
+ graph[i*(ngraph+1)+j] = ograph[i*ngraph+j];
+ free(ograph);
+ ngraph++;
+ for(i=0; i<nmach; i++){
+ g = &graph[i*ngraph+(ngraph-1)];
+ memset(g, 0, sizeof(Graph));
+ g->label = menu2str[n]+Opwid;
+ g->newvalue = newvaluefn[n];
+ g->update = update1; /* no other update functions yet */
+ g->mach = &mach[i];
+ g->colindex = nadd%Ncolor;
+ }
+ present[n] = 1;
+ nadd++;
+}
+
+void
+dropgraph(int which)
+{
+ Graph *ograph;
+ int i, j, n;
+
+ if(which > nelem(menu2str))
+ abort();
+ /* convert n to index in graph table */
+ n = -1;
+ for(i=0; i<ngraph; i++)
+ if(strcmp(menu2str[which]+Opwid, graph[i].label) == 0){
+ n = i;
+ break;
+ }
+ if(n < 0){
+ fprint(2, "stats: internal error can't drop graph\n");
+ killall("error");
+ }
+ ograph = graph;
+ graph = emalloc(nmach*(ngraph-1)*sizeof(Graph));
+ for(i=0; i<nmach; i++){
+ for(j=0; j<n; j++)
+ graph[i*(ngraph-1)+j] = ograph[i*ngraph+j];
+ free(ograph[i*ngraph+j].data);
+ freeimage(ograph[i*ngraph+j].overtmp);
+ for(j++; j<ngraph; j++)
+ graph[i*(ngraph-1)+j-1] = ograph[i*ngraph+j];
+ }
+ free(ograph);
+ ngraph--;
+ present[which] = 0;
+}
+
+int
+addmachine(char *name)
+{
+ if(ngraph > 0){
+ fprint(2, "stats: internal error: ngraph>0 in addmachine()\n");
+ usage();
+ }
+ if(mach == nil)
+ nmach = 0; /* a little dance to get us started with local machine by default */
+ mach = erealloc(mach, (nmach+1)*sizeof(Machine));
+ memset(mach+nmach, 0, sizeof(Machine));
+ if (initmach(mach+nmach, name)){
+ nmach++;
+ return 1;
+ } else
+ return 0;
+}
+
+void
+labelstrs(Graph *g, char strs[Nlab][Lablen], int *np)
+{
+ int j;
+ uvlong v, vmax;
+
+ g->newvalue(g->mach, &v, &vmax, 1);
+ if(vmax == 0)
+ vmax = 1;
+ if(logscale){
+ for(j=1; j<=2; j++)
+ sprint(strs[j-1], "%g", scale*pow(10., j)*(double)vmax/100.);
+ *np = 2;
+ }else{
+ for(j=1; j<=3; j++)
+ sprint(strs[j-1], "%g", scale*(double)j*(double)vmax/4.0);
+ *np = 3;
+ }
+}
+
+int
+labelwidth(void)
+{
+ int i, j, n, w, maxw;
+ char strs[Nlab][Lablen];
+
+ maxw = 0;
+ for(i=0; i<ngraph; i++){
+ /* choose value for rightmost graph */
+ labelstrs(&graph[ngraph*(nmach-1)+i], strs, &n);
+ for(j=0; j<n; j++){
+ w = stringwidth(font, strs[j]);
+ if(w > maxw)
+ maxw = w;
+ }
+ }
+ return maxw;
+}
+
+void
+resize(void)
+{
+ int i, j, k, n, startx, starty, x, y, dx, dy, ly, ondata, maxx, wid, nlab;
+ Graph *g;
+ Rectangle machr, r;
+ uvlong v, vmax;
+ char buf[128], labs[Nlab][Lablen];
+
+ draw(screen, screen->r, tcols[Cback], nil, ZP);
+
+ /* label left edge */
+ x = screen->r.min.x;
+ y = screen->r.min.y + Labspace+font->height+Labspace;
+ dy = (screen->r.max.y - y)/ngraph;
+ dx = Labspace+stringwidth(font, "0")+Labspace;
+ startx = x+dx+1;
+ starty = y;
+ for(i=0; i<ngraph; i++,y+=dy){
+ draw(screen, Rect(x, y-1, screen->r.max.x, y), tcols[Cbord], nil, ZP);
+ draw(screen, Rect(x, y, x+dx, screen->r.max.y), cols[graph[i].colindex][0], nil, paritypt(x));
+ label(Pt(x, y), dy, graph[i].label);
+ draw(screen, Rect(x+dx, y, x+dx+1, screen->r.max.y), cols[graph[i].colindex][2], nil, ZP);
+ }
+
+ /* label top edge */
+ dx = (screen->r.max.x - startx)/nmach;
+ for(x=startx, i=0; i<nmach; i++,x+=dx){
+ draw(screen, Rect(x-1, starty-1, x, screen->r.max.y), tcols[Cbord], nil, ZP);
+ j = dx/stringwidth(font, "0");
+ n = mach[i].nproc;
+ if(n>1 && j>=1+3+mach[i].lgproc){ /* first char of name + (n) */
+ j -= 3+mach[i].lgproc;
+ if(j <= 0)
+ j = 1;
+ snprint(buf, sizeof buf, "%.*s(%d)", j, mach[i].shortname, n);
+ }else
+ snprint(buf, sizeof buf, "%.*s", j, mach[i].shortname);
+ string(screen, Pt(x+Labspace, screen->r.min.y + Labspace), tcols[Ctext], ZP, font, buf);
+ }
+
+ maxx = screen->r.max.x;
+
+ /* label right, if requested */
+ if(ylabels && dy>Nlab*(font->height+1)){
+ wid = labelwidth();
+ if(wid < dx-10){
+ /* else there's not enough room */
+ maxx -= 1+Lx+wid;
+ draw(screen, Rect(maxx, starty, maxx+1, screen->r.max.y), tcols[Cbord], nil, ZP);
+ y = starty;
+ for(j=0; j<ngraph; j++, y+=dy){
+ /* choose value for rightmost graph */
+ g = &graph[ngraph*(nmach-1)+j];
+ labelstrs(g, labs, &nlab);
+ r = Rect(maxx+1, y, screen->r.max.x, y+dy-1);
+ if(j == ngraph-1)
+ r.max.y = screen->r.max.y;
+ draw(screen, r, cols[g->colindex][0], nil, paritypt(r.min.x));
+ for(k=0; k<nlab; k++){
+ ly = y + (dy*(nlab-k)/(nlab+1));
+ draw(screen, Rect(maxx+1, ly, maxx+1+Lx, ly+1), tcols[Cbord], nil, ZP);
+ ly -= font->height/2;
+ string(screen, Pt(maxx+1+Lx, ly), tcols[Ctext], ZP, font, labs[k]);
+ }
+ }
+ }
+ }
+
+ /* create graphs */
+ for(i=0; i<nmach; i++){
+ machr = Rect(startx+i*dx, starty, startx+(i+1)*dx - 1, screen->r.max.y);
+ if(i == nmach-1)
+ machr.max.x = maxx;
+ y = starty;
+ for(j=0; j<ngraph; j++, y+=dy){
+ g = &graph[i*ngraph+j];
+ /* allocate data */
+ ondata = g->ndata;
+ g->ndata = Dx(machr)+1; /* may be too many if label will be drawn here; so what? */
+ g->data = erealloc(g->data, g->ndata*sizeof(ulong));
+ if(g->ndata > ondata)
+ memset(g->data+ondata, 0, (g->ndata-ondata)*sizeof(ulong));
+ /* set geometry */
+ g->r = machr;
+ g->r.min.y = y;
+ g->r.max.y = y+dy - 1;
+ if(j == ngraph-1)
+ g->r.max.y = screen->r.max.y;
+ draw(screen, g->r, cols[g->colindex][0], nil, paritypt(g->r.min.x));
+ g->overflow = 0;
+ r = g->r;
+ r.max.y = r.min.y+font->height;
+ r.max.x = r.min.x+stringwidth(font, "999999999999");
+ freeimage(g->overtmp);
+ g->overtmp = nil;
+ if(r.max.x <= g->r.max.x)
+ g->overtmp = allocimage(display, r, screen->chan, 0, -1);
+ g->newvalue(g->mach, &v, &vmax, 0);
+ if(vmax == 0)
+ vmax = 1;
+ redraw(g, vmax);
+ }
+ }
+
+ flushimage(display, 1);
+}
+
+void
+eresized(int new)
+{
+ lockdisplay(display);
+ if(new && getwindow(display, Refnone) < 0) {
+ fprint(2, "stats: can't reattach to window\n");
+ killall("reattach");
+ }
+ resize();
+ unlockdisplay(display);
+}
+
+void
+inputproc(void)
+{
+ Event e;
+ int i;
+
+ for(;;){
+ switch(eread(Emouse|Ekeyboard, &e)){
+ case Emouse:
+ if(e.mouse.buttons == 4){
+ lockdisplay(display);
+ for(i=0; i<Nmenu2; i++)
+ if(present[i])
+ memmove(menu2str[i], "drop ", Opwid);
+ else
+ memmove(menu2str[i], "add ", Opwid);
+ i = emenuhit(3, &e.mouse, &menu2);
+ if(i >= 0){
+ if(!present[i])
+ addgraph(i);
+ else if(ngraph > 1)
+ dropgraph(i);
+ resize();
+ }
+ unlockdisplay(display);
+ }
+ break;
+ case Ekeyboard:
+ if(e.kbdc==Kdel || e.kbdc=='q')
+ killall(nil);
+ break;
+ }
+ }
+}
+
+void
+startproc(void (*f)(void), int index)
+{
+ int pid;
+
+ switch(pid = rfork(RFPROC|RFMEM|RFNOWAIT)){
+ case -1:
+ fprint(2, "stats: fork failed: %r\n");
+ killall("fork failed");
+ case 0:
+ f();
+ fprint(2, "stats: %s process exits\n", procnames[index]);
+ if(index >= 0)
+ killall("process died");
+ exits(nil);
+ }
+ if(index >= 0)
+ pids[index] = pid;
+}
+
+void
+main(int argc, char *argv[])
+{
+ int i, j;
+ double secs;
+ uvlong v, vmax, nargs;
+ char args[100];
+
+ quotefmtinstall();
+
+ nmach = 1;
+ mysysname = getenv("sysname");
+ if(mysysname == nil){
+ fprint(2, "stats: can't find $sysname: %r\n");
+ exits("sysname");
+ }
+
+ nargs = 0;
+ ARGBEGIN{
+ case 'T':
+ secs = atof(EARGF(usage()));
+ if(secs > 0)
+ sleeptime = 1000*secs;
+ break;
+ case 'S':
+ scale = atof(EARGF(usage()));
+ if(scale <= 0)
+ usage();
+ break;
+ case 'L':
+ logscale++;
+ break;
+ case 'Y':
+ ylabels++;
+ break;
+ case 'O':
+ break;
+ default:
+ if(nargs>=sizeof args || strchr(argchars, ARGC())==nil)
+ usage();
+ args[nargs++] = ARGC();
+ }ARGEND
+
+ if(argc == 0){
+ mach = emalloc(nmach*sizeof(Machine));
+ initmach(&mach[0], mysysname);
+ readmach(&mach[0], 1);
+ }else{
+ rfork(RFNAMEG);
+ for(i=j=0; i<argc; i++){
+ if (addmachine(argv[i]))
+ readmach(&mach[j++], 1);
+ }
+ if (j == 0)
+ exits("connect");
+ }
+
+ for(i=0; i<nargs; i++)
+ switch(args[i]){
+ default:
+ fprint(2, "stats: internal error: unknown arg %c\n", args[i]);
+ usage();
+ case 'b':
+ addgraph(Mbattery);
+ break;
+ case 'c':
+ addgraph(Mcontext);
+ break;
+ case 'e':
+ addgraph(Mether);
+ break;
+ case 'E':
+ addgraph(Metherin);
+ addgraph(Metherout);
+ break;
+ case 'f':
+ addgraph(Mfault);
+ break;
+ case 'i':
+ addgraph(Mintr);
+ break;
+ case 'I':
+ addgraph(Mload);
+ addgraph(Midle);
+ addgraph(Minintr);
+ break;
+ case 'l':
+ addgraph(Mload);
+ break;
+ case 'm':
+ addgraph(Mmem);
+ break;
+ case 'n':
+ addgraph(Metherin);
+ addgraph(Metherout);
+ addgraph(Methererr);
+ break;
+ case 'p':
+ addgraph(Mtlbpurge);
+ break;
+ case 'r':
+ addgraph(Mreclaim);
+ break;
+ case 's':
+ addgraph(Msyscall);
+ break;
+ case 't':
+ addgraph(Mtlbmiss);
+ addgraph(Mtlbpurge);
+ break;
+ case '8':
+ addgraph(Msignal);
+ break;
+ case 'w':
+ addgraph(Mswap);
+ break;
+ case 'k':
+ addgraph(Mkern);
+ break;
+ case 'd':
+ addgraph(Mdraw);
+ break;
+ case 'z':
+ addgraph(Mtemp);
+ break;
+ }
+
+ if(ngraph == 0)
+ addgraph(Mload);
+
+ for(i=0; i<nmach; i++)
+ for(j=0; j<ngraph; j++)
+ graph[i*ngraph+j].mach = &mach[i];
+
+ if(initdraw(nil, nil, "stats") < 0){
+ fprint(2, "stats: initdraw failed: %r\n");
+ exits("initdraw");
+ }
+ display->locking = 1; /* tell library we're using the display lock */
+
+ Theme th[Ntcolor] = {
+ [Cback] { "back", DWhite },
+ [Cbord] { "border", DBlack },
+ [Ctext] { "text", DBlack },
+ [Cmix1] { "rioback", DWhite },
+ [Cmix2] { "palehold", DWhite },
+ [Ccol1] { "htext", 0xFFAAAAFF },
+ [Ccol2] { "high", DPurpleblue },
+ [Ccol3] { "hold", DYellowgreen },
+ [Ccol4] { "size", DDarkgreen },
+ [Ccol5] { "title", 0x0088CCFF },
+ [Ccol6] { "paletext", 0x888888FF },
+ };
+ readtheme(th, nelem(th), nil);
+ for(i=0; i<nelem(tcols); i++)
+ tcols[i] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, th[i].c);
+ colinit(th);
+ einit(Emouse|Ekeyboard);
+ startproc(inputproc, Inputproc);
+ pids[Mainproc] = getpid();
+
+ resize();
+
+ unlockdisplay(display); /* display is still locked from initdraw() */
+ for(;;){
+ for(i=0; i<nmach; i++)
+ readmach(&mach[i], 0);
+ lockdisplay(display);
+ parity = 1-parity;
+ for(i=0; i<nmach*ngraph; i++){
+ graph[i].newvalue(graph[i].mach, &v, &vmax, 0);
+ if(vmax == 0)
+ vmax = 1;
+ graph[i].update(&graph[i], v, vmax);
+ }
+ flushimage(display, 1);
+ unlockdisplay(display);
+ sleep(sleeptime);
+ }
+}