Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

kbhit() alternative in unix

1,279 views
Skip to first unread message

Jonathan Salter

unread,
Mar 5, 2002, 6:34:25 AM3/5/02
to
I am writing a C program and want a neat way of exiting a program
instead of using control Z.
The kbhit() function is available for some DOS C programing environments
but is not part of ansi C or available for use within LINUX.

I need a function which periodically checks if any key has been pressed
on the keyboard and if it has -exit. Simple right?

I posted a question on the C newsgroup and it isn't supported in ansi C
because you can change stdio and not everybody has a keyboard.

For the application I am writing, every user has a keyboard and runs
linux in the same way so I'm not too worried about portability.

Is there a simple function to achieve this.

Cheers
Jon

Alexander Krisak

unread,
Mar 5, 2002, 7:15:25 AM3/5/02
to
Hello, Jonathan Salter

Jonathan Salter <salt...@cs.man.ac.uk> пишет в
сообщении:3C84AD41...@cs.man.ac.uk...

try to use curses/ncurses library, it's can give you such functionality and
it is
wide distributed in UNIX-world
keypad()/notimeout()/wgetch()

WBW,
Alexander mailto://akr...@mail.ru


David Schwartz

unread,
Mar 5, 2002, 7:31:11 AM3/5/02
to
Jonathan Salter wrote:

> Is there a simple function to achieve this.

The problem is three-fold:

1) You have to check if data is available without blocking. A simple
'read' or 'fgets' or whatever will block your process until data is
available -- you don't want that.

2) You have to bypass any buffering because otherwise you would have to
check both the buffer and the device.

3) You need to coerce the terminal driver to give you data as it's
available rather than accumulating the whole thing into a line.

That said, I present the following awkward, hastily-written,
uncommented code, which may be instructive or may not:

#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>

static struct termios orig_term;
void u_cleanup(void)
{
tcsetattr(0, TCSANOW, &orig_term);
}

int u_kbhit(void)
{
struct termios t;
int ret;
fd_set rfd;
struct timeval to;
static int first_hit=0;

if(first_hit==0)
{
if(tcgetattr(0, &t)!=0) exit(0);
orig_term=t;
cfmakeraw(&t);
if(tcsetattr(0, TCSANOW, &t)!=0) exit(0);
atexit(u_cleanup);
first_hit=1;
}

FD_ZERO(&rfd);
FD_SET(0, &rfd);
to.tv_sec=0;
to.tv_usec=0;
if(select(1, &rfd, NULL, NULL, &to)==1) return 1;
return 0;
}

int u_getchar(void)
{
int ret;
fd_set rfc;
unsigned char buf;

if(read(0, &buf, 1)!=1) ret=0;
else ret=buf;
return ret;
}

int main(void)
{
while(1)
{
if(u_kbhit())
{
int key=u_getchar();
printf("hit: %d\r\n", key);
if(key==3)
{
printf("you hit control-c\r\n");
exit(0);
}
}
usleep(100);
}
}

DS

Maurizio Loreti

unread,
Mar 5, 2002, 7:20:57 AM3/5/02
to
Jonathan Salter <salt...@cs.man.ac.uk> writes:

> I am writing a C program and want a neat way of exiting a program
> instead of using control Z.
> The kbhit() function is available for some DOS C programing environments
> but is not part of ansi C or available for use within LINUX.

> ...

Here follows a copy of an old post to this newsgroup. HTH...

--
Maurizio Loreti http://www.pd.infn.it/~loreti/mlo.html
Univ. of Padova, Dept. of Physics - Padova, Italy lor...@pd.infn.it


---------------------Begin forwarded message
From: fl...@tanana.polarnet.com (Floyd Davidson)
Newsgroups: comp.unix.programmer
Subject: Re: getche
Date: 29 Sep 1998 06:16:26 GMT
Lines: 234
Reply-To: fl...@ptialaska.net

Over the years I've posted the following functions several times
and have emailed them dozens of time; and several others have
posted other very similar functions, all of which demonstrate
the relatively easy way to get blocking single character input
with UNIX. The below program includes a demonstration main()
function, and in addition to getch() also has a kbhit()
function. There is nothing unique about the way that I've done
it, and any of the others that have been posted are essentially
the same, though the exact syntax or result may be slightly
different in each case.

Floyd

/*
* getch.c -- getch(), a blocking single character input from stdin
* kbhit(), a keyboard lookahead monitor
*/

/*
* getch() -- a blocking single character input from stdin
*
* This function has been tested on several SysV machines, SunOS4.1,
* BSDI UNIX, and Linux.
*
* BSD or POSIX systems may require a struct termios, older systems
* may require a struct termio, and some (Linux) may be able to use
* either.
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <sys/ioctl.h>

int getch(void);
int kbhit(void);

#define CMIN 1

#ifdef CTIME
#undef CTIME
#endif

#define CTIME 1

/*
* get a single character from stdin.
*
* First stdout is flushed, stdin is then switched to
* raw mode and input is waited for. When a single
* character is received from stdin the tty is restored
* to its original state and the character is returned.
* If the read from stdin fails, a -1 is returned.
*
* A conditional allows compiling with or without echo.
*/

int
getch(void)
{
char ch;
int error;
static struct termios Otty, Ntty;


fflush(stdout);
tcgetattr( 0, &Otty);
Ntty = Otty;

Ntty.c_iflag = 0; /* input mode */
Ntty.c_oflag = 0; /* output mode */

/* Change this conditional to enable echo of input character */
#if 1
Ntty.c_lflag = 0; /* line settings (no echo) */
#else
Ntty.c_lflag = ECHO; /* line settings (echo) */
#endif
Ntty.c_cc[VMIN] = CMIN; /* minimum time to wait */
Ntty.c_cc[VTIME] = CTIME;/* minimum characters to wait for */

#if 0
/*
* use this to flush the input buffer before blocking for new input
*/
#define FLAG TCSAFLUSH
#else
/*
* use this to return a char from the current input buffer, or block if
* no input is waiting.
*/
#define FLAG TCSANOW
#endif

if ( 0 == (error = tcsetattr( 0, FLAG, &Ntty))) {
error = read( 0, &ch, 1 ); /* get char from stdin */
error += tcsetattr(0, FLAG, &Otty); /* restore old settings */
}

return ( error == 1 ? (int) ch : -1 );
}


