Differences between revisions 2 and 10 (spanning 8 versions)
Revision 2 as of 2012-02-03 01:43:55
Size: 35789
Editor: dethrophes
Comment: Added a more complex solution that implments a vt100 parser
Revision 10 as of 2014-01-05 02:03:44
Size: 41825
Editor: ip68-14-14-157
Comment:
Deletions are marked like this. Additions are marked like this.
Line 6: Line 6:

This is sample code to allow more intuitive usage of Function keys in bash.
It abstracts the escape sequence messiness for these keys and as such allows for a more portable/intuitive solutions.
''This page has not been peer-reviewed for correctness, usefulness, portability or style. Use at your own risk.''

<<TableOfContents>>

The initial response to this is mostly "why bother?". The answer is because it is more robust than simply using `read`. The main problem with directly calling `read` is that the escape sequences use digits and letter so if you use something like the following it can lead to unexpected consequences/behavior.
Line 12: Line 13:
#!/bin/bash
#set -o errexit
#set -o errtrace
set -o nounset
  while read -sN1 ; do
    echo "${REPLY}";
    case "${REPLY}" in
      A) echo 'either "A" or up arrow ';;
      B) echo 'either "B" or down arrow ';;
      1) echo 'either "1" or any function key ';;
    esac
  done
}}}
It's even worse than that because an escape sequence can look something like this.

{{{
  $'\e[1;12A'
}}}
So you can just not use the contentious keys "0123456789ABCDEPQRSZ;?+", but no guarantee that that's a full list.

A better solution is to use something like this where we delineate each key with a Enter/Return key press.

{{{
  while read ; do
    echo "${REPLY}";
    case "${REPLY}" in
      A) echo 'Is A';;
      1) echo 'Is 1';;
    esac
  done
}}}
However that makes for a somewhat stilted interface.

Now we could also do something like this

{{{
  ReadKey() {
    # Wait for first char
    if read -sN1 _REPLY; then
      # Read rest of chars
      while read -sN1 -t 0.001 ; do
        _REPLY+="${REPLY}"
      done
    fi
  }
  while ReadKey ; do
    echo "${_REPLY}";
    case "${_REPLY}" in
      A) echo 'Is A';;
      1) echo 'Is 1';;
      $'\e[A') echo 'Up Arrow';;
      $'\e[1;2A') echo 'Shift + Up Arrow';;
      $'\e[4~' | $'\e[4z') echo 'F4';;
      $'\e[4;2~' | $'\e[4;2z') echo 'Shift + F4';;
      *) echo "Unknown, any others keys pressed: ${_REPLY}";;
    esac
  done
}}}
Now the above example is ok if you don't want to require the user to press the enter key and you don't want to use function keys however using function keys in the above manner is a mess there can be something like 4 different escape sequences for a key depending on the emulation mode. Furthermore it is difficult to ignore modifier keys. Technically an escape sequence for an up arrow ignoring the modifier keys requires a regex like this

{{{
    # Requires extglob
    case "${_REPLY}" in
      $'\e['?(+([0-9);+([0-9))A) echo 'Up Arrow Ignoring Modifiers'
    esac
}}}
Though mostly this should do

{{{
    # no extglob
    case "${_REPLY}" in
      $'\e[A' | $'\e[1;'[2-9]A) echo 'Up Arrow Ignoring Modifiers'
    esac
}}}
The following example builds on the last example and allows a more intuitive usage of Function keys in bash. It abstracts the escape sequence messiness for these keys and as such allows for a more portable/intuitive solutions. As it understands about escape sequences it also protects the caller from the problems with the first example above. Furthermore as no sub shells are required it is quicker than calling an external program or function to read the key.

{{{#!/bin/bash
#set -o errexit
#set -o errtrace
set -o nounset
Line 27: Line 100:
  function vt100_DECRST {     vt100_DECRST() {
Line 30: Line 103:
  function vt100_DECSET {     vt100_DECSET() {
Line 43: Line 116:
  function ord {   ord() {
Line 46: Line 119:
  function ord_eascii {   ord_eascii() {
Line 49: Line 122:
  function AdjustMousePos {   AdjustMousePos() {
Line 79: Line 152:
    # A B C D E F H         
    [0x41]="up" [0x42]="down" [0x43]="right" [0x44]="left" [0x45]="keypad-five" [0x46]="end" [0x48]="home"     
    # A B C D E F H
    [0x41]="up" [0x42]="down" [0x43]="right" [0x44]="left" [0x45]="keypad-five" [0x46]="end" [0x48]="home"
Line 82: Line 155:
    [0x49]="InFocus" [0x4f]="OutOfFocus"      
    # P Q R S Z          
    [0x50]="f1" [0x51]="f2" [0x52]="f3" [0x53]="f4" [0x5a]="S-HT" 
    [0x49]="InFocus" [0x4f]="OutOfFocus"
    # P Q R S Z
    [0x50]="f1" [0x51]="f2" [0x52]="f3" [0x53]="f4" [0x5a]="S-HT"
Line 87: Line 160:
    [0x00]="Null" [0x01]="SOH" [0x02]="STX" [0x03]="ETX" [0x04]="EOT" [0x05]="ENQ" [0x06]="ACK" 
    [0x07]="BEL" [0x08]="BS" [0x09]="HT" [0x0A]="LF" [0x0B]="VT" [0x0C]="FF" [0x0D]="CR"  
    [0x0E]="SO" [0x0F]="SI" [0x10]="DLE" [0x11]="DC1" [0x12]="DC2" [0x13]="DC3" [0x14]="DC4" 
    [0x15]="NAK" [0x16]="SYN" [0x17]="ETB" [0x18]="CAN" [0x19]="EM" [0x1A]="SUB" [0x1B]="ESC" 
    [0x1C]="FS" [0x1D]="GS" [0x1E]="RS" [0x1F]="US" [0x20]="SP" [0x7F]="DEL" 
    [0x00]="Null" [0x01]="SOH" [0x02]="STX" [0x03]="ETX" [0x04]="EOT" [0x05]="ENQ" [0x06]="ACK"
    [0x07]="BEL" [0x08]="BS" [0x09]="HT" [0x0A]="LF" [0x0B]="VT" [0x0C]="FF" [0x0D]="CR"
    [0x0E]="SO" [0x0F]="SI" [0x10]="DLE" [0x11]="DC1" [0x12]="DC2" [0x13]="DC3" [0x14]="DC4"
    [0x15]="NAK" [0x16]="SYN" [0x17]="ETB" [0x18]="CAN" [0x19]="EM" [0x1A]="SUB" [0x1B]="ESC"
    [0x1C]="FS" [0x1D]="GS" [0x1E]="RS" [0x1F]="US" [0x20]="SP" [0x7F]="DEL"
Line 94: Line 167:
    [0x01]="C-A" [0x02]="C-B" [0x03]="C-C" [0x04]="C-D" [0x05]="C-E" [0x06]="C-F" [0x07]="C-G" 
    [0x08]="C-H" [0x09]="C-I" [0x0a]="C-J" [0x0b]="C-K" [0x0c]="C-L" [0x0d]="C-M" [0x0e]="C-N"  
    [0x0f]="C-O" [0x10]="C-P" [0x11]="C-Q" [0x12]="C-R" [0x13]="C-S" [0x14]="C-T" [0x15]="C-U" 
    [0x16]="C-V" [0x17]="C-W" [0x18]="C-X" [0x19]="C-Y" [0x1a]="C-Z" [0x1b]="C-[" [0x1c]="C-]" 
    [0x1d]="C-}" [0x1e]="C-^" [0x1f]="C-_" [0x20]="C-SP" [0x7F]="DEL" 
    [0x01]="C-A" [0x02]="C-B" [0x03]="C-C" [0x04]="C-D" [0x05]="C-E" [0x06]="C-F" [0x07]="C-G"
    [0x08]="C-H" [0x09]="C-I" [0x0a]="C-J" [0x0b]="C-K" [0x0c]="C-L" [0x0d]="C-M" [0x0e]="C-N"
    [0x0f]="C-O" [0x10]="C-P" [0x11]="C-Q" [0x12]="C-R" [0x13]="C-S" [0x14]="C-T" [0x15]="C-U"
    [0x16]="C-V" [0x17]="C-W" [0x18]="C-X" [0x19]="C-Y" [0x1a]="C-Z" [0x1b]="C-[" [0x1c]="C-]"
    [0x1d]="C-}" [0x1e]="C-^" [0x1f]="C-_" [0x20]="C-SP" [0x7F]="DEL"
Line 101: Line 174:
    [0x40]="PAD" [0x41]="HOP" [0x42]="BPH" [0x43]="NBH" 
    [0x44]="IND" [0x45]="NEL" [0x46]="SSA" [0x47]="ESA" 
    [0x48]="HTS" [0x49]="HTJ" [0x4A]="VTS" [0x4B]="PLD" 
    [0x4C]="PLU" [0x4D]="RI" [0x4E]="SS2" [0x4F]="SS3" 
    [0x50]="DCS" [0x51]="PU1" [0x52]="PU2" [0x53]="STS" 
    [0x54]="CCH" [0x55]="MW" [0x56]="SPA" [0x57]="EPA" 
    [0x58]="SOS" [0x59]="SGCI" [0x5A]="SCI" [0x5B]="CSI" 
    [0x5C]="ST" [0x5D]="OSC" [0x5E]="PM" [0x5F]="APC" 
    [0x40]="PAD" [0x41]="HOP" [0x42]="BPH" [0x43]="NBH"
    [0x44]="IND" [0x45]="NEL" [0x46]="SSA" [0x47]="ESA"
    [0x48]="HTS" [0x49]="HTJ" [0x4A]="VTS" [0x4B]="PLD"
    [0x4C]="PLU" [0x4D]="RI" [0x4E]="SS2" [0x4F]="SS3"
    [0x50]="DCS" [0x51]="PU1" [0x52]="PU2" [0x53]="STS"
    [0x54]="CCH" [0x55]="MW" [0x56]="SPA" [0x57]="EPA"
    [0x58]="SOS" [0x59]="SGCI" [0x5A]="SCI" [0x5B]="CSI"
    [0x5C]="ST" [0x5D]="OSC" [0x5E]="PM" [0x5F]="APC"
Line 111: Line 184:
    [0x80]="PAD" [0x81]="HOP" [0x82]="BPH" [0x83]="NBH" 
    [0x84]="IND" [0x85]="NEL" [0x86]="SSA" [0x87]="ESA" 
    [0x88]="HTS" [0x89]="HTJ" [0x8A]="VTS" [0x8B]="PLD" 
    [0x8C]="PLU" [0x8D]="RI" [0x8E]="SS2" [0x8F]="SS3" 
    [0x90]="DCS" [0x91]="PU1" [0x92]="PU2" [0x93]="STS" 
    [0x94]="CCH" [0x95]="MW" [0x96]="SPA" [0x97]="EPA" 
    [0x98]="SOS" [0x99]="SGCI" [0x9A]="SCI" [0x9B]="CSI" 
    [0x9C]="ST" [0x9D]="OSC" [0x9E]="PM" [0x9F]="APC" 
  )    
    [0x80]="PAD" [0x81]="HOP" [0x82]="BPH" [0x83]="NBH"
    [0x84]="IND" [0x85]="NEL" [0x86]="SSA" [0x87]="ESA"
    [0x88]="HTS" [0x89]="HTJ" [0x8A]="VTS" [0x8B]="PLD"
    [0x8C]="PLU" [0x8D]="RI" [0x8E]="SS2" [0x8F]="SS3"
    [0x90]="DCS" [0x91]="PU1" [0x92]="PU2" [0x93]="STS"
    [0x94]="CCH" [0x95]="MW" [0x96]="SPA" [0x97]="EPA"
    [0x98]="SOS" [0x99]="SGCI" [0x9A]="SCI" [0x9B]="CSI"
    [0x9C]="ST" [0x9D]="OSC" [0x9E]="PM" [0x9F]="APC"
  )
Line 121: Line 194:
    [0x01]="CA-A" [0x02]="CA-B" [0x03]="CA-C" [0x04]="CA-D" [0x05]="CA-E" [0x06]="CA-F" [0x07]="CA-G" 
    [0x08]="CA-H" [0x09]="CA-I" [0x0a]="CA-J" [0x0b]="CA-K" [0x0c]="CA-L" [0x0d]="CA-M" [0x0e]="CA-N"  
    [0x0f]="CA-O" [0x10]="CA-P" [0x11]="CA-Q" [0x12]="CA-R" [0x13]="CA-S" [0x14]="CA-T" [0x15]="CA-U" 
    [0x16]="CA-V" [0x17]="CA-W" [0x18]="CA-X" [0x19]="CA-Y" [0x1a]="CA-Z" [0x1b]="CA-[" [0x1c]="CA-]" 
    [0x1d]="CA-}" [0x1e]="CA-^" [0x1f]="CA-_" [0x20]="CA-SP" [0x7F]="A-DEL" 
    [0x01]="CA-A" [0x02]="CA-B" [0x03]="CA-C" [0x04]="CA-D" [0x05]="CA-E" [0x06]="CA-F" [0x07]="CA-G"
    [0x08]="CA-H" [0x09]="CA-I" [0x0a]="CA-J" [0x0b]="CA-K" [0x0c]="CA-L" [0x0d]="CA-M" [0x0e]="CA-N"
    [0x0f]="CA-O" [0x10]="CA-P" [0x11]="CA-Q" [0x12]="CA-R" [0x13]="CA-S" [0x14]="CA-T" [0x15]="CA-U"
    [0x16]="CA-V" [0x17]="CA-W" [0x18]="CA-X" [0x19]="CA-Y" [0x1a]="CA-Z" [0x1b]="CA-[" [0x1c]="CA-]"
    [0x1d]="CA-}" [0x1e]="CA-^" [0x1f]="CA-_" [0x20]="CA-SP" [0x7F]="A-DEL"
Line 130: Line 203:
    [0x40]="MB4-P" [0x41]="MB5-P"      [0x40]="MB4-P" [0x41]="MB5-P"
Line 133: Line 206:
    [0x04]="S-" [0x08]="A-" [0x0c]="AS-"      [0x04]="S-" [0x08]="A-" [0x0c]="AS-"
Line 136: Line 209:
  function GetMouseButton {   GetMouseButton() {
Line 146: Line 219:
  function ReadKey {   ReadKey() {
Line 148: Line 221:
    local escapeSequence 
    local REPLY 
    local escapeSequence
    local REPLY
Line 154: Line 227:
        [^[:cntrl:]]) 
          UInput[0]="${escapeSequence}" 
        [^[:cntrl:]])
          UInput[0]="${escapeSequence}"
Line 163: Line 236:
              ${CSI}t)                ${CSI}t)
Line 168: Line 241:
              ${CSI}T)                ${CSI}T)
Line 177: Line 250:
              ${CSI}M*)                 ${CSI}M*)
Line 179: Line 252:
                if [ -n "${UInput[0]}" ]; then                   if [ -n "${UInput[0]}" ]; then
Line 258: Line 331:
 }}} 
  The ReadKey function can then be called in this manor.
 {{{ 
  function HandleKey {
}}}
 . The ReadKey function can then be called in this manor.

 .
{{{
  HandleKey() {
Line 266: Line 340:
          CR|NULL|LF|q)            CR|NULL|LF|q)
Line 270: Line 344:
          *up) echo "Up Arrow ignoring modifier keys" ;;
          down) echo "Down Arrow No modifier keys" ;;
          S-down) echo "Shift + Down Arrow" ;;
          A-down) echo "Alt + Down Arrow" ;;
          AS-down) echo "Alt + Shift + Down Arrow" ;;
          C-down) echo "Control + Down Arrow" ;;
          CS-down) echo "Control + Shift + Down Arrow" ;;
          CA-down) echo "Control + Alt + Down Arrow" ;;
          CAS-down) echo "Control + Alt + Shift + Down Arrow";;
          MCAS-down) echo "Meta + Control + Alt + Shift + Down Arrow";;
