ref: 56d78c5f36adc9dd6dd6f5fa9063f795888e5de6
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 */ Scrollwidth = 12, }; 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, left, scrollcenter; 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 */ 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)-8)*scroll / plnum; r.min.y += scrollcenter + 4; r.max.y = r.min.y + 16; draw(screen, r, colb, nil, ZP); left += Scrollwidth + 4; } p.x = sp.x = left + colwidth + 4; p.y = 0; sp.y = screen->r.max.y; line(screen, p, sp, Endsquare, Endsquare, 0, cola, ZP); p.x = sp.x = left + 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 = 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; 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 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; 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, scrolling, oldscroll, oldpcur, oldscrolling, usingscrollbar; Point lastclick; 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; scrolling = oldscroll = usingscrollbar = 0; 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: 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)*plnum / (Dy(screen->r)-8); else scroll = oldscroll + (lastclick.y - m.xy.y) / f->height; if(scroll < 0) scroll = 0; else if(scroll > plnum-scrollsz-1) scroll = plnum-scrollsz-1; redraw(screen, 0); }else{ 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); }