c - Backspace(\b) 不清除非规范模式中的文本 termios
问题描述
我正在尝试通过按退格键来清除文本,我在非规范模式下使用 termios。我创建了一个条件语句,当用户按下退格键时,它应该通过返回一个字符来删除前一个字符。
但是当我按 Backspace 而不是删除它打印^?
在该行上的字符时。
我不想使用规范模式。
我的代码:
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#define MAX_COMMANDS 1000
#define MAX_LENGTH 200
static struct termios initial_settings, new_settings;
static int peek_character = -1;
void init_keyboard();
void close_keyboard();
int kbhit();
int readch();
void run(char inp[]) {
system(inp);
}
int main() {
//65 = UP
//66 = DOWN
//Backspace = 127
int ch;
char str[MAX_COMMANDS][MAX_LENGTH];
init_keyboard();
int i = 0;
int j = 0;
while(ch != 'q') {
if(kbhit()) {
ch = readch();
if (ch == 127) {
const char delbuf[] = "\b \b";
write(STDOUT_FILENO, delbuf, strlen(delbuf));
}
if (ch == 10) {
run(str[i]);
i++;
j = 0;
} else {
str[i][j] = ch;
j++;
}
}
}
close_keyboard();
exit(0);
}
void init_keyboard() {
tcgetattr(0, &initial_settings);
new_settings = initial_settings;
new_settings.c_lflag &= (ECHO | ECHOE | ~ICANON);
new_settings.c_lflag &= ~ISIG;
new_settings.c_cc[VMIN] = 1;
new_settings.c_cc[VMIN] = 0;
tcsetattr(0, TCSANOW, &new_settings);
}
void close_keyboard() {
tcsetattr(0, TCSANOW, &initial_settings);
}
int kbhit() {
char ch;
int nread;
if (peek_character != -1) {
return 1;
}
new_settings.c_cc[VMIN] = 0;
tcsetattr(0, TCSANOW, &new_settings);
nread = read(0, &ch,1);
new_settings.c_cc[VMIN]=1;
tcsetattr(0, TCSANOW, &new_settings);
if (nread == 1) {
peek_character = ch;
return 1;
}
return 0;
}
int readch() {
char ch;
if (peek_character != -1) {
ch = peek_character;
peek_character = -1;
return ch;
}
read(0, &ch,1);
return ch;
}
解决方案
我正在尝试通过按退格键来清除文本,我在非规范模式下使用 termios。
您的 termios 初始化有一些错误(这只是代码中问题的一部分)。init_keyboard()
中
的以下语句不合逻辑:
new_settings.c_lflag &= (ECHO | ECHOE | ~ICANON);
大概您打算禁用 ICANON 属性(即使用非规范输入)。
您是否打算启用 ECHO 和 ECHOE 属性?(我会根据您代码的其他部分这么认为。)
该语句不启用 ECHO 和 ECHOE 属性,但最终只是保留了它们已经处于的任何状态。
这是一个潜在的错误,运气不好,可能永远不会被触发。
由于以下两个语句都使用相同的索引 VMIN,所以它们中的一个不应该索引 VTIME 吗?
new_settings.c_cc[VMIN] = 1;
new_settings.c_cc[VMIN] = 0;
我创建了一个条件语句,当用户按下退格键时,它应该通过返回一个字符来删除前一个字符。
请注意,您的代码仅尝试“删除显示的前一个字符”。
没有尝试从输入缓冲区中“删除前一个字符” 。
这可能是另一个错误。
但是当我按 Backspace 而不是删除它打印的字符时 ^? 在那条线上。
这似乎不是对您的代码功能的准确报告。
当我在 PC 终端窗口(LXTerminal 0.2.0)中执行您的代码时,我最终看到^
的只是而不是^?
您报告的 Backspace 键。
为了清楚起见,Backspace 键被回显/显示为^?
(因为默认情况下启用 ECHOCTL),但是您的程序会尝试执行额外的处理以响应该特定输入,结果只显示^
.
您的代码假定每个输入字符都作为单个字符回显(即显示)。
这是一个错误的假设。
生成 ASCII 控制代码而不是可打印字符的键将被回显/显示为两个字符(如您所报告的)。
不容易映射到 ASCII 代码的“特殊”键(来自 PC 键盘)将被读取为转义序列,即几个(3 到 5 个或更多)字符的序列。
如果您希望“擦除”或覆盖前一个字符以及回显的 Backspace,那么您的代码将需要返回(至少)三 (3) 个字符。
换句话说
const char delbuf[] = "\b \b";
需要是
const char delbuf[] = "\b\b\b \b\b\b";
请注意,这不是一个完整的解决方案,因为它只能“删除”单个前一个字符,并且不会正确处理前一个控制字符或转义序列。
这个答案只是为了解释为什么你没有得到你期望的结果。
请注意,使用本地回声(通过 termios)可能被认为是问题的一部分,因为您似乎没有掌握后果。
非规范模式通常会抑制任何自动回显,以便简化控制字符和转义序列的处理。
仅供参考,如果没有自动回显,那么backspace, space, backspace
您尝试使用的序列可能是有效的(因为只有一个前一个字符要覆盖,并且不会有 Backspace 的回显)。
请注意,您的代码使用忙等待来检查/检索键盘输入(因为 VTIME 可能设置为 0 并且 VMIN 为 0)。
这是浪费 CPU 周期的最低效的输入方法,并且在电池供电的系统上,电池消耗的速度比必要的快。
即使您添加了延迟,代码仍然会低效地轮询系统缓冲区以获取数据,而不是利用操作系统提供的高效事件驱动功能。
推荐阅读
- macos - 使用 NGinx 将找不到的图像文件重定向到 Uri
- javascript - 如何更新我的 isFetching 道具以使 Activity Spinner 正常工作?
- react-native - 世博会开始找不到模块'hoek'
- php - 在 Laravel 中覆盖路由名称
- c# - Dictionary.ContainsKey() 是否比 FirstOrDefault() 更好?
- asynchronous - 如何优雅地关闭 Tokio 运行时以响应 SIGTERM?
- javascript - 是否可以从谷歌应用程序脚本中的库调用容器绑定脚本函数?
- android-studio - 在 imageView 上捕获并显示图像,现在我想将 imageView 传递给另一个活动
- html - 页面的侧滚动条覆盖页面内容
- c - OpenMP Monte_Carlo 模拟实现目标接近 PI