【问题标题】:Terminal keeps buffering pressed keys终端不断缓冲按下的键
【发布时间】:2019-06-26 23:35:27
【问题描述】:

我正在编写一个 VT100 终端引擎,但输入处理有点棘手。 首先我初始化了我的 tty,没有回显。

static void init_tty() {
    if (tcgetattr(STDIN_FILENO, &ctty) != 0)
        throw std::runtime_error("can not get tty attribute");

    ctty.c_lflag &= ~(FLAG(ICANON) | FLAG(ECHO) | FLAG(ISIG));

    if (tcsetattr(STDIN_FILENO, TCSANOW, &ctty) != 0)
        throw std::runtime_error("can not set tty attribute");
}

接下来我初始化一个非阻塞键盘文件描述符。

static void init_keyboard(const char *device) {
    if ((kfd = open(device, FLAG(O_RDONLY) | FLAG(O_SYNC))) == -1)
        throw std::runtime_error("can not open keyboard fd");

    unsigned flags = fcntl(kfd, F_GETFL, 0);

    flags |= FLAG(O_NONBLOCK);

    if (fcntl(kfd, F_SETFL, flags) == -1)
        throw std::runtime_error("can not set keyboard fd flags");
}

到目前为止一切顺利。主循环只是轮询事件,如果按 ESC 就会中断。

int main() {
    while (true) {
        read(kfd, evt, sizeof(input_event));

        if (evt->type == EV_KEY && evt->value == 1 && evt->code == KEY_ESC)
            break;
    }
}

所以我输入了一些字符。同时,我的终端正在缓冲我输入的所有字符。程序退出后,所有字符都会显示出来。

所以我的问题是:如何在程序运行时禁用终端输出流缓冲?

这是一个 GIF 来说明问题。

步骤:

  • sudo ./clac
  • 引擎正在运行但没有回显;输入一些字符
  • 按 ESC
  • 输入的字符得到显示;程序退出

【问题讨论】:

  • 你为什么用read而不是fgetcread 带有非阻塞文件描述符将发出错误EAGAIN(或EWOULDBLOCK),如果您从套接字以外的文件读取并且读取会阻塞。 (考虑到它可能会尝试读取 sizeof(input_event) 字节)。当您设置了终端和文件描述符时,我将不得不进一步挖掘以确定确切的行为。只需在非规范模式下使用fgetc 阅读即可避免所有问题。
  • 我无法从您的问题中看出您想要的结果是什么:a)丢弃输入的字符,或 b)立即显示输入的字符。如果是 b),您的程序如何尝试显示它们?
  • fgetc 似乎可以工作,但我不能用它处理像“ESC”这样的特殊键,此外没有办法检查某个字符是否被按下。 @Armali a) 丢弃输入的字符。该程序不应显示键入的键,这就是我关闭回显但在后台键被缓冲的原因。当程序退出时,这些字符被打印出来。就像我必须在退出之前刷新一些缓冲区。

标签: c++ linux terminal file-descriptor vt100


【解决方案1】:

程序不应该显示键入的键,这就是我关闭回显但在后台缓冲键的原因。当程序退出时,这些字符被打印出来。就像我必须在退出之前刷新一些缓冲区。

这可以通过调用来完成

    tcflush(STDIN_FILENO, TCIFLUSH);

就在退出之前;见tcflush(3) - Linux man page

【讨论】:

    【解决方案2】:

    我刚刚解决了我的问题。 (https://unix.stackexchange.com/questions/126974/where-do-i-find-ioctl-eviocgrab-documented)

    所以我会在我的程序开始后立即获取所有事件,包括 ENTER

    示例:sudo ./clac ENTER

    解决方案是在开始时添加 1000 毫秒延迟,然后使用 GRAB 获取 fd 事件

    std::this_thread::sleep_for(std::chrono::seconds(1));
    
    if (ioctl(kfd, EVIOCGRAB, FD_GRAB) == -1)
        throw std::runtime_error("can not set keyboard fd grab");
    

    当程序退出 UNGRAB 时,fd 带有

    if (ioctl(kfd, EVIOCGRAB, FD_UNGRAB) == -1)
        throw std::runtime_error("can not set keyboard fd ungrab");
    

    【讨论】:

      猜你喜欢
      • 2013-07-24
      • 2015-11-04
      • 1970-01-01
      • 2011-01-11
      • 2017-10-21
      • 2021-03-30
      • 2019-01-09
      • 2016-04-29
      • 1970-01-01
      相关资源
      最近更新 更多