xref: /inferno-os/libinterp/tk.c (revision 7ef44d652ae9e5e1f5b3465d73684e4a54de73c0)
1 #include "lib9.h"
2 #include "interp.h"
3 #include "isa.h"
4 #include "runt.h"
5 #include "draw.h"
6 #include "tk.h"
7 #include "tkmod.h"
8 #include "pool.h"
9 #include "drawif.h"
10 #include "keyboard.h"
11 #include "raise.h"
12 #include "kernel.h"
13 
14 extern	void	tkfreetop(Heap*, int);
15 Type*	fakeTkTop;
16 static	uchar	TktypeMap[] = Tk_Toplevel_map;
17 int	tkstylus;
18 void	(*tkwiretap)(void*, char*, char*, void*, Rectangle*);
19 
20 static void tktopimagedptr(TkTop*, Draw_Image*);
21 static char*tkputwinimage(Tk*, Draw_Image*, int);
22 
23 static void
24 lockctxt(TkCtxt *ctxt)
25 {
26 	libqlock(ctxt->lock);
27 }
28 
29 static void
30 unlockctxt(TkCtxt *ctxt)
31 {
32 	libqunlock(ctxt->lock);
33 }
34 
35 static void
36 tkmarktop(Type *t, void *vw)
37 {
38 	Heap *h;
39 	TkVar *v;
40 	TkPanelimage *di;
41 	TkTop *top;
42 	Tk *w, *next;
43 	TkWin *tkw;
44 
45 	markheap(t, vw);
46 	top = vw;
47 	// XXX do we need to lock context here??
48 	for(v = top->vars; v; v = v->link) {
49 		if(v->type == TkVchan) {
50 			h = D2H(v->value);
51 			Setmark(h);
52 		}
53 	}
54 	for (di = top->panelimages; di != nil; di = di->link) {
55 		h = D2H(di->image);
56 		Setmark(h);
57 	}
58 	for(w = top->windows; w != nil; w = next){
59 		tkw = TKobj(TkWin, w);
60 		if(tkw->image != nil){
61 			h = D2H(tkw->di);
62 			Setmark(h);
63 		}
64 		next = tkw->next;
65 	}
66 }
67 
68 void
69 tkmodinit(void)
70 {
71 	builtinmod("$Tk", Tkmodtab, Tkmodlen);
72 	fmtinstall('v', tkeventfmt);			/* XXX */
73 
74 	fakeTkTop = dtype(tkfreetop, sizeof(TkTop), TktypeMap, sizeof(TktypeMap));
75 	fakeTkTop->mark = tkmarktop;
76 
77 	tksorttable();
78 }
79 
80 void
81 Tk_toplevel(void *a)
82 {
83 	Tk *tk;
84 	Heap *h;
85 	TkTop *t;
86 	TkWin *tkw;
87 	TkCtxt *ctxt;
88 	Display *disp;
89 	F_Tk_toplevel *f = a;
90 	void *r;
91 
92 	r = *f->ret;
93 	*f->ret = H;
94 	destroy(r);
95 	disp = checkdisplay(f->d);
96 
97 	h = heapz(fakeTkTop);
98 	t = H2D(TkTop*, h);
99 	poolimmutable(h);
100 
101 	t->dd = f->d;
102 	D2H(t->dd)->ref++;
103 
104 	t->execdepth = -1;
105 	t->display = disp;
106 
107 	tk = tknewobj(t, TKframe, sizeof(Tk)+sizeof(TkWin));
108 	if(tk == nil) {
109 		destroy(t);
110 		return;
111 	}
112 
113 	tk->act.x = 0;
114 	tk->act.y = 0;
115 	tk->act.width = 1;		/* XXX why not zero? */
116 	tk->act.height = 1;
117 	tk->flag |= Tkwindow;
118 
119 	tkw = TKobj(TkWin, tk);
120 	tkw->di = H;
121 
122 	tktopopt(tk, string2c(f->arg));
123 
124 	tk->geom = tkmoveresize;
125 	tk->name = tkmkname(".");
126 	if(tk->name == nil) {
127 		tkfreeobj(tk);
128 		destroy(t);
129 		return;
130 	}
131 
132 	ctxt = tknewctxt(disp);
133 	if(ctxt == nil) {
134 		tkfreeobj(tk);
135 		destroy(t);
136 		return;
137 	}
138 	t->ctxt = ctxt;
139 	t->screenr = disp->image->r;
140 
141 	tkw->next = t->windows;
142 	t->windows = tk;
143 	t->root = tk;
144 	Setmark(h);
145 	poolmutable(h);
146 	t->wreq = cnewc(&Tptr, movp, 8);
147 	*f->ret = (Tk_Toplevel*)t;
148 }
149 
150 void
151 Tk_cmd(void *a)
152 {
153 	TkTop *t;
154 	char *val, *e;
155 	F_Tk_cmd *f = a;
156 
157 	t = (TkTop*)f->t;
158 	if(t == H || D2H(t)->t != fakeTkTop) {
159 		retstr(TkNotop, f->ret);
160 		return;
161 	}
162 	lockctxt(t->ctxt);
163 	val = nil;
164 	e = tkexec(t, string2c(f->arg), &val);
165 	unlockctxt(t->ctxt);
166 	if(e == TkNomem){
167 		free(val);
168 		error(exNomem);		/* what about f->ret? */
169 	}
170 	if(e != nil && t->errx[0] != '\0'){
171 		char *s = tkerrstr(t, e);
172 
173 		retstr(s, f->ret);
174 		free(s);
175 	}
176 	else
177 		retstr(e == nil ? val : e, f->ret);
178 	if(tkwiretap != nil)
179 		tkwiretap(t, string2c(f->arg), val, nil, nil);
180 	free(val);
181 }
182 
183 void
184 Tk_color(void *fp)
185 {
186 	ulong rgba;
187 	F_Tk_color *f = fp;
188 	if(tkparsecolor(string2c(f->col), &rgba) != nil)
189 		*f->ret = DNotacolor;
190 	else
191 		*f->ret = rgba;
192 }
193 
194 void
195 Tk_rect(void *fp)
196 {
197 	F_Tk_rect *f = fp;
198 	Tk *tk;
199 	TkTop *t;
200 	Rectangle r;
201 	Point o;
202 	int bd, flags, w, h;
203 
204 	t = (TkTop*)f->t;
205 	if(t == H || D2H(t)->t != fakeTkTop){
206 		*(Rectangle*)f->ret = ZR;
207 		return;
208 	}
209 	lockctxt(t->ctxt);
210 	tk = tklook(t, string2c(f->name), 0);
211 	if(tk == nil){
212 		*(Rectangle*)f->ret = ZR;
213 		unlockctxt(t->ctxt);
214 		return;
215 	}
216 	o = tkposn(tk);
217 	flags = f->flags;
218 	if(flags & Tk_Local)
219 		o = subpt(o, tkposn(tk->env->top->root));
220 	if(flags & Tk_Required){
221 		h = tk->req.height;
222 		w = tk->req.width;
223 	}else{
224 		h = tk->act.height;
225 		w = tk->act.width;
226 	}
227 	unlockctxt(t->ctxt);
228 	if(w < 0)
229 		w = 0;
230 	if(h < 0)
231 		h = 0;
232 	bd = tk->borderwidth;
233 	if(bd < 0)
234 		bd = 0;
235 	if(flags & Tk_Border){
236 		r.min = o;
237 		r.max.x = r.min.x + w + bd + bd;
238 		r.max.y = r.min.y + h + bd + bd;
239 	}else{
240 		r.min.x = o.x + bd;
241 		r.min.y = o.y + bd;
242 		r.max.x = r.min.x + w;
243 		r.max.y = r.min.y + h;
244 	}
245 	*(Rectangle*)f->ret = r;
246 }
247 
248 int
249 tkdescendant(Tk *p, Tk *c)
250 {
251 	int n;
252 
253 	if(c == nil || p->env->top != c->env->top)
254 		return 0;
255 
256 	if (p->name != nil && c->name != nil) {
257  		n = strlen(p->name->name);
258 		if(strncmp(p->name->name, c->name->name, n) == 0)
259 			return 1;
260 	}
261 
262 	return 0;
263 }
264 
265 void
266 tkenterleave(TkTop *t)
267 {
268 	Tk *fw, *ent;
269 	TkMouse m;
270 	TkTop *t1, *t2;
271 	TkCtxt *c;
272 
273 	c = t->ctxt;
274 	m = c->mstate;
275 
276 	if (c->mgrab != nil && (c->mgrab->flag & Tknograb)) {
277 		fw = tkfindfocus(t, m.x, m.y, 1);
278 		if (fw != c->mgrab && fw != nil && (fw->flag & Tknograb) == 0)
279 			fw = nil;
280 	} else if (c->focused) {
281 		fw = tkfindfocus(t, m.x, m.y, 1);
282 		if (fw != c->mfocus)
283 			fw = nil;
284 	} else if (c->mgrab != nil) {
285 		fw = tkfindfocus(t, m.x, m.y, 1);
286 		if (fw != nil) {
287 			if (!tkdescendant(c->mgrab, fw) && !(fw->flag & c->mgrab->flag & Tknograb))
288 				fw = nil;
289 		}
290 	} else if (m.b == 0)
291 		fw = tkfindfocus(t, m.x, m.y, 0);
292 	else if (tkfindfocus(t, m.x, m.y, 1) == c->entered)
293 		return;
294 	else
295 		fw = nil;
296 
297  	if (c->entered == fw)
298 		return;
299 
300 	t1 = t2 = nil;
301 	if (c->entered != nil) {
302 		ent = c->entered;
303 		t1 = ent->env->top;
304 		c->entered = nil;
305 		tkdeliver(ent, TkLeave, nil);
306 	}
307 
308 	if (fw != nil) {
309 		t2 = fw->env->top;
310 		c->entered = fw;
311 		tkdeliver(fw, TkEnter, &m);
312 	}
313 	if (t1 != nil)
314 		tkupdate(t1);
315 	if (t2 != nil && t1 != t2)
316 		tkupdate(t2);
317 }
318 
319 void
320 Tk_pointer(void *a)
321 {
322 	static int buttonr[] = {TkButton1R, TkButton2R, TkButton3R, TkButton4R, TkButton5R, TkButton6R};
323 	static int buttonp[] = {TkButton1P, TkButton2P, TkButton3P, TkButton4P, TkButton5P, TkButton6P};
324 	Tk *fw, *target, *dest, *ent;
325 	TkMouse m;
326 	TkCtxt *c;
327 	TkTop *t, *ot;
328 	int d, dtype, etype;
329 	F_Tk_pointer *f = a;
330 	int b, lastb, inside;
331 
332 	t = (TkTop*)f->t;
333 	if(t == H || D2H(t)->t != fakeTkTop)
334 		return;
335 
336 	c = t->ctxt;
337 
338 	/* ignore no-button-motion for emulated stylus input */
339 	if(tkstylus && c->mstate.b == 0 && (f->p.buttons&0x1f)==0)
340 		return;
341 
342 	lockctxt(c);
343 //if (f->p.buttons != 0 || c->mstate.b != 0)
344 //print("tkmouse %d [%d %d], focused %d[%s], grab %s, entered %s\n",
345 //	f->p.buttons, f->p.xy.x, f->p.xy.y, c->focused, tkname(c->mfocus), tkname(c->mgrab), tkname(c->entered));
346 	/*
347 	 * target is the widget that we're deliver the mouse event to.
348 	 * inside is true if the mouse point is located inside target.
349 	 */
350 	inside = 1;
351 	if (c->mgrab != nil && (c->mgrab->flag & Tknograb)) {
352 		fw = tkfindfocus(t, f->p.xy.x, f->p.xy.y, 1);
353 		if (fw != nil && (fw->flag & Tknograb))
354 			target = fw;
355 		else {
356 			target = c->mgrab;
357 			inside = 0;
358 		}
359 	} else if (c->focused) {
360 		if (c->mfocus != nil) {
361 			fw = tkfindfocus(t, f->p.xy.x, f->p.xy.y, 1);
362 			if (fw != c->mfocus)
363 				inside = 0;
364 		}
365 		target = c->mfocus;
366 	} else if (c->mgrab != nil && (c->mgrab->flag & Tkdisabled) == 0) {
367 		/*
368 		 * XXX this isn't quite right, as perhaps we should do a tkinwindow()
369 		 * (below the grab).
370 		 * so that events to containers underneath the grab arrive
371 		 * via the containers (as is usual)
372 		 */
373 		fw = tkfindfocus(t, f->p.xy.x, f->p.xy.y, 1);
374 		if (fw != nil && tkdescendant(c->mgrab, fw))
375 			target = fw;
376 		else {
377 			target = c->mgrab;
378 			inside = 0;
379 		}
380 	} else
381 		target = tkfindfocus(t, f->p.xy.x, f->p.xy.y, 0);
382 
383 	lastb = c->mstate.b;
384 	c->mstate.x = f->p.xy.x;
385 	c->mstate.y = f->p.xy.y;
386 	c->mstate.b = f->p.buttons & 0x1f;		/* Just the buttons */
387 	m = c->mstate;
388 
389 	/* XXX if the mouse is being moved with the buttons held down
390 	 * and we've no mfocus and no mgrab then ignore
391 	 * the event as our original target has gone away (or never existed)
392 	 */
393 	if (lastb && m.b && !c->focused && c->mgrab == nil)
394 		target = nil;
395 
396 	if (target != c->entered || (c->entered != nil && !inside)) {
397 		if (c->entered != nil) {
398 			fw = c->entered;
399 			c->entered =  nil;
400 			tkdeliver(fw, TkLeave, nil);
401 			if (target == nil || fw->env->top != target->env->top)
402 				tkupdate(fw->env->top);
403 		}
404 		if (inside) {
405 			c->entered = target;
406 			tkdeliver(target, TkEnter, &m);
407 		}
408 	}
409 
410 	dest = nil;
411 	if (target != nil) {
412 		etype = 0;
413 		dtype = 0;
414 		if(f->p.buttons & (1<<8))		/* Double */
415 			dtype = TkDouble;
416 
417 		d = lastb ^ m.b;
418 		if (d)	{
419 			/* cancel any autorepeat, notifying existing client */
420 			tkrepeat(nil, nil, nil, 0, 0);
421 			if (d & ~lastb & 1)		/* button 1 potentially takes the focus */
422 				tkdeliver(target, TkTakefocus|TkButton1P, &m);
423 		}
424 		for(b=0; b<nelem(buttonp); b++){
425 			if(d & (1<<b)){
426 				etype = buttonr[b];
427 				if(m.b & (1<<b))
428 					etype = buttonp[b]|dtype;
429 				dest = tkdeliver(target, etype, &m);
430 			}
431 		}
432 		if(tkstylus && m.b==0) {
433 			if ((ent = c->entered) != nil) {
434 				c->entered = nil;
435 				ot = ent->env->top;
436 				tkdeliver(ent, TkLeave, nil);
437 				if (ot != target->env->top)
438 					tkupdate(ot);
439 			}
440 		} else if(etype == 0) {
441 			etype = TkMotion;
442 			for(b = 0; b<nelem(buttonp); b++)
443 				if (m.b & (1<<b))
444 					etype |= buttonp[b];
445 			tkdeliver(target, etype, &m);
446 		}
447 		if (m.b != 0) {
448 			if (lastb == 0 && !c->focused) {		/* (some deliver might have grabbed it...) */
449 				if (dest == nil)
450 					dest = target;
451 				if ((dest->flag & Tknograb) == 0) {
452 					c->focused = 1;
453 					c->mfocus = dest;
454 				}
455 			}
456 		} else {
457 			c->focused = 0;
458 			c->mfocus = nil;
459 			if (lastb != 0)
460 				tkenterleave(t);
461 		}
462 		tkupdate(target->env->top);
463 	} else if (c->focused && m.b == 0) {
464 		c->focused = 0;
465 		tkenterleave(t);
466 	}
467 	unlockctxt(c);
468 }
469 
470 void
471 Tk_keyboard(void *a)
472 {
473 	Tk *grab;
474 	TkTop *t;
475 	TkCtxt *c;
476 	F_Tk_keyboard *f = a;
477 
478 	t = (TkTop*)f->t;
479 	if(t == H || D2H(t)->t != fakeTkTop)
480 		return;
481 	c = t->ctxt;
482 	if (c == nil)
483 		return;
484 	lockctxt(c);
485 	if (c->tkmenu != nil)
486 		grab = c->tkmenu;
487 	else
488 		grab = c->tkkeygrab;
489 	if(grab == nil){
490 		unlockctxt(c);
491 		return;
492 	}
493 
494 	t = grab->env->top;
495 	tkdeliver(grab, TkKey|TKKEY(f->key), nil);
496 	tkupdate(t);
497 	unlockctxt(c);
498 }
499 
500 TkVar*
501 tkmkvar(TkTop *t, char *name, int type)
502 {
503 	TkVar *v;
504 
505 	for(v = t->vars; v; v = v->link)
506 		if(strcmp(v->name, name) == 0)
507 			return v;
508 
509 	if(type == 0)
510 		return nil;
511 
512 	v = malloc(sizeof(TkVar)+strlen(name)+1);
513 	if(v == nil)
514 		return nil;
515 	strcpy(v->name, name);
516 	v->link = t->vars;
517 	t->vars = v;
518 	v->type = type;
519 	v->value = nil;
520 	if(type == TkVchan)
521 		v->value = H;
522 	return v;
523 }
524 
525 void
526 tkfreevar(TkTop *t, char *name, int swept)
527 {
528 	TkVar **l, *p;
529 
530 	if(name == nil)
531 		return;
532 	l = &t->vars;
533 	for(p = *l; p != nil; p = p->link) {
534 		if(strcmp(p->name, name) == 0) {
535 			*l = p->link;
536 			switch(p->type) {
537 			default:
538 				free(p->value);
539 				break;
540 			case TkVchan:
541 				if(!swept)
542 					destroy(p->value);
543 				break;
544 			}
545 			free(p);
546 			return;
547 		}
548 		l = &p->link;
549 	}
550 }
551 
552 void
553 Tk_namechan(void *a)
554 {
555 	Heap *h;
556 	TkVar *v;
557 	TkTop *t;
558 	char *name;
559 	F_Tk_namechan *f = a;
560 
561 	t = (TkTop*)f->t;
562 	if(t == H || D2H(t)->t != fakeTkTop) {
563 		retstr(TkNotop, f->ret);
564 		return;
565 	}
566 	if(f->c == H) {
567 		retstr("nil channel", f->ret);
568 		return;
569 	}
570 	name = string2c(f->n);
571 	if(name[0] == '\0') {
572 		retstr(TkBadvl, f->ret);
573 		return;
574 	}
575 
576 	lockctxt(t->ctxt);
577 	v = tkmkvar(t, name, TkVchan);
578 	if(v == nil) {
579 		unlockctxt(t->ctxt);
580 		retstr(TkNomem, f->ret);
581 		return;
582 	}
583 	if(v->type != TkVchan) {
584 		unlockctxt(t->ctxt);
585 		retstr(TkNotvt, f->ret);
586 		return;
587 	}
588 	destroy(v->value);
589 	v->value = f->c;
590 	unlockctxt(t->ctxt);
591 	h = D2H(v->value);
592 	h->ref++;
593 	Setmark(h);
594 	retstr("", f->ret);
595 }
596 
597 void
598 Tk_quote(void *a)
599 {
600 	String *s, *ns;
601 	F_Tk_quote *f;
602 	void *r;
603 	int c, i, need, len, userune, last, n;
604 	Rune *sr;
605 	char *sc;
606 
607 	f = a;
608 
609 	r = *f->ret;
610 	*f->ret = H;
611 	destroy(r);
612 
613 	s = f->s;
614 	if(s == H){
615 		retstr("{}", f->ret);
616 		return;
617 	}
618 	len = s->len;
619 	userune = 0;
620 	if(len < 0) {
621 		len = -len;
622 		userune = 1;
623 	}
624 	need = len+2;
625 	for(i = 0; i < len; i++) {
626 		c = userune? s->Srune[i]: s->Sascii[i];
627 		if(c == '{' || c == '}' || c == '\\')
628 			need++;
629 	}
630 	if(userune) {
631 		ns = newrunes(need);
632 		sr = ns->Srune;
633 		*sr++ = '{';
634 		last = 0;
635 		for(i = 0;; i++) {
636 			if(i >= len || (c = s->Srune[i]) == '{' || c == '}' || c == '\\'){
637 				n = i-last;
638 				if(n) {
639 					memmove(sr, &s->Srune[last], n*sizeof(Rune));
640 					sr += n;
641 				}
642 				if(i >= len)
643 					break;
644 				*sr++ = '\\';
645 				last = i;
646 			}
647 		}
648 		*sr = '}';
649 	} else {
650 		ns = newstring(need);
651 		sc = ns->Sascii;
652 		*sc++ = '{';
653 		last = 0;
654 		for(i = 0;; i++) {
655 			if(i >= len || (c = s->Sascii[i]) == '{' || c == '}' || c == '\\'){
656 				n = i-last;
657 				if(n) {
658 					memmove(sc, &s->Sascii[last], n);
659 					sc += n;
660 				}
661 				if(i >= len)
662 					break;
663 				*sc++ = '\\';
664 				last = i;
665 			}
666 		}
667 		*sc= '}';
668 	}
669 	*f->ret = ns;
670 }
671 
672 static void
673 tkreplimg(TkTop *t, Draw_Image *f, Draw_Image *m, Image **ximg)
674 {
675 	Display *d;
676 	Image *cimg, *cmask, *new;
677 
678 	cimg = lookupimage(f);
679 	d = t->display;
680 	if(cimg == nil || cimg->screen != nil || cimg->display != d)
681 		return;
682 	cmask = lookupimage(m);
683 	if(cmask != nil && (cmask->screen != nil || cmask->display != d))
684 		return;
685 
686 	if (cmask == nil)
687 		new = allocimage(d, Rect(0, 0, Dx(cimg->r), Dy(cimg->r)), cimg->chan, 0, DNofill);
688 	else {
689 		if(cmask->screen != nil || cmask->display != d)
690 			return;
691 		new = allocimage(d, Rect(0, 0, Dx(cimg->r), Dy(cimg->r)), RGBA32, 0, DTransparent);
692 	}
693 	if(new == nil)
694 		return;
695 	draw(new, new->r, cimg, cmask, cimg->r.min);
696 	if(tkwiretap != nil)
697 		tkwiretap(t, "replimg", nil, cimg, &cimg->r);
698 	if(*ximg != nil)
699 		freeimage(*ximg);
700 	*ximg = new;
701 }
702 
703 static char*
704 tkaddpanelimage(TkTop *t, Draw_Image *di, Image **i)
705 {
706 	TkPanelimage *pi;
707 
708 	if (di == H) {
709 		*i = 0;
710 		return nil;
711 	}
712 
713 	*i = lookupimage(di);
714 	if (*i == nil || (*i)->display != t->display)
715 		return TkNotwm;
716 
717 	for (pi = t->panelimages; pi != nil; pi = pi->link) {
718 		if (pi->image == di) {
719 			pi->ref++;
720 			return nil;
721 		}
722 	}
723 
724 	pi = malloc(sizeof(TkPanelimage));
725 	if (pi == nil)
726 		return TkNomem;
727 	pi->image = di;
728 	D2H(di)->ref++;
729 	pi->ref = 1;
730 	pi->link = t->panelimages;
731 	t->panelimages = pi;
732 	return nil;
733 }
734 
735 void
736 tkdelpanelimage(TkTop *t, Image *i)
737 {
738 	TkPanelimage *pi, *prev;
739 	int locked;
740 
741 	if (i == nil)
742 		return;
743 
744 	prev = nil;
745 	for (pi = t->panelimages; pi != nil; pi = pi->link) {
746 		if (lookupimage(pi->image) == i)
747 			break;
748 		prev = pi;
749 	}
750 	if (pi == nil || --pi->ref > 0)
751 		return;
752 	if (prev)
753 		prev->link = pi->link;
754 	else
755 		t->panelimages = pi->link;
756 	if (D2H(pi->image)->ref == 1) {		/* don't bother locking if it's not going away */
757 		locked = lockdisplay(t->display);
758 		destroy(pi->image);
759 		if (locked)
760 			unlockdisplay(t->display);
761 	}
762 
763 	free(pi);
764 }
765 
766 void
767 Tk_putimage(void *a)
768 {
769 	TkTop *t;
770 	TkImg *tki;
771 	Image *i, *m, *oldi, *oldm;
772 	int locked, found, reqid, n;
773 	char *words[2];
774 	Display *d;
775 	F_Tk_putimage *f;
776 	void *r;
777 	char *name, *e;
778 	Tk *tk;
779 
780 	f = a;
781 
782 	r = *f->ret;
783 	*f->ret = H;
784 	destroy(r);
785 
786 	t = (TkTop*)f->t;
787 	if(t == H || D2H(t)->t != fakeTkTop) {
788 		retstr(TkNotop, f->ret);
789 		return;
790 	}
791 
792 	if(f->i == H) {
793 		retstr(TkBadvl, f->ret);
794 		return;
795 	}
796 
797 	name = string2c(f->name);
798 	lockctxt(t->ctxt);
799 	e = nil;
800 	found = 0;
801 	if(name[0] == '.'){
802 		n = getfields(name, words, nelem(words), 1, " ");
803 		reqid = -1;
804 		if(n > 1){
805 			reqid = atoi(words[1]);
806 			name = words[0];
807 		}
808 		if((tk = tklook(t, name, 0)) != nil){
809 			if(tk->type == TKchoicebutton){
810 				tk = tkfindchoicemenu(tk);
811 				if(tk == nil)
812 					goto Error;
813 			}
814 			if(tk->type == TKframe || tk->type == TKmenu){
815 				if((tk->flag & Tkwindow) == 0){
816 					e = TkNotwm;
817 					goto Error;
818 				}
819 				e = tkputwinimage(tk, f->i, reqid);
820 				found = 1;
821 			} else
822 			if(tk->type == TKpanel){
823 				if(n > 1){
824 					e = TkBadvl;
825 					goto Error;
826 				}
827 				e = tkaddpanelimage(t, f->i, &i);
828 				if(e != nil)
829 					goto Error;
830 				e = tkaddpanelimage(t, f->m, &m);
831 				if(e != nil){
832 					tkdelpanelimage(t, i);
833 					goto Error;
834 				}
835 				tkgetpanelimage(tk, &oldi, &oldm);
836 				tkdelpanelimage(t, oldi);
837 				tkdelpanelimage(t, oldm);
838 				tksetpanelimage(tk, i, m);
839 				tkdirty(tk);
840 				found = 1;
841 			}
842 		}
843 	}
844 	if(!found){
845 		/* XXX perhaps we shouldn't ever do this if name begins with '.'? */
846 		tki = tkname2img(t, name);
847 		if(tki == nil) {
848 			e = TkBadvl;
849 			goto Error;
850 		}
851 
852 		d = t->display;
853 		locked = lockdisplay(d);
854 		tkreplimg(t, f->i, f->m, &tki->img);
855 		if(locked)
856 			unlockdisplay(d);
857 
858 		tksizeimage(t->root, tki);
859 	}
860 Error:
861 	unlockctxt(t->ctxt);
862 	if(e != nil)
863 		retstr(e, f->ret);
864 	return;
865 }
866 
867 Draw_Image*
868 tkimgcopy(TkTop *t, Image *cimg)
869 {
870 	Image *new;
871 	Display *dp;
872 	Draw_Image *i;
873 
874 	if(cimg == nil)
875 		return H;
876 
877 	dp = t->display;
878 	new = allocimage(dp, cimg->r, cimg->chan, cimg->repl, DNofill);
879 	if(new == nil)
880 		return H;
881 	new->clipr = cimg->clipr;
882 
883 	drawop(new, new->r, cimg, nil, cimg->r.min, S);
884 	if(tkwiretap != nil)
885 		tkwiretap(t, "imgcopy", nil, cimg, &cimg->r);
886 
887 	i = mkdrawimage(new, H, t->dd, nil);
888 	if(i == H)
889 		freeimage(new);
890 
891 	return i;
892 }
893 
894 void
895 Tk_getimage(void *a)
896 {
897 	Tk *tk;
898 	char *n;
899 	TkImg *i;
900 	TkTop *t;
901 	int locked;
902 	Display *d;
903 	F_Tk_getimage *f;
904 	void *r;
905 	void (*getimgs)(Tk*, Image**, Image**);
906 	Image *image, *mask;
907 
908 	f = a;
909 
910 	r = f->ret->t0;
911 	f->ret->t0 = H;
912 	destroy(r);
913 	r = f->ret->t1;
914 	f->ret->t1 = H;
915 	destroy(r);
916 	r = f->ret->t2;
917 	f->ret->t2 = H;
918 	destroy(r);
919 
920 	t = (TkTop*)f->t;
921 	if(t == H || D2H(t)->t != fakeTkTop) {
922 		retstr(TkNotop, &f->ret->t2);
923 		return;
924 	}
925 	d = t->ctxt->display;
926 	n = string2c(f->name);
927 	lockctxt(t->ctxt);
928 	i = tkname2img(t, n);
929 	if (i != nil) {
930 		image = i->img;
931 		mask = nil;
932 	} else {
933 		tk = tklook(t, n, 0);
934 		if (tk == nil || (getimgs = tkmethod[tk->type]->getimgs) == nil) {
935 			unlockctxt(t->ctxt);
936 			retstr(TkBadvl, &f->ret->t2);
937 			return;
938 		}
939 		getimgs(tk, &image, &mask);
940 	}
941 	locked = lockdisplay(d);
942 	f->ret->t0 = tkimgcopy(t, image);
943 	if (mask != nil)
944 		f->ret->t1 = tkimgcopy(t, mask);
945 	if (locked)
946 		unlockdisplay(d);
947 	unlockctxt(t->ctxt);
948 }
949 
950 void
951 tkfreetop(Heap *h, int swept)
952 {
953 	TkTop *t;
954 	Tk *f;
955 	TkImg *i, *nexti;
956 	TkVar *v, *nextv;
957 	int wgtype;
958 	void *r;
959 	TkPanelimage *pi, *nextpi;
960 
961 	t = H2D(TkTop*, h);
962 	lockctxt(t->ctxt);
963 
964 	if(swept) {
965 		t->di = H;
966 		t->dd = H;
967 		t->wreq = H;
968 		t->wmctxt = H;
969 	}
970 
971 	t->windows = nil;
972 
973 	for(f = t->root; f; f = f->siblings) {
974 		f->flag |= Tkdestroy;
975 		tkdeliver(f, TkDestroy, nil);
976 		if(f->destroyed != nil)
977 			f->destroyed(f);
978 	}
979 
980 	for(f = t->root; f; f = t->root) {
981 		t->root = f->siblings;
982 		if(swept)
983 			f->flag |= Tkswept;
984 		tkfreeobj(f);
985 	}
986 
987 	for(v = t->vars; v; v = nextv) {
988 		nextv = v->link;
989 		switch(v->type) {
990 		default:
991 			free(v->value);
992 			break;
993 		case TkVchan:
994 			if(!swept)
995 				destroy(v->value);
996 			break;
997 		}
998 		free(v);
999 	}
1000 
1001 	for (pi = t->panelimages; pi; pi = nextpi) {
1002 		if (!swept)
1003 			destroy(pi->image);
1004 		nextpi = pi->link;
1005 		free(pi);
1006 	}
1007 
1008 	for(i = t->imgs; i; i = nexti) {
1009 		if(i->ref != 1)
1010 			abort();
1011 		nexti = i->link;
1012 		tkimgput(i);
1013 	}
1014 	/* XXX free images inside widgets */
1015 
1016 	for(wgtype = 0; wgtype < TKwidgets; wgtype++)
1017 		if(t->binds[wgtype])
1018 			tkfreebind(t->binds[wgtype]);
1019 
1020 	unlockctxt(t->ctxt);
1021 	/* XXX should we leave it locked for this bit? */
1022 	tkfreectxt(t->ctxt);
1023 	if(!swept) {
1024 		r = t->di;
1025 		t->di = H;
1026 		destroy(r);
1027 
1028 		r = t->dd;
1029 		t->dd = H;
1030 		destroy(r);
1031 
1032 		r = t->wreq;
1033 		t->wreq = H;
1034 		destroy(r);
1035 
1036 		r = t->wmctxt;
1037 		t->wmctxt = H;
1038 		destroy(r);
1039 	}
1040 }
1041 
1042 static void
1043 tktopimagedptr(TkTop *top, Draw_Image *di)
1044 {
1045 	if(top->di != H){
1046 		destroy(top->di);
1047 		top->di = H;
1048 	}
1049 	if(di == H)
1050 		return;
1051 	D2H(di)->ref++;
1052 	top->di = di;
1053 }
1054 
1055 static void
1056 tkfreewinimage(TkWin *w)
1057 {
1058 	destroy(w->di);
1059 	w->image = nil;
1060 	w->di = H;
1061 }
1062 
1063 static int
1064 tksetwindrawimage(Tk *tk, Draw_Image *di)
1065 {
1066 	TkWin *tkw;
1067 	char *name;
1068 	Image *i;
1069 	int locked;
1070 	int same;
1071 
1072 	tkw = TKobj(TkWin, tk);
1073 
1074 	same = tkw->di == di;
1075 	if(!same)
1076 		if(tkw->image != nil)
1077 			destroy(tkw->di);
1078 	if(di == H){
1079 		tkw->di = H;
1080 		tkw->image = nil;
1081 		return same;
1082 	}
1083 	tkw->di = di;
1084 	i = lookupimage(di);
1085 	tkw->image = i;
1086 
1087 	locked = lockdisplay(i->display);
1088 	if(originwindow(i, ZP, i->r.min) == -1)
1089 		print("tk originwindow failed: %r\n");
1090 	di->r = DRECT(i->r);
1091 	di->clipr = DRECT(i->clipr);
1092 	if(locked)
1093 		unlockdisplay(i->display);
1094 
1095 	if(!same){
1096 		D2H(di)->ref++;
1097 		if(tk->name){
1098 			name = tk->name->name;
1099 			if(name[0] == '.' && name[1] == '\0')
1100 				tktopimagedptr(tk->env->top, tkw->di);
1101 		}
1102 	}
1103 	return same;
1104 }
1105 
1106 void
1107 tkdestroywinimage(Tk *tk)
1108 {
1109 	TkWin *tkw;
1110 	TkTop *top;
1111 	char *name;
1112 
1113 	assert(tk->flag & Tkwindow);
1114 	tkw = TKobj(TkWin, tk);
1115 	top = tk->env->top;
1116 
1117 	if(tkw->image != nil && !(tk->flag & Tkswept))
1118 		destroy(tkw->di);
1119 	tkw->di = H;
1120 	tkw->image = nil;
1121 	if(tk->name == nil)
1122 		name = tkw->cbname;
1123 	else
1124 		name = tk->name->name;
1125 	if(name[0] == '.' && name[1] == '\0' && !(tk->flag & Tkswept))
1126 		tktopimagedptr(top, H);
1127 	tkw->reqid++;
1128 	tkwreq(top, "delete %s", name);
1129 }
1130 
1131 static char*
1132 tkputwinimage(Tk *tk, Draw_Image *di, int reqid)
1133 {
1134 	TkWin *tkw;
1135 	TkTop *top;
1136 	Image *i;
1137 	int bw2, prop, resize;
1138 	Rectangle req;
1139 
1140 	top = tk->env->top;
1141 	tkw = TKobj(TkWin, tk);
1142 	i = lookupimage(di);
1143 	if (i == nil || i->display != top->display)
1144 		return TkNotwm;
1145 
1146 	if(reqid != -1 && reqid < tkw->reqid)
1147 		return "!request out of date";
1148 
1149 	bw2 = 2*tk->borderwidth;
1150 	req.min.x = tkw->req.x;
1151 	req.min.y = tkw->req.y;
1152 	req.max.x = req.min.x + tk->act.width + bw2;
1153 	req.max.y = req.min.y + tk->act.height + bw2;
1154 
1155 	resize = 0;
1156 	if(eqrect(req, i->r) == 0){
1157 		/*
1158 		 * if we'd sent a request and our requested rect has now changed,
1159 		 * then resend the request (via tkupdatewinsize),
1160 		 * otherwise accept the new size and repack if necessary
1161 		 */
1162 		if(reqid != -1 && tkw->changed){
1163 			if(tkupdatewinsize(tk))
1164 				return "!requested size has changed";
1165 
1166 		} else if(Dx(req) != Dx(i->r) || Dy(req) != Dy(i->r)){
1167 			tk->flag |= Tksuspended;
1168 			tk->act.width = Dx(i->r) - bw2;
1169 			tk->act.height = Dy(i->r) - bw2;
1170 			tk->req = tk->act;
1171 			prop = tk->flag & Tknoprop;
1172 			tk->flag |= Tknoprop;
1173 			tkpackqit(tk);
1174 			tkrunpack(top);
1175 			tk->flag = (tk->flag & ~Tknoprop) | prop;
1176 			resize = 1;
1177 		}
1178 	}
1179 	if(reqid == -1)
1180 		tkw->reqid++;		/* invalidate all buffered requests. */
1181 	tkw->act = i->r.min;
1182 	tkw->req = tkw->act;
1183 	tkw->changed = 0;
1184 	tk->req.width = Dx(i->r) - bw2;
1185 	tk->req.height = Dy(i->r) - bw2;
1186 	tk->act = tk->req;
1187 	if((tk->flag & Tkmapped) == 0){
1188 		tk->flag |= Tkmapped;
1189 		tkdeliver(tk, TkMap, nil);
1190 	}
1191 	if(tksetwindrawimage(tk, di) == 0 || resize){
1192 		tk->dirty = tkrect(tk, 1);
1193 		tk->flag |= Tkrefresh;
1194 	}
1195 	tk->flag &= ~Tksuspended;
1196 
1197 	lookupimage(di);			/* make sure limbo image coords correspond correctly */
1198 	tkupdate(top);
1199 	return nil;
1200 }
1201 
1202 void
1203 tkwreq(TkTop *top, char *fmt, ...)
1204 {
1205 	char *buf;
1206 	va_list arg;
1207 
1208 	va_start(arg, fmt);
1209 	buf = vsmprint(fmt, arg);
1210 	va_end(arg);
1211 	tktolimbo(top->wreq, buf);
1212 	free(buf);
1213 }
1214 
1215 int
1216 tktolimbo(void *var, char *msg)
1217 {
1218 	void *ptrs[1];
1219 	int r;
1220 
1221 	if(var==H)
1222 		return 0;
1223 	ptrs[0] = H;
1224 	retstr(msg, (String**) &ptrs[0]);
1225 	r = csendalt((Channel *)var, ptrs, &Tptr, TkMaxmsgs);
1226 	return r;
1227 }
1228 
1229 static void
1230 hexify(char *buf, int n)
1231 {
1232 	static char hex[] = "0123456789abcdef";
1233 	uchar b;
1234 	char *dp, *fp;
1235 	fp = buf+n;
1236 	dp = buf+n*2;
1237 	*dp-- = '\0';
1238 	while(fp-- > buf){
1239 		b = (uchar)*fp;
1240 		*dp-- = hex[b & 0xf];
1241 		*dp-- = hex[b >> 4];
1242 	}
1243 }
1244 
1245 char*
1246 tkcursorswitch(TkTop *top, Image *i, TkImg *img)
1247 {
1248 	Image *ci, *scratch;
1249 	char *buf;
1250 	Rectangle r;
1251 	int n, maxb, nb;
1252 
1253 	if(i == nil && img == nil){
1254 		tktolimbo(top->wreq, "cursor");
1255 		return nil;
1256 	}
1257 
1258 	if(img != nil){
1259 		if(img->cursor){
1260 			tktolimbo(top->wreq, img->cursor);
1261 			return nil;
1262 		}
1263 		i = img->img;
1264 	}
1265 	if(i->depth != 1 || Dx(i->r)*Dy(i->r) > 16000 || Dy(i->r)%8 != 0 || Dy(i->r)%2 != 0)
1266 		return TkBadcursor;
1267 	/*
1268 	 * readjust image, inferring hotspot from origin.
1269 	 */
1270 	if(i->r.min.x != 0 || i->r.min.y != 0){
1271 		r.min.x = 0;
1272 		r.min.y = 0;
1273 		r.max.x = Dx(i->r);
1274 		r.max.y = Dy(i->r);
1275 		scratch = allocimage(i->display, r, GREY1, 0, DNofill);
1276 		if(scratch == nil)
1277 			return TkNomem;
1278 		draw(scratch, r, i, nil, i->r.min);
1279 		ci = scratch;
1280 	}else{
1281 		scratch = nil;
1282 		ci = i;
1283 	}
1284 	nb = ci->r.max.x/8 * ci->r.max.y;
1285 	maxb = 7 + 12*4 + 2*nb + 1;
1286 	buf = mallocz(maxb, 0);
1287 	if(buf == nil)
1288 		return TkNomem;
1289 	n = sprint(buf, "cursor %d %d %d %d ", i->r.min.x, i->r.min.y, ci->r.max.x, ci->r.max.y);
1290 	unloadimage(ci, ci->r, (uchar*)buf+n, maxb-n);
1291 	hexify(buf+n, nb);
1292 	tktolimbo(top->wreq, buf);
1293 	if(img != nil){
1294 		free(img->cursor);
1295 		img->cursor = buf;
1296 	}
1297 	freeimage(scratch);
1298 	return nil;
1299 }
1300 
1301 void
1302 tkcursorset(TkTop *t, Point p)
1303 {
1304 	tkwreq(t, "ptr %d %d", p.x, p.y);
1305 }
1306