【问题标题】:Backspace(\b) not clearing text in Non-canonical mode termiosBackspace(\b) 不清除非规范模式中的文本 termios
【发布时间】:2020-01-31 22:43:42
【问题描述】:

我正在尝试通过按退格键来清除文本,我在非规范模式下使用 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;
}


【问题讨论】:

标签: c linux terminal termios


【解决方案1】:

我正在尝试通过按退格键来清除文本,我在非规范模式下使用 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 键回显/显示为^?(因为默认情况下启用 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 序列可能会有效(因为只有一个前一个字符要覆盖并且不会有退格键回显)。


请注意,您的代码使用 忙等待 来检查/检索键盘输入(因为 VTIME 可能设置为 0 而 VMIN 为 0)。
这是效率最低的输入方法,会浪费 CPU 周期,并且在电池供电的系统上,电池消耗的速度比需要的快。
即使您添加了延迟,代码仍然会低效地轮询系统缓冲区中的数据,而不是利用操作系统提供的高效事件驱动功能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-14
    • 1970-01-01
    • 2012-07-27
    • 2011-03-04
    • 2014-02-19
    • 1970-01-01
    • 2023-04-01
    相关资源
    最近更新 更多