/*
* kbhit() -- a keyboard lookahead monitor
*
* returns the number of characters available to read, or -1 on error.
*
*/
int
kbhit(void)
{
int cnt = 0;
int error;
static struct termios Otty, Ntty;


tcgetattr( 0, &Otty);
Ntty = Otty;

Ntty.c_iflag = 0; /* input mode */
Ntty.c_oflag = 0; /* output mode */
Ntty.c_lflag &= ~ICANON; /* raw mode */
Ntty.c_cc[VMIN] = CMIN; /* minimum time to wait */
Ntty.c_cc[VTIME] = CTIME; /* minimum characters to wait for */

if (0 == (error = tcsetattr(0, TCSANOW, &Ntty))) {
error += ioctl(0, FIONREAD, &cnt);
error += tcsetattr(0, TCSANOW, &Otty);
}

return ( error == 0 ? cnt : -1 );
}


int
main(void)
{
int ch;
int cnt;

printf("*** Demonstrate kbhit() function ***\n\n");
printf("You must enter 10 characters: ");
fflush(stdout);
/* collect 10 characters plus allow for a newline */
while (1) {
if (10 <= (cnt = kbhit())) {
break;
}
}

printf("\n");

/* delete any 11th char, regardless of what it is */
if (cnt > 9) {
cnt = 10;
}

/* fetch the ten characters, and print them */
printf("The characters entered were: ");
while (cnt--) {
ch = getch();
printf("%c", ch);
}

while (1) {
printf("\n\n");

printf("*** Demonstrate getch() function ***\n\n");
printf(" Select Action\n");
printf(" 1 Run this program again.\n");
printf(" 2 Print this menu again.\n");
printf(" 3 Exit this program.\n\n");
printf(" Enter a selection: ?\b");
fflush(stdout);

while (1) {
switch (ch = getch()) {
case '1': printf("\n\n"); main();
case '3': printf("\n\n"); exit(EXIT_SUCCESS);
case '2': break;
default: continue;
}
break;
}
}

/* flush stdin before returning to the shell command line. */
while (cnt--) {
ch = getch();
if ('\n' == ch || '\r' == ch) {
break;
}
}

return EXIT_SUCCESS;
}

--
Floyd L. Davidson fl...@ptialaska.net
Ukpeagvik (Barrow, Alaska) fl...@barrow.com

Ilja Tabachnik

unread,
Mar 5, 2002, 7:50:23 AM3/5/02
to

If you will also need to do some character-based full-screen
output then you need curses library. Check your system's
'man curses' or 'man ncurses' for more information.

If all you need is to get single characters from the terminal
then tcsetattr()/tcgetattr() will help you. Check your system's
'man termios' for details.

On Linux also SLANG could be an option (I never used it myself).

Since you use Linux check Linux Serial-Programming-HOWTO which
contains some examples (guess you already have it on your system).

Also "Linux Application Development" book contains some discussion
on termios (and S-Lang). If you don't have one you can at least
look at the examples' sources: http://people.redhat.com/johnsonm/lad/
(maybe not easy to understand without the book).

And you could check section "3. Terminal I/O" of Unix Programming FAQ
(http://www.erlenstar.demon.co.uk/unix/faq_toc.html).

HTH

Ilja.

Victor Wagner

unread,
Mar 5, 2002, 2:14:29 PM3/5/02
to
Jonathan Salter <salt...@cs.man.ac.uk> wrote:
: I am writing a C program and want a neat way of exiting a program

: instead of using control Z.

Control-Z wouldn't exit your program on Unix. In most shells
it would stop it and allow to put it into background?

May be you mean Control-D which is conventional keypress for
sending process an end-of-file on stdin, or Ctrl-C which sends
your program an INT signal.

: The kbhit() function is available for some DOS C programing environments


: but is not part of ansi C or available for use within LINUX.


: I need a function which periodically checks if any key has been pressed
: on the keyboard and if it has -exit. Simple right?

You need to think about potential usage of your program.
On unix simple programs which read input on line by line basis
are seldom invoked interactively. They are rather feed from another
program or from files using IO redirection.

If such usage is feasble for your program, than you shouldn't try
to use something other than eof. Becouse in differnce to humans,
for programs eof (i.e closing pipe) is natural way to tell program
on other end of pipe that work is done.

If you think that your program is purely interactive, than you should
invent something more sophisticated than just reading from stdin.

If line-based interface is natural for your program (such as it is for
ftp client) than libreadline is way to go. In this case
natural way for exiting from the program is typing "quit" or "exit"
followed by <Enter>. It is convinient to accept abbreviation "q".

If you want to react to signle keystrokes rather than editable commands,
following by <Enter> than probably full-screen ncurses-based interface
is what you want.


--
When in doubt, parenthesize. At the very least it will let some
poor schmuck bounce on the % key in vi.
-- Larry Wall in the perl man page

Ralf Fassel

unread,
Mar 5, 2002, 3:14:42 PM3/5/02
to
* Jonathan Salter <salt...@cs.man.ac.uk>

| For the application I am writing, every user has a keyboard and runs
| linux in the same way so I'm not too worried about portability.

Every user on linux is used to ending processes via C-c, so why
bother?

R'

0 new messages