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