ref: 2f722ab652fef9ba47e701033d19d6bc7814927f
dir: /zuke.c/
#include "theme.c" #include <mouse.h> #include <keyboard.h> #include <ctype.h> #include "plist.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; enum { Cstart = 1, Cstop, Ctoggle, Cseekrel, Everror = 1, Evready, Seek = 10, /* 10 seconds */ Seekfast = 60, /* a minute */ Scrollwidth = 14, Scrollheight = 16, Bps = 44100*2*2, /* 44100KHz, stereo, s16 for a sample */ Relbufsz = Bps/5, /* 0.2 second */ }; struct Player { Channel *ctl; Channel *ev; Channel *img; double seek; int pcur; }; int mainstacksize = 32768; static int audio; static Meta *pl; static int plnum; static char *plraw; static int plrawsize; static int volume; 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 *ev; 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; static double seekoff; /* ms */ static char *covers[] = {"folder", "cover", "Cover", "scans/CD", "Scans/Front", "Covers/Front"}; static char *menu3i[] = { "theme", "exit", nil }; static Menu menu3 = { .item = menu3i, }; #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 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 Pduration: snprint(tmp, sizeof(tmp), "%8P", m->duration/1000); return tmp; case Ptitle: return m->title; case Ptrack: snprint(tmp, sizeof(tmp), "%4s", m->track); return m->track ? tmp : nil; case Ppath: return m->path; 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 < plnum; n++){ for(i = 0; cols[i] != 0; i++){ if((x = stringwidth(f, getcol(&pl[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[shuffle != nil ? shuffle[i] : i]; } static void redraw(int full) { Image *col; Point p, sp; Rectangle sel, r; int i, j, left, right, scrollcenter; char tmp[32]; lockdisplay(display); scrollsz = (Dy(screen->r) - f->height - 4) / f->height - 1; left = screen->r.min.x; if(scrollsz < plnum) /* adjust for scrollbar */ left += Scrollwidth + 1; if(full){ draw(screen, screen->r, colors[Dback].im, nil, ZP); adjustcolumns(); if(scrollsz < plnum){ /* 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[Dfmed].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 / (plnum - scrollsz); r.min.y += scrollcenter + Scrollheight/4; r.max.y = r.min.y + Scrollheight; draw(screen, r, colors[Dfmed].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[Dfmed].im, ZP); p.x += 4; } 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, colors[Dbinv].im, nil, ZP); col = colors[Dfinv].im; }else{ col = colors[pcurplaying == i ? Dfhigh : 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[Dfmed].im, sp); leftp.y = rightp.y = p.y + f->height; line(screen, leftp, rightp, 0, 0, 0, colors[Dfmed].im, sp); } } } vlong msec = 0; if(pcurplaying >= 0){ msec = byteswritten*1000/Bps; if(getmeta(pcurplaying)->duration != 0){ msec = MIN(msec, getmeta(pcurplaying)->duration); snprint(tmp, sizeof(tmp), "%s%P/%P %d%%", shuffle != nil ? "∫ " : "", (int)(newseekmx >= 0 ? seekoff : msec)/1000, getmeta(pcurplaying)->duration/1000, volume); seekmx = newseekmx; }else{ snprint(tmp, sizeof(tmp), "%s%P %d%%", shuffle != nil ? "∫ " : "", (int)msec/1000, volume); } }else snprint(tmp, sizeof(tmp), "%s%d%%", shuffle != nil ? "∫ " : "", volume); r = screen->r; right = r.max.x - stringwidth(f, tmp) - 4; r.min.x = left; r.min.y = r.max.y - f->height - 4; if(pcurplaying < 0 || getmeta(pcurplaying)->duration == 0) r.min.x = right; draw(screen, r, colors[Dblow].im, nil, ZP); r.max.x = right; p = addpt(Pt(right, r.min.y), Pt(2, 2)); string(screen, p, colors[Dfhigh].im, sp, f, tmp); sel = r; 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 && getmeta(pcurplaying)->duration != 0){ r = insetrect(sel, 3); draw(screen, r, colors[Dfmed].im, nil, ZP); r = insetrect(r, 1); seekbar = r; r.max.x = r.min.x + Dx(r) * (double)msec / (double)getmeta(pcurplaying)->duration; draw(screen, r, colors[Dbinv].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|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 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, 4096); 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, 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 && (fd = open(cur->path, OREAD)) < 0){ fprint(2, "%r\n"); sendul(player->ev, Everror); goto freeplayer; } pipe(p); if((pid = rfork(RFPROC|RFFDG|RFREND|RFNOTEG)) == 0){ if(fd < 0) fd = open("/dev/null", OREAD); dup(fd, 0); close(fd); dup(p[0], 1); close(p[0]); dup(fd = open("/dev/null", OWRITE), 2); close(fd); close(p[1]); 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){ sendul(player->ev, Evready); 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; boffset = iowrite(io, audio, buf, n); noinit = 1; } byteswritten = boffsetlast = boffset; pcurplaying = player->pcur; if(c != Cseekrel) redraw(1); while(1){ n = ioreadn(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){ goto stop; }else if(r != 0){ if(c == Ctoggle){ 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){ goto stop; } } boffset += n; byteswritten = boffset; if(iowrite(io, audio, buf, n) != n) fprint(2, "player: %r\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) % plnum, 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){ player->seek = off; sendul(player->ctl, Cseekrel); } } 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 = readn(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 if(s[0] == Pfilefmt){ m->filefmt = s+2; s = strchr(s+2, '\n') + 1; }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; } if(m->filefmt == nil) sysfatal("old playlist format?\nplease re-run audio/mkplist"); } } static void search(char d) { Meta *m; static char buf[64]; static int sz; int inc, i, a; 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; for(i = pcur+inc; i >= 0 && i < plnum; i += inc){ 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; } if(i >= 0 && i < plnum){ pcur = i; redraw(1); } } 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(plnum < 2) return; m = plnum; if(plnum < 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(plnum*sizeof(*shuffle)); xi = pcurplaying < 0 ? pcur : pcurplaying; pcurplayingnew = -1; pcurnew = 0; for(i = 0; i < plnum;){ if(xi < plnum){ 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 < plnum-1) playernext = newplayer(pcur+1, 0); } static void usage(void) { fprint(2, "usage: %s [-s] [-c aAdDtTp]\n", argv0); sysfatal("usage"); } void threadmain(int argc, char **argv) { Rune key; Mouse m; Alt a[] = { { nil, &m, CHANRCV }, { nil, nil, CHANRCV }, { nil, &key, CHANRCV }, { nil, nil, CHANEND }, }; int n, scrolling, oldpcur, oldbuttons, pnew, shuffled, themetid; shuffled = 0; ARGBEGIN{ case 's': shuffled = 1; break; case 'c': cols = EARGF(usage()); if(strlen(cols) >= nelem(colwidth)) sysfatal("max %d columns allowed", nelem(colwidth)); break; default: usage(); break; }ARGEND; readplist(); if(plnum < 1){ fprint(2, "empty playlist\n"); sysfatal("empty"); } if((audio = open("/dev/audio", OWRITE)) < 0) sysfatal("audio: %r"); 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; srand(time(0)); pcurplaying = -1; chvolume(0); fmtinstall('P', positionfmt); threadsetname("zuke"); if(shuffled){ pcur = nrand(plnum); toggleshuffle(); } themeinit(); redraw(1); oldbuttons = 0; scrolling = 0; themetid = -1; for(;;){ ev: oldpcur = pcur; if(seekmx != newseekmx) redraw(0); switch(alt(a)){ case 0: 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-1; }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, plnum-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, plnum-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, &audio, 4096); else if(n == 1) goto end; goto ev; } if(scrolling){ if(scrollsz >= plnum) break; scroll = (m.xy.y - screen->r.min.y - Scrollheight/4)*(plnum-scrollsz) / (Dy(screen->r)-Scrollheight/2); scroll = CLAMP(scroll, 0, plnum-scrollsz-1); redraw(1); }else if(m.buttons == 1 || m.buttons == 2){ pcur = scroll + n; if(m.buttons == 2){ stop(playercurr); playercurr = newplayer(pcur, 1); start(playercurr); } } break; case 1: /* resize */ if(getwindow(display, Refnone) < 0) sysfatal("getwindow: %r"); redraw(1); break; case 2: 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 = 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 'i': case 'o': if(pcur == pcurplaying) oldpcur = -1; pcur = pcurplaying; break; case 'b': case '>': if(playercurr == nil) break; pnew = pcurplaying; if(++pnew >= plnum) 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 = plnum-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(); redraw(1); break; case 'c': case 'p': toggle(playercurr); break; case '/': case '?': case 'n': case 'N': search(key); break; } } if(pcur != oldpcur){ pcur = CLAMP(pcur, 0, plnum-1); if(pcur < scroll) scroll = pcur; else if(pcur > scroll + scrollsz) scroll = pcur - scrollsz; scroll = CLAMP(scroll, 0, plnum-scrollsz); if(pcur != oldpcur) redraw(1); } } end: threadint(themetid); threadexitsall(nil); }