#include "theme.c"
#include <mouse.h>
#include <keyboard.h>
#include <ctype.h>
#include "plist.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 Player Player;
enum
{
Cstart = 1,
Cstop,
Ctoggle,
Cseekrel,
Everror = 1,
Evready,
Seek = 10, /* 10 seconds */
Seekfast = 60, /* a minute */
Scrollwidth = 14,
Scrollheight = 16,
Bps = 44100*2*2, /* 44100KHz, stereo, s16 for a sample */
Relbufsz = Bps/5, /* 0.2 second */
};
struct Player
{
Channel *ctl;
Channel *ev;
Channel *img;
double seek;
int pcur;
};
int mainstacksize = 32768;
static int audio;
static Meta *pl;
static int plnum;
static char *plraw;
static int plrawsize;
static int volume;
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 *ev;
static Mousectl *mctl;
static Keyboardctl *kctl;
static int colwidth[7];
static int mincolwidth[7];
static char *cols = "AatD";
static int *shuffle;
static Rectangle seekbar;
static int seekmx, newseekmx;
static double seekoff; /* ms */
static char *covers[] = {"folder", "cover", "Cover", "scans/CD", "Scans/Front", "Covers/Front"};
static char *menu3i[] = {
"theme",
"exit",
nil
};
static Menu menu3 = {
.item = menu3i,
};
#pragma varargck type "P" int
static int
positionfmt(Fmt *f)
{
char *s, tmp[16];
u64int sec;
s = tmp;
sec = va_arg(f->args, int);
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];
switch(c){
case Palbum: return m->album;
case Partist: return m->artist[0];
case Pdate: return m->date;
case Pduration: snprint(tmp, sizeof(tmp), "%8P", m->duration/1000); return tmp;
case Ptitle: return m->title;
case Ptrack: snprint(tmp, sizeof(tmp), "%4s", m->track); return m->track ? tmp : nil;
case Ppath: return m->path;
default: sysfatal("invalid column '%c'", c);
}
return nil;
}
static void
adjustcolumns(void)
{
int i, n, x, total, width;
if(mincolwidth[0] == 0){
for(i = 0; cols[i] != 0; i++)
mincolwidth[i] = 1;
for(n = 0; n < plnum; n++){
for(i = 0; cols[i] != 0; i++){
if((x = stringwidth(f, getcol(&pl[n], cols[i]))) > mincolwidth[i])
mincolwidth[i] = x;
}
}
}
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++;
}
}
for(i = 0; cols[i] != 0; i++){
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 Meta *
getmeta(int i)
{
return &pl[shuffle != nil ? shuffle[i] : i];
}
static void
redraw(int full)
{
Image *col;
Point p, sp;
Rectangle sel, r;
int i, j, left, right, scrollcenter;
char tmp[32];
lockdisplay(display);
scrollsz = (Dy(screen->r) - f->height - 4) / f->height - 1;
left = screen->r.min.x;
if(scrollsz < plnum) /* adjust for scrollbar */
left += Scrollwidth + 1;
if(full){
draw(screen, screen->r, colors[Dback].im, nil, ZP);
adjustcolumns();
if(scrollsz < plnum){ /* scrollbar */
p.x = sp.x = screen->r.min.x + Scrollwidth;
p.y = screen->r.min.y;
sp.y = screen->r.max.y;
line(screen, p, sp, Endsquare, Endsquare, 0, colors[Dfmed].im, ZP);
r = screen->r;
r.max.x = r.min.x + Scrollwidth - 1;
r.min.x += 1;
if(scroll < 1)
scrollcenter = 0;
else
scrollcenter = (Dy(screen->r)-Scrollheight*5/4)*scroll / (plnum - scrollsz);
r.min.y += scrollcenter + Scrollheight/4;
r.max.y = r.min.y + Scrollheight;
draw(screen, r, colors[Dfmed].im, nil, ZP);
}
p.x = sp.x = left;
p.y = 0;
sp.y = screen->r.max.y;
for(i = 0; cols[i+1] != 0; i++){
p.x += colwidth[i] + 4;
sp.x = p.x;
line(screen, p, sp, Endsquare, Endsquare, 0, colors[Dfmed].im, ZP);
p.x += 4;
}
sp.x = sp.y = 0;
p.x = left + 2;
p.y = screen->r.min.y + 2;
for(i = scroll; i < plnum; i++, p.y += f->height){
if(i < 0)
continue;
if(p.y > screen->r.max.y)
break;
if(pcur == i){
sel.min.x = left;
sel.min.y = p.y;
sel.max.x = screen->r.max.x;
sel.max.y = p.y + f->height;
draw(screen, sel, colors[Dbinv].im, nil, ZP);
col = colors[Dfinv].im;
}else{
col = colors[pcurplaying == i ? Dfhigh : Dfmed].im;
}
sel = screen->r;
p.x = left + 2 + 3;
for(j = 0; cols[j] != 0; j++){
sel.max.x = p.x + colwidth[j];
replclipr(screen, 0, sel);
string(screen, p, col, sp, f, getcol(getmeta(i), cols[j]));
p.x += colwidth[j] + 8;
}
replclipr(screen, 0, screen->r);
if(pcurplaying == i){
Point rightp, leftp;
leftp.y = rightp.y = p.y - 1;
leftp.x = left;
rightp.x = screen->r.max.x;
line(screen, leftp, rightp, 0, 0, 0, colors[Dfmed].im, sp);
leftp.y = rightp.y = p.y + f->height;
line(screen, leftp, rightp, 0, 0, 0, colors[Dfmed].im, sp);
}
}
}
vlong msec = 0;
if(pcurplaying >= 0){
msec = byteswritten*1000/Bps;
if(getmeta(pcurplaying)->duration != 0){
msec = MIN(msec, getmeta(pcurplaying)->duration);
snprint(tmp, sizeof(tmp), "%s%P/%P %d%%",
shuffle != nil ? "∫ " : "",
(int)(newseekmx >= 0 ? seekoff : msec)/1000,
getmeta(pcurplaying)->duration/1000,
volume);
seekmx = newseekmx;
}else{
snprint(tmp, sizeof(tmp), "%s%P %d%%",
shuffle != nil ? "∫ " : "",
(int)msec/1000,
volume);
}
}else
snprint(tmp, sizeof(tmp), "%s%d%%", shuffle != nil ? "∫ " : "", volume);
r = screen->r;
right = r.max.x - stringwidth(f, tmp) - 4;
r.min.x = left;
r.min.y = r.max.y - f->height - 4;
if(pcurplaying < 0 || getmeta(pcurplaying)->duration == 0)
r.min.x = right;
draw(screen, r, colors[Dblow].im, nil, ZP);
r.max.x = right;
p = addpt(Pt(right, r.min.y), Pt(2, 2));
string(screen, p, colors[Dfhigh].im, sp, f, tmp);
sel = r;
if(cover != nil && full){
r.max.x = r.min.x;
r.min.x = screen->r.max.x - cover->r.max.x - 8;
draw(screen, r, colors[Dblow].im, nil, ZP);
r = screen->r;
r.min.x = r.max.x - cover->r.max.x - 8;
r.min.y = r.max.y - cover->r.max.y - 8 - f->height - 4;
r.max.y = r.min.y + cover->r.max.y + 8;
draw(screen, r, colors[Dblow].im, nil, ZP);
draw(screen, insetrect(r, 4), cover, nil, ZP);
}
/* seek bar */
seekbar = ZR;
if(pcurplaying >= 0 && getmeta(pcurplaying)->duration != 0){
r = insetrect(sel, 3);
draw(screen, r, colors[Dfmed].im, nil, ZP);
r = insetrect(r, 1);
seekbar = r;
r.max.x = r.min.x + Dx(r) * (double)msec / (double)getmeta(pcurplaying)->duration;
draw(screen, r, colors[Dbinv].im, nil, ZP);
}
flushimage(display, 1);
unlockdisplay(display);
}
void
themechanged(void)
{
redraw(1);
}
static void
coverload(void *player_)
{
int p[2], pid, fd, i;
char *prog, *path, *s, tmp[32];
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 && m->imagereader == 0){
if(strcmp(m->imagefmt, "image/png") == 0)
prog = "png";
else if(strcmp(m->imagefmt, "image/jpeg") == 0)
prog = "jpg";
}
if(prog == nil){
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)) >= 0)
prog = "jpg";
free(s);
s = nil;
if(fd < 0 && (s = smprint("%s/%s.png", path, covers[i])) != nil && (fd = open(s, OREAD)) >= 0)
prog = "png";
free(s);
}
}
free(path);
}
if(prog == nil)
goto done;
if(fd < 0){
fd = open(m->path, OREAD);
seek(fd, m->imageoffset, 0);
}
pipe(p);
if((pid = rfork(RFPROC|RFFDG|RFREND|RFNOTEG)) == 0){
dup(fd, 0); close(fd);
dup(p[1], 1); close(p[1]);
dup(open("/dev/null", OWRITE), 2);
snprint(tmp, sizeof(tmp), "%s -9t | resample -x128", prog);
execl("/bin/rc", "rc", "-c", tmp, nil);
sysfatal("execl: %r");
}
close(fd);
close(p[1]);
if(pid > 0){
newcover = readimage(display, p[0], 1);
sendp(ch, newcover);
}
close(p[0]);
done:
if(pid < 0)
sendp(ch, nil);
chanclose(ch);
chanfree(ch);
if(pid >= 0)
postnote(PNGROUP, pid, "interrupt");
threadexits(nil);
}
static int
playerret(Player *player)
{
return recvul(player->ev) == Everror ? -1 : 0;
}
static void
stop(Player *player)
{
if(player == nil)
return;
if(player == playernext)
playernext = nil;
sendul(player->ctl, Cstop);
}
static void playerthread(void *player_);
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;
threadcreate(playerthread, player, 4096);
if(playerret(player) < 0)
return nil;
done:
if(pcur < plnum-1 && playernext == nil && loadnext)
playernext = newplayer(pcur+1, 0);
return player;
}
static int
start(Player *player)
{
if(player != nil)
sendul(player->ctl, Cstart);
return -1;
}
static void
playerthread(void *player_)
{
char *buf, cmd[64], seekpos[12], *fmt;
Player *player;
Ioproc *io;
Image *thiscover;
ulong c;
int p[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;
pid = -1;
restart:
cur = getmeta(player->pcur);
fmt = cur->filefmt;
fd = -1;
if(*fmt && (fd = open(cur->path, OREAD)) < 0){
fprint(2, "%r\n");
sendul(player->ev, Everror);
goto freeplayer;
}
pipe(p);
if((pid = rfork(RFPROC|RFFDG|RFREND|RFNOTEG)) == 0){
if(fd < 0)
fd = open("/dev/null", OREAD);
dup(fd, 0); close(fd);
dup(p[0], 1); close(p[0]);
dup(fd = open("/dev/null", OWRITE), 2); close(fd);
close(p[1]);
if(*fmt){
snprint(cmd, sizeof(cmd), "/bin/audio/%sdec", fmt);
snprint(seekpos, sizeof(seekpos), "%g", (double)boffset/Bps);
execl(cmd, cmd, "-s", seekpos, nil);
}else{
execl("/bin/play", "play", "-o", "/fd/1", cur->path, nil);
}
sysfatal("execl: %r");
}
if(pid < 0)
sysfatal("rfork: %r");
if(fd >= 0)
close(fd);
close(p[0]);
c = 0;
if(!noinit){
sendul(player->ev, Evready);
buf = malloc(Relbufsz);
if((io = ioproc()) == nil)
sysfatal("player: %r");
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;
boffset = iowrite(io, audio, buf, n);
noinit = 1;
}
byteswritten = boffsetlast = boffset;
pcurplaying = player->pcur;
if(c != Cseekrel)
redraw(1);
while(1){
n = ioreadn(io, p[1], buf, Relbufsz);
if(n <= 0)
break;
thiscover = nil;
if(player->img != nil && nbrecv(player->img, &thiscover) != 0){
freeimage(cover);
cover = thiscover;
redraw(1);
player->img = nil;
}
r = nbrecv(player->ctl, &c);
if(r < 0){
goto stop;
}else if(r != 0){
if(c == Ctoggle){
if(recv(player->ctl, &c) < 0 || c == Cstop)
goto stop;
}else if(c == Cseekrel){
boffset = MAX(0, boffset + player->seek*Bps);
n = 0;
break;
}else if(c == Cstop){
goto stop;
}
}
boffset += n;
byteswritten = boffset;
if(iowrite(io, audio, buf, n) != n)
fprint(2, "player: %r\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;
if(c != Cseekrel || boffset >= getmeta(pcurplaying)->duration/1000*Bps){
next:
playercurr = nil;
playercurr = newplayer((player->pcur+1) % plnum, 1);
start(playercurr);
goto stop;
}
goto restart;
}
stop:
if(player->img != nil)
freeimage(recvp(player->img));
freeplayer:
chanfree(player->ctl);
chanfree(player->ev);
if(pid >= 0)
postnote(PNGROUP, pid, "interrupt");
closeioproc(io);
if(p[1] >= 0)
close(p[1]);
if(player == playercurr)
playercurr = nil;
if(player == playernext)
playernext = nil;
free(buf);
free(player);
threadexits(nil);
}
static void
toggle(Player *player)
{
if(player != nil)
sendul(player->ctl, Ctoggle);
}
static void
seekrel(Player *player, double off)
{
if(player != nil){
player->seek = off;
sendul(player->ctl, Cseekrel);
}
}
static void
readplist(void)
{
Meta *m;
char *s, *e, *endrec;
int i, n, sz, alloc, tagsz, intval;
s = nil;
for(alloc = sz = 0;;){
alloc += 65536;
if((s = realloc(s, alloc)) == nil)
sysfatal("no memory");
for(n = 0; sz < alloc; sz += n){
n = readn(0, s+sz, alloc-sz);
if(n < 0)
sysfatal("%r");
if(n == 0)
break;
}
if(n == 0)
break;
}
plraw = s;
plrawsize = sz;
plraw[plrawsize-1] = 0;
if(sz < 4 || s[0] != '#' || s[1] != ' ' || !isdigit(s[2]) || (s = memchr(plraw, '\n', sz)) == nil)
sysfatal("invalid playlist");
s++; /* at the start of the first record */
plnum = atoi(plraw+2);
pl = calloc(plnum, sizeof(Meta));
for(i = 0; i < plnum; i++, s = endrec){
if(plraw+plrawsize < s+10)
sysfatal("truncated playlist");
if(s[0] != '#' || s[1] != ' ' || !isdigit(s[2]))
sysfatal("invalid record");
if((n = strtol(s+2, &e, 10)) < 0 || n > plnum)
sysfatal("invalid track index");
if(pl[n].path != nil)
sysfatal("duplicate track index");
s[-1] = 0;
sz = strtol(e, &s, 10);
*s++ = 0; /* skip '\n' */
if(s+sz > plraw+plrawsize)
sysfatal("truncated playlist");
s[sz-1] = 0; /* '\n'→'\0' to mark the end of the record */
endrec = s+sz;
m = &pl[n];
for(;;){
if(s[0] == Pimage){
m->imageoffset = strtol(s+2, &e, 10);
m->imagesize = strtol(e+1, &s, 10);
m->imagereader = strtol(s+1, &e, 10);
m->imagefmt = e + 1;
s = strchr(e+2, '\n') + 1;
}else if(s[0] == Pchannels || s[0] == Pduration || s[0] == Psamplerate){
intval = strtol(s+2, &e, 10);
if(s[0] == Pduration) m->duration = intval;
s = e + 1;
}else if(s[0] == Ppath){
m->path = s+2;
break; /* always the last one */
}else if(s[0] == Pfilefmt){
m->filefmt = s+2;
s = strchr(s+2, '\n') + 1;
}else{
tagsz = strtol(s+1, &e, 10);
if(e+tagsz >= plraw+plrawsize)
sysfatal("truncated playlist");
e++; /* point to tag value */
e[tagsz] = 0; /* '\n'→'\0' to mark the end of the tag value */
if(s[0] == Palbum) m->album = e;
else if(s[0] == Partist && m->numartist < Maxartist) m->artist[m->numartist++] = e;
else if(s[0] == Pdate) m->date = e;
else if(s[0] == Ptitle) m->title = e;
else if(s[0] == Pdate) m->date = e;
else if(s[0] == Ptrack) m->track = e;
else sysfatal("unknown tag type %c", s[0]);
s = e + tagsz + 1;
}
s[-1] = 0;
}
if(m->filefmt == nil)
sysfatal("old playlist format?\nplease re-run audio/mkplist");
}
}
static void
search(char d)
{
Meta *m;
static char buf[64];
static int sz;
int inc, i, a;
inc = (d == '/' || d == 'n') ? 1 : -1;
if(d == '/' || d == '?')
sz = enter(inc > 0 ? "forward:" : "backward:", buf, sizeof(buf), mctl, kctl, nil);
if(sz < 1)
return;
for(i = pcur+inc; i >= 0 && i < plnum; i += inc){
m = getmeta(i);
for(a = 0; a < m->numartist; a++){
if(cistrstr(m->artist[a], buf) != nil)
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;
}
if(i >= 0 && i < plnum){
pcur = i;
redraw(1);
}
}
static void
chvolume(int d)
{
int f, l, r, ol, or;
Biobuf b;
char *s, *a[4];
if((f = open("/dev/volume", ORDWR)) < 0)
return;
Binit(&b, f, OREAD);
l = r = 0;
for(; (s = Brdline(&b, '\n')) != nil;) {
if(strncmp(s, "master", 6) == 0 && tokenize(s, a, 3) == 3){
l = ol = atoi(a[1]);
r = or = atoi(a[2]);
for(;;){
l += d;
r += d;
fprint(f, "master %d %d\n", l, r);
Bseek(&b, 0, 0);
for(; (s = Brdline(&b, '\n')) != nil;){
if(strncmp(s, "master", 6) == 0 && tokenize(s, a, 3) == 3){
if(atoi(a[1]) == l && atoi(a[2]) == r)
goto end;
if(atoi(a[1]) != ol && atoi(a[2]) != or)
goto end;
if (l < 0 || r < 0 || l > 100 || r > 100)
goto end;
break;
}
}
}
}
}
end:
volume = (l+r)/2;
if(volume > 100)
volume = 100;
else if(volume < 0)
volume = 0;
Bterm(&b);
close(f);
}
static void
toggleshuffle(void)
{
int i, m, xi, a, c, pcurnew, pcurplayingnew;
if(shuffle == nil){
if(plnum < 2)
return;
m = plnum;
if(plnum < 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(plnum*sizeof(*shuffle));
xi = pcurplaying < 0 ? pcur : pcurplaying;
pcurplayingnew = -1;
pcurnew = 0;
for(i = 0; i < plnum;){
if(xi < plnum){
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 < plnum-1)
playernext = newplayer(pcur+1, 0);
}
static void
usage(void)
{
fprint(2, "usage: %s [-s] [-c aAdDtTp]\n", argv0);
sysfatal("usage");
}
void
threadmain(int argc, char **argv)
{
char tmp[256];
Rune key;
Mouse m;
Alt a[] =
{
{ nil, &m, CHANRCV },
{ nil, nil, CHANRCV },
{ nil, &key, CHANRCV },
{ nil, nil, CHANEND },
};
int fd, n, scrolling, oldpcur, oldbuttons, pnew, shuffled, themetid;
shuffled = 0;
ARGBEGIN{
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;
readplist();
if(plnum < 1){
fprint(2, "empty playlist\n");
sysfatal("empty");
}
if((audio = open("/dev/audio", OWRITE)) < 0)
sysfatal("audio: %r");
if(initdraw(nil, nil, "zuke") < 0)
sysfatal("initdraw: %r");
unlockdisplay(display);
if((mctl = initmouse(nil, screen)) == nil)
sysfatal("initmouse: %r");
if((kctl = initkeyboard(nil)) == nil)
sysfatal("initkeyboard: %r");
a[0].c = mctl->c;
a[1].c = mctl->resizec;
a[2].c = kctl->c;
f = display->defaultfont;
srand(time(0));
pcurplaying = -1;
chvolume(0);
fmtinstall('P', positionfmt);
threadsetname("zuke");
snprint(tmp, sizeof(tmp), "/proc/%d/ctl", getpid());
if((fd = open(tmp, OWRITE)) >= 0){
fprint(fd, "pri 13\n");
close(fd);
}
if(shuffled){
pcur = nrand(plnum);
toggleshuffle();
}
themeinit();
redraw(1);
oldbuttons = 0;
scrolling = 0;
themetid = -1;
for(;;){
ev:
oldpcur = pcur;
if(seekmx != newseekmx)
redraw(0);
switch(alt(a)){
case 0:
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-1;
}else{
newseekmx = -1;
}
if(m.buttons != 2)
scrolling = 0;
if(m.buttons == 0)
break;
if(m.buttons == 8){
scroll = MAX(scroll-scrollsz/4-1, 0);
redraw(1);
break;
}else if(m.buttons == 16){
scroll = MIN(scroll+scrollsz/4+1, plnum-scrollsz-1);
redraw(1);
break;
}
if(oldbuttons == 0 && !scrolling && ptinrect(m.xy, insetrect(seekbar, -4))){
if(ptinrect(m.xy, seekbar))
seekrel(playercurr, seekoff/1000.0 - byteswritten/Bps);
break;
}
n = (m.xy.y - screen->r.min.y)/f->height;
if(oldbuttons == 0 && m.xy.x <= screen->r.min.x+Scrollwidth){
if(m.buttons == 1){
scroll = MAX(0, scroll-n-1);
redraw(1);
break;
}else if(m.buttons == 4){
scroll = MIN(scroll+n+1, plnum-scrollsz-1);
redraw(1);
break;
}else if(m.buttons == 2){
scrolling = 1;
}
}
if(m.buttons == 4){
n = menuhit(3, mctl, &menu3, nil);
if(n == 0)
themetid = proccreate(themeproc, &audio, 4096);
else if(n == 1)
goto end;
goto ev;
}
if(scrolling){
if(scrollsz >= plnum)
break;
scroll = (m.xy.y - screen->r.min.y - Scrollheight/4)*(plnum-scrollsz) / (Dy(screen->r)-Scrollheight/2);
scroll = CLAMP(scroll, 0, plnum-scrollsz-1);
redraw(1);
}else if(m.buttons == 1 || m.buttons == 2){
pcur = scroll + n;
if(m.buttons == 2){
stop(playercurr);
playercurr = newplayer(pcur, 1);
start(playercurr);
}
}
break;
case 1: /* resize */
if(getwindow(display, Refnone) < 0)
sysfatal("getwindow: %r");
redraw(1);
break;
case 2:
switch(key){
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 = plnum-1;
break;
case Khome:
pcur = 0;
break;
case 10:
stop(playercurr);
playercurr = newplayer(pcur, 1);
start(playercurr);
break;
case 'q':
case Kdel:
stop(playercurr);
goto end;
case 'i':
case 'o':
if(pcur == pcurplaying)
oldpcur = -1;
pcur = pcurplaying;
break;
case 'b':
case '>':
if(playercurr == nil)
break;
pnew = pcurplaying;
if(++pnew >= plnum)
pnew = 0;
stop(playercurr);
playercurr = newplayer(pnew, 1);
start(playercurr);
redraw(1);
break;
case 'z':
case '<':
if(playercurr == nil)
break;
pnew = pcurplaying;
if(--pnew < 0)
pnew = plnum-1;
stop(playercurr);
playercurr = newplayer(pnew, 1);
start(playercurr);
redraw(1);
break;
case '-':
chvolume(-1);
redraw(0);
break;
case '+':
case '=':
chvolume(+1);
redraw(0);
break;
case 'v':
stop(playercurr);
playercurr = nil;
pcurplaying = -1;
freeimage(cover);
cover = nil;
redraw(1);
break;
case 's':
toggleshuffle();
redraw(1);
break;
case 'c':
case 'p':
toggle(playercurr);
break;
case '/':
case '?':
case 'n':
case 'N':
search(key);
break;
}
}
if(pcur != oldpcur){
pcur = CLAMP(pcur, 0, plnum-1);
if(pcur < scroll)
scroll = pcur;
else if(pcur > scroll + scrollsz)
scroll = pcur - scrollsz;
scroll = CLAMP(scroll, 0, plnum-scrollsz);
if(pcur != oldpcur)
redraw(1);
}
}
end:
threadint(themetid);
threadexitsall(nil);
}