4

I'm trying to write a simple little snippet of code to respond to an arrow key press. I know that up is represented by ^[[A, and I have the following code that checks for that sequence:

     while( 1 )
     {
         input_char = fgetc( stdin );

         if( input_char == EOF || input_char == '\n' )
         {
             break;
         }

         /* Escape sequence */
         if( input_char == 27 )
         {
             input_char = getc( stdin );

             if( input_char == '[' )
             {
                 switch( getc( stdin ) )
                 {
                     case 'A':
                     printf("Move up\n");
                     break;
                 }
             }
         }
     }

Whenever I hit "up", the escape sequence (^[[A) shows up on the screen, but "Move up" doesn't appear until I hit enter.

The end goal is to replace the text on the current line with some other data, and so I tried to do

printf("\r%s", "New Text");

in place of "Move up", but it still doesn't show up until after enter is pressed.

Is there something wrong with the way I'm reading in characters?

Thanks!

EDIT Quick note, it's for *nix systems.

SOLUTION Thanks for the pointers everyone. I went with stepanbujnak's solution because it was rather straightforward. The one thing I noticed is that a lot of the behavior of keys that modify the string ( backspace, etc ) is different than you would expect. It will backspace through ANYTHING on the line (including printf'd stuff), and I had to account for that. After that it wasn't too bad getting the rest to fall in line :)

Neil
  • 466
  • 1
  • 6
  • 15

3 Answers3

10

stdin is line buffered and hence getc(stdin) or fgetc(stdin) don't get to see those characters till you press ENTER See this link for more details

EDIT: If you don't want to get into ncurses there are other useful methods like setting the terminal to raw mode etc to overcome this limitation. Check this nice SO post

Capture characters from standard input without waiting for enter to be pressed

Community
  • 1
  • 1
Pavan Manjunath
  • 27,404
  • 12
  • 99
  • 125
  • That makes sense. From what that link said, it seems like there isn't a way to do it without curses? I would like to stay away from curses/ncurses if possible. Would POSIX file I/O allow the behavior I'm looking for? – Neil Apr 19 '12 at 13:35
4

You actually only need to disable line buffering using termios

Here's an example of doing it:

#include <stdio.h>
#include <stdlib.h>
#include <termios.h>

int main() {
  struct termios old_term, new_term;
  char c;

  /* Get old terminal settings for further restoration */
  tcgetattr(0, &old_term);

  /* Copy the settings to the new value */
  new_term = old_term;

  /* Disable echo of the character and line buffering */
  new_term.c_lflag &= (~ICANON & ~ECHO);
  /* Set new settings to the terminal */
  tcsetattr(0, TCSANOW, &new_term);

  while ((c = getchar()) != 'q') {
    printf("You pressed: %c\n", c);
  }

  /* Restore old settings */
  tcsetattr(0, TCSANOW, &old_term);

  return 0;
}
stepanbujnak
  • 651
  • 6
  • 16
  • This did it for me. It required some extra black magic to handle displaying the input and handling backspace and such the way I needed, but it worked with the least amount of hassle. thanks! – Neil Apr 19 '12 at 15:06
1

Look at the curses library for capturing escape sequences such as arrow keys.

http://tldp.org/HOWTO/NCURSES-Programming-HOWTO/keys.html

On most systems keys such as arrow keys, home, page up, break etc are escaped keys they use an escape sequence to discern themselves. Something like 0x1B + Sequence, if you wanted to capture it raw you'd need to read the input directly from the file descriptor and listen for the sequences. The alternative is above using ncurses.

Outside of using curses the following illustrates how to accomplish this using system call(s) such as read

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
    int fd = 0x0; /* STDIN */
    size_t bytes_read;
    char buf[12];

    bytes_read = read(fd, buf, 3);
    printf("%02x %02x %02x\n", buf[0], buf[1], buf[2]);
    return 0;
}

output after pressing UP

Lukes-ASA-Macbook:tmp luke$ gcc -o out test.c
Lukes-ASA-Macbook:tmp luke$ ./out
^[[A
1b 5b 41

This should get you on your way.

You can buffer the input looking for 0x1b and then enable a parsing flag to look for an escape sequence of characters in lieu of single character parsing.

lukecampbell
  • 14,728
  • 4
  • 34
  • 32
  • I'm trying to avoid ncurses/curses, so I'm going to try to switch my read functions to POSIX calls and see if that helps (unbuffered input and all that jazz) – Neil Apr 19 '12 at 13:42
  • So make an input read buffer like of size 12 and `read` some characters into the buffer then look for 0x1b (escape), when you see that: begin parsing for an escape sequence all other characters should be treated as ASCII. It's not too terribly difficult. – lukecampbell Apr 19 '12 at 13:44