How to make arrow keys and backspace work correctly when asking input from user in C program using termios.h?

拈花ヽ惹草 提交于 2019-12-03 13:43:16

问题


So I have the following code which basically just reads characters user inputs and prints them until 'q' is entered.

#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;
}

In the beginning of the main function I turn off the canonical mode to make user able to see his input when he's giving it. I also turn off the echo so stuff like "^[[A" doesn't pop up when pressing the up arrow key for example. This works, but I'm also able to move the cursor to upper rows on a terminal window and that's not good. Is there a way fix this so that user can only move within the current row?

Another problem is the backspace. When I press it the program prints a weird symbol (which I assume is 0x7f) instead of erasing the character left to the cursor's current location. I should propably handle the backspace key output in the program somehow but I don't know how to do it since it's this weird hexadecimal number. Any tips for this?

One option I've also been thinking about to make this work is to use canonical mode so the arrow keys and backspace functionalities are automatically in use. However, canonical mode works line by line and so the text doesn't appear until "Enter" is hit by the user. So far, I haven't figured out any way to make user see his input while typing. Is this even possible?

And please, no ncurses or readline suggestions. I want to do this using termios.h.


回答1:


have you looked into the man pages? (should be man termios or look somewhere online)

There I found the ECHOE flag which is said to have the following effect:

If ICANON is also set, the ERASE character erases the preceding input character, and WERASE erases the preceding word.

This should fix your backspace problem?

I also suggest, you have a look into the examples in the man page. E.g. you could do the following:

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

...to set more than one flag at a time in only one line. I know the man pages are hard to read for beginners but you will get used to them and the more you use them, the more efficient they become for looking up C/POSIX-functions etc (just in case, you don't use them anyway).

The arrow-key-problem: Maybe you can try the cfmakeraw()-function; its description sounds promising. I haven't had time to investigate any further about the arrow keys. However, maybe you find something else useful in the man page.

BTW: termios looks interesting, I always wondered which functions certain command line programmes are using; learned something by your question, thanks!

EDIT

I've done some more research this weekend. The "strange" symbol printed when pressing the backspace key is quite easy to hide. It is the ASCII-value 0x7f. So add a simple

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

...to just ignore the backspace key. Or handle it in a way to remove the last character (see code example below).

This simple workaround doesn't work for the arrow keys though as they are no ASCII characters :/ However, these two topics helped me handling also this problem: topic 1 and topic 2. Basically pressing the arrow keys results in a sequence of a couple of chars being sent to stdin (see the second link for more information).

Here is my complete code which (I think) works the way you wish:

#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;
}

BTW you can get the complete escape sequences by the following code:

int main(void) {
    char c;
    while (1) {
        c = getchar();
        printf("%d", c);
    }
    return 0;
}

I think I don't have to say that this complete thing is quite a dirty hack and it's easy to forget handling some special keys. E.g. in my code I don't handle the page-up/down or home keys... --> the code above is far from complete but gives you a point to start. You should also have a look at terminfo which can provide you a lot of the necessary information; it should also help with a more portable solution. As you see, this "simple" thing can become quite complex.So you might rethink your decision against ncurses :)




回答2:


Actually, for handling the arrow keys, you would have to implement a good-sized chunk of ncurses. There are pros/cons: the main drawback to using ncurses in a command-line application might be that it usually clears the screen. However, (n)curses provides a function filter. There is a sample program "test/filter.c" in the ncurses source which illustrates this by using the left-arrow key as an erase character, and passes the resulting line to system() to run simple commands. The sample is less than 100 lines of code -- simpler and more complete than the examples above, it seems.



来源:https://stackoverflow.com/questions/26615045/how-to-make-arrow-keys-and-backspace-work-correctly-when-asking-input-from-user

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!