ref: 4c5bf2f861bb45cb1c2fbce54217c06948c1c8ab
dir: /zuke.c/
#include <u.h> #include <libc.h> #include <draw.h> #include <event.h> #include <keyboard.h> #include <bio.h> #include <thread.h> #include <ctype.h> #include "plist.h" typedef struct Player Player; enum { Cstart = 1, Cstop, Ctoggle, }; static Meta *pl; static int plnum; static char *plraw; static int plrawsize; struct Player { Channel *ctl, *ev; int pcur; }; int mainstacksize = 32768; static int audio; static int pcur, pcurplaying; static int scroll, scrollsz; static Image *cola, *colb; static Font *f; static Channel *ev; static void redraw(Image *screen, int new) { char tmp[256]; Point p, sp; int i; sp.x = sp.y = 0; if(new) draw(screen, screen->r, cola, nil, ZP); p.x = screen->r.min.x + 2; p.y = screen->r.min.y + 2; scrollsz = Dy(screen->r) / f->height - 1; for(i = scroll; i < plnum; i++){ if(p.y > screen->r.max.y) break; if(pcurplaying == i){ Point right, left; left.y = right.y = p.y - 1; left.x = p.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); } snprint(tmp, sizeof(tmp), "%s - %s - %s", pl[i].artist[0], pl[i].album, pl[i].title); if(pcur == i){ Rectangle sel; sel = screen->r; sel.min.y = p.y; sel.max.y = p.y + f->height; draw(screen, sel, colb, nil, ZP); string(screen, p, cola, sp, f, tmp); } else string(screen, p, colb, sp, f, tmp); p.y += f->height; } flushimage(display, 1); } static void playerthread(void *player_) { char *buf; Ioproc *io; Player *player; ulong c; int p[2], pid, n, noinit; player = player_; noinit = 0; io = nil; buf = nil; next: pipe(p); if((pid = rfork(RFPROC|RFFDG|RFREND|RFNOTEG)) == 0){ dup(p[0], 1); close(p[0]); close(p[1]); close(0); close(2); execl("/bin/play", "/bin/play", "-o", "/fd/1", pl[player->pcur].path, nil); sysfatal("execl: %r"); } if(pid < 0) sysfatal("rfork: %r"); close(p[0]); if(!noinit){ threadsetname("player"); sendp(player->ev, nil); /* "ready to start" */ c = recvul(player->ctl); if(c != Cstart) return; io = ioproc(); buf = malloc(8192); } pcurplaying = player->pcur; redraw(screen, 1); while((n = ioread(io, p[1], buf, 8192)) > 0){ c = nbrecvul(player->ctl); if(c == Cstop) goto stop; if(c == Ctoggle){ c = recvul(player->ctl); if(c == Cstop) goto stop; } if(iowrite(io, audio, buf, n) != n) break; } if(n == 0){ /* end of the song, need to skip to the next one */ close(p[1]); player->pcur++; noinit = 1; goto next; } stop: close(p[1]); postnote(PNGROUP, pid, "interrupt"); free(buf); closeioproc(io); sendp(player->ev, nil); /* "finished" */ } static Player * newplayer(int pcur) { Player *player; player = malloc(sizeof(*player)); player->ctl = chancreate(sizeof(ulong), 0); player->ev = chancreate(sizeof(void*), 0); player->pcur = pcur; proccreate(playerthread, player, mainstacksize); recvp(player->ev); /* wait for it to become ready */ return player; } static void stop(Player *player) { if(player == nil) return; sendul(player->ctl, Cstop); recvp(player->ev); /* wait until it dies */ chanclose(player->ev); chanclose(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 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"); sz = strtol(e, &s, 10); s++; /* 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); s = e + 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; } } } } static void usage(void) { fprint(2, "usage: zuke [-b]\n"); exits("usage"); } void eresized(int new) { if(getwindow(display, Refnone) < 0) sysfatal("can't reattach to window: %r"); redraw(screen, new); } void threadmain(int argc, char **argv) { int inv; Player *player; 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 failed"); f = display->defaultfont; cola = inv ? display->black : display->white; colb = inv ? display->white : display->black; einit(Emouse | Ekeyboard); srand(time(0)); pcurplaying = -1; player = nil; threadsetname("zuke"); redraw(screen, 1); for(;;){ int oldpcur, key; Event e; oldpcur = pcur; key = event(&e); if(key == Emouse){ if(e.mouse.buttons > 0){ pcur = scroll + (e.mouse.xy.y - screen->r.min.y)/f->height; if(e.mouse.buttons == 4){ stop(player); player = newplayer(pcur); start(player); } } } else if(key == Ekeyboard){ if(e.kbdc == Kup){ pcur--; }else if(e.kbdc == Kpgup){ pcur -= scrollsz; }else if(e.kbdc == Kdown){ pcur++; }else if(e.kbdc == Kpgdown){ pcur += scrollsz; }else if(e.kbdc == Kend){ pcur = plnum-1; }else if(e.kbdc == Khome){ pcur = 0; }else if(e.kbdc == 0x0a){ stop(player); player = newplayer(pcur); start(player); }else if(e.kbdc == 'q' || e.kbdc == Kesc){ stop(player); break; }else if(e.kbdc == 'o'){ pcur = pcurplaying; }else if(e.kbdc == '>' && pcurplaying >= 0){ pcur = pcurplaying; if(++pcur >= plnum) pcur = 0; stop(player); player = newplayer(pcur); start(player); }else if(e.kbdc == '<' && pcurplaying >= 0){ pcur = pcurplaying; if(--pcur < 0) pcur = plnum-1; stop(player); player = newplayer(pcur); start(player); }else if(e.kbdc == 's' && player != nil){ stop(player); player = nil; }else if(e.kbdc == 'p'){ toggle(player); } } 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, 1); } } threadexitsall(nil); }