master xfid.c
ohmu  ·  2026-09-13
   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
  15enum
  16{
  17	Ctlsize	= 5*12
  18};
  19
  20char	Edel[]		= "deleted window";
  21char	Ebadctl[]		= "ill-formed control message";
  22char	Ebadaddr[]	= "bad address syntax";
  23char	Eaddr[]		= "address out of range";
  24char	Einuse[]		= "already in use";
  25char	Ebadevent[]	= "bad event syntax";
  26extern char Eperm[];
  27
  28static
  29void
  30clampaddr(Window *w)
  31{
  32	if(w->addr.q0 < 0)
  33		w->addr.q0 = 0;
  34	if(w->addr.q1 < 0)
  35		w->addr.q1 = 0;
  36	if(w->addr.q0 > w->body.file->b.nc)
  37		w->addr.q0 = w->body.file->b.nc;
  38	if(w->addr.q1 > w->body.file->b.nc)
  39		w->addr.q1 = w->body.file->b.nc;
  40}
  41
  42void
  43xfidctl(void *arg)
  44{
  45	Xfid *x;
  46	void (*f)(Xfid*);
  47
  48	threadsetname("xfidctlthread");
  49	x = arg;
  50	for(;;){
  51		f = (void(*)(Xfid*))recvp(x->c);
  52		(*f)(x);
  53		flushimage(display, 1);
  54		sendp(cxfidfree, x);
  55	}
  56}
  57
  58void
  59xfidflush(Xfid *x)
  60{
  61	Fcall fc;
  62	int i, j;
  63	Window *w;
  64	Column *c;
  65	Xfid *wx;
  66
  67	xfidlogflush(x);
  68
  69	/* search windows for matching tag */
  70	qlock(&row.lk);
  71	for(j=0; j<row.ncol; j++){
  72		c = row.col[j];
  73		for(i=0; i<c->nw; i++){
  74			w = c->w[i];
  75			winlock(w, 'E');
  76			wx = w->eventx;
  77			if(wx!=nil && wx->fcall.tag==x->fcall.oldtag){
  78				w->eventx = nil;
  79				wx->flushed = TRUE;
  80				sendp(wx->c, nil);
  81				winunlock(w);
  82				goto out;
  83			}
  84			winunlock(w);
  85		}
  86	}
  87out:
  88	qunlock(&row.lk);
  89	respond(x, &fc, nil);
  90}
  91
  92void
  93xfidopen(Xfid *x)
  94{
  95	Fcall fc;
  96	Window *w;
  97	Text *t;
  98	char *s;
  99	Rune *r;
 100	int m, n, q, q0, q1;
 101
 102	w = x->f->w;
 103	t = &w->body;
 104	q = FILE(x->f->qid);
 105	if(w){
 106		winlock(w, 'E');
 107		switch(q){
 108		case QWaddr:
 109			if(w->nopen[q]++ == 0){
 110				w->addr = range(0, 0);
 111				w->limit = range(-1,-1);
 112			}
 113			break;
 114		case QWdata:
 115		case QWxdata:
 116			w->nopen[q]++;
 117			break;
 118		case QWevent:
 119			if(w->nopen[q]++ == 0){
 120				if(!w->isdir && w->col!=nil){
 121					w->filemenu = FALSE;
 122					winsettag(w);
 123				}
 124			}
 125			break;
 126		case QWrdsel:
 127			/*
 128			 * Use a temporary file.
 129			 * A pipe would be the obvious, but we can't afford the
 130			 * broken pipe notification.  Using the code to read QWbody
 131			 * is n², which should probably also be fixed.  Even then,
 132			 * though, we'd need to squirrel away the data in case it's
 133			 * modified during the operation, e.g. by |sort
 134			 */
 135			if(w->rdselfd > 0){
 136				winunlock(w);
 137				respond(x, &fc, Einuse);
 138				return;
 139			}
 140			w->rdselfd = tempfile();
 141			if(w->rdselfd < 0){
 142				winunlock(w);
 143				respond(x, &fc, "can't create temp file");
 144				return;
 145			}
 146			w->nopen[q]++;
 147			q0 = t->q0;
 148			q1 = t->q1;
 149			r = fbufalloc();
 150			s = fbufalloc();
 151			while(q0 < q1){
 152				n = q1 - q0;
 153				if(n > BUFSIZE/UTFmax)
 154					n = BUFSIZE/UTFmax;
 155				bufread(&t->file->b, q0, r, n);
 156				m = snprint(s, BUFSIZE+1, "%.*S", n, r);
 157				if(write(w->rdselfd, s, m) != m){
 158					warning(nil, "can't write temp file for pipe command %r\n");
 159					break;
 160				}
 161				q0 += n;
 162			}
 163			fbuffree(s);
 164			fbuffree(r);
 165			break;
 166		case QWwrsel:
 167			w->nopen[q]++;
 168			seq++;
 169			filemark(t->file);
 170			cut(t, t, nil, FALSE, TRUE, nil, 0);
 171			w->wrselrange = range(t->q1, t->q1);
 172			w->nomark = TRUE;
 173			break;
 174		case QWeditout:
 175			if(editing == FALSE){
 176				winunlock(w);
 177				respond(x, &fc, Eperm);
 178				return;
 179			}
 180			if(!canqlock(&w->editoutlk)){
 181				winunlock(w);
 182				respond(x, &fc, Einuse);
 183				return;
 184			}
 185			w->wrselrange = range(t->q1, t->q1);
 186			break;
 187		}
 188		winunlock(w);
 189	}
 190	else{
 191		switch(q){
 192		case Qlog:
 193			xfidlogopen(x);
 194			break;
 195		case Qeditout:
 196			if(!canqlock(&editoutlk)){
 197				respond(x, &fc, Einuse);
 198				return;
 199			}
 200			break;
 201		}
 202	}
 203	fc.qid = x->f->qid;
 204	fc.iounit = messagesize-IOHDRSZ;
 205	x->f->open = TRUE;
 206	respond(x, &fc, nil);
 207}
 208
 209void
 210xfidclose(Xfid *x)
 211{
 212	Fcall fc;
 213	Window *w;
 214	int q;
 215	Text *t;
 216
 217	w = x->f->w;
 218	x->f->busy = FALSE;
 219	x->f->w = nil;
 220	if(x->f->open == FALSE){
 221		if(w != nil)
 222			winclose(w);
 223		respond(x, &fc, nil);
 224		return;
 225	}
 226
 227	q = FILE(x->f->qid);
 228	x->f->open = FALSE;
 229	if(w){
 230		winlock(w, 'E');
 231		switch(q){
 232		case QWctl:
 233			if(w->ctlfid!=~0 && w->ctlfid==x->f->fid){
 234				w->ctlfid = ~0;
 235				qunlock(&w->ctllock);
 236			}
 237			break;
 238		case QWdata:
 239		case QWxdata:
 240			w->nomark = FALSE;
 241			/* fall through */
 242		case QWaddr:
 243		case QWevent:	/* BUG: do we need to shut down Xfid? */
 244			if(--w->nopen[q] == 0){
 245				if(q == QWdata || q == QWxdata)
 246					w->nomark = FALSE;
 247				if(q==QWevent && !w->isdir && w->col!=nil){
 248					w->filemenu = TRUE;
 249					winsettag(w);
 250				}
 251				if(q == QWevent){
 252					free(w->dumpstr);
 253					free(w->dumpdir);
 254					w->dumpstr = nil;
 255					w->dumpdir = nil;
 256				}
 257			}
 258			break;
 259		case QWrdsel:
 260			close(w->rdselfd);
 261			w->rdselfd = 0;
 262			break;
 263		case QWwrsel:
 264			w->nomark = FALSE;
 265			t = &w->body;
 266			/* before: only did this if !w->noscroll, but that didn't seem right in practice */
 267			textshow(t, min(w->wrselrange.q0, t->file->b.nc),
 268				min(w->wrselrange.q1, t->file->b.nc), 1);
 269			textscrdraw(t);
 270			break;
 271		case QWeditout:
 272			qunlock(&w->editoutlk);
 273			break;
 274		}
 275		winunlock(w);
 276		winclose(w);
 277	}
 278	else{
 279		switch(q){
 280		case Qeditout:
 281			qunlock(&editoutlk);
 282			break;
 283		}
 284	}
 285	respond(x, &fc, nil);
 286}
 287
 288void
 289xfidread(Xfid *x)
 290{
 291	Fcall fc;
 292	int n, q;
 293	uint off;
 294	char *b;
 295	char buf[256];
 296	Window *w;
 297
 298	q = FILE(x->f->qid);
 299	w = x->f->w;
 300	if(w == nil){
 301		fc.count = 0;
 302		switch(q){
 303		case Qcons:
 304		case Qlabel:
 305			break;
 306		case Qindex:
 307			xfidindexread(x);
 308			return;
 309		case Qlog:
 310			xfidlogread(x);
 311			return;
 312		default:
 313			warning(nil, "unknown qid %d\n", q);
 314			break;
 315		}
 316		respond(x, &fc, nil);
 317		return;
 318	}
 319	winlock(w, 'F');
 320	if(w->col == nil){
 321		winunlock(w);
 322		respond(x, &fc, Edel);
 323		return;
 324	}
 325	off = x->fcall.offset;
 326	switch(q){
 327	case QWaddr:
 328		textcommit(&w->body, TRUE);
 329		clampaddr(w);
 330		sprint(buf, "%11d %11d ", w->addr.q0, w->addr.q1);
 331		goto Readbuf;
 332
 333	case QWbody:
 334		xfidutfread(x, &w->body, w->body.file->b.nc, QWbody);
 335		break;
 336
 337	case QWctl:
 338		b = winctlprint(w, buf, 1);
 339		goto Readb;
 340
 341	Readbuf:
 342		b = buf;
 343	Readb:
 344		n = strlen(b);
 345		if(off > n)
 346			off = n;
 347		if(off+x->fcall.count > n)
 348			x->fcall.count = n-off;
 349		fc.count = x->fcall.count;
 350		fc.data = b+off;
 351		respond(x, &fc, nil);
 352		if(b != buf)
 353			free(b);
 354		break;
 355
 356	case QWevent:
 357		xfideventread(x, w);
 358		break;
 359
 360	case QWdata:
 361		/* BUG: what should happen if q1 > q0? */
 362		if(w->addr.q0 > w->body.file->b.nc){
 363			respond(x, &fc, Eaddr);
 364			break;
 365		}
 366		w->addr.q0 += xfidruneread(x, &w->body, w->addr.q0, w->body.file->b.nc);
 367		w->addr.q1 = w->addr.q0;
 368		break;
 369
 370	case QWxdata:
 371		/* BUG: what should happen if q1 > q0? */
 372		if(w->addr.q0 > w->body.file->b.nc){
 373			respond(x, &fc, Eaddr);
 374			break;
 375		}
 376		w->addr.q0 += xfidruneread(x, &w->body, w->addr.q0, w->addr.q1);
 377		break;
 378
 379	case QWtag:
 380		xfidutfread(x, &w->tag, w->tag.file->b.nc, QWtag);
 381		break;
 382
 383	case QWrdsel:
 384		seek(w->rdselfd, off, 0);
 385		n = x->fcall.count;
 386		if(n > BUFSIZE)
 387			n = BUFSIZE;
 388		b = fbufalloc();
 389		n = read(w->rdselfd, b, n);
 390		if(n < 0){
 391			respond(x, &fc, "I/O error in temp file");
 392			break;
 393		}
 394		fc.count = n;
 395		fc.data = b;
 396		respond(x, &fc, nil);
 397		fbuffree(b);
 398		break;
 399
 400	default:
 401		sprint(buf, "unknown qid %d in read", q);
 402		respond(x, &fc, nil);
 403	}
 404	winunlock(w);
 405}
 406
 407static int
 408shouldscroll(Text *t, uint q0, int qid)
 409{
 410	if(qid == Qcons)
 411		return TRUE;
 412	return t->org <= q0 && q0 <= t->org+t->fr.nchars;
 413}
 414
 415static Rune*
 416fullrunewrite(Xfid *x, int *inr)
 417{
 418	int q, cnt, c, nb, nr;
 419	Rune *r;
 420
 421	q = x->f->nrpart;
 422	cnt = x->fcall.count;
 423	if(q > 0){
 424		memmove(x->fcall.data+q, x->fcall.data, cnt);	/* there's room; see fsysproc */
 425		memmove(x->fcall.data, x->f->rpart, q);
 426		cnt += q;
 427		x->f->nrpart = 0;
 428	}
 429	r = runemalloc(cnt);
 430	cvttorunes(x->fcall.data, cnt-UTFmax, r, &nb, &nr, nil);
 431	/* approach end of buffer */
 432	while(fullrune(x->fcall.data+nb, cnt-nb)){
 433		c = nb;
 434		nb += chartorune(&r[nr], x->fcall.data+c);
 435		if(r[nr])
 436			nr++;
 437	}
 438	if(nb < cnt){
 439		memmove(x->f->rpart, x->fcall.data+nb, cnt-nb);
 440		x->f->nrpart = cnt-nb;
 441	}
 442	*inr = nr;
 443	return r;
 444}
 445
 446void
 447xfidwrite(Xfid *x)
 448{
 449	Fcall fc;
 450	int c, qid, nb, nr, eval;
 451	char buf[64], *err;
 452	Window *w;
 453	Rune *r;
 454	Range a;
 455	Text *t;
 456	uint q0, tq0, tq1;
 457
 458	qid = FILE(x->f->qid);
 459	w = x->f->w;
 460	if(w){
 461		c = 'F';
 462		if(qid==QWtag || qid==QWbody)
 463			c = 'E';
 464		winlock(w, c);
 465		if(w->col == nil){
 466			winunlock(w);
 467			respond(x, &fc, Edel);
 468			return;
 469		}
 470	}
 471	x->fcall.data[x->fcall.count] = 0;
 472	switch(qid){
 473	case Qcons:
 474		w = errorwin(x->f->mntdir, 'X');
 475		t=&w->body;
 476		goto BodyTag;
 477
 478	case Qlabel:
 479		fc.count = x->fcall.count;
 480		respond(x, &fc, nil);
 481		break;
 482
 483	case QWaddr:
 484		x->fcall.data[x->fcall.count] = 0;
 485		r = bytetorune(x->fcall.data, &nr);
 486		t = &w->body;
 487		wincommit(w, t);
 488		eval = TRUE;
 489		a = address(FALSE, t, w->limit, w->addr, r, 0, nr, rgetc, &eval, (uint*)&nb);
 490		free(r);
 491		if(nb < nr){
 492			respond(x, &fc, Ebadaddr);
 493			break;
 494		}
 495		if(!eval){
 496			respond(x, &fc, Eaddr);
 497			break;
 498		}
 499		w->addr = a;
 500		fc.count = x->fcall.count;
 501		respond(x, &fc, nil);
 502		break;
 503
 504	case Qeditout:
 505	case QWeditout:
 506		r = fullrunewrite(x, &nr);
 507		if(w)
 508			err = edittext(w, w->wrselrange.q1, r, nr);
 509		else
 510			err = edittext(nil, 0, r, nr);
 511		free(r);
 512		if(err != nil){
 513			respond(x, &fc, err);
 514			break;
 515		}
 516		fc.count = x->fcall.count;
 517		respond(x, &fc, nil);
 518		break;
 519
 520	case QWerrors:
 521		w = errorwinforwin(w);
 522		t = &w->body;
 523		goto BodyTag;
 524
 525	case QWbody:
 526	case QWwrsel:
 527		t = &w->body;
 528		goto BodyTag;
 529
 530	case QWctl:
 531		xfidctlwrite(x, w);
 532		break;
 533
 534	case QWdata:
 535		a = w->addr;
 536		t = &w->body;
 537		wincommit(w, t);
 538		if(a.q0>t->file->b.nc || a.q1>t->file->b.nc){
 539			respond(x, &fc, Eaddr);
 540			break;
 541		}
 542		r = runemalloc(x->fcall.count);
 543		cvttorunes(x->fcall.data, x->fcall.count, r, &nb, &nr, nil);
 544		if(w->nomark == FALSE){
 545			seq++;
 546			filemark(t->file);
 547		}
 548		q0 = a.q0;
 549		if(a.q1 > q0){
 550			textdelete(t, q0, a.q1, TRUE);
 551			w->addr.q1 = q0;
 552		}
 553		tq0 = t->q0;
 554		tq1 = t->q1;
 555		textinsert(t, q0, r, nr, TRUE);
 556		if(tq0 >= q0)
 557			tq0 += nr;
 558		if(tq1 >= q0)
 559			tq1 += nr;
 560		textsetselect(t, tq0, tq1);
 561		if(shouldscroll(t, q0, qid))
 562			textshow(t, q0+nr, q0+nr, 0);
 563		textscrdraw(t);
 564		winsettag(w);
 565		free(r);
 566		w->addr.q0 += nr;
 567		w->addr.q1 = w->addr.q0;
 568		fc.count = x->fcall.count;
 569		respond(x, &fc, nil);
 570		break;
 571
 572	case QWevent:
 573		xfideventwrite(x, w);
 574		break;
 575
 576	case QWtag:
 577		t = &w->tag;
 578		goto BodyTag;
 579
 580	BodyTag:
 581		r = fullrunewrite(x, &nr);
 582		if(nr > 0){
 583			wincommit(w, t);
 584			if(qid == QWwrsel){
 585				q0 = w->wrselrange.q1;
 586				if(q0 > t->file->b.nc)
 587					q0 = t->file->b.nc;
 588			}else
 589				q0 = t->file->b.nc;
 590			if(qid == QWtag)
 591				textinsert(t, q0, r, nr, TRUE);
 592			else{
 593				if(w->nomark == FALSE){
 594					seq++;
 595					filemark(t->file);
 596				}
 597				q0 = textbsinsert(t, q0, r, nr, TRUE, &nr);
 598				textsetselect(t, t->q0, t->q1);	/* insert could leave it somewhere else */
 599				if(qid!=QWwrsel && shouldscroll(t, q0, qid))
 600					textshow(t, q0+nr, q0+nr, 1);
 601				textscrdraw(t);
 602			}
 603			winsettag(w);
 604			if(qid == QWwrsel)
 605				w->wrselrange.q1 += nr;
 606			free(r);
 607		}
 608		fc.count = x->fcall.count;
 609		respond(x, &fc, nil);
 610		break;
 611
 612	default:
 613		sprint(buf, "unknown qid %d in write", qid);
 614		respond(x, &fc, buf);
 615		break;
 616	}
 617	if(w)
 618		winunlock(w);
 619}
 620
 621void
 622xfidctlwrite(Xfid *x, Window *w)
 623{
 624	Fcall fc;
 625	int i, m, n, nb, nr, nulls;
 626	Rune *r;
 627	char *err, *p, *pp, *q, *e;
 628	int isfbuf, scrdraw, settag;
 629	Text *t;
 630
 631	err = nil;
 632	e = x->fcall.data+x->fcall.count;
 633	scrdraw = FALSE;
 634	settag = FALSE;
 635	isfbuf = TRUE;
 636	if(x->fcall.count < RBUFSIZE)
 637		r = fbufalloc();
 638	else{
 639		isfbuf = FALSE;
 640		r = emalloc(x->fcall.count*UTFmax+1);
 641	}
 642	x->fcall.data[x->fcall.count] = 0;
 643	textcommit(&w->tag, TRUE);
 644	for(n=0; n<x->fcall.count; n+=m){
 645		p = x->fcall.data+n;
 646		if(strncmp(p, "lock", 4) == 0){	/* make window exclusive use */
 647			qlock(&w->ctllock);
 648			w->ctlfid = x->f->fid;
 649			m = 4;
 650		}else
 651		if(strncmp(p, "unlock", 6) == 0){	/* release exclusive use */
 652			w->ctlfid = ~0;
 653			qunlock(&w->ctllock);
 654			m = 6;
 655		}else
 656		if(strncmp(p, "clean", 5) == 0){	/* mark window 'clean', seq=0 */
 657			t = &w->body;
 658			t->eq0 = ~0;
 659			filereset(t->file);
 660			t->file->mod = FALSE;
 661			w->dirty = FALSE;
 662			settag = TRUE;
 663			m = 5;
 664		}else
 665		if(strncmp(p, "dirty", 5) == 0){	/* mark window 'dirty' */
 666			t = &w->body;
 667			/* doesn't change sequence number, so "Put" won't appear.  it shouldn't. */
 668			t->file->mod = TRUE;
 669			w->dirty = TRUE;
 670			settag = TRUE;
 671			m = 5;
 672		}else
 673		if(strncmp(p, "show", 4) == 0){	/* show dot */
 674			t = &w->body;
 675			textshow(t, t->q0, t->q1, 1);
 676			m = 4;
 677		}else
 678		if(strncmp(p, "name ", 5) == 0){	/* set file name */
 679			pp = p+5;
 680			m = 5;
 681			q = memchr(pp, '\n', e-pp);
 682			if(q==nil || q==pp){
 683				err = Ebadctl;
 684				break;
 685			}
 686			*q = 0;
 687			nulls = FALSE;
 688			cvttorunes(pp, q-pp, r, &nb, &nr, &nulls);
 689			if(nulls){
 690				err = "nulls in file name";
 691				break;
 692			}
 693			for(i=0; i<nr; i++)
 694				if(r[i] <= ' '){
 695					err = "bad character in file name";
 696					goto out;
 697				}
 698out:
 699			seq++;
 700			filemark(w->body.file);
 701			winsetname(w, r, nr);
 702			m += (q+1) - pp;
 703		}else
 704		if(strncmp(p, "dump ", 5) == 0){	/* set dump string */
 705			pp = p+5;
 706			m = 5;
 707			q = memchr(pp, '\n', e-pp);
 708			if(q==nil || q==pp){
 709				err = Ebadctl;
 710				break;
 711			}
 712			*q = 0;
 713			nulls = FALSE;
 714			cvttorunes(pp, q-pp, r, &nb, &nr, &nulls);
 715			if(nulls){
 716				err = "nulls in dump string";
 717				break;
 718			}
 719			w->dumpstr = runetobyte(r, nr);
 720			m += (q+1) - pp;
 721		}else
 722		if(strncmp(p, "dumpdir ", 8) == 0){	/* set dump directory */
 723			pp = p+8;
 724			m = 8;
 725			q = memchr(pp, '\n', e-pp);
 726			if(q==nil || q==pp){
 727				err = Ebadctl;
 728				break;
 729			}
 730			*q = 0;
 731			nulls = FALSE;
 732			cvttorunes(pp, q-pp, r, &nb, &nr, &nulls);
 733			if(nulls){
 734				err = "nulls in dump directory string";
 735				break;
 736			}
 737			w->dumpdir = runetobyte(r, nr);
 738			m += (q+1) - pp;
 739		}else
 740		if(strncmp(p, "delete", 6) == 0){	/* delete for sure */
 741			colclose(w->col, w, TRUE);
 742			m = 6;
 743		}else
 744		if(strncmp(p, "del", 3) == 0){	/* delete, but check dirty */
 745			if(!winclean(w, TRUE)){
 746				err = "file dirty";
 747				break;
 748			}
 749			colclose(w->col, w, TRUE);
 750			m = 3;
 751		}else
 752		if(strncmp(p, "get", 3) == 0){	/* get file */
 753			get(&w->body, nil, nil, FALSE, XXX, nil, 0);
 754			m = 3;
 755		}else
 756		if(strncmp(p, "put", 3) == 0){	/* put file */
 757			put(&w->body, nil, nil, XXX, XXX, nil, 0);
 758			m = 3;
 759		}else
 760		if(strncmp(p, "dot=addr", 8) == 0){	/* set dot */
 761			textcommit(&w->body, TRUE);
 762			clampaddr(w);
 763			w->body.q0 = w->addr.q0;
 764			w->body.q1 = w->addr.q1;
 765			textsetselect(&w->body, w->body.q0, w->body.q1);
 766			settag = TRUE;
 767			m = 8;
 768		}else
 769		if(strncmp(p, "addr=dot", 8) == 0){	/* set addr */
 770			w->addr.q0 = w->body.q0;
 771			w->addr.q1 = w->body.q1;
 772			m = 8;
 773		}else
 774		if(strncmp(p, "limit=addr", 10) == 0){	/* set limit */
 775			textcommit(&w->body, TRUE);
 776			clampaddr(w);
 777			w->limit.q0 = w->addr.q0;
 778			w->limit.q1 = w->addr.q1;
 779			m = 10;
 780		}else
 781		if(strncmp(p, "nomark", 6) == 0){	/* turn off automatic marking */
 782			w->nomark = TRUE;
 783			m = 6;
 784		}else
 785		if(strncmp(p, "mark", 4) == 0){	/* mark file */
 786			seq++;
 787			filemark(w->body.file);
 788			settag = TRUE;
 789			m = 4;
 790		}else
 791		if(strncmp(p, "nomenu", 6) == 0){	/* turn off automatic menu */
 792			w->filemenu = FALSE;
 793			m = 6;
 794		}else
 795		if(strncmp(p, "menu", 4) == 0){	/* enable automatic menu */
 796			w->filemenu = TRUE;
 797			m = 4;
 798		}else
 799		if(strncmp(p, "cleartag", 8) == 0){	/* wipe tag right of bar */
 800			wincleartag(w);
 801			settag = TRUE;
 802			m = 8;
 803		}else{
 804			err = Ebadctl;
 805			break;
 806		}
 807		while(p[m] == '\n')
 808			m++;
 809	}
 810
 811	if(isfbuf)
 812		fbuffree(r);
 813	else
 814		free(r);
 815	if(err)
 816		n = 0;
 817	fc.count = n;
 818	respond(x, &fc, err);
 819	if(settag)
 820		winsettag(w);
 821	if(scrdraw)
 822		textscrdraw(&w->body);
 823}
 824
 825void
 826xfideventwrite(Xfid *x, Window *w)
 827{
 828	Fcall fc;
 829	int m, n;
 830	Rune *r;
 831	char *err, *p, *q;
 832	int isfbuf;
 833	Text *t;
 834	int c;
 835	uint q0, q1;
 836
 837	err = nil;
 838	isfbuf = TRUE;
 839	if(x->fcall.count < RBUFSIZE)
 840		r = fbufalloc();
 841	else{
 842		isfbuf = FALSE;
 843		r = emalloc(x->fcall.count*UTFmax+1);
 844	}
 845	for(n=0; n<x->fcall.count; n+=m){
 846		p = x->fcall.data+n;
 847		w->owner = *p++;	/* disgusting */
 848		c = *p++;
 849		while(*p == ' ')
 850			p++;
 851		q0 = strtoul(p, &q, 10);
 852		if(q == p)
 853			goto Rescue;
 854		p = q;
 855		while(*p == ' ')
 856			p++;
 857		q1 = strtoul(p, &q, 10);
 858		if(q == p)
 859			goto Rescue;
 860		p = q;
 861		while(*p == ' ')
 862			p++;
 863		if(*p++ != '\n')
 864			goto Rescue;
 865		m = p-(x->fcall.data+n);
 866		if('a'<=c && c<='z')
 867			t = &w->tag;
 868		else if('A'<=c && c<='Z')
 869			t = &w->body;
 870		else
 871			goto Rescue;
 872		if(q0>t->file->b.nc || q1>t->file->b.nc || q0>q1)
 873			goto Rescue;
 874
 875		qlock(&row.lk);	/* just like mousethread */
 876		switch(c){
 877		case 'x':
 878		case 'X':
 879			execute(t, q0, q1, TRUE, nil);
 880			break;
 881		case 'l':
 882		case 'L':
 883			look3(t, q0, q1, TRUE);
 884			break;
 885		default:
 886			qunlock(&row.lk);
 887			goto Rescue;
 888		}
 889		qunlock(&row.lk);
 890
 891	}
 892
 893    Out:
 894	if(isfbuf)
 895		fbuffree(r);
 896	else
 897		free(r);
 898	if(err)
 899		n = 0;
 900	fc.count = n;
 901	respond(x, &fc, err);
 902	return;
 903
 904    Rescue:
 905	err = Ebadevent;
 906	goto Out;
 907}
 908
 909void
 910xfidutfread(Xfid *x, Text *t, uint q1, int qid)
 911{
 912	Fcall fc;
 913	Window *w;
 914	Rune *r;
 915	char *b, *b1;
 916	uint q, off, boff;
 917	int m, n, nr, nb;
 918
 919	w = t->w;
 920	wincommit(w, t);
 921	off = x->fcall.offset;
 922	r = fbufalloc();
 923	b = fbufalloc();
 924	b1 = fbufalloc();
 925	n = 0;
 926	if(qid==w->utflastqid && off>=w->utflastboff && w->utflastq<=q1){
 927		boff = w->utflastboff;
 928		q = w->utflastq;
 929	}else{
 930		/* BUG: stupid code: scan from beginning */
 931		boff = 0;
 932		q = 0;
 933	}
 934	w->utflastqid = qid;
 935	while(q<q1 && n<x->fcall.count){
 936		/*
 937		 * Updating here avoids partial rune problem: we're always on a
 938		 * char boundary. The cost is we will usually do one more read
 939		 * than we really need, but that's better than being n^2.
 940		 */
 941		w->utflastboff = boff;
 942		w->utflastq = q;
 943		nr = q1-q;
 944		if(nr > BUFSIZE/UTFmax)
 945			nr = BUFSIZE/UTFmax;
 946		bufread(&t->file->b, q, r, nr);
 947		nb = snprint(b, BUFSIZE+1, "%.*S", nr, r);
 948		if(boff >= off){
 949			m = nb;
 950			if(boff+m > off+x->fcall.count)
 951				m = off+x->fcall.count - boff;
 952			memmove(b1+n, b, m);
 953			n += m;
 954		}else if(boff+nb > off){
 955			if(n != 0)
 956				error("bad count in utfrune");
 957			m = nb - (off-boff);
 958			if(m > x->fcall.count)
 959				m = x->fcall.count;
 960			memmove(b1, b+(off-boff), m);
 961			n += m;
 962		}
 963		boff += nb;
 964		q += nr;
 965	}
 966	fbuffree(r);
 967	fbuffree(b);
 968	fc.count = n;
 969	fc.data = b1;
 970	respond(x, &fc, nil);
 971	fbuffree(b1);
 972}
 973
 974int
 975xfidruneread(Xfid *x, Text *t, uint q0, uint q1)
 976{
 977	Fcall fc;
 978	Window *w;
 979	Rune *r, junk;
 980	char *b, *b1;
 981	uint q, boff;
 982	int i, rw, m, n, nr, nb;
 983
 984	w = t->w;
 985	wincommit(w, t);
 986	r = fbufalloc();
 987	b = fbufalloc();
 988	b1 = fbufalloc();
 989	n = 0;
 990	q = q0;
 991	boff = 0;
 992	while(q<q1 && n<x->fcall.count){
 993		nr = q1-q;
 994		if(nr > BUFSIZE/UTFmax)
 995			nr = BUFSIZE/UTFmax;
 996		bufread(&t->file->b, q, r, nr);
 997		nb = snprint(b, BUFSIZE+1, "%.*S", nr, r);
 998		m = nb;
 999		if(boff+m > x->fcall.count){
1000			i = x->fcall.count - boff;
1001			/* copy whole runes only */
1002			m = 0;
1003			nr = 0;
1004			while(m < i){
1005				rw = chartorune(&junk, b+m);
1006				if(m+rw > i)
1007					break;
1008				m += rw;
1009				nr++;
1010			}
1011			if(m == 0)
1012				break;
1013		}
1014		memmove(b1+n, b, m);
1015		n += m;
1016		boff += nb;
1017		q += nr;
1018	}
1019	fbuffree(r);
1020	fbuffree(b);
1021	fc.count = n;
1022	fc.data = b1;
1023	respond(x, &fc, nil);
1024	fbuffree(b1);
1025	return q-q0;
1026}
1027
1028void
1029xfideventread(Xfid *x, Window *w)
1030{
1031	Fcall fc;
1032	int i, n;
1033
1034	i = 0;
1035	x->flushed = FALSE;
1036	while(w->nevents == 0){
1037		if(i){
1038			if(!x->flushed)
1039				respond(x, &fc, "window shut down");
1040			return;
1041		}
1042		w->eventx = x;
1043		winunlock(w);
1044		recvp(x->c);
1045		winlock(w, 'F');
1046		i++;
1047	}
1048
1049	n = w->nevents;
1050	if(n > x->fcall.count)
1051		n = x->fcall.count;
1052	fc.count = n;
1053	fc.data = w->events;
1054	respond(x, &fc, nil);
1055	w->nevents -= n;
1056	if(w->nevents){
1057		memmove(w->events, w->events+n, w->nevents);
1058		w->events = erealloc(w->events, w->nevents);
1059	}else{
1060		free(w->events);
1061		w->events = nil;
1062	}
1063}
1064
1065void
1066xfidindexread(Xfid *x)
1067{
1068	Fcall fc;
1069	int i, j, m, n, nmax, isbuf, cnt, off;
1070	Window *w;
1071	char *b;
1072	Rune *r;
1073	Column *c;
1074
1075	qlock(&row.lk);
1076	nmax = 0;
1077	for(j=0; j<row.ncol; j++){
1078		c = row.col[j];
1079		for(i=0; i<c->nw; i++){
1080			w = c->w[i];
1081			nmax += Ctlsize + w->tag.file->b.nc*UTFmax + 1;
1082		}
1083	}
1084	nmax++;
1085	isbuf = (nmax<=RBUFSIZE);
1086	if(isbuf)
1087		b = (char*)x->buf;
1088	else
1089		b = emalloc(nmax);
1090	r = fbufalloc();
1091	n = 0;
1092	for(j=0; j<row.ncol; j++){
1093		c = row.col[j];
1094		for(i=0; i<c->nw; i++){
1095			w = c->w[i];
1096			/* only show the currently active window of a set */
1097			if(w->body.file->curtext != &w->body)
1098				continue;
1099			winctlprint(w, b+n, 0);
1100			n += Ctlsize;
1101			m = min(RBUFSIZE, w->tag.file->b.nc);
1102			bufread(&w->tag.file->b, 0, r, m);
1103			m = n + snprint(b+n, nmax-n-1, "%.*S", m, r);
1104			while(n<m && b[n]!='\n')
1105				n++;
1106			b[n++] = '\n';
1107		}
1108	}
1109	qunlock(&row.lk);
1110	off = x->fcall.offset;
1111	cnt = x->fcall.count;
1112	if(off > n)
1113		off = n;
1114	if(off+cnt > n)
1115		cnt = n-off;
1116	fc.count = cnt;
1117	memmove(r, b+off, cnt);
1118	fc.data = (char*)r;
1119	if(!isbuf)
1120		free(b);
1121	respond(x, &fc, nil);
1122	fbuffree(r);
1123}