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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4293355/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-05 00:09:09  来源:igfitidea点击:

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

clinuxkeypress

提问by Pooja N Babu

how to detect a keyboard event in C without prompting the user in linux? That is the program running should terminate by pressing any key. can anyone please help with this?

linux - 如何在不提示用户的情况下检测C中的键盘事件?即程序运行应按任意键终止。任何人都可以帮忙吗?

回答by peoro

If you want to do that in a graphical application, you should use some libraries to do this.

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

Such a simple task can be easily done with whatever library (even low level ones like Xlib).

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

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

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

回答by user411313

no way with ANSI C. Look at ncurses lib.

ANSI C 没有办法。看看 ncurses lib。

回答by tchrist

Here's code from /usr/src/bin/stty/key.c:

这里的代码来自/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 have to get out of ICANONmode before your select(2)syscall or your FIONREAD ioctlwill work.

至少,您必须ICANONselect(2)系统调用或您的FIONREAD ioctl工作之前退出模式。

I have an ancient, 20-year-old perl4 program that clears CBREAK and ECHO mode this way. It is doing curses stuff without resorting to the curses library:

我有一个古老的、已有 20 年历史的 perl4 程序,它以这种方式清除 CBREAK 和 ECHO 模式。它在不求助于curses库的情况下做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 <[email protected]>

    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 it elsewhere says that for POSIX,

它在其他地方说对于 POSIX,

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

RE-translation back into the original C is left as an exercise for the reader, because I can't remember where the 20-years-ago-me snagged it from originally. :(

重新翻译回原来的 C 留给读者作为练习,因为我不记得 20 年前的我最初从哪里获取它。:(

Once you're out of ICANON mode on the tty, now your select(2)syscall works properly again. When select's read mask returns that that descriptor is ready, then you do a FIONREAD ioctlto discover exactly how many bytes are waiting for you on that file descriptor. Having got that, you can do a read(2)syscall for just that many bytes, preferably on an O_NONBLOCKdescriptor, although by now that should no longer be necessary.

一旦您在 tty 上退出 ICANON 模式,现在您的select(2)系统调用又可以正常工作了。当select的读取掩码返回该描述符已准备好时,您将执行 aFIONREAD ioctl以确切地发现该文件描述符上有多少字节在等待您。有了这个,您可以read(2)对那么多字节进行系统调用,最好是在O_NONBLOCK描述符上,尽管现在应该不再需要了。

Hm, here's a foreboding note in /usr/src/usr.bin/vi/cl/README.signal:

嗯,这里有一个不祥的预感/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 do a recursive grepfor \b(TIOC[SG]ET[NP]|TC[SG]ET[SA]|tc[sg]etattr)\bon the non-kernel portions of /usr/src/, you should find stuff you can use. For example:

如果你做一个递归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}

I would look at /usr/src/usr.bin/less/screen.c, down in the raw_mode()function. Riddled with ifdefs though it is in a quest for portability, that looks like the cleanest code for what you want to do. There's also stuff lurking down in GNU.

我会看看/usr/src/usr.bin/less/screen.c, 在raw_mode()函数中。充满了ifdefs 虽然它是为了寻求可移植性,但它看起来像是您想要做的最干净的代码。还有一些东西潜伏在 GNU 中。

OH MY, look in /usr/src/gnu/usr.bin/perl/h2pl/cbreak.pl! That must be my old code that I posted above. Interesting that it's trickled out to every src system in the world. Scary, too, since it istwenty years out of date. Gosh, it's weird to see echoes of a younger self. Really hard to remember such particulars from 20 years ago.

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

I also see in /usr/src/lib/libcurses/term.hthis line:

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

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

in a bunch of ifdefs that are trying to infer termioor termiosavailability.

在一堆ifdef试图推断termiotermios可用性的s 中。

This should be enough to get you started.

这应该足以让您入门。

回答by jim mcnamara

You have to modify terminal settings using termios. See Stevens & Rago 2nd Ed 'Advanced Programming in the UNIX Environment' it explains why tcsetattr() can return successfuly without having set all terminal characteristcs, and why you see what looks to be redundant calls to tcsetattr().

您必须使用 termios 修改终端设置。请参阅 Stevens & Rago 第二版“UNIX 环境中的高级编程”,它解释了为什么 tcsetattr() 可以在没有设置所有终端特性的情况下成功返回,以及为什么您会看到对 tcsetattr() 的冗余调用。

This is ANSI C in UNIX:

这是 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 */
}

The nanosleep call is there to prevent the code from gobbling up system resources. You could call nice() and not use nanosleep(). All this does is sit and wait for a keystroke, then exit.

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