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

时间:2014-10-28 17:47:00

标签: c linux terminal termios

所以我有以下代码,基本上只读取字符用户输入并打印它们直到输入'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;
}

在主要功能的开头,我关闭了规范模式,让用户在给出时可以看到他的输入。我也关闭回声,所以像“^ [[A”在按下向上箭头键时不会弹出这样的东西。这可行,但我也能够将光标移动到终端窗口的上行,这并不好。有没有办法解决这个问题,以便用户只能在当前行内移动?

另一个问题是退格。当我按下它时,程序打印一个奇怪的符号(我假设是0x7f),而不是删除光标当前位置的字符。我应该以某种方式处理程序中的退格键输出,但我不知道该怎么做,因为它是这个奇怪的十六进制数。有什么提示吗?

我一直在考虑使用的一个选项是使用规范模式,因此箭头键和退格功能会自动使用。但是,规范模式逐行工作,因此在用户点击“Enter”之前不会显示文本。到目前为止,我还没有找到任何方法让用户在打字时看到他的输入。这甚至可能吗?

请不要使用ncurses或readline建议。我想用termios.h来做这件事。

2 个答案:

答案 0 :(得分:4)

你看过手册页吗? (应为man termios或查看某处online

在那里我发现ECHOE标志据说具有以下效果:

  

如果还设置了ICANON,则ERASE字符将删除前面的输入字符,并且WERASE将删除前一个字。

这应该可以解决你的退格问题吗?

我也建议,您可以查看手册页中的示例。例如。你可以做到以下几点:

newtio.c_lflag &= ~(ECHO | ECHOE | ICANON);

...一次只能在一行中设置多个标志。我知道手册很难为初学者阅读,但你会习惯他们,你使用它们越多,他们查找C / POSIX功能等就越有效(以防万一,你不要这样做)无论如何使用它们。)

箭头键问题:也许你可以试试cfmakeraw() - 函数;它的描述很有希望。我还没来得及进一步调查箭头键。但是,也许您在手册页中找到了其他有用的东西。

BTW:termios看起来很有趣,我总是想知道某些命令行程序正在使用哪些函数;通过你的问题学到了一些东西,谢谢!

修改

本周末我做了更多的研究。 &#34;奇怪&#34;按下退格键时打印的符号很容易隐藏。它是ASCII值0x7f。所以添加一个简单的

if (c == 0x7f) { continue; }

...只是忽略退格键。或者以删除最后一个字符的方式处理它(参见下面的代码示例)。

这个简单的解决方法对箭头键不起作用,因为它们不是ASCII字符:/但是,这两个主题帮助我处理了这个问题:topic 1topic 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键... - &gt;上面的代码远非完整,但为您提供了一个重点。你还应该看看terminfo,它可以为你提供很多必要的信息;它还应该有助于提供更便携的解决方案。如你所见,这个&#34;简单&#34;事情可能变得相当复杂。所以你可能会重新考虑你对ncurses的决定:)

答案 1 :(得分:0)

实际上,为了处理箭头键,你必须实现一个大小的ncurses块。有利有弊:在命令行应用程序中使用ncurses的主要缺点可能是它通常会清除屏幕。但是,(n)curses提供了一个函数filter。有一个示例程序&#34; test / filter.c&#34;在ncurses源代码中,通过使用左箭头键作为擦除字符来说明这一点,并将结果行传递给system()以运行简单命令。该样本少于100行代码 - 似乎比上面的例子更简单,更完整。