xref: /plan9-contrib/acme/bin/source/win/main.c (revision d46c239f8612929b7dbade67d0d071633df3a15d)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <thread.h>
5 #include <fcall.h>
6 #include <9p.h>
7 #include <ctype.h>
8 #include "dat.h"
9 
10 void	mainctl(void*);
11 void	startcmd(char *[], int*);
12 void	stdout2body(void*);
13 
14 int	debug;
15 int	notepg;
16 int	eraseinput;
17 int	dirty = 0;
18 
19 Window *win;		/* the main window */
20 
21 void
22 usage(void)
23 {
24 	fprint(2, "usage: win [command]\n");
25 	threadexitsall("usage");
26 }
27 
28 void
29 threadmain(int argc, char *argv[])
30 {
31 	int i, j;
32 	char *dir, *tag, *name;
33 	char buf[1024], **av;
34 
35 	quotefmtinstall();
36 	rfork(RFNAMEG);
37 	ARGBEGIN{
38 	case 'd':
39 		debug = 1;
40 		chatty9p =1;
41 		break;
42 	case 'e':
43 		eraseinput = 1;
44 		break;
45 	case 'D':
46 {extern int _threaddebuglevel;
47 		_threaddebuglevel = 1<<20;
48 }
49 	}ARGEND
50 
51 	if(argc == 0){
52 		av = emalloc(3*sizeof(char*));
53 		av[0] = "rc";
54 		av[1] = "-i";
55 		name = getenv("sysname");
56 	}else{
57 		av = argv;
58 		name = utfrrune(av[0], '/');
59 		if(name)
60 			name++;
61 		else
62 			name = av[0];
63 	}
64 
65 	if(getwd(buf, sizeof buf) == 0)
66 		dir = "/";
67 	else
68 		dir = buf;
69 	dir = estrdup(dir);
70 	tag = estrdup(dir);
71 	tag = eappend(estrdup(tag), "/-", name);
72 	win = newwindow();
73 	winname(win, tag);
74 	wintagwrite(win, "Send Noscroll", 5+8);
75 	threadcreate(mainctl, win, STACK);
76 	mountcons();
77 	threadcreate(fsloop, nil, STACK);
78 	startpipe();
79 	startcmd(av, &notepg);
80 
81 	strcpy(buf, "win");
82 	j = 3;
83 	for(i=0; i<argc && j+1+strlen(argv[i])+1<sizeof buf; i++){
84 		strcpy(buf+j, " ");
85 		strcpy(buf+j+1, argv[i]);
86 		j += 1+strlen(argv[i]);
87 	}
88 
89 	ctlprint(win->ctl, "scroll");
90 	winsetdump(win, dir, buf);
91 }
92 
93 int
94 EQUAL(char *s, char *t)
95 {
96 	while(tolower(*s) == tolower(*t++))
97 		if(*s++ == '\0')
98 			return 1;
99 	return 0;
100 }
101 
102 int
103 command(Window *w, char *s)
104 {
105 	while(*s==' ' || *s=='\t' || *s=='\n')
106 		s++;
107 	if(strcmp(s, "Delete")==0){
108 		windel(w, 1);
109 		threadexitsall(nil);
110 		return 1;
111 	}
112 	if(strcmp(s, "Del")==0){
113 		if(windel(w, 0))
114 			threadexitsall(nil);
115 		return 1;
116 	}
117 	if(EQUAL(s, "scroll")){
118 		ctlprint(w->ctl, "scroll\nshow");
119 		return 1;
120 	}
121 	if(EQUAL(s, "noscroll")){
122 		ctlprint(w->ctl, "noscroll");
123 		return 1;
124 	}
125 	return 0;
126 }
127 
128 static long
129 utfncpy(char *to, char *from, int n)
130 {
131 	char *end, *e;
132 
133 	e = to+n;
134 	if(to >= e)
135 		return 0;
136 	end = memccpy(to, from, '\0', e - to);
137 	if(end == nil){
138 		end = e;
139 		if(end[-1]&0x80){
140 			if(end-2>=to && (end[-2]&0xE0)==0xC0)
141 				return end-to;
142 			if(end-3>=to && (end[-3]&0xF0)==0xE0)
143 				return end-to;
144 			while(end>to && (*--end&0xC0)==0x80)
145 				;
146 		}
147 	}else
148 		end--;
149 	return end - to;
150 }
151 
152 /* sendinput and fsloop run in the same proc (can't interrupt each other). */
153 static Req *q;
154 static Req **eq;
155 static int
156 __sendinput(Window *w, ulong q0, ulong q1)
157 {
158 	char *s, *t;
159 	int n, nb, eofchar;
160 	static int partial;
161 	static char tmp[UTFmax];
162 	Req *r;
163 	Rune rune;
164 
165 	if(!q)
166 		return 0;
167 
168 	r = q;
169 	n = 0;
170 	if(partial){
171 	Partial:
172 		nb = partial;
173 		if(nb > r->ifcall.count)
174 			nb = r->ifcall.count;
175 		memmove(r->ofcall.data, tmp, nb);
176 		if(nb!=partial)
177 			memmove(tmp, tmp+nb, partial-nb);
178 		partial -= nb;
179 		q = r->aux;
180 		if(q == nil)
181 			eq = &q;
182 		r->aux = nil;
183 		r->ofcall.count = nb;
184 		if(debug)
185 			fprint(2, "satisfy read with partial\n");
186 		respond(r, nil);
187 		return n;
188 	}
189 	if(q0==q1)
190 		return 0;
191 	s = emalloc((q1-q0)*UTFmax+1);
192 	n = winread(w, q0, q1, s);
193 	s[n] = '\0';
194 	t = strpbrk(s, "\n\004");
195 	if(t == nil){
196 		free(s);
197 		return 0;
198 	}
199 	r = q;
200 	eofchar = 0;
201 	if(*t == '\004'){
202 		eofchar = 1;
203 		*t = '\0';
204 	}else
205 		*++t = '\0';
206 	nb = utfncpy((char*)r->ofcall.data, s, r->ifcall.count);
207 	if(nb==0 && s<t && r->ifcall.count > 0){
208 		partial = utfncpy(tmp, s, UTFmax);
209 		assert(partial > 0);
210 		chartorune(&rune, tmp);
211 		partial = runelen(rune);
212 		free(s);
213 		n = 1;
214 		goto Partial;
215 	}
216 	n = utfnlen(r->ofcall.data, nb);
217 	if(nb==strlen(s) && eofchar)
218 		n++;
219 	r->ofcall.count = nb;
220 	q = r->aux;
221 	if(q == nil)
222 		eq = &q;
223 	r->aux = nil;
224 	if(debug)
225 		fprint(2, "read returns %lud-%lud: %.*q\n", q0, q0+n, n, r->ofcall.data);
226 	respond(r, nil);
227 	return n;
228 }
229 
230 static int
231 _sendinput(Window *w, ulong q0, ulong *q1)
232 {
233 	char buf[32];
234 	int n;
235 
236 	n = __sendinput(w, q0, *q1);
237 	if(!n || !eraseinput)
238 		return n;
239 	/* erase q0 to q0+n */
240 	sprint(buf, "#%lud,#%lud", q0, q0+n);
241 	winsetaddr(w, buf, 0);
242 	write(w->data, buf, 0);
243 	*q1 -= n;
244 	return 0;
245 }
246 
247 int
248 sendinput(Window *w, ulong q0, ulong *q1)
249 {
250 	ulong n;
251 	Req *oq;
252 
253 	n = 0;
254 	do {
255 		oq = q;
256 		n += _sendinput(w, q0+n, q1);
257 	} while(q != oq);
258 	return n;
259 }
260 
261 Event esendinput;
262 void
263 fsloop(void*)
264 {
265 	Fsevent e;
266 	Req **l, *r;
267 
268 	eq = &q;
269 	memset(&esendinput, 0, sizeof esendinput);
270 	esendinput.c1 = 'C';
271 	for(;;){
272 		while(recv(fschan, &e) == -1)
273 			;
274 		r = e.r;
275 		switch(e.type){
276 		case 'r':
277 			*eq = r;
278 			r->aux = nil;
279 			eq = &r->aux;
280 			/* call sendinput with hostpt and endpt */
281 			sendp(win->cevent, &esendinput);
282 			break;
283 		case 'f':
284 			for(l=&q; *l; l=&(*l)->aux){
285 				if(*l == r->oldreq){
286 					*l = r->oldreq->aux;
287 					if(*l == nil)
288 						eq = l;
289 					closereq(r->oldreq);
290 					break;
291 				}
292 			}
293 			respond(r, nil);
294 			break;
295 		}
296 	}
297 }
298 
299 void
300 sendit(char *s)
301 {
302 //	char tmp[32];
303 
304 	write(win->body, s, strlen(s));
305 /*
306  * RSC: The problem here is that other procs can call sendit,
307  * so we lose our single-threadedness if we call sendinput.
308  * In fact, we don't even have the right queue memory,
309  * I think that we'll get a write event from the body write above,
310  * and we can do the sendinput then, from our single thread.
311  *
312  * I still need to figure out how to test this assertion for
313  * programs that use /srv/win*
314  *
315 	winselect(win, "$", 0);
316 	seek(win->addr, 0UL, 0);
317 	if(read(win->addr, tmp, 2*12) == 2*12)
318 		hostpt += sendinput(win, hostpt, atol(tmp), );
319  */
320 }
321 
322 void
323 execevent(Window *w, Event *e, int (*command)(Window*, char*))
324 {
325 	Event *ea, *e2;
326 	int n, na, len, needfree;
327 	char *s, *t;
328 
329 	ea = nil;
330 	e2 = nil;
331 	if(e->flag & 2)
332 		e2 = recvp(w->cevent);
333 	if(e->flag & 8){
334 		ea = recvp(w->cevent);
335 		na = ea->nb;
336 		recvp(w->cevent);
337 	}else
338 		na = 0;
339 
340 	needfree = 0;
341 	s = e->b;
342 	if(e->nb==0 && (e->flag&2)){
343 		s = e2->b;
344 		e->q0 = e2->q0;
345 		e->q1 = e2->q1;
346 		e->nb = e2->nb;
347 	}
348 	if(e->nb==0 && e->q0<e->q1){
349 		/* fetch data from window */
350 		s = emalloc((e->q1-e->q0)*UTFmax+2);
351 		n = winread(w, e->q0, e->q1, s);
352 		s[n] = '\0';
353 		needfree = 1;
354 	}else
355 	if(na){
356 		t = emalloc(strlen(s)+1+na+2);
357 		sprint(t, "%s %s", s, ea->b);
358 		if(needfree)
359 			free(s);
360 		s = t;
361 		needfree = 1;
362 	}
363 
364 	/* if it's a known command, do it */
365 	/* if it's a long message, it can't be for us anyway */
366 	if(!command(w, s) && s[0]!='\0'){	/* send it as typed text */
367 		/* if it's a built-in from the tag, send it back */
368 		if(e->flag & 1)
369 			fprint(w->event, "%c%c%d %d\n", e->c1, e->c2, e->q0, e->q1);
370 		else{	/* send text to main window */
371 			len = strlen(s);
372 			if(len>0 && s[len-1]!='\n' && s[len-1]!='\004'){
373 				if(!needfree){
374 					/* if(needfree), we left room for a newline before */
375 					t = emalloc(len+2);
376 					strcpy(t, s);
377 					s = t;
378 					needfree = 1;
379 				}
380 				s[len++] = '\n';
381 				s[len] = '\0';
382 			}
383 			sendit(s);
384 		}
385 	}
386 	if(needfree)
387 		free(s);
388 }
389 
390 int
391 hasboundary(Rune *r, int nr)
392 {
393 	int i;
394 
395 	for(i=0; i<nr; i++)
396 		if(r[i]=='\n' || r[i]=='\004')
397 			return 1;
398 	return 0;
399 }
400 
401 void
402 mainctl(void *v)
403 {
404 	Window *w;
405 	Event *e;
406 	int delta, pendingS, pendingK;
407 	ulong hostpt, endpt;
408 	char tmp[32];
409 
410 	w = v;
411 	proccreate(wineventproc, w, STACK);
412 
413 	hostpt = 0;
414 	endpt = 0;
415 	winsetaddr(w, "0", 0);
416 	pendingS = 0;
417 	pendingK = 0;
418 	for(;;){
419 		if(debug)
420 			fprint(2, "input range %lud-%lud\n", hostpt, endpt);
421 		e = recvp(w->cevent);
422 		if(debug)
423 			fprint(2, "msg: %C %C %d %d %d %d %q\n",
424 				e->c1 ? e->c1 : ' ', e->c2 ? e->c2 : ' ', e->q0, e->q1, e->flag, e->nb, e->b);
425 		switch(e->c1){
426 		default:
427 		Unknown:
428 			fprint(2, "unknown message %c%c\n", e->c1, e->c2);
429 			break;
430 
431 		case 'C':	/* input needed for /dev/cons */
432 			if(pendingS)
433 				pendingK = 1;
434 			else
435 				hostpt += sendinput(w, hostpt, &endpt);
436 			break;
437 
438 		case 'S':	/* output to stdout */
439 			sprint(tmp, "#%lud", hostpt);
440 			winsetaddr(w, tmp, 0);
441 			write(w->data, e->b, e->nb);
442 			pendingS += utfnlen(e->b, e->nb);
443 			break;
444 
445 		case 'E':	/* write to tag or body; body happens due to sendit */
446 			delta = e->q1-e->q0;
447 			if(e->c2=='I'){
448 				endpt += delta;
449 				if(e->q0 < hostpt)
450 					hostpt += delta;
451 				else
452 					hostpt += sendinput(w, hostpt, &endpt);
453 				break;
454 			}
455 			if(!islower(e->c2))
456 				fprint(2, "win msg: %C %C %d %d %d %d %q\n",
457 					e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
458 			break;
459 
460 		case 'F':	/* generated by our actions (specifically case 'S' above) */
461 			delta = e->q1-e->q0;
462 			if(e->c2=='D'){
463 				/* we know about the delete by _sendinput */
464 				break;
465 			}
466 			if(e->c2=='I'){
467 				pendingS -= e->q1 - e->q0;
468 				if(pendingS < 0)
469 					fprint(2, "win: pendingS = %d\n", pendingS);
470 				if(e->q0 != hostpt)
471 					fprint(2, "win: insert at %d expected %lud\n", e->q0, hostpt);
472 				endpt += delta;
473 				hostpt += delta;
474 				sendp(writechan, nil);
475 				if(pendingS == 0 && pendingK){
476 					pendingK = 0;
477 					hostpt += sendinput(w, hostpt, &endpt);
478 				}
479 				break;
480 			}
481 			if(!islower(e->c2))
482 				fprint(2, "win msg: %C %C %d %d %d %d %q\n",
483 					e->c1, e->c2, e->q0, e->q1, e->flag, e->nb, e->b);
484 			break;
485 
486 		case 'K':
487 			delta = e->q1-e->q0;
488 			switch(e->c2){
489 			case 'D':
490 				endpt -= delta;
491 				if(e->q1 < hostpt)
492 					hostpt -= delta;
493 				else if(e->q0 < hostpt)
494 					hostpt = e->q0;
495 				break;
496 			case 'I':
497 				delta = e->q1 - e->q0;
498 				endpt += delta;
499 				if(endpt < e->q1)	/* just in case */
500 					endpt = e->q1;
501 				if(e->q0 < hostpt)
502 					hostpt += delta;
503 				if(e->nr>0 && e->r[e->nr-1]==0x7F){
504 					write(notepg, "interrupt", 9);
505 					hostpt = endpt;
506 					break;
507 				}
508 				if(e->q0 >= hostpt
509 				&& hasboundary(e->r, e->nr)){
510 					/*
511 					 * If we are between the S message (which
512 					 * we processed by inserting text in the
513 					 * window) and the F message notifying us
514 					 * that the text has been inserted, then our
515 					 * impression of the hostpt and acme's
516 					 * may be different.  This could be seen if you
517 					 * hit enter a bunch of times in a con
518 					 * session.  To work around the unreliability,
519 					 * only send input if we don't have an S pending.
520 					 * The same race occurs between when a character
521 					 * is typed and when we get notice of it, but
522 					 * since characters tend to be typed at the end
523 					 * of the buffer, we don't run into it.  There's
524 					 * no workaround possible for this typing race,
525 					 * since we can't tell when the user has typed
526 					 * something but we just haven't been notified.
527 					 */
528 					if(pendingS)
529 						pendingK = 1;
530 					else
531 						hostpt += sendinput(w, hostpt, &endpt);
532 				}
533 				break;
534 			}
535 			break;
536 
537 		case 'M':	/* mouse */
538 			delta = e->q1-e->q0;
539 			switch(e->c2){
540 			case 'x':
541 			case 'X':
542 				execevent(w, e, command);
543 				break;
544 
545 			case 'l':	/* reflect all searches back to acme */
546 			case 'L':
547 				if(e->flag & 2)
548 					recvp(w->cevent);
549 				winwriteevent(w, e);
550 				break;
551 
552 			case 'I':
553 				endpt += delta;
554 				if(e->q0 < hostpt)
555 					hostpt += delta;
556 				else
557 					hostpt += sendinput(w, hostpt, &endpt);
558 				break;
559 
560 			case 'D':
561 				endpt -= delta;
562 				if(e->q1 < hostpt)
563 					hostpt -= delta;
564 				else if(e->q0 < hostpt)
565 					hostpt = e->q0;
566 				break;
567 			case 'd':	/* modify away; we don't care */
568 			case 'i':
569 				break;
570 
571 			default:
572 				goto Unknown;
573 			}
574 		}
575 	}
576 }
577 
578 enum
579 {
580 	NARGS		= 100,
581 	NARGCHAR	= 8*1024,
582 	EXECSTACK 	= STACK+(NARGS+1)*sizeof(char*)+NARGCHAR
583 };
584 
585 struct Exec
586 {
587 	char		**argv;
588 	Channel	*cpid;
589 };
590 
591 int
592 lookinbin(char *s)
593 {
594 	if(s[0] == '/')
595 		return 0;
596 	if(s[0]=='.' && s[1]=='/')
597 		return 0;
598 	if(s[0]=='.' && s[1]=='.' && s[2]=='/')
599 		return 0;
600 	return 1;
601 }
602 
603 /* adapted from mail.  not entirely free of details from that environment */
604 void
605 execproc(void *v)
606 {
607 	struct Exec *e;
608 	char *cmd, **av;
609 	Channel *cpid;
610 
611 	e = v;
612 	rfork(RFCFDG|RFNOTEG);
613 	av = e->argv;
614 	close(0);
615 	open("/dev/cons", OREAD);
616 	close(1);
617 	open("/dev/cons", OWRITE);
618 	dup(1, 2);
619 	cpid = e->cpid;
620 	free(e);
621 	procexec(cpid, av[0], av);
622 	if(lookinbin(av[0])){
623 		cmd = estrstrdup("/bin/", av[0]);
624 		procexec(cpid, cmd, av);
625 	}
626 	sendul(cpid, 0UL);
627 	threadexits("can't exec");
628 }
629 
630 void
631 startcmd(char *argv[], int *notepg)
632 {
633 	struct Exec *e;
634 	Channel *cpid;
635 	char buf[64];
636 	int pid;
637 
638 	e = emalloc(sizeof(struct Exec));
639 	e->argv = argv;
640 	cpid = chancreate(sizeof(ulong), 0);
641 	e->cpid = cpid;
642 	sprint(buf, "/mnt/wsys/%d", win->id);
643 	bind(buf, "/dev/acme", MREPL);
644 	proccreate(execproc, e, EXECSTACK);
645 	do
646 		pid = recvul(cpid);
647 	while(pid == -1);
648 	if(pid == 0){
649 		error("can't exec %s: %r", argv[0]);
650 		threadexitsall("can't exec");
651 	}
652 	sprint(buf, "/proc/%d/notepg", pid);
653 	*notepg = open(buf, OWRITE);
654 }
655