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