LinuxQuestions.org
Visit Jeremy's Blog.
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices

Reply
 
Search this Thread
Old 06-28-2011, 07:25 PM   #1
stf92
Senior Member
 
Registered: Apr 2007
Location: Buenos Aires.
Distribution: Slackware
Posts: 3,178

Rep: Reputation: 48
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

Last edited by stf92; 06-28-2011 at 07:27 PM.
 
Old 06-28-2011, 08:54 PM   #2
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942
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.
 
Old 06-28-2011, 10:08 PM   #3
stf92
Senior Member
 
Registered: Apr 2007
Location: Buenos Aires.
Distribution: Slackware
Posts: 3,178

Original Poster
Rep: Reputation: 48
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.

Last edited by stf92; 06-28-2011 at 10:37 PM.
 
Old 06-29-2011, 08:25 AM   #4
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942
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.
 
Old 06-30-2011, 01:25 AM   #5
stf92
Senior Member
 
Registered: Apr 2007
Location: Buenos Aires.
Distribution: Slackware
Posts: 3,178

Original Poster
Rep: Reputation: 48
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.
 
Old 07-01-2011, 02:54 PM   #6
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942
Quote:
Originally Posted by stf92 View Post
(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 View Post
(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.
 
1 members found this post helpful.
Old 07-01-2011, 03:50 PM   #7
stf92
Senior Member
 
Registered: Apr 2007
Location: Buenos Aires.
Distribution: Slackware
Posts: 3,178

Original Poster
Rep: Reputation: 48
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.

Last edited by stf92; 07-01-2011 at 03:51 PM.
 
Old 07-01-2011, 08:49 PM   #8
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942
Quote:
Originally Posted by stf92 View Post
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 View Post
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.
 
Old 07-01-2011, 09:03 PM   #9
stf92
Senior Member
 
Registered: Apr 2007
Location: Buenos Aires.
Distribution: Slackware
Posts: 3,178

Original Poster
Rep: Reputation: 48
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.
 
Old 07-02-2011, 02:36 AM   #10
stf92
Senior Member
 
Registered: Apr 2007
Location: Buenos Aires.
Distribution: Slackware
Posts: 3,178

Original Poster
Rep: Reputation: 48
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.
 
Old 07-02-2011, 01:22 PM   #11
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942
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.
 
1 members found this post helpful.
Old 07-02-2011, 06:43 PM   #12
stf92
Senior Member
 
Registered: Apr 2007
Location: Buenos Aires.
Distribution: Slackware
Posts: 3,178

Original Poster
Rep: Reputation: 48
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.
 
Old 07-02-2011, 09:10 PM   #13
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942
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
 
Old 07-02-2011, 11:06 PM   #14
wje_lq
Member
 
Registered: Sep 2007
Location: Mariposa
Distribution: Debian lenny, Slackware 12
Posts: 809

Rep: Reputation: 178Reputation: 178
Yo! Nominal Animal! Is there a man page which covers all this /dev/input stuff?
 
Old 07-03-2011, 05:55 PM   #15
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942Reputation: 942
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.

Last edited by Nominal Animal; 07-04-2011 at 02:40 AM. Reason: Added note about /usr/include/linux/input.h
 
2 members found this post helpful.
  


Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
saitek usb keyboard "input irq status -32 received" boffman Linux - Hardware 3 12-30-2007 08:46 PM
Repeated "input: AT Translated Set 2 keyboard as /class/input/input" messages AcerKev Mandriva 2 09-16-2007 08:35 AM
Error 17 GRUB Loading stage 1.5, please wait. No keyboard/touch pad response, Unable digital8doug Linux - Newbie 9 10-02-2006 03:14 PM
How do you set the keyboard wait-until-repeat delay? deanbrown3d Linux - Newbie 2 06-18-2004 09:35 AM
my mouse input is takes as keyboard input in BASH e1000 Slackware 5 12-08-2003 03:00 PM


All times are GMT -5. The time now is 03:58 AM.

Main Menu
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration