3

I am using termios as suggested in a previous question I asked but now am asking if there is a way get backspace to work whilst using termios in non-canonical mode. I am using termios to have not have an echo If I use &=ECHO and &=ICANON this is the result I want, the keyboard input is sent to putchar() as soon as the key is press and displayed but the '\b' key is display as hex, if I do the opposite I can't see the text till enter is pressed but '\b' works. I have looked up the manual and some other forums that and they said " not possible just don't make any mistakes", this would make sense seeing as how when I don't enter my password correctly in in a terminal on Ubuntu I can't backspace and change it. But I was making sure I haven't missed anything in the manual.

Code is to get input from stdin and not display empty lines.

#include <unistd.h>
#include <termios.h>
#include <errno.h>
#include <stdio.h>
#define ECHOFLAGS (ECHO)

int setecho(int fd, int onflag);
int first_line(int *ptrc);

int main(void){

struct termios old;
tcgetattr(STDIN_FILENO,&old);


setecho(STDIN_FILENO,0);
    int c;
    while((c = getchar())!= 4) //no end of file in non-canionical match to control D
        first_line(&c);
tcsetattr(STDIN_FILENO,&old);    
return 0;

    }


int setecho(int fd, int onflag){
    int error;
    struct termios term;

    if(tcgetattr(fd, &term) == -1)
        return -1;
    if(onflag){ printf("onflag\n");
        term.c_lflag &= ECHOFLAGS ; // I know the onflag is always set to 0 just 
        term.c_lflag &=ICANON;      // testing at this point
    }
    else{ printf("else\n");
        term.c_lflag &= ECHO;
        term.c_lflag &=ICANON;

    }

    while (((error = tcsetattr(fd, TCSAFLUSH, &term)) ==-1 && (errno == EINTR)))
            return error;
}

int first_line(int *ptrc){
    if (*ptrc != '\n' && *ptrc != '\r'){
            putchar(*ptrc);
        while (*ptrc != '\n'){
            *ptrc = getchar();
            putchar(*ptrc);
            }


    }
    else return 0;

    return 0;
}

Thanks Lachlan

P.S on a side point in my research I noticed someone saying Termios isn't "Standard C" is this because it is system dependant? (only for comments)

Mat
  • 202,337
  • 40
  • 393
  • 406
UNECS
  • 533
  • 1
  • 9
  • 20

3 Answers3

3

How would you expect this to work? If the input characters are sent to your program immediately, then by the time the backspace character is recieved it's simply too late for the terminal to handle backspace - your program has already seen the previous character, so it can't be taken back.

For example, say the user presses A. Your program will receieve 'A' from getchar() and process it. Now the user presses backspace - now what should the terminal do?

So this implies that the only place you can handle backspace in non-canonical mode is in your program itself. When you receive the '\b' character from getchar(), you can handle it specially (just like you have special handling for '\n') - for example, remove the most recently entered character from a buffer.

caf
  • 233,326
  • 40
  • 323
  • 462
  • I understand what you are saying but if I buffer I would need to set a timer with a signal before the char in c is written wouldn't I? eg. if getchar gets a char and buffers it then gets another char before sending a char to stdout to check if it is `'\b'` how would the last char be written or are you suggesting I use an array and move the pointer to the array when the `'\b'` is detected? And then how do I still keep continuous output to stdout with buffering? – UNECS Apr 23 '12 at 07:33
  • 2
    @UNECS: If you need to erase an output character that you've already written then you can normally just output a `"\b \b"` sequence (the backspace moves the cursor back one; the space erases the character that was there; then the backspace moves the cursor back again). But if you're echoing the text immediately to the console and not processing it until `'\n'` then why are you bothering with non-canonical mode at all? – caf Apr 23 '12 at 07:39
  • I'm obviously missing something [here](http://linux.die.net/man/3/termios) I thought when canonical mode was not processed till `'\n'`which means the text isn't available till `'\n'` hence doesn't get set to `putchar` – UNECS Apr 23 '12 at 07:51
  • @UNECS: In canonical mode the entire line becomes available to `getchar()` after the `'\n'`, character-by-character. – caf Apr 24 '12 at 01:58
  • I ok I understand that, the thing I can't get my head around is if I wait to the `\n` I can't see what I'm typing with `echo` off. If I have `echo` on oviously I can see what I'm typing but then I can't stop empty lines being displayed because the terminal with display them even if my program doesn't, and all the output is echoed. Maybe I'm not asking the correct question? – UNECS Apr 24 '12 at 02:20
  • @UNECS: Maybe - so what you want is normal text entry, except that if someone hits enter without entering any characters on a line, the cursor stays where it was? – caf Apr 24 '12 at 04:45
3

It's implementation-dependent. On my machine, pressing backspace led to the byte 127 being read by read(). This code worked on my machine.

#include <unistd.h>
#include <stdio.h>
#include <termios.h>
#include <string.h>
#include <stdlib.h>

#define MAXBUFSIZE (10000U)
#define DEL (127)

int main(void) {
    char buf[MAXBUFSIZE];
    char c;
    size_t top;
    struct termios curterm;

    tcgetattr(STDIN_FILENO, &curterm);
    curterm.c_lflag &= ~(ICANON| ECHO);
    curterm.c_cc[VTIME] = 0;
    curterm.c_cc[VMIN] = 1;
    tcsetattr(STDIN_FILENO, TCSANOW, &curterm);

    top = 0;
    while (read(STDIN_FILENO, &c, sizeof c) == 1) {
        switch (c) {
            case DEL:
                if (top) {
                    --top;
                    const char delbuf[] = "\b \b";
                    write(STDOUT_FILENO, delbuf, strlen(delbuf));
                }
                break;
            case '\n':
                write(STDOUT_FILENO, &c, sizeof c);
                write(STDOUT_FILENO, buf, top);
                top = 0;
                break;
            default:
                buf[top++] = c;
                write(STDOUT_FILENO, &c, sizeof c);
                break;
         }
    }

    return 0;
}
1

When the user presses backspace the application receives some control code. The application has to interpret the control code and take a character out of its buffer (application buffer since you are not using the kernel buffer). If the application is doing any kind of echoing (eg: echoing stars in place of the password characters) then it will have to send some other control codes to move the cursor left and blank out the last star.

Both the codes received for delete or backspace and the codes you send to move the cursor depend on the type of terminal the user has, so before you can do any of this jiggery-pokery you have to detect the terminal type as well. Most programmers don't want to spend time reading the manual for hundreds of different types of terminal, so they generally use a library that hides all of this from them. One such library is curses.

Tom V
  • 11
  • 1