In my console based Linux C++ application I want to get the size of the terminal (lines+columns) and the current cursor position. For the latter I think I could use the ANSI codes for that, but I am not sure how to parse it correctly. Also I don't see how to get the size of the window?
For other reasons switching to ncurses is not an option at this time.
Old method of getting the size was termcap with the libtermcap. New is terminfo (+lib).
I would recommend to use a library that abstracts this (and all other terminal related stuff) away and use a terminal output library like (n)curses.
This will in addition also work on other Unix systems.
To fetch the size, the correct way is to call the TIOCGWINSZ ioctl(). An example from my code:
struct winsize ws = { 0, 0, 0, 0 };
if(ioctl(tt->outfd, TIOCGWINSZ, &ws) == -1)
return;
/* ws.ws_row and ws.ws_col now give the size */
You'll want to do that initially, and then again after receipt of a SIGWINCH signal, which informs of a WINdow CHange.
As for obtaining the cursor position, that's a little harder. Some terminals allow querying it by DSR 6 (Device Status Report)
$ echo -ne "\e[6n"; cat -v
^[[62;1R
The reply from DSR comes in CSI R, here telling me the (1-based) 62nd row, 1st column.
However, since not all terminals support DSR 6, it may be easiest not to rely on being able to query the cursor position, and instead performing your initial terminal addressing in an absolute manner, placing the cursor exactly where you want it.
Related
I'm currently designing a CLI interface for linux, and for various reasons I am not able to use ncurses. I am using exclusively C++ and the Qt framework.
Therefore, in order to have a user-friendly interface, I have to run this getch loop in a separate thread:
https://stackoverflow.com/a/912796/3605689
Which basically means I have to implement all basic functionalities (such as backspace) by myself. I have already implemented command completion and command history(like when you press tab or uparrow/downarrow in linux), but I can't figure out how to implement leftarrow/rightarrow (aka seeking through the typeahead).
Normally, I implement it like this: upon every gech which is not equal to -1, I check whether the user has pressed a special key (one that modifies the typeahead somehow). I then clear the stdout using the following function:
void inputobject::clear_line(int nletters)
{
QTextStream(stdout) << "\033[2K";
for(int i = 0; i < nletters;i++){
QTextStream(stdout) << "\b";
}
rewind(stdout);
}
And replace it with something else, effectively simulating the typeahead. For example, in the case of backspace, I would save the command call clear_line, and print the command out again, just with one less letter, behaving exactly as a normal console application would.
My real problem is with the cursor, in the case of left/rightarrow, I need to move the cursor visual in order to be able to indicate where in the text is the user seeking:
Because of the nature of how I rewrite the given stdout line to simulate the typeahead, it does not really matter where the cursor REALLY is, as long as it stays on the same line - it is just the visual that matters. How can I achieve moving the cursor visual on linux?
The answer was provided in the comment by Evilruff:
Cursor Movement
ANSI escape sequences allow you to move the cursor around the screen at will. This is more useful for full screen user interfaces generated by shell scripts, but can also be used in prompts. The movement escape sequences are as follows:
Position the Cursor:
\033[;H
Or
\033[L;Cf
puts the cursor at line L and column C.
Move the cursor up N lines:
\033[NA
Move the cursor down N lines:
\033[NB
Move the cursor forward N columns:
\033[NC
Move the cursor backward N columns:
\033[ND
Clear the screen, move to (0,0):
\033[2J
Erase to end of line:
\033[K
Save cursor position:
\033[s
Restore cursor position:
\033[u
Not using ncurses and co is a serious limitation.
It is hell to make correct input/output on shell for displaying anything.
The only others real solutions (I can't think as a solution to reimplement a ncurse-like library) I think of are:
making call to dialog (for some example www.linuxjournal.com/article/2807 and for the doc: http://linux.die.net/man/1/dialog)
using the framebuffer mecanism with Qt4 (here)
In my output there are certain lines that are refreshed every few seconds. If I resize the terminal by clicking F11, then output is just as I wanted. If terminal isn't big enough some long lines that are refreshed are splitted in two, and because of that, only one part of line is refreshed, and every time line is refreshed I also get new line.
This could be easily avoided if I could specify default size of terminal (resize terminal from my program). Also it would be great if I could forbid user to change terminal size while program is running.
while(1)
{
cout<<"Long line that is refreshed every 5s... \r";
//if line is splited in two lines, \r will return to beginning of that new line
//and the first part of original line would stay as it is(won't be rewrited)
sleep(5);
}
How do I specify a terminal size or stop terminal resizing?
Some terminal emulators (including the default macOS Terminal.app) support being resized/moved/etc in response to printed control sequences. The sequences are fairly standard but not all terminal emulators implement all of them.
For example:
# set terminal width to 50, height to 100
cout << "\e[8;50;100t";
This answer includes an overview of some other available control sequences.
I don't think you can forbid the user to change the terminal size. A better way would be to catch the SIGWINCH signal that is sent to the process everytime the window size is changed, and use the TIOCGWINSZ / TIOCGSIZE ioctl() to get the dimensions.
I'm currently designing a CLI interface for linux, and for various reasons I am not able to use ncurses. I am using exclusively C++ and the Qt framework.
Therefore, in order to have a user-friendly interface, I have to run this getch loop in a separate thread:
https://stackoverflow.com/a/912796/3605689
Which basically means I have to implement all basic functionalities (such as backspace) by myself. I have already implemented command completion and command history(like when you press tab or uparrow/downarrow in linux), but I can't figure out how to implement leftarrow/rightarrow (aka seeking through the typeahead).
Normally, I implement it like this: upon every gech which is not equal to -1, I check whether the user has pressed a special key (one that modifies the typeahead somehow). I then clear the stdout using the following function:
void inputobject::clear_line(int nletters)
{
QTextStream(stdout) << "\033[2K";
for(int i = 0; i < nletters;i++){
QTextStream(stdout) << "\b";
}
rewind(stdout);
}
And replace it with something else, effectively simulating the typeahead. For example, in the case of backspace, I would save the command call clear_line, and print the command out again, just with one less letter, behaving exactly as a normal console application would.
My real problem is with the cursor, in the case of left/rightarrow, I need to move the cursor visual in order to be able to indicate where in the text is the user seeking:
Because of the nature of how I rewrite the given stdout line to simulate the typeahead, it does not really matter where the cursor REALLY is, as long as it stays on the same line - it is just the visual that matters. How can I achieve moving the cursor visual on linux?
The answer was provided in the comment by Evilruff:
Cursor Movement
ANSI escape sequences allow you to move the cursor around the screen at will. This is more useful for full screen user interfaces generated by shell scripts, but can also be used in prompts. The movement escape sequences are as follows:
Position the Cursor:
\033[;H
Or
\033[L;Cf
puts the cursor at line L and column C.
Move the cursor up N lines:
\033[NA
Move the cursor down N lines:
\033[NB
Move the cursor forward N columns:
\033[NC
Move the cursor backward N columns:
\033[ND
Clear the screen, move to (0,0):
\033[2J
Erase to end of line:
\033[K
Save cursor position:
\033[s
Restore cursor position:
\033[u
Not using ncurses and co is a serious limitation.
It is hell to make correct input/output on shell for displaying anything.
The only others real solutions (I can't think as a solution to reimplement a ncurse-like library) I think of are:
making call to dialog (for some example www.linuxjournal.com/article/2807 and for the doc: http://linux.die.net/man/1/dialog)
using the framebuffer mecanism with Qt4 (here)
I use the following lines to output my simulation's progress info in my c++ program,
double N=0;
double percent=0;
double total = 1000000;
for (int i; i<total; ++i)
{
percent = 100*i/total;
printf("\r[%6.4f%%]",percent);
}
It works fine!
But the problem is I see the terminal cursor keeps blinking cyclically through the numbers, this is very annoying, anyone knows how to get rid of this?
I've seen some programs like wget or ubuntu apt, they use progress bar or percentages too, but they seems no blinking cursor issue, I am wondering how did they do that?
Thanks!
You can hide and show the cursor using the DECTCEM (DEC text cursor enable mode) mode in DECSM and DECRM:
fputs("\e[?25l", stdout); /* hide the cursor */
fputs("\e[?25h", stdout); /* show the cursor */
Just a guess: try to use a proper number of '\b' (backspace) characters instead of '\r'.
== EDIT ==
I'm not a Linux shell wizard, but this may work:
system("setterm -cursor off");
// ...display percentages...
system("setterm -cursor on");
Don't forget to #include <cstdlib> or <iostream>.
One way to avoid a blinking cursor is (as suggested) to hide the cursor temporarily.
However, that is only part of the solution. Your program should also take this into account:
after hiding the cursor and modifying the screen, before showing the cursor again move it back to the original location.
hiding/showing the cursor only keeps the cursor from noticeably blinking when your updates take only a small amount of time. If you happened to mix this with some time-consuming process, your cursor will blink.
The suggested solution using setterm is not portable; it is specific to the Linux console. And running an executable using system is not really necessary. But even running
system("tput civis");
...
system("tput cnorm");
is an improvement over using setterm.
Checking the source-code for wget doesn't find any cursor-hiding escape sequences. What you're seeing with its progress bar is that it leaves the cursor in roughly the same place whenever it does something time-consuming. The output to the terminal takes so little time that you do not notice the momentary rewrite of the line (by printing a carriage return, then writing most of the line over again). If it were slower, then hiding the cursor would help — up to a point.
By the way — this cursor-hiding technique is used in the terminal drivers for some editors (vim and vile).
Those apps are probably using ncurses. See mvaddstr
The reason the cursor jumps around is because stdout is buffered, so you don't know actually how many characters are being printed at some point in time. The reason wget does not have a jumping cursor is that they are actually printing to stderr instead, which is unbuffered. Try the following:
fprintf(stderr, "\r[%6.4f%%]", percent);
This also has the advantage of not cluttering the file if you are saving the rest of the output somewhere using a pipe like:
$ ./executable > log.data
Press insert key...if that doesn't work then press the fn key in your keyboard.
This will definitely work
Hope this helps
No, I don't want to use ncurses, because I want to learn how the
terminal works and have fun programming it on my own. :) It doesn't
have to be portable, it has to work on linux xterm-based terminal emulators only.
What I want to do is programming an interactive terminal application like htop and vim are. What I mean is not the output of characters which look like boxes or setting colors, this is trivial; also to make the content fit to the window size. What I need is
how to get mouse interactions like clicking on a character and scrolling the mouse wheel (when the mouse is at a specific character) to implement scrolling [EDIT: in a terminal emulator of course], and
how to completely save and restore the output of the parent process and seperate my printing from its output, so after leaving my application nothing but the command I entered in the shell should be there, like when running htop and quitting it again: nothing is visible from this application anymore.
I really don't want to use ncurses. But of course, if you know which part of ncurses is responsible for these tasks, you're welcome to tell me where in the source code I can find it, so I will study it.
In order to manipulate the terminal you have to use control sequences. Unfortunately, those codes depend on the particular terminal you are using. That's why terminfo (previously termcap) exists in the first place.
You don't say if you want to use terminfo or not. So:
If you will use terminfo, it will give you the correct control sequence for each action your terminal supports.
If you won't use terminfo... well, you have to manually code every action in every terminal type you want to support.
As you want this for learning purposes, I'll elaborate in the second.
You can discover the terminal type you are using from the environment variable $TERM. In linux the most usual are xterm for terminal emulators (XTerm, gnome-terminal, konsole), and linux for virtual terminals (those when X is not running).
You can discover the control sequences easily with command tput. But as tput prints them on the console, they will apply immediately, so if you want to really see them, use:
$ TERM=xterm tput clear | hd
00000000 1b 5b 48 1b 5b 32 4a |.[H.[2J|
$ TERM=linux tput clear | hd
00000000 1b 5b 48 1b 5b 4a |.[H.[J|
That is, to clear the screen in a xterm you have to output ESC [ H ESC [ 2J in an xterm but ESC [ H ESC [ J in a linux terminal.
About the particular commands you ask about, you should read carefully man 5 terminfo. There is a lot of information there.
Although this is question a bit old, I thought I should share a short example of how to do this without using ncurses, it's not difficult but I'm sure it won't be as portable.
This code sets stdin in raw mode, switches to an alternate buffer screen (which saves the state of the terminal before launching it), enables mouse tracking and prints the button and the coordinates when the user clicks somewhere. After quitting with Ctrl+C the program reverts the terminal configuration.
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
int main (void)
{
unsigned char buff [6];
unsigned int x, y, btn;
struct termios original, raw;
// Save original serial communication configuration for stdin
tcgetattr (STDIN_FILENO, &original);
// Put stdin in raw mode so keys get through directly without
// requiring pressing enter.
cfmakeraw (&raw);
tcsetattr (STDIN_FILENO, TCSANOW, &raw);
// Switch to the alternate buffer screen
write (STDOUT_FILENO, "\e[?47h", 6);
// Enable mouse tracking
write (STDOUT_FILENO, "\e[?9h", 5);
while (1) {
read (STDIN_FILENO, &buff, 1);
if (buff[0] == 3) {
// User pressd Ctr+C
break;
} else if (buff[0] == '\x1B') {
// We assume all escape sequences received
// are mouse coordinates
read (STDIN_FILENO, &buff, 5);
btn = buff[2] - 32;
x = buff[3] - 32;
y = buff[4] - 32;
printf ("button:%u\n\rx:%u\n\ry:%u\n\n\r", btn, x, y);
}
}
// Revert the terminal back to its original state
write (STDOUT_FILENO, "\e[?9l", 5);
write (STDOUT_FILENO, "\e[?47l", 6);
tcsetattr (STDIN_FILENO, TCSANOW, &original);
return 0;
}
Note: This will not work properly for terminals that have more than 255 columns.
The best references for escape sequences I've found are this and this one.
I'm a bit confused. You speak of a “terminal application”,
like vim; terminal applications don't get mouse events, and don't
respond to the mouse.
If you're talking about real terminal applications, which run in an
xterm, the important thing to note is that many of the portability
issues concern the terminal, and not the OS. The terminal is controlled
by sending different escape sequences. Which ones do what depend on the terminal; the ANSI escape codes are now fairly widespread, however, see http://en.wikipedia.org/wiki/ANSI_escape_code. These are generally understood by xterm, for example.
You may have to output an additional sequence at the start and at the end to enter and leave “full screen” mode; this is necessary for xterm.
Finally, you'll have to do something special at the input/output level to ensure that your output driver doesn't add any characters (e.g. convert a simple LF into a CRLF), and ensure that input doesn't echo, is transparent, and returns immediately. Under Linux, this is done using ioctl. (Again, don't forget to restore it when you finish.)