Line 278: Line 362:

 }}}

A more complex solution using a full vt100 parser.
}}}

The main problem with the previous example is that like most such bash implementations it requires a break between function keys. Now in a user interface this is not a problem because there is always a minimal delay between key presses. However on slower systems it could happen that 2 key preses get evaluated together, the only downside is that in those rare cases the keys get ignored. But this also means that it can't be used with buffered function key sequences.

== vt100 Parser Example ==
A more complex solution using a full vt100 parser. This following solution fixes the problems of the previous example as it doesn't blindly gobble up the entire buffer but instead intelligently reads the key sequence. The downside is that it is quite a bit more complex, and takes about 0.005s per function key instead of 0.001 sec.
Line 283: Line 370:

Line 388: Line 473:
  function ord {   ord() {
Line 391: Line 476:
  function ord_eascii {   ord_eascii() {
Line 394: Line 479:
  function substr {   substr() {
Line 421: Line 506:
  function vtparse_init {   vtparse_init() {
Line 429: Line 514:
  function nop {   nop() {
Line 432: Line 517:
  function do_action {   do_action() {
Line 434: Line 519:
    # we hand to our client for processing      # we hand to our client for processing
Line 450: Line 535:
        # process the param character          # process the param character
Line 473: Line 558:
  function vtparse_conv_base {   vtparse_conv_base() {
Line 478: Line 563:
  function do_state_change {   do_state_change() {
Line 487: Line 572:
    
Line 507: Line 592:
  function vtparse {   vtparse() {
Line 514: Line 599:
          # use that. Otherwise use the transition from the current state. 
          
          # use that. Otherwise use the transition from the current state.
Line 524: Line 609:
  function vtparse_read_stdin {   vtparse_read_stdin() {
Line 528: Line 613:
  function vtparse_read_buffer {   vtparse_read_buffer() {
Line 535: Line 620:
  function vtparser_callback_debug {   vtparser_callback_debug() {
Line 569: Line 654:
    # A B C D E F H         
    [0x41]="up" [0x42]="down" [0x43]="right" [0x44]="left" [0x45]="keypad-five" [0x46]="end" [0x48]="home"     
    # A B C D E F H
    [0x41]="up" [0x42]="down" [0x43]="right" [0x44]="left" [0x45]="keypad-five" [0x46]="end" [0x48]="home"
Line 572: Line 657:
    [0x49]="InFocus" [0x4f]="OutOfFocus"      
    # P Q R S Z          
    [0x50]="f1" [0x51]="f2" [0x52]="f3" [0x53]="f4" [0x5a]="S-HT" 
    [0x49]="InFocus" [0x4f]="OutOfFocus"
    # P Q R S Z
    [0x50]="f1" [0x51]="f2" [0x52]="f3" [0x53]="f4" [0x5a]="S-HT"
Line 577: Line 662:
    [0x00]="Null" [0x01]="SOH" [0x02]="STX" [0x03]="ETX" [0x04]="EOT" [0x05]="ENQ" [0x06]="ACK" 
    [0x07]="BEL" [0x08]="BS" [0x09]="HT" [0x0A]="LF" [0x0B]="VT" [0x0C]="FF" [0x0D]="CR"  
    [0x0E]="SO" [0x0F]="SI" [0x10]="DLE" [0x11]="DC1" [0x12]="DC2" [0x13]="DC3" [0x14]="DC4" 
    [0x15]="NAK" [0x16]="SYN" [0x17]="ETB" [0x18]="CAN" [0x19]="EM" [0x1A]="SUB" [0x1B]="ESC" 
    [0x1C]="FS" [0x1D]="GS" [0x1E]="RS" [0x1F]="US" [0x20]="SP" [0x7F]="DEL" 
    [0x00]="Null" [0x01]="SOH" [0x02]="STX" [0x03]="ETX" [0x04]="EOT" [0x05]="ENQ" [0x06]="ACK"
    [0x07]="BEL" [0x08]="BS" [0x09]="HT" [0x0A]="LF" [0x0B]="VT" [0x0C]="FF" [0x0D]="CR"
    [0x0E]="SO" [0x0F]="SI" [0x10]="DLE" [0x11]="DC1" [0x12]="DC2" [0x13]="DC3" [0x14]="DC4"
    [0x15]="NAK" [0x16]="SYN" [0x17]="ETB" [0x18]="CAN" [0x19]="EM" [0x1A]="SUB" [0x1B]="ESC"
    [0x1C]="FS" [0x1D]="GS" [0x1E]="RS" [0x1F]="US" [0x20]="SP" [0x7F]="DEL"
Line 584: Line 669:
    [0x01]="C-A" [0x02]="C-B" [0x03]="C-C" [0x04]="C-D" [0x05]="C-E" [0x06]="C-F" [0x07]="C-G" 
    [0x08]="C-H" [0x09]="C-I" [0x0a]="C-J" [0x0b]="C-K" [0x0c]="C-L" [0x0d]="C-M" [0x0e]="C-N"  
    [0x0f]="C-O" [0x10]="C-P" [0x11]="C-Q" [0x12]="C-R" [0x13]="C-S" [0x14]="C-T" [0x15]="C-U" 
    [0x16]="C-V" [0x17]="C-W" [0x18]="C-X" [0x19]="C-Y" [0x1a]="C-Z" [0x1b]="C-[" [0x1c]="C-]" 
    [0x1d]="C-}" [0x1e]="C-^" [0x1f]="C-_" [0x20]="C-SP" [0x7F]="DEL" 
    [0x01]="C-A" [0x02]="C-B" [0x03]="C-C" [0x04]="C-D" [0x05]="C-E" [0x06]="C-F" [0x07]="C-G"
    [0x08]="C-H" [0x09]="C-I" [0x0a]="C-J" [0x0b]="C-K" [0x0c]="C-L" [0x0d]="C-M" [0x0e]="C-N"
    [0x0f]="C-O" [0x10]="C-P" [0x11]="C-Q" [0x12]="C-R" [0x13]="C-S" [0x14]="C-T" [0x15]="C-U"
    [0x16]="C-V" [0x17]="C-W" [0x18]="C-X" [0x19]="C-Y" [0x1a]="C-Z" [0x1b]="C-[" [0x1c]="C-]"
    [0x1d]="C-}" [0x1e]="C-^" [0x1f]="C-_" [0x20]="C-SP" [0x7F]="DEL"
Line 592: Line 677:
    [0x40]="PAD" [0x41]="HOP" [0x42]="BPH" [0x43]="NBH" 
    [0x44]="IND" [0x45]="NEL" [0x46]="SSA" [0x47]="ESA" 
    [0x48]="HTS" [0x49]="HTJ" [0x4A]="VTS" [0x4B]="PLD" 
    [0x4C]="PLU" [0x4D]="RI" [0x4E]="SS2" [0x4F]="SS3" 
    [0x50]="DCS" [0x51]="PU1" [0x52]="PU2" [0x53]="STS" 
    [0x54]="CCH" [0x55]="MW" [0x56]="SPA" [0x57]="EPA" 
    [0x58]="SOS" [0x59]="SGCI" [0x5A]="SCI" [0x5B]="CSI" 
    [0x5C]="ST" [0x5D]="OSC" [0x5E]="PM" [0x5F]="APC" 
    [0x40]="PAD" [0x41]="HOP" [0x42]="BPH" [0x43]="NBH"
    [0x44]="IND" [0x45]="NEL" [0x46]="SSA" [0x47]="ESA"
    [0x48]="HTS" [0x49]="HTJ" [0x4A]="VTS" [0x4B]="PLD"
    [0x4C]="PLU" [0x4D]="RI" [0x4E]="SS2" [0x4F]="SS3"
    [0x50]="DCS" [0x51]="PU1" [0x52]="PU2" [0x53]="STS"
    [0x54]="CCH" [0x55]="MW" [0x56]="SPA" [0x57]="EPA"
    [0x58]="SOS" [0x59]="SGCI" [0x5A]="SCI" [0x5B]="CSI"
    [0x5C]="ST" [0x5D]="OSC" [0x5E]="PM" [0x5F]="APC"
Line 602: Line 687:
    [0x80]="PAD" [0x81]="HOP" [0x82]="BPH" [0x83]="NBH" 
    [0x84]="IND" [0x85]="NEL" [0x86]="SSA" [0x87]="ESA" 
    [0x88]="HTS" [0x89]="HTJ" [0x8A]="VTS" [0x8B]="PLD" 
    [0x8C]="PLU" [0x8D]="RI" [0x8E]="SS2" [0x8F]="SS3" 
    [0x90]="DCS" [0x91]="PU1" [0x92]="PU2" [0x93]="STS" 
    [0x94]="CCH" [0x95]="MW" [0x96]="SPA" [0x97]="EPA" 
    [0x98]="SOS" [0x99]="SGCI" [0x9A]="SCI" [0x9B]="CSI" 
    [0x9C]="ST" [0x9D]="OSC" [0x9E]="PM" [0x9F]="APC" 
  )    
    [0x80]="PAD" [0x81]="HOP" [0x82]="BPH" [0x83]="NBH"
    [0x84]="IND" [0x85]="NEL" [0x86]="SSA" [0x87]="ESA"
    [0x88]="HTS" [0x89]="HTJ" [0x8A]="VTS" [0x8B]="PLD"
    [0x8C]="PLU" [0x8D]="RI" [0x8E]="SS2" [0x8F]="SS3"
    [0x90]="DCS" [0x91]="PU1" [0x92]="PU2" [0x93]="STS"
    [0x94]="CCH" [0x95]="MW" [0x96]="SPA" [0x97]="EPA"
    [0x98]="SOS" [0x99]="SGCI" [0x9A]="SCI" [0x9B]="CSI"
    [0x9C]="ST" [0x9D]="OSC" [0x9E]="PM" [0x9F]="APC"
  )
Line 612: Line 697:
    [0x01]="CA-A" [0x02]="CA-B" [0x03]="CA-C" [0x04]="CA-D" [0x05]="CA-E" [0x06]="CA-F" [0x07]="CA-G" 
    [0x08]="CA-H" [0x09]="CA-I" [0x0a]="CA-J" [0x0b]="CA-K" [0x0c]="CA-L" [0x0d]="CA-M" [0x0e]="CA-N"  
    [0x0f]="CA-O" [0x10]="CA-P" [0x11]="CA-Q" [0x12]="CA-R" [0x13]="CA-S" [0x14]="CA-T" [0x15]="CA-U" 
    [0x16]="CA-V" [0x17]="CA-W" [0x18]="CA-X" [0x19]="CA-Y" [0x1a]="CA-Z" [0x1b]="CA-[" [0x1c]="CA-]" 
    [0x1d]="CA-}" [0x1e]="CA-^" [0x1f]="CA-_" [0x20]="CA-SP" [0x7F]="A-DEL" 
    [0x01]="CA-A" [0x02]="CA-B" [0x03]="CA-C" [0x04]="CA-D" [0x05]="CA-E" [0x06]="CA-F" [0x07]="CA-G"
    [0x08]="CA-H" [0x09]="CA-I" [0x0a]="CA-J" [0x0b]="CA-K" [0x0c]="CA-L" [0x0d]="CA-M" [0x0e]="CA-N"
    [0x0f]="CA-O" [0x10]="CA-P" [0x11]="CA-Q" [0x12]="CA-R" [0x13]="CA-S" [0x14]="CA-T" [0x15]="CA-U"
    [0x16]="CA-V" [0x17]="CA-W" [0x18]="CA-X" [0x19]="CA-Y" [0x1a]="CA-Z" [0x1b]="CA-[" [0x1c]="CA-]"
    [0x1d]="CA-}" [0x1e]="CA-^" [0x1f]="CA-_" [0x20]="CA-SP" [0x7F]="A-DEL"
Line 622: Line 707:
    [0x40]="MB4-P" [0x41]="MB5-P"      [0x40]="MB4-P" [0x41]="MB5-P"
Line 625: Line 710:
    [0x04]="S-" [0x08]="A-" [0x0c]="AS-"      [0x04]="S-" [0x08]="A-" [0x0c]="AS-"
Line 628: Line 713:
  
  function AdjustMousePos {

  AdjustMousePos() {
Line 634: Line 719:
  function GetMouseButton {   GetMouseButton() {
Line 641: Line 726:
  function vtparser_callback_readkey {   vtparser_callback_readkey() {
Line 643: Line 728:
    case "${1}" in      case "${1}" in
Line 645: Line 730:
        case "${2}" in          case "${2}" in
Line 658: Line 743:
        case "${2}" in          case "${2}" in
Line 686: Line 771:
          t)            t)
Line 692: Line 777:
          T)            T)
Line 702: Line 787:
          M) # Mouse            M) # Mouse
Line 705: Line 790:
            if [ -n "${UInput[0]}" ]; then               if [ -n "${UInput[0]}" ]; then
Line 735: Line 820:
        case "${2}" in          case "${2}" in
Line 742: Line 827:
        case "${2}" in          case "${2}" in
Line 755: Line 840:
   function HandleKey {
 vtparse_init vtparser_callback_readkey vtparse_read_stdin
 while true; do
  vtparse
  case "${UInput[0]}" in
   LF)
    echo "${UInput[@]}"
    break
    ;;
   *)
    echo "${UInput[@]}"
    ;;
  esac
 done
   HandleKey() {
        vtparse_init vtparser_callback_readkey vtparse_read_stdin
        while true; do
                vtparse
                case "${UInput[0]}" in
                        LF)
                                echo "${UInput[@]}"
                                break
                                ;;
                        *up) echo "Up Arrow ignoring modifier keys" ;;
                        down) echo "Down Arrow No modifier keys" ;;
                        S-down) echo "Shift + Down Arrow" ;;
                        A-down) echo "Alt + Down Arrow" ;;
                        AS-down) echo "Alt + Shift + Down Arrow" ;;
                        C-down) echo "Control + Down Arrow" ;;
                        CS-down) echo "Control + Shift + Down Arrow" ;;
                        CA-down) echo "Control + Alt + Down Arrow" ;;
                        CAS-down) echo "Control + Alt + Shift + Down Arrow";;
                        MCAS-down) echo "Meta + Control + Alt + Shift + Down Arrow";;
                        *)
                                echo "${UInput[@]}"
                                ;;
                esac
        done
Line 773: Line 868:
= KEYBD trap (ksh93) =
ksh93 provides lower-level control over interactivity that is extensible directly in the shell language. The special `KEYBD` trap, if set, will fire on any keypress while exposing the necessary primitives to handle the event through special parameters.
||`.sh.edchar` ||The value of the character, or a character sequence if the first character was ESC. Writing to ''.sh.edchar'' within a KEYBD trap modifies the key that was pressed to trigger the trap. ||
||`.sh.edcol` ||Cursor position as of the most recent KEYBD trap. ||
||`.sh.edmode` ||set to ESC in vi insert mode, otherwise empty. ||
||`.sh.edtext` ||The text of the input buffer. ||




For example, after running `trap 'printf %q\\n "${.sh.edchar}"' KEYBD`, a representation of each key pressed will be printed back in shell-escaped format. Using this information, you can use the trap to call a function from an array of callbacks or commands to evaluate associated with certain keypresses or combinations.

TODO: example. This should be enough to get you going.

Reading Function Keys in bash

This page has not been peer-reviewed for correctness, usefulness, portability or style. Use at your own risk.

The initial response to this is mostly "why bother?". The answer is because it is more robust than simply using read. The main problem with directly calling read is that the escape sequences use digits and letter so if you use something like the following it can lead to unexpected consequences/behavior.

  while read -sN1 ; do
    echo "${REPLY}";
    case "${REPLY}" in
      A) echo 'either "A" or up arrow ';;
      B) echo 'either "B" or down arrow ';;
      1) echo 'either "1" or any function key ';;
    esac
  done

It's even worse than that because an escape sequence can look something like this.

  $'\e[1;12A'

So you can just not use the contentious keys "0123456789ABCDEPQRSZ;?+", but no guarantee that that's a full list.

A better solution is to use something like this where we delineate each key with a Enter/Return key press.

  while read  ; do
    echo "${REPLY}";
    case "${REPLY}" in
      A) echo 'Is A';;
      1) echo 'Is 1';;
    esac
  done

However that makes for a somewhat stilted interface.

Now we could also do something like this

  ReadKey() {
    # Wait for first char
    if read -sN1 _REPLY; then
      # Read rest of chars
      while read -sN1 -t 0.001 ; do
        _REPLY+="${REPLY}"
      done
    fi
  }
  while ReadKey  ; do
    echo "${_REPLY}";
    case "${_REPLY}" in
      A) echo 'Is A';;
      1) echo 'Is 1';;
      $'\e[A') echo 'Up Arrow';;
      $'\e[1;2A') echo 'Shift + Up Arrow';;
      $'\e[4~' | $'\e[4z') echo 'F4';;
      $'\e[4;2~' | $'\e[4;2z') echo 'Shift + F4';;
      *) echo "Unknown, any others keys pressed: ${_REPLY}";;
    esac
  done

