Qu'est ce que curses ?
La bibliothèque curses permet une gestion indépendante du terminal des impressions sur l'écran, et de la gestion du clavier pour les terminaux textes comme les VT100s, les consoles linux ou les terminaux virtuels X11 comme xterm ou rxvt. L'affichage des terminaux supportent de multiples codes de contrôles utilisés pour des opérations comme le déplacement du curseur, le scrolling de l'écran et l'effacement de zones sur celui-ci. Les différents terminaux utilisent chacun une large variété de codes de contrôles différents, et chacun nécessitent des petites bidouilles qui lui sont propres.
Dans le monde de l'affichage graphique sous X, certains vous diraient « Et alors ? ». Il est vrai que les terminaux à affichage texte sont une technologie obsolète, mais certaines personnes arrivent encore à en faire des choses élégantes. Certaines d'entre elles sont celles qui recherchent de faibles empreintes mémoire ou qui veulent utiliser des Unices dans l'embarqué dont la présente d'un serveur X n'est pas requise. D'autres sont celles qui installent les systèmes d'exploitations, qui configurent les noyaux, et opèrent bien avant que le serveur X soit disponible.
La bibliothèque curses cache tous les détails propre à chaque terminal, et offre aux programmeur une abstraction de l'affichage, contenant de multiples fenêtres qui ne se chevauchent pas. Le contenu d'une fenêtre peut être modifié de plusieurs manières, en ajoutant du texte, en le supprimant, ou en changeant son apparence, et la bibliothèque curses décidera automagiquement quels sont les codes de contrôles qui seront envoyés au terminal pour produire la sortie adéquate.
La bibliothèque curses fût initialement écrite pour les Unices BSD; plus tard, les versions System V d'AT&T y ajoutèrent plusieurs améliorations et de nouvelles fonctions. La version BSD de curses n'est maintenant plus maintenue, et a été remplacée par ncurses, qui est une implémentation open-source de l'interface d'AT&T. Si vous utilisez un Unix open-source (ndt : et libre) comme Linux ou FreeBSD, votre système utilisera certainement ncurses. Depuis que les versions commerciales d'Unix ne sont plus basées sur le code de Système V, toutes les fonctions décrites ici sont probablement disponibles. Cependant, les vieilles version de curses installées par certains Unices propriétaires ne supporteront peut être pas tout.
Personne n'a fait un portage sous Windows du module curses. Sur les plateformes Windows, essayez le module Console écrit par Fredrik Lundh. Le module console permet la gestion d'un curseur adressable, ainsi qu'une gestion de la souris et du clavier, et il est disponible depuis http://effbot.org/zone/console-index.htm.
Le module curses pour Python
Le module Python n'est qu'une simple glue autour des fonctions C proposées par curses; si vous êtes déjà famillier avec la programmation avec curses en C, ce sera très facile de transposer ce que vous connaissez sur Python. La plus grande différence est que l'interface Python rend les choses plus simple, en regroupant différentes fonctions C comme addstr(), mvaddstr(), mvwaddstr(), dans une seule méthode addstr(). Tout ceci sera détaillé plus tard.
Ce HowTo est une simple introduction à l'écriture de programmes en mode textes avec curses et Python. Il ne cherche pas à être un guide complet sur l'API curses; pour cela, lisez la partie sur curses dans la documentation des modules Python, et les pages de manuel pour la version C de ncurses. Par contre, il vous donnera quelques pistes pour commencer.
Démarrer et terminer les applications curses
Avant de faire quoi que ce soit, curses doit être initialisé. Ceci est réalisé avec l'appel à la fonction initsrc(), qui va déterminer le type de terminal, lui enverra des codes de contrôles d'initialisation, et créeras un certain nombre de structure de données internes pour sa gestion. Si il réussit, initscr() retournera un objet « window » représentant l'écran entier; celui ci est habituellement appelé stdscr d'après le nom de la variable correspondante en C.
import curses
stdscr = curses.initscr()
Habituellement, les applications curses désactivent l'écho automatique des touches sur l'écran, ceci afin de pouvoir lire leurs valeur et de les afficher seulement sous certaines circonstances. Ceci nécessite l'appel à la fonction noecho().
Les applications vont aussi avoir besoin de réagir instantanément à l'appuis des touches, sans attendre que la touche Entrée soit appuyée; ceci est appelé « mode cbreak », en opposition à l'habituel mode d'entrée tamponné (ndt: buffered, ou buffurisé en franglais).
Les terminaux retournent souvent les touches spéciales tels que les touches du curseur ou les touches de navigations comme Page UP et Home, comme des séquences d'échappement multi-octet. Vous pouvez programmer votre application pour qu'elle puisse gérer de tels séquences et effectuer les traitements correspondants, ou bien laisser curses faire cela pour vous, en retournant une valeur spéciale comme curses.KEY_LEFT. Pour permettre à curses de faire ce travail, vous devez activer le mode « keypad ».
Terminer une application curses et encore plus simple que d'en démarrer une. Tout ce que vous avez a appeler pour inverser les paramères adaptés à curses pour le terminal est :
curses.nocbreak(); stdscr.keypad(0); curses.echo()
Vous pouvez alors appeler la fonction endwin() pour restorer le mode de fonctionnement d'origine du terminal.
Un problème commun quand on débogue une application curses est de retrouver votre terminal complètement hors d'usage quand l'application est tuée sans restorer le terminal dans son état initial. Cela arrive souvent en Python quand votre code est bogué et qu'il lève une exception non gérée. Par exemple, les touches ne sont plus répétées sur l'écran lorsque vous tapez dessus, ce qui rend quelque peu difficile l'utilisation du shell.
En Python, vous pouvez éviter ces complications et permettre un débogage plus simple en important le module curses.wrapper. Il offre une fonction wrapper() qui prend un callable en paramètres. Il effectue les initialisation détaillées plus haut, et initialise aussi les couleurs si le support des couleurs est présent. Il va ensuite appeler votre callable, et finalement dé-initialiser correctement le terminal. Le callable est appelé à l'intérieur d'un try-except qui va intercepter les exceptions et permettre une dé-initialisation propre, pour ensuite vous repasser l'exception pour le débogage. Ainsi, votre terminal ne sera pas laissé dans un sale état après une exception.
Fenêtres et "Pads"
Les fenêtres sont les éléments d'abstraction de bases de curses. Un objet fenêtre représente une zone rectangulaire de l'écran, et supporte un certain nombre de méthode pour y afficher du texte, l'effacer, permettre à l'utilisateur d'y entrer du texte, et ainsi de suite.
L'objet stdscr retourné par la fonction initscr() est lui même un objet fenêtre qui recouvre l'écran entier. Beaucoup de programme ne nécessiteront que cette seule fenêtre, mais vous pouvez vouloir diviser l'écran en plusieurs fenêtres plus petites pour redessiner ou effacer chacune d'entre elle séparément. La fonction newwin() créera une nouvelle fenetre d'une taille donnée, et retourne un objet fenêtre.
begin_x = 20 ; begin_y = 7
height = 5 ; width = 40
win = curses.newwin(height, width, begin_y, begin_x)
Un mot à propos des coordonnées utilisées dans curses : elles sont toujours passée dans l'ordre y, x et le coin en haut à gauche possède les coordonnées (0, 0). Ceci casse les conventions habituellement pour la manipulations des coordonnées, où x est normalement définit en premier. Cela est une malheureuse différence avec la plupart des autres applications informatiques, mais c'est comme cela depuis que curses à été écrit, et il est trop tard pour le changer maintenant.
Quand vous appelez une méthode pour afficher ou effacer du texte, son effet est pas immédiat sur l'affichage. Cela provient du fait que curses à été initialement écrit pour des terminaux possédant des connexions lentes, à 300 bauds par seconde; avec ces terminaux, minimiser le temps requis pour redessiner l'écran est très important. Cela oblige curses à accumuler les modifications de l'affichage et à les dessiner de la manière la plus efficace. Par exemple, si votre programme affiche quelques caractères dans une fenêtre pour ensuite effacer la fenêtre, il n'y a pas besoin d'envoyer les caractères à afficher au terminal, puisque ceci ne seront de toute manière jamais visibles.
En conséquence, curses necessite que vous lui indiquiez de redéssiner les fenêtres, en utilisant la méthode refresh() des objets fenêtre. En pratique cela ne complique pas vraiment la programmation avec curses. La plus part des programmes effectuent une suite d'opérations, et font une pause en attente de l'appuis d'une touche ou d'une autre action de l'utilisateur. Tout ce que vous avez à faire est d'être sûr que l'écran sera bien redessiné avant l'attente de l'entrée utilisateur, en appelant simplement la méthode refresh() de la fenêtre en question.
Un pad est un cas spécial de fenêtres; il peut être plus grand que l'affichage actuel de l'écran, et seulement une portion de celui-ci sera affiché. Créer un pad requière simplement d'une hauteur et une largeur, et le rafraichissement du pad requière les coordonnées de la partie de l'écran où afficher une partie du pad.
pad = curses.newpad(100, 100)
# Cette boucle remplit le pad de lettres
# Cela est expliqué dans la prochaine partie
for y in range(0, 100):
for x in range(0, 100):
try: pad.addch(y,x, ord('a') + (x*x+y*y) % 26 )
except curses.error: pass
# Affiche une portion du pad sur l'écran
pad.refresh( 0,0, 5,5, 20,75)
L'appel à refresh() affichera une partie du pad dans un rectangle depuis les coordonnées (5, 5) aux coordonnées (20, 75) sur l'écran; le coin en haut à gauche de la partie affichée à les coordonnées (0, 0) sur le pad. Mis à part cette différence, les pads sont exactement comme les fenêtres ordinaires et possèdent des mêmes méthodes.
Si vous avez plusieurs fenêtres et pads sur un écran, il y a une manière plus efficace de faire, qui va empêcher d'irritants scintillements lors des rafraichissements. Utilisez la méthode noutrefresh() sur chacune des fenêtres pour mettre à jour la structure interne représentant les données sur l'écran; et ensuite, pour changer l'affichage sur l'écran physique, appelez la fonction doupdate(). La méthode classique refresh() appelle en réalité la fonction doupdate() avant de se terminer.
Afficher du texte
Du point de vue d'un programmeur C, curses peut ressembler à un nuage de fonctions, toutes subtillement différentes. Par exemple, addstr() affiche une chaine sur la position courante du curseur dans la fenêtre stdscr alors que mvaddstr() déplace le curseur au coordonnées indiquées avant d'afficher le texte. waddstr() est identique à addstr(), mais permet de spécifier la fenêtre à utiliser, à la place d'utiliser stdscr par défaut. mvwaddstr() suis le même schéma.
Heureusement, l'interface Python cache tous ces détails; stdscr est un objet fenêtre comme tous les autres, et les méthodes comme addstr() acceptent plusieurs formes d'arguments. Il y a habituellement quatre formes différentes :
| Forme |
Description |
| str or ch |
Affiche la chaine str ou le caractère ch à la position courante |
| str or ch, attr |
Affiche la chaine str ou le caractère ch, en utilisant l'attribut attr à la position courante |
| y, x, str or ch |
Déplace le curseur à la position y, x et affiche la chaine str ou le caractère ch |
| y, x, str or ch, attr |
Déplace le curseur à la position y, x et affiche la chaine str ou le caractère ch, en utilisant l'attribut attr |
Des attributs permettent d'afficher du texte en évidence, comme en gras, en souligné, inversé ou en couleur. Ils seront expliqués plus en détails dans la prochaine partie.
La méthode addstr() prendre une chaine en entrée pour l'afficher, alors que la méthode addch() prend un seul caractère, qui est représenté en Python sous la forme d'une chaine de taille 1, ou d'un entier. Si c'est une chaine, vous êtes limités à l'affichage des caractères compris entre 0 et 255. La version SV-r4 de curses apporte des constantes pour l'extension des caractères; ces constantes sont des entier plus grands que 255. Par exemple, ACS_PLMINUS est le symbole ±, et ACS_ULCORNER est le coin en haut à gauche d'une boite (pratique pour dessiner des bordures).
Les fenêtres se souviennent où le curseur à été laissé après la dernière opération, donc, si vous ne précisez pas les coordonnées y, x, la chaine ou le caractère seront affichées là où la dernière opération aura laissé le curseur. Vous pouvez aussi modifier ce dernier avec la méthode move(y, x). A cause de certains terminaux qui affichent en permanence un curseur clignotant, vous allez probablement vouloir vous assurer qu'il ne sera pas placé à un endroit où il sera distrayant pour l'utilisateur; ça peut être déroutant d'avoir un curseur qui clignote à des position apparemment aléatoires.
Si votre application ne nécessite pas un curseur clignotant, vous pouvez appeler curs_set(0) pour le rendre invisible. Pour des raisons de compatibilité avec les vieilles versions de curses, il y a une fonction leaveok(bool) équivalente. Quand bool est à True, la bibliothèque curses va tenter de supprimer le curseur clignotant, et vous n'aurez plus à vous soucier de sa position.
Attributs et couleurs
Les caractères peuvent être affichés de différentes manière. Les lignes d'état dans les applications textes sont communément affichées en mode inversé; un lecteur de texte peut avoir besoin de mettre en évidence certains mots. Curses supportes cela en vous permettant de spécifier un attribut pour chaque cellule sur l'écran.
Un attribut est un entier, chaque bit représente un attribut différent. Vous pouvez essayer d'afficher du texte avec plusieurs attributs à la fois, mais curses ne garantie pas que toutes les combinaisons sont possibles, ou qu'elles sont toutes différentes visuellement. Cela dépend des capacités du terminal utilisé, alors le plus sûr est encore d'utiliser les attributs les plus communs, listés ici :
| Attribut |
Description |
| A_BLINK |
Texte clignotant |
| A_BOLD |
Texte plus brillant ou en gras |
| A_DIM |
Texte à moitier brillant |
| A_REVERSE |
Couleur inversé |
| A_STANDOUT |
Le meilleur mode de mise en évidence disponible |
| A_UNDERLINE |
Texte souligné |
Ainsi, pour afficher une ligne d'état en haut de l'écran, vous pouvez écrire :
stdscr.addstr(0, 0, "Current mode: Typing mode", curses.A_REVERSE)
stdscr.refresh()
La bibliothèque curses supporte aussi les couleurs sur les terminaux qui le permettent. Le plus commun terminal est probablement la console Linux, suivis des versions couleur d'xterm.
Pour utiliser les couleurs, vous devez appeler la fonction start_color() juste après avoir appelé initscr(), afin d'initialiser l'ensemble de couleurs par défaut (la fonction curses.wrapper.wrapper() fait cela automatiquement). Une fois que cela est fait, la fonction has_color() retourne True si le terminal utilisé peut afficher des couleurs.
La bibliothèque curses entretient un certain nombre de paires de couleurs, contenant une couleur de premier plan (le texte) et une couleur de fond. Vous pouvez obtenir la valeur de l'attribut correspondant à une paire de couleur avec la fonction color_pair(); ceux-ci peuvent être mélangés à d'autres attributs comme un A_REVERSE avec une opération binaire OU, mais encore une fois, toutes les combinaisons ne fonctionneront pas forcément avec tous les terminaux.
Un exemple, qui affiche une ligne de texte en utilisant la paire de couleurs 1 :
stdscr.addstr( "Pretty text", curses.color_pair(1) )
stdscr.refresh()
Comme je l'ai dit précédement, une paire de couleurs consiste en une couleur de premier plan et une couleur de fond. start_color() initialise 8 couleurs de base lorsqu'il active le mode coloré. Il y a des numéros de couleur 0 à 7 qui correspondent respectivement aux couleurs noir, rouge, verte, jaune, bleue, magenta, cyan, et blanc. Le module curses définit des constantes pour chacune de ces couleurs : curses.COLOR_BLACK, curses.COLOR_RED, et ainsi de suite.
La fonction init_pair(n, f, b) change la définition de la paire n, pour la couleur de premier plan f, et la couleur de fond b. La paire de couleur 0 est définie « en dur » sur le noir et blanc, et ne peut être changée.
Essayons cela en changeant la paire de couleur 1 pour un texte route au fond blanc :
curses.init_pair(1, curses.COLOR_RED, curses.COLOR_WHITE)
Quand vous changez une paire de couleur, le texte déjà affiché qui utilise cette paire va changer de couleur. Vous pouvez aussi afficher un nouveau texte utilisant cette couleur avec :
stdscr.addstr(0,0, "RED ALERT!", curses.color_pair(1) )
Les terminaux les plus jolis permettent de changer les couleurs avec une valeur RVB. Cela vous permet de changer la couleur 1, qui est habituellement rouge, en violet, bleu, ou n'importe quel couleur que vous aimez. Malheureusement, la console Linux ne supporte pas cela, donc je ne peux essayer cela et fournir aucun exemple. Vous pouvez vérifier la compatibilité de votre terminal en appelant can_change_color() qui retournera True si le terminal le permet. Si vous avez la chance d'avoir un terminal aussi talentueux, vous pouvez consulter vos pages de manuel pour plus d'informations.
Entrées utilisateur
La bibliothèque curses par elle même n'offre que de simples mécanismes d'entrées utilisateur. Le module Python permet l'utilisation d'un widget d'entrée de texte qui permet de combler ce vide.
La plus commune manière d'obtenir une entrée dans une fenêtre est d'utiliser sa méthode getch(). getch() met en pause et attend que l'utilise appuis sur une touche, en affichant celle-ci si echo() a été appelé plus tôt. Vous pouvez également spécifier une coordonnée où déplacer le curseur avant la pause.
Il est possible de changer ce comportement avec la méthode nodelay(). Après nodelay(1), getch() va devenir non-bloquant et retourner curses.ERR quand aucune entrée n'est faite. Il y a aussi une fonction halfdelay() qui peut être utilisée pour définir un timeout sur chaque getch(); si aucune entrée n'est faite après le temps impartis (mesuré en dixième de seconde), curses déclenche une exception.
La méthode getch() retourne un entier; si il est compris entre 0 et 255, il représente le code ASCII de la touche pressée. Les valeurs supérieurs à 255 sont des touches spéciales comme Page UP, Home ou les touches du curseur. Vous pouvez comparer la valeur retournée à des constantes comme curses.KEY_PPAGE, curses.KEY_HOME ou curses.KEY_LEFT. Habituellement, la boucle principale de votre programme va ressembler à ça :
while 1:
c = stdscr.getch()
if c == ord('p'): PrintDocument()
elif c == ord('q'): break # Sortir de la boucle
elif c == curses.KEY_HOME: x = y = 0
Le module curses.ascii apporte une classe ASCII possédant des méthodes statiques qui prennent en argument un entier ou une chaine d'un caractère; elles peuvent être pratiques pour écrire des tests plus lisibles pour vos interpréteurs de commandes. Elle apporte aussi une fonction de conversion qui prend à la fois en argument un entier ou une chaine d'un caractère et retourne le même type. Par exemple, curses.ascii.ctrl() retourne le caractère de contrôle correspondant à ses arguments.
Il y a aussi une méthode pour récupérer une chaine entière, getstr(). Elle est pas utilisée souvent car ses fonctionnalités sont un peu limités; les seuls touches d'éditions disponibles sont la touche d'effacement arrière et la touche entrée qui termine la chaine. Elle peut optionnellement être limité à un nombre fixe de caractères.
curses.echo() # Activation de l'écho des touches
# Obtenir une chaine de 15 caractères avec le curseur au début de la ligne
s = stdscr.getstr(0,0, 15)
Le module Python curses.textpad fournit quelque chose de plus puissant. Avec lui, vous pouvez transformer une fenêtre en boite de texte qui supporte des fonctions d'éditions à-la-Emacs. Plusieurs méthodes de la classe Textbox supportent l'édition avec validation de l'entrée, et collecte des résultats avec ou non espaces. Lisez la documentation de la bibliothèque curses.textpad pour plus dé détails.
Pour plus d'informations
Ce How To ne couvre pas certains sujets avancés comme le screen-scraping, ou la capture des évènements de la souris depuis une instance d'xterm. Mais la page de la documentation Python de la bibliothèque curses est maintenant utilisable. Vous pouvez l'utiliser pour aller plus loin.
Si vous avez des doutes sur le comportement des points d'entrés de ncurses, consultez les pages de manuel de votre implémentation de curses, que ce soit pour ncurses ou une version provenant d'un Unix propriétaire. La page de manuel documentera n'importe quel bizarerie et apportera une liste complète des toutes les fonctions, attributs, et caractères ACS_* qui vous sont disponible.
Du fait de la grosseur de l'API de curses, certaines fonctions ne sont pas supportées avec l'interface Python, pas parce qu'elles sont difficile à implémenter, mais parce que personne n'en à jamais eu besoin. Vous êtes libre de les ajouter et de soumettre un patch. Il y a aussi peu de bibliothèque de menus ou de panneaux pour curses, n'hésitez pas à en faire une.
Si vous écrivez un petit programme intéressant, contribuez en l'ajoutant en démos disponibles. Ca peut toujours en intéresser certains !
La FAQ ncurses (en anglais) : http://invisible-island.net/ncurses/ncurses.faq.html.
NDT : vous pouvez aussi jeter un oeil sur la documentation Python du module curses : http://www.python.org/doc/2.6/library/curses.html.