Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

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