Now the above example is ok if you don't want to require the user to press the enter key and you don't want to use function keys however using function keys in the above manner is a mess there can be something like 4 different escape sequences for a key depending on the emulation mode. Furthermore it is difficult to ignore modifier keys. Technically an escape sequence for an up arrow ignoring the modifier keys requires a regex like this

    # Requires extglob
    case "${_REPLY}" in
      $'\e['?(+([0-9);+([0-9))A) echo 'Up Arrow Ignoring Modifiers'
    esac

Though mostly this should do

    # no extglob
    case "${_REPLY}" in
      $'\e[A' | $'\e[1;'[2-9]A) echo 'Up Arrow Ignoring Modifiers'
    esac

The following example builds on the last example and allows a more intuitive usage of Function keys in bash. It abstracts the escape sequence messiness for these keys and as such allows for a more portable/intuitive solutions. As it understands about escape sequences it also protects the caller from the problems with the first example above. Furthermore as no sub shells are required it is quicker than calling an external program or function to read the key.

{{{#!/bin/bash #set -o errexit #set -o errtrace set -o nounset

  • if [ "${S8C1T:-0}" != "1" ] ; then
    • declare -gr SS3=$'\eO' # Single Shift Select of G3 Character Set ( SS3 is 0x8f): affects next character only declare -gr CSI=$'\e[' # Control Sequence Introducer ( CSI is 0x9b)
    else
    • declare -gr SS3=$'\x8f' # Single Shift Select of G3 Character Set ( SS3 is 0x8f): affects next character only declare -gr CSI=$'\x9b' # Control Sequence Introducer ( CSI is 0x9b)
    fi vt100_DECRST() {
    • IFS=';' eval 'echo -n "${CSI}?${*?Missing Pm}l"'
    } vt100_DECSET() {
    • IFS=';' eval 'echo -n "${CSI}?${*?Missing Pm}h"'
    } mouse_type=(
    • [0]=9 ## X10 mouse reporting, for compatibility with X10's xterm, reports on button press. [1]=1000 ## X11 mouse reporting, reports on button press and release. [2]=1001 ## highlight reporting, useful for reporting mouse highlights [3]=1002 ## button movement reporting, reports movement when a button is presse [4]=1003 ## all movement reporting, reports all movements.

      [5]=1004 ## FocusIn/FocusOut can be combined with any of the mouse events since it uses a different protocol. When set, it causes xterm to send CSI I when the terminal gains focus, and CSI O when it loses focus. [6]=1005 ## Extended mouse mode enables UTF-8 encoding for C x and C y under all tracking modes, expanding the maximum encodable position from 223 to 2015. For positions less than 95, the resulting output is identical under both modes. Under extended mouse mode, positions greater than 95 generate "extra" bytes which will confuse applications which do not treat their input as a UTF-8 stream. Likewise, C b will be UTF-8 encoded, to reduce confusion with wheel mouse events.

    • )
    ord() {
    • printf -v "${1?Missing Dest Variable}" "${3:-%d}" "'${2?Missing Char}"
    } ord_eascii() {
    • LC_CTYPE=C ord "${@}"
    }

    AdjustMousePos() {

    • local -i _INDEX ord_eascii _INDEX "${2}" eval ${1}'=$(( ${_INDEX}-32))'
    } ############################### ## ## READ KEY CRAP ## ## ###############################

    KeyModifiers=(

    • [2]="S" [3]="A" [4]="AS" [5]="C" [6]="CS" [7]="CA" [8]="CAS"
    • [9]="M" [10]="MS" [11]="MA" [12]="MAS" [13]="MC" [14]="MCS" [15]="MCA" [16]="MCAS" )

    KeybFntKeys=(

    • [1]="home" [2]="insert" [3]="delete" [4]="end" [5]="pageUp" [6]="pageDown" [11]="f1" [12]="f2" [13]="f3" [14]="f4" [15]="f5" [17]="f6" [18]="f7" [19]="f8" [20]="f9" [21]="f10" [23]="f11" [24]="f12" [25]="f13" [26]="f14" [28]="f15" [29]="f16" [31]="f17" [32]="f18" [33]="f19" [34]="f20" )

    SunKeybFntKeys=(

    • [214]="home" [2]="insert" [3]="delete" [4]="end" [216]="pageUp" [222]="pageDown" [224]="f1" [225]="f2" [226]="f3" [227]="f4" [228]="f5" [229]="f6" [230]="f7" [231]="f8" [232]="f9" [233]="f10" [192]="f11" [193]="f12" [218]="keypad-five" [220]="keypad-delete" )

    KeybFntKeysAlt=(

    • # A B C D E F H [0x41]="up" [0x42]="down" [0x43]="right" [0x44]="left" [0x45]="keypad-five" [0x46]="end" [0x48]="home" # I O

      [0x49]="InFocus" [0x4f]="OutOfFocus" # P Q R S Z [0x50]="f1" [0x51]="f2" [0x52]="f3" [0x53]="f4" [0x5a]="S-HT" )

    C0CtrlChars=(

    • [0x00]="Null" [0x01]="SOH" [0x02]="STX" [0x03]="ETX" [0x04]="EOT" [0x05]="ENQ" [0x06]="ACK" [0x07]="BEL" [0x08]="BS" [0x09]="HT" [0x0A]="LF" [0x0B]="VT" [0x0C]="FF" [0x0D]="CR" [0x0E]="SO" [0x0F]="SI" [0x10]="DLE" [0x11]="DC1" [0x12]="DC2" [0x13]="DC3" [0x14]="DC4" [0x15]="NAK" [0x16]="SYN" [0x17]="ETB" [0x18]="CAN" [0x19]="EM" [0x1A]="SUB" [0x1B]="ESC" [0x1C]="FS" [0x1D]="GS" [0x1E]="RS" [0x1F]="US" [0x20]="SP" [0x7F]="DEL"
    )

    C0CtrlCharsAlt=(

    • [0x01]="C-A" [0x02]="C-B" [0x03]="C-C" [0x04]="C-D" [0x05]="C-E" [0x06]="C-F" [0x07]="C-G" [0x08]="C-H" [0x09]="C-I" [0x0a]="C-J" [0x0b]="C-K" [0x0c]="C-L" [0x0d]="C-M" [0x0e]="C-N" [0x0f]="C-O" [0x10]="C-P" [0x11]="C-Q" [0x12]="C-R" [0x13]="C-S" [0x14]="C-T" [0x15]="C-U" [0x16]="C-V" [0x17]="C-W" [0x18]="C-X" [0x19]="C-Y" [0x1a]="C-Z" [0x1b]="C-[" [0x1c]="C-]" [0x1d]="C-}" [0x1e]="C-^" [0x1f]="C-_" [0x20]="C-SP" [0x7F]="DEL"
    )

    C1CtrlCharsEsc=(

    • [0x40]="PAD" [0x41]="HOP" [0x42]="BPH" [0x43]="NBH" [0x44]="IND" [0x45]="NEL" [0x46]="SSA" [0x47]="ESA" [0x48]="HTS" [0x49]="HTJ" [0x4A]="VTS" [0x4B]="PLD" [0x4C]="PLU" [0x4D]="RI" [0x4E]="SS2" [0x4F]="SS3" [0x50]="DCS" [0x51]="PU1" [0x52]="PU2" [0x53]="STS" [0x54]="CCH" [0x55]="MW" [0x56]="SPA" [0x57]="EPA" [0x58]="SOS" [0x59]="SGCI" [0x5A]="SCI" [0x5B]="CSI" [0x5C]="ST" [0x5D]="OSC" [0x5E]="PM" [0x5F]="APC"
    )

    C1CtrlChars=(

    • [0x80]="PAD" [0x81]="HOP" [0x82]="BPH" [0x83]="NBH" [0x84]="IND" [0x85]="NEL" [0x86]="SSA" [0x87]="ESA" [0x88]="HTS" [0x89]="HTJ" [0x8A]="VTS" [0x8B]="PLD" [0x8C]="PLU" [0x8D]="RI" [0x8E]="SS2" [0x8F]="SS3" [0x90]="DCS" [0x91]="PU1" [0x92]="PU2" [0x93]="STS" [0x94]="CCH" [0x95]="MW" [0x96]="SPA" [0x97]="EPA" [0x98]="SOS" [0x99]="SGCI" [0x9A]="SCI" [0x9B]="CSI" [0x9C]="ST" [0x9D]="OSC" [0x9E]="PM" [0x9F]="APC"
    )

    C1CtrlCharsAlt=(

    • [0x01]="CA-A" [0x02]="CA-B" [0x03]="CA-C" [0x04]="CA-D" [0x05]="CA-E" [0x06]="CA-F" [0x07]="CA-G" [0x08]="CA-H" [0x09]="CA-I" [0x0a]="CA-J" [0x0b]="CA-K" [0x0c]="CA-L" [0x0d]="CA-M" [0x0e]="CA-N" [0x0f]="CA-O" [0x10]="CA-P" [0x11]="CA-Q" [0x12]="CA-R" [0x13]="CA-S" [0x14]="CA-T" [0x15]="CA-U" [0x16]="CA-V" [0x17]="CA-W" [0x18]="CA-X" [0x19]="CA-Y" [0x1a]="CA-Z" [0x1b]="CA-[" [0x1c]="CA-]" [0x1d]="CA-}" [0x1e]="CA-^" [0x1f]="CA-_" [0x20]="CA-SP" [0x7F]="A-DEL"
    )

    MouseButtons=(

    • [0x00]="MB1-P" [0x01]="MB2-P" [0x02]="MB3-P" [0x03]="MB-R" [0x20]="MB1-M" [0x21]="MB2-M" [0x22]="MB3-M" [0x23]="MB-M" [0x40]="MB4-P" [0x41]="MB5-P"
    )

    MouseMetaButtons=(

    • [0x04]="S-" [0x08]="A-" [0x0c]="AS-" [0x10]="C-" [0x14]="CS-" [0x1c]="CAS-"
    )

    GetMouseButton() {

    } mouse_on="$(vt100_DECSET ${mouse_type[1]})" mouse_off="$(vt100_DECRST "${mouse_type[1]}" )"

    ReadKey() {

    • unset UInput[@] local escapeSequence local REPLY echo -n "${mouse_on}"

      if IFS= read -srN1 ${1:-} escapeSequence; then

      • case "${escapeSequence}" in
        • [^[:cntrl:]])
          • UInput[0]="${escapeSequence}" ;;
          $'\e')
          • while IFS= read -srN1 -t0.0001 ; do

            • escapeSequence+="${REPLY}"
            done case "${escapeSequence}" in
            • $'\e'[^[:cntrl:]]) echo -n "A-${escapeSequence:1}" ;; ${CSI}t) ${CSI}T) ${CSI}M*)
              • GetMouseButton UInput[0] "${escapeSequence:3:1}" if [ -n "${UInput[0]}" ]; then

                else
                • UInput[0]=$(printf 'Mouse-\\x%02x %q' "'${escapeSequence:3:1}" "${escapeSequence:4}")
                fi ;;
              ${CSI}[0-9]*[ABCDEFHIOZPQRSz~])
              • local CSI_Params=( ${escapeSequence//[!0-9]/ } ) local CSI_Func="${escapeSequence:${#escapeSequence}-1}" case "${CSI_Func}" in
                • z) # Sun Function Keys
                  • UInput[0]="${SunKeybFntKeys[${CSI_Params[0]}]-}" if [ -n "${UInput[0]}" ]; then

                    • [ ${#CSI_Params[@]} -le 1 ] || UInput[0]="${KeyModifiers[${CSI_Params[1]}]}-${UInput[0]}"

                    else
                    • UInput[0]="CSI ${CSI_Params[*]} ${CSI_Func}"
                    fi ;;
                  '~') # Function Keys
                  • UInput[0]="${KeybFntKeys[${CSI_Params[0]}]-}" if [ -n "${UInput[0]}" ]; then

                    • [ ${#CSI_Params[@]} -le 1 ] || UInput[0]="${KeyModifiers[${CSI_Params[1]}]}-${UInput[0]}"

                    else
                    • UInput[0]="CSI ${CSI_Params[*]} ${CSI_Func}"
                    fi ;;
                  A|B|C|D|E|F|H|I|O|Z|P|Q|R|S)
                  • ord_eascii CSI_Func "${CSI_Func}"

                    UInput[0]="${KeybFntKeysAlt[${CSI_Func}]}" if [ -n "${UInput[0]}" ]; then

                    • [ ${#CSI_Params[@]} -le 1 ] || UInput[0]="${KeyModifiers[${CSI_Params[1]}]}-${UInput[0]}"

                    else
                    • UInput[0]="CSI ${CSI_Params[*]} ${CSI_Func}"
                    fi ;;
                • )
                  • UInput[0]="CSI ${CSI_Params[*]} ${CSI_Func}" ;;
                esac ;;
              ${SS3}*[ABCDEFHPQRSIO~])
              • local SS3_Params=( ${escapeSequence//[!0-9]/ } ) local SS3_Func="${escapeSequence:${#escapeSequence}-1}" case "${SS3_Func}" in
                • A|B|C|D|E|F|H|P|Q|R|S|~)
                  • ord_eascii SS3_Func "${SS3_Func}"

                    UInput[0]="${KeybFntKeysAlt[${SS3_Func}]-}" if [ -n "${UInput[0]}" ]; then

                    • [ ${#SS3_Params[@]} -lt 1 ] || UInput[0]="${KeyModifiers[${SS3_Params[0]}]}-${UInput[0]}"

                    else
                    • UInput[0]="SS3 ${SS3_Params[*]-} ${SS3_Func}"
                    fi ;;
                • )
                  • UInput[0]="SS3 ${SS3_Params[*]-} ${SS3_Func}" ;;
                esac ;;

              $'\e':cntrl:)

              • ord_eascii UInput[0] "${escapeSequence:1:1}"

                UInput[0]="${C1CtrlCharsAlt[${UInput[0]}]:-}" [ -n "${UInput[0]:-}" ] || UInput[0]="$(printf "%q" "${escapeSequence}")" ;;

              $'\e') UInput[0]="ESC" ;;
            • )
              • UInput[0]="$(printf "%q" "${escapeSequence}")" ;;
            esac ;;
        • )
          • ord_eascii UInput[0] "${escapeSequence}"

            UInput[0]="${C0CtrlChars[${UInput[0]}]:-}" [ -n "${UInput[0]:-}" ] || UInput[0]="$(printf '%q' "'${escapeSequence}")" ;;

        esac
      fi echo -n "${mouse_off}"
    }

}}}

  • The ReadKey function can then be called in this manor.

  •   HandleKey() {
        local -a UInput
        while true; do
          if ReadKey ; then
            case "${UInput[0]:-}" in
              CR|NULL|LF|q)
                echo "\"${UInput[*]-}\""
                break
                ;;
              *up)       echo "Up Arrow ignoring modifier keys"  ;;
              down)      echo "Down Arrow No modifier keys"      ;;
              S-down)    echo "Shift + Down Arrow"               ;;
              A-down)    echo "Alt + Down Arrow"                 ;;
              AS-down)   echo "Alt + Shift + Down Arrow"         ;;
              C-down)    echo "Control + Down Arrow"             ;;
              CS-down)   echo "Control + Shift + Down Arrow"      ;;
              CA-down)   echo "Control + Alt + Down Arrow"        ;;
              CAS-down)  echo "Control + Alt + Shift + Down Arrow";;
              MCAS-down) echo "Meta + Control + Alt + Shift + Down Arrow";;
              *)
                echo "\"${UInput[*]-}\""
                ;;
            esac
          fi
        done
      }
    HandleKey

