When exiting terminal rawmode my contents stays on the screen - c

I've used this small editor for the basis of my project that I'm doing: https://github.com/antirez/kilo
The editor uses the terminal in rawmode and writes using VT100 escape sequences, however when exiting the program the contents that were displayed, stay displayed.
Before exiting...
After exiting...
As you can see the prompt appears again but what was left of the editor stays there until written over.
// Low level terminal handling
void disable_raw_mode(int fd)
{
// dont bother checking the return value as its too late
if (Editor.rawmode) {
tcsetattr(fd, TCSAFLUSH, &orig_termios);
Editor.rawmode = 0;
}
}
void editor_at_exit(void)
{
disable_raw_mode(STDIN_FILENO);
}
int enable_raw_mode(int fd)
{
struct termios raw;
if(Editor.rawmode) return 0; //already enabled
if(!isatty(STDIN_FILENO)) goto fatal;
atexit(editor_at_exit);
if(tcgetattr(fd, &orig_termios) == -1) goto fatal;
raw = orig_termios; // modify the original mode
/* input modes: no break, no CR to NL, no parity check, no strip char,
* * no start/stop output control. */
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
// output modes - disable post processing
raw.c_oflag &= ~(OPOST);
//control modes - set 8 bit chars
raw.c_cflag |= (CS8);
//local modes, choing off, canonical off, no extended functions, no signal chars (, etc)
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
//control chars - set return condition: min number of bytes and a timer
raw.c_cc[VMIN] = 0; // return each byte, or zero for a timeout
raw.c_cc[VTIME] = 1; //100ms timeout
//put terminal in raw mode after flushing
if(tcsetattr(fd, TCSAFLUSH, &raw) < 0) goto fatal;
Editor.rawmode = 1;
return 0;
fatal:
errno = ENOTTY;
return -1;
}
From what I understand, when the program exits the atexit(editor_at_exit) function is called and in that function raw mode is disabled. What am I missing to clean the terminal back to what it was before the editor was opened. I'm not looking to just clear the whole terminal.

The functionality you are looking for is called "alternate screen buffer", which originated in xterm but is nowadays supported by most terminals.
Alternate screen buffer is designed to provide exactly this functionality for full-screen terminal programs. In normal operation, output gets added to the scrollback buffer (and most terminals let the user scroll back to previous lines). Switching to the alternate screen buffer the scrollback buffer is left alone, and alternate screen buffer output is not added to the scrollback buffer. When returning from alternate screen buffer, the original scrollback buffer state is restored. This is what full-screen applications like nano use.
To switch to the alternate screen buffer, I recommend writing (the C string)
"\033[?1049h\033[2J\033[H" (15 chars)
to the terminal. If the terminal emulator supports the alternate screen buffer, this changes to it, clearing it and moving the cursor to the upper left corner. If the terminal emulator does not support it, this will clear the screen and move the cursor to the upper left corner.
To return from the alternate screen buffer, I recommend writing (the C string)
"\033[2J\033[H\033[?1049l" (15 chars)
to the terminal. If the terminal emulator supports the alternate screen buffer, this first clears the alternate screen buffer, then returns to the original scrollback buffer (like e.g. nano does). If the terminal emulator does not support it, this will clear the screen and move the cursor to the upper left corner.
I recommend this pair ("\033[?1049h\033[2J\033[H" and "\033[2J\033[H\033[?1049l"), because it works in a reasonable fashion regardless of whether the terminal emulator supports the alternate screen buffer or not, not leaving the full-screen application state visible afterwards.
If standard input is a terminal, I also recommend using e.g.
int write_term(const char *p)
{
const char *q = p;
ssize_t n;
int retval = 0, saved_errno;
/* Nothing to write? */
if (!q || !*q)
return 0;
saved_errno = errno;
/* async-signal safe version of q = p + strlen(p) */
while (*q)
q++;
while (p < q) {
n = write(STDIN_FILENO, p, (size_t)(q - p));
if (n > 0) {
p += n;
} else
if (n != -1) {
retval = EIO;
break;
} else
if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
retval = errno;
break;
}
}
errno = saved_errno;
return retval;
}
to write the strings to the terminal, because standard C I/O functions may not be able to write to standard input (which is intended for reading only, after all). The above function is extremely careful, ignoring signal delivery (and even busy-looping if necessary if standard input is nonblocking), and even keeping errno intact; it is also async-signal safe, which means it could be used in a signal handler safely (although I advise against changing terminal buffer mode or settings in a signal handler, as that gets quite complicated to do correctly).
(The OP's code might have a suitable low-level I/O function already implemented, but one was not shown in the question.)

Related

Non canonical terminal mode buffer stdout in c program

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 ! :)
✅ 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:
http://software-dl.ti.com/ccs/esd/documents/sdto_cgt_tips_for_using_printf.html

