1 | <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 3.0//EN">
|
---|
2 | <!--
|
---|
3 | $Id: hackguide.html,v 1.26 2003/10/04 22:34:02 tom Exp $
|
---|
4 | -->
|
---|
5 | <HTML>
|
---|
6 | <HEAD>
|
---|
7 | <TITLE>A Hacker's Guide to Ncurses Internals</TITLE>
|
---|
8 | <link rev="made" href="mailto:bugs-ncurses@gnu.org">
|
---|
9 | <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
|
---|
10 | <!--
|
---|
11 | This document is self-contained, *except* that there is one relative link to
|
---|
12 | the ncurses-intro.html document, expected to be in the same directory with
|
---|
13 | this one.
|
---|
14 | -->
|
---|
15 | </HEAD>
|
---|
16 | <BODY>
|
---|
17 |
|
---|
18 | <H1>A Hacker's Guide to NCURSES</H1>
|
---|
19 |
|
---|
20 | <H1>Contents</H1>
|
---|
21 | <UL>
|
---|
22 | <LI><A HREF="#abstract">Abstract</A>
|
---|
23 | <LI><A HREF="#objective">Objective of the Package</A>
|
---|
24 | <UL>
|
---|
25 | <LI><A HREF="#whysvr4">Why System V Curses?</A>
|
---|
26 | <LI><A HREF="#extensions">How to Design Extensions</A>
|
---|
27 | </UL>
|
---|
28 | <LI><A HREF="#portability">Portability and Configuration</A>
|
---|
29 | <LI><A HREF="#documentation">Documentation Conventions</A>
|
---|
30 | <LI><A HREF="#bugtrack">How to Report Bugs</A>
|
---|
31 | <LI><A HREF="#ncurslib">A Tour of the Ncurses Library</A>
|
---|
32 | <UL>
|
---|
33 | <LI><A HREF="#loverview">Library Overview</A>
|
---|
34 | <LI><A HREF="#engine">The Engine Room</A>
|
---|
35 | <LI><A HREF="#input">Keyboard Input</A>
|
---|
36 | <LI><A HREF="#mouse">Mouse Events</A>
|
---|
37 | <LI><A HREF="#output">Output and Screen Updating</A>
|
---|
38 | </UL>
|
---|
39 | <LI><A HREF="#fmnote">The Forms and Menu Libraries</A>
|
---|
40 | <LI><A HREF="#tic">A Tour of the Terminfo Compiler</A>
|
---|
41 | <UL>
|
---|
42 | <LI><A HREF="#nonuse">Translation of Non-<STRONG>use</STRONG> Capabilities</A>
|
---|
43 | <LI><A HREF="#uses">Use Capability Resolution</A>
|
---|
44 | <LI><A HREF="#translation">Source-Form Translation</A>
|
---|
45 | </UL>
|
---|
46 | <LI><A HREF="#utils">Other Utilities</A>
|
---|
47 | <LI><A HREF="#style">Style Tips for Developers</A>
|
---|
48 | <LI><A HREF="#port">Porting Hints</A>
|
---|
49 | </UL>
|
---|
50 |
|
---|
51 | <H1><A NAME="abstract">Abstract</A></H1>
|
---|
52 |
|
---|
53 | This document is a hacker's tour of the <STRONG>ncurses</STRONG> library and utilities.
|
---|
54 | It discusses design philosophy, implementation methods, and the
|
---|
55 | conventions used for coding and documentation. It is recommended
|
---|
56 | reading for anyone who is interested in porting, extending or improving the
|
---|
57 | package.
|
---|
58 |
|
---|
59 | <H1><A NAME="objective">Objective of the Package</A></H1>
|
---|
60 |
|
---|
61 | The objective of the <STRONG>ncurses</STRONG> package is to provide a free software API for
|
---|
62 | character-cell terminals and terminal emulators with the following
|
---|
63 | characteristics:
|
---|
64 |
|
---|
65 | <UL>
|
---|
66 | <LI>Source-compatible with historical curses implementations (including
|
---|
67 | the original BSD curses and System V curses.
|
---|
68 | <LI>Conformant with the XSI Curses standard issued as part of XPG4 by
|
---|
69 | X/Open.
|
---|
70 | <LI>High-quality -- stable and reliable code, wide portability, good
|
---|
71 | packaging, superior documentation.
|
---|
72 | <LI>Featureful -- should eliminate as much of the drudgery of C interface
|
---|
73 | programming as possible, freeing programmers to think at a higher
|
---|
74 | level of design.
|
---|
75 | </UL>
|
---|
76 |
|
---|
77 | These objectives are in priority order. So, for example, source
|
---|
78 | compatibility with older version must trump featurefulness -- we cannot
|
---|
79 | add features if it means breaking the portion of the API corresponding
|
---|
80 | to historical curses versions.
|
---|
81 |
|
---|
82 | <H2><A NAME="whysvr4">Why System V Curses?</A></H2>
|
---|
83 |
|
---|
84 | We used System V curses as a model, reverse-engineering their API, in
|
---|
85 | order to fulfill the first two objectives. <P>
|
---|
86 |
|
---|
87 | System V curses implementations can support BSD curses programs with
|
---|
88 | just a recompilation, so by capturing the System V API we also
|
---|
89 | capture BSD's. <P>
|
---|
90 |
|
---|
91 | More importantly for the future, the XSI Curses standard issued by X/Open
|
---|
92 | is explicitly and closely modeled on System V. So conformance with
|
---|
93 | System V took us most of the way to base-level XSI conformance.
|
---|
94 |
|
---|
95 | <H2><A NAME="extensions">How to Design Extensions</A></H2>
|
---|
96 |
|
---|
97 | The third objective (standards conformance) requires that it be easy to
|
---|
98 | condition source code using <STRONG>ncurses</STRONG> so that the absence of nonstandard
|
---|
99 | extensions does not break the code. <P>
|
---|
100 |
|
---|
101 | Accordingly, we have a policy of associating with each nonstandard extension
|
---|
102 | a feature macro, so that ncurses client code can use this macro to condition
|
---|
103 | in or out the code that requires the <STRONG>ncurses</STRONG> extension. <P>
|
---|
104 |
|
---|
105 | For example, there is a macro <CODE>NCURSES_MOUSE_VERSION</CODE> which XSI Curses
|
---|
106 | does not define, but which is defined in the <STRONG>ncurses</STRONG> library header.
|
---|
107 | You can use this to condition the calls to the mouse API calls.
|
---|
108 |
|
---|
109 | <H1><A NAME="portability">Portability and Configuration</A></H1>
|
---|
110 |
|
---|
111 | Code written for <STRONG>ncurses</STRONG> may assume an ANSI-standard C compiler and
|
---|
112 | POSIX-compatible OS interface. It may also assume the presence of a
|
---|
113 | System-V-compatible <EM>select(2)</EM> call. <P>
|
---|
114 |
|
---|
115 | We encourage (but do not require) developers to make the code friendly
|
---|
116 | to less-capable UNIX environments wherever possible. <P>
|
---|
117 |
|
---|
118 | We encourage developers to support OS-specific optimizations and methods
|
---|
119 | not available under POSIX/ANSI, provided only that:
|
---|
120 |
|
---|
121 | <UL>
|
---|
122 | <LI>All such code is properly conditioned so the build process does not
|
---|
123 | attempt to compile it under a plain ANSI/POSIX environment.
|
---|
124 | <LI>Adding such implementation methods does not introduce incompatibilities
|
---|
125 | in the <STRONG>ncurses</STRONG> API between platforms.
|
---|
126 | </UL>
|
---|
127 |
|
---|
128 | We use GNU <CODE>autoconf(1)</CODE> as a tool to deal with portability issues.
|
---|
129 | The right way to leverage an OS-specific feature is to modify the autoconf
|
---|
130 | specification files (configure.in and aclocal.m4) to set up a new feature
|
---|
131 | macro, which you then use to condition your code.
|
---|
132 |
|
---|
133 | <H1><A NAME="documentation">Documentation Conventions</A></H1>
|
---|
134 |
|
---|
135 | There are three kinds of documentation associated with this package. Each
|
---|
136 | has a different preferred format:
|
---|
137 |
|
---|
138 | <UL>
|
---|
139 | <LI>Package-internal files (README, INSTALL, TO-DO etc.)
|
---|
140 | <LI>Manual pages.
|
---|
141 | <LI>Everything else (i.e., narrative documentation).
|
---|
142 | </UL>
|
---|
143 |
|
---|
144 | Our conventions are simple:
|
---|
145 | <OL>
|
---|
146 | <LI><STRONG>Maintain package-internal files in plain text.</STRONG>
|
---|
147 | The expected viewer for them <EM>more(1)</EM> or an editor window; there's
|
---|
148 | no point in elaborate mark-up.
|
---|
149 |
|
---|
150 | <LI><STRONG>Mark up manual pages in the man macros.</STRONG> These have to be viewable
|
---|
151 | through traditional <EM>man(1)</EM> programs.
|
---|
152 |
|
---|
153 | <LI><STRONG>Write everything else in HTML.</STRONG>
|
---|
154 | </OL>
|
---|
155 |
|
---|
156 | When in doubt, HTMLize a master and use <EM>lynx(1)</EM> to generate
|
---|
157 | plain ASCII (as we do for the announcement document). <P>
|
---|
158 |
|
---|
159 | The reason for choosing HTML is that it's (a) well-adapted for on-line
|
---|
160 | browsing through viewers that are everywhere; (b) more easily readable
|
---|
161 | as plain text than most other mark-ups, if you don't have a viewer; and (c)
|
---|
162 | carries enough information that you can generate a nice-looking printed
|
---|
163 | version from it. Also, of course, it make exporting things like the
|
---|
164 | announcement document to WWW pretty trivial.
|
---|
165 |
|
---|
166 | <H1><A NAME="bugtrack">How to Report Bugs</A></H1>
|
---|
167 |
|
---|
168 | The <A NAME="bugreport">reporting address for bugs</A> is
|
---|
169 | <A HREF="mailto:bug-ncurses@gnu.org">bug-ncurses@gnu.org</A>.
|
---|
170 | This is a majordomo list; to join, write
|
---|
171 | to <CODE>bug-ncurses-request@gnu.org</CODE> with a message containing the line:
|
---|
172 | <PRE>
|
---|
173 | subscribe <name>@<host.domain>
|
---|
174 | </PRE>
|
---|
175 |
|
---|
176 | The <CODE>ncurses</CODE> code is maintained by a small group of
|
---|
177 | volunteers. While we try our best to fix bugs promptly, we simply
|
---|
178 | don't have a lot of hours to spend on elementary hand-holding. We rely
|
---|
179 | on intelligent cooperation from our users. If you think you have
|
---|
180 | found a bug in <CODE>ncurses</CODE>, there are some steps you can take
|
---|
181 | before contacting us that will help get the bug fixed quickly. <P>
|
---|
182 |
|
---|
183 | In order to use our bug-fixing time efficiently, we put people who
|
---|
184 | show us they've taken these steps at the head of our queue. This
|
---|
185 | means that if you don't, you'll probably end up at the tail end and
|
---|
186 | have to wait a while.
|
---|
187 |
|
---|
188 | <OL>
|
---|
189 | <LI>Develop a recipe to reproduce the bug.
|
---|
190 | <p>
|
---|
191 | Bugs we can reproduce are likely to be fixed very quickly, often
|
---|
192 | within days. The most effective single thing you can do to get a
|
---|
193 | quick fix is develop a way we can duplicate the bad behavior --
|
---|
194 | ideally, by giving us source for a small, portable test program that
|
---|
195 | breaks the library. (Even better is a keystroke recipe using one of
|
---|
196 | the test programs provided with the distribution.)
|
---|
197 |
|
---|
198 | <LI>Try to reproduce the bug on a different terminal type. <P>
|
---|
199 |
|
---|
200 | In our experience, most of the behaviors people report as library bugs
|
---|
201 | are actually due to subtle problems in terminal descriptions. This is
|
---|
202 | especially likely to be true if you're using a traditional
|
---|
203 | asynchronous terminal or PC-based terminal emulator, rather than xterm
|
---|
204 | or a UNIX console entry. <P>
|
---|
205 |
|
---|
206 | It's therefore extremely helpful if you can tell us whether or not your
|
---|
207 | problem reproduces on other terminal types. Usually you'll have both
|
---|
208 | a console type and xterm available; please tell us whether or not your
|
---|
209 | bug reproduces on both. <P>
|
---|
210 |
|
---|
211 | If you have xterm available, it is also good to collect xterm reports for
|
---|
212 | different window sizes. This is especially true if you normally use an
|
---|
213 | unusual xterm window size -- a surprising number of the bugs we've seen
|
---|
214 | are either triggered or masked by these.
|
---|
215 |
|
---|
216 | <LI>Generate and examine a trace file for the broken behavior. <P>
|
---|
217 |
|
---|
218 | Recompile your program with the debugging versions of the libraries.
|
---|
219 | Insert a <CODE>trace()</CODE> call with the argument set to <CODE>TRACE_UPDATE</CODE>.
|
---|
220 | (See <A HREF="ncurses-intro.html#debugging">"Writing Programs with
|
---|
221 | NCURSES"</A> for details on trace levels.)
|
---|
222 | Reproduce your bug, then look at the trace file to see what the library
|
---|
223 | was actually doing. <P>
|
---|
224 |
|
---|
225 | Another frequent cause of apparent bugs is application coding errors
|
---|
226 | that cause the wrong things to be put on the virtual screen. Looking
|
---|
227 | at the virtual-screen dumps in the trace file will tell you immediately if
|
---|
228 | this is happening, and save you from the possible embarrassment of being
|
---|
229 | told that the bug is in your code and is your problem rather than ours. <P>
|
---|
230 |
|
---|
231 | If the virtual-screen dumps look correct but the bug persists, it's
|
---|
232 | possible to crank up the trace level to give more and more information
|
---|
233 | about the library's update actions and the control sequences it issues
|
---|
234 | to perform them. The test directory of the distribution contains a
|
---|
235 | tool for digesting these logs to make them less tedious to wade
|
---|
236 | through. <P>
|
---|
237 |
|
---|
238 | Often you'll find terminfo problems at this stage by noticing that the
|
---|
239 | escape sequences put out for various capabilities are wrong. If not,
|
---|
240 | you're likely to learn enough to be able to characterize any bug in
|
---|
241 | the screen-update logic quite exactly.
|
---|
242 |
|
---|
243 | <LI>Report details and symptoms, not just interpretations. <P>
|
---|
244 |
|
---|
245 | If you do the preceding two steps, it is very likely that you'll discover
|
---|
246 | the nature of the problem yourself and be able to send us a fix. This
|
---|
247 | will create happy feelings all around and earn you good karma for the first
|
---|
248 | time you run into a bug you really can't characterize and fix yourself. <P>
|
---|
249 |
|
---|
250 | If you're still stuck, at least you'll know what to tell us. Remember, we
|
---|
251 | need details. If you guess about what is safe to leave out, you are too
|
---|
252 | likely to be wrong. <P>
|
---|
253 |
|
---|
254 | If your bug produces a bad update, include a trace file. Try to make
|
---|
255 | the trace at the <EM>least</EM> voluminous level that pins down the
|
---|
256 | bug. Logs that have been through tracemunch are OK, it doesn't throw
|
---|
257 | away any information (actually they're better than un-munched ones because
|
---|
258 | they're easier to read). <P>
|
---|
259 |
|
---|
260 | If your bug produces a core-dump, please include a symbolic stack trace
|
---|
261 | generated by gdb(1) or your local equivalent. <P>
|
---|
262 |
|
---|
263 | Tell us about every terminal on which you've reproduced the bug -- and
|
---|
264 | every terminal on which you can't. Ideally, sent us terminfo sources
|
---|
265 | for all of these (yours might differ from ours). <P>
|
---|
266 |
|
---|
267 | Include your ncurses version and your OS/machine type, of course! You can
|
---|
268 | find your ncurses version in the <CODE>curses.h</CODE> file.
|
---|
269 | </OL>
|
---|
270 |
|
---|
271 | If your problem smells like a logic error or in cursor movement or
|
---|
272 | scrolling or a bad capability, there are a couple of tiny test frames
|
---|
273 | for the library algorithms in the progs directory that may help you
|
---|
274 | isolate it. These are not part of the normal build, but do have their
|
---|
275 | own make productions. <P>
|
---|
276 |
|
---|
277 | The most important of these is <CODE>mvcur</CODE>, a test frame for the
|
---|
278 | cursor-movement optimization code. With this program, you can see
|
---|
279 | directly what control sequences will be emitted for any given cursor
|
---|
280 | movement or scroll/insert/delete operations. If you think you've got
|
---|
281 | a bad capability identified, you can disable it and test again. The
|
---|
282 | program is command-driven and has on-line help. <P>
|
---|
283 |
|
---|
284 | If you think the vertical-scroll optimization is broken, or just want to
|
---|
285 | understand how it works better, build <CODE>hashmap</CODE> and read the
|
---|
286 | header comments of <CODE>hardscroll.c</CODE> and <CODE>hashmap.c</CODE>; then try
|
---|
287 | it out. You can also test the hardware-scrolling optimization separately
|
---|
288 | with <CODE>hardscroll</CODE>. <P>
|
---|
289 |
|
---|
290 | There's one other interactive tester, <CODE>tctest</CODE>, that exercises
|
---|
291 | translation between termcap and terminfo formats. If you have a serious
|
---|
292 | need to run this, you probably belong on our development team!
|
---|
293 |
|
---|
294 | <H1><A NAME="ncurslib">A Tour of the Ncurses Library</A></H1>
|
---|
295 |
|
---|
296 | <H2><A NAME="loverview">Library Overview</A></H2>
|
---|
297 |
|
---|
298 | Most of the library is superstructure -- fairly trivial convenience
|
---|
299 | interfaces to a small set of basic functions and data structures used
|
---|
300 | to manipulate the virtual screen (in particular, none of this code
|
---|
301 | does any I/O except through calls to more fundamental modules
|
---|
302 | described below). The files
|
---|
303 | <blockquote>
|
---|
304 | <CODE>
|
---|
305 | lib_addch.c
|
---|
306 | lib_bkgd.c
|
---|
307 | lib_box.c
|
---|
308 | lib_chgat.c
|
---|
309 | lib_clear.c
|
---|
310 | lib_clearok.c
|
---|
311 | lib_clrbot.c
|
---|
312 | lib_clreol.c
|
---|
313 | lib_colorset.c
|
---|
314 | lib_data.c
|
---|
315 | lib_delch.c
|
---|
316 | lib_delwin.c
|
---|
317 | lib_echo.c
|
---|
318 | lib_erase.c
|
---|
319 | lib_gen.c
|
---|
320 | lib_getstr.c
|
---|
321 | lib_hline.c
|
---|
322 | lib_immedok.c
|
---|
323 | lib_inchstr.c
|
---|
324 | lib_insch.c
|
---|
325 | lib_insdel.c
|
---|
326 | lib_insstr.c
|
---|
327 | lib_instr.c
|
---|
328 | lib_isendwin.c
|
---|
329 | lib_keyname.c
|
---|
330 | lib_leaveok.c
|
---|
331 | lib_move.c
|
---|
332 | lib_mvwin.c
|
---|
333 | lib_overlay.c
|
---|
334 | lib_pad.c
|
---|
335 | lib_printw.c
|
---|
336 | lib_redrawln.c
|
---|
337 | lib_scanw.c
|
---|
338 | lib_screen.c
|
---|
339 | lib_scroll.c
|
---|
340 | lib_scrollok.c
|
---|
341 | lib_scrreg.c
|
---|
342 | lib_set_term.c
|
---|
343 | lib_slk.c
|
---|
344 | lib_slkatr_set.c
|
---|
345 | lib_slkatrof.c
|
---|
346 | lib_slkatron.c
|
---|
347 | lib_slkatrset.c
|
---|
348 | lib_slkattr.c
|
---|
349 | lib_slkclear.c
|
---|
350 | lib_slkcolor.c
|
---|
351 | lib_slkinit.c
|
---|
352 | lib_slklab.c
|
---|
353 | lib_slkrefr.c
|
---|
354 | lib_slkset.c
|
---|
355 | lib_slktouch.c
|
---|
356 | lib_touch.c
|
---|
357 | lib_unctrl.c
|
---|
358 | lib_vline.c
|
---|
359 | lib_wattroff.c
|
---|
360 | lib_wattron.c
|
---|
361 | lib_window.c
|
---|
362 | </CODE>
|
---|
363 | </blockquote>
|
---|
364 | are all in this category. They are very
|
---|
365 | unlikely to need change, barring bugs or some fundamental
|
---|
366 | reorganization in the underlying data structures. <P>
|
---|
367 |
|
---|
368 | These files are used only for debugging support:
|
---|
369 | <blockquote>
|
---|
370 | <code>
|
---|
371 | lib_trace.c
|
---|
372 | lib_traceatr.c
|
---|
373 | lib_tracebits.c
|
---|
374 | lib_tracechr.c
|
---|
375 | lib_tracedmp.c
|
---|
376 | lib_tracemse.c
|
---|
377 | trace_buf.c
|
---|
378 | </code>
|
---|
379 | </blockquote>
|
---|
380 | It is rather unlikely you will ever need to change these, unless
|
---|
381 | you want to introduce a new debug trace level for some reasoon.<P>
|
---|
382 |
|
---|
383 | There is another group of files that do direct I/O via <EM>tputs()</EM>,
|
---|
384 | computations on the terminal capabilities, or queries to the OS
|
---|
385 | environment, but nevertheless have only fairly low complexity. These
|
---|
386 | include:
|
---|
387 | <blockquote>
|
---|
388 | <code>
|
---|
389 | lib_acs.c
|
---|
390 | lib_beep.c
|
---|
391 | lib_color.c
|
---|
392 | lib_endwin.c
|
---|
393 | lib_initscr.c
|
---|
394 | lib_longname.c
|
---|
395 | lib_newterm.c
|
---|
396 | lib_options.c
|
---|
397 | lib_termcap.c
|
---|
398 | lib_ti.c
|
---|
399 | lib_tparm.c
|
---|
400 | lib_tputs.c
|
---|
401 | lib_vidattr.c
|
---|
402 | read_entry.c.
|
---|
403 | </code>
|
---|
404 | </blockquote>
|
---|
405 | They are likely to need revision only if
|
---|
406 | ncurses is being ported to an environment without an underlying
|
---|
407 | terminfo capability representation. <P>
|
---|
408 |
|
---|
409 | These files
|
---|
410 | have serious hooks into
|
---|
411 | the tty driver and signal facilities:
|
---|
412 | <blockquote>
|
---|
413 | <code>
|
---|
414 | lib_kernel.c
|
---|
415 | lib_baudrate.c
|
---|
416 | lib_raw.c
|
---|
417 | lib_tstp.c
|
---|
418 | lib_twait.c
|
---|
419 | </code>
|
---|
420 | </blockquote>
|
---|
421 | If you run into porting snafus
|
---|
422 | moving the package to another UNIX, the problem is likely to be in one
|
---|
423 | of these files.
|
---|
424 | The file <CODE>lib_print.c</CODE> uses sleep(2) and also
|
---|
425 | falls in this category.<P>
|
---|
426 |
|
---|
427 | Almost all of the real work is done in the files
|
---|
428 | <blockquote>
|
---|
429 | <code>
|
---|
430 | hardscroll.c
|
---|
431 | hashmap.c
|
---|
432 | lib_addch.c
|
---|
433 | lib_doupdate.c
|
---|
434 | lib_getch.c
|
---|
435 | lib_mouse.c
|
---|
436 | lib_mvcur.c
|
---|
437 | lib_refresh.c
|
---|
438 | lib_setup.c
|
---|
439 | lib_vidattr.c
|
---|
440 | </code>
|
---|
441 | </blockquote>
|
---|
442 | Most of the algorithmic complexity in the
|
---|
443 | library lives in these files.
|
---|
444 | If there is a real bug in <STRONG>ncurses</STRONG> itself, it's probably here.
|
---|
445 | We'll tour some of these files in detail
|
---|
446 | below (see <A HREF="#engine">The Engine Room</A>). <P>
|
---|
447 |
|
---|
448 | Finally, there is a group of files that is actually most of the
|
---|
449 | terminfo compiler. The reason this code lives in the <STRONG>ncurses</STRONG>
|
---|
450 | library is to support fallback to /etc/termcap. These files include
|
---|
451 | <blockquote>
|
---|
452 | <code>
|
---|
453 | alloc_entry.c
|
---|
454 | captoinfo.c
|
---|
455 | comp_captab.c
|
---|
456 | comp_error.c
|
---|
457 | comp_hash.c
|
---|
458 | comp_parse.c
|
---|
459 | comp_scan.c
|
---|
460 | parse_entry.c
|
---|
461 | read_termcap.c
|
---|
462 | write_entry.c
|
---|
463 | </code>
|
---|
464 | </blockquote>
|
---|
465 | We'll discuss these in the compiler tour.
|
---|
466 |
|
---|
467 | <H2><A NAME="engine">The Engine Room</A></H2>
|
---|
468 |
|
---|
469 | <H3><A NAME="input">Keyboard Input</A></H3>
|
---|
470 |
|
---|
471 | All <CODE>ncurses</CODE> input funnels through the function
|
---|
472 | <CODE>wgetch()</CODE>, defined in <CODE>lib_getch.c</CODE>. This function is
|
---|
473 | tricky; it has to poll for keyboard and mouse events and do a running
|
---|
474 | match of incoming input against the set of defined special keys. <P>
|
---|
475 |
|
---|
476 | The central data structure in this module is a FIFO queue, used to
|
---|
477 | match multiple-character input sequences against special-key
|
---|
478 | capabilities; also to implement pushback via <CODE>ungetch()</CODE>. <P>
|
---|
479 |
|
---|
480 | The <CODE>wgetch()</CODE> code distinguishes between function key
|
---|
481 | sequences and the same sequences typed manually by doing a timed wait
|
---|
482 | after each input character that could lead a function key sequence.
|
---|
483 | If the entire sequence takes less than 1 second, it is assumed to have
|
---|
484 | been generated by a function key press. <P>
|
---|
485 |
|
---|
486 | Hackers bruised by previous encounters with variant <CODE>select(2)</CODE>
|
---|
487 | calls may find the code in <CODE>lib_twait.c</CODE> interesting. It deals
|
---|
488 | with the problem that some BSD selects don't return a reliable
|
---|
489 | time-left value. The function <CODE>timed_wait()</CODE> effectively
|
---|
490 | simulates a System V select.
|
---|
491 |
|
---|
492 | <H3><A NAME="mouse">Mouse Events</A></H3>
|
---|
493 |
|
---|
494 | If the mouse interface is active, <CODE>wgetch()</CODE> polls for mouse
|
---|
495 | events each call, before it goes to the keyboard for input. It is
|
---|
496 | up to <CODE>lib_mouse.c</CODE> how the polling is accomplished; it may vary
|
---|
497 | for different devices. <P>
|
---|
498 |
|
---|
499 | Under xterm, however, mouse event notifications come in via the keyboard
|
---|
500 | input stream. They are recognized by having the <STRONG>kmous</STRONG> capability
|
---|
501 | as a prefix. This is kind of klugey, but trying to wire in recognition of
|
---|
502 | a mouse key prefix without going through the function-key machinery would
|
---|
503 | be just too painful, and this turns out to imply having the prefix somewhere
|
---|
504 | in the function-key capabilities at terminal-type initialization. <P>
|
---|
505 |
|
---|
506 | This kluge only works because <STRONG>kmous</STRONG> isn't actually used by any
|
---|
507 | historic terminal type or curses implementation we know of. Best
|
---|
508 | guess is it's a relic of some forgotten experiment in-house at Bell
|
---|
509 | Labs that didn't leave any traces in the publicly-distributed System V
|
---|
510 | terminfo files. If System V or XPG4 ever gets serious about using it
|
---|
511 | again, this kluge may have to change. <P>
|
---|
512 |
|
---|
513 | Here are some more details about mouse event handling: <P>
|
---|
514 |
|
---|
515 | The <CODE>lib_mouse()</CODE>code is logically split into a lower level that
|
---|
516 | accepts event reports in a device-dependent format and an upper level that
|
---|
517 | parses mouse gestures and filters events. The mediating data structure is a
|
---|
518 | circular queue of event structures. <P>
|
---|
519 |
|
---|
520 | Functionally, the lower level's job is to pick up primitive events and
|
---|
521 | put them on the circular queue. This can happen in one of two ways:
|
---|
522 | either (a) <CODE>_nc_mouse_event()</CODE> detects a series of incoming
|
---|
523 | mouse reports and queues them, or (b) code in <CODE>lib_getch.c</CODE> detects the
|
---|
524 | <STRONG>kmous</STRONG> prefix in the keyboard input stream and calls _nc_mouse_inline
|
---|
525 | to queue up a series of adjacent mouse reports. <P>
|
---|
526 |
|
---|
527 | In either case, <CODE>_nc_mouse_parse()</CODE> should be called after the
|
---|
528 | series is accepted to parse the digested mouse reports (low-level
|
---|
529 | events) into a gesture (a high-level or composite event).
|
---|
530 |
|
---|
531 | <H3><A NAME="output">Output and Screen Updating</A></H3>
|
---|
532 |
|
---|
533 | With the single exception of character echoes during a <CODE>wgetnstr()</CODE>
|
---|
534 | call (which simulates cooked-mode line editing in an ncurses window),
|
---|
535 | the library normally does all its output at refresh time. <P>
|
---|
536 |
|
---|
537 | The main job is to go from the current state of the screen (as represented
|
---|
538 | in the <CODE>curscr</CODE> window structure) to the desired new state (as
|
---|
539 | represented in the <CODE>newscr</CODE> window structure), while doing as
|
---|
540 | little I/O as possible. <P>
|
---|
541 |
|
---|
542 | The brains of this operation are the modules <CODE>hashmap.c</CODE>,
|
---|
543 | <CODE>hardscroll.c</CODE> and <CODE>lib_doupdate.c</CODE>; the latter two use
|
---|
544 | <CODE>lib_mvcur.c</CODE>. Essentially, what happens looks like this: <P>
|
---|
545 |
|
---|
546 | The <CODE>hashmap.c</CODE> module tries to detect vertical motion
|
---|
547 | changes between the real and virtual screens. This information
|
---|
548 | is represented by the oldindex members in the newscr structure.
|
---|
549 | These are modified by vertical-motion and clear operations, and both are
|
---|
550 | re-initialized after each update. To this change-journalling
|
---|
551 | information, the hashmap code adds deductions made using a modified Heckel
|
---|
552 | algorithm on hash values generated from the line contents. <P>
|
---|
553 |
|
---|
554 | The <CODE>hardscroll.c</CODE> module computes an optimum set of scroll,
|
---|
555 | insertion, and deletion operations to make the indices match. It calls
|
---|
556 | <CODE>_nc_mvcur_scrolln()</CODE> in <CODE>lib_mvcur.c</CODE> to do those motions. <P>
|
---|
557 |
|
---|
558 | Then <CODE>lib_doupdate.c</CODE> goes to work. Its job is to do line-by-line
|
---|
559 | transformations of <CODE>curscr</CODE> lines to <CODE>newscr</CODE> lines. Its main
|
---|
560 | tool is the routine <CODE>mvcur()</CODE> in <CODE>lib_mvcur.c</CODE>. This routine
|
---|
561 | does cursor-movement optimization, attempting to get from given screen
|
---|
562 | location A to given location B in the fewest output characters posible. <P>
|
---|
563 |
|
---|
564 | If you want to work on screen optimizations, you should use the fact
|
---|
565 | that (in the trace-enabled version of the library) enabling the
|
---|
566 | <CODE>TRACE_TIMES</CODE> trace level causes a report to be emitted after
|
---|
567 | each screen update giving the elapsed time and a count of characters
|
---|
568 | emitted during the update. You can use this to tell when an update
|
---|
569 | optimization improves efficiency. <P>
|
---|
570 |
|
---|
571 | In the trace-enabled version of the library, it is also possible to disable
|
---|
572 | and re-enable various optimizations at runtime by tweaking the variable
|
---|
573 | <CODE>_nc_optimize_enable</CODE>. See the file <CODE>include/curses.h.in</CODE>
|
---|
574 | for mask values, near the end.
|
---|
575 |
|
---|
576 | <H1><A NAME="fmnote">The Forms and Menu Libraries</A></H1>
|
---|
577 |
|
---|
578 | The forms and menu libraries should work reliably in any environment you
|
---|
579 | can port ncurses to. The only portability issue anywhere in them is what
|
---|
580 | flavor of regular expressions the built-in form field type TYPE_REGEXP
|
---|
581 | will recognize. <P>
|
---|
582 |
|
---|
583 | The configuration code prefers the POSIX regex facility, modeled on
|
---|
584 | System V's, but will settle for BSD regexps if the former isn't available. <P>
|
---|
585 |
|
---|
586 | Historical note: the panels code was written primarily to assist in
|
---|
587 | porting u386mon 2.0 (comp.sources.misc v14i001-4) to systems lacking
|
---|
588 | panels support; u386mon 2.10 and beyond use it. This version has been
|
---|
589 | slightly cleaned up for <CODE>ncurses</CODE>.
|
---|
590 |
|
---|
591 | <H1><A NAME="tic">A Tour of the Terminfo Compiler</A></H1>
|
---|
592 |
|
---|
593 | The <STRONG>ncurses</STRONG> implementation of <STRONG>tic</STRONG> is rather complex
|
---|
594 | internally; it has to do a trying combination of missions. This starts
|
---|
595 | with the fact that, in addition to its normal duty of compiling
|
---|
596 | terminfo sources into loadable terminfo binaries, it has to be able to
|
---|
597 | handle termcap syntax and compile that too into terminfo entries. <P>
|
---|
598 |
|
---|
599 | The implementation therefore starts with a table-driven, dual-mode
|
---|
600 | lexical analyzer (in <CODE>comp_scan.c</CODE>). The lexer chooses its
|
---|
601 | mode (termcap or terminfo) based on the first `,' or `:' it finds in
|
---|
602 | each entry. The lexer does all the work of recognizing capability
|
---|
603 | names and values; the grammar above it is trivial, just "parse entries
|
---|
604 | till you run out of file".
|
---|
605 |
|
---|
606 | <H2><A NAME="nonuse">Translation of Non-<STRONG>use</STRONG> Capabilities</A></H2>
|
---|
607 |
|
---|
608 | Translation of most things besides <STRONG>use</STRONG> capabilities is pretty
|
---|
609 | straightforward. The lexical analyzer's tokenizer hands each capability
|
---|
610 | name to a hash function, which drives a table lookup. The table entry
|
---|
611 | yields an index which is used to look up the token type in another table,
|
---|
612 | and controls interpretation of the value. <P>
|
---|
613 |
|
---|
614 | One possibly interesting aspect of the implementation is the way the
|
---|
615 | compiler tables are initialized. All the tables are generated by various
|
---|
616 | awk/sed/sh scripts from a master table <CODE>include/Caps</CODE>; these
|
---|
617 | scripts actually write C initializers which are linked to the compiler.
|
---|
618 | Furthermore, the hash table is generated in the same way, so it doesn't
|
---|
619 | have to be generated at compiler startup time (another benefit of this
|
---|
620 | organization is that the hash table can be in shareable text space). <P>
|
---|
621 |
|
---|
622 | Thus, adding a new capability is usually pretty trivial, just a matter
|
---|
623 | of adding one line to the <CODE>include/Caps</CODE> file. We'll have more
|
---|
624 | to say about this in the section on <A HREF="#translation">Source-Form
|
---|
625 | Translation</A>.
|
---|
626 |
|
---|
627 | <H2><A NAME="uses">Use Capability Resolution</A></H2>
|
---|
628 |
|
---|
629 | The background problem that makes <STRONG>tic</STRONG> tricky isn't the capability
|
---|
630 | translation itself, it's the resolution of <STRONG>use</STRONG> capabilities. Older
|
---|
631 | versions would not handle forward <STRONG>use</STRONG> references for this reason
|
---|
632 | (that is, a using terminal always had to follow its use target in the
|
---|
633 | source file). By doing this, they got away with a simple implementation
|
---|
634 | tactic; compile everything as it blows by, then resolve uses from compiled
|
---|
635 | entries. <P>
|
---|
636 |
|
---|
637 | This won't do for <STRONG>ncurses</STRONG>. The problem is that that the whole
|
---|
638 | compilation process has to be embeddable in the <STRONG>ncurses</STRONG> library
|
---|
639 | so that it can be called by the startup code to translate termcap
|
---|
640 | entries on the fly. The embedded version can't go promiscuously writing
|
---|
641 | everything it translates out to disk -- for one thing, it will typically
|
---|
642 | be running with non-root permissions. <P>
|
---|
643 |
|
---|
644 | So our <STRONG>tic</STRONG> is designed to parse an entire terminfo file into a
|
---|
645 | doubly-linked circular list of entry structures in-core, and then do
|
---|
646 | <STRONG>use</STRONG> resolution in-memory before writing everything out. This
|
---|
647 | design has other advantages: it makes forward and back use-references
|
---|
648 | equally easy (so we get the latter for free), and it makes checking for
|
---|
649 | name collisions before they're written out easy to do. <P>
|
---|
650 |
|
---|
651 | And this is exactly how the embedded version works. But the stand-alone
|
---|
652 | user-accessible version of <STRONG>tic</STRONG> partly reverts to the historical
|
---|
653 | strategy; it writes to disk (not keeping in core) any entry with no
|
---|
654 | <STRONG>use</STRONG> references. <P>
|
---|
655 |
|
---|
656 | This is strictly a core-economy kluge, implemented because the
|
---|
657 | terminfo master file is large enough that some core-poor systems swap
|
---|
658 | like crazy when you compile it all in memory...there have been reports of
|
---|
659 | this process taking <STRONG>three hours</STRONG>, rather than the twenty seconds
|
---|
660 | or less typical on the author's development box. <P>
|
---|
661 |
|
---|
662 | So. The executable <STRONG>tic</STRONG> passes the entry-parser a hook that
|
---|
663 | <EM>immediately</EM> writes out the referenced entry if it has no use
|
---|
664 | capabilities. The compiler main loop refrains from adding the entry
|
---|
665 | to the in-core list when this hook fires. If some other entry later
|
---|
666 | needs to reference an entry that got written immediately, that's OK;
|
---|
667 | the resolution code will fetch it off disk when it can't find it in
|
---|
668 | core. <P>
|
---|
669 |
|
---|
670 | Name collisions will still be detected, just not as cleanly. The
|
---|
671 | <CODE>write_entry()</CODE> code complains before overwriting an entry that
|
---|
672 | postdates the time of <STRONG>tic</STRONG>'s first call to
|
---|
673 | <CODE>write_entry()</CODE>, Thus it will complain about overwriting
|
---|
674 | entries newly made during the <STRONG>tic</STRONG> run, but not about
|
---|
675 | overwriting ones that predate it.
|
---|
676 |
|
---|
677 | <H2><A NAME="translation">Source-Form Translation</A></H2>
|
---|
678 |
|
---|
679 | Another use of <STRONG>tic</STRONG> is to do source translation between various termcap
|
---|
680 | and terminfo formats. There are more variants out there than you might
|
---|
681 | think; the ones we know about are described in the <STRONG>captoinfo(1)</STRONG>
|
---|
682 | manual page. <P>
|
---|
683 |
|
---|
684 | The translation output code (<CODE>dump_entry()</CODE> in
|
---|
685 | <CODE>ncurses/dump_entry.c</CODE>) is shared with the <STRONG>infocmp(1)</STRONG>
|
---|
686 | utility. It takes the same internal representation used to generate
|
---|
687 | the binary form and dumps it to standard output in a specified
|
---|
688 | format. <P>
|
---|
689 |
|
---|
690 | The <CODE>include/Caps</CODE> file has a header comment describing ways you
|
---|
691 | can specify source translations for nonstandard capabilities just by
|
---|
692 | altering the master table. It's possible to set up capability aliasing
|
---|
693 | or tell the compiler to plain ignore a given capability without writing
|
---|
694 | any C code at all. <P>
|
---|
695 |
|
---|
696 | For circumstances where you need to do algorithmic translation, there
|
---|
697 | are functions in <CODE>parse_entry.c</CODE> called after the parse of each
|
---|
698 | entry that are specifically intended to encapsulate such
|
---|
699 | translations. This, for example, is where the AIX <STRONG>box1</STRONG> capability
|
---|
700 | get translated to an <STRONG>acsc</STRONG> string.
|
---|
701 |
|
---|
702 | <H1><A NAME="utils">Other Utilities</A></H1>
|
---|
703 |
|
---|
704 | The <STRONG>infocmp</STRONG> utility is just a wrapper around the same
|
---|
705 | entry-dumping code used by <STRONG>tic</STRONG> for source translation. Perhaps
|
---|
706 | the one interesting aspect of the code is the use of a predicate
|
---|
707 | function passed in to <CODE>dump_entry()</CODE> to control which
|
---|
708 | capabilities are dumped. This is necessary in order to handle both
|
---|
709 | the ordinary De-compilation case and entry difference reporting. <P>
|
---|
710 |
|
---|
711 | The <STRONG>tput</STRONG> and <STRONG>clear</STRONG> utilities just do an entry load
|
---|
712 | followed by a <CODE>tputs()</CODE> of a selected capability.
|
---|
713 |
|
---|
714 | <H1><A NAME="style">Style Tips for Developers</A></H1>
|
---|
715 |
|
---|
716 | See the TO-DO file in the top-level directory of the source distribution
|
---|
717 | for additions that would be particularly useful. <P>
|
---|
718 |
|
---|
719 | The prefix <CODE>_nc_</CODE> should be used on library public functions that are
|
---|
720 | not part of the curses API in order to prevent pollution of the
|
---|
721 | application namespace.
|
---|
722 |
|
---|
723 | If you have to add to or modify the function prototypes in curses.h.in,
|
---|
724 | read ncurses/MKlib_gen.sh first so you can avoid breaking XSI conformance.
|
---|
725 |
|
---|
726 | Please join the ncurses mailing list. See the INSTALL file in the
|
---|
727 | top level of the distribution for details on the list. <P>
|
---|
728 |
|
---|
729 | Look for the string <CODE>FIXME</CODE> in source files to tag minor bugs
|
---|
730 | and potential problems that could use fixing. <P>
|
---|
731 |
|
---|
732 | Don't try to auto-detect OS features in the main body of the C code.
|
---|
733 | That's the job of the configuration system. <P>
|
---|
734 |
|
---|
735 | To hold down complexity, do make your code data-driven. Especially,
|
---|
736 | if you can drive logic from a table filtered out of
|
---|
737 | <CODE>include/Caps</CODE>, do it. If you find you need to augment the
|
---|
738 | data in that file in order to generate the proper table, that's still
|
---|
739 | preferable to ad-hoc code -- that's why the fifth field (flags) is
|
---|
740 | there. <P>
|
---|
741 |
|
---|
742 | Have fun!
|
---|
743 |
|
---|
744 | <H1><A NAME="port">Porting Hints</A></H1>
|
---|
745 |
|
---|
746 | The following notes are intended to be a first step towards DOS and Macintosh
|
---|
747 | ports of the ncurses libraries. <P>
|
---|
748 |
|
---|
749 | The following library modules are `pure curses'; they operate only on
|
---|
750 | the curses internal structures, do all output through other curses
|
---|
751 | calls (not including <CODE>tputs()</CODE> and <CODE>putp()</CODE>) and do not
|
---|
752 | call any other UNIX routines such as signal(2) or the stdio library.
|
---|
753 | Thus, they should not need to be modified for single-terminal
|
---|
754 | ports.
|
---|
755 |
|
---|
756 | <blockquote>
|
---|
757 | <code>
|
---|
758 | lib_addch.c
|
---|
759 | lib_addstr.c
|
---|
760 | lib_bkgd.c
|
---|
761 | lib_box.c
|
---|
762 | lib_clear.c
|
---|
763 | lib_clrbot.c
|
---|
764 | lib_clreol.c
|
---|
765 | lib_delch.c
|
---|
766 | lib_delwin.c
|
---|
767 | lib_erase.c
|
---|
768 | lib_inchstr.c
|
---|
769 | lib_insch.c
|
---|
770 | lib_insdel.c
|
---|
771 | lib_insstr.c
|
---|
772 | lib_keyname.c
|
---|
773 | lib_move.c
|
---|
774 | lib_mvwin.c
|
---|
775 | lib_newwin.c
|
---|
776 | lib_overlay.c
|
---|
777 | lib_pad.c
|
---|
778 | lib_printw.c
|
---|
779 | lib_refresh.c
|
---|
780 | lib_scanw.c
|
---|
781 | lib_scroll.c
|
---|
782 | lib_scrreg.c
|
---|
783 | lib_set_term.c
|
---|
784 | lib_touch.c
|
---|
785 | lib_tparm.c
|
---|
786 | lib_tputs.c
|
---|
787 | lib_unctrl.c
|
---|
788 | lib_window.c
|
---|
789 | panel.c
|
---|
790 | </code>
|
---|
791 | </blockquote>
|
---|
792 | <P>
|
---|
793 |
|
---|
794 | This module is pure curses, but calls outstr():
|
---|
795 |
|
---|
796 | <blockquote>
|
---|
797 | <code>
|
---|
798 | lib_getstr.c
|
---|
799 | </code>
|
---|
800 | </blockquote>
|
---|
801 | <P>
|
---|
802 |
|
---|
803 | These modules are pure curses, except that they use <CODE>tputs()</CODE>
|
---|
804 | and <CODE>putp()</CODE>:
|
---|
805 |
|
---|
806 | <blockquote>
|
---|
807 | <code>
|
---|
808 | lib_beep.c
|
---|
809 | lib_color.c
|
---|
810 | lib_endwin.c
|
---|
811 | lib_options.c
|
---|
812 | lib_slk.c
|
---|
813 | lib_vidattr.c
|
---|
814 | </code>
|
---|
815 | </blockquote>
|
---|
816 | <P>
|
---|
817 |
|
---|
818 | This modules assist in POSIX emulation on non-POSIX systems:
|
---|
819 | <DL>
|
---|
820 | <DT> sigaction.c
|
---|
821 | <DD> signal calls
|
---|
822 | </DL>
|
---|
823 |
|
---|
824 | The following source files will not be needed for a
|
---|
825 | single-terminal-type port.
|
---|
826 |
|
---|
827 | <blockquote>
|
---|
828 | <code>
|
---|
829 | alloc_entry.c
|
---|
830 | captoinfo.c
|
---|
831 | clear.c
|
---|
832 | comp_captab.c
|
---|
833 | comp_error.c
|
---|
834 | comp_hash.c
|
---|
835 | comp_main.c
|
---|
836 | comp_parse.c
|
---|
837 | comp_scan.c
|
---|
838 | dump_entry.c
|
---|
839 | infocmp.c
|
---|
840 | parse_entry.c
|
---|
841 | read_entry.c
|
---|
842 | tput.c
|
---|
843 | write_entry.c
|
---|
844 | </code>
|
---|
845 | </blockquote>
|
---|
846 | <P>
|
---|
847 |
|
---|
848 | The following modules will use open()/read()/write()/close()/lseek() on files,
|
---|
849 | but no other OS calls.
|
---|
850 |
|
---|
851 | <DL>
|
---|
852 | <DT>lib_screen.c
|
---|
853 | <DD>used to read/write screen dumps
|
---|
854 | <DT>lib_trace.c
|
---|
855 | <DD>used to write trace data to the logfile
|
---|
856 | </DL>
|
---|
857 |
|
---|
858 | Modules that would have to be modified for a port start here: <P>
|
---|
859 |
|
---|
860 | The following modules are `pure curses' but contain assumptions inappropriate
|
---|
861 | for a memory-mapped port.
|
---|
862 |
|
---|
863 | <dl>
|
---|
864 | <dt>lib_longname.c<dd>assumes there may be multiple terminals
|
---|
865 | <dt>lib_acs.c<dd>assumes acs_map as a double indirection
|
---|
866 | <dt>lib_mvcur.c<dd>assumes cursor moves have variable cost
|
---|
867 | <dt>lib_termcap.c<dd>assumes there may be multiple terminals
|
---|
868 | <dt>lib_ti.c<dd>assumes there may be multiple terminals
|
---|
869 | </dl>
|
---|
870 |
|
---|
871 | The following modules use UNIX-specific calls:
|
---|
872 |
|
---|
873 | <dl>
|
---|
874 | <dt>lib_doupdate.c<dd>input checking
|
---|
875 | <dt>lib_getch.c<dd>read()
|
---|
876 | <dt>lib_initscr.c<dd>getenv()
|
---|
877 | <dt>lib_newterm.c
|
---|
878 | <dt>lib_baudrate.c
|
---|
879 | <dt>lib_kernel.c<dd>various tty-manipulation and system calls
|
---|
880 | <dt>lib_raw.c<dd>various tty-manipulation calls
|
---|
881 | <dt>lib_setup.c<dd>various tty-manipulation calls
|
---|
882 | <dt>lib_restart.c<dd>various tty-manipulation calls
|
---|
883 | <dt>lib_tstp.c<dd>signal-manipulation calls
|
---|
884 | <dt>lib_twait.c<dd>gettimeofday(), select().
|
---|
885 | </dl>
|
---|
886 |
|
---|
887 | <HR>
|
---|
888 | <ADDRESS>Eric S. Raymond <esr@snark.thyrsus.com></ADDRESS>
|
---|
889 | (Note: This is <EM>not</EM> the <A HREF="#bugtrack">bug address</A>!)
|
---|
890 | </BODY>
|
---|
891 | </HTML>
|
---|