Writing a “real” interactive terminal program like

2019-03-07 23:46发布

问题:

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

  1. 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

  2. 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.

回答1:

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 think 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.)



回答2:

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.



回答3:

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.