0

我试图了解终端 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 生成的单字节也与大多数多字节生成按钮按下的输出的第一个字节相同,这一事实似乎表明没有。是否有其他机制来确定按钮边界的位置?

4

3 回答 3

2

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

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

一些程序识别 ESCDELAY 环境变量来微调这个时间。

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

于 2013-05-13T03:42:58.317 回答
1

好的,多亏了 nm,我在这里走上了正确的道路。

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

类似于以下内容:

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、向上、向下等...)。
  • 非 ASCII 字符的其他一些多字节序列,结果证明是相关字符的 UTF-8 表示。

我试着像疯子一样按下键盘上的按钮,但上面的循环总是能够正确识别哪些字节是一个单元。

但是,这可能无法在负担重的机器上或在缓冲的高延迟网络连接上完全按预期工作。也许在这些情况下,来自多个后面的按钮按下的更多字节已经在终端缓冲区中发现,导致多个按钮显示为一个。

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

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

于 2013-05-13T14:21:57.687 回答
0

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

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

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

于 2013-05-13T04:11:03.490 回答