The main problem with the previous example is that like most such bash implementations it requires a break between function keys. Now in a user interface this is not a problem because there is always a minimal delay between key presses. However on slower systems it could happen that 2 key preses get evaluated together, the only downside is that in those rare cases the keys get ignored. But this also means that it can't be used with buffered function key sequences.

vt100 Parser Example

A more complex solution using a full vt100 parser. This following solution fixes the problems of the previous example as it doesn't blindly gobble up the entire buffer but instead intelligently reads the key sequence. The downside is that it is quite a bit more complex, and takes about 0.005s per function key instead of 0.001 sec.

declare -gri VTPARSE_ACTION_CLEAR="1"
declare -gri VTPARSE_ACTION_COLLECT="2"
declare -gri VTPARSE_ACTION_CSI_DISPATCH="3"
declare -gri VTPARSE_ACTION_ESC_DISPATCH="4"
declare -gri VTPARSE_ACTION_ESC_EXECUTE="5"
declare -gri VTPARSE_ACTION_EXECUTE="6"
declare -gri VTPARSE_ACTION_HOOK="7"
declare -gri VTPARSE_ACTION_IGNORE="8"
declare -gri VTPARSE_ACTION_OSC_END="9"
declare -gri VTPARSE_ACTION_OSC_PUT="10"
declare -gri VTPARSE_ACTION_OSC_START="11"
declare -gri VTPARSE_ACTION_PARAM="12"
declare -gri VTPARSE_ACTION_PRINT="13"
declare -gri VTPARSE_ACTION_PUT="14"
declare -gri VTPARSE_ACTION_SS2_DISPATCH="15"
declare -gri VTPARSE_ACTION_SS3_DISPATCH="16"
declare -gri VTPARSE_ACTION_UNHOOK="17"

