How to detect keystrokes in a Linux C GUI program without prompting the user?

how to detect keyboard event in C without prompting user in Linux? That is, the start of the program must be completed by pressing any key. can anyone help with this?

+2


source to share


4 answers


You need to change your terminal settings with termios. See Stevens and Rago 2nd Ed Advanced Programming in UNIX for an explanation of why tcsetattr () can return successfully without setting all terminal characteristics, and why you can see what looks like a redundant call to tcsetattr ().

This is ANSI C on UNIX:



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

int checktty(struct termios *p, int term_fd)
{
    struct termios ck;
    return (
       tcgetattr(term_fd, &ck) == 0 &&
      (p->c_lflag == ck.c_lflag) &&
      (p->c_cc[VMIN] == ck.c_cc[VMIN]) &&
      (p->c_cc[VTIME] == ck.c_cc[VMIN])
    );
}


int
keypress(int term_fd)
{
     unsigned char ch;
   int retval=read(term_fd, &ch, sizeof ch);
   return retval;
}

int   /* TCSAFLUSH acts like fflush for stdin */
flush_term(int term_fd, struct termios *p)
{
   struct termios newterm;
   errno=0;
   tcgetattr(term_fd, p);  /* get current stty settings*/

   newterm = *p; 
   newterm.c_lflag &= ~(ECHO | ICANON); 
   newterm.c_cc[VMIN] = 0; 
   newterm.c_cc[VTIME] = 0; 

   return( 
       tcgetattr(term_fd, p) == 0 &&
       tcsetattr(term_fd, TCSAFLUSH, &newterm) == 0 &&
       checktty(&newterm, term_fd) != 0
   );
}
void 
term_error(void)
{
     fprintf(stderr, "unable to set terminal characteristics\n");
     perror("");                                                
     exit(1);                                                   
}


void
wait_and_exit(void)
{
    struct timespec tsp={0,500};  /* sleep 500 usec (or likely more ) */
    struct termios  attr;
    struct termios *p=&attr;
    int keepon=0;
    int term_fd=fileno(stdin);

    fprintf(stdout, "press any key to continue:");
    fflush(stdout);
    if(!flush_term(term_fd, p) )
       term_error();
    for(keepon=1; keepon;)
    {
        nanosleep(&tsp, NULL);
        switch(keypress(term_fd) )
        {
              case 0:
              default:
                 break;
            case -1:
                 fprintf(stdout, "Read error %s", strerror(errno));
                 exit(1);
                 break;
            case 1:       /* time to quit */
                 keepon=0;
                 fprintf(stdout, "\n");
                 break;                 
        } 
    }
    if( tcsetattr(term_fd, TCSADRAIN, p) == -1 && 
          tcsetattr(term_fd, TCSADRAIN, p) == -1 )
          term_error();
    exit(0);
}

int main()
{
      wait_and_exit();
      return 0;  /* never reached */
}

      

The nano-mud call is to prevent code-copying of system resources. You can call nice () and not use nanosleep (). All of this is to sit and wait for a key press and then exit.

+6


source


If you want to do this in a graphical application, you must use some libraries for this.

Such a simple task can be easily accomplished with any library (even low-level ones like Xlib).



Just pick one and look for a tutorial that shows how to handle keyboard events.

+1


source


not with ANSI C. Look at the ncurses lib.

+1


source


Heres code from /usr/src/bin/stty/key.c

:

f_cbreak(struct info *ip)
{

        if (ip->off)
                f_sane(ip);
        else {
                ip->t.c_iflag |= BRKINT|IXON|IMAXBEL;
                ip->t.c_oflag |= OPOST;
                ip->t.c_lflag |= ISIG|IEXTEN;
                ip->t.c_lflag &= ~ICANON;
                ip->set = 1;
        }
}

      

At a minimum, you must exit mode ICANON

before syscall select(2)

or yours is executed FIONREAD ioctl

.

I have an old, 20 year old perl4 program that cleans up CBREAK and ECHO this way. He makes a curse without resorting to the curse library:

