plan9port

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

wind.c (14905B)


      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 
     15 int	winid;
     16 
     17 void
     18 wininit(Window *w, Window *clone, Rectangle r)
     19 {
     20 	Rectangle r1, br;
     21 	File *f;
     22 	Reffont *rf;
     23 	Rune *rp;
     24 	int nc;
     25 
     26 	w->tag.w = w;
     27 	w->taglines = 1;
     28 	w->tagexpand = TRUE;
     29 	w->body.w = w;
     30 	w->id = ++winid;
     31 	incref(&w->ref);
     32 	if(globalincref)
     33 		incref(&w->ref);
     34 	w->ctlfid = ~0;
     35 	w->utflastqid = -1;
     36 	r1 = r;
     37 
     38 	w->tagtop = r;
     39 	w->tagtop.max.y = r.min.y + font->height;
     40 	r1.max.y = r1.min.y + w->taglines*font->height;
     41 
     42 	incref(&reffont.ref);
     43 	f = fileaddtext(nil, &w->tag);
     44 	textinit(&w->tag, f, r1, &reffont, tagcols);
     45 	w->tag.what = Tag;
     46 	/* tag is a copy of the contents, not a tracked image */
     47 	if(clone){
     48 		textdelete(&w->tag, 0, w->tag.file->b.nc, TRUE);
     49 		nc = clone->tag.file->b.nc;
     50 		rp = runemalloc(nc);
     51 		bufread(&clone->tag.file->b, 0, rp, nc);
     52 		textinsert(&w->tag, 0, rp, nc, TRUE);
     53 		free(rp);
     54 		filereset(w->tag.file);
     55 		textsetselect(&w->tag, nc, nc);
     56 	}
     57 	r1 = r;
     58 	r1.min.y += w->taglines*font->height + 1;
     59 	if(r1.max.y < r1.min.y)
     60 		r1.max.y = r1.min.y;
     61 	f = nil;
     62 	if(clone){
     63 		f = clone->body.file;
     64 		w->body.org = clone->body.org;
     65 		w->isscratch = clone->isscratch;
     66 		rf = rfget(FALSE, FALSE, FALSE, clone->body.reffont->f->name);
     67 	}else
     68 		rf = rfget(FALSE, FALSE, FALSE, nil);
     69 	f = fileaddtext(f, &w->body);
     70 	w->body.what = Body;
     71 	textinit(&w->body, f, r1, rf, textcols);
     72 	r1.min.y -= 1;
     73 	r1.max.y = r1.min.y+1;
     74 	draw(screen, r1, tagcols[BORD], nil, ZP);
     75 	textscrdraw(&w->body);
     76 	w->r = r;
     77 	br.min = w->tag.scrollr.min;
     78 	br.max.x = br.min.x + Dx(button->r);
     79 	br.max.y = br.min.y + Dy(button->r);
     80 	draw(screen, br, button, nil, button->r.min);
     81 	w->filemenu = TRUE;
     82 	w->maxlines = w->body.fr.maxlines;
     83 	w->autoindent = globalautoindent;
     84 	if(clone){
     85 		w->dirty = clone->dirty;
     86 		w->autoindent = clone->autoindent;
     87 		textsetselect(&w->body, clone->body.q0, clone->body.q1);
     88 		winsettag(w);
     89 	}
     90 }
     91 
     92 /*
     93  * Draw the appropriate button.
     94  */
     95 void
     96 windrawbutton(Window *w)
     97 {
     98 	Image *b;
     99 	Rectangle br;
    100 
    101 	b = button;
    102 	if(!w->isdir && !w->isscratch && (w->body.file->mod || w->body.ncache))
    103 		b = modbutton;
    104 	br.min = w->tag.scrollr.min;
    105 	br.max.x = br.min.x + Dx(b->r);
    106 	br.max.y = br.min.y + Dy(b->r);
    107 	draw(screen, br, b, nil, b->r.min);
    108 }
    109 
    110 int
    111 delrunepos(Window *w)
    112 {
    113 	Rune *r;
    114 	int i;
    115 
    116 	r = parsetag(w, 0, &i);
    117 	free(r);
    118 	i += 2;
    119 	if(i >= w->tag.file->b.nc)
    120 		return -1;
    121 	return i;
    122 }
    123 
    124 void
    125 movetodel(Window *w)
    126 {
    127 	int n;
    128 
    129 	n = delrunepos(w);
    130 	if(n < 0)
    131 		return;
    132 	moveto(mousectl, addpt(frptofchar(&w->tag.fr, n), Pt(4, w->tag.fr.font->height-4)));
    133 }
    134 
    135 /*
    136  * Compute number of tag lines required
    137  * to display entire tag text.
    138  */
    139 int
    140 wintaglines(Window *w, Rectangle r)
    141 {
    142 	int n;
    143 	Rune rune;
    144 	Point p;
    145 
    146 	if(!w->tagexpand && !w->showdel)
    147 		return 1;
    148 	w->showdel = FALSE;
    149 	w->tag.fr.noredraw = 1;
    150 	textresize(&w->tag, r, TRUE);
    151 	w->tag.fr.noredraw = 0;
    152 	w->tagsafe = FALSE;
    153 
    154 	if(!w->tagexpand) {
    155 		/* use just as many lines as needed to show the Del */
    156 		n = delrunepos(w);
    157 		if(n < 0)
    158 			return 1;
    159 		p = subpt(frptofchar(&w->tag.fr, n), w->tag.fr.r.min);
    160 		return 1 + p.y / w->tag.fr.font->height;
    161 	}
    162 
    163 	/* can't use more than we have */
    164 	if(w->tag.fr.nlines >= w->tag.fr.maxlines)
    165 		return w->tag.fr.maxlines;
    166 
    167 	/* if tag ends with \n, include empty line at end for typing */
    168 	n = w->tag.fr.nlines;
    169 	if(w->tag.file->b.nc > 0){
    170 		bufread(&w->tag.file->b, w->tag.file->b.nc-1, &rune, 1);
    171 		if(rune == '\n')
    172 			n++;
    173 	}
    174 	if(n == 0)
    175 		n = 1;
    176 	return n;
    177 }
    178 
    179 int
    180 winresize(Window *w, Rectangle r, int safe, int keepextra)
    181 {
    182 	int oy, y, mouseintag, mouseinbody;
    183 	Point p;
    184 	Rectangle r1;
    185 
    186 	mouseintag = ptinrect(mouse->xy, w->tag.all);
    187 	mouseinbody = ptinrect(mouse->xy, w->body.all);
    188 
    189 	/* tagtop is first line of tag */
    190 	w->tagtop = r;
    191 	w->tagtop.max.y = r.min.y+font->height;
    192 
    193 	r1 = r;
    194 	r1.max.y = min(r.max.y, r1.min.y + w->taglines*font->height);
    195 
    196 	/* If needed, recompute number of lines in tag. */
    197 	if(!safe || !w->tagsafe || !eqrect(w->tag.all, r1)){
    198 		w->taglines = wintaglines(w, r);
    199 		r1.max.y = min(r.max.y, r1.min.y + w->taglines*font->height);
    200 	}
    201 
    202 	/* If needed, resize & redraw tag. */
    203 	y = r1.max.y;
    204 	if(!safe || !w->tagsafe || !eqrect(w->tag.all, r1)){
    205 		textresize(&w->tag, r1, TRUE);
    206 		y = w->tag.fr.r.max.y;
    207 		windrawbutton(w);
    208 		w->tagsafe = TRUE;
    209 
    210 		/* If mouse is in tag, pull up as tag closes. */
    211 		if(mouseintag && !ptinrect(mouse->xy, w->tag.all)){
    212 			p = mouse->xy;
    213 			p.y = w->tag.all.max.y-3;
    214 			moveto(mousectl, p);
    215 		}
    216 
    217 		/* If mouse is in body, push down as tag expands. */
    218 		if(mouseinbody && ptinrect(mouse->xy, w->tag.all)){
    219 			p = mouse->xy;
    220 			p.y = w->tag.all.max.y+3;
    221 			moveto(mousectl, p);
    222 		}
    223 	}
    224 
    225 	/* If needed, resize & redraw body. */
    226 	r1 = r;
    227 	r1.min.y = y;
    228 	if(!safe || !eqrect(w->body.all, r1)){
    229 		oy = y;
    230 		if(y+1+w->body.fr.font->height <= r.max.y){	/* room for one line */
    231 			r1.min.y = y;
    232 			r1.max.y = y+1;
    233 			draw(screen, r1, tagcols[BORD], nil, ZP);
    234 			y++;
    235 			r1.min.y = min(y, r.max.y);
    236 			r1.max.y = r.max.y;
    237 		}else{
    238 			draw(screen, r1, textcols[BACK], nil, ZP);
    239 			r1.min.y = y;
    240 			r1.max.y = y;
    241 		}
    242 		y = textresize(&w->body, r1, keepextra);
    243 		w->r = r;
    244 		w->r.max.y = y;
    245 		textscrdraw(&w->body);
    246 		w->body.all.min.y = oy;
    247 	}
    248 	w->maxlines = min(w->body.fr.nlines, max(w->maxlines, w->body.fr.maxlines));
    249 	return w->r.max.y;
    250 }
    251 
    252 void
    253 winlock1(Window *w, int owner)
    254 {
    255 	incref(&w->ref);
    256 	qlock(&w->lk);
    257 	w->owner = owner;
    258 }
    259 
    260 void
    261 winlock(Window *w, int owner)
    262 {
    263 	int i;
    264 	File *f;
    265 
    266 	f = w->body.file;
    267 	for(i=0; i<f->ntext; i++)
    268 		winlock1(f->text[i]->w, owner);
    269 }
    270 
    271 void
    272 winunlock(Window *w)
    273 {
    274 	int i;
    275 	File *f;
    276 
    277 	/*
    278 	 * subtle: loop runs backwards to avoid tripping over
    279 	 * winclose indirectly editing f->text and freeing f
    280 	 * on the last iteration of the loop.
    281 	 */
    282 	f = w->body.file;
    283 	for(i=f->ntext-1; i>=0; i--){
    284 		w = f->text[i]->w;
    285 		w->owner = 0;
    286 		qunlock(&w->lk);
    287 		winclose(w);
    288 	}
    289 }
    290 
    291 void
    292 winmousebut(Window *w)
    293 {
    294 	moveto(mousectl, addpt(w->tag.scrollr.min,
    295 		divpt(Pt(Dx(w->tag.scrollr), font->height), 2)));
    296 }
    297 
    298 void
    299 windirfree(Window *w)
    300 {
    301 	int i;
    302 	Dirlist *dl;
    303 
    304 	if(w->isdir){
    305 		for(i=0; i<w->ndl; i++){
    306 			dl = w->dlp[i];
    307 			free(dl->r);
    308 			free(dl);
    309 		}
    310 		free(w->dlp);
    311 	}
    312 	w->dlp = nil;
    313 	w->ndl = 0;
    314 }
    315 
    316 void
    317 winclose(Window *w)
    318 {
    319 	int i;
    320 
    321 	if(decref(&w->ref) == 0){
    322 		xfidlog(w, "del");
    323 		windirfree(w);
    324 		textclose(&w->tag);
    325 		textclose(&w->body);
    326 		if(activewin == w)
    327 			activewin = nil;
    328 		for(i=0; i<w->nincl; i++)
    329 			free(w->incl[i]);
    330 		free(w->incl);
    331 		free(w->events);
    332 		free(w);
    333 	}
    334 }
    335 
    336 void
    337 windelete(Window *w)
    338 {
    339 	Xfid *x;
    340 
    341 	x = w->eventx;
    342 	if(x){
    343 		w->nevents = 0;
    344 		free(w->events);
    345 		w->events = nil;
    346 		w->eventx = nil;
    347 		sendp(x->c, nil);	/* wake him up */
    348 	}
    349 }
    350 
    351 void
    352 winundo(Window *w, int isundo)
    353 {
    354 	Text *body;
    355 	int i;
    356 	File *f;
    357 	Window *v;
    358 
    359 	w->utflastqid = -1;
    360 	body = &w->body;
    361 	fileundo(body->file, isundo, &body->q0, &body->q1);
    362 	textshow(body, body->q0, body->q1, 1);
    363 	f = body->file;
    364 	for(i=0; i<f->ntext; i++){
    365 		v = f->text[i]->w;
    366 		v->dirty = (f->seq != v->putseq);
    367 		if(v != w){
    368 			v->body.q0 = v->body.fr.p0+v->body.org;
    369 			v->body.q1 = v->body.fr.p1+v->body.org;
    370 		}
    371 	}
    372 	winsettag(w);
    373 }
    374 
    375 void
    376 winsetname(Window *w, Rune *name, int n)
    377 {
    378 	Text *t;
    379 	Window *v;
    380 	int i;
    381 	static Rune Lslashguide[] = { '/', 'g', 'u', 'i', 'd', 'e', 0 };
    382 	static Rune Lpluserrors[] = { '+', 'E', 'r', 'r', 'o', 'r', 's', 0 };
    383 
    384 	t = &w->body;
    385 	if(runeeq(t->file->name, t->file->nname, name, n) == TRUE)
    386 		return;
    387 	w->isscratch = FALSE;
    388 	if(n>=6 && runeeq(Lslashguide, 6, name+(n-6), 6))
    389 		w->isscratch = TRUE;
    390 	else if(n>=7 && runeeq(Lpluserrors, 7, name+(n-7), 7))
    391 		w->isscratch = TRUE;
    392 	filesetname(t->file, name, n);
    393 	for(i=0; i<t->file->ntext; i++){
    394 		v = t->file->text[i]->w;
    395 		winsettag(v);
    396 		v->isscratch = w->isscratch;
    397 	}
    398 }
    399 
    400 void
    401 wintype(Window *w, Text *t, Rune r)
    402 {
    403 	int i;
    404 
    405 	texttype(t, r);
    406 	if(t->what == Body)
    407 		for(i=0; i<t->file->ntext; i++)
    408 			textscrdraw(t->file->text[i]);
    409 	winsettag(w);
    410 }
    411 
    412 void
    413 wincleartag(Window *w)
    414 {
    415 	int i, n;
    416 	Rune *r;
    417 
    418 	/* w must be committed */
    419 	n = w->tag.file->b.nc;
    420 	r = parsetag(w, 0, &i);
    421 	for(; i<n; i++)
    422 		if(r[i] == '|')
    423 			break;
    424 	if(i == n)
    425 		return;
    426 	i++;
    427 	textdelete(&w->tag, i, n, TRUE);
    428 	free(r);
    429 	w->tag.file->mod = FALSE;
    430 	if(w->tag.q0 > i)
    431 		w->tag.q0 = i;
    432 	if(w->tag.q1 > i)
    433 		w->tag.q1 = i;
    434 	textsetselect(&w->tag, w->tag.q0, w->tag.q1);
    435 }
    436 
    437 Rune*
    438 parsetag(Window *w, int extra, int *len)
    439 {
    440 	static Rune Ldelsnarf[] = { ' ', 'D', 'e', 'l', ' ', 'S', 'n', 'a', 'r', 'f', 0 };
    441 	static Rune Lspacepipe[] = { ' ', '|', 0 };
    442 	static Rune Ltabpipe[] = { '\t', '|', 0 };
    443 	int i;
    444 	Rune *r, *p, *pipe;
    445 
    446 	r = runemalloc(w->tag.file->b.nc+extra+1);
    447 	bufread(&w->tag.file->b, 0, r, w->tag.file->b.nc);
    448 	r[w->tag.file->b.nc] = '\0';
    449 
    450 	/*
    451 	 * " |" or "\t|" ends left half of tag
    452 	 * If we find " Del Snarf" in the left half of the tag
    453 	 * (before the pipe), that ends the file name.
    454 	 */
    455 	pipe = runestrstr(r, Lspacepipe);
    456 	if((p = runestrstr(r, Ltabpipe)) != nil && (pipe == nil || p < pipe))
    457 		pipe = p;
    458 	if((p = runestrstr(r, Ldelsnarf)) != nil && (pipe == nil || p < pipe))
    459 		i = p - r;
    460 	else {
    461 		for(i=0; i<w->tag.file->b.nc; i++)
    462 			if(r[i]==' ' || r[i]=='\t')
    463 				break;
    464 	}
    465 	*len = i;
    466 	return r;
    467 }
    468 
    469 void
    470 winsettag1(Window *w)
    471 {
    472 	int i, j, k, n, bar, dirty, resize;
    473 	Rune *new, *old, *r;
    474 	uint q0, q1;
    475 	static Rune Ldelsnarf[] = { ' ', 'D', 'e', 'l', ' ',
    476 		'S', 'n', 'a', 'r', 'f', 0 };
    477 	static Rune Lundo[] = { ' ', 'U', 'n', 'd', 'o', 0 };
    478 	static Rune Lredo[] = { ' ', 'R', 'e', 'd', 'o', 0 };
    479 	static Rune Lget[] = { ' ', 'G', 'e', 't', 0 };
    480 	static Rune Lput[] = { ' ', 'P', 'u', 't', 0 };
    481 	static Rune Llook[] = { ' ', 'L', 'o', 'o', 'k', ' ', 0 };
    482 	static Rune Lpipe[] = { ' ', '|', 0 };
    483 
    484 	/* there are races that get us here with stuff in the tag cache, so we take extra care to sync it */
    485 	if(w->tag.ncache!=0 || w->tag.file->mod)
    486 		wincommit(w, &w->tag);	/* check file name; also guarantees we can modify tag contents */
    487 	old = parsetag(w, 0, &i);
    488 	if(runeeq(old, i, w->body.file->name, w->body.file->nname) == FALSE){
    489 		textdelete(&w->tag, 0, i, TRUE);
    490 		textinsert(&w->tag, 0, w->body.file->name, w->body.file->nname, TRUE);
    491 		free(old);
    492 		old = runemalloc(w->tag.file->b.nc+1);
    493 		bufread(&w->tag.file->b, 0, old, w->tag.file->b.nc);
    494 		old[w->tag.file->b.nc] = '\0';
    495 	}
    496 
    497 	/* compute the text for the whole tag, replacing current only if it differs */
    498 	new = runemalloc(w->body.file->nname+100);
    499 	i = 0;
    500 	if(w->body.file->nname != 0)
    501 		runemove(new, w->body.file->name, w->body.file->nname);
    502 	i += w->body.file->nname;
    503 	runemove(new+i, Ldelsnarf, 10);
    504 	i += 10;
    505 	if(w->filemenu){
    506 		if(w->body.needundo || w->body.file->delta.nc>0 || w->body.ncache){
    507 			runemove(new+i, Lundo, 5);
    508 			i += 5;
    509 		}
    510 		if(w->body.file->epsilon.nc > 0){
    511 			runemove(new+i, Lredo, 5);
    512 			i += 5;
    513 		}
    514 		dirty = w->body.file->nname && (w->body.ncache || w->body.file->seq!=w->putseq);
    515 		if(!w->isdir && dirty){
    516 			runemove(new+i, Lput, 4);
    517 			i += 4;
    518 		}
    519 	}
    520 	if(w->isdir){
    521 		runemove(new+i, Lget, 4);
    522 		i += 4;
    523 	}
    524 	runemove(new+i, Lpipe, 2);
    525 	i += 2;
    526 	r = runestrchr(old, '|');
    527 	if(r)
    528 		k = r-old+1;
    529 	else{
    530 		k = w->tag.file->b.nc;
    531 		if(w->body.file->seq == 0){
    532 			runemove(new+i, Llook, 6);
    533 			i += 6;
    534 		}
    535 	}
    536 	new[i] = 0;
    537 
    538 	/* replace tag if the new one is different */
    539 	resize = 0;
    540 	if(runeeq(new, i, old, k) == FALSE){
    541 		resize = 1;
    542 		n = k;
    543 		if(n > i)
    544 			n = i;
    545 		for(j=0; j<n; j++)
    546 			if(old[j] != new[j])
    547 				break;
    548 		q0 = w->tag.q0;
    549 		q1 = w->tag.q1;
    550 		textdelete(&w->tag, j, k, TRUE);
    551 		textinsert(&w->tag, j, new+j, i-j, TRUE);
    552 		/* try to preserve user selection */
    553 		r = runestrchr(old, '|');
    554 		if(r){
    555 			bar = r-old;
    556 			if(q0 > bar){
    557 				bar = (runestrchr(new, '|')-new)-bar;
    558 				w->tag.q0 = q0+bar;
    559 				w->tag.q1 = q1+bar;
    560 			}
    561 		}
    562 	}
    563 	free(old);
    564 	free(new);
    565 	w->tag.file->mod = FALSE;
    566 	n = w->tag.file->b.nc+w->tag.ncache;
    567 	if(w->tag.q0 > n)
    568 		w->tag.q0 = n;
    569 	if(w->tag.q1 > n)
    570 		w->tag.q1 = n;
    571 	textsetselect(&w->tag, w->tag.q0, w->tag.q1);
    572 	windrawbutton(w);
    573 	if(resize){
    574 		w->tagsafe = 0;
    575 		winresize(w, w->r, TRUE, TRUE);
    576 	}
    577 }
    578 
    579 void
    580 winsettag(Window *w)
    581 {
    582 	int i;
    583 	File *f;
    584 	Window *v;
    585 
    586 	f = w->body.file;
    587 	for(i=0; i<f->ntext; i++){
    588 		v = f->text[i]->w;
    589 		if(v->col->safe || v->body.fr.maxlines>0)
    590 			winsettag1(v);
    591 	}
    592 }
    593 
    594 void
    595 wincommit(Window *w, Text *t)
    596 {
    597 	Rune *r;
    598 	int i;
    599 	File *f;
    600 
    601 	textcommit(t, TRUE);
    602 	f = t->file;
    603 	if(f->ntext > 1)
    604 		for(i=0; i<f->ntext; i++)
    605 			textcommit(f->text[i], FALSE);	/* no-op for t */
    606 	if(t->what == Body)
    607 		return;
    608 	r = parsetag(w, 0, &i);
    609 	if(runeeq(r, i, w->body.file->name, w->body.file->nname) == FALSE){
    610 		seq++;
    611 		filemark(w->body.file);
    612 		w->body.file->mod = TRUE;
    613 		w->dirty = TRUE;
    614 		winsetname(w, r, i);
    615 		winsettag(w);
    616 	}
    617 	free(r);
    618 }
    619 
    620 void
    621 winaddincl(Window *w, Rune *r, int n)
    622 {
    623 	char *a;
    624 	Dir *d;
    625 	Runestr rs;
    626 
    627 	a = runetobyte(r, n);
    628 	d = dirstat(a);
    629 	if(d == nil){
    630 		if(a[0] == '/')
    631 			goto Rescue;
    632 		rs = dirname(&w->body, r, n);
    633 		r = rs.r;
    634 		n = rs.nr;
    635 		free(a);
    636 		a = runetobyte(r, n);
    637 		d = dirstat(a);
    638 		if(d == nil)
    639 			goto Rescue;
    640 		r = runerealloc(r, n+1);
    641 		r[n] = 0;
    642 	}
    643 	free(a);
    644 	if((d->qid.type&QTDIR) == 0){
    645 		free(d);
    646 		warning(nil, "%s: not a directory\n", a);
    647 		free(r);
    648 		return;
    649 	}
    650 	free(d);
    651 	w->nincl++;
    652 	w->incl = realloc(w->incl, w->nincl*sizeof(Rune*));
    653 	memmove(w->incl+1, w->incl, (w->nincl-1)*sizeof(Rune*));
    654 	w->incl[0] = runemalloc(n+1);
    655 	runemove(w->incl[0], r, n);
    656 	free(r);
    657 	return;
    658 
    659 Rescue:
    660 	warning(nil, "%s: %r\n", a);
    661 	free(r);
    662 	free(a);
    663 	return;
    664 }
    665 
    666 int
    667 winclean(Window *w, int conservative)
    668 {
    669 	if(w->isscratch || w->isdir)	/* don't whine if it's a guide file, error window, etc. */
    670 		return TRUE;
    671 	if(!conservative && w->nopen[QWevent]>0)
    672 		return TRUE;
    673 	if(w->dirty){
    674 		if(w->body.file->nname)
    675 			warning(nil, "%.*S modified\n", w->body.file->nname, w->body.file->name);
    676 		else{
    677 			if(w->body.file->b.nc < 100)	/* don't whine if it's too small */
    678 				return TRUE;
    679 			warning(nil, "unnamed file modified\n");
    680 		}
    681 		w->dirty = FALSE;
    682 		return FALSE;
    683 	}
    684 	return TRUE;
    685 }
    686 
    687 char*
    688 winctlprint(Window *w, char *buf, int fonts)
    689 {
    690 	sprint(buf, "%11d %11d %11d %11d %11d ", w->id, w->tag.file->b.nc,
    691 		w->body.file->b.nc, w->isdir, w->dirty);
    692 	if(fonts)
    693 		return smprint("%s%11d %q %11d %11d %11d ", buf, Dx(w->body.fr.r),
    694 			w->body.reffont->f->name, w->body.fr.maxtab, seqof(w, 1) != 0, seqof(w, 0) != 0);
    695 	return buf;
    696 }
    697 
    698 void
    699 winevent(Window *w, char *fmt, ...)
    700 {
    701 	int n;
    702 	char *b;
    703 	Xfid *x;
    704 	va_list arg;
    705 
    706 	if(w->nopen[QWevent] == 0)
    707 		return;
    708 	if(w->owner == 0)
    709 		error("no window owner");
    710 	va_start(arg, fmt);
    711 	b = vsmprint(fmt, arg);
    712 	va_end(arg);
    713 	if(b == nil)
    714 		error("vsmprint failed");
    715 	n = strlen(b);
    716 	w->events = erealloc(w->events, w->nevents+1+n);
    717 	w->events[w->nevents++] = w->owner;
    718 	memmove(w->events+w->nevents, b, n);
    719 	free(b);
    720 	w->nevents += n;
    721 	x = w->eventx;
    722 	if(x){
    723 		w->eventx = nil;
    724 		sendp(x->c, nil);
    725 	}
    726 }