c++linuxbashshellscancodes

Linux keyboard scancode issues: For example, UP ARROW gives ^[[A


We've been struggling for a while now to understand the keyboard scancode behavior in Linux.

When we open a normal bash shell, the arrow keys works as expected: UP shows the previous item in the history etc. However when you spawn a process, arrows does not work as expected anymore. For example, UP prints ^[[A instead of the previous command.

To demonstrate this, do something like:

bash$ ping www.google.com

Now, press UP or DOWN etc. and you will see the wrongly mapped key codes while the process is running. However, when you terminate the process the arrow keys will work again.

We've tested it on CentOs, Ubuntu, Mac and even in different shells (bash, sh, zsh) and the same happens everywhere. I've also tried different keyboard modes using kbd_mode where we tested with RAW and XLATE modes.

The closest thing I could see while searching for answers were IPython users have experienced the same behavior when IPython was not build against readline. However, this is not related to our case as far as I can see.

We are developing a C++ Tcl based console application which uses cin and cout to communicate with, and get input from the user. We are having issues with the arrow keys when we try to access the history of previously entered commands. This is a major issue for us as 99% of people expects the arrow characters to just work.

Any ideas on how we could overcome this would be much appreciated.


Solution

  • You must set the terminal into raw mode to get the scan codes and handle them (that is: disable ICANON, you want the non-canonical mode). You probably also want to disable ECHO (so that it doesn't print your input on the terminal).

    That is what readline or linenoise are doing. See here for some code. Esp. see the function linenoiseEnableRawMode. Some other simple sample Python code is here where I have written a simple console media player which checks for the keypresses left ("\x1b[D") and right ("\x1b[C").

    The relevant calls are to tcgetattr (to get the current terminal state and modify that state struct to get into raw mode and enable some other useful behavior stuff) and tcsetattr (to set the state).

    readline, libedit or linenoise are some libraries which do all that work for you and provide you with history, autocompletion, etc.

    At the end, you must restore back the old state, otherwise you get the behavior you are describing in your shell.