如何在C语言中处理Linux控制台中的按键输入?

19

我正在使用Linux控制台,并希望写一个程序,直到按下ESC键才停止输出随机字符。如何编写这样的键盘处理程序?


类似或相关的 - C非阻塞键盘输入 - jschmier
4个回答

29

终端设备的行规则通常默认为规范模式。在这种模式下,终端驱动程序直到看到换行符(按下 Enter 键)才将缓冲区呈现给用户空间。

您可以使用 tcsetattr() 操作 termios 结构将终端设置为原始(非规范)模式。分别清除 ECHOICANON 标志可禁用字符随键入而发生的回显,并导致读请求直接从输入队列中满足。在 c_cc 数组中将 VTIMEVMIN 的值设置为零会使读取请求 (fgetc()) 立即返回而不是阻塞;有效地轮询标准输入。如果流中没有字符可用,则调用 fgetc() 会返回 EOF

#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>

int getkey() {
    int character;
    struct termios orig_term_attr;
    struct termios new_term_attr;

    /* set the terminal to raw mode */
    tcgetattr(fileno(stdin), &orig_term_attr);
    memcpy(&new_term_attr, &orig_term_attr, sizeof(struct termios));
    new_term_attr.c_lflag &= ~(ECHO|ICANON);
    new_term_attr.c_cc[VTIME] = 0;
    new_term_attr.c_cc[VMIN] = 0;
    tcsetattr(fileno(stdin), TCSANOW, &new_term_attr);

    /* read a character from the stdin stream without blocking */
    /*   returns EOF (-1) if no character is available */
    character = fgetc(stdin);

    /* restore the original terminal attributes */
    tcsetattr(fileno(stdin), TCSANOW, &orig_term_attr);

    return character;
}

int main()
{
    int key;

    /* initialize the random number generator */
    srand(time(NULL));

    for (;;) {
        key = getkey();
        /* terminate loop on ESC (0x1B) or Ctrl-D (0x04) on STDIN */
        if (key == 0x1B || key == 0x04) {
            break;
        }
        else {
            /* print random ASCII character between 0x20 - 0x7F */
            key = (rand() % 0x7F);
            printf("%c", ((key < 0x20) ? (key + 0x20) : key));
        }
    }

    return 0;
}

注意:为简单起见,此代码省略了错误检查。


1
这似乎可以工作,但每次似乎都提供整个缓冲区。因此,如果我按a然后按b再按c,在按c后它会显示aababc。 - Jackie
@Jackie 在 character = fgetc(stdin); 后面加上 while (getchar() != EOF); - Ciro Santilli OurBigBook.com
非常感谢您给出如此简洁明了的答案! - Brian Cannard

5

更改tty设置以进行单个按键操作:

int getch(void) {
      int c=0;

      struct termios org_opts, new_opts;
      int res=0;
          //-----  store old settings -----------
      res=tcgetattr(STDIN_FILENO, &org_opts);
      assert(res==0);
          //---- set new terminal parms --------
      memcpy(&new_opts, &org_opts, sizeof(new_opts));
      new_opts.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL | ECHOPRT | ECHOKE | ICRNL);
      tcsetattr(STDIN_FILENO, TCSANOW, &new_opts);
      c=getchar();
          //------  restore old settings ---------
      res=tcsetattr(STDIN_FILENO, TCSANOW, &org_opts);
      assert(res==0);
      return(c);
}

“ICRNL” 不是应该放在 “c_iflag” 字段中,而不是 “c_lflag” 字段中吗? - Dan Moulding

4

getch() 可能来自Curses库?此外,您需要使用 notimeout() 来告诉 getch() 不要等待下一个按键。


1
你应该明确地提到你正在谈论 (N)curses 库。 - James Morris
4
注意:ncurses 中的 getch() 函数需要适当初始化 ncurses "screen",否则将无法正常工作。 - ShinTakezou
2
你还需要初始化库——这是对@ShinTakezou所说的一种变化。 - Jonathan Leffler

-3
#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <signal.h>

char * me = "Parent";

void sigkill(int signum)
{
    //printf("=== %s EXIT SIGNAL %d ===\n", me, signum);
    exit(0);
}

main()
{
    int pid = fork();
    signal(SIGINT, sigkill);
    signal(SIGQUIT, sigkill);
    signal(SIGTERM, sigkill);
    if(pid == 0) //IF CHILD
    {  
        int ch;
        me = "Child";
        while(1)
        {  
            ch = (rand() % 26) + 'A';   // limit range to ascii A-Z
            printf("%c",ch);
            fflush(stdout); // flush output buffer
            sleep(2);   // don't overwhelm
            if (1 == getppid())
            {  
                printf("=== CHILD EXIT SINCE PARENT DIED ===\n");
                exit(0);
            }
        }
        printf("==CHILD EXIT NORMAL==\n");
    }
    else //PARENT PROCESS
    {  
        int ch;
        if((ch = getchar())==27)
            kill(pid, SIGINT);
        //printf("==PARENT EXIT NORMAL (ch=%d)==\n", ch);
    }
    return(0);
}

在这个程序中,你只需要在esc字符后按下enter键,因为getchar()是一个阻塞函数。 此外,您可以根据需要删除或减少子进程的睡眠时间。

2
除了 getchar 等待输入外,在 getchar 等待用户按下 [enter] 之前不会输出任何随机字符。 - James Morris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接