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

如何在C中检测键盘事件而不提示用户在Linux? 那就是程序运行应该通过按任意键来终止。 任何人都可以请帮忙吗?

您必须使用termios修改终端设置。 请参阅Stevens&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()。 所有这一切都是坐着等待击键,然后退出。

如果你想在图形应用程序中做到这一点,你应该使用一些库来做到这一点。

这样一个简单的任务可以很容易地完成任何库(甚至像Xlib这样的低级别)。

只要选择一个,并寻找一个教程,显示如何处理键盘事件。

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

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

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

我有一个古老的20年历史的perl4程序,以这种方式清除CBREAK和ECHO模式。 这是诅咒的东西,而不是诉诸于诅咒图书馆:

 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描述符上,尽管现在不需要这样做。

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

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

 % 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 s充满了争执,看起来像你想要做的最干净的代码。 GNU中也有潜伏的东西。

OH MY ,请查看/usr/src/gnu/usr.bin/perl/h2pl/cbreak.pl ! 这一定是我上面贴出的旧代码。 有趣的是,它被排除在世界上的每一个src系统之外。 也是可怕的,因为它已经过了 20年了。 天哪,看到一个年轻的自己的回声是奇怪的。 真的很难记住20年前的这些细节。

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

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

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

这应该足以让你开始。