Definindo as cores do TTY

Outro dia, um dos membros do grupo Curso GNU, no Telegram estava batendo cabeça para alterar as cores do seu TTY (o terminal no popular “modo texto”). Por se tratar de algo raramente comentado, eu resolvi deixar registrada a minha solução em Bash aqui no blog da comunidade debxp.

Definição de cores vs paletas de cores

Para começar, existe uma diferença fundamental entre definir uma cor e definir uma paleta: a primeira diz respeito a invocar o nome de uma cor, enquanto a segunda refere-se ao que nós veremos quando a cor invocada for exibida.

Repare bem, é bastante popular o uso de sequências ANSI para estilizar a cor dos textos no terminal, por exemplo:

printf '\e[32m%s\e[31m%s\e[0m' 'verde' 'vermelho'

Que resulta em:

Aqui, as sequências \e[32m e \e[31m foram utilizadas para alterar a cor do texto (foreground) para verde e vermelho, respectivamente. A sequência \e[0m, por sua vez, restaura a cor que está definida por padrão para os textos do terminal. Contudo, quem diz o que nós perceberemos como verde, vermelho ou qualquer outra cor com os nossos olhos é uma paleta de cores.

A maioria dos terminais gráficos oferece uma interface para definirmos a paleta. No caso do terminal que eu uso, o xfce4-terminal, esta é a interface:

Observe que os botões da seção Paleta me permitem escolher 16 cores conforme a minha preferência – basta clicar em qualquer um deles para fazer a minha definição:

Além dos elementos gráficos que me permitem escolher a cor com o mouse, existe uma caixa de texto onde eu posso escrever a cor em hexadecimal: no caso, #2A94E0.

Cores em hexadecimal

A representação em hexadecimal de uma cor corresponde aos níveis de intensidade das três cores básicas que participarão de uma mistura aditiva: vermelho, verde e azul, nesta ordem, ou RGB.

R=Vermelho
G=Verde
B=Azul

 R  |G  |B
#2A|94|E0

Com os dois dígitos hexadecimais de cada cor, nós podemos definir até 256 níveis de intensidade, de 00 a FF.

Apenas para efeito ilustrativo, no Bash, nós podemos converter facilmente os valores em hexa para os valores equivalentes em base decimal e, assim, ter uma noção melhor dos níveis de intensidade de cada componente de cor:

:~$ printf '%d %d %d\n' '0x2A' '0x94' '0xE0'
42 148 224
 ↑   ↑    ↑
 R   G    B

Adiantando-me à sua curiosidade, a conversão contrária também é possível:

:~$ printf '%x %x %x\n' '42' '148' '224'
2a 94 e0

Voltando à paleta

Como eu ia dizendo, a interface do meu terminal me permite definir até 16 cores, apresentadas em duas fileiras de 8 botões.

A fileira de cima, refere-se às 8 cores “normais”, enquanto a de baixo são as cores “brilhantes”. Em ambas as fileiras, os botões são apresentados na ordem exata da numeração dessas cores para o sistema operacional: números de 0 a 7.

Nas sequências ANSI, a fileira de cima recebe o dígito 3 antes do número de cada cor, enquanto a numeração da fileira de baixo é precedida do dígito 9. Deste modo, quando queremos aplicar a terceira cor da paleta “normal” a um texto, por exemplo, nós nos referimos a ela com o número 32 (verde). Se quisermos utilizar a quinta cor da paleta “brilhante”, nós utilizamos 94 (azul brilhante), e assim por diante.

Alguns terminais ainda oferecem a opção de exibir as cores brilhantes como versões em negrito (bold) das cores normais.

Sequências ANSI

As 16 cores da paleta do terminal podem ser obtidas em uma sequência de escape (uma sequencia de caracteres iniciados com o caractere ESC, representado por \e[) segundo a tabela abaixo:

Cores “normais”

Cor Frente Fundo
Preto 30 40
Vermelho 31 41
Verde 32 42
Amarelo 33 43
Azul 34 44
Magenta 35 45
Ciano 36 46
Cinza claro 37 47

Importante! Uma mesma sequência de escape pode receber vários comandos separados por ; e deve ser sempre terminada com o caractere m.

Agora ficou fácil entender o que estava acontecendo no nosso primeiro exemplo:

printf '\e[32m%s\e[31m%s\e[0m' 'verde' 'vermelho'

Já as cores “brilhantes” podem ser utilizadas conforme esta tabela:

Cores “brilhantes”

Cor Frente Fundo
Cinza escuro 90 100
Vermelho 91 101
Verde 92 102
Amarelo 93 103
Azul 94 104
Magenta 95 105
Ciano 96 106
Branco 97 107

Mas, não se confunda, esses nomes e números são apenas isso: nomes e números de cores – o que nós veremos no terminal são as definições da paleta! Isso é importante porque, como o membro do grupo do Curso GNU queria, nós podemos modificar a paleta de modo que todas as cores sejam, por exemplo, apenas diferentes intensidades e tonalidades de verde.

E no TTY?

Os terminais TTY (os consoles) não possuem uma interface gráfica para facilitar a definição da paleta de cores e não é uma tarefa trivial fazer essa alteração. Para a nossa sorte, porém, nós também temos sequências de escape para controlar a paleta do TTY: o detalhe é que sequências de escape são executadas a partir de um shell. No caso do Bash, nós podemos utilizar o comando interno printf para executar as sequências ANSI, como já fizemos no terminal gráfico, só que, desta vez, nós vamos manipular a paleta de cores.

Sequências de escape OSC

Sequências de escape OSC são, como o nome diz, Comandos para o Sistema Operacional (Operating System Commands, em inglês). O console do Linux (e aqui é Linux mesmo, o kernel) utiliza a sequência \e]PXRRGGBB para definir a combinação dos componentes RGB na paleta de cada cor de índice X(um dígito hexadecimal de 0a F).

Importante! as sequências OSC para definição da paleta do TTY devem ser executadas no TTY, ou poderão causar o travamento de alguns terminais gráficos!

Portanto, para alterar a paleta da cor de índice 2 (verde normal), nós podemos fazer, por exemplo:

:~$ printf '%b' '\e]P2009900'

A tabela completa fica assim:

Cores da paleta do TTY

Índice Cor normal Índice Cor brilhante
P0 Preto P8 Cinza escuro
P1 Vermelho P9 Vermelho brilhante
P2 Verde PA Verde brilhante
P3 Amarelo PB Amarelo brilhante
P4 Azul PC Azul brilhante
P5 Magenta PD Magenta brilhante
P6 Ciano PE Ciano brilhante
P7 Cinza claro PF Branco

Evitando problemas

Como as sequências OSC para definição da paleta do TTY devem ser executadas apenas no TTY, nós podemos fazer um teste, antes de utilizá-las, exapandindo a variável TERM:

:~$ echo $TERM
linux

Em um script, seria algo assim:

[[ $TERM = 'linux' ]] && printf '%b' '\e]P2009900'

Recarregando a definição da paleta em novas sessões do TTY

As alterações da paleta do TTY afetam apenas o console em que foram feitas e irão durar até o próximo reboot. Para que elas sejam aplicadas novamente a cada início de uma sessão do Bash no console, será necessário incluir os comandos no arquivo ~/.bashrc. Contudo, o .bashrc é carregado indiscriminadamente em todos os terminais, gráficos ou não, quando o Bash é iniciado. Por este motivo, é fortemente recomendado criar uma função e condicionar sua chamada à expansão da variável TERM.

No meu .bashrc, eu inclui esta função:

# ------------------------------------------------------------------------------
# Paleta de cores do TTY
# ------------------------------------------------------------------------------

paleta_de_cores() {
    local -a paleta
    paleta=(
        '\e]P00A0B1E'
        '\e]P1ED4646'
        '\e]P24E9743'
        '\e]P3DDA434'
        '\e]P42A94e0'
        '\e]P59F33BA'
        '\e]P61BA797'
        '\e]P7C1C2C6'
        '\e]P8585A6C'
        '\e]P9EB6E6E'
        '\e]PA7BC86F'
        '\e]PBE6C758'
        '\e]PC6BC2FA'
        '\e]PDC990DC'
        '\e]PE70D9C8'
        '\e]PFFBFCFE'
    )
    printf '%b' ${paleta[@]}
    # O 'clear' é opcional...
    clear
}

[[ $TERM = 'linux' ]] && paleta_de_cores
# ------------------------------------------------------------------------------

Assim que a sessão do Bash for iniciada em um console TTY, a função será executada e a minha paleta de cores será redefinida. Porém, ela não afetará os caracteres que já tiverem sido exibidos nem a cor de fundo do console. Por este motivo, opcionalmente, nós podemos incluir o comando clear, que aparece no código da função, mas isso implica em perder todas as mensagens anteriormente exibidas. Se isso não for um problema para você, mantenha o clear na função. Caso contrário, basta executar o atalho Ctrl+L quando for mais conveniente – a escolha é sua!