繁体   English   中英

使用termios.h在C程序中询问用户输入时如何使箭头键和退格键正常工作?

[英]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/downhome键... --> 上面的代码远未完成,但为您提供了一个起点。 您还应该查看terminfo ,它可以为您提供许多必要的信息; 它还应该有助于更便携的解决方案。 如您所见,这个“简单”的事情可能会变得非常复杂。所以您可能会重新考虑您对 ncurses 的决定:)

实际上,为了处理箭头键,您必须实现一大块 ncurses。 优点/缺点:在命令行应用程序中使用 ncurses 的主要缺点可能是它通常会清除屏幕。 但是,(n)curses 提供了一个函数filter ncurses 源中有一个示例程序“test/filter.c”,它通过使用左箭头键作为擦除字符来说明这一点,并将结果行传递给 system() 以运行简单的命令。 该示例不到 100 行代码 —— 看起来比上面的示例更简单、更完整。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM