Questo documento descrive come usare il modulo curses per controllare interfacce in modalità testuale.
Che cosa è la libreria curses ?
I terminali a schermo supportano vari codici di controllo per svolgere operazioni usuali come muovere il cursore, scorrere lo schermo e cancellare i campi. Terminali differenti usano codici di controllo molto diversi fra loro, e spesso hanno il loro piccole stranezze.
In un mondo di display grafici, uno potrebbe domandare “Perchè disturbarsi ?” È vero che terminali con display a caratteri sono tecnologia obsoleta, ma ci sono nicchie nelle quali avere la capacità di fare delle belle cose con questi strumenti ha ancora un valore. Una di queste nicchie sono gli Unix embedded sui quali non gira un server X. Un'altra nicchia sono i tools per l'installazione dei sistemi operativi oppure i software di configurazione del kernel che devono girare prima che il sistema grafico sia disponibile.
La libreria curses ha funzionalità abbastanza spartane e fornisce al programmatore l'astrazione di un display che puo' contenere finestre testuali multiple e non sovrapponibili. Il contenuto di una finestra puo' essere cambiato in vari modi - aggiungendo testo, cancellandolo o cambiandone l'aspetto - e la libreria curses deciderà quali codici di controllo dovranno essere inviati al terminale per produrre il giusto output. La curses non fornisce molti concetti delle interfacce utenti come bottoni, checkboxes o finestre di dialogo. Se hai bisogno di queste caratteristiche considera l'uso della libreria Urwid.
La libreria curses è stata originariamente scritta per gli Unix BSD, successivamente le versioni System V di Unix dell'AT&T hanno aggiunto molte nuove funzioni e miglioramenti. La versione BSD delle curses non è più mantenuta ed è stata rimpiazzata dalle ncurses, che sono un'implementazione open-source della vecchia versione di AT&T. Se stai usando uno unix open-source come Linux o FreeBSD il tuo sistema ha quasi sicuramente le nCurses. Da quando la maggior parte degli UNIX commerciali correnti sono basati sul codice di SYSTEM V, tutte le funzioni descritte qui saranno disponibili. Le vecchie versioni delle curses che sono a bordo di UNIX proprietari possono comunque non supportare tutte le caratteristiche.
La versione WINDOWS di Python non include il modulo curses. C'è comunque un porting disponibile di curses chiamato UniCurses. Potresti anche provare “The Console Module” scritto da Fredrik Lundh, il quale non usa le stessi API delle curses ma fornisce un output di testo indirizzabile in base al cursore e un pieno supporto a mouse e tastiera per l'input.
Il modulo curses di Python
Le curses di Python è semplicemente un wrapper delle funzioni C fornite dalla curses; se hai già familiarità con le curses del C sarà abbastanza semplice trasferire quella conoscenza a Python. La maggior differenza è che Python rende le cose più semplici unendo differenti funzioni C come per esempio addstr(), mvaddstr() e mvwaddstr() nel semplice metodo addstr(). Vedremo questo in dettaglio più avanti.Questo HOWTO è un'introduzione alla scrittura di programmi con le curses in Python. Non è una guida completa alle API delle curses, per questo devi vedere la sezione della guida alle librerie di ncurses e le man page delle ncurses in C (qui un howto in Italiano). Ti darà comunque delle idee di base.
Aprire e chiudere un'applicazione con le curses
Prima di fare qualsisasi cosa, le curses devono essere inizializzate. Questo viene fatto chiamando la funzione initscr() la quale determinerà il tipo di terminale, invierà il codice di setup richiesto al terminale e creerà varie strutture di dati interne. Se l'inizializzazione con initscr() viene completata con successo ritorna un oggetto finestra che rappresenta l'intero schermo, questo è solitamente chiamato stdscr dopo il nome della corrispondente variabile C.import curses
curses.initscr()
curses.noecho()
curses.cbreak()
stdscr.keypad(True)
curses.nocbreak()
stdscr.keypad(False)
curses.echo()
curses.endwin()
In Python puoi evitare questo tipo di problemi e fare debugging più semplicemente importando la funzione curses.wrapper(), usandola così:
from curses import wrapper
def main(stdscr):
# Pulisce lo schermo
stdscr.clear()
# Questo solleva una ZeroDivisionError quando i == 10.
for i in range(0, 11):
v = i-10
stdscr.addstr(i, 0, '10 divided by {} is {}'.format(v, 10/v))
stdscr.refresh()
stdscr.getkey()
wrapper(main)
Finestre e Pad
begin_x = 20; begin_y = 7
height = 5; width = 40
win = curses.newwin(height, width, begin_y, begin_x)
La tua applicazione può determinare la misura dello schermo mediante l'uso delle variabili curses.LINES e curses.COLS in modo da avere la misura degli assi Y e X. Quindi le coordinate si estenderanno da (0,0) a (curses.LINE - 1, curses.COLS - 1).
Quando usi un metodo per mostrare o cancellare testo, esso non verrà mostrato immediatamente sullo schermo. Infatti dovrai chiamare il metodo refresh() per aggiornare l'oggetto finestra sullo schermo.
Questo perché le curses originariamente sono state scritte per terminali che erano connessi a 300-baud; con questi terminali era molto importante minimizzare il tempo richiesto per rinfrescare lo schermo. Infatti le curses accumulano i cambiamenti dello schermo ma li mostrano nel modo più efficente possibile quando viene chiamata la refresh(). Per esempio il tuo software può mostrare del testo sullo schermo e successivamente cancellare la finestra, ma non c'è nessun bisogno di inviare il testo originale visto che non verrà mai mostrato.
In pratica, dicendo esplicitamente alle curses di ridisegnare la finestra non rende molto più complicata la programmazione con le curses. Molti programmi entrano in un vortice di attività e quindi in pausa in attesa di qualche genere di azione da parte dell'utente o della pressione di un tasto. Tutto ciò che devi fare è essere sicuro è stato rinfrescato prima della pausa per aspettare l'input dell'utente prima chiamando il metodo stdscr.refresh() o refresh() sulla finestra rilevante.
Un pad è un caso speciale di finestra il quale può essere più largo della dimensione dello schermo mostrato quindi solo una parte del pad puo' essere mostrato per volta. Creare un pad richiede l'altezza e la larghezza del pad stesso, mentre rinfrescare sullo schermo un pad richiede dare le coordinate dell'area sullo schermo dove la sottosezione del pad verrà mostrata.
pad = curses.newpad(100, 100)
# Questi cicli riempiono il pad di lettere;
# addch() è spiegata nella prossima sezione
for y in range(0, 99):
for x in range(0, 99):
pad.addch(y,x, ord('a') + (x*x+y*y) % 26)
# Mostra una sezione di un pad in mezzo allo schermo
# (0,0) : coordinate dell'angolo in alto a sinistra dell'area del pad da mostrare.
# (5,5) : coordinate dell'angolo in alto a sinistra dell'area della
# finestra che deve essere riempita col contenuto del pad.
# (20, 75) : coordinate of dell'angolo in basso a destra dell'area della finestra
# : che deve essere riempita col contenuto di un pad.
pad.refresh( 0,0, 5,5, 20,75)
sullo schermo si estende dalle coordinate (5,5) alle coordinate (20,75); l'angolo in alto a sinistra della sezione mostrata ha le coordinate (0,0) del pad. Aldilà di questa differenza, i pads sono esattamente come normali finestre e supportano gli estessi metodi.
Se hai pads e finestre multiple c'è un modo più efficiente di aggiornare lo schermo così da evitare lo sfarffallio ed ogni sua parte è aggiornata correttamente. refresh attualmente fa due cose:
1) Chiama il metodo noutrefresh di ogni finestra per aggiornare la sottostante struttura dati che rappresenta lo stato dello schermo desiderato.
2) Chiama la funzione doupdate in modo che lo schermo fisico possa cambiare per adattarsi allo stato desiderata contenuto nella struttura dati.
Piuttosto puoi chiamare noutrefresh su un numero di finestre per aggiornare la struttura dati, e quindi chiamare doupdate per aggiornare lo schermo.
Stampare del testo sullo schermo
Forma | Descrizione |
---|---|
str or ch | Stampa la stringa str o il carattere ch alla posizione corrente |
str o ch, attr | Stampa la stringa str o carattere ch, usando l'attributo attr alla posizione corrente |
y, x, str or ch | Si sposta alla posizione y,x all'interno della finestra, e stampa str o ch |
y, x, str or ch, attr |
Si muove alla posizione y,x all'interno della finestra, e stamp str o ch, usando l'attributo attr |
Le costansti sono fornite per estensione dei caratteri, queste costanti sono interi più grandi di 255. Per esempio, :const:ACS_PLMINUS è un simbolo +/-, e ACS_ULCORNER è l'angolo in alto a sinistra di un quadrato (pratico per disegnare bordi). Ovviamente puoi anche usare i caratteri unicode appropriati.
Le finestre ricordano dove il cursore è rimasto dall'ultima operazione, così se non indichi le coordinate y,x, la stringa o il carattere saranno stampati ovunque sia terminata l'ultima operazione. Puoi anche spostare il cursore con il metodo move(y,x). Dato che alcuni terminali mostrano sempre il cursore lampeggiante, potresti volerlo spostare in una poszione che non distrae l'utente; potrebbe essere elemento di confusione vedere il cursore lampeggiante in una poszione casuale sullo schermo.
Se il tuo software non ha per nulla bisogno del cursore che lampeggia puoi chiamare curs_set(False) per renderlo invisibile. Per compatibilità con altre versioni di curses c'è la funzione leaveok(bool) che è un alias di curs_set(). Se bool è true le curses proveranno a disabilitare il lampeggìo del cursore e non ti dovrai preoccupare di lasciarlo in posizioni strane.
Attributi e colori
I caratteri si possono stampate in modi diversi. Le linee di status in un'applicazione testuale sono solitamente mostrate in "reverse video", o un visualizzatore di testo può aver bisogno di evidenziare certe parole. Le curses supportano tutto questo permettendoti di specificare un attributo per ogni cella dello schermo.
Un attributo è un intero e ogni bit rappresenta un differente attirbuto. Puoi provare a stampare testo impostando multipli bit attributi, ma le curses non ti garantiscono che tutte le possibili combinazioni siano disponibili, o che esse siano differenti da un punto di vista visivo. Ciò dipende dall'abilità nell'usare il terminale, quindi è meglio definire con chiarezza gli attributi più comunemente disponibili, ecco la lista.
Attributo | Descrizione |
---|---|
A_BLINK | Testo lampeggiante |
A_BOLD | Testo in grassetto |
A_DIM | Testo mezzo in grassetto |
A_REVERSE | Testo in modalità reverse-video |
A_STANDOUT | La modalità migliore disponibile |
A_UNDERLINE | Testo sottolineato |
stdscr.addstr(0, 0, "Modalità corrente: modo scrittura",curses.A_REVERSE)
stdscr.refresh()
Per usare i colori bisogna chiamare la funziona start_color() e subito dopo chiamare initscr(), per inizializzare il set di colori di default (la funzione wrapper() lo fa automaticamente). Una volta fatto la funzione has_colors() ritornerà TRUE se il terminale in uso è in grado di stampare i colori. (Nota: le curses usano la parola americana 'color' anzichè quella Britannica Canadese 'colour'. Se sei abituato ad usare la parola Inglese Britannica dovrai rassegnarti a fare errori per questo insieme di funzioni.)
La libreria curses mantiene un numero finito di coppie di colori, contenenti un colore per il testo (foreground color) e uno per lo sfondo del testo (background color). Per avere il valore dell'attributo corrispondente alla coppia di colori devi chiamare la funzione color_pair(); ciò puo' essere a livello di bit con altri attributi come per esempio A_REVERSE, ma di nuovo, queste combinazioni non sono garantite come funzionanti su tutti i terminali.
Questo esempio mostra un linea di testo che usa una coppia di colori 1::
stdscr.addstr("Bel testo", curses.color_pair(1))
stdscr.refresh()
I colori sono numerati e la funzione start_color() inizializza gli 8 colori di base quando viene attivata la modalità colore. I colori sono: 0:nero, 1:rosso, 2:verde, 3:giallo, 4:blu, 5:magenta, 6:azzurro e 7:bianco. Il modulo curses definisce delle costanti per ognuno di questi colori: COLOR_BLACK (nero ndt), COLOR_RED (rosso ndt) e così via.
Adesso mettiamo tutto insieme. Per cambiare dal colore 1 a colore rosso per il testo e bianco per lo sfondo il codice è:
curses.init_pair(1, curses.COLOR_RED, curses.COLOR_WHITE)
stdscr.addstr(0,0, "RED ALERT!", curses.color_pair(1))
Input da utente
Le librerie curses del C offrono un meccanismo di input molto semplice. Il modulo curses di Python aggiunge un widget per l'input testuale di base. (Altre librerie come per esempio Urwid hanno una collezione di widget più estesa).
Ci sono due metodi per ottenere input da una finestra:
- window.getch() aggiorna lo schermo e quindi aspetta che l'utente prema un tasto. Se precedentemente è stata chiama la funzione echo() allora stampa a schermo il tasto premuto. In alternativa puoi anche spostare il cursore verso una coordinata specifica prima della pausa.
- window.getkey() fa la stessa cosa ma converte l'intero in stringa. Singoli caratteri vengono restituiti come stringhe da 1 solo carattere, e tasti speciali come i tasti funzione restituiscono stringhe più lunghe contenenti nomi dei tasti come KEY_UP o ^G.
É possibile evitare di aspettare l'input dell'utente usando il metodo window nodelay(). Dopo nodelay(True), getch() e getkey() per rendere la finestra non bloccante (non-blocking). Al segnale di nessun input si rende disponibile, il metodo getch restituisce curses.ERR (un valore di -1) e il metodo getkey emette un'eccezzione. C'è anche la funzione halfdelay, che infatti puo' essere usata per impostare un timer per ogni metodo getch; se nessun input diventa disponibile all'interno di uno specifico ritardo (misurato in decimi di secondo), le curses emetteno un'eccezzione.
Il metodo getch restituisce un intero; se è tra 0 e 255, rappresenta il codice asci di un tasto premuto. Valori più grandi di 255 sono tasti speciali come Page Up, Home, o tasti per lo spostamento del cursore. Puoi confrontare i valori di ritorno a costanti come curses.KEY_PPAGE, curses.KEY_HOME, oppure curses.KEY_LEFT. Il ciclio principale del tuo programma sarà qualcosa come questo:
while True:
c = stdscr.getch()
if c == ord('p'):
PrintDocument()
elif c == ord('q'):
break # ESce dal ciclo while
elif c == curses.KEY_HOME:
x = y = 0
C'è anche un metodo per ottenere un'intera stringa, curses.window.getstr(). Non è molto usato perché la sua funzionalità è abbastanza limitata; gli unici tasti editabili disponibili sono il backspace e l'invio, che sono alla fine della stringa. In alternativa può essere limitato ad un numero fisso di caratteri.
curses.echo() # Abilita il echo dei caratteri
# ottiene una stringa di 15 caratteri col cursore sulla linea in cima
s = stdscr.getstr(0,0, 15)
import curses
from curses.textpad import Textbox, rectangle
def main(stdscr):
stdscr.addstr(0, 0, "Inserisci un messaggio IM: (poi Ctrl-G per inviare)")
editwin = curses.newwin(5,30, 2,1)
rectangle(stdscr, 1,0, 1+5+1, 1+30+1)
stdscr.refresh()
box = Textbox(editwin)
# permette all'utente di inserire i dati fino a quando non preme Ctrl-G
box.edit()
# ottiene il contenuto risultante
message = box.gather()
Per maggiori dettagli leggi la documentazione ufficiale curses.textpad
Per maggiori informazioni
Se hai qualche dubbio sui dettagli del comportamento delle funzioni del modulo curses consulta la pagina del manuale dell'implementazione curses che stai usando. chesia la ncurses o di qualche unix proprietario. La pagina del manuale copre tutte le stranezze che possano venirti in mente, oltre che fornirti una completa lista di tutte le funzioni, attributi, e i caratteri ACS_\* disponibili.
Dato che le API delle librerie curses sono troppo ampie alcune funzioni non sono supportate in ambiente Python. Spesso non perché sia difficile l'implementazione ma piuttosto perchè nessuno ne ha mai avuto bisogno fino ad oggi. Inoltre Python ancora non supporta il menu libreria associato con le ncurses.
Patch che aggiungono questo tipo di supporto sono molto apprezzate; vedi the Python Developer's Guide per saperne di più so come inviare patch a Python.
- Scrivere programmi con le NCURRSES (in Inglese): un tutorial un po' prolisso per programmatori in C.
- La pagine di manuale nCurses in Inglese
- Le FAQ in inglese
- Usa le curses... senza sbattersi: video in Inglese del PyCon 2013 sul controllo dei terminali usando le curses oppure Urwid.
- Console Applications with Urwid: Video in Inglese di un PyCon CA 2012 che mostra alcune applicazioni scritte usando Urwid.