501 lines
22 KiB
HTML
501 lines
22 KiB
HTML
<!--startcut ==============================================-->
|
|
<!-- *** BEGIN HTML header *** -->
|
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
|
|
<HTML><HEAD>
|
|
<title>I Broke the Console Barrier LG #86</title>
|
|
</HEAD>
|
|
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#0000FF" VLINK="#0000AF"
|
|
ALINK="#FF0000">
|
|
<!-- *** END HTML header *** -->
|
|
|
|
<!-- *** BEGIN navbar *** -->
|
|
<IMG ALT="" SRC="../gx/navbar/left.jpg" WIDTH="14" HEIGHT="45" BORDER="0" ALIGN="bottom"><A HREF="artime.html"><IMG ALT="[ Prev ]" SRC="../gx/navbar/prev.jpg" WIDTH="16" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="index.html"><IMG ALT="[ Table of Contents ]" SRC="../gx/navbar/toc.jpg" WIDTH="220" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../index.html"><IMG ALT="[ Front Page ]" SRC="../gx/navbar/frontpage.jpg" WIDTH="137" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="http://www.linuxgazette.com/cgi-bin/talkback/all.py?site=LG&article=http://www.linuxgazette.com/issue86/bint.html"><IMG ALT="[ Talkback ]" SRC="../gx/navbar/talkback.jpg" WIDTH="121" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../lg_faq.html"><IMG ALT="[ FAQ ]" SRC="./../gx/navbar/faq.jpg"WIDTH="62" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="collinge.html"><IMG ALT="[ Next ]" SRC="../gx/navbar/next.jpg" WIDTH="15" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><IMG ALT="" SRC="../gx/navbar/right.jpg" WIDTH="15" HEIGHT="45" ALIGN="bottom">
|
|
<!-- *** END navbar *** -->
|
|
|
|
<!--endcut ============================================================-->
|
|
|
|
<TABLE BORDER><TR><TD WIDTH="200">
|
|
<A HREF="http://www.linuxgazette.com/">
|
|
<IMG ALT="LINUX GAZETTE" SRC="../gx/2002/lglogo_200x41.png"
|
|
WIDTH="200" HEIGHT="41" border="0"></A>
|
|
<BR CLEAR="all">
|
|
<SMALL>...<I>making Linux just a little more fun!</I></SMALL>
|
|
</TD><TD WIDTH="380">
|
|
|
|
|
|
<CENTER>
|
|
<BIG><BIG><STRONG><FONT COLOR="maroon">I Broke the Console Barrier</FONT></STRONG></BIG></BIG>
|
|
<BR>
|
|
<STRONG>By <A HREF="../authors/bint.html">Stephen Bint</A></STRONG>
|
|
</CENTER>
|
|
|
|
</TD></TR>
|
|
</TABLE>
|
|
<P>
|
|
|
|
<!-- END header -->
|
|
|
|
|
|
|
|
<P><B><CENTER><U>Colour text, keyboard and mouse under DOS and the Linux
|
|
console</U></CENTER></B>
|
|
|
|
<P>When I began using Linux I noticed that most Linux text editors are
|
|
rubbish, having little or no mouse support, no shift-selection and no
|
|
menus or file open dialogs. So I thought I could make a contribution, by
|
|
writing an editor which had all the features we have come to associate
|
|
with DOS editors, for the Linux console. Why shouldn't the better OS
|
|
have editors which are at least as good?
|
|
|
|
<P>So I searched for a library that would give me a colour text-mode
|
|
interface to both platforms and I found Slang and curses. Neither were
|
|
satisfactory. In order to provide an interface to a huge list of
|
|
platforms, many features possible at the Linux console were disabled.
|
|
Also, they were so big there was no prospect of customising them for my
|
|
own needs and bundling the modified versions with my own sources. Out of
|
|
frustration I set out to write my own.
|
|
|
|
<P>I set myself the target of producing an interface with the widest
|
|
possible range of ctrl- and alt- key combinations, a function to report
|
|
the state of shift, ctrl and alt keys, full mouse reporting (including
|
|
movement) and direct access to a screen array of EGA-style
|
|
character-colour pairs. I hoped to produce something small and simple
|
|
enough for programmers to bundle with their own source, so they could
|
|
modify it if they wish and distribute with confidence.
|
|
|
|
<H2>The Mouse</H2>
|
|
|
|
<P>Programming for the mouse was relatively easy. Under DOS, I just used
|
|
int86 and Ralf Brown's Interrupt List. Under Linux, I struggled for a
|
|
while and eventually mastered the gpm mouse driver, which has pretty
|
|
good docs and demo programs.
|
|
|
|
|
|
<H2>The Screen</H2>
|
|
|
|
<P>Finding out how to output colour text to the Linux screen was more of
|
|
a struggle. I was saved by an article at Linux Gazette called
|
|
<A HREF="../issue65/padala.html">"So You Like Color!!!"</A>.
|
|
I was shocked at what it said.
|
|
|
|
<P>Unlike DOS, under which characters and colours are written directly
|
|
to video memory as byte pairs, the Linux screen is refreshed by using
|
|
fwrite to write to stdout! Instead of a colour being written with each
|
|
character, the output colour must be changed whenever a character is a
|
|
different colour to the one written before it. Changing the output
|
|
colour involves writing an 11-byte string to stdout.
|
|
|
|
<P>Consequently, screen refresh is very slow under Linux. I did what I
|
|
could to speed it up. I keep a duplicate screen buffer which is updated
|
|
as the screen is refreshed. Comparing this to the screen buffer, I
|
|
refresh only parts of the screen which have changed. Even so, screen
|
|
refresh takes twenty times as long under Linux as it does under DOS.
|
|
|
|
<P>It is possible to access video memory in recent versions of Linux, by
|
|
opening /dev/vcsa as a file. (see man vcs for details) There are two
|
|
reasons not to do this. One is that only programs run by the superuser
|
|
are allowed to do it. The second is that only the US ASCII character set
|
|
is supported. At least with fwrite, the local character set is
|
|
respected, which is important because Linux is an international thing,
|
|
from its friendly welcome screen to its big, warm heart.
|
|
|
|
<P>I found out how to show, hide and position the text cursor by
|
|
examining the Slang sources and by using an excellent program bundled
|
|
with Slang, called untic. Untic reads the terminfo database and
|
|
translates it into human-readable form. (The terminfo database contains
|
|
the command strings to write to stdout to perform control operations on
|
|
any terminal.)
|
|
|
|
<P>There was one little niggle. Under Linux, box-drawing characters are
|
|
not part of the default character set. ASCII values which produce boxes
|
|
under DOS produce funny foreign letters under Linux unless you send a
|
|
string to stdout to switch to the alt character set. Switching to that
|
|
charset permanently was not an option. I wanted the library to be
|
|
international like Linux, supporting international character sets, so
|
|
what to do?
|
|
|
|
<P>I decided to use the high bit of the colour byte as a box bit.
|
|
Programmers wishing to draw boxes would have to set the box bit in the
|
|
colour for any characters they wish to be shown as box characters. This
|
|
meant that blinking text would not be available, because the high bit is
|
|
otherwise used for that, but I was happy. I never liked blinking text
|
|
anyway.
|
|
|
|
|
|
<H2>The Keyboard</H2>
|
|
|
|
<P>Interpreting keyboard events on either platform is a giant screaming
|
|
nightmare on stilts. Under DOS, the BIOS scancodes are so illogically
|
|
allocated, they might as well be random numbers. Under Linux, the
|
|
terminal has to be specially prepared and then, the function keys
|
|
generate strings of bytes which need to be converted to scancodes
|
|
through a lookup table.
|
|
|
|
<P>It was almost inconceivable to convert Linux key events to DOS, or
|
|
vice versa. I decided instead, to produce a pure key function, which
|
|
would report a key value which is unaffected by control or alt, but will
|
|
be shifted if shift is pressed. Programmers wishing to use a ctrl- or
|
|
alt-key combination for a hot key could examine the keyboard status word
|
|
seperately.
|
|
|
|
|
|
<H3>The DOS Keyboard</H3>
|
|
|
|
<P>You might hope that a two-byte BIOS scancode would use the high byte
|
|
as a key ID which never varies and the low byte for an ASCII value which
|
|
depends on whether shift, control or alt are pressed. Unfortunately,
|
|
because of a need to maintain compatability with the old XT keyboard,
|
|
the high byte varies as much much as the low byte. What is worse,
|
|
different keys react differently to control and alt. To avoid a
|
|
time-consuming switch block, I produced a tangle of "if" tests to sieve
|
|
the identities out of ctrl'd and alt'd scancodes.
|
|
|
|
<P>Then I found that holding down shift reverses the sense of the
|
|
numlock under DOS, but not Linux. I had to complicate my key purifier
|
|
still further to undo that stupidity, so numlock means numbers, no
|
|
matter what. So DOS was conquered and I faced the horror of the Linux
|
|
keyboard.
|
|
|
|
|
|
<H3>The Linux Keyboard</H3>
|
|
|
|
<P>In its default state, the Linux keyboard is far from suitable for an
|
|
interactive program. The <TT>fgetc()</TT> function does not return until
|
|
return is pressed, then it returns a whole string at once, so moving the
|
|
cursor with arrows can't work. It echoes characters to the screen and
|
|
ctrl-z, ctrl-q and ctrl-s all generate interrupts. It's a nightmare.
|
|
|
|
<P>I had hoped I could avoid using <TT>fgetc()</TT> and slip the
|
|
keyboard into raw mode (pure scancodes), but the gpm mouse driver
|
|
offered me no choice. It provides a single function to read events from
|
|
both keyboard and mouse, and the keyboard part uses
|
|
<TT>fgetc(stdin)</TT>. There is a mouse-only polling function, but I
|
|
couldn't make it work.
|
|
|
|
<P>I am glad of that now, because I have realised since that
|
|
<TT>fgetc()</TT> receives high-level keycodes which are likely to be the
|
|
same on foreign keyboards, where the layout and probably the scancodes
|
|
would be different. I resigned myself to translating strings of bytes
|
|
into scancodes as a necessity and it turned out to be easier than
|
|
dealing with BIOS scancodes under DOS had been.
|
|
|
|
<P>I found out how to set up the terminal by examining the Slang
|
|
sources. You use a function called <TT>tcsetattr()</TT> to set flags and
|
|
values in a terminal control structure. So I fixed the keyboard to
|
|
return characters immediately without echo and to treat ctrl-z, ctrl-q
|
|
and ctrl-s as ordinary keys.
|
|
|
|
<P>I still had no <TT>kbhit()</TT> function, nor any way to read the
|
|
shift state (whether ctrl, alt or shift are pressed). Google turned up
|
|
an article at Linux Gazette called
|
|
<A HREF="../issue76/marinov.html">"Taming the Linux Keyboard"</A>, which
|
|
gave me both those functions, full source code.
|
|
|
|
|
|
<H2>The Final Trial</H2>
|
|
|
|
<P>Still one bugbear remained. It may seem trivial to you but it was
|
|
everything to me. It seemed insurmountable and I don't mind admitting,
|
|
it nearly broke me.
|
|
|
|
<P>You know how on DOS editors you can select text by holding down shift
|
|
while using cursor-movement keys, including page up and page down? Well
|
|
under Linux, shift-PageUp and shift-PageDown are reserved for a
|
|
pointless function called scrollback. That means applications receive
|
|
nothing from <TT>fgetc()</TT> when shift-PageUp/Down are pressed. The
|
|
kernel spirits these keys away and your program never sees them.
|
|
|
|
<P>But that is not the worst of it by a long chalk. After weeks of
|
|
brain-busting work I found out at the final furlong, that if a user
|
|
tries to select text with shift-PageUp, half my lovely colour text
|
|
screen disappears - scrolled back!
|
|
|
|
<P>There was no way I could release my library now. I felt like I had
|
|
read a thousand-page novel and found the last page missing. I went round
|
|
and round in circles of man pages and info files and searched the net to
|
|
no avail. Then I noticed that the <TT>shift_state()</TT> function I got
|
|
from that article I mentioned earlier, used a function called
|
|
<TT><b>ioctl()</b></TT> to work its magic.
|
|
|
|
<P>I used <TT><b>"apropos ioctl"</b></TT> to search the man pages and
|
|
found one called <TT><b>"console_ioctls"</b></TT>. There I discovered
|
|
that <TT><b>ioctl()</b></TT> is the Linux equivalent of a DOS interrupt
|
|
call. The same page gave a full list of low-level system calls and a
|
|
warning from a kernel programmer, never to use these because they are
|
|
<U>not guaranteed</U> and are <U>subject to change</U> in future
|
|
versions of the kernel.
|
|
|
|
<P><EM>But we all know we can ignore kernel programmers when they say
|
|
things like that. They are just denying responsibility, like when Scotty
|
|
tells Captain Kirk it's going to take twice as long as it really
|
|
will.</EM>
|
|
|
|
<P>In the list I found one to change the functions associated with keys
|
|
- including PageUp and PageDown. It involved filling a struct with three
|
|
integers, to indicate which table, which key and which command to
|
|
assign. The problem was, there were no docs telling me what these
|
|
numbers should be, to disable scrollback for shift-PageUp.
|
|
|
|
<P>Further research turned up the kbd package, which contains great docs
|
|
and a bunch of utilities for changing the key mapping. You can dump the
|
|
current mapping to stdout by running dumpkeys. Here is an excerpt from
|
|
my dumpkeys output. Notice that it only gives me one of the three
|
|
numbers I need - the keycode.
|
|
|
|
<PRE>
|
|
keycode 103 = Up
|
|
alt keycode 103 = KeyboardSignal
|
|
<STRONG>keycode 104 = Prior
|
|
shift keycode 104 = Scroll_Backward</STRONG>
|
|
keycode 105 = Left
|
|
alt keycode 105 = Decr_Console
|
|
keycode 106 = Right
|
|
alt keycode 106 = Incr_Console
|
|
keycode 107 = Select
|
|
keycode 108 = Down
|
|
<STRONG>keycode 109 = Next
|
|
shift keycode 109 = Scroll_Forward</STRONG>
|
|
keycode 110 = Insert
|
|
</PRE>
|
|
|
|
<P>If you redirect the output into a text file, you can edit it and pass
|
|
it to loadkeys to alter the mapping. Experiments revealed that you can
|
|
delete most of the file - only leaving the keys you want to change. So I
|
|
reduced it to two lines:
|
|
|
|
<PRE>
|
|
<STRONG>shift keycode 104 = Scroll_Backward
|
|
shift keycode 109 = Scroll_Forward</STRONG>
|
|
</PRE>
|
|
|
|
<P>and changed the current functions to the ones for those keys without
|
|
shift pressed:
|
|
|
|
<PRE><STRONG>shift keycode 104 = Prior
|
|
shift keycode 109 = Next</STRONG>
|
|
</PRE>
|
|
|
|
<P>I called the file kmap and ran "loadkeys kmap". Then I tried my test
|
|
program and found that scrollback had been disabled - exactly the result
|
|
I was looking for. I knew now that it was possible. A peek at the source
|
|
for loadkeys revealed that it used the ioctl I had found, to change the
|
|
key functions, but I still did not know what numbers to use.
|
|
|
|
<P>I had no choice but to use cunning. I found out that loadkeys has a
|
|
-m option, to produce a source file, which contains tables of 256
|
|
values. I ran "loadkeys -m kmap" and found it produced one table with
|
|
254 null values and two non-null. Counting elements I found that the
|
|
non-null elements were numbered 104 and 109 - the key codes in my kmap
|
|
file. The values in the table had to be the values of the "Prior" and
|
|
"Next" commands.
|
|
|
|
<P>I also saw that this table had a number. I tried changing "shift" to
|
|
"control" in one of the lines in kmap and got two tables, one for shift
|
|
and one for control. In both cases the shift table was table number 1.
|
|
Along with the actual values in the table, I had my three numbers.
|
|
|
|
<P>To disable scrollback and scroll forward and make shift-PageUp/Down
|
|
into ordinary keys, you must save the existing values, then change them
|
|
and install an exit routine to restore them to normal function
|
|
afterwards.
|
|
|
|
<P>If you want to disable any key, such as the console switching keys
|
|
for example, you will need to mess about like I did with "loadkeys -m"
|
|
to find the numbers you are looking for.
|
|
|
|
<P><b>This function changes a key's action and saves the old one in an
|
|
integer you pass in by reference (written for gcc):</b>
|
|
|
|
<P><A HREF=misc/bint/kbe.c.txt>(text version of all listings)</A>
|
|
|
|
<PRE>
|
|
#include <sys/ioctl.h>
|
|
#include <linux/kd.h>
|
|
#include <linux/keyboard.h>
|
|
#include <stdio.h>
|
|
|
|
int set_kb_entry( unsigned short table, unsigned short keycode,
|
|
unsigned short value, unsigned short *oldvalue ) {
|
|
|
|
struct kbentry ke;
|
|
|
|
ke.kb_table = table;
|
|
ke.kb_index = keycode;
|
|
|
|
/* Get old value, return error if table or keycode are duff */
|
|
if( ioctl( fileno(stdin), KDGKBENT, &ke ) )
|
|
return -1;
|
|
|
|
/* Unless oldvalue ptr is NULL, save old value to restore later */
|
|
if( oldvalue ) *oldvalue = ke.kb_value;
|
|
|
|
/* The new action for this key */
|
|
ke.kb_value = value;
|
|
|
|
/* Do the business, return error if value is duff */
|
|
if( ioctl( fileno(stdin), KDSKBENT, &ke ) )
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
</PRE>
|
|
|
|
<P><b>To use the above function to disable scrollback and restore it on
|
|
exit:</b>
|
|
|
|
<PRE>
|
|
#include <stdlib.h>
|
|
|
|
/* Old key action values will be stored in these */
|
|
unsigned short scroll_forward = 0;
|
|
unsigned short scroll_backward = 0;
|
|
|
|
/* The magic numbers gleaned from dumpkeys and loadkeys -m */
|
|
#define SHIFT_TABLE 1
|
|
#define PAGE_UP_KEYCODE 104
|
|
#define PAGE_DOWN_KEYCODE 109
|
|
#define PAGE_UP_ACTION 0x0118 /* Prior */
|
|
#define PAGE_DOWN_ACTION 0x0119 /* Next */
|
|
|
|
|
|
/* Restore default funcs for shift-PageUp and shift-PageDown */
|
|
static void restore_scrollback() {
|
|
|
|
if( scroll_backward )
|
|
set_kb_entry( SHIFT_TABLE, PAGE_UP_KEYCODE,
|
|
scroll_backward, 0 );
|
|
|
|
if( scroll_forward )
|
|
set_kb_entry( SHIFT_TABLE, PAGE_DOWN_KEYCODE,
|
|
scroll_forward, 0 );
|
|
}
|
|
|
|
|
|
/* Liberate shift-PageUp and shift-PageDown for normal use */
|
|
int disable_scrollback() {
|
|
|
|
if( set_kb_entry( SHIFT_TABLE, PAGE_UP_KEYCODE,
|
|
PAGE_UP_ACTION, &scroll_backward ) )
|
|
return -1;
|
|
|
|
if( set_kb_entry( SHIFT_TABLE, PAGE_DOWN_KEYCODE,
|
|
PAGE_DOWN_ACTION, &scroll_forward ) )
|
|
return -1;
|
|
|
|
atexit( restore_scrollback );
|
|
|
|
return 0;
|
|
}
|
|
</PRE>
|
|
|
|
|
|
<H2>Return of the Jedi</H2>
|
|
|
|
<P>So I emerged from the dark underworld of the Linux console, prizes in
|
|
hand, triumphant. I have made it possible for programmers to write
|
|
console apps which behave exactly the same under DOS and Linux and (I
|
|
think) secured my place in legend.
|
|
|
|
<P>And you know what? I never did write that text editor. I can't
|
|
because I am homeless and I was lucky to get access to a computer long
|
|
enough to do this little thing. Perhaps that is where you come in.
|
|
|
|
<P>Linux is a virgin territory, about to be colonized by the people of
|
|
India and Africa. They can't afford flash computers that can run X, so
|
|
they need console apps. Now even those of you who don't have Linux
|
|
installed can help them.
|
|
|
|
<P>Linux needs pioneers to carve out the infrastructure before the first
|
|
big wave of settlers can move in. Those settlers will need configuration
|
|
dialogs for common apps like Apache and for common filters like grep.
|
|
They will need a good text editor, with a right-click cut-copy-paste
|
|
menu.
|
|
|
|
<P>Programmers who mean to produce these tools will need a widget
|
|
library and especially, a file Open/Save dialog. They would benefit from
|
|
a well-written string array class with cut-copy-paste functions,
|
|
provided separately to be used in various, competing text editors.
|
|
|
|
<P>The perfect editor wouldn't have many features, but would have a
|
|
simple facility for adding functions to its menus. It would be set up so
|
|
that any fool could write a C++ function which takes a pointer to an
|
|
editor as an argument and add that function to the editor's menu, just
|
|
by adding a single line to <TT>main()</TT>. Programmers could swap C++
|
|
editor functions with eachother and we would be on course to the
|
|
ultimate editor.
|
|
|
|
<P>Will you be a pioneer? If no-one bothers, I fear that Linux may fall
|
|
and we may all end up the helpless playthings of the evil Darth Gates.
|
|
So I am hoping you will pick up my fallen standard. You may be our last,
|
|
our only hope. Good luck.
|
|
|
|
<P>May the Source be with you.
|
|
<STRONG>
|
|
<P><A HREF=http://members.lycos.co.uk/ctio/ctio.zip>ctio.zip</A> (41.7kb)
|
|
<BR><A HREF=http://members.lycos.co.uk/ctio/ctio.tar>ctio.tar</A> (150kb)
|
|
</STRONG>
|
|
|
|
<H2>Credits</H2>
|
|
|
|
<P><STRONG><A HREF=http://www.s-lang.org/>
|
|
Slang</A></STRONG>, by John E. Davis.
|
|
Slang is easy to rob because it is well-written. I learned how to init
|
|
the keyboard and got most of the command strings for the screen from the
|
|
Slang sources. I got other command strings by using the untic program
|
|
that comes with it. But the best thing about Slang is what enables
|
|
Midnight Commander to run in a telnet window. Anyone who has ever had to
|
|
fix a web server remotely will know, it's a beautiful thing.
|
|
|
|
<P><STRONG><A HREF=../issue65/padala.html>
|
|
So You Like Color !!!</A></STRONG> By Pradeep Padala (LG #65).
|
|
This article got me started on the Linux console screen.
|
|
|
|
<P><STRONG><A HREF=../issue76/marinov.html>
|
|
Taming The Linux Keyboard</A></STRONG> By Petar Marinov (LG #76).
|
|
My <TT>shift_status()</TT> and <TT>key_awaits()</TT> functions are
|
|
modified versions of <TT>shift_state()</TT> and <TT>kbhit()</TT> given
|
|
away with this article.
|
|
|
|
<P><STRONG><A HREF=http://www-2.cs.cmu.edu/afs/cs/user/ralf/pub/WWW/files.html>
|
|
Ralf Brown</A></STRONG>, Patron Saint of DOS programmers
|
|
|
|
|
|
|
|
|
|
<!-- *** BEGIN author bio *** -->
|
|
<P>
|
|
<P>
|
|
<!-- *** BEGIN bio *** -->
|
|
<P>
|
|
<img ALIGN="LEFT" ALT="[BIO]" SRC="../gx/2002/note.png">
|
|
<em>
|
|
Stephen is a homeless Englishman who lives in a tent in the woods. He eats out
|
|
of bins and smokes cigarette butts he finds on the road. Though he once worked
|
|
for a short time as a C programmer, he prefers to describe himself as a "keen
|
|
amateur".
|
|
</em>
|
|
<br CLEAR="all">
|
|
<!-- *** END bio *** -->
|
|
|
|
<!-- *** END author bio *** -->
|
|
|
|
|
|
<!-- *** BEGIN copyright *** -->
|
|
<hr>
|
|
<CENTER><SMALL><STRONG>
|
|
Copyright © 2003, Stephen Bint.
|
|
Copying license <A HREF="../copying.html">http://www.linuxgazette.com/copying.html</A><BR>
|
|
Published in Issue 86 of <i>Linux Gazette</i>, January 2003
|
|
</STRONG></SMALL></CENTER>
|
|
<!-- *** END copyright *** -->
|
|
<HR>
|
|
|
|
<!--startcut ==========================================================-->
|
|
<CENTER>
|
|
<!-- *** BEGIN navbar *** -->
|
|
<IMG ALT="" SRC="../gx/navbar/left.jpg" WIDTH="14" HEIGHT="45" BORDER="0" ALIGN="bottom"><A HREF="artime.html"><IMG ALT="[ Prev ]" SRC="../gx/navbar/prev.jpg" WIDTH="16" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="index.html"><IMG ALT="[ Table of Contents ]" SRC="../gx/navbar/toc.jpg" WIDTH="220" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../index.html"><IMG ALT="[ Front Page ]" SRC="../gx/navbar/frontpage.jpg" WIDTH="137" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="http://www.linuxgazette.com/cgi-bin/talkback/all.py?site=LG&article=http://www.linuxgazette.com/issue86/bint.html"><IMG ALT="[ Talkback ]" SRC="../gx/navbar/talkback.jpg" WIDTH="121" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><A HREF="../lg_faq.html"><IMG ALT="[ FAQ ]" SRC="./../gx/navbar/faq.jpg"WIDTH="62" HEIGHT="45" BORDER="0" ALIGN="bottom"></A><A HREF="collinge.html"><IMG ALT="[ Next ]" SRC="../gx/navbar/next.jpg" WIDTH="15" HEIGHT="45" BORDER="0" ALIGN="bottom" ></A><IMG ALT="" SRC="../gx/navbar/right.jpg" WIDTH="15" HEIGHT="45" ALIGN="bottom">
|
|
<!-- *** END navbar *** -->
|
|
</CENTER>
|
|
</BODY></HTML>
|
|
<!--endcut ============================================================-->
|