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