xref: /plan9/sys/src/libcontrol/control.c (revision ff8c3af2f44d95267f67219afa20ba82ff6cf7e4)
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <thread.h>
5 #include <mouse.h>
6 #include <keyboard.h>
7 #include <control.h>
8 
9 enum	/* alts */
10 {
11 	AKey,
12 	AMouse,
13 	ACtl,
14 	AExit,
15 	NALT
16 };
17 
18 static Controlset **controlset;
19 int	ncontrolset;
20 int	ctldeletequits;
21 
22 char *alignnames[Nalignments] = {
23 	[Aupperleft] =		"upperleft",
24 	[Auppercenter] =	"uppercenter",
25 	[Aupperright] =		"upperright",
26 	[Acenterleft] =		"centerleft",
27 	[Acenter] =		"center",
28 	[Acenterright] =	"centerright",
29 	[Alowerleft] =		"lowerleft",
30 	[Alowercenter] =	"lowercenter",
31 	[Alowerright] =		"lowerright",
32 };
33 
34 char *ctltypenames[Ntypes] = {
35 	[Ctlunknown] =		"unknown",
36 	[Ctlbox] =			"box",
37 	[Ctlbutton] =		"button",
38 	[Ctlentry] =		"entry",
39 	[Ctlkeyboard] =		"keyboard",
40 	[Ctllabel] =		"label",
41 	[Ctlmenu] =		"menu",
42 	[Ctlradio] =		"radio",
43 	[Ctlscribble] =		"scribble",
44 	[Ctlslider] =		"slider",
45 	[Ctltabs] =			"tabs",
46 	[Ctltext] =			"text",
47 	[Ctltextbutton] =	"textbutton",
48 	[Ctlgroup] =		"group",		// divider between controls and metacontrols
49 	[Ctlboxbox] =		"boxbox",
50 	[Ctlcolumn] =		"column",
51 	[Ctlrow] =			"row",
52 	[Ctlstack] =		"stack",
53 	[Ctltab] =			"tab",
54 };
55 
56 static void	_ctlcmd(Controlset*, char*);
57 static void	_ctlcontrol(Controlset*, char*);
58 
59 static char*
60 _mkctlcmd(Control *c, char *fmt, va_list arg)
61 {
62 	char *name, *p, *both;
63 
64 	name = quotestrdup(c->name);
65 	if(name == nil)
66 		ctlerror("quotestrdup in ctlprint failed");
67 	p = vsmprint(fmt, arg);
68 	if(p == nil){
69 		free(name);
70 		ctlerror("vsmprint1 in ctlprint failed");
71 	}
72 	both = ctlmalloc(strlen(name)+strlen(p)+2);
73 	strcpy(both, name);
74 	strcat(both, " ");
75 	strcat(both, p);
76 	free(name);
77 	free(p);
78 	return both;
79 }
80 
81 int
82 ctlprint(Control *c, char *fmt, ...)
83 {
84 	int n;
85 	char *p;
86 	va_list arg;
87 
88 	va_start(arg, fmt);
89 	p = _mkctlcmd(c, fmt, arg);
90 	va_end(arg);
91 	n = sendp(c->controlset->ctl, p);
92 	yield();
93 	return n;
94 }
95 
96 void
97 _ctlprint(Control *c, char *fmt, ...)
98 {
99 	char *p;
100 	va_list arg;
101 
102 	va_start(arg, fmt);
103 	p = _mkctlcmd(c, fmt, arg);
104 	va_end(arg);
105 	_ctlcmd(c->controlset, p);
106 	free(p);
107 }
108 
109 int
110 _ctllookup(char *s, char *tab[], int ntab)
111 {
112 	int i;
113 
114 	for(i=0; i<ntab && tab[i] != nil; i++)
115 		if(strcmp(s, tab[i]) == 0)
116 			return i;
117 	return -1;
118 }
119 
120 static Control*
121 _newcontrol(Controlset *cs, uint n, char *name, char *type)
122 {
123 	Control *c;
124 
125 	for(c=cs->controls; c; c=c->next)
126 		if(strcmp(c->name, name) == 0){
127 			werrstr("control %q already defined", name);
128 			return nil;
129 		}
130 	c = ctlmalloc(n);
131 	c->screen = cs->screen;
132 	c->name = ctlstrdup(name);
133 	c->type = _ctllookup(type, ctltypenames, Ntypes);
134 	if (c->type < 0)
135 		ctlerror("unknown type: %s", type);
136 	c->event = chancreate(sizeof(char*), 64);
137 	c->data = chancreate(sizeof(char*), 0);
138 	c->size = Rect(1, 1, _Ctlmaxsize, _Ctlmaxsize);
139 	c->hidden = 0;
140 	c->ctl = nil;
141 	c->mouse = nil;
142 	c->key = nil;
143 	c->exit = nil;
144 	c->setsize = nil;
145 
146 	c->controlset = cs;
147 	c->next = cs->controls;
148 	cs->controls = c;
149 	return c;
150 }
151 
152 static void
153 controlsetthread(void *v)
154 {
155 	Controlset *cs;
156 	Mouse mouse;
157 	Control *f;
158 	int prevbut, n, i;
159 	Alt alts[NALT+1];
160 	char tmp[64], *str;
161 	Rune buf[2][20], *rp;
162 
163 	cs = v;
164 	snprint(tmp, sizeof tmp, "controlsetthread 0x%p", cs);
165 	threadsetname(tmp);
166 
167 	alts[AKey].c = cs->kbdc;
168 	alts[AKey].v = &rp;
169 	alts[AKey].op = CHANRCV;
170 	alts[AMouse].c = cs->mousec;
171 	alts[AMouse].v = &mouse;
172 	alts[AMouse].op = CHANRCV;
173 	alts[ACtl].c = cs->ctl;
174 	alts[ACtl].v = &str;
175 	alts[ACtl].op = CHANRCV;
176 	alts[AExit].c = cs->csexitc;
177 	alts[AExit].v = nil;
178 	alts[AExit].op = CHANRCV;
179 	alts[NALT].op = CHANEND;
180 
181 	cs->focus = nil;
182 	prevbut=0;
183 	n = 0;
184 	for(;;){
185 		/* toggle so we can receive in one buffer while client processes the other */
186 		alts[AKey].v = buf[n];
187 		rp = buf[n];
188 		n = 1-n;
189 		switch(alt(alts)){
190 		case AKey:
191 			if(ctldeletequits && rp[0]=='\177')
192 				ctlerror("delete");
193 			for(i=1; i<nelem(buf[0])-1; i++)
194 				if(nbrecv(cs->kbdc, rp+i) <= 0)
195 					break;
196 			rp[i] = L'\0';
197 			if(cs->focus && cs->focus->key)
198 				cs->focus->key(cs->focus, rp);
199 			break;
200 		case AMouse:
201 			/* is this a focus change? */
202 			if(prevbut)	/* don't change focus if button was down */
203 				goto Send;
204 			if(cs->focus!=nil && cs->focus->hidden == 0 && ptinrect(mouse.xy, cs->focus->rect))
205 				goto Send;
206 			if(cs->clicktotype == 0)
207 				goto Change;
208 			/* click to type: only change if button is down */
209 			if(mouse.buttons == 0)
210 				goto Send;
211 		Change:
212 			/* change of focus */
213 			if(cs->focus != nil)
214 				_ctlprint(cs->focus, "focus 0");
215 			cs->focus = nil;
216 			for(f=cs->actives; f!=nil; f=f->nextactive)
217 				if(f->hidden == 0 && f->mouse && ptinrect(mouse.xy, f->rect)){
218 					cs->focus = f;
219 					_ctlprint(f, "focus 1");
220 					if (f->mouse)
221 						f->mouse(f, &mouse);
222 					break;
223 				}
224 		Send:
225 			if(cs->focus && cs->focus->mouse)
226 				cs->focus->mouse(cs->focus, &mouse);
227 			prevbut=mouse.buttons;
228 			break;
229 		case ACtl:
230 			_ctlcontrol(cs, str);
231 			free(str);
232 			break;
233 		case AExit:
234 			threadexits(nil);
235 		}
236 	}
237 }
238 
239 Control*
240 _createctl(Controlset *cs, char *type, uint size, char *name)
241 {
242 	Control *c;
243 
244 	c = _newcontrol(cs, size, name, type);
245 	if(c == nil)
246 		ctlerror("can't create %s control %q: %r", type, name);
247 	return c;
248 }
249 
250 void
251 closecontrol(Control *c)
252 {
253 	Control *prev, *p;
254 
255 	if(c == nil)
256 		return;
257 	if (c == c->controlset->focus)
258 		c->controlset->focus = nil;
259 	if(c->exit)
260 		c->exit(c);
261 
262 	prev = nil;
263 	for(p=c->controlset->controls; p; p=p->next){
264 		if(p == c)
265 			break;
266 		prev = p;
267 	}
268 	if(p == nil)
269 		ctlerror("closecontrol: no such control %q %p\n", c->name, c);
270 	if(prev == nil)
271 		c->controlset->controls = c->next;
272 	else
273 		prev->next = c->next;
274 
275 	/* is it active? if so, delete from active list */
276 	prev = nil;
277 	for(p=c->controlset->actives; p; p=p->nextactive){
278 		if(p == c)
279 			break;
280 		prev = p;
281 	}
282 	if(p != nil){
283 		if(prev == nil)
284 			c->controlset->actives = c->nextactive;
285 		else
286 			prev->nextactive = c->nextactive;
287 	}
288 
289 	if(!c->wevent)
290 		chanfree(c->event);
291 	if(!c->wdata)
292 		chanfree(c->data);
293 	free(c->name);
294 	free(c->format);
295 	free(c);
296 }
297 
298 Control*
299 controlcalled(char *name)
300 {
301 	Control *c;
302 	int i;
303 
304 	for(i=0; i<ncontrolset; i++)
305 		for(c=controlset[i]->controls; c; c=c->next)
306 			if(strcmp(c->name, name) == 0)
307 				return c;
308 	return nil;
309 }
310 
311 void
312 ctlerror(char *fmt, ...)
313 {
314 	va_list arg;
315 	char buf[256];
316 
317 	va_start(arg, fmt);
318 	vfprint(2, fmt, arg);
319 	va_end(arg);
320 	write(2, "\n", 1);
321 	threadexitsall(buf);
322 }
323 
324 Rune*
325 _ctlrunestr(char *s)
326 {
327 	Rune *r, *ret;
328 
329 	ret = r = ctlmalloc((utflen(s)+1)*sizeof(Rune));
330 	while(*s != '\0')
331 		s += chartorune(r++, s);
332 	*r = L'\0';
333 	return ret;
334 }
335 
336 char*
337 _ctlstrrune(Rune *r)
338 {
339 	char *s;
340 	s = ctlmalloc(runestrlen(r)*UTFmax+1);
341 	sprint(s, "%S", r);
342 	return s;
343 }
344 
345 void*
346 ctlmalloc(uint n)
347 {
348 	void *p;
349 
350 	p = mallocz(n, 1);
351 	if(p == nil)
352 		ctlerror("control allocation failed: %r");
353 	return p;
354 }
355 
356 void*
357 ctlrealloc(void *p, uint n)
358 {
359 	p = realloc(p, n);
360 	if(p == nil)
361 		ctlerror("control reallocation failed: %r");
362 	return p;
363 }
364 
365 char*
366 ctlstrdup(char *s)
367 {
368 	char *t;
369 
370 	t = strdup(s);
371 	if(t == nil)
372 		ctlerror("control strdup(%q) failed: %r", s);
373 	return t;
374 }
375 
376 static void
377 ctokenize(char *s, CParse *cp)
378 {
379 	snprint(cp->str, sizeof cp->str, "%s", s);
380 	cp->args = cp->pargs;
381 	cp->nargs = tokenize(s, cp->args, nelem(cp->pargs));
382 }
383 
384 static int
385 ctlparse(CParse *cp, char *s, int hasreceiver)
386 {
387 	int i;
388 	char *t;
389 
390 	/* keep original string for good error messages */
391 	strncpy(cp->str, s, sizeof cp->str);
392 	cp->str[sizeof cp->str - 1] = '\0';
393 	ctokenize(s, cp);
394 	if(cp->nargs == 0)
395 		return -1;
396 	/* strip leading sender name if present */
397 	cp->sender = nil;
398 	i = strlen(cp->args[0])-1;
399 	if(cp->args[0][i] == ':'){
400 		cp->sender = cp->args[0];
401 		cp->sender[i] = '\0';
402 		cp->args++;
403 		cp->nargs--;
404 	}
405 	if(hasreceiver){
406 		if(cp->nargs-- == 0)
407 			return -1;
408 		cp->receiver = *cp->args++;
409 	}else
410 		cp->receiver = nil;
411 	for(i=0; i<cp->nargs; i++){
412 		t = cp->args[i];
413 		while(*t == '[')	/* %R gives [0 0] [1 1]; atoi will stop at closing ] */
414 			t++;
415 		cp->iargs[i] = atoi(t);
416 	}
417 	return cp->nargs;
418 }
419 
420 void
421 _ctlargcount(Control *c, CParse *cp, int n)
422 {
423 	if(cp->nargs != n)
424 		ctlerror("%q: wrong argument count in '%s'", c->name, cp->str);
425 }
426 
427 static void
428 _ctlcmd(Controlset *cs, char*s)
429 {
430 	CParse cp;
431 	char	*rcvrs[32];
432 	int	ircvrs[32], n, i, hit;
433 	Control *c;
434 
435 //	fprint(2, "_ctlcmd: %s\n", s);
436 	cp.args = cp.pargs;
437 	if (ctlparse(&cp, s, 1) < 0)
438 		ctlerror("bad command string: %q", cp.str);
439 	if (cp.nargs == 0 && strcmp(cp.receiver, "sync") == 0){
440 		chanprint(cs->data, "sync");
441 		return;
442 	}
443 	if (cp.nargs == 0)
444 		ctlerror("no command in command string: %q", cp.str);
445 
446 	n = tokenize(cp.receiver, rcvrs, nelem(rcvrs));
447 
448 	// lookup type names: a receiver can be a named type or a named control
449 	for (i = 0; i < n; i++)
450 		ircvrs[i] = _ctllookup(rcvrs[i], ctltypenames, Ntypes);
451 
452 	for(c = cs->controls; c != nil; c = c->next){
453 		/* if a control matches on more than one receiver element,
454 		 * make sure it gets processed once; hence loop through controls
455 		 * in the outer loop
456 		 */
457 		hit = 0;
458 		for (i = 0; i < n; i++)
459 			if(strcmp(c->name, rcvrs[i]) == 0 || c->type == ircvrs[i])
460 				hit++;
461 		if (hit && c->ctl)
462 			c->ctl(c, &cp);
463 	}
464 }
465 
466 static void
467 _ctlcontrol(Controlset *cs, char *s)
468 {
469 	char *lines[16];
470 	int i, n;
471 	char *l;
472 
473 //	fprint(2, "_ctlcontrol: %s\n", s);
474 	n = gettokens(s, lines, nelem(lines), "\n");
475 	for(i=0; i<n; i++){
476 		l = lines[i];
477 		while(*l==' ' || *l=='\t')
478 			l++;
479 		if(*l != '\0')
480 			_ctlcmd(cs, l);
481 	}
482 }
483 
484 Rune*
485 _ctlgetsnarf(void)
486 {
487 	int i, n;
488 	char *sn, buf[512];
489 	Rune *snarf;
490 
491 	if(_ctlsnarffd < 0)
492 		return nil;
493 	sn = nil;
494 	i = 0;
495 	seek(_ctlsnarffd, 0, 0);
496 	while((n = read(_ctlsnarffd, buf, sizeof buf)) > 0){
497 		sn = ctlrealloc(sn, i+n+1);
498 		memmove(sn+i, buf, n);
499 		i += n;
500 		sn[i] = 0;
501 	}
502 	snarf = nil;
503 	if(i > 0){
504 		snarf = _ctlrunestr(sn);
505 		free(sn);
506 	}
507 	return snarf;
508 }
509 
510 void
511 _ctlputsnarf(Rune *snarf)
512 {
513 	int fd, i, n, nsnarf;
514 
515 	if(_ctlsnarffd<0 || snarf[0]==0)
516 		return;
517 	fd = open("/dev/snarf", OWRITE);
518 	if(fd < 0)
519 		return;
520 	nsnarf = runestrlen(snarf);
521 	/* snarf buffer could be huge, so fprint will truncate; do it in blocks */
522 	for(i=0; i<nsnarf; i+=n){
523 		n = nsnarf-i;
524 		if(n >= 256)
525 			n = 256;
526 		if(fprint(fd, "%.*S", n, snarf+i) < 0)
527 			break;
528 	}
529 	close(fd);
530 }
531 
532 int
533 _ctlalignment(char *s)
534 {
535 	int i;
536 
537 	i = _ctllookup(s, alignnames, Nalignments);
538 	if (i < 0)
539 		ctlerror("unknown alignment: %s", s);
540 	return i;
541 }
542 
543 Point
544 _ctlalignpoint(Rectangle r, int dx, int dy, int align)
545 {
546 	Point p;
547 
548 	p = r.min;	/* in case of trouble */
549 	switch(align%3){
550 	case 0:	/* left */
551 		p.x = r.min.x;
552 		break;
553 	case 1:	/* center */
554 		p.x = r.min.x+(Dx(r)-dx)/2;
555 		break;
556 	case 2:	/* right */
557 		p.x = r.max.x-dx;
558 		break;
559 	}
560 	switch((align/3)%3){
561 	case 0:	/* top */
562 		p.y = r.min.y;
563 		break;
564 	case 1:	/* center */
565 		p.y = r.min.y+(Dy(r)-dy)/2;
566 		break;
567 	case 2:	/* bottom */
568 		p.y = r.max.y - dy;
569 		break;
570 	}
571 	return p;
572 }
573 
574 void
575 controlwire(Control *cfrom, char *name, Channel *chan)
576 {
577 	Channel **p;
578 
579 	p = nil;
580 	if(strcmp(name, "event") == 0){
581 		p = &cfrom->event;
582 		cfrom->wevent = 1;
583 	}else if(strcmp(name, "data") == 0){
584 		p = &cfrom->data;
585 		cfrom->wdata = 1;
586 	}else
587 		ctlerror("%q: unknown controlwire channel %s", cfrom->name, name);
588 	chanfree(*p);
589 	*p = chan;
590 }
591 
592 void
593 _ctlfocus(Control *me, int set)
594 {
595 	Controlset *cs;
596 
597 	cs = me->controlset;
598 	if(set){
599 		if(cs->focus == me)
600 			return;
601 		if(cs->focus != nil)
602 			_ctlprint(cs->focus, "focus 0");
603 		cs->focus = me;
604 	}else{
605 		if(cs->focus != me)
606 			return;
607 		cs->focus = nil;
608 	}
609 }
610 
611 static void
612 resizethread(void *v)
613 {
614 	Controlset *cs;
615 	char buf[64];
616 	Alt alts[3];
617 
618 	cs = v;
619 	snprint(buf, sizeof buf, "resizethread0x%p", cs);
620 	threadsetname(buf);
621 
622 	alts[0].c = cs->resizec;
623 	alts[0].v = nil;
624 	alts[0].op = CHANRCV;
625 	alts[1].c = cs->resizeexitc;
626 	alts[1].v = nil;
627 	alts[1].op = CHANRCV;
628 	alts[2].op = CHANEND;
629 
630 	for(;;){
631 		switch(alt(alts)){
632 		case 0:
633 			resizecontrolset(cs);
634 			break;
635 		case 1:
636 			return;
637 		}
638 	}
639 }
640 
641 void
642 activate(Control *a)
643 {
644 	Control *c;
645 
646 	for(c=a->controlset->actives; c; c=c->nextactive)
647 		if(c == a)
648 			ctlerror("%q already active\n", a->name);
649 
650 	if (a->activate){
651 		a->activate(a, 1);
652 		return;
653 	}
654 	/* prepend */
655 	a->nextactive = a->controlset->actives;
656 	a->controlset->actives = a;
657 }
658 
659 void
660 deactivate(Control *a)
661 {
662 	Control *c, *prev;
663 
664 	/* if group, first deactivate kids, then self */
665 	if (a->activate){
666 		a->activate(a, 0);
667 		return;
668 	}
669 	prev = nil;
670 	for(c=a->controlset->actives; c; c=c->nextactive){
671 		if(c == a){
672 			if(a->controlset->focus == a)
673 				a->controlset->focus = nil;
674 			if(prev != nil)
675 				prev->nextactive = a->nextactive;
676 			else
677 				a->controlset->actives = a->nextactive;
678 			return;
679 		}
680 		prev = c;
681 	}
682 	ctlerror("%q not active\n", a->name);
683 }
684 
685 static struct
686 {
687 	char	*name;
688 	ulong	color;
689 }coltab[] = {
690 	"red",			DRed,
691 	"green",			DGreen,
692 	"blue",			DBlue,
693 	"cyan",			DCyan,
694 	"magenta",		DMagenta,
695 	"yellow",			DYellow,
696 	"paleyellow",		DPaleyellow,
697 	"darkyellow",		DDarkyellow,
698 	"darkgreen",		DDarkgreen,
699 	"palegreen",		DPalegreen,
700 	"medgreen",		DMedgreen,
701 	"darkblue",		DDarkblue,
702 	"palebluegreen",	DPalebluegreen,
703 	"paleblue",		DPaleblue,
704 	"bluegreen",		DBluegreen,
705 	"greygreen",		DGreygreen,
706 	"palegreygreen",	DPalegreygreen,
707 	"yellowgreen",		DYellowgreen,
708 	"medblue",		DMedblue,
709 	"greyblue",		DGreyblue,
710 	"palegreyblue",		DPalegreyblue,
711 	"purpleblue",		DPurpleblue,
712 	nil,	0
713 };
714 
715 void
716 initcontrols(void)
717 {
718 	int i;
719 	Image *im;
720 
721 	quotefmtinstall();
722 	namectlimage(display->opaque, "opaque");
723 	namectlimage(display->transparent, "transparent");
724 	namectlimage(display->white, "white");
725 	namectlimage(display->black, "black");
726 	for(i=0; coltab[i].name!=nil; i++){
727 		im = allocimage(display, Rect(0,0,1,1), RGB24, 1, coltab[i].color);
728 		namectlimage(im, coltab[i].name);
729 	}
730 	namectlfont(font, "font");
731 	_ctlsnarffd = open("/dev/snarf", OREAD);
732 }
733 
734 Controlset*
735 newcontrolset(Image *im, Channel *kbdc, Channel *mousec, Channel *resizec)
736 {
737 	Controlset *cs;
738 
739 	if(im == nil)
740 		im = screen;
741 	if((mousec==nil && resizec!=nil) || (mousec!=nil && resizec==nil))
742 		ctlerror("must specify either or both of mouse and resize channels");
743 
744 	cs = ctlmalloc(sizeof(Controlset));
745 	cs->screen = im;
746 
747 	if(kbdc == nil){
748 		cs->keyboardctl = initkeyboard(nil);
749 		if(cs->keyboardctl == nil)
750 			ctlerror("can't initialize keyboard: %r");
751 		kbdc = cs->keyboardctl->c;
752 	}
753 	cs ->kbdc = kbdc;
754 
755 	if(mousec == nil){
756 		cs->mousectl = initmouse(nil, im);
757 		if(cs->mousectl == nil)
758 			ctlerror("can't initialize mouse: %r");
759 		mousec = cs->mousectl->c;
760 		resizec = cs->mousectl->resizec;
761 	}
762 	cs->mousec = mousec;
763 	cs->resizec = resizec;
764 	cs->ctl = chancreate(sizeof(char*), 64);	/* buffer to prevent deadlock */
765 	cs->data = chancreate(sizeof(char*), 0);
766 	cs->resizeexitc = chancreate(sizeof(int), 0);
767 	cs->csexitc = chancreate(sizeof(int), 0);
768 
769 	threadcreate(resizethread, cs, 32*1024);
770 	threadcreate(controlsetthread, cs, 32*1024);
771 
772 	controlset = ctlrealloc(controlset, (ncontrolset+1)*sizeof(Controlset*));
773 	controlset[ncontrolset++] = cs;
774 	return cs;
775 }
776 
777 void
778 closecontrolset(Controlset *cs)
779 {
780 	int i;
781 
782 	sendul(cs->resizeexitc, 0);
783 	chanfree(cs->resizeexitc);
784 	sendul(cs->csexitc, 0);
785 	chanfree(cs->csexitc);
786 	chanfree(cs->ctl);
787 	chanfree(cs->data);
788 
789 	for(i=0; i<ncontrolset; i++)
790 		if(cs == controlset[i]){
791 			memmove(controlset+i, controlset+i+1, (ncontrolset-(i+1))*sizeof(Controlset*));
792 			ncontrolset--;
793 			goto Found;
794 		}
795 
796 	if(i == ncontrolset)
797 		ctlerror("closecontrolset: control set not found");
798 
799     Found:
800 	while(cs->controls != nil)
801 		closecontrol(cs->controls);
802 }
803