C语言 在非规范模式下确定终端IO的按钮边界



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

程序依赖于按键不能按得太快。如果延迟小于100ms,这是一次按键;否则会有两个独立的事件。

是程序实际上暂停一段时间后,ESC被按下,以确保它是ESC,而不是其他一些键。有时这种停顿可以用肉眼看到。

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

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

好吧,多亏了n.m.,我才走上了正确的道路。

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

类似如下:

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("rn");
}

在这种情况下,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)。一个完整的程序至少应该看到终端正在使用的字符编码。

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

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

一些功能键可能有本地配置的扩展,特别是如果它们与其他实现的各种标准终端的代码不匹配。

最新更新