60

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.

leemes
  • 44,967
  • 21
  • 135
  • 183
  • 6
    "you're welcome to tell me where in the source code I can find it" – leemes Dec 12 '11 at 15:14
  • 2
    I said this because I am sure I don't know the ncurses source code as good as someone using it all day. :) – leemes Dec 12 '11 at 15:15
  • most people use its API but doesn't change its implementation, so there's small chance you'll meet such people. Just read the sources, for example, I found file "lib_mvcur.c" in one minute (including downloading sources) which contains "The routines for moving the physical cursor and scrolling". Check file comments, documentation looks fine – Andriy Tylychko Dec 12 '11 at 15:22
  • 2
    Most terminals emulate atleast a [vt220](http://vt100.net/docs/vt220-rm/), so you could start implementing the control for that. (Though few programs are crazy enough to do that, including vim, and they rather use ncurses or at least termcap) – nos Dec 12 '11 at 15:26
  • First you need to know how to set the terminal into raw mode, second you at least need termcap (libtermcap) or roll your own tput and abstraction layer. Without the mouse, I'd estimate this would require a few months of work for someone with C and unix experience. The tgetc with timeout on ESC is nasty for parsers. Certainly not for the faint at heart... – wildplasser Dec 12 '11 at 15:30
  • Saving and restoring output is completely impossible on a terminal. Apps that seem to be doing this are simply switching to the "alternate terminal buffer" and back with an escape sequence. – R.. GitHub STOP HELPING ICE Dec 12 '11 at 15:32
  • @wildplasser what's the timeout I need to parse ESCape sequences? I already do so, but with a timeout of arbitrarily chosen 1ms. I already have an abstraction layer for output of characters which abstracts this to text boxes with HTML support, and no, I didn't need monts but 2 days :) – leemes Dec 12 '11 at 15:59
  • The classic (serial) terminal code used a 1 second timeout to wait for an ESC, IIRC. Regarding several month: try adapting your code to *every* terminal type that's ever been made, not only the ANSI-fied vt220. – wildplasser Dec 12 '11 at 16:05
  • It doesn't have to be portable, it only sould support xterm-based terminal emulators. – leemes Dec 12 '11 at 16:15
  • Have you looked into the code? Is it too big for you? – Sebastian Mach Dec 12 '11 at 16:21

3 Answers3

23

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.

rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • _There are many ways to skin a cat._ And there are many ways to clear the screen. Particularly, IIRC, `ESC[2J` clears the whole screen while `ESC[J` clears from the cursor to the end-of-screen. But since `ESC[H` moves the cursor to the HOME, those should be equivalent. Not surprisingly `xterm` and `linux` terminals tend to be quite similar. – rodrigo Dec 12 '11 at 16:27
  • @leemes: That is because the terminal you are using seems to support both. – Martin York Dec 12 '11 at 16:28
  • 1
    Only really obscure stuff actually varies heavily between real-world terminals, which all roughly conform to the ANSI/ECMA standard. If you just treat both ^H and ^? as backspace key and otherwise follow the standard, curses/termcap/terminfo is pretty much obsolete... – R.. GitHub STOP HELPING ICE Dec 12 '11 at 18:56
  • @R.. I would say the opposite, that only the most used stuff is common . For example, pressing F2 (`tput kf2`) will generate `ESC OQ` in xterm but `ESC[[B` in linux. However pressing right-arrow (`tput cuf1`) will do `ESC [C` on both. – rodrigo Dec 12 '11 at 19:28
15

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.

santileortiz
  • 323
  • 4
  • 9
3

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

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • 2
    There is GPM for mouse events in terminal applications, no? – Antoine Dec 12 '11 at 15:28
  • 21
    The beginning of this answer is completely bogus. There's a protocol to inform the terminal you want mouse click events, and a protocol for them to be sent from the terminal to the application, all as escape sequences. Linux console does not support this and instead uses the hideous GPM approach, but `xterm` and others do support it correctly. – R.. GitHub STOP HELPING ICE Dec 12 '11 at 15:31
  • With mouse events, I mean within terminal emulators. I'm already disabling echo of user input and input buffer, so I use stdin as a kind of 'key events' stream rather than lines of text. Where can I find what additional sequences these are which I need to put at start / end for "full screen" mode? Isn't there any ANSI escape code? – leemes Dec 12 '11 at 15:36
  • @R..: Where can I find this protocol? This is exactly what I'm looking for. Of course I don't need mouse control in the real linux terminal, as there isn't such a thing like in vim and htop (That's why i am comparing to vim and htop) – leemes Dec 12 '11 at 15:40
  • @R.. I'm not sure what you mean "Linux console does not support this and instead uses the hideous GPM approach, but xterm and others do support it correctly." - when GPM is running, applications can read the escape sequences from the terminal just like in xterm. All "the GPM approach" is, is that mouse support is not built in to the kernel and it won't send the escape sequences without GPM. – Random832 Dec 12 '11 at 15:52
  • @R Real terminals don't have mice:-). (Of course, real terminals can't be resized, either, but `vim` picks up changes in the size of the `xterm`. So there must be something.) – James Kanze Dec 12 '11 at 17:27
  • 1
    @leemes It's a ANSI escape that you need to send. I'm not sure what, but it should be in the `termcaps` file. (The whole point of `ncurses`/`termcaps` is that the control sequences are read from a configuration file, and vary according to the type of terminal.) – James Kanze Dec 12 '11 at 17:29
  • @JamesKanze thanks, I will now write a termcaps wrapper class and see how far I come :) – leemes Dec 12 '11 at 18:04
  • @R.. I think there's another API that lets you do extra stuff like messing with the selection, reading/writing the clipboard directly, etc... And maybe detecting finer-grained events [e.g. movement, interactive dragging] back when the escape sequences only supported detecting clicks, but for as long as I can remember linux+gpm has supported xterm's mouse protocol. – Random832 Dec 12 '11 at 21:22
  • @Antoine Maybe, maybe not. Depends on lots of stuff. Does your router have a mouse driver? – n. m. could be an AI Feb 21 '16 at 09:08