17

is it possible to have a cross-platform way to handle backspace and arrows keys within a C or OCaml program?

Actually an OCaml solution would be appreciated but many standard unix functions are wrapped directly to corresponding API calls so there's should be no problem in porting a C solution.

What I'm going to achieve is to catch the arrow keys to override its behaviour inside the shell (by repropting last line or operations like these). I think that this thing falls before the actual program and it's not handled by code itself so I don't know if it's possible.

The program is compiled either on Linux, OS X and Windows (on cygwin) so I would like to do it for all platforms..

Michael Ekstrand
  • 28,379
  • 9
  • 61
  • 93
Jack
  • 131,802
  • 30
  • 241
  • 343

4 Answers4

14

I've done something pretty similar recently (although my code is Linux only). You have to set stdin to non-canonical mode in order to read arrow key presses. This should work on OS X and Linux and will probably work on Cygwin although I can't say for sure.

open Unix

let terminfo = tcgetattr stdin in
let newterminfo = {terminfo with c_icanon = false; c_vmin = 0; c_vtime = 0} in
at_exit (fun _ -> tcsetattr stdin TCSAFLUSH terminfo); (* reset stdin when you quit*)
tcsetattr stdin TCSAFLUSH newterminfo;

when canonical mode is off, you don't need to wait for a newline in order to read from stdin. c_vmin represents the minimum numbers of characters to read before returning (you probably want to be able to read a single character at a time) and c_vtime is the maximum read wait time (in 0.1s units).

You might also want to set c_echo to false so that the arrow key presses are printed to the terminal (but then you'll have to manually print everything else.

Most terminals represent arrow key presses using ANSI escape sequences. If you run cat with no arguments and start hitting the arrow keys you can see the escape sequences used. They are typically

up - "\033[A"
down - "\033[B"
left - "\033[D"
right - "\033[C"

Where '\033' is the ascii value for esc

Chris
  • 26,361
  • 5
  • 21
  • 42
Niki Yoshiuchi
  • 16,883
  • 1
  • 35
  • 44
  • I almost managed to make this work, but how should I manage the manual echoing of the character? I ask this because the stdin is then forwarded to a parser which parses line by line and interprets it. I should place in the middle and print out every char? But then when the user presses the upkey it should replace the already printed out character to something different.. – Jack Nov 12 '10 at 14:09
  • Having reread your questions I think using readline/ledit is probably your best bet. But if you want to do things manually, then you're going to have to manually control the stdin of your parser. Read from stdin, check for an up-char escape sequence, right to your parser's stdin. I'm doing something similar here: https://github.com/aplusbi/reflow/blob/master/reflow.ml in the `main` function, where I'm reading stdin to a string and then writing it to a file_descr. You'd probably end up doing the same, but checking for escape sequences first. – Niki Yoshiuchi Nov 12 '10 at 15:17
  • those arrow characters are actually from ECMA 48 standartd here http://man7.org/linux/man-pages/man4/console_codes.4.html you can see which control characters of ECMA 48 does Linux support: it would appear that to move cursor left 5 characters you can do \027[5D – aeroson Nov 17 '16 at 09:39
  • @aeroson It appears not. The ESC key corresponds to ASCII code 27, which is 33 octal. Prefacing a number with 0 is often used to specify octal, so Niki's answer is correct when specifying \033. \027 would be Octal 23 = 23 decimal = ASCII ETB (End of Transmission Block), which likely has no special meaning for ECMA 48. – TOOGAM Apr 11 '19 at 09:45
  • More detailed anser: [here](https://stackoverflow.com/a/75499310/6013016) – Scott Feb 20 '23 at 00:10
5

Use ncurses to extract the sequences for the arrow key capabilities and then look for them when you read stdin. It's probably easier to use something like libedit or readline instead, and let that deal with the keyhandling.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • A couple further notes on the ncurses solution: on a single platform, it is cross-terminal, taking care of the hairy terminal differences for you. Also, there are versions available for Windows so you can have the editing features in Windows consoles as well. – Michael Ekstrand Nov 10 '10 at 13:58
2

The standard way of supporting keyboard input beyond lines of printable characters is through the ncurses library, which has an Ocaml binding. Another common possibility is the readline library (most famously used by Bash).

If all you're doing is reading input line by line, but want your users to have a decent line editor, there's no need to include any support in your program. Instead, just tell your users to use a wrapper program such as rlwrap (which is based on readline) or ledit. These wrappers do provide line edition and history, the two features you list as your requirements. I would recommend that you build input processing into your program only if you want something fancier, such as program-specific completion when the user presses Tab.

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
0

Backspace is an ASCII character and will be placed in stdin like any other character. The character escape sequence '\b' is the backspace character.

For cursor keys, these are not associated with any control character, so do not generate data in the stdin stream. Low level access is necessarily platform specific, although there are cross-platform libraries that abstract away the platform differences. I believe ncurses is available for all the platforms you mentioned.

Clifford
  • 88,407
  • 13
  • 85
  • 165
  • 1
    I'm pretty sure than any terminal in canonical mode will not place a backspace character into stdin. The text is buffered, so if you typed "abcd\b\n" you'd get "abc\n" in your program. – Niki Yoshiuchi Nov 10 '10 at 17:05
  • @Niki: Oops, I spend most of my time coding embedded systems connected to dumb-terminal emulator software. – Clifford Nov 10 '10 at 23:20