shithub: zuke

ref: feeab3fe530af252f3e7062b9bc638fa72c4b348
dir: /zuke.c/

View raw version
#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,

	Relbufsz = 65536,
};

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 u64int byteswritten;
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(Relbufsz);
		byteswritten = 0;
	}

	pcurplaying = player->pcur;
	redraw(screen, 1);

	while((n = ioread(io, p[1], buf, Relbufsz)) > 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;
		byteswritten += n;
	}

	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 */
	chanfree(player->ev);
	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
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);
				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;
			}
			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 == '?')
		sz = eenter(inc > 0 ? "forward:" : "backward:", buf, sizeof(buf), nil);
	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
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);
			}else if(e.kbdc == '/' || e.kbdc == '?' || e.kbdc == 'n' || e.kbdc == 'N'){
				search(e.kbdc);
			}
		}

		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);
}