Programación con nCurses en C
Introducción
A librería ncurses proporciona un conxunto de funcións pra manexar a entrada e saída por terminal no mundo UNIX. A diferencia de outros sistemas operativos (WINDOWS,DOS) Linux é un sistema multiusuario no que moitos usuarios poden traballar o mesmo tempo co mesmo ordenador dende terminais que poden estar situados en calquera sitio. Como os terminais non son todos iguais, nin dende o punto de vista físico nin lóxico, non é posible ter unhas chamadas ó sistema xerais que funcionen pra todos os modelos. O Linux, igual que a maior parte (ou todos) os UNIX, utiliza unha base de datos que recolle as capacidades dos diferentes tipos de terminais soportados, e os programas deben poder traballar con todos eles. A librería curses é unha librería de funcións de manexo de terminais de usuario que fai uso desta base de datos pra poder facer tarefas comúns como o borrado do terminal ou a colocación do cursor nunha zona concreta da pantalla, ademáis de outras tarefas máis complexas como creación de ventanas, manexo das cores... etc. A implementación da librería curses presente en (que eu saiba) todos os UNIX chámase na versión de Linux ncurses e a información máis importante pode obterse con:
- man ncurses : Uso xeral da librería, como iniciar a librería e pechala e como enlazar a librería os nosos programas
- man curs_color : Descripción das funcións de manexo de cores
- man curs_attr : Descripción das funcións de manexo dos atributos
- man curs_getch: Descripción das funcións de entrada de caracteres (getch)
- man curs_getstr: Descripción das funcións de entrada de cadeas (getstr)
- man curs_mouse: Descripción das funcións de manexo de rato
- man curs_move: Descripción das funcións de posicionamento do cursor
- man curs_initscr: Descripción das funcións de inicialización da librería
- man curs_window: Descripción das funcións de manexo de ventanas
- man curs_inopts: Descripción das funcións de control de entrada e saída
- man curs_terminfo: Descripción das funcións pra obter información do terminal
- man curs_printw: Descripción das funcións pra visualizar (printf)
- man curs_refresh: Descripción das funcións de volcado de datos no terminal (flushing).
- man curs_scanw: Descripción das funcións pra ler datos do teclado (scanf)
- man curs_clear: Descripción das funcións pra borra-lo terminal (clear)
- man curs_panel: Descripción das funcións de manexo dos paneis das ventanas.
Case todas as funcións utilizan ventanas pra traballar. Unha ventana é unha zona da pantalla delimitada na que poderemos realizar tanto entrada como saída de información. Para poder referirnos ó terminal completo, a librería curses dispón dunha ventana que sempre está creada chamada stdscr (estandar screen).
A terminoloxía empregada para o nome das funcións é moi simple; cando a función traballa cunha ventana o seu nome comenza coa letra w, mentras que si traballa coa ventana principal stdscr (o terminal completo) non. Ademáis, as funcións que moven de posición o cursor antes de facer o seu traballo comenzan con mv, de xeito que unha función que mova o cursor e que traballe cunha ventana comenzaría con mvw.
Pra utilizar as funcións da librería ncurses é necesario incluír o ficheiro curses.h e enlazar o programa coa librería ncurses mediante a opción -l do gcc do seguinte xeito
gcc programa.c -o programa executable -lncurses
xa que as curses non forman parte da librería C estándar. A librería curses define unha serie de constantes de moita utilidade a hora de programar aplicacións:
- LINES : Número de liñas que ten o terminal.
- COLS : Número de columnas do terminal.
- TRUE : Constante que indica 'certo' (1)
- FALSE : Constante pra indicar 'falso' (0)
- ERR : Constante que devolven as funcións de curses cando se produce un erro.
- OK : Constante que devolven as funcións de curses cando traballan ben.
- bool : Tipo de datos pra definir variables que poden tomar os valores TRUE ou FALSE indicados anteriormente.
Inicialización e Peche da Librería.
Pra utilizar a librería curses nun programa é necesario chamar en primeiro lugar a unha función encargada da inicialización da librería chamada initscr() (man curs_initscr), esta función encárgase de inicializar as estructuras que necesita a librería e de obter a información do terminal. Antes de rematar, o programa debe pechar as curses pra que o terminal recupere o seu modo de funcionamento normal mediante a función endwin() (man curs_initscr). Hai moitas outras funcións de inicialización comúns que afectan sobre todo o xeito de coller a entrada de usuario:
- cbreak() ou nocbreak() (man curs_inopts): cbreak() indica que cando lemos unha tecla do terminal (getch), o código da mesma pase a estar dispoñible sin esperar a que o usuario pulse o "Enter". Con nocbreak(), o carácter non estará dispoñible hasta que o usuario pulse "Enter".
- echo() ou noecho() (man curs_inopts): echo() indica que cando lemos unha tecla do terminal (getch) a pulsación sairá na pantalla. Con noecho() se indica que cando o usuario pulse a tecla (getch) a pulsación non se visualizará.
- keypad(WINDOW *win,bool bf) (man curs_inopts): Activa ou desactiva o teclado numérico do terminal na ventana indicada. É imprescindible chamar a esta función pra que traballen algunhas teclas especiais como as flechas ou as teclas de función.
- start_color() (man curs_color): Si queremos utilizar cores no programa, é necesario iniciar a librería coa función start_color().
Xa que a inicialización das curses pode requerir varias tarefas e convinte (pra clarificar o programa e para manexo máis sinxelo do código) definir unha función donde se realicen todas as tarefas de inicialización. Tamén é habitual facer unha función de finalización. Un exemplo podería ser:
//Inicialización da librería curses
//
void inicia_terminal(void) {
// Inicialización da librería
initscr();
// Non se espera polo Enter nos getch()
cbreak();
// Non se visualiza a tecla pulsada ó facer o getch()
noecho();
// Se habilitan as teclas especiais
keypad(stdscr,TRUE);
// Se inicializan as estructuras de manexo de cores
start_color();
}
// Finalización (peche) das curses.
//
void pecha_terminal(void) {
endwin();
}
Outras funcións de uso común na inicialización das curses poden verse nas páxinas do manual (man curs_inopts, man curs_outopts).
Visualización e Posicionamento do Cursor
A visualización nos terminais faise a través dun buffer pra axilizar a saída por pantalla. Unha vez que temos 'pintada' a pantalla debemos forzar a súa saída o terminal chamando a unha das funcións de 'refresco' das curses:
int refresh(void);
int wrefresh(WINDOW *win);
int wnoutrefresh(WINDOW *win);
int doupdate(void);
int redrawwin(WINDOW *win);
int wredrawwin(WINDOW *win, int begline, int numlines);
É necesario chamar a unha destas funcións pra ober a saída no terminal. A función wrefresh chama en primeiro lugar a wnoutrefresh que copia a ventana na pantalla virtual, e logo a doupdate que se encarga de comprobar as diferencias co contido actual da pantalla e a actualiza. Si se queren visualizar varias ventanas o mesmo tempo é máis efectivo chamar a wrefresh pra cada ventana e facer unha soa chamada a doupdate que realizar un wrefresh pra cada unha. As funcións redrawwin e wredrawwin indican que algunhas liñas da pantalla deberán ser eliminadas na seguinte actualización.
Pra borrar a pantalla curses proporciona as seguintes funcións:
int clear(void);
int wclear(WINDOW *win);
int erase(void);
int werase(WINDOW *win);
int clearok(WINDOW *win, bool bf);
int clrtobot(void);
int wclrtobot(WINDOW *win);
int clrtoeol(void);
int wclrtoeol(WINDOW *win);
As funcións que comenzan por _w_, como sempre, traballan cunha ventana mentras que as outras o fan coa ventana principal stdscr (a pantalla). clear e erase borran a ventana, pero mentras erase o fai poñendo blancos no interior, clear chama despois a función clearok con bf a TRUE o que forza o repintado de toda a pantalla dende 0. clrtobot borra o contido da pantalla dende o cursor ó final da pantalla e clrtoeol dende o cursor ata final de liña.
As funcións de manexo do cursor son:
int move(int y, int x);
int wmove(WINDOW *win, int y, int x);
Que poñen o cursor na posición y (fila) e x (columna). wmove traballa cunha ventana que ten que estar creada, mentras que move traballa con stdscr.
Pra visualizar información dispoñemos das seguintes funcións:
int addch(chtype ch);
int waddch(WINDOW *win, chtype ch);
int mvaddch(int y, int x, chtype ch);
int wmvaddch(WINDOW *win, int y, int x, chtype ch);
int echochar(chtype ch);
int wechochar(WINDOW *win, chtype ch);
Estas funcións visualizan unha letra na pantalla ou nunha ventana (w...), as que empezan por mv colocan tamén o cursor. Unha vez visualizada a letra o cursor avanza á posición seguinte. Si non hi máis posicións e scrollok está activo, a pantalla/ventana fará un scroll do seu contido hacia arriba. echochar e wechochar fan o mesmo pero actualizan a pantalla chamando á función (w)refresh. A librería curses ten predefinidos unha serie de caracteres especiais pra facer debuxos (caixas... etc). Eses caracteres poden consultarse na páxina do manual (man curs_addch) e son constantes que comenzan por ACS_. Si o terminal non soporta eses dibuxos, os sustituirá pola letra ASCII máis axeitada.
Pra visualizar información máis complexa, curses dispón de unha serie de funcións análogas á función printf:
int printw(char *formato [, arg] ...);
int wprintw(WINDOW *win, char *formato [, arg] ...);
int mvprintw(int y, int x, char *formato [, arg] ...);
int mvwprintw(WINDOW *win, int y, int x, char *formato [, arg] ...);
Entrada de Datos.
A función complementaria á familia de addch() é a familia wgetch(), que lee da entrada estándar o valor dunha tecla:
int getch(void);
int wgetch(WINDOW *win);
int mvgetch(int y, int x);
int mvwgetch(WINDOW *win,int y,int x);
int ungetch(int ch);
int has_key(int ch);
O comportamento das funcións getch varía según si se chamou a cbreak; (a función non espera pola pulsación do Enter) ou a nocbreak (é necesario pulsar Enter despois da tecla) e a noecho (a letra non sae na pantalla) ou a echo (a letra sae na pantalla). Mediante a función int nodelay(WINDOW *w,bool bf) pódese controlar si getch espera pola pulsación da tecla (bf=TRUE) ou non (bf=FALSE). Ademáis si se chama á función keypad con TRUE, se habilita a lectura das teclas especiais (como as teclas de función), devolvendo éstas unha das constantes indicadas na páxina do manual (man curs_getch) que empezan por KEY_. A función ungetch devolve o caracter indicado ó buffer do teclado, e has_key nos indicará si o terminal soporta a tecla especial que lle pasamos como parámetro.
Na librería curses están dispoñibles tamén versións especiais das funcións de entrada estándar da librería C:
a) man curs_getstr
int getstr(char *str);
int getnstr(char *str, int n);
int mvgetstr(int y, int x, char *str);
int mvgetnstr(int y, int x, char *str, int n);
int wgetstr(WINDOW *win, char *str);
int wgetnstr(WINDOW *win, char *str, int n);
int mvwgetstr(WINDOW *win, int y, int x, char *str);
int mvwgetnstr(WINDOW *win, int y, iny x, char *str, int n);
b)man curs_scanw
int scanw(char *fmt [, arg] ...);
int mvscanw(int y, int x, char *fmt [, arg] ...);
int wscanw(WINDOW *win, char *fmt [, arg] ...);
int mvwscanw(WINDOW *win, int y, int x, char *fmt [, arg] ...);
Creación e Manexo de Ventanas
Unha ventana é unha área da pantalla na que se pode operar como si se tratara dunha pantalla independiente. Todas as funcións das curses que acceden á pantalla teñen versións (que teñen w_ ou mvw__ ó principio) que incorporan como parámetro a ventana á que se refiren. A librería curses proporciona moitas funcións pra manexar ventanas:
WINDOW *newwin(int nlines, int ncols, int begin_y, int begin_x);
int delwin(WINDOW *win);
int mvwin(WINDOW *win, int y, int x);
WINDOW *subwin(WINDOW *orig, int nlines, int ncols, int begin_y, int begin_x);
WINDOW *derwin(WINDOW *orig, int nlines, int ncols, int begin_y, int begin_x);
int mvderwin(WINDOW *win, int par_y, int par_x);
WINDOW *dupwin(WINDOW *win);
int wresize(WINDOW *win, int lines, int cols);
void wsyncup(WINDOW *win);
void syncok(WINDOW *win, bool bf);
void wcursyncup(WINDOW *win);
- A función newwin crea unha nova estructura de tipo WINDOW que representará unha área de traballo na pantalla que comenza nas coordenadas begin_y,begin_x (fila,columna) sendo a primeira posición a 0,0 e que mide nlines liñas e ncols columnas. Si nlines e 0, as liñas serán as que faltan dende begin_y ata final da pantalla, e si ncols é 0 as columnas son as que van dende begin_x ata final da pantalla, deste xeito newwin(0,0,0,0) creará unha ventana que ocupa todo o terminal.
- delwin elimina a ventana indicada liberando todos os recursos que tiña ocupados. Si a ventana ten subventanas, é necesario eliminalas antes. Non se borrará a ventana da pantalla, simplemente se elimina da memoria.
- mvwin coloca a ventana indicada na posición y,x que se especifique. Non se permite mover unha ventana máis alá dos límites do terminal.
- subwin crea unha ventana filla de outra. As coordenadas que se indican na creación son relativas á pantalla completa. Antes de refrescar unha ventana con subventanas con wrefresh é necesario chamar á función int touchwin(WINDOW *win); pra esa ventana.
- derwin e similar a subwin pero as coordenadas indicadas na creación son relativas á ventana pai.
- mvderwin move unha ventana filla xa sexa creada con subwin como con derwin.
- dupwin crea un duplicado exacto da ventana indicada.
- wresize cambia o tamaño dunha ventana ó tamaño indicado.
- wsyncup marca pra actualizar todas as zonas que cambiaron nos fillos da ventana indicada. Si se chama a syncok con bf a TRUE, esta función chamarase automáticamente cando se produzca algún cambio.
- wcursyncup actualiza a posición do cursor en todos os pais da ventana.
As funcións de subventanas (subwin, derwin, mvderwin, syncok, wsyncup, wcursyncup) son incompletas, están pouco probadas e poden ter bugs
O principal inconvinte das ventanas das curses é que non permiten xestionar de xeito eficiente as ventanas solapadas. Pra solucionar este tema xurdiu a librería panel. A librería panel proporciona unha serie de funcións que permiten o solapamento de ventanas que normalmente ven acompañando a curses. Si a queremos utilizar debemos incluir o ficheiro panel.h e indicarlle ó compilador que a enlace mediante o parámetro -lpanel :
gcc programa.c -o executable -lncurses -lpanel
Os paneles están asociados con ventanas creadas con newwin, e perminten o manexo de ventanas superpostas.As principais funcións da librería panel son as seguintes:
PANEL *new_panel(WINDOW *win);
void update_panels(void);
int del_panel(PANEL *pan);
int hide_panel(PANEL *pan);
int show_panel(PANEL *pan);
int top_panel(PANEL *pan);
int bottom_panel(PANEL *pan);
int move_panel(PANEL *pan, int starty, int startx);
int replace_panel(PANEL *pan,WINDOW *win);
PANEL *panel_above(const PANEL *pan);
PANEL *panel_below(const PANEL *pan);
int set_panel_userptr(PANEL *pan, const void *ptr);
const void *panel_userptr(const PANEL *pan);
WINDOW *panel_window(const PANEL *pan);
- new_panel crea unha estructura PANEL asociándoa con unha ventana e colocándoa enriba de todas as creadas con anterioridade. Devolve NULL en caso de erro e se non a dirección do PANEL creado.
- update_panels actualiza a pantalla virtual pra reflexar os cambios, pero non os pon na pantalla real. Pra visualizar os cambios realmente, será necesario chamara doupdate. É posible (e aconsellable) chamar a update_panels as veces que faga falta antes de chamar a doupdate.
- del_panel elimina o panel e todos os recursos que ocupe. A ventana a que está asociado o panel non se elimina.
- hide_panel oculta o panel da vista.
- show_panel fai que un panel que non está á vista se coloque visible e por enriba do resto.
- top_panel pon o panel por enriba do resto.
- bottom_panel pon o panel debaixo dos demáis.
- move_panel move o panel á posición indicada. Non cambia a súa posición na pila de paneis (continúa a ter os mesmos enriba e debaixo). Esta é a función apropiada pra mover unha ventana.
- replace_panel cambia a ventana asociada ó panel pola ventana indicada. Non modifica a súa posición na pila de paneis.
- panel_above devolve o panel que se atopa xusto enriba do indicado. Si lle pasamos como argumento NULL devolverá o primeiro panel (o de abaixo de todo).
- panel_below devolve o panel que se atoba xusto debaixo do indicado. Si lle pasamos como argumento NULL devolverá o panel de enriba de todo.
- set_panel_userptr pon un valor no punteiro do usuario do panel. Este punteiro de usuario é útil pra poder asociar datos co panel.
- panel_userptr devolve o punteiro de usuario asociado ó panel.
- panel_window devolve un punteiro á ventana asociada co panel.
Debuxo de Liñas e Bordes.
As curses proporcionan unha multitude de funcións pra debuxar liñas e bordes pras ventanas utilizando os caracteres que se desexe. As funcións principais son:
int border(chtype ls, chtype rs, chtype ts, chtype bs, chtype tl, chtype tr, chtype bl, chtype br);
int wborder(WINDOW *win, chtype ls, chtype rs, chtype ts, chtype bs, chtype tl, chtype tr, chtype bl, chtype br);
int box(WINDOW *win, chtype verch, chtype horch);
int hline(chtype ch, int n);
int whline(WINDOW *win, chtype ch, int n);
int mvhline(int y, int x, chtype ch, int n);
int mvwhline(WINDOW *win, int y, int x, chtype ch, int n);
int vline(chtype ch, int n);
int wvline(WINDOW *win, chtype ch, int n);
int mvvline(int y, int x, chtype ch, int n);
int mvwvline(WINDOW *win, int y, int x, chtype ch, int n);
- border e wborder debuxan un marco á ventana indicada (border traballa con stdscr). Os parámetros indican os caracteres que se utilizarán pra face-lo debuxo: ls esquerda, rs dereita, ts arriba, bs abaixo, tl esquina superior esquerda, tr esquina superior dereita, bl esquina inferior esquerda e br esquina inferior dereita. Si algún valor é 0 curses utiliza uns caracteres por defecto.
- box en realidade é unha chamada a wborder(win,verch,verch,horch,horch,0,0,0,0).
- hline, whline, mvhline e mvwhline debuxan unha liña horizontal de n caracteres ch de lonxitude comenzando na posición do cursor e na ventana actual ou especificada. A posición do cursor non cambia.
- vline, wvline, mvvline e mvwvline debuxan unha liña vertical de n caracteres ch de lonxitude comenzando na posición do cursor e na ventana actual ou especificada. A posición do cursor non cambia.