sub BSD_cbreak {
    local($on) = shift;
    local(@sb);
    local($sgttyb);
    # global $sbttyb_t 

    $sgttyb_t = &sgttyb'typedef() unless defined $sgttyb_t;

    # native BSD stuff by author (tsc)

    ioctl(TTY,&TIOCGETP,$sgttyb)
        || die "Can't ioctl TIOCGETP: $!";

    @sb = unpack($sgttyb_t,$sgttyb);
    if ($on) {
        $sb[&sgttyb'sg_flags] |= &CBREAK;
        $sb[&sgttyb'sg_flags] &= ~&ECHO;
    } else {
        $sb[&sgttyb'sg_flags] &= ~&CBREAK;
        $sb[&sgttyb'sg_flags] |= &ECHO;
    }
    $sgttyb = pack($sgttyb_t,@sb);
    ioctl(TTY,&TIOCSETN,$sgttyb)
            || die "Can't ioctl TIOCSETN: $!";
}


sub SYSV_cbreak {
    # SysV code contributed by Jeff Okamoto <okamoto@hpcc25.corp.hp.com>

    local($on) = shift;
    local($termio,@termio);
    # global termio_t ???

    $termio_t = &termio'typedef() unless defined $termio_t;

    ioctl(TTY,&TCGETA,$termio)
       || die "Can't ioctl TCGETA: $!";

    @termio = unpack($termio_t, $termio);
    if ($on) {
        $termio[&termio'c_lflag] &= ~(&ECHO | &ICANON);
        $termio[&termio'c_cc + &VMIN] = 1;
        $termio[&termio'c_cc + &VTIME] = 1;
    } else {
        $termio[&termio'c_lflag] |= (&ECHO | &ICANON);

        # In HP-UX, it appears that turning ECHO and ICANON back on is
        # sufficient to re-enable cooked mode.  Therefore I'm not bothering
        # to reset VMIN and VTIME (VEOF and VEOL above).  This might be a
        # problem on other SysV variants.

    }
    $termio = pack($termio_t, @termio);
    ioctl(TTY, &TCSETA, $termio)
        || die "Can't ioctl TCSETA: $!";

}


sub POSIX_cbreak {
    local($on) = shift;
    local(@termios, $termios, $bitmask);

    # "file statics" for package cbreak:
    #      $savebits, $save_vtime, $save_vmin, $is_on

    $termios_t = &termios'typedef() unless defined $termios_t;
    $termios = pack($termios_t, ());  # for Sun SysVr4, which dies w/o this

    ioctl(TTY,&$GETIOCTL,$termios)
        || die "Can't ioctl GETIOCTL ($GETIOCTL): $!";

    @termios = unpack($termios_t,$termios);

    $bitmask  = &ICANON | &IEXTEN | &ECHO;
    if ($on && $cbreak'ison == 0) {
        $cbreak'ison = 1;
        $cbreak'savebits = $termios[&termios'c_lflag] & $bitmask;
        $termios[&termios'c_lflag] &= ~$bitmask;
        $cbreak'save_vtime = $termios[&termios'c_cc + &VTIME];
        $termios[&termios'c_cc + &VTIME] = 0;
        $cbreak'save_vmin  = $termios[&termios'c_cc + &VMIN];
        $termios[&termios'c_cc + &VMIN] = 1;
    } elsif ( !$on && $cbreak'ison == 1 ) {
        $cbreak'ison = 0;
        $termios[&termios'c_lflag] |= $cbreak'savebits;
        $termios[&termios'c_cc + &VTIME] = $cbreak'save_vtime;
        $termios[&termios'c_cc + &VMIN]  = $cbreak'save_vmin;
    } else {
        return 1;
    } 
    $termios = pack($termios_t,@termios);
    ioctl(TTY,&$SETIOCTL,$termios)
        || die "Can't ioctl SETIOCTL ($SETIOCTL): $!";
}

sub DUMB_cbreak {
    local($on) = shift;

    if ($on) {
        system("stty  cbreak -echo");
    } else {
        system("stty -cbreak  echo");
    }
} 

      

And elsewhere it says that for POSIX

    ($GETIOCTL, $SETIOCTL)  = (TIOCGETA, TIOCSETA); 

      

The RE translating back to original C is left as an exercise for the reader because I can't remember where 20 years ago I hooked it up originally. :(

Once you get out of ICANON mode on the tty, your syscall select(2)

now works correctly again. When select

read mask returns that a descriptor is ready, you do FIONREAD ioctl

to find out exactly how many bytes are waiting for you in that file descriptor. With this in place, you can do a syscall read(2)

for that large number of bytes only, preferably in a descriptor O_NONBLOCK

, although this is no longer needed by now.

Hm, here's the anticipatory note in /usr/src/usr.bin/vi/cl/README.signal

:

    Run in cbreak mode.  There are two problems in this area.  First, the
    current curses implementations (both System V and Berkeley) don't give
    you clean cbreak modes. For example, the IEXTEN bit is left on, turning
    on DISCARD and LNEXT.  To clarify, what vi WANTS is 8-bit clean, with
    the exception that flow control and signals are turned on, and curses
    cbreak mode doesn't give you this.

    We can either set raw mode and twiddle the tty, or cbreak mode and
    twiddle the tty.  I chose to use raw mode, on the grounds that raw
    mode is better defined and I'm less likely to be surprised by a curses
    implementation down the road.  The twiddling consists of setting ISIG,
    IXON/IXOFF, and disabling some of the interrupt characters (see the
    comments in cl_init.c).  This is all found in historic System V (SVID
    3) and POSIX 1003.1-1992, so it should be fairly portable.

      

If you are using recursive grep

for \b(TIOC[SG]ET[NP]|TC[SG]ET[SA]|tc[sg]etattr)\b

non-nuclear parts /usr/src/

, you should find stuff that you can use. For example:

% grep -Pr '\b(TIOC[SG]ET[NP]|TC[SG]ET[SA]|tc[sg]etattr)\b' /usr/src/{{s,}bin,usr.{s,}bin,etc,gnu}

      

I would look at /usr/src/usr.bin/less/screen.c

, down in function raw_mode()

. Thrifty ifdef

, although looking for portability, this looks like the cleanest code for what you want to do. Theres also stuff hiding in GNU.

OH MY , look /usr/src/gnu/usr.bin/perl/h2pl/cbreak.pl

! This should be my old code I wrote above. Interestingly, it has been flocked to every src system in the world. Scary, since it's twenty years old. God, it's strange to see the echoes of the young self. In fact, it is difficult to recall such details 20 years ago.

I can also see in /usr/src/lib/libcurses/term.h

this line:

#define tcgetattr(fd, arg) ioctl(fd, TCGETA, arg)

      

in a bunch ifdef

that are trying to make available termio

or termios

.

This should be enough to get you started.

+1


source







All Articles