[英]How to make arrow keys and backspace work correctly when asking input from user in C program using termios.h?
所以我有以下代码,它基本上只是读取用户输入的字符并打印它们,直到输入“q”。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<termios.h>
int main(void) {
char c;
static struct termios oldtio, newtio;
tcgetattr(0, &oldtio);
newtio = oldtio;
newtio.c_lflag &= ~ICANON;
newtio.c_lflag &= ~ECHO;
tcsetattr(0, TCSANOW, &newtio);
printf("Give text: ");
fflush(stdout);
while (1) {
read(0, &c, 1);
printf("%c", c);
fflush(stdout);
if (c == 'q') { break; }
}
printf("\n");
tcsetattr(0, TCSANOW, &oldtio);
return 0;
}
在 main 函数的开头,我关闭了规范模式,让用户能够在他提供输入时看到他的输入。 我还关闭了回声,因此在按向上箭头键时不会弹出诸如“^[[A”之类的东西。 这有效,但我也可以将光标移动到终端窗口上的上一行,这并不好。 有没有办法解决这个问题,以便用户只能在当前行内移动?
另一个问题是退格。 当我按下它时,程序会打印一个奇怪的符号(我假设是 0x7f)而不是擦除光标当前位置左侧的字符。 我应该以某种方式处理程序中的退格键输出,但我不知道该怎么做,因为它是这个奇怪的十六进制数。 有什么提示吗?
我也一直在考虑使这项工作的一个选项是使用规范模式,以便自动使用箭头键和退格功能。 但是,规范模式逐行工作,因此在用户点击“Enter”之前文本不会出现。 到目前为止,我还没有想出任何方法让用户在打字时看到他的输入。 这甚至可能吗?
请不要使用 ncurses 或 readline 建议。 我想使用 termios.h 来做到这一点。
你看过手册页吗? (应该是man termios
或在线查看某处)
在那里我找到了ECHOE
标志,据说它具有以下效果:
如果还设置了 ICANON,ERASE 字符会擦除前面的输入字符,而 WERASE 会擦除前面的单词。
这应该可以解决您的退格问题吗?
我还建议您查看手册页中的示例。 例如,您可以执行以下操作:
newtio.c_lflag &= ~(ECHO | ECHOE | ICANON);
...一次在一行中设置多个标志。 我知道手册页对于初学者来说很难阅读,但你会习惯它们,并且使用得越多,它们查找 C/POSIX 函数等的效率就越高(以防万一,你不使用它们反正)。
箭头键问题:也许你可以试试cfmakeraw()
函数; 它的描述听起来很有希望。 我没有时间进一步调查箭头键。 但是,也许您会在手册页中找到其他有用的东西。
顺便说一句: termios
看起来很有趣,我一直想知道某些命令行程序正在使用哪些函数; 从你的问题中学到了一些东西,谢谢!
这个周末我做了更多的研究。 按退格键时打印的“奇怪”符号很容易隐藏。 它是 ASCII 值0x7f
。 所以添加一个简单的
if (c == 0x7f) { continue; }
...只是忽略退格键。 或者以删除最后一个字符的方式处理它(参见下面的代码示例)。
这个简单的解决方法不适用于箭头键,因为它们不是 ASCII 字符:/但是,这两个主题也帮助我处理了这个问题: 主题 1和主题 2 。 基本上按箭头键会导致一系列字符被发送到stdin
(有关更多信息,请参阅第二个链接)。
这是我的完整代码(我认为)按您希望的方式工作:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
int main(void) {
char c;
static struct termios oldtio, newtio;
tcgetattr(0, &oldtio);
newtio = oldtio;
newtio.c_lflag &= ~ICANON;
newtio.c_lflag &= ~ECHO;
tcsetattr(0, TCSANOW, &newtio);
printf("Give text:\n");
fflush(stdout);
while (1) {
c = getchar();
// is this an escape sequence?
if (c == 27) {
// "throw away" next two characters which specify escape sequence
c = getchar();
c = getchar();
continue;
}
// if backspace
if (c == 0x7f) {
// go one char left
printf("\b");
// overwrite the char with whitespace
printf(" ");
// go back to "now removed char position"
printf("\b");
continue;
}
if (c == 'q') {
break;
}
printf("%c", c);
}
printf("\n");
tcsetattr(0, TCSANOW, &oldtio);
return 0;
}
顺便说一句,您可以通过以下代码获得完整的转义序列:
int main(void) {
char c;
while (1) {
c = getchar();
printf("%d", c);
}
return 0;
}
我想我不必说这个完整的东西是一个相当肮脏的黑客,很容易忘记处理一些特殊的键。 例如,在我的代码中,我不处理page-up/down
或home
键... --> 上面的代码远未完成,但为您提供了一个起点。 您还应该查看terminfo
,它可以为您提供许多必要的信息; 它还应该有助于更便携的解决方案。 如您所见,这个“简单”的事情可能会变得非常复杂。所以您可能会重新考虑您对 ncurses 的决定:)
实际上,为了处理箭头键,您必须实现一大块 ncurses。 优点/缺点:在命令行应用程序中使用 ncurses 的主要缺点可能是它通常会清除屏幕。 但是,(n)curses 提供了一个函数filter 。 ncurses 源中有一个示例程序“test/filter.c”,它通过使用左箭头键作为擦除字符来说明这一点,并将结果行传递给 system() 以运行简单的命令。 该示例不到 100 行代码 —— 看起来比上面的示例更简单、更完整。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.