2

I am working a school project (building a very basic shell).

The idea is to be able to do line edition like in bash. For this, I change the terminal mode to non canonical and I stop echo.

I made a very simple code to expose my issue (please note, I do check for functions returns etc... I just made it as short as possible for this post)

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

int main()
{
    int     ret;
    char    buff;
    char    *term_name;
    char    *termcap;
    struct termios  termios_new;
    struct termios  termios_backup;

    /*
    ** Init termcap library
    */
    term_name = getenv("TERM");
    tgetent(NULL, term_name);

    /*
    ** Get the terminal mode to non canonical and shut down echo
    */
    bzero(&termios_new, sizeof(struct termios));
    tcgetattr(STDIN_FILENO, &termios_backup);
    termios_new = termios_backup;

    termios_new.c_lflag &= ~(ICANON);
    termios_new.c_lflag &= ~(ECHO);
    termios_new.c_cc[VMIN] = 1;
    termios_new.c_cc[VTIME] = 0;

    /*
    **  Set the change
    */
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios_new);

    /*
    ** Get the termcap for clearing screen on the particular terminal
    */
    termcap = tgetstr("cl", NULL);

    /*
    ** Loop read to get user entries and clear screen for 'c', output char for 'b', break for 'q'
    */
    while((ret = read(STDIN_FILENO, &buff, 1)) > 0)
    {
        if (buff == 'c')
            tputs(termcap, 1, putchar);
        else if (buff == 'b')
            putchar(buff);
        else if (buff == 'q')
            break ;
        buff = 0;
    }

    /*
    ** Put back the terminal mode as found before
    */
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios_backup);
    return (0);
}

So basically it's a loop on read to catch user entries. It clears the screen for 'c', output char for 'b', break and restore the original terminal mode for 'q'.

Issue is :

Whenever I type anything, it seems to be buffered because nothing happens until I break the loop with 'q'. At this moment, the output show on screen, if I typed 5 times b, I'll get the 5 b's and if I types 'c', the screen will be cleared. BUT, only after typing 'q'. The behaviour is the same while restoring or not the original terminal mode. (the last line before return)

What I suspect :

After making the code very short and checking all returns, I tend to think there could only be an issue with the way I change the terminal mode ? I tries with the flags TCSAFLUSH and TCSADRAIN for the tcsetattr function with same result.

Thanks ! :)

Bstorm
  • 247
  • 1
  • 10
  • _The idea is to be able to do line edition like in bash_. Then forget `termios`, you want [GNU readline](https://tiswww.case.edu/php/chet/readline/rltop.html) – David Ranieri Sep 05 '20 at 07:35
  • Unbuffer the `stdin` using `setvbuf(stdin, NULL, _IONBF, 0 );` – Frankie_C Sep 05 '20 at 07:51
  • Thanks @DavidRanieri I wish I could but actually the school project ask us to use only termcap lib. – Bstorm Sep 05 '20 at 07:54
  • Same for setvbuf @Frankie_C , I can't use external functions which are not in the termcap lib. But according to your answer you don't seem to think there is anything bad with the current code, is it supposed to act this way ? – Bstorm Sep 05 '20 at 07:55
  • Not sure, but can you try with `tcsetattr(STDIN_FILENO, TCSANOW, &termios_new);` instead of `tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios_new);`? – David Ranieri Sep 05 '20 at 08:03
  • I only see one `putchar(buff);` and that is after `b` (break). That is the only time you will get output.with echo off. There are a number of answers already on this site that show input handling in raw-non-cannonical mode. It's worth a search. – David C. Rankin Sep 05 '20 at 08:06
  • I see. Anyway the canonical mode already sets unbuffered mode.So the problem is elsewhere. – Frankie_C Sep 05 '20 at 08:11
  • 1
    Here is a quick answer related to handling swapping mode, it's used related to [Hide password input on terminal](https://stackoverflow.com/a/32421674/3422102), but does provide some additional line editing techniques, (such as the user making a mistake and needing to backspace and correct). It may provide some of the missing information – David C. Rankin Sep 05 '20 at 08:14
  • Thanks @DavidC.Rankin. The `putchar` is after the `else if` not after the `break`, not sure what you meant ? Thanks for the post I'll look into it :) – Bstorm Sep 05 '20 at 08:18
  • Ok so I made it work by using `putstr(termcap)` instead of `tputs()`... but it seems weird because then why do `tputs` exists ?! – Bstorm Sep 05 '20 at 08:30

1 Answers1

2

✅ SOLVED :

Ok so for anyone who encounter this situation, it's quite interesting because it made me learn many stuffs (well it's a school project so...).

Using putchar in the tputs parameters was the issue, because putchar is buffered, juste like printf. I ended up trying two things that led me to the realisation: fflush() after the tputs function call would work so obviously the issue was buffer related. Then tried to output to STDERR_FILENO and it kinda solved it too, and in fact the stderr is the only one _IONBF, so not buffered.

So I ended up creating a putchar function with only write in it, and that was it.

Here are more info about the three buffer modes:

Bstorm
  • 247
  • 1
  • 10