Keyboard Input

FreeBASIC

Keyboard Input
 
Basics

Using FB's built-in functionality, there are four ways of getting keyboard input:

    • Inkey() returns a string containing an ASCII char corresponding to the key pressed by the user, or a 2-byte FB extended keycode for some special keys, such as the Arrow keys or Page Up/Down. It works pretty much like it did in QB.
    • Getkey() returns the same information as inkey(), but in form of an integer instead of a string. inkey() and getkey() belong together: They use the same code and they are located in the same modules.
    • Multikey() takes an FB scancode (SC_*) and checks whether that key is pressed at this moment.
    • Screenevent() returns key presses in form of EVENT_KEY_PRESS events (and others for key release or repeat). It returns the FB scancode in the Event.Scancode Field, and the ASCII char value or 0 in the EVENT.ascii field. EVENT.ascii does not use FB extended keycodes; the EVENT.scancode field can be checked instead in order to handle extended keys.
"scancode" refers to the SC_* #defines which are more or less matching the DOS keyboard scancodes. The values are not made up, they themselves correspond to certain ASCII chars, for example: SC_HOME = asc( "G" ) = &h47.; They're also the same values that you get under DOS/DJGPP or from the Linux kernel as part of extended key code sequences. Besides their use in multikey() or screenevent(), scancodes are used in various places internally, for example when translating between different kinds of key codes, as an easy-to-use and portable representation of keycodes.

"key" refers to an ASCII char, or a 2-byte extended keycode string for other keys as returned by inkey(). The rtlib has several KEY_* #defines for the available 2-byte extended keycodes, in form of integers. These are used internally and also match the values returned by getkey().

FB's 2-byte extended keycodes consist of a &hFF; byte followed by a byte containing the SC_* scancode value corresponding to the keypress. Checking for SC_HOME returned by inkey() could look like:
if( inkey( ) = chr( 255 ) + "G" ) then ...
Checking for SC_HOME returned by getkey():
if( getkey() = &h47FF; ) then ...
if( getkey() = ((SC_HOME shl 8) or &hFF;) ) then ...
inkey(), getkey() and multikey() use wrapper functions that call ...
    • the console-mode versions fb_ConsoleInkey(), fb_ConsoleGetkey(), fb_ConsoleMultikey() by default,
    • or the gfxlib versions fb_GfxInkey(), fb_GfxGetkey(), fb_GfxMultikey() if a graphics SCREEN is active,
by using function pointer hooks.

rtlib

The rtlib has separate console-mode implementations of the above functions, for each platform:

  • DOS
fb_ConsoleInkey() and fb_ConsoleGetkey() use DJGPP's getch() function to retrieve input characters anytime they're called. getch() returns ASCII chars, but also 2-byte sequences for special keys, which are easy to handle because they match the SC_* scancodes.
fb_ConsoleMultikey() installs an interrupt handler that uses port I/O to read keyboard information and updates a key state table which is checked by multikey().
  • Win32
fb_ConsoleInkey() and fb_ConsoleGetkey() (indirectly) use the Win32 API functions PeekConsoleInput() and ReadConsoleInput() to get queued key press/release events whenever needed. All currently pending events are handled during a call, and after very complex internal translation involving MapVirtualKey(), the keys are put into a buffer, from where fb_ConsoleInkey() and fb_ConsoleGetkey() read the keys they return.

SetConsoleCtrlHandler() is used to listen for console close/system shutdown events to provide SC_CLOSE events for console-mode (the win32 port of the rtlib might be the only one going this far).

fb_ConsoleMultikey() uses a FindWindow()/GetForegroundWindow() hack to determine whether the console window is focused, and if yes, simply uses GetAsyncKeyState().
  • Linux, *BSD
The Unix port of the rtlib runs a console keyboard handler (and a console mouse handler) in a background thread, in order to provide input for multikey() (and getmouse()).

fb_ConsoleInkey() and fb_ConsoleGetkey() read input bytes through the __fb_con.keyboard_getch() hook. By default, __fb_con.keyboard_getch() points to a simple function that just uses fgetc() on /dev/tty (indirectly; the Unix rtlib initialization code opens the handle, and changes I/O settings etc., not only for the purpose of keyboard input, but mostly).

The terminal returns ASCII chars for simple key presses, and special escape sequences for extended keys. On the first call, various termcap lookups (via tgetstr()) are done to determine these terminal-specific escape sequences for certain key press events, and they are put into a lookup tree to allow easy & fast translation to the corresponding FB extended keycodes. By doing the termcap query the Unix rtlib can support all the different terminals (e.g. xterm vs. linux) quite well, although there still are some keys not working here and there.

Only one "event" (ASCII char or escape sequence) is read at a time, the resulting key is added to a key buffer, from where fb_ConsoleInkey() and fb_ConsoleGetkey() can read it.

fb_ConsoleMultikey() is currently implemented for the Linux port only, not under *BSD though. In console-input mode (used under 'console'/'linux' terminals), it dup()licates the rtlib's /dev/tty handle, and switches it over into medium raw mode. Then it overrides the background thread's __fb_con.keyboard_handler() hook to a function that read()s kernel key codes from the duplicated /dev/tty handle.
Called from the background thread, it reads a fixed amount of input at once, whenever it arrives. After somewhat complex translation, a key state table is updated to reflect the state of pressed/released keys, to be checked by fb_ConsoleMultikey() at any time, and the keys are added to a key buffer from where an overridden __fb_con.keyboard_getch() reads them, whenever called by fb_ConsoleInkey() or fb_ConsoleGetkey() [why is this done?]. Furthermore, the keys are sent to the Linux fbdev gfxlib2 driver, if it's active.
In X11 mode (used under 'xterm' terminal), fb_ConsoleMultikey() sets the background thread's __fb_con.keyboard_handler() to a function that checks whether the xterm has input focus (XGetInputFocus()) and if yes, simply uses XQueryKeymap() to update the key state table for fb_ConsoleMultikey().

gfxlib2

In the gfxlib, fb_GfxInkey() and fb_GfxGetkey() use one key buffer (same code on all platforms), to which the different/platform-specific gfx drivers post keys to. Similar to that, there is a single key state table for fb_GfxMultikey(), and it is also updated by the gfx drivers. Whether or not the gfx drivers actually do post keys or update key states is up to them though.

  • DOS
The DOS gfxlib2 port (for all DOS gfx drivers) sets a hook/callback that's called by the same keyboard interrupt handler used by the DOS fb_ConsoleMultikey().
  • Win32 driver
The gfx window thread listens to WM_KEYDOWN, WM_CHAR and WM_CLOSE, translates the keys, and then updates the key state table, posts them to the fb_GfxInkey()/fb_GfxGetkey() buffer, and fills in & posts the corresponding EVENT for screenevent().
  • X11 driver
The gfx window thread listens to KeyPress and other XEvent's, translates the keys, then posts them etc., just like the Win32 driver.
  • Linux fbdev driver
As mentioned above, the fbdev driver gets its input from the same keyboard handler code that's used by the Linux fb_ConsoleMultikey().