'Raw' Console I/O

Answer To: [23:49:44] <john> in c, is it possible to read character by character rather than line by line?

:!: Everything described here only applies to unix's (OS X, Linux, Solaris, ect…) For Windows see ReadConsoleInput() on MSDN.

Often, when one is making a console application you don't want to wait for [enter] to get input; rather, you want to get each and every character as its typed. The default mode (called canonical mode) sees input as lines - it will wait to act until the line is finished, by receiving a EOF, EOL or NL, before returning. The other option is non-canonical mode - in this mode, everything is left up to the application, and input is simply passed along.

To switch modes, the first thing we need to do is include termio.h, which defines the terminal I/O interface.

#include <stdio.h>
#include <termio.h>

We create a temporary termios structure to hold the existing terminal settings. termios is defined in termio.h.

struct termios temp = {0};
int    error;
char   ch;

To get the existing terminal settings, we use tcgetattr(). The first parameter is the file descriptor - POSIX defines 0 as stdin.

if((error = tcgetattr(0/*stdin*/,&temp)) < 0){
    printf("tcgetattr() failed {got '%d' expecting '0')\n",error);
    return 1;
}

We toggle off canonical mode with ICANON and echo with ECHO. We also set the minimum number of characters to read before returning.

    /* Turn off canonical mode */
    temp.c_lflag &= ~ICANON;
    /* Turn off character echo */
    temp.c_lflag &= ~ECHO;
    /* Minimum number of characters to read */
    temp.c_cc[VMIN] = 1;

The last step is to apply these new settings using tcsetattr(). As with tcgetattr(), the first parameter is a file descriptor. TCSANOW simply means to apply these changes immediately.

    /* Apply the new settings */
    if((error = tcsetattr(0/*stdin*/,TCSANOW, &temp)) < 0){
        printf("tcsetattr() failed {got '%d' expecting '0')\n",error);
        return 1;
    }

Example (C)

#include <stdio.h>
#include <termio.h>
 
int (*getch)(void) = &getchar;
 
int rawch(void){
    struct termios temp = {0};
    int    error;
    char   ch;
 
    if((error = tcgetattr(0/*stdin*/,&temp)) < 0){
        printf("tcgetattr() failed {got '%d' expecting '0')\n",error);
        return 1;
    }
 
    /* Turn off canocial mode */
    temp.c_lflag &= ~ICANON;
    /* Turn off character echo */
    temp.c_lflag &= ~ECHO;
    /* Minimum number of characters to read */
    temp.c_cc[VMIN] = 1;
 
    /* Apply the new settings */
    if((error = tcsetattr(0/*stdin*/,TCSANOW, &temp)) < 0){
        printf("tcsetattr() failed {got '%d' expecting '0')\n",error);
        return 1;
    }
 
    if((error = read(0/*stdin*/,&ch,1)) < 0){
        printf("read() failed {got '%d' expecting '1')\n",error);
        return 1;
    }
 
    /* Re-enable canocial mode and echo */
    temp.c_lflag |= ICANON;
    temp.c_lflag |= ECHO;
 
    if((error = tcsetattr(0/*stdin*/,TCSANOW, &temp)) < 0){
        printf("tcsetattr() failed {got '%d' expecting '0')\n",error);
        return 1;
    }
 
    return (int)ch;
 
}
 
int main(const int argc,const char* argv[]){
    if(argc > 1 && argv[1][0] == 'r'){
        getch = &rawch;       
    }
 
    printf("%c\n",getch());
 
    return 0;
}
 
raw.txt · Last modified: 2010/01/09 15:43 (external edit)
 
Except where otherwise noted, content on this wiki is licensed under the following license:Public Domain
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki