ref: c2eaf5845f78e87c37c85447e4a0afc6443957fd
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, Cforwardfast, Cbackwardfast, Everror = 1, Evready, Bps = 44100*2*2, /* 44100KHz, stereo, u16 for a sample */ Seekbytes = Bps*10, /* 10 seconds */ Seekbytesfast = Bps*60, /* 1 minute */ Scrollwidth = 12, Scrollheight = 16, Relbufsz = Bps/5, /* 0.2 second */ }; static Meta *pl; static int plnum; static char *plraw; static int plrawsize; struct Player { Channel *ctl; Channel *ev; Channel *img; 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; static int colwidth[3]; static int mincolwidth[3]; #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 adjustcolumns(void) { int i, x, total; if(mincolwidth[0] == 0){ mincolwidth[0] = mincolwidth[1] = mincolwidth[2] = 1; for(i = 0; i < plnum; i++){ if((x = stringwidth(f, pl[i].artist[0])) > mincolwidth[0]) mincolwidth[0] = x; if((x = stringwidth(f, pl[i].album)) > mincolwidth[1]) mincolwidth[1] = x; if((x = stringwidth(f, pl[i].title)) > mincolwidth[2]) mincolwidth[2] = x; } } total = mincolwidth[0] + mincolwidth[1] + mincolwidth[2]; for(i = 0; i < nelem(mincolwidth); i++) colwidth[i] = (Dx(screen->r) - 8) * mincolwidth[i] / total; } static void redraw(Image *screen, int new) { Image *col; Point p, sp; Rectangle sel, r; int i, left, scrollcenter; char tmp[32]; if(entering) return; lockdisplay(display); if(new && getwindow(display, Refnone) < 0) sysfatal("getwindow: %r"); else draw(screen, screen->r, cola, nil, ZP); scrollsz = Dy(screen->r) / f->height - 1; adjustcolumns(); left = screen->r.min.x; if(scrollsz < plnum){ /* add a 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, colb, 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/2)*scroll / plnum; r.min.y += scrollcenter + Scrollheight/4; r.max.y = r.min.y + Scrollheight; draw(screen, r, colb, nil, ZP); left += Scrollwidth + 4; } p.x = sp.x = left + colwidth[0] + 4; p.y = 0; sp.y = screen->r.max.y; line(screen, p, sp, Endsquare, Endsquare, 0, colb, ZP); p.x = sp.x = left + colwidth[0] + 8 + colwidth[1] + 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 = 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, colb, nil, ZP); col = cola; }else{ col = colb; } sel = screen->r; r = screen->r; p.x = left + 2; sel.max.x = p.x + colwidth[0]; replclipr(screen, 0, sel); string(screen, p, col, sp, f, pl[i].artist[0]); p.x += colwidth[0] + 8; sel.min.x = p.x; sel.max.x = p.x + colwidth[1]; replclipr(screen, 0, sel); string(screen, p, col, sp, f, pl[i].album); p.x += colwidth[1] + 8; sel.min.x = p.x; sel.max.x = p.x + colwidth[2]; replclipr(screen, 0, sel); string(screen, p, col, sp, f, pl[i].title); replclipr(screen, 0, 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, colb, sp); leftp.y = rightp.y = p.y + f->height; line(screen, leftp, rightp, 0, 0, 0, colb, sp); } } 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 - f->height - 4; 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); unlockdisplay(display); } static void coverload(void *player_) { int p[2], pid, fd; char *prog, *path, *s, tmp[32]; Meta *m; Channel *ch; Player *player; Image *newcover; threadsetname("cover"); player = player_; m = &pl[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; if((s = smprint("%s/cover.jpg", path)) != nil && (fd = open(s, OREAD)) >= 0) prog = "jpg"; free(s); s = nil; if(fd < 0 && (s = smprint("%s/cover.png", path)) != 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 Player *playernext; static Player *playercurr; 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, mainstacksize); 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; Player *player; Ioproc *io; Image *thiscover; ulong c; int p[2], fd, pid, n, got, noinit, trycoverload; u64int bytesfrom; threadsetname("player"); player = player_; noinit = 0; bytesfrom = 0; c = 0; buf = nil; trycoverload = 1; io = nil; pid = -1; restart: if((fd = open(pl[player->pcur].path, OREAD)) < 0){ fprint(2, "%r\n"); sendul(player->ev, Everror); goto freeplayer; } pipe(p); if((pid = rfork(RFPROC|RFFDG|RFREND|RFNOTEG)) == 0){ dup(p[0], 1); close(p[0]); dup(fd, 0); close(fd); close(p[1]); dup(open("/dev/null", OWRITE), 2); execl("/bin/play", "play", "-o", "/fd/1", nil); sysfatal("execl: %r"); } if(pid < 0) sysfatal("rfork: %r"); close(fd); close(p[0]); byteswritten = 0; if(!noinit){ sendul(player->ev, Evready); buf = malloc(Relbufsz); io = ioproc(); for(c = 0, got = 0; got < Relbufsz; got += n){ if((c = nbrecvul(player->ctl)) != 0) break; n = ioread(io, p[1], buf+got, Relbufsz-got); if(n < 1) break; } if(c == 0) c = recvul(player->ctl); if(c != Cstart) goto freeplayer; iowrite(io, audio, buf, got); byteswritten = got; bytesfrom = 0; c = 0; noinit = 1; } pcurplaying = player->pcur; if(c != Cbackward && c != Cbackwardfast) redraw(screen, 0); while(1){ n = Relbufsz; if(bytesfrom > byteswritten && n > bytesfrom-byteswritten) n = bytesfrom-byteswritten; n = ioread(io, p[1], buf, n); if(n < 1) break; thiscover = nil; if(player->img != nil && nbrecv(player->img, &thiscover) != 0){ freeimage(cover); cover = thiscover; redraw(screen, 0); player->img = nil; } c = nbrecvul(player->ctl); if(c == Cstop || c == -1) goto stop; if(c == Ctoggle){ c = recvul(player->ctl); if(c == Cstop) goto stop; }else if(c == Cforward){ bytesfrom = byteswritten + Seekbytes; }else if(c == Cforwardfast){ bytesfrom = byteswritten + Seekbytesfast; }else if(c == Cbackward){ /* to seek backwards we need to restart playback */ bytesfrom = byteswritten >= Seekbytes ? byteswritten - Seekbytes : 0; n = 0; /* not an error */ break; }else if(c == Cbackwardfast){ bytesfrom = byteswritten >= Seekbytesfast ? byteswritten - Seekbytesfast : 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; } if(trycoverload && byteswritten >= Bps){ player->img = chancreate(sizeof(Image*), 0); proccreate(coverload, player, 4096); trycoverload = 0; } } byteswritten += n; if(bytesfrom == byteswritten || (byteswritten/Bps > (byteswritten-n)/Bps)) redraw(screen, 0); } if(n == 0){ /* seeking backwards or end of the song */ close(p[1]); if(c != Cbackward && c != Cbackwardfast){ 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: if(player == playercurr) playercurr = nil; if(player == playernext) playernext = nil; chanclose(player->ctl); chanclose(player->ev); chanfree(player->ctl); chanfree(player->ev); close(p[1]); closeioproc(io); free(buf); free(player); if(pid >= 0) postnote(PNGROUP, pid, "interrupt"); threadexits(nil); } 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 backwardfast(Player *player) { if(player != nil) sendul(player->ctl, Cbackwardfast); } static void forwardfast(Player *player) { if(player != nil) sendul(player->ctl, Cforwardfast); } 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])) 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{ 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) { char tmp[256]; Point lastclick; Rune key; Mouse m; Alt a[] = { { nil, &m, CHANRCV }, { nil, nil, CHANRCV }, { nil, &key, CHANRCV }, { nil, nil, CHANEND }, }; int fd, scrolling, oldscroll, oldpcur, oldscrolling, usingscrollbar; USED(argc); USED(argv); audio = open("/dev/audio", OWRITE); if(audio < 0) sysfatal("audio: %r"); readplist(); if(plnum < 1){ fprint(2, "empty playlist\n"); sysfatal("empty"); } 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; cola = display->white; colb = display->black; srand(time(0)); pcurplaying = -1; scrolling = oldscroll = usingscrollbar = 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); } redraw(screen, 1); for(;;){ oldpcur = pcur; switch(alt(a)){ case 0: oldscrolling = scrolling; scrolling = m.buttons & 2; if(!oldscrolling && (m.buttons & 2) != 0){ usingscrollbar = m.xy.x < screen->r.min.x+Scrollwidth; lastclick = m.xy; oldscroll = scroll; } if(m.buttons == 0) break; if(scrolling){ if(scrollsz >= plnum) break; if(usingscrollbar) scroll = (m.xy.y - screen->r.min.y - Scrollheight/4)*plnum / (Dy(screen->r)-Scrollheight/2); else scroll = oldscroll + (lastclick.y - m.xy.y) / f->height; if(scroll > plnum-scrollsz-1) scroll = plnum-scrollsz-1; if(scroll < 0) scroll = 0; redraw(screen, 0); }else{ pcur = scroll + (m.xy.y - screen->r.min.y)/f->height; if(m.buttons == 4){ stop(playercurr); playercurr = newplayer(pcur, 1); start(playercurr); } } break; case 1: /* resize */ redraw(screen, 1); break; case 2: switch(key){ case Kleft: backward(playercurr); break; case Kright: forward(playercurr); break; case ',': backwardfast(playercurr); break; case '.': forwardfast(playercurr); 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 'o': pcur = pcurplaying; break; case '>': if(playercurr == nil) break; pcur = pcurplaying; if(++pcur >= plnum) pcur = 0; stop(playercurr); playercurr = newplayer(pcur, 1); start(playercurr); break; case '<': if(playercurr == nil) break; pcur = pcurplaying; if(--pcur < 0) pcur = plnum-1; stop(playercurr); playercurr = newplayer(pcur, 1); start(playercurr); break; case 's': stop(playercurr); playercurr = nil; pcurplaying = -1; redraw(screen, 1); break; case 'p': toggle(playercurr); 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; if(scroll < 0) scroll = 0; if(pcur != oldpcur) redraw(screen, 0); } } end: closemouse(mctl); closekeyboard(kctl); threadexitsall(nil); }