Monday, September 11, 2017

Keyboard - Keystations

Following my general keyboard introduction which focused on the non-English layout issues of the console I'd like to present on easy / lazy way of detecting the numerical code of each keyboard key according to the Solaris keyboard device driver known as the /dev/kbd.

I call it lazy because it's not based on highly sophisticated techniques or knowledge such as the DDI (device driver interface), STREAMS Unix architecture, dtrace, termio, SMF and so on. A simple shell script will do. The script can be run under a regular non-privileged account but in order to correctly detect the numerical codes of the console keyboard it's necessary to run it directly on the console to avoid interference from other keyboard filtering layers such as X-Windows, Compiz, Gnome and so on.

By the way, regarding the numerical code of a keyboard key, the precise nomenclature adopted by Solaris defines it as a keystation. So the task is to determine the keystations reported by /dev/kbd for each key on your Frankenstein keyboard.

The strategy consists on making each key report its keystation instead of a its current behavior whatever could that be (right or wrong, character or shift or toggle) before the script is run. Once the keystation of each key is know for sure, than it will be possible to fix whatever is necessary to properly configure the keyboard layout.

NOTE
Some keyboards have multimedia keys for volume and playback control as well as some suspend / hibernation keys that aren't reported by /dev/kbd. As the technique I present does not circumvent /dev/kbd, such keys can't be reached by this solution; in other words, unfortunately, they are permanently dead on this context. Not that those keys should necessarily work on a text-mode console, but every available key is a potential additional resource at hand.
To start, set your console keyboard layout to what you believe would be the closest match to your actual device. This somehow internally maximizes the chances of getting better results. For instance, if I have a Brazilian keyboard derivative which is known to be problematic with respect to unspecified keys (omitted from the standard) such as /, \, ? and | among others I'll start with:

$ nlsadm list-console-keymap

...
Brazilian
...
US-English

$ profiles |grep Languages
National Languages Support Management


$ nlsadm set-console-keymap Brazilian
Setting SMF property keymap/layout with value: 'Brazilian' ...
Sucessfully set

At this point you'll be able to note all the inconsistencies / misbehavior of your keyboard but be careful not to get mislead by eventual terminal or shell misbehavior associated to some sort of buffering or so-called line discipline settings. Don't worry, just ignore it for the moment.

As a baseline, dump the current keymap to a temporary file:

$ dumpkeys >/tmp/current_keymap

Verify if the baseline needs some minor adjustments:

$ loadkeys /tmp/current_keymap
... line 228: illegal character in escape sequence

I know it's odd that dumpkeys generates a file that can't always be reused by loadkeys. Don't ask me why! (although I suspect it's because some keystations may be associated to a value which is impossible to represent as an escaped 3-digit octal constant). For now, just mitigate the error at the offending line, for instance:

$ i=0; \
while ((i<228)); \
do \
line; \
((i++)); \
done < /tmp/current_keymap
...
key 227     all 0x309

Note that 0x309 = 777 (decimal) = 01411 (octal).
Void the invalid hexadecimal constant (or other):

$ grep 'key 227' /tmp/current_keymap
key 227     all hole

The now fixed /tmp/current_keymap will be used by the detection script. The script is transcribed next but needs to be prepared beforehand on another computer and then get transfered or made accessible to the console which attached keyboard is to be inspected:

$ cat /tmp/scan_keystations
#!/bin/bash

LAYOUT_CURRENT=$(mktemp)
cp /tmp/current_keymap $LAYOUT_CURRENT

#
LAYOUT_FRAGMENT=$(mktemp)

function cleanup()
{
  rm $LAYOUT_FRAGMENT

  #
  loadkeys $LAYOUT_CURRENT
  rm $LAYOUT_CURRENT
}

# Just trying to be well-behaved

trap cleanup EXIT

function test_fragment()
{
  echo -------------------------------------------------------
  cat $LAYOUT_FRAGMENT
  loadkeys $LAYOUT_FRAGMENT
 
  #
  echo -------------------------------------------------------
  echo The above keystation range is now being inspected.
  echo In general, ^C should interrupt the inspection process.
  echo Pace yourself pressing all keys to inspect in the range.
  echo In general ENTER or ^M to should advance to next range.
  echo If a keystroke kicks a number in the range, take note.
  printf "> "
  read
  #
  loadkeys $LAYOUT_CURRENT
}


# There's a limitation on the total # of keystations strings.
# Hence I stay conservative with fragments, going 5 by 5.
 
I=0; MAX=5 

# Consider just the keystations present on $LAYOUT_CURRENT
# but it could be simplified to a bare list from 0 to 254.
 
INSPECT=$(egrep -v '(^$|^#.*)' $LAYOUT_CURRENT \
          |grep 'key' |tr -s ' ' |cut -d ' ' -f 2)

for KEY in $INSPECT
do
  echo key $KEY all \"$KEY\" numl nonl up nop >>$LAYOUT_FRAGMENT
  (( I++ ))
  if [[ $I -eq $MAX ]]
  then
    test_fragment
    #
    cat /dev/null >$LAYOUT_FRAGMENT
    I=0
  fi
done

# When $INSPECT #elements isn't a multiple of $MAX

if ! [[ $I -eq 0 ]] && ! [[ $I -eq $MAX ]]
then
  test_fragment
fi


Run the above script on the console and at each iteration press each key you want to inspect and take note of the generated number, if any. If you get a number while depressing a key you've just got that phsyical key keystation number. Take note and proceed until you finish checking all keys and have fully indentified most or all the keys on your keyboard. While doing this, take care of noting the state of the CapsLock, the NumLock (if any) and the ScrollLock (if any).

Here's a sample run of the script under a generic x86:
("Pace yourself" 😲 Sorry for the mispelling below)



















                                                              
Note that in the above particular case:
  • Physical key a/A has been identified as keystation 4.
  • Physical keys b/B through f/F are keystations 5 through 9.
  • While the spacebar wasn't detected I've used it after each keystroke.

The script I crafted above is simple yet powerful enough to detect some very strange / unexpected behavior caused by some odd layout tweaks by the manufacturer. I shall present one difficult example where it helped me to elucidate a mystery on one of my keyboards:

Dell KM-632 (BR)
The labeling is my artwork 😎

The keyboard is made in Asia and is re-branded by Dell. For clarity, I didn't draw the Alt_Gr chars on certain keys (most are useless anyway). In fact, this product series has many variations by language which also implies on many physical keys shapes variations as well. In case of the Brazilian variation there is an odd additional keypadd key '.' which clashes with the Alt_Gr key for using the same keystation when not trapped by NumLock! Not to mention that NumLock is hard-wired as always on (toggled) and its key has been substituted by a mirrored Del key. The ScrollLock and System / Pause keys are also missing. The keys highlighted in orange are for multimedia playback and volume control plus an hibernation (moon) key; all useless as long as /dev/kbd is concerned.

NOTE
The above keyboard is best suited to Windows because out-of-the-box it only fully works with the manufacturer device driver which is only available to that system. This is ugly and I don't like this non-standards stuff. But, as usual, being non-standards has been one of M$ rules. Perhaps they have been learning that this ill approach will isolate them, which is bad. M$ has done a great job in making the PC a popular tool, but, hum, that seems to be all, with a few honorable exceptions.

Bottom-line, unless you really have a reason not to, always choose an US-English keyboard and most definitely do so for system administration tasks.

To close this post let me present the keystations for the above keyboard:
(I broke down the script results in the hope it would be easier to read it)
 


And that's it for keystations. On a following post I'll attempt some keyboard layout configurations based on the above keystation information. In the process, depending on the situation I'll have to decide what to do with keystation 230.