如何在不提示用户的情况下检测Linux C GUI程序中的按键操作?

时间:2010-11-27 19:06:43

标签: c linux keypress

如何在不提示linux用户的情况下检测C中的键盘事件?也就是说,运行的程序应该通过按任意键终止。 有人可以帮忙吗?

4 个答案:

答案 0 :(得分:6)

您必须使用termios修改终端设置。见史蒂文斯& Rago 2nd Ed在UNIX环境中的高级编程'它解释了为什么tcsetattr()可以在没有设置所有终端特性的情况下成功返回,以及为什么你看到看起来是对tcsetattr()的冗余调用。

这是UNIX中的ANSI C:

#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 */
}

nanosleep调用是为了防止代码吞噬系统资源。你可以调用nice()而不是使用nanosleep()。所有这一切都是等待按键,然后退出。

答案 1 :(得分:1)

如果您想在图形应用程序中执行此操作,则应使用一些库来执行此操作。

使用任何库(甚至像Xlib这样的低级库)都可以轻松完成这样一个简单的任务。

只需选择一个并查找显示如何处理键盘事件的教程。

答案 2 :(得分:1)

无法使用ANSI C.查看ncurses lib。

答案 3 :(得分:1)

以下是来自/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;
        }
}

至少,您需要在ICANON系统调用或select(2)工作之前退出FIONREAD ioctl模式。

我有一个古老的,已有20年历史的perl4程序,可以通过这种方式清除CBREAK和ECHO模式。它正在做诅咒的东西而不诉诸curses库:

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");
    }
} 

其他地方也说过POSIX,

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

重新翻译回原版C留给读者作为练习,因为我不记得20年前我从哪里抓过它。 :(

一旦你在tty上退出ICANON模式,现在你的select(2)系统调用再次正常工作。当select的读掩码返回该描述符已准备就绪时,您执行FIONREAD ioctl以确切地发现该文件描述符上等待您的字节数。有了这个,你可以为那么多字节做一个read(2)系统调用,最好是在O_NONBLOCK描述符上,尽管现在已经不再需要了。{/ p>

嗯,这是/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.

如果您对grep的非内核部分\b(TIOC[SG]ET[NP]|TC[SG]ET[SA]|tc[sg]etattr)\b执行递归/usr/src/,则应找到可以使用的内容。例如:

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

我会在/usr/src/usr.bin/less/screen.c函数中查看raw_mode()。虽然它是为了实现可移植性而惹恼ifdef,但它看起来像是你想要做的最干净的代码。还有潜伏在GNU中的东西。

OH MY ,查看/usr/src/gnu/usr.bin/perl/h2pl/cbreak.pl!这一定是我上面发布的旧代码。有趣的是,它被传播到世界上每个src系统。也是可怕的,因为 已经过时了二十年。天哪,看到更年轻的自我的回声是奇怪的。真的很难记住20年前的这些细节。

我也在/usr/src/lib/libcurses/term.h这行看到了:

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

在一群试图推断ifdeftermio可用性的termios中。

这应该足以让你入门。