# Definindo as cores do TTY Outro dia, um dos membros do grupo [Curso GNU, no Telegram](https://t.me/cursognu) 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](https://gitter.im/debxp-comunidade). ## 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](https://codeberg.org/blau_araujo/ansi_escape_sequences) para estilizar a cor dos textos no terminal, por exemplo: ``` printf '\e[32m%s\e[31m%s\e[0m' 'verde' 'vermelho' ``` Que resulta em: ![](https://debxp.org/site/wp-content/uploads/2022/03/paleta-01.png) 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: ![](https://debxp.org/site/wp-content/uploads/2022/03/paleta-02.png) 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: ![](https://debxp.org/site/wp-content/uploads/2022/03/paleta-03.png) 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. ![](https://debxp.org/site/wp-content/uploads/2022/03/paleta-04.png) 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 `0`a `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](https://blauaraujo.com/shell/livro/06-comandos_simples_e_compostos#funcoes) 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!