web-dev-qa-db-fra.com

Ecrire un "vrai" programme de terminal interactif comme vim, htop, ... en C / C ++ sans ncurses

Non, je ne veux pas utiliser ncurses, parce que je veux apprendre comment fonctionne le terminal et m'amuser à le programmer par moi-même. :) Il ne doit pas nécessairement être portable, il doit fonctionner uniquement sur les émulateurs de terminaux basés sur Linux xterm.

Ce que je veux faire, c'est programmer une application de terminal interactif comme htop et vim. Ce que je veux dire, ce n'est pas la sortie de caractères qui ressemblent à des cases ou à des couleurs, c'est trivial; également pour adapter le contenu à la taille de la fenêtre. Ce dont j'ai besoin c'est

  1. comment obtenir interactions avec la souris comme cliquer sur un caractère et faire défiler la molette de la souris (lorsque la souris est sur un caractère spécifique) pour implémenter le défilement [EDIT: dans un émulateur de terminal bien sûr =], et

  2. comment complètement enregistrer et restaurer la sortie du processus parent et séparer mon impression de sa sortie, donc après avoir quitté mon application, rien d'autre que la commande que j'ai entrée dans le shell ne devrait être là, comme lors de l'exécution de htop et le quitter à nouveau: plus rien n'est visible depuis cette application.

Je ne veux vraiment pas utiliser ncurses. Mais bien sûr, si vous savez quelle partie de ncurses est responsable de ces tâches, vous pouvez me dire où je peux le trouver dans le code source, donc je vais l'étudier.

49
leemes

Je suis un peu confus. Vous parlez d'une "application terminale", comme vim; les applications de terminal n'obtiennent pas d'événements de souris et ne répondent pas à la souris.

Si vous parlez de vraies applications de terminal, qui s'exécutent dans un xterm, l'important est de noter que de nombreux problèmes de portabilité concernent le terminal, et non le système d'exploitation. Le terminal est contrôlé en envoyant différentes séquences d'échappement. Lesquels font ce qui dépend du terminal; les codes d'échappement ANSI sont maintenant assez répandus, cependant, voir http://en.wikipedia.org/wiki/ANSI_escape_code . Ceux-ci sont généralement compris par xterm, par exemple.

Vous devrez peut-être produire une séquence supplémentaire au début et à la fin pour entrer et quitter le mode "plein écran"; cela est nécessaire pour xterm.

Enfin, vous devrez faire quelque chose de spécial au niveau d'entrée/sortie pour vous assurer que votre pilote de sortie n'ajoute aucun caractère (par exemple convertir un simple LF en CRLF), et vous assurer cette entrée ne fait pas d'écho, est transparente et retourne immédiatement. Sous Linux, cela se fait en utilisant ioctl. (Encore une fois, n'oubliez pas de la restaurer lorsque vous avez terminé.)

3
James Kanze

Pour manipuler le terminal, vous devez utiliser séquences de contrôle. Malheureusement, ces codes dépendent du terminal particulier que vous utilisez. C'est pourquoi terminfo (auparavant termcap) existe en premier lieu.

Vous ne dites pas si vous souhaitez utiliser terminfo ou non. Alors:

  • Si vous utilisez terminfo, il vous donnera la séquence de contrôle correcte pour chaque action prise en charge par votre terminal.
  • Si vous n'utilisez pas terminfo ... eh bien, vous devez coder manuellement chaque action dans chaque type de terminal que vous souhaitez prendre en charge.

Comme vous le souhaitez à des fins d'apprentissage, je développerai dans le second.

Vous pouvez découvrir le type de terminal que vous utilisez à partir de la variable d'environnement $TERM. Sous Linux, les plus courants sont xterm pour les émulateurs de terminaux (XTerm, gnome-terminal, konsole) et linux pour les terminaux virtuels (ceux lorsque X n'est pas en cours d'exécution).

Vous pouvez découvrir facilement les séquences de contrôle avec la commande tput. Mais comme tput les imprime sur la console, ils s'appliqueront immédiatement, donc si vous voulez vraiment les voir, utilisez:

$ 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|

Autrement dit, pour effacer l'écran dans un xterm, vous devez générer ESC [ H ESC [ 2J dans un xterm mais ESC [ H ESC [ J dans un terminal Linux.

À propos des commandes particulières que vous demandez, vous devriez lire attentivement man 5 terminfo. Il y a là beaucoup d'informations.

18
rodrigo

Bien que cette question soit un peu ancienne, je pensais que je devrais partager un court exemple de la façon de le faire sans utiliser ncurses, ce n'est pas difficile mais je suis sûr que ce ne sera pas aussi portable.

Ce code définit stdin en mode brut, bascule vers un autre écran tampon (qui enregistre l'état du terminal avant de le lancer), active le suivi de la souris et imprime le bouton et les coordonnées lorsque l'utilisateur clique quelque part. Après avoir quitté avec Ctrl+C le programme rétablit la configuration du terminal.

#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;
}

Remarque: cela ne fonctionnera pas correctement pour les terminaux qui ont plus de 255 colonnes.

Les meilleures références pour les séquences d'échappement que j'ai trouvées sont this et this one.

7
santileortiz