首页 > 解决方案 > 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;
}


标签: clinuxterminaltermios

解决方案


我正在尝试通过按退格键来清除文本,我在非规范模式下使用 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 周期的最低效的输入方法,并且在电池供电的系统上,电池消耗的速度比必要的快。
即使您添加了延迟,代码仍然会低效地轮询系统缓冲区以获取数据,而不是利用操作系统提供的高效事件驱动功能。


推荐阅读