shithub: pplay

Download patch

ref: ccebe45fe754f1484f104e17a22e627bd1553d02
parent: 79cda97b806baf2adfc0045537675684d1edfe75
author: qwx <qwx@sciops.net>
date: Sun Sep 1 15:24:59 EDT 2024

cmd: user process interruption and prohibit multiple readers

--- a/cmd.c
+++ b/cmd.c
@@ -4,6 +4,8 @@
 #include "dat.h"
 #include "fns.h"
 
+extern Channel *pidc;
+
 Dot dot;
 int bound;
 
@@ -57,7 +59,7 @@
 	return 0;
 }
 
-static vlong
+static int
 cut(char *)
 {
 	dprint(nil, "cmd/cut %Δ\n", &dot);
@@ -115,7 +117,7 @@
 	dup(epfd[0], 0);
 	dup(epfd[0], 1);
 	close(epfd[0]);
-	procexecl(nil, "/bin/rc", "rc", "-c", s, nil);
+	procexecl(pidc, "/bin/rc", "rc", "-c", s, nil);
 	sysfatal("procexec: %r");
 }
 
@@ -124,7 +126,6 @@
 {
 	int fd;
 
-	threadsetgrp(1);
 	fd = (intptr)efd;
 	writebuf(fd);
 	close(fd);
@@ -160,6 +161,12 @@
 static int
 pipeline(char *arg, int rr, int wr)
 {
+	if(nslots == 0){
+		fprint(2, "pipeline: too many backgrounded processes\n");
+		return -1;
+	}
+	if(rr)
+		reader = 0;
 	if(pipe(epfd) < 0)
 		sysfatal("pipe: %r");
 	if(procrfork(rc, arg, mainstacksize, RFFDG|RFNOTEG|RFNAMEG) < 0)
@@ -247,9 +254,9 @@
 cmd(char *s)
 {
 	int n, x;
+	int (*fn)(char*);
 	Rune r, r´;
 
-	/* FIXME: avoid potential conflicts with keys in main() */
 	assert(s != nil);
 	s += chartorune(&r, s);
 	for(;;){
@@ -263,28 +270,53 @@
 		s += n;
 	}
 	dprint(dot.norris, "current dot=%Δ\n", &dot);
+	if(reader >= 0){
+		switch(r){
+		case '<':
+		case '^':
+		case 'c':
+		case 'd':
+		//case 'm':
+		case 'p':
+		case 'r':
+		case 'U':
+		case 'u':
+		case 'w':
+		case 'x':
+			werrstr("still reading from external process\n");
+			return -1;
+		}
+	}
+	x = 666;
+	fn = nil;
 	switch(r){
-	case '<': x = pipefrom(s); break;
-	case '^': x = pipethrough(s); break;
-	case '|': x = pipeto(s); break;
-	case '!': x = pipeselflessly(s); break;
-	case 'L': x = setleft(s); break;
-	case 'R': x = setright(s); break;
-	case 'c': x = copy(s); break;
-	case 'd': x = cut(s); break;
-	case 'j': x = jumpto(s); break;
-	//case 'm': x = mark(s); break;
-	case 'p': x = paste(s); break;
 	case 'q': threadexitsall(nil);
-	case 'r': x = readfrom(s); break;
-	case 's': x = replicate(s); break;
-	case 'U': x = unpop(s); break;
-	case 'u': x = popop(s); break;
-	case 'w': x = writeto(s); break;
-	case 'x': x = crop(s); break;
-	default: werrstr("unknown command %C", r); x = -1; break;
+	case 'D': killreader(); break;
+	case '<': fn = pipefrom; break;
+	case '^': fn = pipethrough; break;
+	case '|': fn = pipeto; break;
+	case '!': fn = pipeselflessly; break;
+	case 'L': fn = setleft; break;
+	case 'R': fn = setright; break;
+	case 'c': fn = copy; break;
+	case 'd': fn = cut; break;
+	case 'j': fn = jumpto; break;
+	//case 'm': fn = mark; break;
+	case 'p': fn = paste; break;
+	case 'r': fn = readfrom; break;
+	case 's': fn = replicate; break;
+	case 'U': fn = unpop; break;
+	case 'u': fn = popop; break;
+	case 'w': fn = writeto; break;
+	case 'x': fn = crop; break;
+	default:
+		werrstr("unknown command %C", r);
+		return -1;
 	}
-	dprint(dot.norris, "final dot=%Δ\n", &dot);
+	if(fn != nil){
+		x = fn(s);
+		dprint(dot.norris, "final dot=%Δ\n", &dot);
+	}
 	return x;
 }
 
@@ -308,12 +340,4 @@
 advance(usize n)
 {
 	advanceone(&dot, n);
-}
-
-static void
-catch(void *, char *msg)
-{
-	if(strstr(msg, "closed pipe"))
-		noted(NCONT);
-	noted(NDFLT);
 }
--- a/dat.h
+++ b/dat.h
@@ -42,6 +42,8 @@
 extern int stereo, chan;
 extern int debug, paused;
 extern int samptime;
+extern int nslots;
+extern int reader;
 
 #define MIN(x,y)	((x) < (y) ? (x) : (y))
 #define MAX(x,y)	((x) > (y) ? (x) : (y))
--- a/fns.h
+++ b/fns.h
@@ -1,5 +1,6 @@
 void	dprint(Chunk*, char*, ...);
 void	freechain(Chunk*);
+void	killreader(void);
 int	unpop(char*);
 int	popop(char*);
 int	cpaste(Dot*);
--- a/pplay.c
+++ b/pplay.c
@@ -11,13 +11,35 @@
 
 int stereo, chan;
 int debug, paused, notriob;
+int reader = -1;
+Channel *pidc;
 
 static Keyboardctl *kc;
 static Mousectl *mc;
 static int cat;
 static Channel *crm114;
+static int pids[32];
+int nslots = nelem(pids);
 
+void
+killreader(void)
+{
+	if(reader <= 0)
+		return;
+	postnote(PNGROUP, reader, "kill");
+}
+
 static void
+killemall(void)
+{
+	int i, pid;
+
+	for(i=0; i<nelem(pids); i++)
+		if((pid = pids[i]) >= 0)
+			postnote(PNGROUP, pid, "kill");
+}
+
+static void
 aproc(void *)
 {
 	int afd, nerr;
@@ -105,8 +127,11 @@
 void
 threadmain(int argc, char **argv)
 {
+	int i, pid;
 	char *p;
 	Mouse m, mo;
+	Channel *waitc;
+	Waitmsg *w;
 	Rune r;
 
 	notriob = 0;
@@ -130,22 +155,35 @@
 	if((mc = initmouse(nil, screen)) == nil)
 		sysfatal("initmouse: %r");
 	mo.xy = ZP;
-	Alt a[] = {
-		{mc->resizec, nil, CHANRCV},
-		{mc->c, &mc->Mouse, CHANRCV},
-		{kc->c, &r, CHANRCV},
-		{nil, nil, CHANEND}
-	};
+	for(i=0; i<nelem(pids); i++)
+		pids[i] = -1;
 	if(setpri(13) < 0)
 		fprint(2, "setpri: %r\n");
-	if((crm114 = chancreate(sizeof(ulong), 2)) == nil)
+	if((crm114 = chancreate(sizeof(ulong), 2)) == nil
+	|| (pidc = chancreate(sizeof(int), 1)) == nil)
 		sysfatal("chancreate: %r");
 	if(proccreate(aproc, nil, 16*1024) < 0)
 		sysfatal("threadcreate: %r");
 	toggleplay();
+	waitc = threadwaitchan();
+	enum{
+		Aresize,
+		Amouse,
+		Akey,
+		Apid,
+		Await,
+	};
+	Alt a[] = {
+		[Aresize] {mc->resizec, nil, CHANRCV},
+		[Amouse] {mc->c, &mc->Mouse, CHANRCV},
+		[Akey] {kc->c, &r, CHANRCV},
+		[Apid] {pidc, &pid, CHANRCV},
+		[Await] {waitc, &w, CHANRCV},
+		{nil, nil, CHANEND}
+	};
 	for(;;){
 		switch(alt(a)){
-		case 0:
+		case Aresize:
 			mo = mc->Mouse;
 			lockdisplay(display);
 			if(getwindow(display, Refnone) < 0)
@@ -153,7 +191,7 @@
 			unlockdisplay(display);
 			redraw(1);
 			break;
-		case 1:
+		case Amouse:
 			m = mc->Mouse;
 			if(mo.msec == 0)
 				mo = m;
@@ -167,7 +205,7 @@
 			}
 			mo = m;
 			break;
-		case 2:
+		case Akey:
 			switch(r){
 			case ' ': toggleplay(); break;
 			case Kesc: setrange(0, dot.totalsz); break;
@@ -180,13 +218,14 @@
 			case '1': bound = 0; break;
 			case '2': bound = 1; break;
 			case 'S': stereo ^= 1; redraw(1); break;
-			case Kdel:
-			case 'q': threadexitsall(nil);
 			case 'b': setjump(dot.from); break;
 			case 't': samptime ^= 1; break;
 			case 'z': zoominto(0, dot.totalsz); break;
 			case Kleft: setpage(-1); break;
 			case Kright: setpage(1); break;
+			case 'D': killreader(); break;
+			case Kdel: killemall(); break;
+			case 'q': threadexitsall(nil);
 			default:
 				if((p = prompt(r)) == nil || strlen(p) == 0){
 					refresh(Drawrender);
@@ -197,8 +236,38 @@
 				case 0: refresh(Drawall); break;
 				case 1: redraw(0); break;
 				case 2: redraw(1); break;
+				default: break;
 				}
 			}
+			break;
+		case Apid:
+			if(pid < 0){
+				fprint(2, "process exited with error\n");
+				break;
+			}
+			for(i=0; i<nelem(pids); i++)
+				if(pids[i] < 0){
+					pids[i] = pid;
+					break;
+				}
+			assert(i < nelem(pids));
+			if(reader == 0)
+				reader = pid;
+			nslots--;
+			break;
+		case Await:
+			for(i=0; i<nelem(pids); i++)
+				if(pids[i] == w->pid){
+					pids[i] = -1;
+					break;
+				}
+			if(i == nelem(pids))
+				fprint(2, "phase error -- no such pid %d\n", w->pid);
+			if(w->pid == reader)
+				reader = -1;
+			nslots++;
+			free(w);
+			break;
 		}
 	}
 }
--- a/pplay.man
+++ b/pplay.man
@@ -130,6 +130,12 @@
 .B →
 Pan right by screenful
 .TP
+.B D
+Kill spawned reader process if it exists
+.TP
+.B Del
+Kill any spawned external processes
+.TP
 .B q
 Quit
 .PD
@@ -221,11 +227,23 @@
 Shell commands are passed verbatim to
 .IR rc (1)
 and may be any valid expression including function definitions.
-Each of them runs concurrently in the background
-and updates the then selected range once done.
-Upon exit,
-.I pplay
-will wait for subcommands to end and exit first.
+Commands which spawn a process which will write data back
+.RB ( < ,
+.BR ^ ,
+.BR r )
+disable all commands which may also modify the data
+until the process exits.
+.I rc
+commands which do not exit on their own,
+for instance when reading from a pipe,
+can be interrupted with the
+.B D
+shortcut.
+Commands which do not read in data
+.RB ( | ,
+.BR ! ,
+.BR w )
+will be left alone on exit, allowing them to finish.
 .PP
 Undo is infinite.
 .SH EXAMPLES
@@ -248,6 +266,15 @@
 .EX
 ^pcmenv 1 0 1.1
 .EE
+.PP
+Record arbitrary amount of data from a pipe:
+.IP
+.EX
+</dev/audio
+.EE
+Use the
+.B D
+shortcut to interrupt the reader process and paste the data read so far.
 .SH "SEE ALSO"
 .IR audio (1),
 .IR play (1),
@@ -257,15 +284,6 @@
 .I Pplay
 first spawned on 9front (October, 2017), beyond the environment.
 .SH BUGS
-An external command that never exits will freeze
-.I pplay
-forever on exit due to the reliance on
-.BR thread (2).
-Subprocess abnormal exits are completely unhandled.
-Edits in a range while a shell command affecting it is running,
-or the case of multiple shell commands on intersecting ranges,
-are not serialized or protected in any way.
-.PP
 The front may fall off if attempting to load data
 which cannot entirely fit in available memory.
 The maximum size of a single buffer is bound by the limits of