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