xref: /inferno-os/libtk/ebind.c (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1 #include "lib9.h"
2 #include "draw.h"
3 #include "tk.h"
4 #include <kernel.h>
5 #include <interp.h>
6 
7 enum
8 {
9 	Cmask,
10 	Cctl,
11 	Ckey,
12 	Cbp,
13 	Cbr,
14 };
15 
16 struct
17 {
18 	char*	event;
19 	int	mask;
20 	int	action;
21 } etab[] =
22 {
23 	"Motion",		TkMotion,	Cmask,
24 	"Double",		TkDouble,	Cmask,
25 	"Map",			TkMap,		Cmask,
26 	"Unmap",		TkUnmap,	Cmask,
27 	"Destroy",		TkDestroy, Cmask,
28 	"Enter",		TkEnter,	Cmask,
29 	"Leave",		TkLeave,	Cmask,
30 	"FocusIn",		TkFocusin,	Cmask,
31 	"FocusOut",		TkFocusout,	Cmask,
32 	"Configure",		TkConfigure,	Cmask,
33 	"Control",		0,		Cctl,
34 	"Key",			0,		Ckey,
35 	"KeyPress",		0,		Ckey,
36 	"Button",		0,		Cbp,
37 	"ButtonPress",		0,		Cbp,
38 	"ButtonRelease",	0,		Cbr,
39 };
40 
41 static
42 TkOption tkcurop[] =
43 {
44 	"x",		OPTdist,	O(TkCursor, p.x),	nil,
45 	"y",		OPTdist,	O(TkCursor, p.y),	nil,
46 	"bitmap",	OPTbmap,	O(TkCursor, bit),	nil,
47 	"image",	OPTimag,	O(TkCursor, img),	nil,
48 	"default",	OPTbool,	O(TkCursor, def),	nil,
49 	nil
50 };
51 
52 static
53 TkOption focusopts[] = {
54 	"global",			OPTbool,	0,	nil,
55 	nil
56 };
57 
58 static char*
59 tkseqitem(char *buf, char *arg)
60 {
61 	while(*arg && (*arg == ' ' || *arg == '-'))
62 		arg++;
63 	while(*arg && *arg != ' ' && *arg != '-' && *arg != '>')
64 		*buf++ = *arg++;
65 	*buf = '\0';
66 	return arg;
67 }
68 
69 static char*
70 tkseqkey(Rune *r, char *arg)
71 {
72 	char *narg;
73 
74 	while(*arg && (*arg == ' ' || *arg == '-'))
75 		arg++;
76 	if (*arg == '\\') {
77 		if (*++arg == '\0') {
78 			*r = 0;
79 			return arg;
80 		}
81 	} else if (*arg == '\0' || *arg == '>' || *arg == '-') {
82 		*r = 0;
83 		return arg;
84 	}
85 	narg = arg + chartorune(r, arg);
86 	return narg;
87 }
88 
89 int
90 tkseqparse(char *seq)
91 {
92 	Rune r;
93 	int i, event;
94 	char *buf;
95 
96 	buf = mallocz(Tkmaxitem, 0);
97 	if(buf == nil)
98 		return -1;
99 
100 	event = 0;
101 
102 	while(*seq && *seq != '>') {
103 		seq = tkseqitem(buf, seq);
104 
105 		for(i = 0; i < nelem(etab); i++)
106 			if(strcmp(buf, etab[i].event) == 0)
107 				break;
108 
109 		if(i >= nelem(etab)) {
110 			seq = tkextnparseseq(buf, seq, &event);
111 			if (seq == nil) {
112 				free(buf);
113 				return -1;
114 			}
115 			continue;
116 		}
117 
118 
119 		switch(etab[i].action) {
120 		case Cmask:
121 			event |= etab[i].mask;
122 			break;
123 		case Cctl:
124 			seq = tkseqkey(&r, seq);
125 			if(r == 0) {
126 				free(buf);
127 				return -1;
128 			}
129 			if(r <= '~')
130 				r &= 0x1f;
131 			event |= TkKey|TKKEY(r);
132 			break;
133 		case Ckey:
134 			seq = tkseqkey(&r, seq);
135 			if(r != 0)
136 				event |= TKKEY(r);
137 			event |= TkKey;
138 			break;
139 		case Cbp:
140 			seq = tkseqitem(buf, seq);
141 			switch(buf[0]) {
142 			default:
143 				free(buf);
144 				return -1;
145 			case '\0':
146 				event |= TkEpress;
147 				break;
148 			case '1':
149 				event |= TkButton1P;
150 				break;
151 			case '2':
152 				event |= TkButton2P;
153 				break;
154 			case '3':
155 				event |= TkButton3P;
156 				break;
157 			case '4':
158 				event |= TkButton4P;
159 				break;
160 			case '5':
161 				event |= TkButton5P;
162 				break;
163 			case '6':
164 				event |= TkButton6P;
165 				break;
166 			}
167 			break;
168 		case Cbr:
169 			seq = tkseqitem(buf, seq);
170 			switch(buf[0]) {
171 			default:
172 				free(buf);
173 				return -1;
174 			case '\0':
175 				event |= TkErelease;
176 				break;
177 			case '1':
178 				event |= TkButton1R;
179 				break;
180 			case '2':
181 				event |= TkButton2R;
182 				break;
183 			case '3':
184 				event |= TkButton3R;
185 				break;
186 			case '4':
187 				event |= TkButton4R;
188 				break;
189 			case '5':
190 				event |= TkButton5R;
191 				break;
192 			case '6':
193 				event |= TkButton6R;
194 				break;
195 			}
196 			break;
197 		}
198 	}
199 	free(buf);
200 	return event;
201 }
202 
203 void
204 tkcmdbind(Tk *tk, int event, char *s, void *data)
205 {
206 	Point p;
207 	TkMouse *m;
208 	TkGeom *g;
209 	int v, len;
210 	char *e, *c, *ec, *cmd;
211 	TkTop *t;
212 
213 	if(s == nil)
214 		return;
215 	cmd = malloc(2*Tkmaxitem);
216 	if (cmd == nil) {
217 		print("tk: bind command \"%s\": %s\n",
218 			tk->name ? tk->name->name : "(noname)", TkNomem);
219 		return;
220 	}
221 
222 	m = (TkMouse*)data;
223 	c = cmd;
224 	ec = cmd+2*Tkmaxitem-1;
225 	while(*s && c < ec) {
226 		if(*s != '%') {
227 			*c++ = *s++;
228 			continue;
229 		}
230 		s++;
231 		len = ec-c;
232 		switch(*s++) {
233 		def:
234 		default:
235 			*c++ = s[-1];
236 			break;
237 		case '%':
238 			*c++ = '%';
239 			break;
240 		case 'b':
241 			v = 0;
242 			if (!(event & TkKey)) {
243 				if(event & (TkButton1P|TkButton1R))
244 					v = 1;
245 				else
246 				if(event & (TkButton2P|TkButton2R))
247 					v = 2;
248 				else
249 				if(event & (TkButton3P|TkButton3R))
250 					v = 3;
251 			}
252 			c += snprint(c, len, "%d", v);
253 			break;
254 		case 'h':
255 			if((event & TkConfigure) == 0)
256 				goto def;
257 			g = (TkGeom*)data;
258 			c += snprint(c, len, "%d", g->height);
259 			break;
260 		case 's':
261 			if((event & TkKey))
262 				c += snprint(c, len, "%d", TKKEY(event));
263 			else
264 			if((event & (TkEmouse|TkEnter)))
265 				c += snprint(c, len, "%d", m->b);
266 			else
267 			if((event & TkFocusin))
268 				c += snprint(c, len, "%d", (int)data);
269 			else
270 				goto def;
271 			break;
272 		case 'w':
273 			if((event & TkConfigure) == 0)
274 				goto def;
275 			g = (TkGeom*)data;
276 			c += snprint(c, len, "%d", g->width);
277 			break;
278 		case 'x':		/* Relative mouse coords */
279 		case 'y':
280 			if((event & TkKey) || (event & (TkEmouse|TkEnter)) == 0)
281 				goto def;
282 			p = tkposn(tk);
283 			if(s[-1] == 'x')
284 				v = m->x - p.x;
285 			else
286 				v = m->y - p.y;
287 			c += snprint(c, len, "%d", v - tk->borderwidth);
288 			break;
289 		case 'X':		/* Absolute mouse coords */
290 		case 'Y':
291 			if((event & TkKey) || (event & TkEmouse) == 0)
292 				goto def;
293 			c += snprint(c, len, "%d", s[-1] == 'X' ? m->x : m->y);
294 			break;
295 		case 'A':
296 			if((event & TkKey) == 0)
297 				goto def;
298 			v = TKKEY(event);
299 			if(v == '{' || v == '}' || v == '\\')
300 				c += snprint(c, len, "\\%C", v);
301 			else
302 			if(v != '\0')
303 				c += snprint(c, len, "%C", v);
304 			break;
305 		case 'K':
306 			if((event & TkKey) == 0)
307 				goto def;
308 			c += snprint(c, len, "%.4X", TKKEY(event));
309 			break;
310 		case 'W':
311 		        if (tk->name != nil)
312 			  c += snprint(c, len, "%s", tk->name->name);
313 			break;
314 		}
315 	}
316 	*c = '\0';
317 	e = nil;
318 	t = tk->env->top;
319 	t->execdepth = 0;
320 	if(cmd[0] == '|')
321 		tkexec(t, cmd+1, nil);
322 	else
323 	if(cmd[0] != '\0')
324 		e = tkexec(t, cmd, nil);
325 	t->execdepth = -1;
326 
327 	if(e == nil) {
328 		free(cmd);
329 		return;
330 	}
331 
332 	if(tk->name != nil){
333 		char *s;
334 
335 		if(t->errx[0] != '\0')
336 			s = tkerrstr(t, e);
337 		else
338 			s = e;
339 		print("tk: bind command \"%s\": %s: %s\n", tk->name->name, cmd, s);
340 		if(s != e)
341 			free(s);
342 	}
343 	free(cmd);
344 }
345 
346 char*
347 tkbind(TkTop *t, char *arg, char **ret)
348 {
349 	Rune r;
350 	Tk *tk;
351 	TkAction **ap;
352 	int i, mode, event;
353 	char *cmd, *tag, *seq;
354 	char *e;
355 
356 	USED(ret);
357 
358 	tag = mallocz(Tkmaxitem, 0);
359 	if(tag == nil)
360 		return TkNomem;
361 	seq = mallocz(Tkmaxitem, 0);
362 	if(seq == nil) {
363 		free(tag);
364 		return TkNomem;
365 	}
366 
367 	arg = tkword(t, arg, tag, tag+Tkmaxitem, nil);
368 	if(tag[0] == '\0') {
369 		e = TkBadtg;
370 		goto err;
371 	}
372 
373 	arg = tkword(t, arg, seq, seq+Tkmaxitem, nil);
374 	if(seq[0] == '<') {
375 		event = tkseqparse(seq+1);
376 		if(event == -1) {
377 			e = TkBadsq;
378 			goto err;
379 		}
380 	}
381 	else {
382 		chartorune(&r, seq);
383 		event = TkKey | r;
384 	}
385 	if(event == 0) {
386 		e = TkBadsq;
387 		goto err;
388 	}
389 
390 	arg = tkskip(arg, " \t");
391 
392 	mode = TkArepl;
393 	if(*arg == '+') {
394 		mode = TkAadd;
395 		arg++;
396 	}
397 	else if(*arg == '-'){
398 		mode = TkAsub;
399 		arg++;
400 	}
401 
402 	if(*arg == '{') {
403 		cmd = tkskip(arg+1, " \t");
404 		if(*cmd == '}') {
405 			tk = tklook(t, tag, 0);
406 			if(tk == nil) {
407 				for(i = 0; ; i++) {
408 					if(i >= TKwidgets) {
409 						e = TkBadwp;
410 						tkerr(t, tag);
411 						goto err;
412 					}
413 					if(strcmp(tag, tkmethod[i]->name) == 0) {
414 						ap = &(t->binds[i]);
415 						break;
416 					}
417 				}
418 			}
419 			else
420 				ap = &tk->binds;
421 			tkcancel(ap, event);
422 		}
423 	}
424 
425 	tkword(t, arg, seq, seq+Tkmaxitem, nil);
426 	if(tag[0] == '.') {
427 		tk = tklook(t, tag, 0);
428 		if(tk == nil) {
429 			e = TkBadwp;
430 			tkerr(t, tag);
431 			goto err;
432 		}
433 
434 		cmd = strdup(seq);
435 		if(cmd == nil) {
436 			e = TkNomem;
437 			goto err;
438 		}
439 		e = tkaction(&tk->binds, event, TkDynamic, cmd, mode);
440 		if(e != nil)
441 			goto err;	/* tkaction does free(cmd) */
442 		free(tag);
443 		free(seq);
444 		return nil;
445 	}
446 	/* documented but doesn't work */
447 	if(strcmp(tag, "all") == 0) {
448 		for(tk = t->root; tk; tk = tk->next) {
449 			cmd = strdup(seq);
450 			if(cmd == nil) {
451 				e = TkNomem;
452 				goto err;
453 			}
454 			e = tkaction(&tk->binds, event, TkDynamic, cmd, mode);
455 			if(e != nil)
456 				goto err;
457 		}
458 		free(tag);
459 		free(seq);
460 		return nil;
461 	}
462 	/* undocumented, probably unused, and doesn't work consistently */
463 	for(i = 0; i < TKwidgets; i++) {
464 		if(strcmp(tag, tkmethod[i]->name) == 0) {
465 			cmd = strdup(seq);
466 			if(cmd == nil) {
467 				e = TkNomem;
468 				goto err;
469 			}
470 			e = tkaction(t->binds + i,event, TkDynamic, cmd, mode);
471 			if(e != nil)
472 				goto err;
473 			free(tag);
474 			free(seq);
475 			return nil;
476 		}
477 	}
478 
479 	e = TkBadtg;
480 err:
481 	free(tag);
482 	free(seq);
483 
484 	return e;
485 }
486 
487 char*
488 tksend(TkTop *t, char *arg, char **ret)
489 {
490 
491 	TkVar *v;
492 	char *var;
493 
494 	USED(ret);
495 
496 	var = mallocz(Tkmaxitem, 0);
497 	if(var == nil)
498 		return TkNomem;
499 
500 	arg = tkword(t, arg, var, var+Tkmaxitem, nil);
501 	v = tkmkvar(t, var, 0);
502 	free(var);
503 	if(v == nil)
504 		return TkBadvr;
505 	if(v->type != TkVchan)
506 		return TkNotvt;
507 
508 	arg = tkskip(arg, " \t");
509 	if(tktolimbo(v->value, arg) == 0)
510 		return TkMovfw;
511 
512 	return nil;
513 }
514 
515 static Tk*
516 tknextfocus(TkTop *t, int d)
517 {
518 	int i, n, j, k;
519 	Tk *oldfocus;
520 
521 	if (t->focusorder == nil)
522 		tkbuildfocusorder(t);
523 
524 	oldfocus = t->ctxt->tkkeygrab;
525 	n = t->nfocus;
526 	if (n == 0)
527 		return oldfocus;
528 	for (i = 0; i < n; i++)
529 		if (t->focusorder[i] == oldfocus)
530 			break;
531 	if (i == n) {
532 		for (i = 0; i < n; i++)
533 			if ((t->focusorder[i]->flag & Tkdisabled) == 0)
534 				return t->focusorder[i];
535 		return oldfocus;
536 	}
537 	for (j = 1; j < n; j++) {
538 		k = (i + d * j + n) % n;
539 		if ((t->focusorder[k]->flag & Tkdisabled) == 0)
540 			return t->focusorder[k];
541 	}
542 	return oldfocus;
543 }
544 
545 /* our dirty little secret */
546 static void
547 focusdirty(Tk *tk)
548 {
549 	if(tk->highlightwidth > 0){
550 		tk->dirty = tkrect(tk, 1);
551 		tkdirty(tk);
552 	}
553 }
554 
555 void
556 tksetkeyfocus(TkTop *top, Tk *new, int dir)
557 {
558 	TkCtxt *c;
559 	Tk *old;
560 
561 	c = top->ctxt;
562 	old = c->tkkeygrab;
563 
564 	if(old == new)
565 		return;
566 	c->tkkeygrab = new;
567 	if(top->focused == 0)
568 		return;
569 	if(old != nil && old != top->root){
570 		tkdeliver(old, TkFocusout, nil);
571 		focusdirty(old);
572 	}
573 	if(new != nil && new != top->root){
574 		tkdeliver(new, TkFocusin, (void*)dir);
575 		focusdirty(new);
576 	}
577 }
578 
579 void
580 tksetglobalfocus(TkTop *top, int in)
581 {
582 	Tk *tk;
583 	in = (in != 0);
584 	if (in != top->focused){
585 		top->focused = in;
586 		tk = top->ctxt->tkkeygrab;
587 		if(in){
588 			tkdeliver(top->root, TkFocusin, (void*)0);
589 			if(tk != nil && tk != top->root){
590 				tkdeliver(tk, TkFocusin, (void*)0);
591 				focusdirty(tk);
592 			}
593 		}else{
594 			if(tk != nil && tk != top->root){
595 				tkdeliver(tk, TkFocusout, nil);
596 				focusdirty(tk);
597 			}
598 			tkdeliver(top->root, TkFocusout, nil);
599 		}
600 	}
601 }
602 
603 char*
604 tkfocus(TkTop *top, char *arg, char **ret)
605 {
606 	Tk *tk;
607 	char *wp, *e;
608 	int dir, global;
609 	TkOptab tko[2];
610 	TkName *names;
611 
612 	tko[0].ptr = &global;
613 	tko[0].optab = focusopts;
614 	tko[1].ptr = nil;
615 
616 	global = 0;
617 
618 	names = nil;
619 	e = tkparse(top, arg, tko, &names);
620 	if (e != nil)
621 		return e;
622 
623 	if(names == nil){
624 		if(global)
625 			return tkvalue(ret, "%d", top->focused);
626 		tk = top->ctxt->tkkeygrab;
627 		if (tk != nil && tk->name != nil)
628 			return tkvalue(ret, "%s", tk->name->name);
629 		return nil;
630 	}
631 
632 	if(global){
633 		tksetglobalfocus(top, atoi(names->name));
634 		return nil;
635 	}
636 
637 	wp = mallocz(Tkmaxitem, 0);
638 	if(wp == nil)
639 		return TkNomem;
640 
641 	tkword(top, arg, wp, wp+Tkmaxitem, nil);
642 	if (!strcmp(wp, "next")) {
643 		tk = tknextfocus(top, 1);		/* can only return nil if c->tkkeygrab is already nil */
644 		dir = +1;
645 	} else if (!strcmp(wp, "previous")) {
646 		tk = tknextfocus(top, -1);
647 		dir = -1;
648 	} else if(*wp == '\0') {
649 		tk = nil;
650 		dir = 0;
651 	} else {
652 		tk = tklook(top, wp, 0);
653 		if(tk == nil){
654 			tkerr(top, wp);
655 			free(wp);
656 			return TkBadwp;
657 		}
658 		dir = 0;
659 	}
660 	free(wp);
661 
662 	tksetkeyfocus(top, tk, dir);
663 	return nil;
664 }
665 
666 char*
667 tkraise(TkTop *t, char *arg, char **ret)
668 {
669 	Tk *tk;
670 	char *wp;
671 
672 	USED(ret);
673 
674 	wp = mallocz(Tkmaxitem, 0);
675 	if(wp == nil)
676 		return TkNomem;
677 	tkword(t, arg, wp, wp+Tkmaxitem, nil);
678 	tk = tklook(t, wp, 0);
679 	if(tk == nil){
680 		tkerr(t, wp);
681 		free(wp);
682 		return TkBadwp;
683 	}
684 	free(wp);
685 
686 	if((tk->flag & Tkwindow) == 0)
687 		return TkNotwm;
688 
689 	tkwreq(tk->env->top, "raise %s", tk->name->name);
690 	return nil;
691 }
692 
693 char*
694 tklower(TkTop *t, char *arg, char **ret)
695 {
696 	Tk *tk;
697 	char *wp;
698 
699 	USED(ret);
700 	wp = mallocz(Tkmaxitem, 0);
701 	if(wp == nil)
702 		return TkNomem;
703 	tkword(t, arg, wp, wp+Tkmaxitem, nil);
704 	tk = tklook(t, wp, 0);
705 	if(tk == nil){
706 		tkerr(t, wp);
707 		free(wp);
708 		return TkBadwp;
709 	}
710 	free(wp);
711 
712 	if((tk->flag & Tkwindow) == 0)
713 		return TkNotwm;
714 
715 	tkwreq(tk->env->top, "lower %s", tk->name->name);
716 	return nil;
717 }
718 
719 char*
720 tkgrab(TkTop *t, char *arg, char **ret)
721 {
722 	Tk *tk;
723 	TkCtxt *c;
724 	char *r, *buf, *wp;
725 
726 	USED(ret);
727 
728 	buf = mallocz(Tkmaxitem, 0);
729 	if(buf == nil)
730 		return TkNomem;
731 
732 	wp = mallocz(Tkmaxitem, 0);
733 	if(wp == nil) {
734 		free(buf);
735 		return TkNomem;
736 	}
737 	arg = tkword(t, arg, buf, buf+Tkmaxitem, nil);
738 
739 	tkword(t, arg, wp, wp+Tkmaxitem, nil);
740 	tk = tklook(t, wp, 0);
741 	if(tk == nil) {
742 		free(buf);
743 		tkerr(t, wp);
744 		free(wp);
745 		return TkBadwp;
746 	}
747 	free(wp);
748 
749 	c = t->ctxt;
750 	if(strcmp(buf, "release") == 0) {
751 		free(buf);
752 		if(c->mgrab == tk)
753 			tksetmgrab(t, nil);
754 		return nil;
755 	}
756 	if(strcmp(buf, "set") == 0) {
757 		free(buf);
758 		return tksetmgrab(t, tk);
759 	}
760 	if(strcmp(buf, "ifunset") == 0) {
761 		free(buf);
762 		if(c->mgrab == nil)
763 			return tksetmgrab(t, tk);
764 		return nil;
765 	}
766 	if(strcmp(buf, "status") == 0) {
767 		free(buf);
768 		r = "none";
769 		if ((c->mgrab != nil) && (c->mgrab->name != nil))
770 			r = c->mgrab->name->name;
771 		return tkvalue(ret, "%s", r);
772 	}
773 	free(buf);
774 	return TkBadcm;
775 }
776 
777 char*
778 tkputs(TkTop *t, char *arg, char **ret)
779 {
780 	char *buf;
781 
782 	USED(ret);
783 
784 	buf = mallocz(Tkmaxitem, 0);
785 	if(buf == nil)
786 		return TkNomem;
787 	tkword(t, arg, buf, buf+Tkmaxitem, nil);
788 	print("%s\n", buf);
789 	free(buf);
790 	return nil;
791 }
792 
793 char*
794 tkdestroy(TkTop *t, char *arg, char **ret)
795 {
796 	int found, len, isroot;
797 	Tk *tk, **l, *next, *slave;
798 	char *n, *e, *buf;
799 
800 	USED(ret);
801 	buf = mallocz(Tkmaxitem, 0);
802 	if(buf == nil)
803 		return TkNomem;
804 	e = nil;
805 	for(;;) {
806 		arg = tkword(t, arg, buf, buf+Tkmaxitem, nil);
807 		if(buf[0] == '\0')
808 			break;
809 
810 		len = strlen(buf);
811 		found = 0;
812 		isroot = (strcmp(buf, ".") == 0);
813 		for(tk = t->root; tk; tk = tk->siblings) {
814 		        if (tk->name != nil) {
815 				n = tk->name->name;
816 				if(strcmp(buf, n) == 0) {
817 					tk->flag |= Tkdestroy;
818 					found = 1;
819 				} else if(isroot || (strncmp(buf, n, len) == 0 &&n[len] == '.'))
820 					tk->flag |= Tkdestroy;
821 			}
822 		}
823 		if(!found) {
824 			e = TkBadwp;
825 			tkerr(t, buf);
826 			break;
827 		}
828 	}
829 	free(buf);
830 
831 	for(tk = t->root; tk; tk = tk->siblings) {
832 		if((tk->flag & Tkdestroy) == 0)
833 			continue;
834 		if(tk->flag & Tkwindow) {
835 			tkunmap(tk);
836 			if((tk->name != nil)
837 			   && (strcmp(tk->name->name, ".") == 0))
838 				tk->flag &= ~Tkdestroy;
839 			else
840 				tkdeliver(tk, TkDestroy, nil);
841 		} else
842 			tkdeliver(tk, TkDestroy, nil);
843 		if(tk->destroyed != nil)
844 			tk->destroyed(tk);
845 		tkpackqit(tk->master);
846 		tkdelpack(tk);
847 		for (slave = tk->slave; slave != nil; slave = next) {
848 			next = slave->next;
849 			slave->master = nil;
850 			slave->next = nil;
851 		}
852 		tk->slave = nil;
853 		if(tk->parent != nil && tk->geom != nil)		/* XXX this appears to be bogus */
854 			tk->geom(tk, 0, 0, 0, 0);
855 		if(tk->grid){
856 			tkfreegrid(tk->grid);
857 			tk->grid = nil;
858 		}
859 	}
860 	tkrunpack(t);
861 
862 	l = &t->windows;
863 	for(tk = t->windows; tk; tk = next) {
864 		next = TKobj(TkWin, tk)->next;
865 		if(tk->flag & Tkdestroy) {
866 			*l = next;
867 			continue;
868 		}
869 		l = &TKobj(TkWin, tk)->next;
870 	}
871 	l = &t->root;
872 	for(tk = t->root; tk; tk = next) {
873 		next = tk->siblings;
874 		if(tk->flag & Tkdestroy) {
875 			*l = next;
876 			tkfreeobj(tk);
877 			continue;
878 		}
879 		l = &tk->siblings;
880 	}
881 
882 	return e;
883 }
884 
885 char*
886 tkupdatecmd(TkTop *t, char *arg, char **ret)
887 {
888 	Tk *tk;
889 	int x, y;
890 	Rectangle *dr;
891 	char buf[Tkmaxitem];
892 
893 	USED(ret);
894 
895 	tkword(t, arg, buf, buf+sizeof(buf), nil);
896 	if(strcmp(buf, "-onscreen") == 0){
897 		tk = t->root;
898 		dr = &t->screenr;
899 		x = tk->act.x;
900 		if(x+tk->act.width > dr->max.x)
901 			x = dr->max.x - tk->act.width;
902 		if(x < 0)
903 			x = 0;
904 		y = tk->act.y;
905 		if(y+tk->act.height > dr->max.y)
906 			y = dr->max.y - tk->act.height;
907 		if(y < 0)
908 			y = 0;
909 		tkmovewin(tk, Pt(x, y));
910 	}else if(strcmp(buf, "-disable") == 0){
911 		t->noupdate = 1;
912 	}else if(strcmp(buf, "-enable") == 0){
913 		t->noupdate = 0;
914 	}
915 	return tkupdate(t);
916 }
917 
918 char*
919 tkwinfo(TkTop *t, char *arg, char **ret)
920 {
921 	Tk *tk;
922 	char *cmd, *arg1;
923 
924 	cmd = mallocz(Tkmaxitem, 0);
925 	if(cmd == nil)
926 		return TkNomem;
927 
928 	arg = tkword(t, arg, cmd, cmd+Tkmaxitem, nil);
929 	if(strcmp(cmd, "class") == 0) {
930 		arg1 = mallocz(Tkmaxitem, 0);
931 		if(arg1 == nil) {
932 			free(cmd);
933 			return TkNomem;
934 		}
935 		tkword(t, arg, arg1, arg1+Tkmaxitem, nil);
936 		tk = tklook(t, arg1, 0);
937 		if(tk == nil){
938 			tkerr(t, arg1);
939 			free(arg1);
940 			free(cmd);
941 			return TkBadwp;
942 		}
943 		free(arg1);
944 		free(cmd);
945 		return tkvalue(ret, "%s", tkmethod[tk->type]->name);
946 	}
947 	free(cmd);
948 	return TkBadvl;
949 }
950 
951 char*
952 tkcursorcmd(TkTop *t, char *arg, char **ret)
953 {
954 	char *e;
955 	int locked;
956 	Display *d;
957 	TkCursor c;
958 	TkOptab tko[3];
959 	enum {Notset = 0x80000000};
960 
961 	c.def = 0;
962 	c.p.x = Notset;
963 	c.p.y = Notset;
964 	c.bit = nil;
965 	c.img = nil;
966 
967 	USED(ret);
968 
969 	c.def = 0;
970 	tko[0].ptr = &c;
971 	tko[0].optab = tkcurop;
972 	tko[1].ptr = nil;
973 	e = tkparse(t, arg, tko, nil);
974 	if(e != nil)
975 		return e;
976 
977 	d = t->display;
978 	locked = lockdisplay(d);
979 	if(c.def)
980 		tkcursorswitch(t, nil, nil);
981 	if(c.img != nil || c.bit != nil){
982 		e = tkcursorswitch(t, c.bit, c.img);
983 		tkimgput(c.img);
984 		freeimage(c.bit);
985 	}
986 	if(e == nil){
987 		if(c.p.x != Notset && c.p.y != Notset)
988 			tkcursorset(t, c.p);
989 	}
990 	if(locked)
991 		unlockdisplay(d);
992 	return e;
993 }
994 
995 char *
996 tkbindings(TkTop *t, Tk *tk, TkEbind *b, int blen)
997 {
998 	TkAction *a, **ap;
999 	char *cmd, *e;
1000 	int i;
1001 
1002 	e = nil;
1003 	for(i = 0; e == nil && i < blen; i++)	/* default bindings */ {
1004 		int how = TkArepl;
1005 		char *cmd = b[i].cmd;
1006 		if(cmd[0] == '+') {
1007 			how = TkAadd;
1008 			cmd++;
1009 		}
1010 		else if(cmd[0] == '-'){
1011 			how = TkAsub;
1012 			cmd++;
1013 		}
1014 		e = tkaction(&tk->binds, b[i].event, TkStatic, cmd, how);
1015 	}
1016 
1017 	if(e != nil)
1018 		return e;
1019 
1020 	ap = &tk->binds;
1021 	for(a = t->binds[tk->type]; a; a = a->link) {	/* user "defaults" */
1022 		cmd = strdup(a->arg);
1023 		if(cmd == nil)
1024 			return TkNomem;
1025 
1026 		e = tkaction(ap, a->event, TkDynamic, cmd,
1027 						(a->type >> 8) & 0xff);
1028 		if(e != nil)
1029 			return e;
1030 		ap = &(*ap)->link;
1031 	}
1032 	return nil;
1033 }
1034