Disallow input at certain times

I have got a text based game in c that uses scanf.
There are a few times when the player is supposed to type in things, however, while he isn't, the cursor stays in the game, letting the user type in anything he wants, which ruins future scanfs and the story.
Is there a way to disallow input unless there is a scanf waiting for a response?
I think it would be helpful to step back and think about all the moving parts that exist in the execution environment of your program.
When executed, your program becomes a distinct process running in the multitasking environment of the OS. The terminal is a separate process with an associated GUI window, and which may be running locally or remotely (e.g. someone could theoretically run your game from a remote location by connecting over a network via ssh). The user interacts with the terminal program through their keyboard and screen.
Now, it is actually the terminal process (working closely with the OS kernel) that is responsible for most of the nuances of user input. It is the terminal that prints just-typed characters to its GUI window as soon as it receives them, and it is the terminal that maintains an input buffer of characters that have been typed but that have not yet been read by a foreground process.
Conveniently, terminals allow their behavior to be controlled by a set of configuration settings, and these settings can be changed programmatically during the run-time of the connected program. The C-level API that we can use to read and write these settings is called termios.
There's a great article on terminals I highly recommend: The TTY demystified. For the purposes of this question, the section Configuring the TTY device is most useful. It doesn't demonstrate the termios library directly, but shows how to use the stty utility which uses the termios library internally.
(Note that, although the links I've been giving so far are focused on Linux, they are applicable to all Unix-like systems, which includes Mac OS X.)
Unfortunately there's no way to completely "disallow" input with a single switch, but we can achieve the same effect by toggling a couple of terminal settings and manually discarding buffered input at the right times.
The two terminal settings we need to concern ourselves with are ECHO and ICANON. Both settings are normally on by default.
By turning off ECHO, we can prevent the terminal from printing just-typed characters to the terminal window when it receives them. Hence, while the program is running, any characters the user types will seem to be ignored completely, although they will still be buffered internally by the terminal.
By turning off ICANON, we ensure that the terminal will not wait for an enter keypress to submit a complete line of input before returning input to the program, e.g. when the program makes a read() call. Rather, it will return whatever characters it currently has buffered in its internal input buffer, thereby making it possible for us to discard them immediately and carry on with execution.
The full process will look like this:
1: Disable input, meaning turn off ECHO and ICANON.
2: Run some gameplay with output, not requiring any user input.
3: Enable input, meaning discard any buffered terminal input and then turn on ECHO and ICANON.
4: Read user input.
5: Repeat from step 1. Subsequent gameplay can now make use of the latest user input.
There is a complication in step 3 related to discarding buffered input. We can implement this discarding operation by simply reading input from stdin via read() with a fixed-length buffer until there's no more input to be read. But if there's no input ready to be read at all for the discarding operation, then the first call would block until the user types something. We need to prevent this blocking.
I believe there are two ways this could be done. There's such a thing called a non-blocking read, which can be set up with termios or fcntl() (or by opening a second file descriptor to the same endpoint with the O_NONBLOCK flag, I think) which would cause read() to return immediately with errno set to EAGAIN if it would block. The second way is to poll the file descriptor with poll() or select() to determine if there's data ready to be read; if not, we can avoid the read() call completely.
Here's a working solution that uses select() to avoid blocking:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <termios.h>
struct termios g_terminalSettings; // global to track and change terminal settings
void disableInput(void);
void enableInput(void);
void discardInputBuffer(void);
void discardInputLine(void);
void setTermiosBit(int fd, tcflag_t bit, int onElseOff );
void turnEchoOff(void);
void turnEchoOn(void);
void turnCanonOff(void);
void turnCanonOn(void);
int main(void) {
// prevent input immediately
disableInput();
printf("welcome to the game\n");
// infinite game loop
int line = 1;
int quit = 0;
while (1) {
// print dialogue
for (int i = 0; i < 3; ++i) {
printf("line of dialogue %d\n",line++);
sleep(1);
} // end for
// input loop
enableInput();
int input;
while (1) {
printf("choose a number in 1:3 (-1 to quit)\n");
int ret = scanf("%d",&input);
discardInputLine(); // clear any trailing garbage (can do this immediately for all cases)
if (ret == EOF) {
if (ferror(stdin)) { fprintf(stderr, "[error] scanf() failed: %s", strerror(errno) ); exit(1); }
printf("end of input\n");
quit = 1;
break;
} else if (ret == 0) { // invalid syntax
printf("invalid input\n");
} else if (input == -1) { // quit code
quit = 1;
break;
} else if (!(input >= 1 && input <= 3)) { // invalid value
printf("number is out-of-range\n");
} else { // valid
printf("you entered %d\n",input);
break;
} // end if
} // end while
if (quit) break;
disableInput();
} // end while
printf("goodbye\n");
return 0;
} // end main()
void disableInput(void) {
turnEchoOff(); // so the terminal won't display all the crap the user decides to type during gameplay
turnCanonOff(); // so the terminal will return crap characters immediately, so we can clear them later without waiting for a LF
} // end disableInput()
void enableInput(void) {
discardInputBuffer(); // clear all crap characters before enabling input
turnCanonOn(); // so the user can type and edit a full line of input before submitting it
turnEchoOn(); // so the user can see what he's doing as he's typing
} // end enableInput()
void turnEchoOff(void) { setTermiosBit(0,ECHO,0); }
void turnEchoOn(void) { setTermiosBit(0,ECHO,1); }
void turnCanonOff(void) { setTermiosBit(0,ICANON,0); }
void turnCanonOn(void) { setTermiosBit(0,ICANON,1); }
void setTermiosBit(int fd, tcflag_t bit, int onElseOff ) {
static int first = 1;
if (first) {
first = 0;
tcgetattr(fd,&g_terminalSettings);
} // end if
if (onElseOff)
g_terminalSettings.c_lflag |= bit;
else
g_terminalSettings.c_lflag &= ~bit;
tcsetattr(fd,TCSANOW,&g_terminalSettings);
} // end setTermiosBit()
void discardInputBuffer(void) {
struct timeval tv;
fd_set rfds;
while (1) {
// poll stdin to see if there's anything on it
FD_ZERO(&rfds);
FD_SET(0,&rfds);
tv.tv_sec = 0;
tv.tv_usec = 0;
if (select(1,&rfds,0,0,&tv) == -1) { fprintf(stderr, "[error] select() failed: %s", strerror(errno) ); exit(1); }
if (!FD_ISSET(0,&rfds)) break; // can break if the input buffer is clean
// select() doesn't tell us how many characters are ready to be read; just grab a big chunk of whatever is there
char buf[500];
ssize_t numRead = read(0,buf,500);
if (numRead == -1) { fprintf(stderr, "[error] read() failed: %s", strerror(errno) ); exit(1); }
printf("[debug] cleared %d chars\n",numRead);
} // end while
} // end discardInputBuffer()
void discardInputLine(void) {
// assumes the input line has already been submitted and is sitting in the input buffer
int c;
while ((c = getchar()) != EOF && c != '\n');
} // end discardInputLine()
I should clarify that the discardInputLine() feature I included is completely separate from the discarding of the input buffer, which is implemented in discardInputBuffer() and called by enableInput(). Discarding of the input buffer is an essential step in the solution of temporarily disallowing user input, while discarding the remainder of the input line that is left unread by scanf() is not exactly essential. But I think it does make sense to prevent residual line input from being scanned on subsequent iterations of the input loop. It's also necessary to prevent infinite loops if the user entered invalid input, so for that reason we can probably call it essential.
Here's a demo of me playing around with the input:
welcome to the game
line of dialogue 1
line of dialogue 2
line of dialogue 3
[debug] cleared 12 chars
choose a number in 1:3 (-1 to quit)
0
number is out-of-range
choose a number in 1:3 (-1 to quit)
4
number is out-of-range
choose a number in 1:3 (-1 to quit)
asdf
invalid input
choose a number in 1:3 (-1 to quit)
asdf 1 2 3
invalid input
choose a number in 1:3 (-1 to quit)
0 1
number is out-of-range
choose a number in 1:3 (-1 to quit)
1 4
you entered 1
line of dialogue 4
line of dialogue 5
line of dialogue 6
choose a number in 1:3 (-1 to quit)
2
you entered 2
line of dialogue 7
line of dialogue 8
line of dialogue 9
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 238 chars
choose a number in 1:3 (-1 to quit)
-1
goodbye
During the first triplet of dialogue I typed 12 random characters which were discarded afterward. Then I demonstrated various types of invalid input and how the program responds to them. During the second triplet of dialogue I didn't type anything, so no characters were discarded. During the final triplet of dialogue I quickly pasted a large block of text into my terminal several times (using a mouse right-click, which is a quick and easy shortcut for pasting into my particular terminal), and you can see it discarded all of it properly, taking several iterations of the select()/read() loop to complete.
On Linux and HP-UX machines, use
to disable display of inputs from keyboard on terminal
stty -echo
to enable display of inputs from keyboard on terminal
stty echo

Understanding read + write in c

char buf[1];
if (argc == 1) {
while (read(STDIN_FILENO, buf, 1) > 0) {
write(1, buf, sizeof(buf));
}
}
I have a few things I'd like to clarify about this snippet. We run it, ./exec_file Let's say we just press Enter. We move to the next line and read 1 byte '\n' then write it to stdout bringing us down one more line... simple enough. Now lets say we type h, then Enter. The program spits out h on the next line with an invisible '\n'.
Looking at the code after we type h it reads it into the buffer then writes it to stdout but somehow the program waits to spit it out on the next line till after I've pressed Enter..how?
Lastly, when we first hit the while loop wouldn't read initially return 0 since we haven't typed anything in initially??
stdin behaves a bit different than most other streams.
First, input is line buffered. That means that input isn't available until you press enter. this explains while the h won't appear until you press enter.
Since it is a stream it doesn't really have an end. Instead of failing when there is no data to read, the call will block until some data is available (or until the program receives a signal). A socket works the same way.
The blocking behaviour can be turned off using fcntl :
int fd = STDIN_FILENO;
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
The terminal is by default line buffered, because it is in canonical mode. From Linux manuals tcgetattr(3):
Canonical and noncanonical mode
The setting of the ICANON canon
flag in c_lflag determines whether the terminal is operating in
canonical mode (ICANON set) or noncanonical mode (ICANON unset).
By default, ICANON set.
In canonical mode:
Input is made available line by line. An input line is
available
when one of the line delimiters is typed (NL, EOL, EOL2; or EOF at
the start of line). Except in the case of EOF, the line delimiter
is included in the buffer returned by read(2).
Line editing is enabled (ERASE, KILL; and if the IEXTEN flag is
set:
WERASE, REPRINT, LNEXT). A read(2) returns at most one line of
input; if the read(2) requested fewer bytes than are available in
the current line of input, then only as many bytes as requested are
read, and the remaining characters will be available for a future
read(2).
You can switch off canonical mode on the terminal by calling tcgetattr with proper flags. First of all disable the canonical mode; then set the timeout to 0; set minimum read to 1 for blocking reads or 0 for non-blocking reads. Usually it is customary to also disable local echo, otherwise everything you type would still be automatically visible (and displayed twice in your program):
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
int main() {
struct termios old_settings, new_settings;
int is_terminal;
// check whether the stdin is a terminal to begin with
if (is_terminal = isatty(STDIN_FILENO)) {
// get the old settings
tcgetattr(STDIN_FILENO, &old_settings);
new_settings = old_settings;
// disable canonical mode and echo
new_settings.c_lflag &= (~ICANON & ~ECHO);
// at least one character must be written before read returns
new_settings.c_cc[VMIN] = 1;
// no timeout
new_settings.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &new_settings);
}
while (read(STDIN_FILENO, buf, 1) > 0) {
// add this here so that you can verify that it is character by character,
// and not the local echo from the terminal
write(STDOUT_FILENO, ">", 1);
write(STDOUT_FILENO, buf, sizeof(buf));
}
// finally restore the old settings if it was a terminal
if (is_terminal) {
tcsetattr(STDIN_FILENO, TCSANOW, &old_settings);
}
return 0;
}
If you still want the blocking to happen, but want to read character by character, you can use termios to configure how the input will be given to your program. See the code below.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
int main()
{
char buf[1];
struct termios term, term_orig;
if (tcgetattr(0, &term_orig)) {
printf("tcgetattr failed\n");
exit(-1);
}
term = term_orig;
term.c_lflag &= ~ICANON;
term.c_lflag |= ECHO;
term.c_cc[VMIN] = 1;
term.c_cc[VTIME] = 0;
if (tcsetattr(0, TCSANOW, &term)) {
printf("tcsetattr failed\n");
exit(-1);
}
while (read(0, buf, 1) > 0) {
write(1, buf, sizeof(buf));
}
return 0;
}

