9

Possible Duplicate:
What is equivalent to getch() & getche() in Linux?

I'm a newbie in linux programming. :-)

I just wonder if my program (using C language) can capture every single key stroke immediately in linux, and then decide whether to echo it or not and how to display it, just like telnet.

for example, if a user strikes 'A', I want the program to display 'B', if he types 'B', 'C' is wanted, and so on.

It sounds funny and useless. I am just curious about it.

Community
  • 1
  • 1
Yishu Fang
  • 9,448
  • 21
  • 65
  • 102
  • Do you mean something like `getch()`? – TheNewOne Oct 03 '12 at 14:28
  • @TheNewOne, getch() will echo that key, but what I want is to control the echo. – Yishu Fang Oct 03 '12 at 14:32
  • getch doesn't echo the key... – TheNewOne Oct 03 '12 at 14:33
  • @TheNewOne, but, if getch() is a standard c function? – Yishu Fang Oct 03 '12 at 14:37
  • Posted an answer that can be found in the internet and linux forum under getch for gcc. – TheNewOne Oct 03 '12 at 14:38
  • 1
    @UniMouS There's a number of ways to approach the problem, depending on details which you will decide as you work on it. The main problem is that often one approach will not be structured in a manner that will allow you to expand upon it easily if you need more features than are available with that approach. To get more features, you typically have to dig deeper and that means partially reimplementing a lot of stuff that's in standard libraries because the library does just a little more than you want. – Edwin Buck Oct 03 '12 at 15:13

4 Answers4

13

Basically, it depends heavily on how you define immediately.

There are two tasks here. The first is to disable the regular key echoing that is built into most C input libraries. The second is to print out the new character instead of the old one.

In pseudo code.

 echo(off);
 while (capturing && charIsAvailable()) {
   c = readOneChar();
   if (c == '\n') {
     capturing = false;
   }
   printf("%c", c++);
 }
 echo(on);

There are a number of systems communicating to capture a key press.

  1. The keyboard
  2. (possibly) a USB bus
  3. The CPU interrupt handler
  4. The operating system
  5. The X window server process
  6. The X "window" that has focus.

The last step is done with a program that runs a continuous loop that captures events from the X server and processes them. If you wanted to expand this program in certain ways (get the length of time the key was pressed) you need to tell the other programs that you want "raw" keyboard events, which means that you won't really be receiving fully "cooked" characters. As a result, you will have to keep track of which keys are up and down, and how long, and handle all the odd meta key behavior in your program (that's not an 'a' it's a 'A' because shift is down, etc).

There are also other processing modes to consider, like canonical and non-canonical, which will control whether you wish the events to be received in line oriented chunks (line events) or character oriented chunks (character events). Again this is somewhat complicated by the need to make the upstream programs aware of the requirements of the downstream client.

Now that you have some idea of your environment, let's revisit the actual code needed to suppress character output.

// define a terminal configuration data structure
struct termios term;

// copy the stdin terminal configuration into term
tcgetattr( fileno(stdin), &term );

// turn off Canonical processing in term
term.c_lflag &= ~ICANON;

// turn off screen echo in term
term.c_lflag &= ~ECHO;

// set the terminal configuration for stdin according to term, now
tcsetattr( fileno(stdin), TCSANOW, &term);


(fetch characters here, use printf to show whatever you like)

// turn on Canonical processing in term
term.c_lflag |= ICANON;

// turn on screen echo in term
term.c_lflag |= ECHO;

// set the terminal configuration for stdin according to term, now
tcsetattr( fileno(stdin), TCSANOW, &term);

Even this is not immediate. To get immediate, you need to get closer to the source, which eventually means a kernel module (which still isn't as immediate as the keyboard micro-controller, which isn't as immediate as the moment the switch actually closes). With enough items in between the source and the destination, eventually it becomes possible to notice the difference, however, in practice this code has been worked on a lot by people who are seeking the best tradeoff between performance and flexibility.

Edwin Buck
  • 69,361
  • 7
  • 100
  • 138
  • 3
    It sounds fantastic, and there are so many things to learn. – Yishu Fang Oct 03 '12 at 15:09
  • 3
    @UniMouS There always is, and stdin input processing is wrapped by such nice libraries that you really don't get to appreciate how much is going on behind the covers until you have to do something special. – Edwin Buck Oct 03 '12 at 15:24
  • 1
    Edwin Buck, you probably want to change the first parameter for the `tcgetattr()` and `tcsetattr()` calls to `fileno(stdin)` (which is in POSIX, use `#define _POSIX_SOURCE`). – Nominal Animal Oct 03 '12 at 16:47
  • @NominalAnimal Thank you for the correction. I've made the updates. – Edwin Buck Oct 03 '12 at 16:52
6

Edwin Buck provided a great answer. Here is another alternative that uses ncurses which makes such things a bit more easy and is supported on nearly every Linux distribution and other Unix variants.

#include <ncurses.h>

int main(int argc, char *argv[])
{
    int ch,c;
    initscr();
    cbreak();
    noecho();

    while( ch != 'q')
    {
        switch( c = getch() )
        {
            case 'a':
                ch = 'b';
            break;

            case 'b':
                ch = 'c';
            break;

            case 'c':
                ch = 'd';
            break;

            default:
                ch = c;                 
            break;
        }
        printw("%c",ch);
    }
    endwin();
    return(0);
}

compile with gcc code.c -o code -lncurses

And here are some supporting links:

ncurses noecho and other - man page
ncurses programming HOWTO

Community
  • 1
  • 1
Chimera
  • 5,884
  • 7
  • 49
  • 81
5

This code can be found in the internet and it's by kermi3 from c-board

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

int mygetch(void)
{
    struct termios oldt,newt;
    int ch;
    tcgetattr( STDIN_FILENO, &oldt );
    newt = oldt;
    newt.c_lflag &= ~( ICANON | ECHO );
    tcsetattr( STDIN_FILENO, TCSANOW, &newt );
    ch = getchar();
    tcsetattr( STDIN_FILENO, TCSANOW, &oldt );
    return ch;
}

EDIT: of course that if you want to get the char itself and not the ascii value change to char instead of int

Felipe
  • 16,649
  • 11
  • 68
  • 92
TheNewOne
  • 1,613
  • 11
  • 19
  • I haven't mean runs like executes, I meant that it can be found in the internet. *EDIT*: i changed it in order to clarify. – TheNewOne Oct 03 '12 at 14:37
  • Oh, it seems so complex, is there a simple way or a single function to do this? – Yishu Fang Oct 03 '12 at 14:41
  • 1
    you copy this function, and then you call it mygetch().the int which is returned is the ascii value. if a was typed for instance, you will get 97 – TheNewOne Oct 03 '12 at 14:43
  • by the way you can use and use getch() - char ch=getch(), ch would contain the char that was typed (that's maybe the most simple way :) you need to compile it by adding -lcurses at the of the gcc command). – TheNewOne Oct 03 '12 at 14:47
  • I have tried curses' getch(), but it seems not work such well as the block of code in your answer, confusing. – Yishu Fang Oct 03 '12 at 15:12
  • @UniMouS See [this question](http://stackoverflow.com/questions/7410447/why-getch-returns-before-press-any-key/). Basically, you can't use curses' getch() by itself without initializing the rest of the curses' functionality. – luser droog Oct 03 '12 at 17:47
0

it may be overkill but you can use game libraries such as sdl to achieve that.

none
  • 11,793
  • 9
  • 51
  • 87