ref: 17b3ea38d2c957d182b9f4cba318243c098419b2
dir: /zuke.c/
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <mouse.h>
#include <keyboard.h>
#include <thread.h>
#include <ctype.h>
#include "plist.h"
typedef struct Player Player;
enum
{
Cstart = 1,
Cstop,
Ctoggle,
Cforward,
Cbackward,
Relbufsz = 32768,
Bps = 44100*2*2, /* 44100KHz, stereo, u16 for a sample */
Seekbytes = Bps*10, /* 10 seconds */
};
static Meta *pl;
static int plnum;
static char *plraw;
static int plrawsize;
struct Player
{
Channel *ctl;
int pcur;
};
int mainstacksize = 32768;
static int audio;
static u64int byteswritten;
static int pcur, pcurplaying;
static int scroll, scrollsz;
static Image *cola, *colb;
static Font *f;
static Image *cover;
static Channel *ev;
static Mousectl *mctl;
static Keyboardctl *kctl;
static int entering;
#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 void
redraw(Image *screen, int new)
{
Image *col;
Point p, sp;
Rectangle sel, r;
int i, colwidth;
char tmp[32];
if(entering)
return;
if(new && getwindow(display, Refnone) < 0)
sysfatal("getwindow: %r");
else
draw(screen, screen->r, cola, nil, ZP);
scrollsz = Dy(screen->r) / f->height - 1;
colwidth = (Dx(screen->r) - 8) / 3; /* 3 = artist, album, title */
p.x = sp.x = screen->r.min.x + colwidth + 4;
p.y = 0;
sp.y = screen->r.max.y;
line(screen, p, sp, Endsquare, Endsquare, 0, colb, ZP);
p.x = sp.x = screen->r.min.x + colwidth + 8 + colwidth + 4;
p.y = 0;
sp.y = screen->r.max.y;
line(screen, p, sp, Endsquare, Endsquare, 0, colb, ZP);
sp.x = sp.y = 0;
p.x = screen->r.min.x + 2;
p.y = screen->r.min.y + 2;
for(i = scroll; i < plnum; i++){
if(p.y > screen->r.max.y)
break;
if(pcur == i){
sel = screen->r;
sel.min.y = p.y;
sel.max.y = p.y + f->height;
draw(screen, sel, colb, nil, ZP);
col = cola;
}else{
col = colb;
}
sel = screen->r;
r = screen->r;
p.x = screen->r.min.x + 2;
sel.max.x = p.x + colwidth;
replclipr(screen, 0, sel);
string(screen, p, col, sp, f, pl[i].artist[0]);
p.x += colwidth + 8;
sel.min.x = p.x;
sel.max.x = p.x + colwidth;
replclipr(screen, 0, sel);
string(screen, p, col, sp, f, pl[i].album);
p.x += colwidth + 8;
sel.min.x = p.x;
sel.max.x = p.x + colwidth;
replclipr(screen, 0, sel);
string(screen, p, col, sp, f, pl[i].title);
replclipr(screen, 0, r);
if(pcurplaying == i){
Point right, left;
left.y = right.y = p.y - 1;
left.x = screen->r.min.x;
right.x = screen->r.max.x;
line(screen, left, right, 0, 0, 0, colb, sp);
left.y = right.y = p.y + f->height;
line(screen, left, right, 0, 0, 0, colb, sp);
}
p.y += f->height;
}
if(cover != nil){
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;
draw(screen, r, display->black, nil, ZP);
r.min.x += 4;
r.min.y += 4;
r.max.x -= 4;
r.max.y -= 4;
draw(screen, r, cover, nil, ZP);
}
if(pcurplaying >= 0){
snprint(tmp, sizeof(tmp), "%P/%P", (int)(byteswritten/Bps), pl[pcurplaying].duration/1000);
r = screen->r;
r.min.x = r.max.x - stringwidth(f, tmp) - 4;
r.min.y = r.max.y - f->height - 4;
draw(screen, r, display->black, nil, ZP);
r.min.x += 2;
r.min.y += 2;
string(screen, r.min, cola, sp, f, tmp);
}
flushimage(display, 1);
}
static void
coverload(void *m_)
{
int p[2], p2[2], fd, pid, pid2;
char *prog;
Meta *m;
Ioproc *io;
threadsetname("cover");
m = m_;
freeimage(cover);
cover = nil;
redraw(screen, 1);
if(m->imagefmt == nil || m->imagereader != 0)
return;
if(strcmp(m->imagefmt, "image/png") == 0)
prog = "/bin/png";
else if(strcmp(m->imagefmt, "image/jpeg") == 0)
prog = "/bin/jpg";
else
return;
io = ioproc();
if((fd = ioopen(io, m->path, OREAD)) < 0){
closeioproc(io);
return;
}
if(seek(fd, m->imageoffset, 0) != m->imageoffset){
ioclose(io, fd);
closeioproc(io);
return;
}
pipe(p);
if((pid = rfork(RFPROC|RFFDG|RFREND|RFNOTEG)) == 0){
dup(fd, 0); close(fd);
dup(p[0], 1); close(p[0]);
close(p[1]);
execl(prog, prog, "-9t", nil);
sysfatal("execl: %r");
}
ioclose(io, fd);
close(p[0]);
pipe(p2);
if((pid2 = rfork(RFPROC|RFFDG|RFREND|RFNOTEG)) == 0){
dup(p[1], 0); close(p[1]);
dup(p2[0], 1); close(p2[0]);
close(p2[1]);
execl("/bin/resize", "/bin/resize", "-x128", nil);
sysfatal("execl: %r");
}
close(p[1]);
close(p2[0]);
if(pid > 0 && pid2 > 0){
cover = readimage(display, p2[1], 0);
redraw(screen, 1);
}
postnote(PNGROUP, pid, "interrupt");
postnote(PNGROUP, pid2, "interrupt");
close(p2[1]);
closeioproc(io);
}
static void
playerthread(void *player_)
{
char *buf;
Player *player;
Ioproc *io;
ulong c;
int p[2], fd, pid, n, noinit;
u64int bytesfrom;
player = player_;
noinit = 0;
bytesfrom = 0;
c = 0;
buf = nil;
restart:
pipe(p);
if((pid = rfork(RFPROC|RFFDG|RFREND|RFNOTEG)) == 0){
dup(p[0], 1);
if((fd = open(pl[player->pcur].path, OREAD)) < 0){
fprint(2, "%r\n");
exits(nil);
}
dup(fd, 0);
close(fd);
close(p[0]);
close(p[1]);
close(2);
execl("/bin/play", "/bin/play", "-o", "/fd/1", nil);
sysfatal("execl: %r");
}
if(pid < 0)
sysfatal("rfork: %r");
close(p[0]);
if(!noinit){
threadsetname("player");
c = recvul(player->ctl);
if(c != Cstart)
return;
buf = malloc(Relbufsz);
bytesfrom = 0;
c = 0;
noinit = 1;
}
byteswritten = 0;
pcurplaying = player->pcur;
if(c != Cbackward)
redraw(screen, 1);
/*proccreate(coverload, &pl[pcurplaying], 4096);*/
io = ioproc();
while(1){
n = Relbufsz;
if(bytesfrom > byteswritten && n > bytesfrom-byteswritten)
n = bytesfrom-byteswritten;
n = ioread(io, p[1], buf, n);
if(n < 1)
break;
c = nbrecvul(player->ctl);
if(c == Cstop)
goto stop;
if(c == Ctoggle){
c = recvul(player->ctl);
if(c == Cstop)
goto stop;
}else if(c == Cforward){
bytesfrom = byteswritten + Seekbytes;
}else if(c == Cbackward){ /* to seek backwards we need to restart playback */
bytesfrom = byteswritten >= Seekbytes ? byteswritten - Seekbytes : 0;
n = 0; /* not an error */
break;
}
c = 0;
if(bytesfrom <= byteswritten){
if(iowrite(io, audio, buf, n) != n){
fprint(2, "failed to write %d bytes: %r\n", n);
break;
}
}
byteswritten += n;
if(bytesfrom == byteswritten || (bytesfrom < byteswritten && byteswritten/Bps > (byteswritten-n)/Bps))
redraw(screen, 0);
}
closeioproc(io);
io = nil;
if(n == 0){ /* seeking backwards or end of the song */
close(p[1]);
postnote(PNGROUP, pid, "interrupt");
waitpid();
if(c != Cbackward){
bytesfrom = 0;
player->pcur++;
}
goto restart;
}
stop:
closeioproc(io);
close(p[1]);
postnote(PNGROUP, pid, "interrupt");
waitpid();
free(buf);
}
static Player *
newplayer(int pcur)
{
Player *player;
player = malloc(sizeof(*player));
player->ctl = chancreate(sizeof(ulong), 0);
player->pcur = pcur;
threadcreate(playerthread, player, mainstacksize);
return player;
}
static void
stop(Player *player)
{
if(player == nil)
return;
sendul(player->ctl, Cstop);
chanfree(player->ctl);
free(player);
}
static void
start(Player *player)
{
if(player != nil)
sendul(player->ctl, Cstart);
}
static void
toggle(Player *player)
{
if(player != nil)
sendul(player->ctl, Ctoggle);
}
static void
backward(Player *player)
{
if(player != nil)
sendul(player->ctl, Cbackward);
}
static void
forward(Player *player)
{
if(player != nil)
sendul(player->ctl, Cforward);
}
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 = read(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]) || strtol(s+2, &e, 10) != i)
sysfatal("invalid record");
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[i];
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] == Pchannels) m->channels = intval;
else if(s[0] == Pduration) m->duration = intval;
else if(s[0] == Psamplerate) m->samplerate = intval;
s = e + 1;
}else if(s[0] == Ppath){
m->path = s+2;
break; /* always the last one */
}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;
}
}
}
static void
search(char d)
{
char *s, *snext;
static char buf[48];
static int sz;
int inc;
inc = (d == '/' || d == 'n') ? 1 : -1;
if(d == '/' || d == '?'){
entering = 1;
sz = enter(inc > 0 ? "forward:" : "backward:", buf, sizeof(buf), mctl, kctl, nil);
entering = 0;
}
if(sz < 1 || (inc > 0 && pcur >= plnum-1) || (inc < 0 && pcur < 1))
return;
s = pl[pcur + (inc > 0 ? 0 : -1)].path;
s += strlen(s) + 1;
for(; s > plraw && s < plraw+plrawsize-sz; s += inc){
if(cistrncmp(s, buf, sz) != 0)
continue;
snext = s;
for(; s != plraw && *s; s--);
if(s == plraw || (s[1] != Partist && s[1] != Palbum && s[1] != Ptitle && s[1] != Pdate)){
if(inc > 0)
s = snext;
continue;
}
for(s--; s != plraw; s--){
if(memcmp(s, "\0# ", 3) == 0 && isdigit(s[3])){
pcur = atoi(s+3);
redraw(screen, 1);
return;
}
}
break;
}
}
static void
usage(void)
{
fprint(2, "usage: zuke [-b]\n");
exits("usage");
}
void
threadmain(int argc, char **argv)
{
int inv, fd, oldpcur;
Player *player;
Rune key;
Mouse m;
Alt a[] =
{
{ nil, &m, CHANRCV },
{ nil, nil, CHANRCV },
{ nil, &key, CHANRCV },
{ nil, nil, CHANEND },
};
char tmp[256];
inv = 0;
ARGBEGIN{
case 'b':
inv = 1;
break;
default:
usage();
}ARGEND
audio = open("/dev/audio", OWRITE);
if(audio < 0)
sysfatal("audio: %r");
readplist();
if(plnum < 1){
fprint(2, "empty playlist\n");
sysfatal("empty");
}
if(initdraw(0, 0, "zuke") < 0)
sysfatal("initdraw: %r");
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;
cola = inv ? display->black : display->white;
colb = inv ? display->white : display->black;
srand(time(0));
pcurplaying = -1;
player = nil;
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);
}
redraw(screen, 1);
for(;;){
oldpcur = pcur;
switch(alt(a)){
case 0:
if(m.buttons > 0){
pcur = scroll + (m.xy.y - screen->r.min.y)/f->height;
if(m.buttons == 4){
stop(player);
player = newplayer(pcur);
start(player);
}
}
break;
case 1:
redraw(screen, 1);
break;
case 2:
switch(key){
case Kleft:
backward(player);
break;
case Kright:
forward(player);
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(player);
player = newplayer(pcur);
start(player);
break;
case 'q': case Kdel:
stop(player);
goto end;
case 'o':
pcur = pcurplaying;
break;
case '>':
if(player == nil)
break;
pcur = pcurplaying;
if(++pcur >= plnum)
pcur = 0;
stop(player);
player = newplayer(pcur);
start(player);
break;
case '<':
if(player == nil)
break;
pcur = pcurplaying;
if(--pcur < 0)
pcur = plnum-1;
stop(player);
player = newplayer(pcur);
start(player);
break;
case 's':
stop(player);
player = nil;
pcurplaying = -1;
redraw(screen, 1);
break;
case 'p':
toggle(player);
break;
case '/': case '?': case 'n': case 'N':
search(key);
break;
}
}
if(pcur != oldpcur){
if(pcur < 0)
pcur = 0;
else if(pcur >= plnum)
pcur = plnum - 1;
if(pcur < scroll)
scroll = pcur;
else if(pcur > scroll + scrollsz)
scroll = pcur - scrollsz;
if(scroll > plnum - scrollsz)
scroll = plnum - scrollsz;
else if(scroll < 0)
scroll = 0;
if(pcur != oldpcur)
redraw(screen, 0);
}
}
end:
closemouse(mctl);
closekeyboard(kctl);
threadexitsall(nil);
}