0

I'm following a tutorial for making a text editor . So far it's been tinkering with raw mode . The following code is supposed to turn off canonical mode , and output each keypress.

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

struct termios orig_termios;
void disableRawMode() { … }
void enableRawMode() { … }

int main() {
  enableRawMode();
  char c;
  while (read(STDIN_FILENO, &c, 1) == 1 && c != 'q') {
    if (iscntrl(c)) {
      printf("%d\n", c);
    } else {
      printf("%d ('%c')\n", c, c);
    }
  }
  return 0;
}

I originally forgot to add "\n" after the printf() statements, and the result was that I only got the outputted characters after the program terminates , ie after pressing q in this example . However after adding "\n", the terminal outputs each letter as pressed.

Could anyone be so kind and explain why is it behaving this way?

Ait-Gacem Nabil
  • 165
  • 3
  • 12
  • Strong suggestion: [NCurses](https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/) is your friend. To answer your question: the behavior you're describing is [buffered I/O](https://www.geeksforgeeks.org/i-o-buffering-and-its-various-techniques/). – paulsm4 Aug 26 '21 at 18:28
  • @paulam4 But the above code presumably *disables* canonical mode, and characters are read bye by byte no ? I'm confused sorry – Ait-Gacem Nabil Aug 26 '21 at 18:33
  • You hide exactly the part of the program that switches the tty into raw mode... please read [How to create a Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) in order to know how to make our life a bit easier. The implementation for `enableRawMode()` is missing, and fundamental to see if you have switched correctly to raw mode. – Luis Colorado Aug 27 '21 at 12:37
  • @paulsm4, don't recommend using ncurses just to switch terminal to raw mode.... ncurses is a full capability library that handles terminal independence, while switching to raw mode requires just a single `ioctl(2)` call. Your program will grow instantly to over 2Mb at runtime and will load a huge library as requirement just to use one or two calls to it. – Luis Colorado Aug 27 '21 at 12:43
  • @Luis Colorado - I *DO* recommend using ncurses for "making a text editor". The OP needs more than just "going into raw mode" to accomplish his goal. Nevertheless, the *REAL* problem, as I stated, is "buffered I/O". Simply putting the terminal into raw mode is *NOT* sufficient for "printf()". The OP *ALSO* needs to unbuffer "stdout". But that's just the start. If the OP also wants to control keyboard input - and screen output - then ncurses becomes a much more attractive solution. – paulsm4 Aug 27 '21 at 15:34
  • 1
    @paulsm4, yes, you are right in all you say. But the question was about strange terminal in raw mode.... for now, the OP doesn't use ncurses, and it's clear that he wants to learn how to do it, not o have the problem solved efficiently with a library that does all the dirty work. We should focus on what he is asking, as we don;t actually know which are his/her intentions. – Luis Colorado Aug 29 '21 at 16:11
  • Sigh... This discussion is moot. The underlying problem was that the OP didn't realize that just setting termio wasn't sufficient. If he wanted to use "printf()", then he ALSO needed to update the stream, by calling setvbuf(). Fortunately, he got an answer :) Nevertheless, we both want to help. Since the OP said "I'm making a text editor", and since he's in text mode, I thought it would be a DISSERVICE not to suggest "ncurses". – paulsm4 Aug 29 '21 at 17:33

2 Answers2

3

Raw-mode is the concern of the terminal but buffer management of stdout occurs before reaching the terminal.

By default, when file-descriptor 1 (STDOUT_FILENO) is link to a terminal, then stdout uses a line-buffering policy. This means that the output buffer of stdout is flushed to the file-descriptor 1 when a \n is written (or when it is full). Only at this moment the characters can reach the terminal which can react in different ways depending on its configuration.

In your example, the characters just stay in memory until the process terminates (stdout is flushed at this moment).

prog-fh
  • 13,492
  • 1
  • 15
  • 30
  • Occurs *before* on output... on input I think it's the reverse exactly. – Luis Colorado Aug 27 '21 at 12:38
  • By the way, he is not using `stdio` for reading at all. He reads using `read(2)`, not `fread(3)`, so `stdin` is not aware on how he reads in raw mode. – Luis Colorado Aug 27 '21 at 12:40
  • @LuisColorado sorry, I don't understand your comments since the question is about the sensibility to `\n` when printing (on output, not on input). I agree that the raw mode is the concern of input, but the confusion in the question comes from the fact that the author could think that raw-mode **could** eventually have caused the observed behaviour. – prog-fh Aug 27 '21 at 13:31
  • it's not clear what he is pretending.... and it;s not clear if the standard output is directred to a terminal from the sample code or the question. The actual code of the raw-mode-switching routines is hidden, and the OP has been asked to edit his question to include the code to switch to raw mode. He wants to make an editor and this requires to switch to raw mode to read input char by char. The question is clear, his explanation is not. – Luis Colorado Aug 29 '21 at 16:17
2

Commonly, when a C program starts with the standard output stream connected to a terminal, the stream is line buffered. This means characters printed with printf or standard library methods are kept in a buffer until one of:

  • \n is printed (ending the line, hence “line buffered”),
  • the buffer is full,
  • the stream is manually flushed (as with fflush), or
  • input is solicited on a stream that is unbuffered or line buffered but requires characters from “the host environment” (notably a human).

The terminal setting is irrelevant as the characters are kept in an internal buffer of the standard C library implementation and are not sent to the terminal until one of the above events.

You can set the stream to unbuffered by calling setvbuf(stdout, NULL, _IONBF, 0) before performing any other operation on stdout.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • Ohhh i see now, I guess I was confusing Input Buffer with Output Buffer .. I didn't realize they were two separate things, Thank you so much for the explanation ! – Ait-Gacem Nabil Aug 26 '21 at 18:35
  • @Ait-Gacem Nabil: please consider "upvoting" and "accepting" this answer. Also please walk through the ncurses tutorial, and see if it's a good fit for your project. – paulsm4 Aug 27 '21 at 15:37
  • 1
    @paulsm4 that may not be all of it. Inside a Docker container in raw mode I had to use read(STDIN_FILENO ...) instead of fgetc() as [shown here](https://stackoverflow.com/questions/75537686/c-program-inside-docker-container-ignores-fgetcstdin). But that had a side-effect similar to Ait-Gacem Nabil's question: after the read() output such as printf("\rSame line") was no longer visible until at least one "\n" was output. With fgetc() that was not an issue. I'm wondering if there is something that's not saved in termios that needs to be when temporarily entering raw mode – Jeff Brower Mar 01 '23 at 19:36
  • @paulsm4 but yes I agree this should be the accepted answer. Hopefully OP returns at some point and marks it – Jeff Brower Mar 01 '23 at 19:58