Termios.h

A termios.h é uma biblioteca especificada pelo padrão POSIX que permite que nós alteremos as opções do terminal em que o programa está sendo executado. O exemplo mais típico é quando vamos fazer o login em um console virtual e quando digitamos a senha não aparece nada; isso pode ser feito tirando a “opção” ECHO do terminal que estamos usando.

Exemplos

Detectando eventos no terminal

Digamos que um software esteja sendo desenvolvido (para o terminal) e que em um dado momento, o usuário deve escolher uma opção, alguma coisa parecida com isto:

Houve um erro na hora de executar asdf:
[R]epetir    [C]ontinuar    [A]bortar

Usando getchar ou scanf ou qualquer outra função da biblioteca stdio.h, a leitura será feita linha por linha. Por isso, mesmo que nós queiramos apenas um caractere, o usuário terá que apertar esse caractere e depois Enter. Eu queria uma forma de fazer isso em que o usuário simplesmente digitasse ‘r’ ou ‘c’, enfim, apenas uma tecla. Pensei em usar ncurses até, mas achei essa solução exagerada, e além disso, como o ncurses faz isso?

A solução apareceu com o uso de termios.h e de uma função de leitura também especificada pelos padrões POSIX, mas na biblioteca unistd.h, a função read().

Código

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
#include <termios.h>
 
void silent(struct termios *init_setting, struct termios *new_setting);
void verbose(struct termios *init_setting);
 
unsigned char get_char(void);
 
int main(int argc, char *argv[])
{
    struct termios init_setting, new_setting;
    unsigned char ch = 0;
 
    printf("Houve um erro na execução de foo.\n[C]ontinuar\t[R]epetir\t[A]bortar\n");
    silent(&init_setting, &new_setting);
 
    while(ch != 'a' && ch != 'A')
    {
        ch = get_char();
 
        switch(ch)
        {
            case 'r':
            case 'R':
                verbose(&init_setting);
                printf("Repetindo...\n");
 
                printf("Houve um erro na execução de foo.\n[C]ontinuar\t[R]epetir\t[A]bortar\n");
                silent(&init_setting, &new_setting);
                break;
 
            case 'c':
            case 'C':
                verbose(&init_setting);
                printf("Continuando...\n");
 
                sleep(2);
                printf("Houve um erro na execução de foo.\n[C]ontinuar\t[R]epetir\t[A]bortar\n");
                silent(&init_setting, &new_setting);
                break;
        }
    }
 
    verbose(&init_setting);
    return(0);
}
 
void silent(struct termios *init_setting, struct termios *new_setting)
{
    tcgetattr(fileno(stdin), init_setting);
    new_setting = init_setting;
 
    new_setting->c_lflag &= ~(ICANON | ECHO | ISIG);
 
    new_setting->c_cc[VMIN] = 1;
    new_setting->c_cc[VTIME] = 0;
 
    tcsetattr(fileno(stdin), TCSADRAIN, new_setting);
}
 
void verbose(struct termios *init_setting)
{
    tcsetattr(fileno(stdin), TCSADRAIN, init_setting);
}
 
unsigned char get_char()
{
    unsigned char ch;
 
    read(fileno(stdin), &ch, sizeof(unsigned char));
 
    return ch;
}

Explicações

Para que a idéia funcione, precisamos alterar algumas opções do terminal, e é aí que entra a biblioteca termios.h. Vamos definir duas funções, verbose(), que volta as configurações ao normal, e silent(), que tira o echo do terminal. O comportamento desejado é que quando o usuário aperte uma tecla, ela não apareça na tela, mas isso traz uma consequência: se você tentar usar o printf() ou qualquer outra função do tipo, ela também não aparecerá. Por isso, temos que ficar alternando entre os modos.

O echo é desativado na linha “new_setting->c_lflag &= ~(ICANON | ECHO);“, onde nós também desligamos o modo canônico, o que nos permite fazer a leitura caractere por caractere, ao invés de linha por linha, como é o padrão.

Outro detalhe importante são as linhas new_setting->c_cc[]. A função read() é uma função que bloqueia a execução do programa até que certa condição seja encontrada. No caso, definimos que ela só pode retornar após ler 1 caractere (VMIN), não importa quanto tempo (em décimos de segundo) isso demore (VTIME). Neste exemplo, poderíamos também usar c_cc[VMIN] = 0 e c_cc[VTIME] = 100, que faz com que a função retorne assim que receber o primeiro caractere ou depois de 10 segundos, permitindo que o programa continue com uma opção padrão.

Um último detalhe é o tipo de loop que eu usei. De maneira geral, um loop do tipo while(1) pode maximizar o uso de uma das CPUs, mas no caso não temos esse problema, já que read() é uma função que bloqueia a execução do programa até que ela retorne.

Calculadora RPN

Uma calculadora que usa notação polonesa reversa para o terminal que eu escrevi usando a biblioteca usando termios.h

Page tags: c termios
page_revision: 1, last_edited: 1238075568|%e %b %Y, %H:%M %Z (%O ago)
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License