declare -gra VTPARSE_ACTION_NAMES=(
   [0 ]="<no action>"
   [${VTPARSE_ACTION_CLEAR}]="CLEAR"
   [${VTPARSE_ACTION_COLLECT}]="COLLECT"
   [${VTPARSE_ACTION_CSI_DISPATCH}]="CSI_DISPATCH"
   [${VTPARSE_ACTION_ESC_DISPATCH}]="ESC_DISPATCH"
   [${VTPARSE_ACTION_ESC_EXECUTE}]="ESC_EXECUTE"
   [${VTPARSE_ACTION_EXECUTE}]="EXECUTE"
   [${VTPARSE_ACTION_HOOK}]="HOOK"
   [${VTPARSE_ACTION_IGNORE}]="IGNORE"
   [${VTPARSE_ACTION_OSC_END}]="OSC_END"
   [${VTPARSE_ACTION_OSC_PUT}]="OSC_PUT"
   [${VTPARSE_ACTION_OSC_START}]="OSC_START"
   [${VTPARSE_ACTION_PARAM}]="PARAM"
   [${VTPARSE_ACTION_PRINT}]="PRINT"
   [${VTPARSE_ACTION_PUT}]="PUT"
   [${VTPARSE_ACTION_SS2_DISPATCH}]="SS2_DISPATCH"
   [${VTPARSE_ACTION_SS3_DISPATCH}]="SS3_DISPATCH"
   [${VTPARSE_ACTION_UNHOOK}]="UNHOOK"
)
declare -gri VTPARSE_STATE_ANYWHERE="0"
declare -gri VTPARSE_STATE_CSI_ENTRY="1"
declare -gri VTPARSE_STATE_CSI_IGNORE="2"
declare -gri VTPARSE_STATE_CSI_INTERMEDIATE="3"
declare -gri VTPARSE_STATE_CSI_PARAM="4"
declare -gri VTPARSE_STATE_DCS_ENTRY="5"
declare -gri VTPARSE_STATE_DCS_IGNORE="6"
declare -gri VTPARSE_STATE_DCS_INTERMEDIATE="7"
declare -gri VTPARSE_STATE_DCS_PARAM="8"
declare -gri VTPARSE_STATE_DCS_PASSTHROUGH="9"
declare -gri VTPARSE_STATE_ESCAPE="10"
declare -gri VTPARSE_STATE_ESCAPE_INTERMEDIATE="11"
declare -gri VTPARSE_STATE_GROUND="12"
declare -gri VTPARSE_STATE_OSC_STRING="13"
declare -gri VTPARSE_STATE_SOS_PM_APC_STRING="14"
declare -gri VTPARSE_STATE_SS2_ENTRY="15"
declare -gri VTPARSE_STATE_SS3_ENTRY="16"
declare -gri VTPARSE_STATE_SS3_PARAM="17"

