Re: Esc key

Search this archive.

From: Russell Marks (russell.marks@dtn.ntl.com)
Date: Tue 14 Mar 2000 - 15:35:41 IST


> > > > When using vga_getch() and vga_getkey(), how do you avoid a value of "27"
> > > > applying to more than just the "Esc" key? I know the directional arrows
> > > > are "escaped", and so pressing the up arrow sends (at first) the same
> > > > signal as the Esc key. This is the same problem as you get with ncurses.
> > > > 
> > > > So, what's the answer? Can this be done without raw keyboard mode?
> > [...]
> > > Use vga_getkey() to read the keyboard, if you see an ESC read it again
> > > immediately (without waiting). If you get 0 then no other key was
> > > pressed and you have an ESC key press, if you get anything else then
> > > treat it as an escape sequence.
> > 
> > In the spirit of "show me the code" :-), there's a working (I hope)
> > example of how to do this below (after all my verbiage).
> 
> I didn't think my explanation needed any futher clarification which is
> why I didn't include any code in this case.

You're right, it didn't, but:

- parsing all the keys is a pain. I had working code to do it, so I
  thought I'd post it. I meant the comment you quoted above as `yes,
  and here's how to do it', rather than `you should have included some
  code'.

- I thought it was worth pointing out why ncurses takes the approach
  it does, and why it just so happens you can get away without waiting
  when using svgalib (as you know you're on a console and
  (fortunately) Linux shoves the multi-byte keypresses at you in one
  atomic chunk).

>                                             I do "show you the code"
> when I think its usefull or necessary (check the logs for my posting
> on the anti aliased line code and pretty picture).

Yes, I remember, very pretty. :-)

> > BTW, the reason this read-without-waiting works is because Linux dumps
> > the whole string generated by the key into your input at once (AFAIK),
> > something you can't depend on over network or serial connections. So
[...]
> > > I don't know about this ncurses problem, but this method also works with
> > > normal terminals connected to a unix box, you just need to be sure to
> > 
> > Ummm, for terminals you really ought to wait a bit, rather than
> > reading the chars after Esc immediately. If it happens to work without
> > waiting, it's only luck and/or buffering. :-) (I know you mentioned
> > disabling buffering, but that won't affect a UART - using a multi-byte
> > buffering UART like a 16550A might be all that's saving you here.)
> > ncurses may wait a stupidly long time (IMHO), but you really should
> > wait at least a *short* time. (But as I say, there's no need to wait
> > when using svgalib, so I'm veering off-topic.)
> 
> I didn't want to go completely off topic and explain the exact line
> setting etc but basically you set the line disaplin to have a time out
> of about 3 char transmition periods and you short circuit this by a
> 'minimum number of chars to wait for' to 2. You then issue a read()
> requesting a short string (minimum of 2 but could be several chars) and
> the read will return a single char for normal keys including ESC and
> multiple chars in the case where a function key has been pressed.
> read() returns the count of chars read into the buffer. In effect the
> programmer sees an ESC+ch without a pause between the two but will still
> be using non-blocking non-buffered IO.

This is a neat idea, but it's broken in the general case because you
can never be sure of controlling all the buffering, and here's an
example of how the assumption that you're doing so can bite.
Ironically enough, this is an example of buffering breaking this
scheme, rather than saving it as it usually would. :-)

First, some background. I looked at how Linux works on 16550As a while
back - at 1200 baud or less it does things a byte at a time (a hack
for mice I suspect), but at other rates it buffers 8 bytes of input
(more precisely, asks to be interrupted when 8 bytes have been
received, leaving the other 8 as plenty of insurance against overrun).
You can't do anything to change this without modifying the kernel;
it's hardcoded. [1]

Now, say I have a fairly heavily-loaded machine I'm using (among other
things) as a terminal to my main (16550A-equipped) Linux box,
connected via a plain old serial connection. Then say I've typed
`abcd' at the terminal program, then pressed cursor-up twice, then
typed `wibble' - without this loaded machine managing to send any of
it (which is a bit crap, but maybe it's a Windows box reading a floppy
[2]). Then, it suddenly gets the chance to do so, and sends it all as
fast as its little legs can carry it. :-) Hey presto, you get
`abcd^[[A^[' in the buffer, which the kernel dutifully gives to your
program. The buffering you specified is irrelevant; the kernel (and
hardware) decides what's happening at this level, and you can't change
it. Shortly after getting those 8 bytes (but much later than your 3
char transmission periods, I might add), you finally get the
`[Awibble'. But - you didn't wait for it. So you don't see the second
cursor-up. Whoops.

I suspect there are *lots* of ways this sort of thing can happen (I
bet telnet over the net is a great example, but buffering might save
you there). This was just an example that I thought stood a decent
chance of being convincing, since I could point to the kernel source
and everything. :-)

[1] From drivers/char/serial.c in 2.2.13:

> static struct serial_uart_config uart_config[] = {
> 	{ "unknown", 1, 0 }, 
> 	{ "8250", 1, 0 }, 
> 	{ "16450", 1, 0 }, 
> 	{ "16550", 1, 0 }, 
> 	{ "16550A", 16, UART_CLEAR_FIFO | UART_USE_FIFO }, 
[...]
> 	/* Set up FIFO's */
> 	if (uart_config[info->state->type].flags & UART_USE_FIFO) {
> 		if ((info->state->baud_base / quot) < 2400)
> 			fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1;
> 		else
> 			fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_8;
> 	}

(`info->state->baud_base / quot' is the bps it's running at.)

[2] Reading/writing a floppy brings anything else running on W9x to a
grinding halt, pretty much - I'm not exaggerating things here. Fscking
pathetic, really...


> I do appriciate that its possible to execute several million machine
> instructions between two consequtive received chars on a uart - really
> I do, honest :-)

Indeed, but having to wait for the time taken for 8 chars in order to
be sure means that waiting for the time taken for 3 isn't good enough.
And some UARTs come with bigger buffers, and even if you can reliably
detect the UART type on every OS your program is going to be run on,
that doesn't cope with links not running over a serial connection, and
is prone to not knowing about new UARTs, or breaking if someone
decides to (say) up the kernel's `trigger' position to 14 bytes (as is
possible on a 16550A).

This sort of thing is why ncurses waits quite a long time after Esc,
which brings us full circle. Ok then, it might be nice if someone
added a Linux-specific hack to ncurses so that it doesn't wait on
consoles, you might think - but even then, you can have a serial
console which AFAIK is not really distinguishable from the usual type,
because it uses the same device major/minor with only the kernel
knowing that there's anything special about it. (That is, after all,
the whole point of having the option in the first place.) Or, in other
words, here we go again. :-)


ObSvgalib: But, luckily, none of this is a problem with svgalib. :-)

-Rus.


This archive was generated by hypermail 2.1.4 : Wed 21 Jan 2004 - 22:10:23 IST