plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

acme.c (24333B)


      1 #include <u.h>
      2 #include <libc.h>
      3 #include <draw.h>
      4 #include <thread.h>
      5 #include <cursor.h>
      6 #include <mouse.h>
      7 #include <keyboard.h>
      8 #include <frame.h>
      9 #include <fcall.h>
     10 #include <plumb.h>
     11 #include <libsec.h>
     12 #include "dat.h"
     13 #include "fns.h"
     14 	/* for generating syms in mkfile only: */
     15 	#include <bio.h>
     16 	#include "edit.h"
     17 
     18 void	mousethread(void*);
     19 void	keyboardthread(void*);
     20 void	waitthread(void*);
     21 void	xfidallocthread(void*);
     22 void	newwindowthread(void*);
     23 void	plumbproc(void*);
     24 int	timefmt(Fmt*);
     25 
     26 Reffont	**fontcache;
     27 int		nfontcache;
     28 char		wdir[512] = ".";
     29 Reffont	*reffonts[2];
     30 int		snarffd = -1;
     31 int		mainpid;
     32 int		swapscrollbuttons = FALSE;
     33 char		*mtpt;
     34 
     35 enum{
     36 	NSnarf = 1000	/* less than 1024, I/O buffer size */
     37 };
     38 Rune	snarfrune[NSnarf+1];
     39 
     40 char		*fontnames[2] =
     41 {
     42 	"/lib/font/bit/lucsans/euro.8.font",
     43 	"/lib/font/bit/lucm/unicode.9.font"
     44 };
     45 
     46 Command *command;
     47 
     48 void	shutdownthread(void*);
     49 void	acmeerrorinit(void);
     50 void	readfile(Column*, char*);
     51 static int	shutdown(void*, char*);
     52 
     53 void
     54 derror(Display *d, char *errorstr)
     55 {
     56 	USED(d);
     57 	error(errorstr);
     58 }
     59 
     60 void
     61 threadmain(int argc, char *argv[])
     62 {
     63 	int i;
     64 	char *p, *loadfile;
     65 	Column *c;
     66 	int ncol;
     67 	Display *d;
     68 
     69 	rfork(RFENVG|RFNAMEG);
     70 
     71 	ncol = -1;
     72 
     73 	loadfile = nil;
     74 	ARGBEGIN{
     75 	case 'D':
     76 		{extern int _threaddebuglevel;
     77 		_threaddebuglevel = ~0;
     78 		}
     79 		break;
     80 	case 'a':
     81 		globalautoindent = TRUE;
     82 		break;
     83 	case 'b':
     84 		bartflag = TRUE;
     85 		break;
     86 	case 'c':
     87 		p = ARGF();
     88 		if(p == nil)
     89 			goto Usage;
     90 		ncol = atoi(p);
     91 		if(ncol <= 0)
     92 			goto Usage;
     93 		break;
     94 	case 'f':
     95 		fontnames[0] = ARGF();
     96 		if(fontnames[0] == nil)
     97 			goto Usage;
     98 		break;
     99 	case 'F':
    100 		fontnames[1] = ARGF();
    101 		if(fontnames[1] == nil)
    102 			goto Usage;
    103 		break;
    104 	case 'l':
    105 		loadfile = ARGF();
    106 		if(loadfile == nil)
    107 			goto Usage;
    108 		break;
    109 	case 'm':
    110 		mtpt = ARGF();
    111 		if(mtpt == nil)
    112 			goto Usage;
    113 		break;
    114 	case 'r':
    115 		swapscrollbuttons = TRUE;
    116 		break;
    117 	case 'W':
    118 		winsize = ARGF();
    119 		if(winsize == nil)
    120 			goto Usage;
    121 		break;
    122 	default:
    123 	Usage:
    124 		fprint(2, "usage: acme -a -c ncol -f fontname -F fixedwidthfontname -l loadfile -W winsize\n");
    125 		threadexitsall("usage");
    126 	}ARGEND
    127 
    128 	fontnames[0] = estrdup(fontnames[0]);
    129 	fontnames[1] = estrdup(fontnames[1]);
    130 
    131 	quotefmtinstall();
    132 	fmtinstall('t', timefmt);
    133 
    134 	cputype = getenv("cputype");
    135 	objtype = getenv("objtype");
    136 	home = getenv("HOME");
    137 	acmeshell = getenv("acmeshell");
    138 	if(acmeshell && *acmeshell == '\0')
    139 		acmeshell = nil;
    140 	p = getenv("tabstop");
    141 	if(p != nil){
    142 		maxtab = strtoul(p, nil, 0);
    143 		free(p);
    144 	}
    145 	if(maxtab == 0)
    146 		maxtab = 4;
    147 	if(loadfile)
    148 		rowloadfonts(loadfile);
    149 	putenv("font", fontnames[0]);
    150 	snarffd = open("/dev/snarf", OREAD|OCEXEC);
    151 /*
    152 	if(cputype){
    153 		sprint(buf, "/acme/bin/%s", cputype);
    154 		bind(buf, "/bin", MBEFORE);
    155 	}
    156 	bind("/acme/bin", "/bin", MBEFORE);
    157 */
    158 	getwd(wdir, sizeof wdir);
    159 
    160 /*
    161 	if(geninitdraw(nil, derror, fontnames[0], "acme", nil, Refnone) < 0){
    162 		fprint(2, "acme: can't open display: %r\n");
    163 		threadexitsall("geninitdraw");
    164 	}
    165 */
    166 	if(initdraw(derror, fontnames[0], "acme") < 0){
    167 		fprint(2, "acme: can't open display: %r\n");
    168 		threadexitsall("initdraw");
    169 	}
    170 
    171 	d = display;
    172 	font = d->defaultfont;
    173 /*assert(font); */
    174 
    175 	reffont.f = font;
    176 	reffonts[0] = &reffont;
    177 	incref(&reffont.ref);	/* one to hold up 'font' variable */
    178 	incref(&reffont.ref);	/* one to hold up reffonts[0] */
    179 	fontcache = emalloc(sizeof(Reffont*));
    180 	nfontcache = 1;
    181 	fontcache[0] = &reffont;
    182 
    183 	iconinit();
    184 	timerinit();
    185 	rxinit();
    186 
    187 	cwait = threadwaitchan();
    188 	ccommand = chancreate(sizeof(Command**), 0);
    189 	ckill = chancreate(sizeof(Rune*), 0);
    190 	cxfidalloc = chancreate(sizeof(Xfid*), 0);
    191 	cxfidfree = chancreate(sizeof(Xfid*), 0);
    192 	cnewwindow = chancreate(sizeof(Channel*), 0);
    193 	cerr = chancreate(sizeof(char*), 0);
    194 	cedit = chancreate(sizeof(int), 0);
    195 	cexit = chancreate(sizeof(int), 0);
    196 	cwarn = chancreate(sizeof(void*), 1);
    197 	if(cwait==nil || ccommand==nil || ckill==nil || cxfidalloc==nil || cxfidfree==nil || cerr==nil || cexit==nil || cwarn==nil){
    198 		fprint(2, "acme: can't create initial channels: %r\n");
    199 		threadexitsall("channels");
    200 	}
    201 	chansetname(ccommand, "ccommand");
    202 	chansetname(ckill, "ckill");
    203 	chansetname(cxfidalloc, "cxfidalloc");
    204 	chansetname(cxfidfree, "cxfidfree");
    205 	chansetname(cnewwindow, "cnewwindow");
    206 	chansetname(cerr, "cerr");
    207 	chansetname(cedit, "cedit");
    208 	chansetname(cexit, "cexit");
    209 	chansetname(cwarn, "cwarn");
    210 
    211 	mousectl = initmouse(nil, screen);
    212 	if(mousectl == nil){
    213 		fprint(2, "acme: can't initialize mouse: %r\n");
    214 		threadexitsall("mouse");
    215 	}
    216 	mouse = &mousectl->m;
    217 	keyboardctl = initkeyboard(nil);
    218 	if(keyboardctl == nil){
    219 		fprint(2, "acme: can't initialize keyboard: %r\n");
    220 		threadexitsall("keyboard");
    221 	}
    222 	mainpid = getpid();
    223 	startplumbing();
    224 /*
    225 	plumbeditfd = plumbopen("edit", OREAD|OCEXEC);
    226 	if(plumbeditfd < 0)
    227 		fprint(2, "acme: can't initialize plumber: %r\n");
    228 	else{
    229 		cplumb = chancreate(sizeof(Plumbmsg*), 0);
    230 		threadcreate(plumbproc, nil, STACK);
    231 	}
    232 	plumbsendfd = plumbopen("send", OWRITE|OCEXEC);
    233 */
    234 
    235 	fsysinit();
    236 
    237 	#define	WPERCOL	8
    238 	disk = diskinit();
    239 	if(!loadfile || !rowload(&row, loadfile, TRUE)){
    240 		rowinit(&row, screen->clipr);
    241 		if(ncol < 0){
    242 			if(argc == 0)
    243 				ncol = 2;
    244 			else{
    245 				ncol = (argc+(WPERCOL-1))/WPERCOL;
    246 				if(ncol < 2)
    247 					ncol = 2;
    248 			}
    249 		}
    250 		if(ncol == 0)
    251 			ncol = 2;
    252 		for(i=0; i<ncol; i++){
    253 			c = rowadd(&row, nil, -1);
    254 			if(c==nil && i==0)
    255 				error("initializing columns");
    256 		}
    257 		c = row.col[row.ncol-1];
    258 		if(argc == 0)
    259 			readfile(c, wdir);
    260 		else
    261 			for(i=0; i<argc; i++){
    262 				p = utfrrune(argv[i], '/');
    263 				if((p!=nil && strcmp(p, "/guide")==0) || i/WPERCOL>=row.ncol)
    264 					readfile(c, argv[i]);
    265 				else
    266 					readfile(row.col[i/WPERCOL], argv[i]);
    267 			}
    268 	}
    269 	flushimage(display, 1);
    270 
    271 	acmeerrorinit();
    272 	threadcreate(keyboardthread, nil, STACK);
    273 	threadcreate(mousethread, nil, STACK);
    274 	threadcreate(waitthread, nil, STACK);
    275 	threadcreate(xfidallocthread, nil, STACK);
    276 	threadcreate(newwindowthread, nil, STACK);
    277 /*	threadcreate(shutdownthread, nil, STACK); */
    278 	threadnotify(shutdown, 1);
    279 	recvul(cexit);
    280 	killprocs();
    281 	threadexitsall(nil);
    282 }
    283 
    284 void
    285 readfile(Column *c, char *s)
    286 {
    287 	Window *w;
    288 	Rune rb[256];
    289 	int nr;
    290 	Runestr rs;
    291 
    292 	w = coladd(c, nil, nil, -1);
    293 	if(s[0] != '/')
    294 		runesnprint(rb, sizeof rb, "%s/%s", wdir, s);
    295 	else
    296 		runesnprint(rb, sizeof rb, "%s", s);
    297 	nr = runestrlen(rb);
    298 	rs = cleanrname(runestr(rb, nr));
    299 	winsetname(w, rs.r, rs.nr);
    300 	textload(&w->body, 0, s, 1);
    301 	w->body.file->mod = FALSE;
    302 	w->dirty = FALSE;
    303 	winsettag(w);
    304 	winresize(w, w->r, FALSE, TRUE);
    305 	textscrdraw(&w->body);
    306 	textsetselect(&w->tag, w->tag.file->b.nc, w->tag.file->b.nc);
    307 	xfidlog(w, "new");
    308 }
    309 
    310 char *ignotes[] = {
    311 	"sys: write on closed pipe",
    312 	"sys: ttin",
    313 	"sys: ttou",
    314 	"sys: tstp",
    315 	nil
    316 };
    317 
    318 char *oknotes[] ={
    319 	"delete",
    320 	"hangup",
    321 	"kill",
    322 	"exit",
    323 	nil
    324 };
    325 
    326 int	dumping;
    327 
    328 static int
    329 shutdown(void *v, char *msg)
    330 {
    331 	int i;
    332 
    333 	USED(v);
    334 
    335 	for(i=0; ignotes[i]; i++)
    336 		if(strncmp(ignotes[i], msg, strlen(ignotes[i])) == 0)
    337 			return 1;
    338 
    339 	killprocs();
    340 	if(!dumping && strcmp(msg, "kill")!=0 && strcmp(msg, "exit")!=0 && getpid()==mainpid){
    341 		dumping = TRUE;
    342 		rowdump(&row, nil);
    343 	}
    344 	for(i=0; oknotes[i]; i++)
    345 		if(strncmp(oknotes[i], msg, strlen(oknotes[i])) == 0)
    346 			threadexitsall(msg);
    347 	print("acme: %s\n", msg);
    348 	return 0;
    349 }
    350 
    351 /*
    352 void
    353 shutdownthread(void *v)
    354 {
    355 	char *msg;
    356 	Channel *c;
    357 
    358 	USED(v);
    359 
    360 	threadsetname("shutdown");
    361 	c = threadnotechan();
    362 	while((msg = recvp(c)) != nil)
    363 		shutdown(nil, msg);
    364 }
    365 */
    366 
    367 void
    368 killprocs(void)
    369 {
    370 	Command *c;
    371 
    372 	fsysclose();
    373 /*	if(display) */
    374 /*		flushimage(display, 1); */
    375 
    376 	for(c=command; c; c=c->next)
    377 		postnote(PNGROUP, c->pid, "hangup");
    378 }
    379 
    380 static int errorfd;
    381 int erroutfd;
    382 
    383 void
    384 acmeerrorproc(void *v)
    385 {
    386 	char *buf;
    387 	int n;
    388 
    389 	USED(v);
    390 	threadsetname("acmeerrorproc");
    391 	buf = emalloc(8192+1);
    392 	while((n=read(errorfd, buf, 8192)) >= 0){
    393 		buf[n] = '\0';
    394 		sendp(cerr, estrdup(buf));
    395 	}
    396 	free(buf);
    397 }
    398 
    399 void
    400 acmeerrorinit(void)
    401 {
    402 	int pfd[2];
    403 
    404 	if(pipe(pfd) < 0)
    405 		error("can't create pipe");
    406 #if 0
    407 	sprint(acmeerrorfile, "/srv/acme.%s.%d", getuser(), mainpid);
    408 	fd = create(acmeerrorfile, OWRITE, 0666);
    409 	if(fd < 0){
    410 		remove(acmeerrorfile);
    411   		fd = create(acmeerrorfile, OWRITE, 0666);
    412 		if(fd < 0)
    413 			error("can't create acmeerror file");
    414 	}
    415 	sprint(buf, "%d", pfd[0]);
    416 	write(fd, buf, strlen(buf));
    417 	close(fd);
    418 	/* reopen pfd[1] close on exec */
    419 	sprint(buf, "/fd/%d", pfd[1]);
    420 	errorfd = open(buf, OREAD|OCEXEC);
    421 #endif
    422 	fcntl(pfd[0], F_SETFD, FD_CLOEXEC);
    423 	fcntl(pfd[1], F_SETFD, FD_CLOEXEC);
    424 	erroutfd = pfd[0];
    425 	errorfd = pfd[1];
    426 	if(errorfd < 0)
    427 		error("can't re-open acmeerror file");
    428 	proccreate(acmeerrorproc, nil, STACK);
    429 }
    430 
    431 /*
    432 void
    433 plumbproc(void *v)
    434 {
    435 	Plumbmsg *m;
    436 
    437 	USED(v);
    438 	threadsetname("plumbproc");
    439 	for(;;){
    440 		m = threadplumbrecv(plumbeditfd);
    441 		if(m == nil)
    442 			threadexits(nil);
    443 		sendp(cplumb, m);
    444 	}
    445 }
    446 */
    447 
    448 void
    449 keyboardthread(void *v)
    450 {
    451 	Rune r;
    452 	Timer *timer;
    453 	Text *t;
    454 	enum { KTimer, KKey, NKALT };
    455 	static Alt alts[NKALT+1];
    456 
    457 	USED(v);
    458 	alts[KTimer].c = nil;
    459 	alts[KTimer].v = nil;
    460 	alts[KTimer].op = CHANNOP;
    461 	alts[KKey].c = keyboardctl->c;
    462 	alts[KKey].v = &r;
    463 	alts[KKey].op = CHANRCV;
    464 	alts[NKALT].op = CHANEND;
    465 
    466 	timer = nil;
    467 	typetext = nil;
    468 	threadsetname("keyboardthread");
    469 	for(;;){
    470 		switch(alt(alts)){
    471 		case KTimer:
    472 			timerstop(timer);
    473 			t = typetext;
    474 			if(t!=nil && t->what==Tag){
    475 				winlock(t->w, 'K');
    476 				wincommit(t->w, t);
    477 				winunlock(t->w);
    478 				flushimage(display, 1);
    479 			}
    480 			alts[KTimer].c = nil;
    481 			alts[KTimer].op = CHANNOP;
    482 			break;
    483 		case KKey:
    484 		casekeyboard:
    485 			typetext = rowtype(&row, r, mouse->xy);
    486 			t = typetext;
    487 			if(t!=nil && t->col!=nil && !(r==Kdown || r==Kleft || r==Kright))	/* scrolling doesn't change activecol */
    488 				activecol = t->col;
    489 			if(t!=nil && t->w!=nil)
    490 				t->w->body.file->curtext = &t->w->body;
    491 			if(timer != nil)
    492 				timercancel(timer);
    493 			if(t!=nil && t->what==Tag) {
    494 				timer = timerstart(500);
    495 				alts[KTimer].c = timer->c;
    496 				alts[KTimer].op = CHANRCV;
    497 			}else{
    498 				timer = nil;
    499 				alts[KTimer].c = nil;
    500 				alts[KTimer].op = CHANNOP;
    501 			}
    502 			if(nbrecv(keyboardctl->c, &r) > 0)
    503 				goto casekeyboard;
    504 			flushimage(display, 1);
    505 			break;
    506 		}
    507 	}
    508 }
    509 
    510 void
    511 mousethread(void *v)
    512 {
    513 	Text *t, *argt;
    514 	int but;
    515 	uint q0, q1;
    516 	Window *w;
    517 	Plumbmsg *pm;
    518 	Mouse m;
    519 	char *act;
    520 	enum { MResize, MMouse, MPlumb, MWarnings, NMALT };
    521 	enum { Shift = 5 };
    522 	static Alt alts[NMALT+1];
    523 
    524 	USED(v);
    525 	threadsetname("mousethread");
    526 	alts[MResize].c = mousectl->resizec;
    527 	alts[MResize].v = nil;
    528 	alts[MResize].op = CHANRCV;
    529 	alts[MMouse].c = mousectl->c;
    530 	alts[MMouse].v = &mousectl->m;
    531 	alts[MMouse].op = CHANRCV;
    532 	alts[MPlumb].c = cplumb;
    533 	alts[MPlumb].v = &pm;
    534 	alts[MPlumb].op = CHANRCV;
    535 	alts[MWarnings].c = cwarn;
    536 	alts[MWarnings].v = nil;
    537 	alts[MWarnings].op = CHANRCV;
    538 	if(cplumb == nil)
    539 		alts[MPlumb].op = CHANNOP;
    540 	alts[NMALT].op = CHANEND;
    541 
    542 	for(;;){
    543 		qlock(&row.lk);
    544 		flushwarnings();
    545 		qunlock(&row.lk);
    546 		flushimage(display, 1);
    547 		switch(alt(alts)){
    548 		case MResize:
    549 			if(getwindow(display, Refnone) < 0)
    550 				error("attach to window");
    551 			draw(screen, screen->r, display->white, nil, ZP);
    552 			iconinit();
    553 			scrlresize();
    554 			rowresize(&row, screen->clipr);
    555 			break;
    556 		case MPlumb:
    557 			if(strcmp(pm->type, "text") == 0){
    558 				act = plumblookup(pm->attr, "action");
    559 				if(act==nil || strcmp(act, "showfile")==0)
    560 					plumblook(pm);
    561 				else if(strcmp(act, "showdata")==0)
    562 					plumbshow(pm);
    563 			}
    564 			plumbfree(pm);
    565 			break;
    566 		case MWarnings:
    567 			break;
    568 		case MMouse:
    569 			/*
    570 			 * Make a copy so decisions are consistent; mousectl changes
    571 			 * underfoot.  Can't just receive into m because this introduces
    572 			 * another race; see /sys/src/libdraw/mouse.c.
    573 			 */
    574 			m = mousectl->m;
    575 			qlock(&row.lk);
    576 			t = rowwhich(&row, m.xy);
    577 
    578 			if((t!=mousetext && t!=nil && t->w!=nil) &&
    579 				(mousetext==nil || mousetext->w==nil || t->w->id!=mousetext->w->id)) {
    580 				xfidlog(t->w, "focus");
    581 			}
    582 
    583 			if(t!=mousetext && mousetext!=nil && mousetext->w!=nil){
    584 				winlock(mousetext->w, 'M');
    585 				mousetext->eq0 = ~0;
    586 				wincommit(mousetext->w, mousetext);
    587 				winunlock(mousetext->w);
    588 			}
    589 			mousetext = t;
    590 			if(t == nil)
    591 				goto Continue;
    592 			w = t->w;
    593 			if(t==nil || m.buttons==0)
    594 				goto Continue;
    595 			but = 0;
    596 			if(m.buttons == 1)
    597 				but = 1;
    598 			else if(m.buttons == 2)
    599 				but = 2;
    600 			else if(m.buttons == 4)
    601 				but = 3;
    602 			barttext = t;
    603 			if(t->what==Body && ptinrect(m.xy, t->scrollr)){
    604 				if(but){
    605 					if(swapscrollbuttons){
    606 						if(but == 1)
    607 							but = 3;
    608 						else if(but == 3)
    609 							but = 1;
    610 					}
    611 					winlock(w, 'M');
    612 					t->eq0 = ~0;
    613 					textscroll(t, but);
    614 					winunlock(w);
    615 				}
    616 				goto Continue;
    617 			}
    618 			/* scroll buttons, wheels, etc. */
    619 			if(w != nil && (m.buttons & (8|16))){
    620 				if(m.buttons & 8)
    621 					but = Kscrolloneup;
    622 				else
    623 					but = Kscrollonedown;
    624 				winlock(w, 'M');
    625 				t->eq0 = ~0;
    626 				texttype(t, but);
    627 				winunlock(w);
    628 				goto Continue;
    629 			}
    630 			if(ptinrect(m.xy, t->scrollr)){
    631 				if(but){
    632 					if(t->what == Columntag)
    633 						rowdragcol(&row, t->col, but);
    634 					else if(t->what == Tag){
    635 						coldragwin(t->col, t->w, but);
    636 						if(t->w)
    637 							barttext = &t->w->body;
    638 					}
    639 					if(t->col)
    640 						activecol = t->col;
    641 				}
    642 				goto Continue;
    643 			}
    644 			if(m.buttons){
    645 				if(w)
    646 					winlock(w, 'M');
    647 				t->eq0 = ~0;
    648 				if(w)
    649 					wincommit(w, t);
    650 				else
    651 					textcommit(t, TRUE);
    652 				if(m.buttons & 1){
    653 					textselect(t);
    654 					if(w)
    655 						winsettag(w);
    656 					argtext = t;
    657 					seltext = t;
    658 					if(t->col)
    659 						activecol = t->col;	/* button 1 only */
    660 					if(t->w!=nil && t==&t->w->body)
    661 						activewin = t->w;
    662 				}else if(m.buttons & 2){
    663 					if(textselect2(t, &q0, &q1, &argt))
    664 						execute(t, q0, q1, FALSE, argt);
    665 				}else if(m.buttons & (4|(4<<Shift))){
    666 					if(textselect3(t, &q0, &q1))
    667 						look3(t, q0, q1, FALSE, (m.buttons&(4<<Shift))!=0);
    668 				}
    669 				if(w)
    670 					winunlock(w);
    671 				goto Continue;
    672 			}
    673     Continue:
    674 			qunlock(&row.lk);
    675 			break;
    676 		}
    677 	}
    678 }
    679 
    680 /*
    681  * There is a race between process exiting and our finding out it was ever created.
    682  * This structure keeps a list of processes that have exited we haven't heard of.
    683  */
    684 typedef struct Pid Pid;
    685 struct Pid
    686 {
    687 	int	pid;
    688 	char	msg[ERRMAX];
    689 	Pid	*next;
    690 };
    691 
    692 void
    693 waitthread(void *v)
    694 {
    695 	Waitmsg *w;
    696 	Command *c, *lc;
    697 	uint pid;
    698 	int found, ncmd;
    699 	Rune *cmd;
    700 	char *err;
    701 	Text *t;
    702 	Pid *pids, *p, *lastp;
    703 	enum { WErr, WKill, WWait, WCmd, NWALT };
    704 	Alt alts[NWALT+1];
    705 
    706 	USED(v);
    707 	threadsetname("waitthread");
    708 	pids = nil;
    709 	alts[WErr].c = cerr;
    710 	alts[WErr].v = &err;
    711 	alts[WErr].op = CHANRCV;
    712 	alts[WKill].c = ckill;
    713 	alts[WKill].v = &cmd;
    714 	alts[WKill].op = CHANRCV;
    715 	alts[WWait].c = cwait;
    716 	alts[WWait].v = &w;
    717 	alts[WWait].op = CHANRCV;
    718 	alts[WCmd].c = ccommand;
    719 	alts[WCmd].v = &c;
    720 	alts[WCmd].op = CHANRCV;
    721 	alts[NWALT].op = CHANEND;
    722 
    723 	command = nil;
    724 	for(;;){
    725 		switch(alt(alts)){
    726 		case WErr:
    727 			qlock(&row.lk);
    728 			warning(nil, "%s", err);
    729 			free(err);
    730 			flushimage(display, 1);
    731 			qunlock(&row.lk);
    732 			break;
    733 		case WKill:
    734 			found = FALSE;
    735 			ncmd = runestrlen(cmd);
    736 			for(c=command; c; c=c->next){
    737 				/* -1 for blank */
    738 				if(runeeq(c->name, c->nname-1, cmd, ncmd) == TRUE){
    739 					if(postnote(PNGROUP, c->pid, "kill") < 0)
    740 						warning(nil, "kill %S: %r\n", cmd);
    741 					found = TRUE;
    742 				}
    743 			}
    744 			if(!found)
    745 				warning(nil, "Kill: no process %S\n", cmd);
    746 			free(cmd);
    747 			break;
    748 		case WWait:
    749 			pid = w->pid;
    750 			lc = nil;
    751 			for(c=command; c; c=c->next){
    752 				if(c->pid == pid){
    753 					if(lc)
    754 						lc->next = c->next;
    755 					else
    756 						command = c->next;
    757 					break;
    758 				}
    759 				lc = c;
    760 			}
    761 			qlock(&row.lk);
    762 			t = &row.tag;
    763 			textcommit(t, TRUE);
    764 			if(c == nil){
    765 				/* helper processes use this exit status */
    766 				if(strncmp(w->msg, "libthread", 9) != 0){
    767 					p = emalloc(sizeof(Pid));
    768 					p->pid = pid;
    769 					strncpy(p->msg, w->msg, sizeof(p->msg));
    770 					p->next = pids;
    771 					pids = p;
    772 				}
    773 			}else{
    774 				if(search(t, c->name, c->nname, FALSE)){
    775 					textdelete(t, t->q0, t->q1, TRUE);
    776 					textsetselect(t, 0, 0);
    777 				}
    778 				if(w->msg[0])
    779 					warning(c->md, "%.*S: exit %s\n", c->nname-1, c->name, w->msg);
    780 				flushimage(display, 1);
    781 			}
    782 			qunlock(&row.lk);
    783 			free(w);
    784     Freecmd:
    785 			if(c){
    786 				if(c->iseditcmd)
    787 					sendul(cedit, 0);
    788 				free(c->text);
    789 				free(c->name);
    790 				fsysdelid(c->md);
    791 				free(c);
    792 			}
    793 			break;
    794 		case WCmd:
    795 			/* has this command already exited? */
    796 			lastp = nil;
    797 			for(p=pids; p!=nil; p=p->next){
    798 				if(p->pid == c->pid){
    799 					if(p->msg[0])
    800 						warning(c->md, "%s\n", p->msg);
    801 					if(lastp == nil)
    802 						pids = p->next;
    803 					else
    804 						lastp->next = p->next;
    805 					free(p);
    806 					goto Freecmd;
    807 				}
    808 				lastp = p;
    809 			}
    810 			c->next = command;
    811 			command = c;
    812 			qlock(&row.lk);
    813 			t = &row.tag;
    814 			textcommit(t, TRUE);
    815 			textinsert(t, 0, c->name, c->nname, TRUE);
    816 			textsetselect(t, 0, 0);
    817 			flushimage(display, 1);
    818 			qunlock(&row.lk);
    819 			break;
    820 		}
    821 	}
    822 }
    823 
    824 void
    825 xfidallocthread(void *v)
    826 {
    827 	Xfid *xfree, *x;
    828 	enum { Alloc, Free, N };
    829 	static Alt alts[N+1];
    830 
    831 	USED(v);
    832 	threadsetname("xfidallocthread");
    833 	alts[Alloc].c = cxfidalloc;
    834 	alts[Alloc].v = nil;
    835 	alts[Alloc].op = CHANRCV;
    836 	alts[Free].c = cxfidfree;
    837 	alts[Free].v = &x;
    838 	alts[Free].op = CHANRCV;
    839 	alts[N].op = CHANEND;
    840 
    841 	xfree = nil;
    842 	for(;;){
    843 		switch(alt(alts)){
    844 		case Alloc:
    845 			x = xfree;
    846 			if(x)
    847 				xfree = x->next;
    848 			else{
    849 				x = emalloc(sizeof(Xfid));
    850 				x->c = chancreate(sizeof(void(*)(Xfid*)), 0);
    851 				chansetname(x->c, "xc%p", x->c);
    852 				x->arg = x;
    853 				threadcreate(xfidctl, x->arg, STACK);
    854 			}
    855 			sendp(cxfidalloc, x);
    856 			break;
    857 		case Free:
    858 			x->next = xfree;
    859 			xfree = x;
    860 			break;
    861 		}
    862 	}
    863 }
    864 
    865 /* this thread, in the main proc, allows fsysproc to get a window made without doing graphics */
    866 void
    867 newwindowthread(void *v)
    868 {
    869 	Window *w;
    870 
    871 	USED(v);
    872 	threadsetname("newwindowthread");
    873 
    874 	for(;;){
    875 		/* only fsysproc is talking to us, so synchronization is trivial */
    876 		recvp(cnewwindow);
    877 		w = makenewwindow(nil);
    878 		winsettag(w);
    879 		xfidlog(w, "new");
    880 		sendp(cnewwindow, w);
    881 	}
    882 }
    883 
    884 Reffont*
    885 rfget(int fix, int save, int setfont, char *name)
    886 {
    887 	Reffont *r;
    888 	Font *f;
    889 	int i;
    890 
    891 	r = nil;
    892 	if(name == nil){
    893 		name = fontnames[fix];
    894 		r = reffonts[fix];
    895 	}
    896 	if(r == nil){
    897 		for(i=0; i<nfontcache; i++)
    898 			if(strcmp(name, fontcache[i]->f->name) == 0){
    899 				r = fontcache[i];
    900 				goto Found;
    901 			}
    902 		f = openfont(display, name);
    903 		if(f == nil){
    904 			warning(nil, "can't open font file %s: %r\n", name);
    905 			return nil;
    906 		}
    907 		r = emalloc(sizeof(Reffont));
    908 		r->f = f;
    909 		fontcache = erealloc(fontcache, (nfontcache+1)*sizeof(Reffont*));
    910 		fontcache[nfontcache++] = r;
    911 	}
    912     Found:
    913 	if(save){
    914 		incref(&r->ref);
    915 		if(reffonts[fix])
    916 			rfclose(reffonts[fix]);
    917 		reffonts[fix] = r;
    918 		if(name != fontnames[fix]){
    919 			free(fontnames[fix]);
    920 			fontnames[fix] = estrdup(name);
    921 		}
    922 	}
    923 	if(setfont){
    924 		reffont.f = r->f;
    925 		incref(&r->ref);
    926 		rfclose(reffonts[0]);
    927 		font = r->f;
    928 		reffonts[0] = r;
    929 		incref(&r->ref);
    930 		iconinit();
    931 	}
    932 	incref(&r->ref);
    933 	return r;
    934 }
    935 
    936 void
    937 rfclose(Reffont *r)
    938 {
    939 	int i;
    940 
    941 	if(decref(&r->ref) == 0){
    942 		for(i=0; i<nfontcache; i++)
    943 			if(r == fontcache[i])
    944 				break;
    945 		if(i >= nfontcache)
    946 			warning(nil, "internal error: can't find font in cache\n");
    947 		else{
    948 			nfontcache--;
    949 			memmove(fontcache+i, fontcache+i+1, (nfontcache-i)*sizeof(Reffont*));
    950 		}
    951 		freefont(r->f);
    952 		free(r);
    953 	}
    954 }
    955 
    956 Cursor boxcursor = {
    957 	{-7, -7},
    958 	{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    959 	 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
    960 	 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
    961 	 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
    962 	{0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
    963 	 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
    964 	 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
    965 	 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
    966 };
    967 
    968 Cursor2 boxcursor2 = {
    969 	{-15, -15},
    970 	{0xFF, 0xFF, 0xFF, 0xFF,
    971 	 0xFF, 0xFF, 0xFF, 0xFF,
    972 	 0xFF, 0xFF, 0xFF, 0xFF,
    973 	 0xFF, 0xFF, 0xFF, 0xFF,
    974 	 0xFF, 0xFF, 0xFF, 0xFF,
    975 	 0xFF, 0xFF, 0xFF, 0xFF,
    976 	 0xFF, 0xFF, 0xFF, 0xFF,
    977 	 0xFF, 0xFF, 0xFF, 0xFF,
    978 	 0xFF, 0xFF, 0xFF, 0xFF,
    979 	 0xFF, 0xFF, 0xFF, 0xFF,
    980 	 0xFF, 0xC0, 0x03, 0xFF,
    981 	 0xFF, 0xC0, 0x03, 0xFF,
    982 	 0xFF, 0xC0, 0x03, 0xFF,
    983 	 0xFF, 0xC0, 0x03, 0xFF,
    984 	 0xFF, 0xC0, 0x03, 0xFF,
    985 	 0xFF, 0xC0, 0x03, 0xFF,
    986 	 0xFF, 0xC0, 0x03, 0xFF,
    987 	 0xFF, 0xC0, 0x03, 0xFF,
    988 	 0xFF, 0xC0, 0x03, 0xFF,
    989 	 0xFF, 0xC0, 0x03, 0xFF,
    990 	 0xFF, 0xC0, 0x03, 0xFF,
    991 	 0xFF, 0xC0, 0x03, 0xFF,
    992 	 0xFF, 0xFF, 0xFF, 0xFF,
    993 	 0xFF, 0xFF, 0xFF, 0xFF,
    994 	 0xFF, 0xFF, 0xFF, 0xFF,
    995 	 0xFF, 0xFF, 0xFF, 0xFF,
    996 	 0xFF, 0xFF, 0xFF, 0xFF,
    997 	 0xFF, 0xFF, 0xFF, 0xFF,
    998 	 0xFF, 0xFF, 0xFF, 0xFF,
    999 	 0xFF, 0xFF, 0xFF, 0xFF,
   1000 	 0xFF, 0xFF, 0xFF, 0xFF,
   1001 	 0xFF, 0xFF, 0xFF, 0xFF},
   1002 	{0x00, 0x00, 0x00, 0x00,
   1003 	 0x00, 0x00, 0x00, 0x00,
   1004 	 0x3F, 0xFF, 0xFF, 0xFC,
   1005 	 0x3F, 0xFF, 0xFF, 0xFC,
   1006 	 0x3F, 0xFF, 0xFF, 0xFC,
   1007 	 0x3F, 0xFF, 0xFF, 0xFC,
   1008 	 0x3F, 0xFF, 0xFF, 0xFC,
   1009 	 0x3F, 0xFF, 0xFF, 0xFC,
   1010 	 0x3F, 0x00, 0x00, 0xFC,
   1011 	 0x3F, 0x00, 0x00, 0xFC,
   1012 	 0x3F, 0x00, 0x00, 0xFC,
   1013 	 0x3F, 0x00, 0x00, 0xFC,
   1014 	 0x3F, 0x00, 0x00, 0xFC,
   1015 	 0x3F, 0x00, 0x00, 0xFC,
   1016 	 0x3F, 0x00, 0x00, 0xFC,
   1017 	 0x3F, 0x00, 0x00, 0xFC,
   1018 	 0x3F, 0x00, 0x00, 0xFC,
   1019 	 0x3F, 0x00, 0x00, 0xFC,
   1020 	 0x3F, 0x00, 0x00, 0xFC,
   1021 	 0x3F, 0x00, 0x00, 0xFC,
   1022 	 0x3F, 0x00, 0x00, 0xFC,
   1023 	 0x3F, 0x00, 0x00, 0xFC,
   1024 	 0x3F, 0x00, 0x00, 0xFC,
   1025 	 0x3F, 0x00, 0x00, 0xFC,
   1026 	 0x3F, 0xFF, 0xFF, 0xFC,
   1027 	 0x3F, 0xFF, 0xFF, 0xFC,
   1028 	 0x3F, 0xFF, 0xFF, 0xFC,
   1029 	 0x3F, 0xFF, 0xFF, 0xFC,
   1030 	 0x3F, 0xFF, 0xFF, 0xFC,
   1031 	 0x3F, 0xFF, 0xFF, 0xFC,
   1032 	 0x00, 0x00, 0x00, 0x00,
   1033 	 0x00, 0x00, 0x00, 0x00}
   1034 };
   1035 
   1036 void
   1037 iconinit(void)
   1038 {
   1039 	Rectangle r;
   1040 	Image *tmp;
   1041 
   1042 	if(tagcols[BACK] == nil) {
   1043 		/* Blue */
   1044 		tagcols[BACK] = allocimagemix(display, DPalebluegreen, DWhite);
   1045 		tagcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPalegreygreen);
   1046 		tagcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue);
   1047 		tagcols[TEXT] = display->black;
   1048 		tagcols[HTEXT] = display->black;
   1049 
   1050 		/* Yellow */
   1051 		textcols[BACK] = allocimagemix(display, DPaleyellow, DWhite);
   1052 		textcols[HIGH] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DDarkyellow);
   1053 		textcols[BORD] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellowgreen);
   1054 		textcols[TEXT] = display->black;
   1055 		textcols[HTEXT] = display->black;
   1056 	}
   1057 
   1058 	r = Rect(0, 0, Scrollwid, font->height+1);
   1059 	if(button && eqrect(r, button->r))
   1060 		return;
   1061 
   1062 	if(button){
   1063 		freeimage(button);
   1064 		freeimage(modbutton);
   1065 		freeimage(colbutton);
   1066 	}
   1067 
   1068 	button = allocimage(display, r, screen->chan, 0, DNofill);
   1069 	draw(button, r, tagcols[BACK], nil, r.min);
   1070 	border(button, r, ButtonBorder, tagcols[BORD], ZP);
   1071 
   1072 	r = button->r;
   1073 	modbutton = allocimage(display, r, screen->chan, 0, DNofill);
   1074 	draw(modbutton, r, tagcols[BACK], nil, r.min);
   1075 	border(modbutton, r, ButtonBorder, tagcols[BORD], ZP);
   1076 	r = insetrect(r, ButtonBorder);
   1077 	tmp = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DMedblue);
   1078 	draw(modbutton, r, tmp, nil, ZP);
   1079 	freeimage(tmp);
   1080 
   1081 	r = button->r;
   1082 	colbutton = allocimage(display, r, screen->chan, 0, DPurpleblue);
   1083 
   1084 	but2col = allocimage(display, r, screen->chan, 1, 0xAA0000FF);
   1085 	but3col = allocimage(display, r, screen->chan, 1, 0x006600FF);
   1086 }
   1087 
   1088 /*
   1089  * /dev/snarf updates when the file is closed, so we must open our own
   1090  * fd here rather than use snarffd
   1091  */
   1092 
   1093 /* rio truncates larges snarf buffers, so this avoids using the
   1094  * service if the string is huge */
   1095 
   1096 #define MAXSNARF 100*1024
   1097 
   1098 void
   1099 acmeputsnarf(void)
   1100 {
   1101 	int i, n;
   1102 	Fmt f;
   1103 	char *s;
   1104 
   1105 	if(snarfbuf.nc==0)
   1106 		return;
   1107 	if(snarfbuf.nc > MAXSNARF)
   1108 		return;
   1109 
   1110 	fmtstrinit(&f);
   1111 	for(i=0; i<snarfbuf.nc; i+=n){
   1112 		n = snarfbuf.nc-i;
   1113 		if(n >= NSnarf)
   1114 			n = NSnarf;
   1115 		bufread(&snarfbuf, i, snarfrune, n);
   1116 		if(fmtprint(&f, "%.*S", n, snarfrune) < 0)
   1117 			break;
   1118 	}
   1119 	s = fmtstrflush(&f);
   1120 	if(s && s[0])
   1121 		putsnarf(s);
   1122 	free(s);
   1123 }
   1124 
   1125 void
   1126 acmegetsnarf(void)
   1127 {
   1128 	char *s;
   1129 	int nb, nr, nulls, len;
   1130 	Rune *r;
   1131 
   1132 	s = getsnarf();
   1133 	if(s == nil || s[0]==0){
   1134 		free(s);
   1135 		return;
   1136 	}
   1137 
   1138 	len = strlen(s);
   1139 	r = runemalloc(len+1);
   1140 	cvttorunes(s, len, r, &nb, &nr, &nulls);
   1141 	bufreset(&snarfbuf);
   1142 	bufinsert(&snarfbuf, 0, r, nr);
   1143 	free(r);
   1144 	free(s);
   1145 }
   1146 
   1147 int
   1148 ismtpt(char *file)
   1149 {
   1150 	int n;
   1151 
   1152 	if(mtpt == nil)
   1153 		return 0;
   1154 
   1155 	/* This is not foolproof, but it will stop a lot of them. */
   1156 	n = strlen(mtpt);
   1157 	return strncmp(file, mtpt, n) == 0 && ((n > 0 && mtpt[n-1] == '/') || file[n] == '/' || file[n] == 0);
   1158 }
   1159 
   1160 int
   1161 timefmt(Fmt *f)
   1162 {
   1163 	Tm *tm;
   1164 
   1165 	tm = localtime(va_arg(f->args, ulong));
   1166 	return fmtprint(f, "%04d/%02d/%02d %02d:%02d:%02d",
   1167 		tm->year+1900, tm->mon+1, tm->mday, tm->hour, tm->min, tm->sec);
   1168 }