declare -gra VTPARSE_STATE_NAMES=(
   [${VTPARSE_STATE_ANYWHERE}]="ANYWHERE"
   [${VTPARSE_STATE_CSI_ENTRY}]="CSI_ENTRY"
   [${VTPARSE_STATE_CSI_IGNORE}]="CSI_IGNORE"
   [${VTPARSE_STATE_CSI_INTERMEDIATE}]="CSI_INTERMEDIATE"
   [${VTPARSE_STATE_CSI_PARAM}]="CSI_PARAM"
   [${VTPARSE_STATE_DCS_ENTRY}]="DCS_ENTRY"
   [${VTPARSE_STATE_DCS_IGNORE}]="DCS_IGNORE"
   [${VTPARSE_STATE_DCS_INTERMEDIATE}]="DCS_INTERMEDIATE"
   [${VTPARSE_STATE_DCS_PARAM}]="DCS_PARAM"
   [${VTPARSE_STATE_DCS_PASSTHROUGH}]="DCS_PASSTHROUGH"
   [${VTPARSE_STATE_ESCAPE}]="ESCAPE"
   [${VTPARSE_STATE_ESCAPE_INTERMEDIATE}]="ESCAPE_INTERMEDIATE"
   [${VTPARSE_STATE_GROUND}]="GROUND"
   [${VTPARSE_STATE_OSC_STRING}]="OSC_STRING"
   [${VTPARSE_STATE_SOS_PM_APC_STRING}]="SOS_PM_APC_STRING"
   [${VTPARSE_STATE_SS2_ENTRY}]="SS2_ENTRY"
   [${VTPARSE_STATE_SS3_ENTRY}]="SS3_ENTRY"
   [${VTPARSE_STATE_SS3_PARAM}]="SS3_PARAM"
)
declare -gra VTPARSE_STATE_TABLE=(
  [${VTPARSE_STATE_ANYWHERE}]="0000000000000000000000000000000000000000000000006c006c0a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006c6c6c6c6c6c6c6c6c6c6c6c6c6c0f6c056c6c6c6c6c6c6c0e6c6c016c0d0e0e"
  [${VTPARSE_STATE_CSI_ENTRY}]="606060606060606060606060606060606060606060606060006000006060606023232323232323232323232323232323c4c4c4c4c4c4c4c4c4c402c4242424243c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c80"
  [${VTPARSE_STATE_CSI_IGNORE}]="606060606060606060606060606060606060606060606060006000006060606080808080808080808080808080808080808080808080808080808080808080800c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c80"
  [${VTPARSE_STATE_CSI_INTERMEDIATE}]="606060606060606060606060606060606060606060606060006000006060606020202020202020202020202020202020020202020202020202020202020202023c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c80"
  [${VTPARSE_STATE_CSI_PARAM}]="606060606060606060606060606060606060606060606060006000006060606023232323232323232323232323232323c0c0c0c0c0c0c0c0c0c002c0020202023c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c80"
  [${VTPARSE_STATE_DCS_ENTRY}]="808080808080808080808080808080808080808080808080008000008080808027272727272727272727272727272727c8c8c8c8c8c8c8c8c8c806c82828282809090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090980"
  [${VTPARSE_STATE_DCS_IGNORE}]="8080808080808080808080808080808080808080808080800080000080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080000000000000000000000000000000000000000000000000000000000c"
  [${VTPARSE_STATE_DCS_INTERMEDIATE}]="8080808080808080808080808080808080808080808080800080000080808080202020202020202020202020202020200606060606060606060606060606060609090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090980"
  [${VTPARSE_STATE_DCS_PARAM}]="808080808080808080808080808080808080808080808080008000008080808027272727272727272727272727272727c0c0c0c0c0c0c0c0c0c006c00606060609090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090980"
  [${VTPARSE_STATE_DCS_PASSTHROUGH}]="e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e000e00000e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e080000000000000000000000000000000000000000000000000000000000c"
  [${VTPARSE_STATE_ESCAPE}]="50505050505050505050505050505050505050505050505000500000505050502b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c0f0g054c4c4c4c4c4c4c0e4c4c014c0d0e0e4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c5c"
  [${VTPARSE_STATE_ESCAPE_INTERMEDIATE}]="6060606060606060606060606060606060606060606060600060000060606060202020202020202020202020202020204c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c80"
  [${VTPARSE_STATE_GROUND}]="6060606060606060606060606060606060606060606060600060000060606060d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d06060606060606060606060606060606000606060606060606060600060"
  [${VTPARSE_STATE_OSC_STRING}]="8080808080808080808080808080808080808080808080800080000080808080a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0000000000000000000000000000000000000000000000000000000000c"
  [${VTPARSE_STATE_SOS_PM_APC_STRING}]="8080808080808080808080808080808080808080808080800080000080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080000000000000000000000000000000000000000000000000000000000c"
  [${VTPARSE_STATE_SS2_ENTRY}]="60606060606060606060606060606060606060606060606000600000606060600000000000000000000000000000000000000000000000000000000000000000fcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfc80"
  [${VTPARSE_STATE_SS3_ENTRY}]="606060606060606060606060606060606060606060606060006000006060606000000000000000000000000000000000chchchchchchchchchch000000000000gcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgc80"
  [${VTPARSE_STATE_SS3_PARAM}]="606060606060606060606060606060606060606060606060006000006060606000000000000000000000000000000000c0c0c0c0c0c0c0c0c0c0000000000000gcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgcgc80"
)

declare -gr VTPARSE_BASE="0123456789abcdefghijklmnopqrstuvwxyz"
declare -gr VTPARSE_ENTRY_ACTIONS="0100010007100b0110"

