CONTROL(2) CONTROL(2)
NAME
Control, Controlset, activate, closecontrol,
closecontrolset, controlcalled, controlwire, createbox,
createbutton, createentry, createkeyboard, createlabel,
createmenu, createradiobutton, createscribble, createslider,
createtext, createtextbutton, ctlerror, ctlmalloc,
ctlrealloc, ctlstrdup, deactivate, freectlfont,
freectlimage, initcontrols, namectlfont, namectlimage,
newcontrolset, printctl, resizecontrolset - interactive
graphical controls
SYNOPSIS
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <keyboard.h>
#include <mouse.h>
#include <control.h>
typedef struct Control Control;
typedef struct Controlset Controlset;
struct Control
{
char *name;
Rectangle rect; /* area on screen */
Channel *event; /* chan(char*) to client */
Channel *ctl; /* chan(char*) from client */
Channel *data; /* chan(char*) to client */
...
};
struct Controlset
{
...
int clicktotype;
...
};
void initcontrols(void)
Controlset* newcontrolset(Image *i,
Channel *kc, Channel *mc, Channel *rc)
void closecontrolset(Controlset *cs)
int namectlfont(Font *font, char *name)
int freectlfont(char *name)
Page 1 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
int namectlimage(Image *image, char *name)
int freectlimage(char *name)
Control* createbox(Controlset *cs, char *name)
Control* createbutton(Controlset *cs, char *name)
Control* createentry(Controlset *cs, char *name)
Control* createkeyboard(Controlset *cs, char *name)
Control* createlabel(Controlset *cs, char *name)
Control* createmenu(Controlset *cs, char *name)
Control* createradiobutton(Controlset *cs, char *name)
Control* createscribble(Controlset *cs, char *name)
Control* createslider(Controlset *cs, char *name)
Control* createtext(Controlset *cs, char *name)
Control* createtextbutton(Controlset *cs, char *name)
void closecontrol(Control *c)
int printctl(Channel *c, char *fmt, ...)
void ctlerror(char *fmt, ...)
Control* controlcalled(char *name)
void controlwire(Control *c, char *cname, Channel *ch)
void activate(Control *c)
void deactivate(Control *c)
void resizecontrolset(Controlset *cs)
void* ctlmalloc(uint n)
void* ctlrealloc(void *p, uint n)
char* ctlstrdup(char *s)
int ctldeletequits
DESCRIPTION
This library provides a preliminary implementation of a set
Page 2 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
of interactive controls for graphical displays: buttons,
sliders, text entry boxes, and so on. Each control runs as
a separate thread in the program, which must therefore be
linked with the thread library, thread(2). The controls are
manipulated by reading and writing to its channels, as
defined in thread(2). Each control has three channels: ctl
accepts messages to configure and manage the control, while
event delivers messages about actions within the control
(such as a button press) and data delivers (if requested by
an appropriate write to ctl) control-specific data such as
the contents of a field.
The library has no provision for automatic layout; the geom-
etry of controls must be specified explicitly.
Message format
All messages are represented as UTF-8 text. Numbers are
formatted in decimal, and strings are transmitted in the
quoted form of quote(2).
Messages sent to a control are of the form,
verb [argument ... ]
For example, the initial field of a text entry control could
be set by sending the message,
value 'Hello, world!'
to its ctl file. This message contains the verb value and
the single argument Hello, world!.
Messages sent by a control on its event channel are of the
form,
sender: verb [argument ... ]
The sender is the name of the control sending the message;
the verb and arguments are control- and message-dependent.
For example, when the user types a newline at a text entry
control named entry, it sends on its event channel the mes-
sage
entry: value 'Hello again!'
To make it easy to write messages, the function printctl
formats its arguments in the manner of print(2) and sends
the result to the channel c. The %q and %Q formats are con-
venient for properly quoting string arguments, as in
printctl(e->event, "value %q", "Don't touch!");
Page 3 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
It is wise to use %q always instead of %s when sending mes-
sages, and avoid dealing with the quoting explicitly. In
the other direction, tokenize (see getfields(2)) parses
these messages and interprets the quotes correctly.
Initialization and Control sets
After initdraw (see graphics(2)) is called, the function
initcontrols should be called to initialize the library. It
calls quotefmtinstall to install the %q and %Q formats; see
quote(2).
Each control is represented by a Control data structure and
is associated with a Controlset that groups a set of con-
trols sharing mouse, keyboard, and display. Most applica-
tions will need only one Controlset; only those with multi-
ple windows or unusual configurations will need more than
one. The function newcontrolset creates a Controlset. Its
arguments are the image (usually a window) on which its con-
trols will appear, typically the screen variable in the draw
library, and three channels: kc, a channel of Runes from the
keyboard; mc, a channel of Mouse structures from the mouse;
and rc, a channel of int that indicates when the window has
been resized. Any of the channels may be nil, in which case
newcontrolset will call initkeyboard and/or initmouse (see
keyboard(2) and mouse(2)) to initialize the keyboard and
mouse and connect them to the control set. The mouse and
resize channels must both be nil or both be non-nil.
The function closecontrolset frees all the controls in the
control set and tears down all the associated threads. It
does not close the mouse and keyboard.
The only public element of a Controlset is the flag
clicktotype, which is zero by default. If it is set to
non-zero, the controls in the set will acquire `focus' by
the click-to-type paradigm. Otherwise, focus is always
given to the control under the mouse.
The function resizecontrolset must be provided by the user.
When the associated window is resized, the library will call
resizecontrolset with the affected Controlset; the function
should reconnect to and redraw the window.
Fonts and images
Fonts and images must be given names so they may be refer-
enced in messages. The functions namectlfont and
namectlimage associate a (unique) name with the specified
font or image. The association is removed by freectlfont
and freectlimage. The font or image is not freed by these
functions, however.
The function initcontrols establishes name bindings for all
Page 4 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
the colors mentioned in <draw.h>, such as black, white, red,
yellow, etc., as well as masks transparent and opaque. It
also sets the name font to refer to the default font vari-
able set up by initdraw.
Creation
Each type of control has an associated creation function:
createbutton, createentry, etc., whose arguments are the
Controlset to attach it to and a globally unique name for
it. A control may be destroyed by calling closecontrol.
The function controlcalled returns a pointer to the Control
with the given name, or nil if no such control exists.
Configuration
After a control is created, it must be configured using the
control-specific commands documented below. Commands are
sent to the ctl channel of the control. Multiple commands
may be sent in a single message; newline characters separate
commands. For an example, see the implementation of
resizecontrolset in the EXAMPLES section. Note that newline
is a separator, not a terminator; the final command does not
need a newline.
The recipient of a message to its ctl file ignores the ini-
tial sender: field of the message, if present, making it
possible to send messages generated on an event channel
directly to another control's ctl channel.
Activation
When they are created, controls are disabled: they do not
respond to user input. Not all controls need to be respon-
sive; for example, labels are static and a text display
might show a log of messages but not be useful to edit. But
buttons, entry boxes, and other text displays should be
active.
To enable a control, call the activate function, which spec-
ifies that the Control c should respond to mouse and key-
board events; deactivate turns it off again.
The function controlwire permits rearrangement of the chan-
nels associated with a Control. The channel cname (one of
"ctl", "data", or "event") of Control c is reassigned to the
channel ch. There are several uses for this operation: one
may reassign all the event channels to a single channel, in
effect multiplexing all the events onto a single channel; or
connect the event channel of a slider to the ctl channel of
a text display (after setting the format for the slider's
messages to the appropriate syntax) to let the slider act as
a scroll bar for the text without rerouting the messages
explicitly.
Page 5 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
Controls
The following sections document the individual controls in
alphabetical order. The layout of each section is a brief
description of the control's behavior, followed by the mes-
sages it sends on event, followed by the messages it accepts
on ctl. The event messages are triggered only by mouse or
keyboard action; messages to the ctl file do not cause
events to be generated.
All controls accept the following messages:
rect minx miny maxx maxy
Set the bounding rectangle for the control on the dis-
play. The syntax generated by the %R print format of
the draw library is also acceptable for the coordi-
nates.
show Display the control on its screen. Some actions will
also cause the controls to show themselves automati-
cally. The details of how redisplay is handled in gen-
eral are likely to change.
Many messages are common between multiple controls. Such
messages are described in detail here to avoid repetition.
In the individual descriptions, only the syntax is pre-
sented.
align n
Specify the alignment of (some part of) the control's
display within its rectangle. For textual controls,
the alignment specifies where the text should appear.
For multiline text, the alignment refers to each line
within its box, and only the horizontal part is hon-
ored. For other controls, the alignment affects the
appearance of the display in a reasonable way. The
valid alignments are words with obvious interpreta-
tions: upperleft, uppercenter, upperright, centerleft,
center, centerright, lowerleft, lowercenter, and
lowerright.
border n
Inset the control within its rectangle by n pixels,
default zero.
bordercolor name
Paint the border of the control with the named color,
default black.
focus n
The control now has (if n is non-zero) or does not have
( if n is zero) focus. Most controls ignore the mes-
sage; there are plans to make them react.
Page 6 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
format fmt
Set the format of `value' messages sent on the event
channel. By default, the format is "%q: value %q" for
string-valued controls, "%q: value %d" for integer-
valued controls such as buttons, and "%q: value 0x%x"
for the keyboard and scribble controls. The %q prints
the name of the control; the rest the value. Any sup-
plied format string must be type-equivalent to the
default for that control.
image name
light name
mask name
Many controls set a background image or color for dis-
play. The image message sets the image. The mask and
light images together specify how the control shows it
is enabled: the light is printed through the mask when
the state is `on' or `pressed'. Otherwise, the image
appears unmodified. The default image is white; mask
opaque; light yellow.
font name
textcolor name
These commands set the font and color for displaying
text. The defaults are the default font set up by the
draw library, and black.
value v
Set the value of the control. Textual images accept an
arbitrary string; others an integral value.
Box
A box is a trivial control that does nothing more than pass
keyboard, mouse, and focus messages back on its event chan-
nel. Keyboard characters are sent in the format
boxname: key 0xnn
where nn is the hexadecimal value of the character. Mouse
messages are sent in the format
boxname: mouse [x y] but msec
where x, y, but, and msec are the various fields of the
Mouse structure. The focus message is just
boxname: focus n
where n is 0 if the box has lost focus, 1 if it has acquired
Page 7 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
it.
The box displays within its rectangle an image, under mask,
with specified alignment. The control messages it accepts
are:
align a
Controls the placement of the image in the rectangle
(unimplemented).
border b
bordercolor name
focus n
image name
rect minx miny maxx maxy
show
Button
A button is a simple control that toggles its state when
mouse button 1 is pressed on its rectangle. Each state
change triggers an event message:
buttonname: value n
The button displays an image (which may of course be a sim-
ple color) and illuminates in the standard way when it is
`on'. The control messages it accepts are:
align a
Controls the placement of the image in the rectangle
(unimplemented).
border b
bordercolor name
focus n
format fmt
image name
light name
mask name
rect minx miny maxx maxy
Page 8 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
show
value n
Set the button to `on' (if n is non-zero) or `off' (if
n is zero).
Entry
The entry control manages a single line of editable text.
When the user hits a carriage return anywhere in the text,
the control generates the event message,
entryname: value s
with s the complete text of the entry box.
The cursor can be moved by clicking button 1; at the moment,
there is no way to select characters, only a typing posi-
tion. Some control characters have special actions:
control-H (backspace) deletes the character before the cur-
sor; control-U clears the line; and control-V pastes the
snarf buffer at the typing position. Most important, car-
riage return sends the text to the event channel.
The control messages the entry control accepts are:
align a
Controls the placement of the text in the rectangle.
border b
bordercolor name
data After receiving this message, the entry will send its
value to its data channel as an unadorned, unquoted
string.
focus n
When it receives focus, the entry box displays a typing
cursor. When it does not have focus, the cursor is not
displayed.
font name
format fmt
image name
rect minx miny maxx maxy
show
textcolor name
Page 9 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
value s
Set the string displayed in the entry box.
Keyboard
The keyboard control implements a simulated keyboard useful
on palmtop devices. Keystrokes, generated by mouse button 1
on the simulated keys, are sent as event messages:
keyboardname: value 0xnn
where nn is the hexadecimal Unicode value of the character.
Shift, control, and caps lock are handled by the keyboard
control itself; shift and control affect only the next regu-
lar keystroke. The Alt key is unimplemented; it will become
equivalent to the standard Plan 9 key for synthesizing non-
ASCII characters.
There are two special keys, Scrib and Menu, which return
values 0x10000 and 0x10001.
The image, mask, light rules are used to indicate that a key
is pressed, but to aid clumsy fingers the keystroke is not
generated until the key is released, so it is possible to
slide the pointer to a different key to correct for bad aim.
The control messages the keyboard accepts are:
border b
bordercolor name
focus n
font name1 name2
Sets the font for the keys. If only one font is named,
it is used for all keys. If two are named, the second
is used for key caps with special names such as Shift
and Enter. (Good choices on the Bitsy are
/lib/font/bit/lucidasans/boldlatin1.6.font for the
first and /lib/font/bit/lucidasans/unicode.6.font for
the second argument.) If neither is specified, both
will be set to the default global font.
format fmt
image name
light name
mask name
rect minx miny maxx maxy
Page 10 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
show
Label
A label is like a textbutton (q.v.) that does not react,
but whose value is the text it displays. The control mes-
sages it accepts are:
align a
Controls the placement of the image in the rectangle.
border b
bordercolor name
focus n
font name
image name
rect minx miny maxx maxy
show
textcolor name
value s
The value is a string that can be modified only by
sending this message to the ctl file.
Menu
A menu is a pop-up window containing a set of textual selec-
tions. When a selection is made, it removes itself from the
screen and reports the selection by value:
menuname: value n
If no selection is made, no message is reported. Because it
creates a window, programs using a menu must have their
screen variable (see graphics(2) and window(2)) set up to be
refreshed properly. The easiest way to do this is to call
getwindow with refresh argument Refbackup (see graphics(2));
most programs use Refnone.
The control messages accepted by a menu are:
add text
Add a line of text to the end of the menu.
align a
Controls the left-right placement of the text in its
rectangle.
Page 11 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
border b
bordercolor name
focus n
font name
format fmt
image name
rect minx miny maxx maxy
the appropriate size.
Only the origin of the rectangle is significant; menus calculate
selectcolor name
Set the color in which to highlight selected lines;
default yellow.
selecttextcolor name
Set the color in which to draw the text in selected
lines; default black.
show Display the menu. Not usually needed unless the menu is
changed while visible; use window instead.
window
window n
With no arguments, toggle the menu's visibility; other-
wise make it visible (1) or invisible (0). When the
selection is made, the menu will remove its window
automatically.
Radiobutton
The radiobutton assembles a group of buttons or textbuttons
into a single control with a numeric value. Its value is -1
if none of the constituent buttons is pressed; otherwise it
is the index, starting at zero, of the button that is
pressed. Only one button may be pressed; the radiobutton
manipulates its buttons to guarantee this. State changes
trigger an event message:
radiobuttonname: value n
Buttons are added to the radio button using the add message;
there is no way to remove them, although they may be turned
off independently using deactivate. The index reported in
the value is defined by the order in which the buttons are
added. The constituent buttons should be configured and
Page 12 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
layed out in the usual way; the rectangle of the radiobutton
is used only to `catch' mouse events and should almost
always correspond to the bounding box of the constituent
buttons. In other words, the geometry is not maintained
automatically.
The control messages the radiobutton accepts are:
add name
Add the control with the specified name to the
radiobutton.
focus n
format fmt
rect minx miny maxx maxy
show
value n
Scribble
The scribble control provides a region in which strokes
drawn with mouse button 1 are interpreted as characters in
the manner of scribble(2). In most respects, including the
format of its event messages, it is equivalent to a keyboard
control.
The control messages it accepts are:
align a
Controls the placement of the image in the rectangle
(unimplemented).
border b
bordercolor name
focus n
font name
Used to display the indicia.
image name
linecolor name
The color in which to draw the strokes; default black.
rect minx miny maxx maxy
show
Page 13 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
Slider
A slider controls an integer value by dragging the mouse
with a button. Configured appropriately, it can serve as a
scroll bar with the standard Plan 9 behavior. When the
value changes, an event message is sent:
slidername: value n
The slider is a good candidate for connecting to another
control by setting its format and rewiring its event channel
to the other's ctl channel.
The geometry of the slider is defined by three numbers: max
is a number representing the range of the slider; vis is a
number representing how much of what is being controlled is
visible; and value is a number representing the value of the
slider within its range. For example, if the slider is man-
aging a textual display of 1000 lines, with 18 visible, and
the first visible line (numbered starting form 0) is 304,
max will be 1000, vis will be 18, and value will be 304.
The indicator is the visual representation of the vis por-
tion of the controlled object.
The control messages the slider accepts are:
absolute n
If n is zero, the slider behaves like a Plan 9 scroll
bar: button 2 sets absolute position, button 1
decreases the value, and button 3 increases it. If n
is non-zero, all buttons behave like button 2, setting
the absolute value.
border b
bordercolor name
clamp end n
The end is either the word high or low; n sets whether
that end is clamped or not. If it is clamped, that end
of the indicator is always at its supremum. A standard
scroll bar has neither end clamped; a volume slider
would have its low end clamped. If the low end is
clamped, the value of the slider is represented by the
high end of the indicator; otherwise it is represented
by the low end.
focus n
format fmt
image name
Page 14 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
indicatorcolor name
Set the color in which to draw the indicator; default
black.
max n
Set the maximum value of the range covered by the
slider.
orient dir
The string dir begins either hor or ver to specify the
orientation of the slider. The default is vertical.
The value always increases to the right for horizontal
sliders and downwards for vertical sliders.
rect minx miny maxx maxy
show
value n
vis n
Set the visible area shown by the indicator.
Text
A text control presents a set of lines of text. The text
cannot be edited with the keyboard, but can be changed by
control messages. (A more interactive text control will be
created eventually.) The mouse can be used to select lines
of text. The only event message reports a state change in
the selection of a line:
textname: select n s
states that line n has changed its selection state to s,
either 0 (unselected) or 1 (selected).
The control messages the text control accepts are:
accumulate s
accumulate n s
add s
add n s
With one argument, append the string s as a new last
line of the control; if n is specified, add the line
before the current line n, making the new line number
n. The lines are zero indexed and n can be no greater
than the current number of lines. Add refreshes the
display, but accumulate does not, to avoid n-squared
behavior when assembling a piece of text.
Page 15 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
align a
Controls the placement of each line of text left-to-
right in its rectangle. Vertically, lines are tightly
packed with separation set by the font's interline
spacing.
border b
bordercolor name
clear
Delete all text.
delete n
Delete line n.
focus n
font name
image name
rect minx miny maxx maxy
replace n s
Replace line n by the string s.
scroll n
If n is non-zero, the text will automatically scroll so
the last line is always visible when new text is added.
select n m
Set the selection state of line n to m.
selectcolor name
Set the color in which to highlight selected lines;
default yellow.
selectmode s
The string s is either single or multi. If single, the
default, only one line may be selected at a time; when
a line is selected, other lines are unselected. If
multi, the selection state of individual lines can be
toggled independently.
textcolor name
topline n
Scroll the text so the top visible line is number n.
show
Page 16 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
value s
Delete all the text in the control and then add the
single line s.
Textbutton
A textbutton is a textual variant of a plain button. Each
state change triggers an event message:
textbuttonname: value n
Like a regular button, the value of a textbutton is an inte-
ger; the text is the string that appears in the button. It
uses the image, light, mask method of indicating its state;
moreover, the color of the text can be set to change when
the button is pressed. The control messages it accepts are:
align a
Controls the placement of the text in the rectangle.
border b
bordercolor name
focus n
font name
format fmt
image name
light name
mask name
pressedtextcolor name
Set the color in which to display text when the
textbutton is pressed.
rect minx miny maxx maxy
show
text s
Set the text displayed in the button.
textcolor name
value n
Set the button to `on' (if n is non-zero) or `off' (if
n is zero).
Page 17 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
Helper functions
The function ctlerror is called when the library encounters
an error. It prints the formatted message and exits the
program.
The functions ctlmalloc, ctlrealloc, ctlstrdup, and
ctlrunestrdup are packagings of the corresponding C library
functions. They call ctlerror if they fail to allocate mem-
ory, and ctlmalloc zeros the memory it returns.
Finally, for debugging, if the global variable
ctldeletequits is set to a non-zero value, typing a DEL will
cause the program to call
ctlerror("delete");
Caveat
This library is very new and is still missing a number of
important features. The details are all subject to change.
Another level of library that handles geometry and has sen-
sible default appearances for the controls would be useful.
One unusual design goal of this library was to make the con-
trols themselves easy to implement. The reader is encour-
aged to create new controls by adapting the source to exist-
ing ones.
EXAMPLES
This example creates two entry boxes, top and bot, and
copies the contents of one to the other whenever a newline
is typed.
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <draw.h>
#include <mouse.h>
#include <keyboard.h>
#include <control.h>
Control *top;
Control *bot;
Controlset *cs;
int ctldeletequits = 1;
void
resizecontrolset(Controlset*)
{
int i;
Rectangle r, r1, r2;
Page 18 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
if(getwindow(display, Refnone) < 0)
ctlerror("resize failed: %r");
r = insetrect(screen->r, 10);
r1 = r;
r2 = r;
r1.max.y = r1.min.y+1+font->height+1;
r2.min.y = r1.max.y+10;
r2.max.y = r2.min.y+1+font->height+1;
printctl(top->ctl, "rect %R\nshow", r1);
printctl(bot->ctl, "rect %R\nshow", r2);
}
void
threadmain(int argc, char *argv[])
{
char *s, *args[3];
Channel *c;
int n;
initdraw(0, 0, "example");
initcontrols();
cs = newcontrolset(screen, nil, nil, nil);
cs->clicktotype = 1;
top = createentry(cs, "top");
printctl(top->ctl, "image paleyellow");
printctl(top->ctl, "border 1");
bot = createentry(cs, "bot");
printctl(bot->ctl, "image paleyellow");
printctl(bot->ctl, "border 1");
c = chancreate(sizeof(char*), 0);
controlwire(top, "event", c);
controlwire(bot, "event", c);
activate(top);
activate(bot);
resizecontrolset(cs);
for(;;){
s = recvp(c);
n = tokenize(s, args, nelem(args));
if(n==3 && strcmp(args[1], "value")==0){
if(strcmp(args[0], "top:") == 0)
printctl(bot->ctl, "value %q", args[2]);
else
printctl(top->ctl, "value %q", args[2]);
}
}
threadexitsall(nil);
}
Page 19 Plan 9 (printed 3/7/26)
CONTROL(2) CONTROL(2)
A richer variant couples a text entry box to a slider.
Since the value of a slider is its numerical setting, as a
decimal number, all that needs changing is the setup of bot:
bot = createslider(cs, "bot");
printctl(bot->ctl, "border 1");
printctl(bot->ctl, "image paleyellow");
printctl(bot->ctl, "indicatorcolor red");
printctl(bot->ctl, "max 100");
printctl(bot->ctl, "clamp low 1");
printctl(bot->ctl, "orient horizontal");
The rest is the same. Of course, the value of the entry box
is only meaningful to the slider if it is also a decimal
number.
Finally, we can avoid processing events altogether by
cross-coupling the controls. Replace the rest of threadmain
with this:
controlwire(top, "event", bot->ctl);
controlwire(bot, "event", top->ctl);
activate(top);
activate(bot);
resizecontrolset(cs);
for(;;)
yield();
threadexitsall(nil);
SOURCE
/sys/src/libcontrol
SEE ALSO
draw(2) frame(2) graphics(2) quote(2) thread(2)
BUGS
The library is strict about matters of formatting, argument
count in messages, etc., and calls ctlerror in situations
where it may be fine to ignore the error and continue.
Page 20 Plan 9 (printed 3/7/26)