在非规范模式下使用终端IO确定按钮边界

时间:2013-05-12 23:26:19

标签: c linux input terminal posix

我正在尝试了解终端I / O的工作原理。

当终端处于非规范模式时(缺少错误处理):

struct termios term_original, term_current;
tcgetattr(STDIN_FILENO, &term_original);
term_current = term_original;
term_current.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
term_current.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK | ISTRIP | IXON | PARMRK);
term_current.c_oflag &= ~(OPOST);
term_current.c_cc[VMIN]  = 1;
term_current.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSADRAIN, &term_current);

一个简单的读取循环可以读取每个按钮按下的数据,如下所示:

char c;
while (read(0, &c, 1) != -1) { PRINT_CHAR(c); }

现在,

  • 按键盘上的Esc会生成:0x1b。
  • 按F1生成:0x1b 0x4f 0x50。
  • 按F5生成:0x1b 0x5b 0x31 0x35 0x7e。

在读取和处理此输入方面,如何确定一个按钮按下的输出结束而下一个按钮的输出开始?我找不到任何可辨别的模式,并且Esc生成单个字节的事实也与大多数多字节生成按钮按下的输出的第一个字节相同,这似乎表明没有。是否有其他机制来确定按钮边界的位置?

3 个答案:

答案 0 :(得分:2)

程序依赖于按键不被按下太快。如果延迟小于100毫秒,这是一键按下;否则有两个单独的事件。

是的,程序实际上在按下ESC后暂停了一段时间,以确保它是ESC而没有其他键。有时这种停顿可以用肉眼辨别。

某些程序会识别ESCDELAY环境变量以微调此时间。

是的,这不是完美的,你可以通过按键过快来欺骗系统。

答案 1 :(得分:1)

好的,感谢上午,我在这里设置了正确的轨道。

尝试一次读取一个字节是不正确的。相反,应该尝试一次读取多个字符。

如下所示:

int r, i;
char buffer[10]; //10 chosen arbitrarily
while ((r = read(STDIN_FILENO, buffer, sizeof(buffer))) != -1)
{
  printf("%d bytes: ", r);
  for (i = 0; i < r; ++i) { PRINT_CHAR(buffer[i]); }
  printf("\r\n");
}

在这种情况下,只要按下按钮,read()调用就会返回,并返回读取的字节数。现在,这些字节可用于识别有问题的按钮或字符。

使用上面的循环按下顶行按钮,我看到了:

1 bytes: 1b
3 bytes: 1b 4f 50
3 bytes: 1b 4f 51
3 bytes: 1b 4f 52
3 bytes: 1b 4f 53
5 bytes: 1b 5b 31 35 7e
5 bytes: 1b 5b 31 37 7e

在我的机器上,我似乎得到了:

  • ASCII字符的单个字节。
  • 0x1b为第一个字符,后跟其他字符为特殊按钮(F1-F12,Up,Down等)。
  • 非ASCII字符的其他一些多字节序列,结果证明是相关字符的UTF-8表示。

我试着像疯子一样卡住键盘上的按钮,但上面的循环始终能够正确识别哪个字节是一个单位。

然而,这可能无法在高负担的机器上或在缓冲的高延迟网络连接上完全按预期工作。也许在这些情况下,多次按下按钮的更多字节已经在终端缓冲区中找到,导致多个按钮显示为一个。

在这种情况下,可能无法确保不会发生错误,但可以将其最小化。单字节字符始终显示在0x00-0x7F范围内。特殊按钮始终是多字节的,以0x1B开头,后跟0x00-0x7F内的内容。多字节字符始终在0x80-0xFF范围内。 UTF-8编码序列还有第一个字节,表示当前字符中有多少字节。鉴于此信息,足以确保错误最小化,并且不会不必要地传播到即将进行的读取。

最后,重要的是要强调我所描述的是我的机器(PC,经典的US 101键盘,终端编码设置为UTF-8)。完整的程序应该最低限度地看到终端使用的字符编码。

答案 2 :(得分:0)

最终,您必须按上下文确定这些。根据转义后收到的字符,您可以确定已知序列的整体序列长度,然后返回正常解释字符。

您应该能够查找已知终端的转义序列。

您的某些功能键可能具有本地配置的扩展,特别是如果它们与其他任何类型的标准终端的代码不匹配。