declare -gr VTPARSE_EXIT_ACTIONS="000000000h00090000"

  ord() {
    printf -v "${1?Missing Dest Variable}" "${3:-%d}" "'${2?Missing Char}"
  }
  ord_eascii() {
    LC_CTYPE=C ord "${@}"
  }
  substr() {
      local idx
      typeset -gi _INDEX
      case "${1}" in
        *${2}*)
            idx="${1%"${2}"*}";
            _INDEX=${#idx}
        ;;
          *)
            _INDEX=0
        ;;
      esac
  }




  declare -gr  MAX_INTERMEDIATE_CHARS=2

  declare -gi  vtparser_state
  declare -g   vtparser_intermediate_chars
  declare -ga  vtparser_params
  declare -gi  vtparser_ignore_flagged
  declare -g   vtparser_cb
  declare -g   vtparser_read


  vtparse_init() {
    vtparser_state="${VTPARSE_STATE_GROUND}"
    vtparser_intermediate_chars=""
    vtparser_params=( )
    vtparser_ignore_flagged=0
    vtparser_cb=${1?Missing Callback Function}
    vtparser_read=${2?Missing Read Function}
  }
  nop() {
    echo -n ""
  }
  do_action() {
    # Some actions we handle internally (like parsing parameters), others
    # we hand to our client for processing
    case "${1}" in
      ${VTPARSE_ACTION_ESC_EXECUTE}|${VTPARSE_ACTION_PRINT}|${VTPARSE_ACTION_EXECUTE}|${VTPARSE_ACTION_HOOK}|${VTPARSE_ACTION_PUT}|${VTPARSE_ACTION_OSC_START}|${VTPARSE_ACTION_OSC_PUT}|${VTPARSE_ACTION_OSC_END}|${VTPARSE_ACTION_UNHOOK}|${VTPARSE_ACTION_CSI_DISPATCH}|${VTPARSE_ACTION_ESC_DISPATCH}|${VTPARSE_STATE_SS2_ENTRY}|${VTPARSE_STATE_SS3_ENTRY})
        ${vtparser_cb} "${@}" || return $?
        ;;
      ${VTPARSE_ACTION_IGNORE})
        nop
        ;;
      ${VTPARSE_ACTION_COLLECT})
        if [ ${#vtparser_intermediate_chars} -ge ${MAX_INTERMEDIATE_CHARS} ]; then
            vtparser_ignore_flagged=1
        else
            vtparser_intermediate_chars+="${2}";
        fi
        ;;
      ${VTPARSE_ACTION_PARAM})
        # process the param character
        if [ "${2}" = ";" ]; then
            vtparser_params[${#vtparser_params[@]}]="";
        else
            # the character is a digit
            if [ ${#vtparser_params[@]} -eq 0 ]; then
                vtparser_params[0]=""
            fi
            vtparser_params[${#vtparser_params[@]}-1]+="${2}"
        fi
        ;;
      ${VTPARSE_ACTION_CLEAR})
        vtparser_intermediate_chars=""
        vtparser_params=( )
        vtparser_ignore_flagged=0
        ;;
      *)
        echo "Unknown action ${1}"
        ;;

    esac
    return 0
  }
  vtparse_conv_base() {
    local _INDEX
    substr "${VTPARSE_BASE}" "${2:0:1}"
    eval ${1}'="${_INDEX}"'
  }
  do_state_change() {
    # A state change is an action and/or a new state to transition to.
    local -i action
    local -i new_state
    local -i RValue=0

    vtparse_conv_base action "${1:0:1}"
    vtparse_conv_base new_state "${1:1:1}"
    #printf "action=%s new_state=%s vtparser_state=%s change=%s\n" "${action}"  "${new_state}"  "${vtparser_state}"  "${1}"

    if [ "${new_state:=0}" != "0" ]; then
        # Perform up to three actions:
        #   1. the exit action of the old state
        #   2. the action associated with the transition
        #   3. the entry actionk of the new action
        local -i exit_action
        local -i entry_action
        vtparse_conv_base exit_action  "${VTPARSE_EXIT_ACTIONS:${vtparser_state}:1}"
        vtparse_conv_base entry_action "${VTPARSE_ENTRY_ACTIONS:${new_state}:1}"
        [ "${exit_action}" = "0" ]  ||  do_action "${exit_action}" $'\x00' 0
        [ "${action}" = "0" ]       ||  do_action "${action}" "${2}" "${3}" || RValue=$?
        [ "${entry_action}" = "0" ] ||  do_action "${entry_action}" $'\x00' 0
        vtparser_state=${new_state}
        return ${RValue}
    else
        [ "${action}" = "0" ]       ||  do_action "${action}" "${2}" "${3}" || return $?
    fi
  }

  vtparse() {
      local -i _INDEX
      local REPLY
      while ${vtparser_read} 1 ; do
          ord_eascii _INDEX "${REPLY:0:1}"

          # If a transition is defined from the "anywhere" state, always
          # use that.  Otherwise use the transition from the current state.

          _INDEX=${_INDEX}*2  # 2 characters per entry
          local change="${VTPARSE_STATE_TABLE[${VTPARSE_STATE_ANYWHERE}]:${_INDEX}:2}"
          [ "${change:-00}" != "00" ] || change="${VTPARSE_STATE_TABLE[${vtparser_state}]:${_INDEX}:2}"
          _INDEX=${_INDEX}/2

          do_state_change "${change}" "${REPLY}" "${_INDEX}" || break
      done
  }
  vtparse_read_stdin() {
    read -srN${1:-1} -d '' "${@:2}"
  }
  declare -g vtparse_read_buffer
  vtparse_read_buffer() {
    REPLY="${vtparse_read_buffer:0:${1:-1}}"
    vtparse_read_buffer="${vtparse_read_buffer:${1}}"
    [ ${#REPLY} -eq ${1} ] || return $?
  }


  vtparser_callback_debug() {
    local CArg
    printf "Received action %s, char=0x%02x char=%q\n" "${VTPARSE_ACTION_NAMES[${1}]}" "'${2}" "${2}"
    printf "Intermediate chars: '%s'\n" "${vtparser_intermediate_chars}"
    printf "%d Parameters:\n"  ${#vtparser_params[@]}
    for CArg in "${vtparser_params[@]-}"; do
        printf "\t%d\n" "${CArg}"
    done
    printf "\n"
  }
  ###############################
  ##
  ##    READ KEY CRAP
  ##
  ##
  ###############################
  KeyModifiers=(
    [1]=""  [2]="S-"   [3]="A-"   [4]="AS-"   [5]="C-"   [6]="CS-"  [7]="CA-"    [8]="CAS-"
    [9]="M-" [10]="MS-" [11]="MA-" [12]="MAS-" [13]="MC-" [14]="MCS-" [15]="MCA-" [16]="MCAS-"
    )
  KeybFntKeys=(
    [1]="home" [2]="insert" [3]="delete"  [4]="end"   [5]="pageUp" [6]="pageDown"
    [11]="f1"  [12]="f2"    [13]="f3"     [14]="f4"   [15]="f5"
    [17]="f6"  [18]="f7"    [19]="f8"     [20]="f9"   [21]="f10"
    [23]="f11" [24]="f12"   [25]="f13"    [26]="f14"  [28]="f15"
    [29]="f16" [31]="f17"   [32]="f18"    [33]="f19"  [34]="f20"
    )
  SunKeybFntKeys=(
    [214]="home"  [2]="insert" [3]="delete" [4]="end"   [216]="pageUp" [222]="pageDown"
    [224]="f1"  [225]="f2"    [226]="f3"    [227]="f4"  [228]="f5"
    [229]="f6"  [230]="f7"    [231]="f8"    [232]="f9"  [233]="f10"
    [192]="f11" [193]="f12"   [218]="keypad-five" [220]="keypad-delete"
    )
  KeybFntKeysAlt=(
    # A          B              C               D             E                   F             H
    [0x41]="up" [0x42]="down" [0x43]="right" [0x44]="left" [0x45]="keypad-five" [0x46]="end" [0x48]="home"
    # I               O
    [0x49]="InFocus" [0x4f]="OutOfFocus"
    # P           Q           R           S             Z
    [0x50]="f1" [0x51]="f2" [0x52]="f3" [0x53]="f4"  [0x5a]="S-HT"
    )
  C0CtrlChars=(
    [0x00]="Null" [0x01]="SOH" [0x02]="STX" [0x03]="ETX" [0x04]="EOT" [0x05]="ENQ" [0x06]="ACK"
    [0x07]="BEL"  [0x08]="BS"  [0x09]="HT"  [0x0A]="LF"  [0x0B]="VT"  [0x0C]="FF"  [0x0D]="CR"
    [0x0E]="SO"   [0x0F]="SI"  [0x10]="DLE" [0x11]="DC1" [0x12]="DC2" [0x13]="DC3" [0x14]="DC4"
    [0x15]="NAK"  [0x16]="SYN" [0x17]="ETB" [0x18]="CAN" [0x19]="EM"  [0x1A]="SUB" [0x1B]="ESC"
    [0x1C]="FS"   [0x1D]="GS"  [0x1E]="RS"  [0x1F]="US"  [0x20]="SP"  [0x7F]="DEL"
  )
  C0CtrlCharsAlt=(
    [0x01]="C-A" [0x02]="C-B" [0x03]="C-C" [0x04]="C-D" [0x05]="C-E" [0x06]="C-F" [0x07]="C-G"
    [0x08]="C-H" [0x09]="C-I" [0x0a]="C-J" [0x0b]="C-K" [0x0c]="C-L" [0x0d]="C-M" [0x0e]="C-N"
    [0x0f]="C-O" [0x10]="C-P" [0x11]="C-Q" [0x12]="C-R" [0x13]="C-S" [0x14]="C-T" [0x15]="C-U"
    [0x16]="C-V" [0x17]="C-W" [0x18]="C-X" [0x19]="C-Y" [0x1a]="C-Z" [0x1b]="C-[" [0x1c]="C-]"
    [0x1d]="C-}" [0x1e]="C-^" [0x1f]="C-_" [0x20]="C-SP"  [0x7F]="DEL"
  )

  C1CtrlCharsEsc=(
    [0x40]="PAD"  [0x41]="HOP"  [0x42]="BPH" [0x43]="NBH"
    [0x44]="IND"  [0x45]="NEL"  [0x46]="SSA" [0x47]="ESA"
    [0x48]="HTS"  [0x49]="HTJ"  [0x4A]="VTS" [0x4B]="PLD"
    [0x4C]="PLU"  [0x4D]="RI"   [0x4E]="SS2" [0x4F]="SS3"
    [0x50]="DCS"  [0x51]="PU1"  [0x52]="PU2" [0x53]="STS"
    [0x54]="CCH"  [0x55]="MW"   [0x56]="SPA" [0x57]="EPA"
    [0x58]="SOS"  [0x59]="SGCI" [0x5A]="SCI" [0x5B]="CSI"
    [0x5C]="ST"   [0x5D]="OSC"  [0x5E]="PM"  [0x5F]="APC"
  )
  C1CtrlChars=(
    [0x80]="PAD"  [0x81]="HOP"  [0x82]="BPH" [0x83]="NBH"
    [0x84]="IND"  [0x85]="NEL"  [0x86]="SSA" [0x87]="ESA"
    [0x88]="HTS"  [0x89]="HTJ"  [0x8A]="VTS" [0x8B]="PLD"
    [0x8C]="PLU"  [0x8D]="RI"   [0x8E]="SS2" [0x8F]="SS3"
    [0x90]="DCS"  [0x91]="PU1"  [0x92]="PU2" [0x93]="STS"
    [0x94]="CCH"  [0x95]="MW"   [0x96]="SPA" [0x97]="EPA"
    [0x98]="SOS"  [0x99]="SGCI" [0x9A]="SCI" [0x9B]="CSI"
    [0x9C]="ST"   [0x9D]="OSC"  [0x9E]="PM"  [0x9F]="APC"
  )
  C1CtrlCharsAlt=(
    [0x01]="CA-A" [0x02]="CA-B" [0x03]="CA-C" [0x04]="CA-D"  [0x05]="CA-E" [0x06]="CA-F" [0x07]="CA-G"
    [0x08]="CA-H" [0x09]="CA-I" [0x0a]="CA-J" [0x0b]="CA-K"  [0x0c]="CA-L" [0x0d]="CA-M" [0x0e]="CA-N"
    [0x0f]="CA-O" [0x10]="CA-P" [0x11]="CA-Q" [0x12]="CA-R"  [0x13]="CA-S" [0x14]="CA-T" [0x15]="CA-U"
    [0x16]="CA-V" [0x17]="CA-W" [0x18]="CA-X" [0x19]="CA-Y"  [0x1a]="CA-Z" [0x1b]="CA-[" [0x1c]="CA-]"
    [0x1d]="CA-}" [0x1e]="CA-^" [0x1f]="CA-_" [0x20]="CA-SP" [0x7F]="A-DEL"
  )

  MouseButtons=(
    [0x00]="MB1-P" [0x01]="MB2-P" [0x02]="MB3-P" [0x03]="MB-R"
    [0x20]="MB1-M" [0x21]="MB2-M" [0x22]="MB3-M" [0x23]="MB-M"
    [0x40]="MB4-P" [0x41]="MB5-P"
  )
  MouseMetaButtons=(
    [0x04]="S-"    [0x08]="A-"    [0x0c]="AS-"
    [0x10]="C-"    [0x14]="CS-"   [0x1c]="CAS-"
  )

  AdjustMousePos() {
    local -i _INDEX
    ord_eascii _INDEX "${2}"
    eval ${1}'=$(( ${_INDEX}-32))'
  }
  GetMouseButton() {
    local MouseBtn
    AdjustMousePos MouseBtn "${2}"
    MouseBtn="${MouseMetaButtons[$(( ${MouseBtn} & 0x1C))]-}${MouseButtons[$(( ${MouseBtn} & 0xe3))]}"
    eval ${1}='"${MouseBtn}"'
  }

  vtparser_callback_readkey() {
    unset UInput[@]
    case "${1}" in
      ${VTPARSE_ACTION_ESC_EXECUTE})
        case "${2}" in
          [[:cntrl:]])
            UInput[0]="${C1CtrlCharsAlt[${3}]:-$(printf "%q" $'\e'"${2}")}"
            ;;
          *)
            UInput[0]="${C1CtrlChars[${3}]:-$(printf "%q" $'\e'"${2}")}"
            ;;
        esac
        ;;
      ${VTPARSE_ACTION_EXECUTE})
          UInput[0]="${C0CtrlChars[${3}]:-$(printf "%q" "${2}")}"
        ;;
      ${VTPARSE_ACTION_CSI_DISPATCH})
        case "${2}" in
          z) # Sun Function Keys
            UInput[0]="${SunKeybFntKeys[${vtparser_params[0]}]:-}"
            if [ -n "${UInput[0]}" ]; then
              UInput[0]="${KeyModifiers[${vtparser_params[1]:-1}]}${UInput[0]}"
              UInput[1]="1" # Repeat Count
            else
              UInput[0]="CSI ${vtparser_params[*]} ${2}"
            fi
            ;;
          '~') # Function Keys
            UInput[0]="${KeybFntKeys[${vtparser_params[0]}]}"
            if [ -n "${UInput[0]}" ]; then
              UInput[0]="${KeyModifiers[${vtparser_params[1]:-1}]}${UInput[0]}"
              UInput[1]="1" # Repeat Count
            else
              UInput[0]="CSI ${vtparser_params[*]} ${2}"
            fi
            ;;
          A|B|C|D|E|F|H|I|O|Z|P|Q|R|S)
            UInput[0]="${KeybFntKeysAlt[${3}]:-}"
            if [ -n "${UInput[0]}" ]; then
              UInput[0]="${KeyModifiers[${vtparser_params[1]:-1}]}${UInput[0]}"
              UInput[1]="${vtparser_params[0]:-1}" # Repeat Count
            else
              UInput[0]="CSI ${vtparser_params[*]} ${2}"
            fi
            ;;
          t)
            ${vtparser_read} 2
            UInput[0]="MouseTrack"
            AdjustMousePos UInput[1] "${REPLY:0:1}"
            AdjustMousePos UInput[2] "${REPLY:1:1}"
            ;;
          T)
            ${vtparser_read} 6
            UInput[0]="MouseTrack"
            AdjustMousePos UInput[1] "${REPLY:0:1}"
            AdjustMousePos UInput[2] "${REPLY:1:1}"
            AdjustMousePos UInput[3] "${REPLY:2:1}"
            AdjustMousePos UInput[4] "${REPLY:3:1}"
            AdjustMousePos UInput[5] "${REPLY:4:1}"
            AdjustMousePos UInput[6] "${REPLY:5:1}"
            ;;
          M)  # Mouse
            ${vtparser_read} 3
            GetMouseButton UInput[0] "${REPLY:0:1}"
            if [ -n "${UInput[0]}" ]; then
              AdjustMousePos UInput[1] "${REPLY:1:1}"
              AdjustMousePos UInput[2] "${REPLY:2:1}"
            else
              UInput[0]=$(printf 'Mouse-\\x%02x %q'  "'${escapeSequence:0:1}" "${escapeSequence:1}")
            fi
            ;;

          *)
            UInput[0]="CSI ${vtparser_params[*]} ${2}"
            ;;
        esac
        ;;
      ${VTPARSE_ACTION_SS3_DISPATCH})
        case "${2}" in
          A|B|C|D|E|F|H|P|Q|R|S|~)
            UInput[0]+="${KeybFntKeysAlt[${3}]}"
            if [ -n "${UInput[0]}" ]; then
              UInput[0]="${KeyModifiers[${vtparser_params[1]:-1}]}${UInput[0]}"
              UInput[1]="${vtparser_params[0]:-1}" # Repeat Count
            else
              UInput[0]="SS3 ${vtparser_params[*]} ${2}"
            fi
            ;;
          *)
            UInput[0]="SS3 ${vtparser_params[*]} ${2}"
            ;;
        esac
        ;;
      ${VTPARSE_ACTION_ESC_DISPATCH})
        case "${2}" in
          [][}^_A-Z]) UInput[0]="${C1CtrlChars[${3}]}" ;;
          [^[:cntrl:]]) UInput[0]="CA-${2}" ;;
          *) UInput[0]="${C1CtrlChars[${3}]:-$(printf "%q" $'\e'"${2}")}" ;;
        esac
        ;;
      ${VTPARSE_ACTION_PRINT})
        case "${2}" in
          [^[:cntrl:]]) UInput[0]="${2}" ;;
          *) UInput[0]="${C0CtrlChars[${3}]:-$(printf "%q" $'\e'"${2}")}" ;;
        esac
        ;;
      *)
        vtparser_callback_debug "${@}" || return $?
        ;;
    esac
    return 1

  }

   HandleKey() {
        vtparse_init vtparser_callback_readkey vtparse_read_stdin
        while true; do
                vtparse
                case "${UInput[0]}" in
                        LF)
                                echo "${UInput[@]}"
                                break
                                ;;
                        *up)       echo "Up Arrow ignoring modifier keys"  ;;
                        down)      echo "Down Arrow No modifier keys"      ;;
                        S-down)    echo "Shift + Down Arrow"               ;;
                        A-down)    echo "Alt + Down Arrow"                 ;;
                        AS-down)   echo "Alt + Shift + Down Arrow"         ;;
                        C-down)    echo "Control + Down Arrow"             ;;
                        CS-down)   echo "Control + Shift + Down Arrow"      ;;
                        CA-down)   echo "Control + Alt + Down Arrow"        ;;
                        CAS-down)  echo "Control + Alt + Shift + Down Arrow";;
                        MCAS-down) echo "Meta + Control + Alt + Shift + Down Arrow";;
                        *)
                                echo "${UInput[@]}"
                                ;;
                esac
        done

   }
HandleKey

KEYBD trap (ksh93)

ksh93 provides lower-level control over interactivity that is extensible directly in the shell language. The special KEYBD trap, if set, will fire on any keypress while exposing the necessary primitives to handle the event through special parameters.

.sh.edchar

The value of the character, or a character sequence if the first character was ESC. Writing to .sh.edchar within a KEYBD trap modifies the key that was pressed to trigger the trap.

.sh.edcol

Cursor position as of the most recent KEYBD trap.

.sh.edmode

set to ESC in vi insert mode, otherwise empty.

.sh.edtext

The text of the input buffer.

For example, after running trap 'printf %q\\n "${.sh.edchar}"' KEYBD, a representation of each key pressed will be printed back in shell-escaped format. Using this information, you can use the trap to call a function from an array of callbacks or commands to evaluate associated with certain keypresses or combinations.

TODO: example. This should be enough to get you going.

ReadingFunctionKeysInBash (last edited 2021-10-20 16:52:02 by EmanueleTorre)