LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (http://www.linuxquestions.org/questions/programming-9/)
-   -   Read keyboard status (not: wait for keyboard input). (http://www.linuxquestions.org/questions/programming-9/read-keyboard-status-not-wait-for-keyboard-input-888869/)

stf92 06-28-2011 07:25 PM

Read keyboard status (not: wait for keyboard input).
 
Hi:

has bash a command that reads the keyboard status and exits? I want to write a loop of this form:
Code:

while [ 1 ] do
    sleep (1)
    if any key was pressed then
        break 
done


Nominal Animal 06-28-2011 08:54 PM

If you use Bash (version 4 or newer), then read -t 0 -N 0 will return true if there is stuff in the input buffer, false otherwise. By default, the terminal is line-oriented, but you can set it to unbuffered using stty .

Try this small Bash script:
Code:

#!/bin/bash

# This requires terminal input to work.
if ! tty -s ; then
    echo "$0 needs terminal input." >&2
    exit 1
fi

# Make sure terminal returns to the current mode at exit.
trap "stty `stty -g`" EXIT

# Set terminal unbuffered, don't echo input.
stty -icanon -echo

# Tell Bash to use NUL as separator.
IFS=$'\0'

# Drain input buffer.
while read -t 0 -N 0 ; do
    read -N 1 KEY
done

# Do something.
echo -n "Sleeping .. "
sleep 1
echo "done."

# Check if there is anything in the input buffer.
if read -t 0 -N 0 ; then

        # Drain input to KEYS array.
        KEYS=()
        while read -t 0 -N 0 ; do
                read -N 1 -r KEY
                KEYS=("${KEYS[@]}" "$KEY")
        done

        echo "${#KEYS[@]} keys (${KEYS[*]}) were pressed."
else
        echo "No key pressed."
fi

However, your do have a loop waiting for keyboard input. If sleep 1 is just a placeholder for some work, then I'd use
Code:

#!/bin/bash

# This requires terminal input to work.
if ! tty -s ; then
    echo "$0 needs terminal input." >&2
    exit 1
fi

# Make sure terminal returns to the current mode at exit.
trap "stty `stty -g`" EXIT

# Set terminal unbuffered, don't echo input.
stty -icanon -echo

# Tell Bash to use NUL as separator.
IFS=$'\0'

# Drain input buffer.
while read -t 0 -N 0 ; do
    read -N 1 KEY
done

# Work loop.
while ! read -t 0 -N 0 ; do

    # Do some work
    echo "Working."
    sleep 1

done

# Drain input buffer to KEYS array.
KEYS=()
while read -t 0 -N 0 ; do
        read -N 1 -r KEY
        KEYS=("${KEYS[@]}" "$KEY")
done

echo "Stopped work because you pressed '${KEYS[*]}'."

Note that the above assumes the real work takes measurable time, and that the user may manage to press more than one key during that time.

If you only want to wait until the user presses a key, use
Code:

#!/bin/bash

# This requires terminal input to work.
if ! tty -s ; then
    echo "$0 needs terminal input." >&2
    exit 1
fi

# Make sure terminal returns to the current mode at exit.
trap "stty `stty -g`" EXIT

# Set terminal unbuffered, don't echo input.
stty -icanon -echo

# Tell Bash to use NUL as separator.
IFS=$'\0'

# Drain input buffer.
while read -t 0 -N 0 ; do
    read -N 1 KEY
done

echo -n "Waiting for a keypress .. "

# Wait for ONE keypress; save it in KEY.
read -N 1 KEY

echo "You pressed '$KEY'."

Note that stty can be used to put the terminal in all kinds of fun modes, but the above does the minimal (I'm aware of) changes to make it unbuffered and disable echoing. Many examples I know recommend "raw" and "cooked" modes, but they're usually less useful for shell scripts. For one, they also disable interrupting the script with Ctrl-C, and my examples do not. Finally, many examples use "cooked" mode to return to normal, but that may b0rk certain terminals (it does mine, and I'm too lazy to fix the entry in the terminal database); my examples use the exit trap to restore whatever terminal mode was used when the script started.

I hope you found this useful.

stf92 06-28-2011 10:08 PM

This is a beautiful post, and does not deserve an answer like this. However, my bash is 3.1.17. Very unlucky for me. But I always can fall back on C. Although with the same interrogative. The algorithm is still the same.
Code:


while true do
    wait for 1 s /* nothing to be done except wait */
    if any key was pressed then
        break 
    else
        continue
done

If the algorithm is confusing, please tell me. I do NOT want to wait for keyboard input. I would rather tell the whole story, so that the algorithm does not need a modificaiton later.

The program has to be a daemon (if in bash, it would have been run in the background). The daemon mission is, when I wake up, to be inquired about the last time there was keyboard activity. One of the posible approaches is: I have oldtime and newtime. And, this time waiting for keyboard
Code:


1. wait for key;
2. oldtime= newtime
3. newtime= current time
4. goto step 1

A special key or key combination would print oldtime in stdout.

Thanks a lot.

Nominal Animal 06-29-2011 08:25 AM

You mean something like this?
Code:

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <time.h>
#include <linux/input.h>
#include <errno.h>

static inline double seconds(struct timeval const t)
{
        return (double)t.tv_sec
            + (double)t.tv_usec / 1000000.0;
}

static char *timestamp(struct timeval t)
{
        static char        buffer[1024];

        struct tm        *tm = localtime(&t.tv_sec);
        size_t                n;

        n = strftime(buffer, sizeof(buffer) - 8, "%Y-%d-%m %H:%M:%S", tm);
        n += snprintf(buffer + n, sizeof(buffer) - n - 1, ".%06d", (int)t.tv_usec);
        buffer[n] = 0;

        return (char *)buffer;       
}

int main(int argc, char *argv[])
{
        double const                interval = 1.0; /* Seconds */
        char const                *input;
        int                          inputfd;
        struct input_event        event;
        double                  prev, curr, since;
        ssize_t                        bytes;

        if (argc == 2)
                input = argv[1];
        else
                input = "/dev/input/event0";

        do {
                inputfd = open(input, O_RDWR);
        } while (inputfd == -1 && errno == EINTR);
        if (inputfd == -1) {
                fprintf(stderr, "%s: Cannot open input device.\n", input);
                return 1;
        }

        gettimeofday(&event.time, NULL);
        curr = since = seconds(event.time);

        while(1) {
                do {
                        bytes = read(inputfd, &event, sizeof(event));
                } while (bytes == (ssize_t)-1 && errno == EINTR);
                if (bytes != (ssize_t)sizeof(struct input_event))
                        break;

                prev = curr;
                curr = seconds(event.time);
                if (curr < since || curr - since >= interval) {
                       
                        printf("%s: %.3f seconds since previous keypress\n",
                                timestamp(event.time), curr - prev);
                        fflush(stdout);
                        since = curr;
                }
        }

        /* Never reached */
        close(inputfd);
        return 0;
}

You need to run it as root, and specify one of the /dev/input/eventN devices as a parameter. For me,
Code:

gcc -Wall -O3 -o input-monitor keyboard-monitor.c
sudo ./input-monitor /dev/input/event4

works. Unlike the shell script stuff, this uses the kernel input subsystem directly, and is thus works independently of the focus and even X11. It literally watches for keyboard events.

It would be better to monitor all keyboard (EV_KEY) events (see /usr/include/linux/input.h) on all (/dev/input/event*) devices (via poll() or select()), but I'll let you expand and adapt this to your needs. Let me know if you have any questions.

stf92 06-30-2011 01:25 AM

Hi: I think I've got it:
Code:


cur      since      prev
=========================
3        3          3
8                  8
1000                1000
10000
prints time I woke up and went to the machine and time elapsed.

Interval= 3600*10 would be a candidate value. since is a constant reference. cur < since just a precaution. The arg[] thing supplies the arg in case I did not. Now some doubts:

(a) type struct input_event: 'cause &event is 1st argument in gettimeofday, and 1st arg in gettimeofday is type *(struct timeval), I get
Code:

type &event == type *(struct timeval)
hence
Code:

type event ==type **(struct timeval)
hence
Code:

type input_event == type **(struct timeval)
. But really this is a question: what type is input_event?

(b) What really needs clarification is the do just after while (1). Of course, if not an external interrupt and some other condition, the do is exited. (ssize_t)-1 is a typecast for -1, which is false. A little confussion here. And -1 && anything == -1.

I wanna bother you anymore for this time. And thanks for the lesson.

Nominal Animal 07-01-2011 02:54 PM

Quote:

Originally Posted by stf92 (Post 4399940)
(a) type struct input_event: 'cause &event is 1st argument in gettimeofday

No. struct input_event is defined in /usr/include/linux/input.h as
Code:

struct input_event {
        struct timeval time;
        __u16 type;
        __u16 code;
        __s32 value;
};

I quite simply used gettimeofday( time field of event ) as a temporary storage for the initial time in seconds. Remember that
&event.time
is exactly the same as
&(event.time)
and ends up being a pointer to a struct timeval.

Perhaps I should have used a temporary variable instead. The value is only used as a parameter for the conversion to floating-point seconds on the next line. I needed to do that because I didn't want to special-case the very first event; otherwise there would be no previous time to compare to for the very first event.

The type field will match one of the EV_constants defined in the same header file. The interpretation of code and value fields depend on the type, but their values are also listed in the same header file. For example, EV_KEY are keyboard events, with KEY_ constants specifying the corresponding code field values.

Quote:

Originally Posted by stf92 (Post 4399940)
(b) What really needs clarification is the do just after while (1).

You mean this bit of code?
Code:

                do {
                        bytes = read(inputfd, &event, sizeof(event));
                } while (bytes == (ssize_t)-1 && errno == EINTR);
                if (bytes != (ssize_t)sizeof(struct input_event))
                        break;

The do { } while ( ) loop is used to redo the read whenever it returns -1 with errno set to EINTR . This can happen for a number of reasons, although signals are, as you said, the typical reason.

Because it is likely that ssize_t is a larger integer type than int, I explicitly cast the -1. The difference is the same as between -1 and -1LL for example. It makes it explicit (to the programmer, mostly, although I think there are braindead C compilers that get this wrong) that bytes is not compared against an int, but against a -1 of a sufficiently large signed integer type. (In this case, there is no possibility of a misunderstanding, since you will never have large enough ssize_t bytes for (int)bytes == -1 . I'm just careful this way.)

The conditional clause makes sure only reads that returned with a single complete event are handled. The kernel is only supposed to give out complete events, so the above is a bit overboard. You can get the same functionality (except ignoring any mangled packets a bad kernel might give) with simply
Code:

                bytes = read(inputfd, &event, sizeof(event));
                if (bytes != (ssize_t)sizeof(struct input_event))
                        continue;

The reason I use sizeof(event) instead of the more logical sizeof(struct input_event) is the fact that if somebody changes the type or size of event , the code will just stop working (until the above check is fixed to match), [I]and not cause a buffer overrun if event were smaller than a struct input_event.

stf92 07-01-2011 03:50 PM

Thanks for your kind reply. Question (b) was only caused by lack of attention. I read

((ssize_t)-1 && errno == EINTR)) which of course is always -1 and did not pay attention to

bytes ==

on the left.

I augmented the interval constant because that seemed what had to be done. The program compiled perfectly well with your command line, but did not performed as expected either with interval = 1 or interval = 3600*10. What should really do this program?

I went to VT 1 where I had logged in as root, and did
Code:

./input-monitor /dev/input/event4
However, nothing happened except that, in VT 1 I had no prompt. Perhaps I do not get the meaning of /dev/input/event4. Well, that's all. Regards.

Nominal Animal 07-01-2011 08:49 PM

Quote:

Originally Posted by stf92 (Post 4401755)
I augmented the interval constant because that seemed what had to be done. The program compiled perfectly well with your command line, but did not performed as expected either with interval = 1 or interval = 3600*10.

The interval specifies the interval between outputs. If multiple events happen in quick succession, only the first one is reported, until at least interval seconds elapses.

Quote:

Originally Posted by stf92 (Post 4401755)
What should really do this program?

It monitors one event stream from the kernel input subsystem, but tries to avoid flooding (outputting too much) by skipping events that happen within interval seconds of the last reported event. Run
Code:

ls -l /dev/input/by-id/ /dev/input/by-path/
and you will see which devices are connected to which event streams. My keyboard happens to be connected to event4:
Code:

lrwxrwxrwx 1 root root 9 2011-07-01 19:41 /dev/input/by-path/platform-i8042-serio-0-event-kbd -> ../event4
Just adjust the event device name accordingly, as your keyboard is connected somewhere else.

stf92 07-01-2011 09:03 PM

I'll do these things (restore interval and find the keyboard event. And let you know. I see what I intended to do is much more complex than I had thought. Thanks.

stf92 07-02-2011 02:36 AM

How heavy a load! In my box it's .../event1. I ran it successfully. But ... always a but: If I run it from tty1, then it catches events from tty1. What about ttyN, N= 2,...,6 kbd events? Well, I made it run, thanks to you.

Nominal Animal 07-02-2011 01:22 PM

Actually, it does catch all the events; it's just the program that gets stopped while you switch to another tty.

If you run it via
Code:

( sudo setsid ./input-monitor /dev/input/event1 </dev/null &>/tmp/input.log & )
then it will be detached from your terminal (i.e. won't get stopped when you switch terminals, or killed when you log out), and output to /tmp/input.log instead.

stf92 07-02-2011 06:43 PM

Hi:

The last '&' makes it run in the background. But doing 'jobs' it does not show anything. So, I cannot kill it. I wanted to kill it in order to run it without the parentesis and see what happens.

Also, in the session started by setsid, stdin is redirected to /dev/null.

To make input.log less bulky, I'll set interval to 60s. Even 5m would be fine for me. Thanks a lot.

Nominal Animal 07-02-2011 09:10 PM

It won't show up in 'jobs' because it is no longer a task in the current shell; it has been daemonized. Use
Code:

ps -C input-monitor
to find the pid (listed first) of the process, then
Code:

sudo kill pid
to kill it. Or, if you have the psmisc package installed, just run
Code:

sudo killall input-monitor

wje_lq 07-02-2011 11:06 PM

Yo! Nominal Animal! Is there a man page which covers all this /dev/input stuff?

Nominal Animal 07-03-2011 05:55 PM

For everybody who is interested in this, I'd like to remind that this is the low-level interface to the input subsystem of the Linux kernel. It is exceedingly rare that this is what you actually want to use, because all higher-level interfaces like X (and SDL, for example) provide very good, and widely compatible interfaces and APIs for this.

This is useful stuff if you work with a machine or test board without X, for example on a bare framebuffer, and want to do e.g. multitouch. Or if you have a machine with a number of independent keyboards connected to it, and you want to deal with them separately. (Note that X should be able to do this natively, for multiseat.) Or if it is your own desktop machine, and you're not afraid to delve deeply into the internals to try out something, like OP stf92 here.

Because of this, there is of course no man page; it is a kernel-level interface, after all. Very Linux-specific, too. I haven't personally verified this, but my understanding is that you can use the very same interface to inject events into the kernel subsystem by simply writing suitable input_event structures to the desired event device.

The Linux Journal had a nice article about the input subsystem (part I and II) in 2003. Not much has changed since. I just glanced thorough, but the info in it seems still to be quite accurate. The userspace stuff is in the second part.

For the gritty technical details, there are a number of files in the Documentation/input directory in Linux kernel sources (input.txt, event-codes.txt, ff.txt, multi-touch-protocol.txt) that provide insight into how the subsystem works.

Other than that, there is not much to it, after all. Aside from the input events there are a few ioctl()s described in the links above you can use for interface versioning, device queries, and so on, but that's about it.

Forgot to add: There is of course the /usr/include/linux/input.h file I mentioned earlier, which contains the structure definitions and constants needed.


All times are GMT -5. The time now is 06:15 AM.