How can I capture a key stroke immediately in linux? [duplicate]
Asked Answered
E

4

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.

Euchre answered 3/10, 2012 at 14:25 Comment(6)
Do you mean something like getch()?Aesop
@TheNewOne, getch() will echo that key, but what I want is to control the echo.Euchre
getch doesn't echo the key...Aesop
@TheNewOne, but, if getch() is a standard c function?Euchre
Posted an answer that can be found in the internet and linux forum under getch for gcc.Aesop
@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.Brey
B
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.

Brey answered 3/10, 2012 at 14:51 Comment(4)
It sounds fantastic, and there are so many things to learn.Euchre
@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.Brey
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).Guthry
@NominalAnimal Thank you for the correction. I've made the updates.Brey
S
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

Seating answered 3/10, 2012 at 16:46 Comment(0)
A
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

Aesop answered 3/10, 2012 at 14:34 Comment(6)
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.Aesop
Oh, it seems so complex, is there a simple way or a single function to do this?Euchre
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 97Aesop
by the way you can use <curses.h> 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).Aesop
I have tried curses' getch(), but it seems not work such well as the block of code in your answer, confusing.Euchre
@UniMouS See this question. Basically, you can't use curses' getch() by itself without initializing the rest of the curses' functionality.Jonahjonas
G
0

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

Glacial answered 3/10, 2012 at 14:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.