ref: 940115935451666e84f3e972a4fb4a7972b57d91
dir: /zuke.c/
#include <u.h> #include <libc.h> #include <bio.h> #include <thread.h> #include <draw.h> #include <mouse.h> #include <keyboard.h> #include <plumb.h> #include <ctype.h> #include "plist.h" #include "theme.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; typedef struct Playlist Playlist; enum { Cstart = 1, Cstop, Ctoggle, Cseekrel, Everror = 1, Evready, Seek = 10, /* 10 seconds */ Seekfast = 60, /* a minute */ Bps = 44100*2*2, /* 44100KHz, stereo, s16 for a sample */ Relbufsz = Bps/2, /* 0.5s */ }; struct Player { Channel *ctl; Channel *ev; Channel *img; double seek; int pcur; }; struct Playlist { Meta *m; int n; char *raw; int rawsz; }; int mainstacksize = 32768; static int debug; static int audio = -1; static int volume; static int pnotifies; static Playlist *pl; 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 *playc; 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 = -1; static double seekoff; /* ms */ static Lock audiolock; static int audioerr = 0; static Biobuf out; static char *covers[] = { "art", "folder", "cover", "Cover", "scans/CD", "Scans/Front", "Covers/Front" }; static int Scrollwidth; static int Scrollheight; static int Coversz; static char *menu3i[] = { "theme", "exit", nil }; static Menu menu3 = { .item = menu3i, }; static void audioon(void) { lock(&audiolock); if(audio < 0 && (audio = open("/dev/audio", OWRITE|OCEXEC)) < 0 && audioerr == 0){ fprint(2, "%r\n"); audioerr = 1; } unlock(&audiolock); } static void audiooff(void) { lock(&audiolock); close(audio); audio = -1; audioerr = 0; unlock(&audiolock); } #pragma varargck type "P" uvlong 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 Ptitle: return m->title; case Ptrack: snprint(tmp, sizeof(tmp), "%4s", m->track); return m->track ? tmp : nil; case Ppath: return m->path; case Pduration: tmp[0] = 0; if(m->duration > 0) snprint(tmp, sizeof(tmp), "%8P", m->duration/1000); return tmp; 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 < pl->n; n++){ for(i = 0; cols[i] != 0; i++){ if((x = stringwidth(f, getcol(pl->m+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->m[shuffle != nil ? shuffle[i] : i]; } static void updatescrollsz(void) { scrollsz = (Dy(screen->r) - f->height - 4) / f->height - 1; } static void redraw(int full) { Image *col; Point p, sp; Rectangle sel, r; int i, j, left, right, scrollcenter, w; uvlong dur, msec; char tmp[32]; lockdisplay(display); updatescrollsz(); left = screen->r.min.x; if(scrollsz < pl->n) /* adjust for scrollbar */ left += Scrollwidth + 1; if(full){ draw(screen, screen->r, colors[Dback].im, nil, ZP); yield(); adjustcolumns(); if(scrollsz < pl->n){ /* 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[Dflow].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 / (pl->n - scrollsz); r.min.y += scrollcenter + Scrollheight/4; r.max.y = r.min.y + Scrollheight; draw(screen, r, colors[Dblow].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[Dflow].im, ZP); p.x += 4; } sp.x = sp.y = 0; p.x = left + 2; p.y = screen->r.min.y + 2; yield(); for(i = scroll; i < pl->n; 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[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[Dflow].im, sp); leftp.y = rightp.y = p.y + f->height; line(screen, leftp, rightp, 0, 0, 0, colors[Dflow].im, sp); } yield(); } } msec = 0; dur = getmeta(pcurplaying)->duration; if(pcurplaying >= 0){ msec = byteswritten*1000/Bps; if(dur > 0){ snprint(tmp, sizeof(tmp), "%s%P/%P 100%%", shuffle != nil ? "∫ " : "", dur/1000, dur/1000); w = stringwidth(f, tmp); msec = MIN(msec, dur); snprint(tmp, sizeof(tmp), "%s%P/%P %d%%", shuffle != nil ? "∫ " : "", (uvlong)(newseekmx >= 0 ? seekoff : msec)/1000, dur/1000, volume); }else{ snprint(tmp, sizeof(tmp), "%s%P %d%%", shuffle != nil ? "∫ " : "", msec/1000, 100); w = stringwidth(f, tmp); snprint(tmp, sizeof(tmp), "%s%P %d%%", shuffle != nil ? "∫ " : "", msec/1000, volume); } }else{ snprint(tmp, sizeof(tmp), "%s%d%%", shuffle != nil ? "∫ " : "", 100); w = stringwidth(f, tmp); snprint(tmp, sizeof(tmp), "%s%d%%", shuffle != nil ? "∫ " : "", volume); } r = screen->r; right = r.max.x - w - 4; r.min.x = left; r.min.y = r.max.y - f->height - 4; if(pcurplaying < 0 || dur == 0) r.min.x = right; draw(screen, r, colors[Dblow].im, nil, ZP); p = addpt(Pt(r.max.x-stringwidth(f, tmp)-4, r.min.y), Pt(2, 2)); r.max.x = right; string(screen, p, colors[Dfhigh].im, sp, f, tmp); sel = r; yield(); 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 && dur > 0){ r = insetrect(sel, 3); draw(screen, r, colors[Dback].im, nil, ZP); seekbar = r; r.max.x = r.min.x + Dx(r) * (double)msec / (double)dur; draw(screen, r, colors[Dbmed].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|RFNOTEG|RFCENVG|RFNOWAIT)) == 0){ dup(fd, 0); close(fd); dup(p[1], 1); close(p[1]); if(!debug){ dup(fd = open("/dev/null", OWRITE), 2); close(fd); } snprint(tmp, sizeof(tmp), "%s -9t | resample -x%d", prog, Coversz); 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 pnotify(Player *p) { Meta *m; char *s; int i; if(!pnotifies) return; if(p != nil){ m = getmeta(p->pcur); for(i = 0; cols[i] != 0; i++) Bprint(&out, "%s\t", (s = getcol(m, cols[i])) ? s : ""); } Bprint(&out, "\n"); Bflush(&out); } static void stop(Player *player) { if(player == nil) return; if(player == playernext) playernext = nil; if(!getmeta(player->pcur)->filefmt[0]) playerret(player); if(player == playercurr) pnotify(nil); sendul(player->ctl, Cstop); } static void start(Player *player) { if(player == nil) return; if(!getmeta(player->pcur)->filefmt[0]) playerret(player); pnotify(player); sendul(player->ctl, Cstart); } 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(getmeta(pcur)->filefmt[0] && playerret(player) < 0) return nil; done: if(pcur < pl->n-1 && playernext == nil && loadnext) playernext = newplayer(pcur+1, 0); return player; } 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){ if((fd = open(cur->path, OREAD)) < 0){ fprint(2, "%r\n"); sendul(player->ev, Everror); chanclose(player->ev); goto freeplayer; } }else{ sendul(player->ev, Evready); chanclose(player->ev); } pipe(p); if((pid = rfork(RFPROC|RFFDG|RFNOTEG|RFCENVG|RFNOWAIT)) == 0){ close(p[1]); if(fd < 0) fd = open("/dev/null", OREAD); dup(fd, 0); close(fd); dup(p[0], 1); close(p[0]); if(!debug){ dup(fd = open("/dev/null", OWRITE), 2); close(fd); } 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){ if(*fmt){ sendul(player->ev, Evready); chanclose(player->ev); } 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; audioon(); boffset = iowrite(io, audio, buf, n); noinit = 1; } boffsetlast = boffset; byteswritten = boffset; pcurplaying = player->pcur; if(c != Cseekrel) redraw(1); while(1){ n = ioread(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){ audiooff(); goto stop; }else if(r != 0){ if(c == Ctoggle){ audiooff(); 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){ audiooff(); goto stop; } } boffset += n; byteswritten = boffset; audioon(); iowrite(io, audio, buf, 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) % pl->n, 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 && getmeta(pcurplaying)->duration > 0){ player->seek = off; sendul(player->ctl, Cseekrel); } } static void writeplist(void) { int i; for(i = 0; i < pl->n; i++) printmeta(&out, pl->m+i); } static void freeplist(Playlist *pl) { if(pl != nil){ free(pl->m); free(pl->raw); } free(pl); } static Playlist * readplistnew(char *raw) { char *s, *e, *a[5]; Playlist *pl; int plsz; Meta *m; plsz = 0; for(s = raw; (s = strchr(s, '\n')) != nil; s++){ if(*(++s) == '\n') plsz++; } if((pl = calloc(1, sizeof(*pl))) == nil || (pl->m = calloc(plsz+1, sizeof(Meta))) == nil){ freeplist(pl); werrstr("no memory"); return nil; } pl->raw = raw; for(s = pl->raw, m = pl->m;; s = e){ if((e = strchr(s, '\n')) == nil) break; s += 2; *e++ = 0; switch(s[-2]){ case 0: if(m->path != nil){ pl->n++; m++; } break; case Pimage: if(tokenize(s, a, nelem(a)) >= 4){ m->imageoffset = atoi(a[0]); m->imagesize = atoi(a[1]); m->imagereader = atoi(a[2]); m->imagefmt = a[3]; } break; case Pduration: m->duration = strtoull(s, nil, 0); break; case Partist: if(m->numartist < Maxartist) m->artist[m->numartist++] = s; break; case Pfilefmt: m->filefmt = s; break; case Palbum: m->album = s; break; case Pdate: m->date = s; break; case Ppath: m->path = s; break; case Ptitle: m->title = s; break; case Ptrack: m->track = s; break; } } if(m != nil && m->path != nil) pl->n++; return pl; } static Playlist * readplist(int fd) { Meta *m; Playlist *pl; char *s, *e, *endrec; int i, n, sz, alloc, tagsz, intval; s = nil; for(alloc = sz = 0;;){ alloc += 65536; if((e = realloc(s, alloc)) == nil){ nomem: werrstr("no memory"); free(s); return nil; } s = e; for(n = 0; sz < alloc; sz += n){ n = readn(fd, s+sz, alloc-sz); if(n < 0){ free(s); return nil; } if(n == 0) break; } if(n == 0) break; } s[sz-1] = 0; if(sz < 4 || s[0] != '#' || s[1] != ' ' || !isdigit(s[2]) || (s = memchr(s, '\n', sz)) == nil){ if((pl = readplistnew(s)) == nil) return nil; goto end; } if((pl = calloc(1, sizeof(*pl))) == nil) goto nomem; if((pl->m = calloc(pl->n, sizeof(Meta))) == nil){ werrstr("no memory"); error: freeplist(pl); return nil; } pl->raw = s; pl->rawsz = sz; pl->n = atoi(pl->raw+2); s++; /* at the start of the first record */ for(i = 0; i < pl->n; i++, s = endrec){ if(pl->raw+pl->rawsz < s+10){ werrstr("truncated playlist"); goto error; } if(s[0] != '#' || s[1] != ' ' || !isdigit(s[2])){ werrstr("invalid record"); goto error; } if((n = strtol(s+2, &e, 10)) < 0 || n > pl->n){ werrstr("invalid track index"); goto error; } if(pl->m[n].path != nil){ werrstr("duplicate track index"); goto error; } s[-1] = 0; sz = strtol(e, &s, 10); *s++ = 0; /* skip '\n' */ if(s+sz > pl->raw+pl->rawsz){ werrstr("truncated playlist"); goto error; } s[sz-1] = 0; /* '\n'→'\0' to mark the end of the record */ endrec = s+sz; m = pl->m+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 >= pl->raw+pl->rawsz){ werrstr("truncated playlist"); goto error; } 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] == Ptitle) m->title = e; else if(s[0] == Pdate) m->date = e; else if(s[0] == Ptrack) m->track = e; else{ werrstr("unknown tag type %c", s[0]); goto error; } s = e + tagsz + 1; } s[-1] = 0; } if(m->filefmt == nil){ fprint(2, "old playlist format? please re-run audio/mkplist or use zuke -G (see man page)\n"); werrstr("unknown file format"); goto error; } } end: if(pl->n < 1){ werrstr("empty playlist"); goto error; } return pl; } static void recenter(void) { updatescrollsz(); scroll = pcur - scrollsz/2; if(scroll < 0) scroll = 0; else if(scroll >= scrollsz) scroll = scrollsz-1; } static void search(char d) { Meta *m; static char buf[64]; static int sz; int inc, i, a, cycle; 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; cycle = 1; for(i = pcur+inc; i >= 0 && i < pl->n;){ 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; onemore: i += inc; } if(i >= 0 && i < pl->n){ pcur = i; recenter(); redraw(1); }else if(cycle && i+inc < 0){ cycle = 0; i = pl->n; goto onemore; }else if(cycle && i+inc >= pl->n){ cycle = 0; i = -1; goto onemore; } } 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(pl->n < 2) return; m = pl->n; if(pl->n < 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(pl->n*sizeof(*shuffle)); xi = pcurplaying < 0 ? pcur : pcurplaying; pcurplayingnew = -1; pcurnew = 0; for(i = 0; i < pl->n;){ if(xi < pl->n){ 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 < pl->n-1) playernext = newplayer(pcur+1, 0); } static void plumbaudio(void *kbd) { int i, f, pf; Playlist *p; Plumbmsg *m; char *s, *e; Rune c; threadsetname("audio/plumb"); if((f = plumbopen("audio", OREAD)) >= 0){ while((m = plumbrecv(f)) != nil){ s = m->data; if(strncmp(s, "key", 3) == 0 && isspace(s[3])){ for(s = s+4; isspace(*s); s++); for(; (i = chartorune(&c, s)) > 0 && c != Runeerror; s += i) sendul(kbd, c); continue; } if(*s != '/' && m->wdir != nil) s = smprint("%s/%.*s", m->wdir, m->ndata, m->data); if((e = strrchr(s, '.')) != nil && strcmp(e, ".plist") == 0 && (pf = open(s, OREAD)) >= 0){ p = readplist(pf); close(pf); if(p == nil) continue; freeplist(pl); pl = p; memset(mincolwidth, 0, sizeof(mincolwidth)); /* readjust columns */ sendul(playc, 0); }else{ for(i = 0; i < pl->n; i++){ if(strcmp(pl->m[i].path, s) == 0){ sendul(playc, i); break; } } } if(s != m->data) free(s); plumbfree(m); } } threadexits(nil); } static void usage(void) { fprint(2, "usage: %s [-s] [-G] [-c aAdDtTp]\n", argv0); sysfatal("usage"); } void threadmain(int argc, char **argv) { Rune key; Mouse m; ulong ind; enum { Emouse, Eresize, Ekey, Eplay, }; Alt a[] = { { nil, &m, CHANRCV }, { nil, nil, CHANRCV }, { nil, &key, CHANRCV }, { nil, &ind, CHANRCV }, { nil, nil, CHANEND }, }; int n, scrolling, oldpcur, oldbuttons, pnew, shuffled, themetid, nogui; char buf[64]; shuffled = 0; nogui = 0; ARGBEGIN{ case 'd': debug++; break; case 's': shuffled = 1; break; case 'c': cols = EARGF(usage()); if(strlen(cols) >= nelem(colwidth)) sysfatal("max %d columns allowed", nelem(colwidth)); break; case 'G': nogui = 1; break; default: usage(); break; }ARGEND; if((pl = readplist(0)) == nil){ fprint(2, "playlist: %r\n"); sysfatal("playlist error"); } close(0); Binit(&out, 1, OWRITE); if(nogui){ writeplist(); Bterm(&out); threadexitsall(nil); } pnotifies = fd2path(1, buf, sizeof(buf)) == 0 && strcmp(buf, "/dev/cons") != 0; if(initdraw(nil, nil, "zuke") < 0) sysfatal("initdraw: %r"); f = display->defaultfont; Scrollwidth = MAX(14, stringwidth(f, "#")); Scrollheight = MAX(16, f->height); Coversz = MAX(64, stringwidth(f, "∫ 00:00:00/00:00:00 100%")); unlockdisplay(display); display->locking = 1; 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; a[3].c = chancreate(sizeof(ind), 0); playc = a[3].c; srand(time(0)); pcurplaying = -1; chvolume(0); fmtinstall('P', positionfmt); threadsetname("zuke"); if(shuffled){ pcur = nrand(pl->n); toggleshuffle(); recenter(); } themeinit(); redraw(1); oldbuttons = 0; scrolling = 0; themetid = -1; proccreate(plumbaudio, kctl->c, 4096); for(;;){ ev: oldpcur = pcur; if(seekmx != newseekmx){ seekmx = newseekmx; redraw(0); } switch(alt(a)){ case Emouse: 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; }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, pl->n-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, pl->n-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, nil, 4096); else if(n == 1) goto end; goto ev; } if(scrolling){ if(scrollsz >= pl->n) break; scroll = (m.xy.y - screen->r.min.y - Scrollheight/4)*(pl->n-scrollsz) / (Dy(screen->r)-Scrollheight/2); scroll = CLAMP(scroll, 0, pl->n-scrollsz-1); redraw(1); }else if(m.buttons == 1 || m.buttons == 2){ n += scroll; if(n < pl->n){ pcur = n; if(m.buttons == 2){ stop(playercurr); playercurr = newplayer(pcur, 1); start(playercurr); } } } break; case Eresize: /* resize */ if(getwindow(display, Refnone) < 0) sysfatal("getwindow: %r"); redraw(1); break; case Ekey: 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 = pl->n-1; break; case Khome: pcur = 0; break; case '\n': playcur: 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; recenter(); break; case 'b': case '>': if(playercurr == nil) break; pnew = pcurplaying; if(++pnew >= pl->n) 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 = pl->n-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(); recenter(); redraw(1); break; case 'c': case 'p': case ' ': toggle(playercurr); break; case '/': case '?': case 'n': case 'N': search(key); break; } break; case Eplay: pcur = ind; recenter(); if(playercurr != nil) goto playcur; break; } if(pcur != oldpcur){ pcur = CLAMP(pcur, 0, pl->n-1); if(pcur < scroll) scroll = pcur; else if(pcur > scroll + scrollsz) scroll = pcur - scrollsz; scroll = CLAMP(scroll, 0, pl->n-scrollsz); if(pcur != oldpcur) redraw(1); } } end: threadint(themetid); threadexitsall(nil); }