1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
|
<chapter id="ch-configlet">
<chapterinfo>
<releaseinfo>$Progeny: configlet.xml,v 1.11 2002/01/18 06:19:44 dsp Exp $</releaseinfo>
</chapterinfo>
<title>Writing a configlet</title>
<section>
<title>Files</title>
<para>Configlets consist of several files, all contained in a
common directory. The minimal files included in this directory
are:</para>
<variablelist>
<varlistentry>
<term><filename>main.glade</filename></term>
<listitem>
<para>the Glade XML file that describes the user interface
for the configlet. Creating and editing Glade files is
beyond the scope of this document. Refer to <ulink
url="http://glade.gnome.org/">the Glade web page</ulink>
for more information.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><filename>main.py</filename></term>
<listitem>
<para>the Python code that implements the "meat" of the
configlet's code.</para>
</listitem>
</varlistentry>
</variablelist>
<para>These are the only two files directly referenced by the
infrastructure classes. The developer is free to include new
files or subdirectories in addition to these two. For example,
some configlets may include icons or other graphics.</para>
<para>The Glade file has two requirements:</para>
<itemizedlist>
<listitem>
<para>It must contain widgets that correspond to the
configlet pages that the configlet needs to display. Each
of these widgets must have a name that will be used by the
configlet <ulink url="api/index.html">API</ulink>. Since
these widgets will be inserted into a parent object created
by the front ends, they should not be toplevel objects; they
may be wrapped by a toplevel to allow them to be edited in
Glade, as long as those toplevel objects can be discarded at
runtime. As a special case, single-page configlets may name
their page widget <literal>mainwidget</literal>; this will
enable some automatic handling by the <ulink
url="api/index.html">API</ulink>.</para>
</listitem>
<listitem>
<para>The configlet page widgets must not be set visible by
default; they are set visible as a result of the configlet
module when appropriate.</para>
</listitem>
</itemizedlist>
</section>
<section>
<title>Python code</title>
<para>The Python file contains the code for the configlet. It
must follow these rules:</para>
<itemizedlist>
<listitem>
<para>It must <literal>import configlet</literal>.</para>
</listitem>
<listitem>
<para>It must define a class derived from <ulink
url="api/configlet_Configlet.py.html"><classname>configlet.Configlet</classname></ulink>.
It must provide a <ulink
url="api/configlet_Configlet.py.html#gnome_setup"><function>gnome_setup</function></ulink>
method which does any GUI-specific initialization. For example,
Etherconf's configlet uses <ulink
url="api/configlet_Configlet.py.html#gnome_setup"><function>gnome_setup</function></ulink>
to retrieve the widgets it uses from the wtree, get a list of
network interfaces, and the following bit of code for
automatically connecting signal handlers.</para>
<example>
<title>Etherconf gnome_setup</title>
<programlisting>
<![CDATA[
dict = {}
global Etherconf
for key in dir(Etherconf):
dict[key] = getattr(self, key)
self.wtree.signal_autoconnect(dict)
]]>
</programlisting>
</example>
</listitem>
<listitem>
<para>It must define a dictionary of attributes, which are
used to initialize the configlet. At least the following
attributes must be set:</para>
<itemizedlist>
<listitem>
<para><literal>display_title</literal>: This should be a
short descriptive name for the configlet. Examples of
good names would include "X Configuration" or "Network";
some bad ones would include "Configure the X Server's
Display Settings" (too long) or "etherconf" (too short
and user-hostile).</para>
</listitem>
<listitem>
<para><literal>description</literal>: This should be a
longer description of the configlet.</para>
</listitem>
<listitem>
<para><literal>packages</literal>: This must be a list
of the packages the configlet configures.</para>
</listitem>
</itemizedlist>
<para>Additionally, the <literal>page_names</literal>
attribute must be defined as a list of the names of the
configlet page widgets. Only one exception is allowed:
single-page configlets may omit this attribute if the name
for its page widget is <literal>mainwidget</literal>.</para>
<para>Multi-page configlets must also define a
<literal>page_display_titles</literal> attribute. This
should be a dictionary containing short descriptive titles
for each page of the configlet.</para>
<para>Optionally, the <literal>priority</literal> attribute
may be defined. This should be a number between 1 and 100;
1 is considered to be the highest priority, and will be
displayed first, most prominently, or however the front end
chooses to interpret priority. This value is entirely
advisory, and may not make sense in all front ends. If not
set, the <ulink url="api/index.html">API</ulink> will set it
to the default value of 50.</para>
<para>This list of attributes is not exhaustive; the
configlet may decide to set other attributes for other uses.
See the <ulink url="api/index.html">API Reference</ulink>
for details.</para>
<para>This dictionary of attributes must be passed to <ulink
url="api/configlet.py.html#register_configlet"><function>configlet.register_configlet(<replaceable>classname</replaceable>,
<replaceable>attributes</replaceable>)</function></ulink>.</para>
<example>
<title>Etherconf attributes and registration</title>
<programlisting>
<![CDATA[
_attrs = { "name": "etherconf",
"display_title": _("Configure Network Interfaces"),
"description": _("Select this option to configure the devices your system uses to access networks such as the Internet."),
"packages": ["etherconf", "postfix"]
}
configlet.register_configlet(Etherconf, _attrs)
]]>
</programlisting>
</example>
</listitem>
</itemizedlist>
<para>If your configlet is a front end to Debconf questions (most
are), you will also want to define <ulink
url="api/configlet_Configlet.py.html#load_debconf"><function>load_debconf</function></ulink>
and <ulink
url="api/configlet_Configlet.py.html#report_debconf"><function>report_debconf</function></ulink>
methods. <ulink
url="api/configlet_Configlet.py.html#load_debconf"><function>load_debconf</function></ulink>
receives as the list of all Debconf values, not just those pertaining
to the packages this configlet configures. It is up to the configlet
to determine which values it is interested in.</para>
<example>
<title>Etherconf load_debconf</title>
<programlisting>
<![CDATA[
def load_debconf(self, dcdata):
self.mail_config = []
for i in dcdata:
(template, question, value) = re.split(r"\s+", i, 2)
(package, varname) = re.split("/", question, 1)
if package == "postfix":
self.mail_config.append(i)
if package != "etherconf":
continue
if value == '""' or value == "none":
value = ""
if varname == "INT-devices":
# We don't need to read this one, but we do need to
# set it. See debconf() below.
pass
elif varname == "hostname":
if value:
self.hostname_entry.set_text(value)
elif varname == "domainname":
if value:
self.domainname_entry.set_text(value)
elif varname == "nameservers":
if value:
self.nameservers_entry.set_text(value)
else:
# If a ValueError occurs, this is not a dhcp-p:eth0
# type question (which is what we're after), so it
# doesn't matter what it is, we're simply not
# interested.
try:
(varname, device) = re.split(":", varname, 1)
except ValueError:
continue
info = self.get_device_info(device)
if varname == "configure":
if value == "true":
info.configure = TRUE
else:
info.configure = FALSE
elif varname == "removable":
if value == "true":
info.removable = TRUE
else:
info.removable = FALSE
elif varname == "dhcp-p":
if value == "true":
info.dhcp = TRUE
else:
info.dhcp = FALSE
elif varname == "dhcphost":
if value:
info.dhcphost = value
elif varname == "ipaddr":
if value:
info.ip = value
elif varname == "netmask":
if value:
info.netmask = value
elif varname == "gateway":
if value:
info.gateway = value
self.setup_menu(self.devices)
self.change_device(self.devices[0])
]]>
</programlisting>
</example>
<example>
<title>Etherconf report_debconf</title>
<programlisting>
<![CDATA[
def _dcstring(var, val, device=None):
if device:
s = "etherconf/%s etherconf/%s:%s %s" % (var, var, device, val)
else:
s = "etherconf/%s etherconf/%s %s" % (var, var, val)
debug("Reporting %s" % (s,))
return s
def report_debconf(self):
results = []
int_devices = ""
results.extend(self.mail_config)
results.append("postfix/relayhost postfix/relayhost %s"
% (self.relayhost,))
results.append("postfix/mailname postfix/mailname %s.%s"
% (self.hostname, self.domainname))
results.append(_dcstring("replace-existing-files", "true"))
results.append(_dcstring("hostname", self.hostname))
results.append(_dcstring("domainname", self.domainname))
results.append(_dcstring("nameservers", self.nameservers))
for i in self.devices:
int_devices = "%s:%s" % (int_devices, i)
info = self.get_device_info(i)
if info.configure:
results.append(_dcstring("configure", "true", i))
else:
results.append(_dcstring("configure", "false", i))
if info.removable:
results.append(_dcstring("removable", "true", i))
else:
results.append(_dcstring("removable", "false", i))
if info.dhcp:
results.append(_dcstring("dhcp-p", "true", i))
else:
results.append(_dcstring("dhcp-p", "false", i))
results.append(_dcstring("dhcphost", info.dhcphost, i))
results.append(_dcstring("ipaddr", info.ip, i))
results.append(_dcstring("netmask", info.netmask, i))
results.append(_dcstring("gateway", info.gateway, i))
results.append(_dcstring("INT-devices", int_devices[1:]))
return results
]]>
</programlisting>
</example>
<para>Configlet directories must be located in a standard place,
<filename>/usr/share/configlets/</filename>, where front ends
can find them easily. Note that the directory name under
<filename>/usr/share/configlets</filename> is arbitrary, but
should be descriptive and unlikely to risk a collision with
other configlets, and must not contain spaces or other unusual
characters.</para>
<para>Here is a sample hierarchy.</para>
<literallayout class="monospaced">
/usr/share/configlets
|
+--etherconf
| |
| +--main.glade
| +--main.py
|
+--timezoneconf
| |
| +--main.glade
| +--main.py
|
(and so on)
</literallayout>
<para>Any package providing a configlet must call
<command>update-configlets <option>--install</option>
<parameter>directory_name</parameter></command> in its
<filename>postinst</filename> and <command>update-configlets
<option>--remove</option>
<parameter>directory_name</parameter></command> in its
<filename>prerm</filename>; this allows the configlet system to
register configlets with front ends that require registration of
some kind. This will require that the package either test for
<filename>/usr/sbin/update-configlets</filename> in the
<filename>postinst</filename> or declare a dependency on the
configlet package.</para>
</section>
<section id="multi-page-configlets">
<title>Multi-Page Configlets</title>
<para>For a standard configlet, this is all you need. For very
complex configlets, however, you might want several pages of
configuration screens; this shouldn't be done with a simple
tabbed widget to prevent nested tab widgets (if the front end
implements tabs, for example). Instead, implement a multi-page
configlet. The front end can then present the pages in whichever
way makes sense.</para>
<para>To implement a multipage configlet, create your multiple
pages in Glade and save them in <filename>main.glade</filename>.
For each page, the container widget that envelops the entire
page should be named with a sensible name. Then, set the
attribute <literal>page_names</literal> to a list of these
names. Optionally, you may set the
<literal>page_display_titles</literal> attribute as well; this
should be a mapping between page names (as in the
<literal>page_names</literal> attribute) and the short
descriptive titles for each page.</para>
<para>You can also define <function>validate_page</function>
that can be called by the front end to validate a particular
page's input. As with <function>validate</function>, this
function is advisory only; there are no guarantees that it will
be called at all, that it will be called in any particular
order, or that its results will not be ignored. It returns a
similar value to <function>validate</function>, and takes the
name of the page to validate as its only argument. The default
definitions for <function>validate</function> and
<function>validate_page</function> complement each other; thus,
a configlet should only override one or the other.</para>
</section>
</chapter>
<!-- Local variables: -->
<!-- eval: (sgml-load-dtd "../../doctools/docbook.ced") -->
<!-- End: -->
|