Implementing a KeyPress Event in C

I have a infinite loop like the following one, and within this loop, I want to continuously check the keyboard to see if the escape key (ESC) has been pressed or not. If it is pressed, then the loop should be broken. How I can do this in C? (I am using gcc, and do access to pthreads as well in case this must be done via threads)
while(1){
//do something
//check for the ESC key
}
This is heavily system dependent. In Unix/Linux systems, the default terminal handler gathers lines and only notifies the program when a full line is available (after Enter is hit.) If you instead want keystrokes immediately, you need to put the terminal into non-canonical mode:
#include <termios.h>
struct termios info;
tcgetattr(0, &info); /* get current terminal attirbutes; 0 is the file descriptor for stdin */
info.c_lflag &= ~ICANON; /* disable canonical mode */
info.c_cc[VMIN] = 1; /* wait until at least one keystroke available */
info.c_cc[VTIME] = 0; /* no timeout */
tcsetattr(0, TCSANOW, &info); /* set immediately */
Once you've done that, you can use any calls that read from stdin and they will return keys without waiting for the end of the line. You can in addition set c_cc[VMIN] = 0 to cause it to not wait for keystrokes at all when you read from stdin.
If, however, you're reading stdin with stdio FILE related calls (getchar, etc), setting VMIN = 0 will make it think you've reached EOF whenever there are no keys available, so you'll have to call clearerr after that happens to try to read more characters. You can use a loop like:
int ch;
while((ch = getchar()) != 27 /* ascii ESC */) {
if (ch < 0) {
if (ferror(stdin)) { /* there was an error... */ }
clearerr(stdin);
/* do other stuff */
} else {
/* some key OTHER than ESC was hit, do something about it? */
}
}
After you're done, you probably want to be sure to set the terminal back into canonical mode, lest other programs (such as your shell) get confused:
tcgetattr(0, &info);
info.c_lflag |= ICANON;
tcsetattr(0, TCSANOW, &info);
There are also other things you can do with tcsetattr -- see then manual page for details. One thing that might suffice for your purposes is setting an alternative EOL character.
If the main job you're doing can be placed within this main loop, you could go for using STDIN in non-blocking mode. You still have a problem with the terminal which does line-buffering normally. You shall put the terminal to raw mode as well.
What about using Ctrl-C (interrupt)?
Non-blocking means that the read() system call always returns immediately even if there are no new bytes in the file. On Linux/Unix you can make STDIN nonblocking this way:
#include <unistd.h>
#include <fcntl.h>
fcntl(0, F_SETFL, O_NONBLOCK); /* 0 is the stdin file decriptor */
This is what you want:
#include <stdio.h>
#include <conio.h>
void main() {
int c;
while((c = getch()) != EOF )
if(c == 27) break;
/* 27 is the ASCII code for Esc */
}

Capturing input without \n

I am making a simple 2d game in the terminal, and I have been wondering how I could get stdin without having to return. So, instead of the user having to press w\n (\n for return), they would just press 'w' and it would go forwards.
scanf, gets, and getchar cannot do this, but I have seen it be done before in programs such as Vi. How would I achieve this?
You need to set your terminal to non-canonical mode. You can use functions like tcsetattr, and tcgetattr to set and get terminal attributes. Here is a trivial example:
int main(int argc, const char *argv[])
{
struct termios old, new;
if (tcgetattr(fileno(stdin), &old) != 0) // get terminal attributes
return 1;
new = old;
new.c_lflag &= ~ICANON; // turn off canonical bit.
if (tcsetattr(fileno(stdin), TCSAFLUSH, &new) != 0) // set terminal attributes
return 1;
// at this point, you can read terminal without user needing to
// press return
tcsetattr(fileno(stdin), TCSAFLUSH, &old); // restore terminal when you are done.
return 0;
}
For more info about these functions, see glibc documentation. Especially this part.

Resources