Elmord's Magic Valley

Software, lingüística e rock'n'roll. Sometimes in English.

Migrando para o XKB

2013-02-23 21:48 -0300. Tags: comp, unix, kbd

(Se você não quer saber de preliminares, clique aqui.)

Depois de muita enrolação, decidi migrar meu keymap do xmodmap para o XKB. Sobrevivi à experiência.

O objetivo deste post é relatar parte do que eu aprendi no processo. Para uma referência mais ou menos completa da configuração do XKB, você pode consultar o mui excelente An Unreliable Guide to XKB Configuration, ou algum outro dos links listados no final do post sobre o xmodmap. Neste post, pretendo cobrir o básico necessário para migrar do xmodmap para o XKB (talvez um pouco mais do que o básico, mas enfim).

Little rant

O XKB é um pequeno monstro. Ele foi feito para resolver todos os problemas que ninguém teve antes dele. Por exemplo: existe uma seção do keymap, xkb_geometry, cujo objetivo é descrever a disposição física das teclas no teclado. Afinal, algum programa pode querer desenhar o teclado na tela (o comando xkbprint faz isso), ou saber que tecla fica do lado de qual. Nunca se sabe. Existem pelo menos três maneiras de descrever um layout pré-pronto (XkbKeymap, XkbRules, ou descrevendo componente por componente do keymap), e pelo menos três meios de ativar uma configuração (setxkbmap, xkbcomp ou pelo arquivo de configuração do X). E como todo programa no Unix, o XKB usa um arquivo de configuração com sua própria sintaxe idiossincrática, com seu próprio parser que emite mensagens de erro extremamente prestativas, como esta diante da falta de um ; na linha 21:

syntax error: line 23 of /root/.xkb/default
last scanned symbol is: modifier_map
Errors encountered in /root/.xkb/default; not compiled.

Alguém podia voltar no tempo e colocar um leitor de S-expressions na libc original.

Dito isso, muito da complexidade do XKB pode ser ignorada para tarefas mais simples de edição de keymap (leia-se: tudo o que era possível com o xmodmap). Além disso, o fato de o povo do X ter conseguido implementar as loucuras do XKB sem quebrar compatibilidade com os programas mais antigos é digno de três estrelinhas (pelo que eu entendo o próprio xmodmap continuou funcionando intacto depois da adição do XKB ao X, ainda que com ocasionais comportamentos estranhos).

But that's enough talk. Have at you!

Conceitos básicos

Antes de mais nada, gostaria de esclarecer que XKB não é um programa, e sim uma infraestrutura de configuração de keymap. O XKB é composto por uma extensão do servidor X e um conjunto de funções da biblioteca Xlib que torna as funcionalidades do XKB acessíveis aos clientes X. Além disso, o X vem com alguns programas utilitários, tais como xkbcomp, setxkbmap e xkbprint, que utilizam a infraestrutura do XKB para ler e manipular o layout. (O xmodmap, por outro lado, é um programa que utiliza a o "core protocol" do X para manipular o teclado; a função do xmodmap é vagamente similar à do xkbcomp e setxkbmap.)

Outra diferença entre o xmodmap/core-protocol e o XKB é que no XKB não existe (aparentemente) a noção de alterar o keybinding de teclas individuais do keymap; é necessário sempre carregar um keymap completo. Por outro lado, os arquivos de configuração do XKB suportam uma diretiva include, que nos permite incluir um keymap pré-pronto em um arquivo de configuração e especificar apenas o que queremos de diferente do default.

O programa principal de controle do XKB é o xkbcomp. A função principal desse programa é ler um arquivo texto descrevendo um keymap e compilá-lo para um formato binário que o X é capaz de digerir. A sintaxe básica é:

xkbcomp [opções] origem destino

Porém, o xkbcomp é uma criatura bastante flexível. Você pode especificar como destino algo como :0, instruindo o xkbcomp a compilar o keymap e ativá-lo no display :0, sem necessidade de criar um arquivo binário intermediário. Além disso, você pode especificar :0 como origem para fazer um dump do keymap atual para um arquivo texto especificado como destino (você pode usar - para imprimir para a stdout). O dump do xkbcomp é enorme (1844 linhas na minha máquina; arquivos de keymap na prática usam includes, o que os torna muito menores), mas é útil para vermos quais são as diretivas de configuração possíveis e qual é a sintaxe de cada uma.

A função primária do setxkbmap é pôr em efeito um keymap pré-pronto. A sintaxe básica é:

setxkbmap [opções] [layout [variant [option...]]]

Onde layout, variant e option são uma das três maneiras de descrever um keymap no X (XkbRules). Exemplos:

setxkbmap br abnt2                 # Layout ABNT-2
setxkbmap us intl                  # Layout US internacional (com acentos)
setxkbmap br abnt2 ctrl:swapcaps   # Layout ABNT-2 com Ctrl e CapsLock invertidos
setxkbmap br abnt2 ctrl:nocaps     # Layout ABNT-2, CapsLock se comporta como Ctrl

Como mencionado anteriormente, você pode atribuir keymaps diferentes a teclados diferentes usando a opção -device id, onde id é o id do dispositivo tal como exibido pelo comando xinput list.

O esquema de rules é muito bonito quando queremos usar um keymap pronto, mas não quando queremos fazer modificações ao keymap. Nesse caso, teremos que criar nosso próprio arquivo de keymap. Como não é possível aplicar um keymap parcial sobre o keymap existente com o XKB, e não queremos ter que descrever todo o keymap só para alterar meia dúzia de teclas, o ideal é usar includes. Felizmente, o setxkbmap suporta uma opção -print, que imprime um keymap do XKB que descreve o keymap atual (ou um especificado na linha de comando) em termos de includes:

# setxkbmap -print -option "" br abnt2
xkb_keymap {
        xkb_keycodes  { include "evdev+aliases(qwerty)" };
        xkb_types     { include "complete"      };
        xkb_compat    { include "complete"      };
        xkb_symbols   { include "pc+br(abnt2)+inet(evdev)"      };
        xkb_geometry  { include "pc(pc105)"     };
};

(O -option "" serve para resetar as options do XKB (tais como ctrl:nocaps e ctrl:swapcaps). Por padrão, o setxkbmap usa as opções ativas atualmente, mesmo que elas não sejam especificadas na linha de comando. Você também pode executar simplesmente setxkbmap -print, sem especificar um keymap, para usar o keymap atual (ou pelo menos o que o setxkbmap pensa que é o keymap atual).)

Eis um keymap completo! Se você salvar esse keymap em um arquivo, você pode usar o comando xkbcomp arquivo :0 para pôr o keymap em efeito.

Componentes e includes

Como podemos ver na saída do setxkbmap -print, um keymap é composto por cinco componentes:

Basicamente a única parte que nos interessa do keymap é a xkb_symbols; podemos ignorar todo o resto e simplesmente copiar a configuração emitida pelo setxkbmap.

A diretiva include instrui o xkbcomp a procurar os componentes especificados em seu caminho de pesquisa. Por padrão, o xkbcomp procurará no diretório /usr/share/X11/xkb. Dentro desse diretório você encontrará subdiretórios para cada componente (keycodes, symbols, etc.). O xkbcomp procurará no subdiretório apropriado dependendo da seção em que a diretiva include ocorrer.

Cada um desses arquivos pode conter mais de uma versão do mesmo componente. Uma diretiva da forma include "br(abnt2)" indica o componente chamado abnt2 dentro do arquivo br no diretório apropriado. Também é possível incluir múltiplos componentes, usando a sintaxe include "componente1+componente2".

Você pode adicionar outros diretórios ao caminho de pesquisa do xkbcomp usando a opção -Idiretório (sem espaço após o -I). O xkbcomp esperará encontrar os componentes a incluir dentro dos subdiretórios apropriados no diretório especificado (e.g., diretório/symbols, etc.).

Símbolos

Vamos ao que interessa: fazer com o XKB o que a essas alturas já teríamos feito com o xmodmap. Eis um arquivo de keymap de exemplo:

xkb_keymap {
    xkb_keycodes  { include "evdev+aliases(qwerty)" };
    xkb_types     { include "complete"      };
    xkb_compat    { include "complete"      };
    xkb_symbols   {
         include "pc+br(abnt2)+inet(evdev)"
         key <LatA> { [ a, A, aacute, Aacute ] };
         key <AE02> { [ 3, numbersign, threesuperior ] };
         key <CAPS> { [ Control_L ] };
         modifier_map Control { <CAPS> };
    };
    xkb_geometry  { include "pc(pc105)"     };
};

O trecho em negrito são os comandos que adicionamos; o resto foi gerado pelo comando setxkbmap -print -option "" br abnt2. Note que não vai ponto-e-vírgula após o include.

O principal comando da seção xkb_symbols é o key. A sintaxe básica é:

key <nome-da-tecla> { [ level1, level2, level3, level4 ] };

Onde <nome-da-tecla> é o nome simbólico da tecla definido na seção xkb_keycodes (uma maneira "prática" de descobrir os nomes das teclas é olhar a seção xkb_symbols do keymap atual com o comando xkbcomp :0 - | less), e level1~level4 são (basicamente) os símbolos emitidos pela tecla pura, Shift+tecla, AltGr+tecla e AltGr+Shift+tecla. Você pode especificar menos que quatro níveis, se desejar.

O comando modifier_map associa teclas a modificadores, e tem basicamente a mesma função do comando add no xmodmap, com a diferença de que no XKB ele funciona direito. A sintaxe é:

modifier_map ModifierName { <nome-da-tecla-1>, <nome-da-tecla-2>, ... };

Onde ModifierName é algo como Control, Shift, Lock, Mod1, etc. (i.e., os mesmos modifiers do xmodmap), e <nome-da-tecla-1> é um nome simbólico de tecla, como <CAPS> ou <LCTL> (left Control) ou <RALT> (right Alt).

É possível especificar mais de um comando modifier_map com o mesmo modificador (o que é ótimo, pois o arquivo em que estamos dando include contém também suas próprias definições de modifier_map). Aparentemente não é possível desassociar um modificador de uma tecla (i.e., aparentemente não existe algo equiparável aos comandos remove e clear do xmodmap), mas o XKB parece ser esperto o suficiente para limpar uma tecla automaticamente quando ela é redefinida pelo comando key (i.e., não é necessário fazer os malabarismos de clear e add que tínhamos que fazer com o xmodmap para remapear Control e CapsLock).

Você pode salvar esse keymap e executar xkbcomp arquivo :0 para pô-lo em vigor.

Simples, hã? Se tudo o que você queria era fazer com o XKB o que você fazia com o xmodmap, você já tem toda a informação de que precisa. (Exceto o comando pointer do xmodmap, cujo equivalente no XKB, se houver, eu não conheço.)

Levels e Groups

Se você fizer um dump do keymap atual com xkbcomp :0 -, verá que a sintaxe que ele usa para o comando key é um pouco mais verbosa:

key <AC01> {
    type= "FOUR_LEVEL_ALPHABETIC",
    symbols[Group1]= [ a, A, aacute, Aacute ]
};

Comecemos pelo fim. A sintaxe symbols[Group1] = [ ... ] indica que os quatro símbolos estão sendo associados aos quatro níveis do grupo Group1 da tecla. Basicamente, o nível de uma tecla é afetado pelas teclas Shift, AltGr e CapsLock. É por isso que, em versões modernas do X, a tecla AltGr é associada ao símbolo ISO_Level3_Shift, como discutido no post sobre o xmodmap.

Um grupo, por sua vez, é uma maneira de atribuir diversos conjuntos de símbolos para uma mesma tecla. Por exemplo, se você costuma escrever em russo, você pode ter um layout ABNT-2 no grupo Group1 e um layout russo no grupo Group2, e atribuir uma tecla para alternar entre os grupos. A vantagem de se usar, por exemplo, dois grupos de quatro níveis ao invés de um grupo de oito níveis, é que cada grupo pode ser definido em um arquivo separado. Se você usar uma diretiva do tipo:

include "pc+br(abnt2)+us(intl):2"

você está instruindo o xkbcomp a incluir o layout br(abnt2) como o primeiro grupo e o layout us(intl) como o segundo*. Você pode então definir uma tecla para alternar entre os dois grupos: o símbolo Mode_switch alterna para o próximo grupo enquanto a tecla é pressionada, e ISO_Next_Group, ISO_Prev_Group e outros alternam permanentemente o grupo atual (como se fosse um CapsLock para grupos).

Ao invés de usar a sintaxe symbols[Group1] = [ ... ], symbols[Group2] = [ ... ], você pode simplesmente especificar mais de um grupo envolvido em [ ... ] dentro do comando key:

key <AC01> { [ a, A, aacute, Aacute ], [ ae, AE, aring, Aring ] };

Agora voltemos ao começo. type= "FOUR_LEVEL_ALPHABETIC" diz que o tipo da tecla A é FOUR_LEVEL_ALPHABETIC. Se nós olharmos o conteúdo da seção xkb_types do dump do xkbcomp, encontraremos a seguinte definição:

type "FOUR_LEVEL_ALPHABETIC" {
    modifiers= Shift+Lock+LevelThree;
    map[Shift]= Level2;
    map[Lock]= Level2;
    map[LevelThree]= Level3;
    map[Shift+LevelThree]= Level4;
    map[Lock+LevelThree]= Level4;
    map[Shift+Lock+LevelThree]= Level3;
    level_name[Level1]= "Base";
    level_name[Level2]= "Shift";
    level_name[Level3]= "Alt Base";
    level_name[Level4]= "Shift Alt";
};

O que essa definição nos diz é que uma tecla do tipo FOUR_LEVEL_ALPHABETIC é afetada pelos modificadores Shift, Lock e LevelThree (i.e., AltGr), e que diferentes combinações desses modificadores ativam diferentes níveis da tecla. As teclas de pontuação, por outro lado, são do tipo FOUR_LEVEL. A definição de FOUR_LEVEL é similar à de FOUR_LEVEL_ALPHABETIC, mas não inclui o Lock entre os modificadores que afetam a tecla. As teclas do keypad numérico, por sua vez, são do tipo FOUR_LEVEL_KEYPAD, que é análogo ao FOUR_LEVEL_ALPHABETIC, mas usa o modificador NumLock ao invés de Lock para escolher os níveis superiores. Há diversos outros tipos de teclas, definidos de maneira similar.

End of Game

Por hoje ficamos por aqui. O XKB possui inúmeras outras features, sobre as quais talvez eu venha escrever no futuro. Para mais informações, consulte os links já referidos, e dê uma olhada na saída do xkbcomp :0 -.

____

* Três estrelinhas para o primeiro que achar essa informação documentada em algum lugar; eu descobri olhando o /usr/share/X11/xkb/rules/xorg, chutando o significado do :n e testando. By the way, aparentemente simplesmente não existe documentação oficial, que dirá uma especificação, da sintaxe desses arquivos. Para mais informações, consulte o código-fonte da libxkbfile. (E não, a gramática não é especificada por um arquivo yacc da vida. Vire-se no parser escrito à mão em C, se sua paciência for capaz.)

2 comentários

Múltiplos keymaps no X

2013-02-09 03:50 -0200. Tags: comp, unix, mundane, kbd

(O script desenvolvido neste post pode ser encontrado aqui.)

Em um episódio anterior deste blog, vimos como usar o xmodmap para alterar o keymap do X. Também vimos que embora o xmodmap seja muito mais simples de usar do que o XKB, ele está semi-obsoleto e não lida muito bem com certos recursos mais modernos do XKB. Recentemente eu descobri mais um: o xmodmap não funciona direito com múltiplos teclados (e.g., um teclado builtin de notebook e um teclado externo). Especificamente:

  1. O xmodmap altera os layouts de todos os teclados conectados; não é possível atribuir um layout US para um teclado e um ABNT-2 para outro, por exemplo;
  2. O xmodmap altera apenas os layouts dos teclados conectados; se você ligar um novo teclado, ele assumirá o layout default, e você terá que rodar o xmodmap novamente para afetá-lo.

Sendo assim, temos dois problemas a resolver: detectar quando um teclado é conectado (o que independe de usarmos xmodmap ou XKB), e atribuir um layout a ele (o que exige o XKB se quisermos usar um layout específico para cada teclado; se quisermos apenas propagar o layout corrente para o novo teclado, podemos usar o xmodmap).

Detectando o teclado

A maneira mais palatável que eu encontrei de detectar o teclado* é monitorando o arquivo de log do X (/var/log/Xorg.n.log, onde n é o número do display). As linhas relevantes do arquivo têm a forma:

[ 10848.216] (II) XINPUT: Adding extended input device "GASIA GASIA USB KB Pro" (type: KEYBOARD)

Podemos fazer um script que monitora esse arquivo com o comando tail -f, filtra apenas os dados que nos interessam, e fica em um laço aguardando que novas linhas sejam escritas no arquivo:

#!/bin/bash

[[ $DISPLAY == :* ]] || echo "$0: Oops, remote display?" >&2
display="${DISPLAY##*:}"
display="${display%%.*}"

logfile="/var/log/Xorg.$display.log"

tail -n +1 -f "$logfile" |
    sed -une 's/.*XINPUT: Adding extended input device "\([^"]*\)" (type: \([^)]*\)).*/\2\t\1/p' |
    while IFS=$'\t' read type name; do

        echo "Dispositivo detectado: $name"
        # comandos...

    done

Salve esse script em algum local apropriado (e.g., ~/bin/xinput-monitor) e dê-lhe permissão de execução (chmod +x ~/bin/xinput-monitor). Você pode executá-lo como está para testar se os dispositivos estão sendo realmente detectados (experimente conectar um teclado externo com o script rodando).

A opção -n +1 faz com que o tail imprima o conteúdo do arquivo desde a primeira linha antes de começar a monitorá-lo; isso faz com que o script "detecte" mesmo os dispositivos que já estavam conectados antes de ele rodar. Se você não desejar esse comportamento, pode trocar o -n +1 por -n 0.

Se tudo o que você quer é rodar o xmodmap (ou qualquer outro comando) quando um dispositivo é conectado, basta colocar o comando no trecho indicado no while. Feito isso, basta pôr o script para rodar na inicialização de seu ambiente gráfico (colocando uma linha do tipo ~/bin/xinput-monitor & no seu ~/.xinitrc ou ~/.xsession, ou em algum arquivo de configuração do seu ambiente gráfico favorito).

Note que nem todos os dispositivos listados são teclados (nem mesmo todos os dispositivos com type: KEYBOARD são teclados); se você não quiser executar o xmodmap uma vez para cada um desses dispositivos (o que em tese é inofensivo, mas nunca se sabe), você pode usar um if ou case para ignorar os dispositivos que não lhe interessam. Por exemplo:

    ...
    while IFS=$'\t' read type name; do
        echo "Dispositivo detectado: $name"

        case "$name" in
            'Power Button'|'Video Bus'|'Integrated Webcam'|*'Synaptics'*)
                # Não faz nada.
                ;;
            *)
                # Os comandos que desejamos executar quando um teclado é conectado.
                xmodmap ~/.xmodmaprc
                xset r rate 250
                xkbset exp =m
                ;;
        esac
    done

Setando um layout específico para cada teclado

Para atribuir um layout individual para cada teclado conectado, teremos que abandonar o bom[citation needed] e velho xmodmap e entrar no maravilhoso[dubious – discuss] mundo do XKB. Felizmente, usar um layout pronto com o XKB é bem simples: basta executar um comando como:

# Layout ABNT-2.
setxkbmap -device device_id br abnt2

# Layout US internacional (com acentos).
setxkbmap -device device_id us intl

A opção -device device_id indica qual teclado o comando deve afetar. Se ela não for especificada, o comando afeta todos os teclados.

O grande problema é determinar o device_id de cada teclado; ele pode variar dependendo dos dispositivos presentes e da ordem em que o X os encontra. Aparentemente o id não aparece no arquivo de log do X. A solução é usar o xinput (que vem no pacote xinput no Debian, Ubuntu e companhia), um comando que permite ver e modificar diversas configurações de dispositivos de entrada no X (tais como os dez mil parâmetros que controlam velocidade, aceleração, gestures e outras firulas de touchpads), entre outras coisas. O comando xinput list produz uma listagem dos dispositivos presentes e respectivos ids, com a seguinte cara:

# xinput list
⎡ Virtual core pointer                          \tid=2\t[master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer                \tid=4\t[slave  pointer  (2)]
⎜   ↳ SynPS/2 Synaptics TouchPad                \tid=13\t[slave  pointer  (2)]
⎜   ↳ GASIA GASIA USB KB Pro                    \tid=10\t[slave  pointer  (2)]
⎣ Virtual core keyboard                         \tid=3\t[master keyboard (2)]
    ↳ Virtual core XTEST keyboard               \tid=5\t[slave  keyboard (3)]
    ↳ Power Button                              \tid=6\t[slave  keyboard (3)]
    ↳ Video Bus                                 \tid=7\t[slave  keyboard (3)]
    ↳ Power Button                              \tid=8\t[slave  keyboard (3)]
    ↳ Integrated Webcam                         \tid=11\t[slave  keyboard (3)]
    ↳ AT Translated Set 2 keyboard              \tid=12\t[slave  keyboard (3)]
    ↳ GASIA GASIA USB KB Pro                    \tid=9\t[slave  keyboard (3)]

(Os \t representam TABs.)

Um problema visível nessa saída é que pode haver mais de um dispositivo com o mesmo nome (temos dois Power Button e dois GASIA GASIA USB KB Pro (sendo que um deles é um mouse (!))). Como chamar o setxkbmap sobre um não-teclado parece não ter efeito algum, podemos simplesmente tentar os dois ids e nos darmos por satisfeitos.

O primeiro passo é obter os ids a partir dos nomes. Para isso, podemos recorrer ao sed:

        device_ids="$(xinput list | sed -n "s|.*↳ $name *\tid=\([^\t]*\)\t.*|\1|p")"

Com os ids à mão, basta executar o setxkbmap para cada id, usando os parâmetros apropriados dependendo do nome do dispositivo detectado (altere as cláusulas do case para "configurar" o script para os seus teclados e layouts):

        for id in $device_ids; do
            case "$name" in
                'AT Translated Set 2 keyboard') setxkbmap -device "$id" us intl ;;
                'Some weird keyboard')          setxkbmap -device "$id" is ;;
                *)                              setxkbmap -device "$id" br abnt2 ;;
            esac
        done

'Tis over

E era isso. Eis uma versão completa do script. Ficou faltando resolver o problema de portar um xmodmaprc personalizado (caso você tenha um) para o XKB, mas isso fica para um próximo post.

(Este foi o 100-ésimo post do blog, por sinal.)

____

* Outra maneira freqüentemente citada pelo povo da Internet é alterar as configurações do udev para rodar um script quando o teclado é conectado. Além de exigir direitos de root, o script vai ter que descobrir o nome que o X atribui ao teclado por vodu, vai rodar como root (a menos que você tome a precaução de fazer o script trocar para o usuário "dono" da sessão X, o que implica descobrir quem é o dono), e você vai ter que tomar precauções para o caso de haver mais de um display X rodando (por exemplo, se você estiver usando a função "trocar de usuário" de certos ambientes gráficos). Just say no.

Comentários

Diversão com keymaps no X

2012-08-20 04:35 -0300. Tags: comp, unix, mundane, kbd

Em que o poeta compartilha seus nem tão vastos conhecimentos sobre alteração de keymaps no X. Em um post futuro, pretendo falar sobre as modificações que fiz em meu keymap e que achei úteis.

Introdução ao caos

Keymaps no X funcionam da seguinte maneira: A cada tecla está associado um keycode, um número único para cada tecla. Ao pressionar (ou soltar) uma tecla, o X envia o keycode ao cliente que tem o foco do teclado. O cliente, então, pode fazer o que bem entender com esse keycode. Na maior parte dos casos, entretanto, o cliente utiliza-se da keymap table que o servidor X armazena para converter um keycode em um keysym, que dá o significado da tecla em questão. Por exemplo, ao pressionar a tecla Enter, o X repassa para o cliente o keycode 36 (em um teclado comum de PC), que normalmente está associado a um keysym Return na tabela de keymap. Você pode alterar a keymap table usando um programinha chamado xmodmap. Alternativamente, você pode usar alguma outra interface para realizar essa tarefa, como o programa gráfico XKeyCaps (que altera a keymap table em tempo real e gera um arquivo de configuração para o xmodmap).

A cada keycode está associado mais de um keysym; o keysym apropriado depende dos modificadores que estão ativos no momento em que a tecla é pressionada. Em geral, as teclas Shift e AltGr estão associadas a modificadores que influenciam na seleção do keysym (e.g., em um teclado ABNT2, o keycode 10 está associado aos keysyms 1, exclam (Shift+1) e onesuperior (AltGr+1)). [O Caps_Lock também controla um modificador, que não é o mesmo do Shift; mais informação adiante.]

Insatisfeitos com os poderes do xmodmap, o povo do X inventou o Xkb, um mecanismo mais flexível para a especificação de keymaps. Em tese, a criação do Xkb torna o xmodmap obsoleto, mas como disse um sábio:

In principle, Xkb is supposed to take over. In practice, there are only three people in the known universe who understand Xkb, and nobody is quite sure who they are.

O xmodmap, embora apresente o ocasional bug, é bem mais simples de usar do que o Xkb. (Os relatos de experiências com a migração do xmodmap para o Xkb que eu vejo por aí não são muito animadores.) Ainda não estudei o Xkb direito para poder dar instruções; talvez eu poste algo sobre ele no futuro. Para mais informações, vide links. Neste post, focarei no xmodmap.

Fundamentos de xmodmap

Usualmente, o xmodmap lê de um arquivo uma série de comandos, um por linha, que indicam as alterações que se deseja realizar no keymap. O tipo de comando mais simples é da forma:

keycode número = keysym1 keysym2 ...

Esse comando associa os keysyms ao keycode especificado. Até 8 keysyms podem ser especificados em um comando keycode. O primeiro keysym é usado quando a tecla é pressionada sozinha. O segundo é usado quando a tecla é pressionada enquanto o Shift está ativo. O terceiro é usado quando Mode_switch está ativo (já chegamos lá); o quarto com Mode_switch+Shift; o quinto com ISO_Level3_Shift; o sexto com ISO_Level3_Shift+Shift.

Agora, que diabos são Mode_switch e ISO_Level3_Shift?

No começo dos tempos, Mode_switch correspondia ao AltGr. Os quatro primeiros keysyms correspondiam à tecla pura, Shift, AltGr e AltGr+Shift. E o mundo era feliz.

Por algum diabo, em algum ponto da história o AltGr passou a ser mapeado como ISO_Level3_Shift na maior parte dos keymaps padrão do X. A conseqüência mais visível disso é que o terceiro e quarto keysym da tabela do xmodmap não fazem nada no keymap padrão. Conseqüentemente, se você pretende atribuir um keysym diferente para AltGr+alguma-coisa, você deve preencher as colunas 3 e 4 com algum valor tapa-buraco (e.g., NoSymbol, ou uma cópia das colunas 5 e 6, ou qualquer coisa).

Se o problema fosse só esse, seria apenas um pequeno inconveniente. O problema é que o tal ISO_Level3_Shift é uma invenção do Xkb que não interage muito bem com o xmodmap. Se uma tecla não possui um valor associado a ISO_Level3_Shift+tecla no keymap padrão, não é possível associar um valor a essa combinação via xmodmap; o X simplesmente ignorará o valor das colunas 5 e 6. Por exemplo, se você está usando um keymap us-intl (US Internacional) com AltGr mapeado para ISO_Level3_Shift, você conseguirá alterar o keysym da combinação AltGr+a, pois essa combinação existe no keymap original, mas você não conseguirá atribuir efetivamente um keysym a AltGr+j, pois essa combinação não possui mapeamento no us-intl. Da mesma forma, você não conseguirá mapear AltGr-↑.

A solução mais simples para esse problema é simplesmente remapear o AltGr para Mode_switch e ser feliz para sempre. O problema é que nesse caso você perde todos os mapeamentos com AltGr que possam existir originalmente no seu keymap padrão, pois eles estão associados ao ISO_Level3_Shift. Por exemplo, se você remapear o AltGr para Mode_switch em um layout ABNT2, você perderá o mapeamento padrão de AltGr+1 para ¹ e terá que remapeá-lo manualmente (ou com um script; vejamos mais adiante) se desejá-lo.

Evidentemente, isso só é um problema se você deseja usar o AltGr com alguma tecla não prevista no layout original.

A solução de remapear o AltGr não resolve o problema se você deseja ter tanto o Mode_switch quanto o ISO_Level3_Shift no mesmo teclado (permitindo associar seis keysyms por tecla). Nesse caso, você vai ter que arranjar um keymap do Xkb que mapeie todas as combinações desejadas com ISO_Level3_Shift. O keymap br(abnt2) parece servir bem para esse propósito. Em último caso, você pode experimentar alterar os arquivos /usr/share/X11/xkb/symbols/us e companhia e adicionar o que falta (mas nesse caso não teria mais por que usar o xmodmap, em tese (exceto pelo fato de que é mais prático alterar o teclado posteriormente via xmodmap do que alterando os arquivos de sistema)).

Quanto às colunas 7 e 8, elas aparentemente não servem para nada. Dizem as más línguas que elas deveriam conter o keysym associado à combinação Mode_switch+ISO_Level3_Shift+Shift+tecla, mas nunca consegui fazer funcionar. Tentar criar um modificador ISO_Level4_Shift ou ISO_Level5_Shift também não foi eficaz. (Por sinal, o keysym ISO_Level4_Shift não existe, mas ISO_Level5_Shift sim. Go figure.)

E como eu descubro os keycodes e keysyms?

O X vem com um programinha supimpa chamado xev. Esse programa cria uma janela e reporta pela stdout (o terminal por onde você abriu o xev) todos os eventos que essa janela recebe. Assim, ao pressionar uma tecla, o xev imprimirá algo do tipo:

KeyPress event, serial 33, synthetic NO, window 0x2200001,
root 0x69, subw 0x0, time 39264530, (38,371), root:(1137,799),
state 0x0, keycode 98 (keysym 0xff52, Up), same_screen YES,
XLookupString gives 0 bytes: 
XmbLookupString gives 0 bytes: 
XFilterEvent returns: False

Com isso, você consegue descobrir o keycode da tecla (para poder atribuir keysyms) e o keysym que ela gera atualmente (para poder atribuí-lo a outra tecla).

Se você deseja atribuir a uma tecla um keysym que ainda não existe no seu keymap e que você não sabe o nome (e.g., se você quiser atribuir o caractere ĉ à combinação Mode_switch+c), você pode:

  1. usar Uxxxx, onde xxxx é o código Unicode hexadecimal do caractere desejado (no caso de o símbolo desejado ser um caractere, e não um XF86AudioStop da vida); ou
  2. olhar uma tabela de keysyms, tal como a tabela incompleta em /usr/share/X11/XKeysymDB (que normalmente acompanha o X) ou a tabela completa em /usr/include/X11/keysymdef.h (descartando o prefixo XK_; esse arquivo vem no pacote x11proto-core-dev no Debian); ou
  3. adivinhar o nome do símbolo. Nomes típicos incluem:
    • dead_acute, dead_diaresis, dead_circumflex, dead_tilde, dead_macron, dead_breve, dead_abovedot, dead_cedilla, dead_caron, etc.: acentos e diacríticos de todos os sabores (uma deadkey é uma tecla que não faz nada até que seja digitado o caractere seguinte);
    • aacute, Aacute, ccedilla, Ccedilla, etc.: caracteres acentuados prontos;
    • XF86AudioMute, XF86AudioLowerVolume, XF86AudioRaiseVolume, XF86AudioPlay, XF86AudioStop, XF86AudioPrev, XF86AudioNext, XF86PowerOff, etc.: teclas "multimídia" e firulas relacionadas;

O comando keysym

Em alguns casos você não precisa saber o keycode de uma tecla para mapeá-la. O xmodmap reconhece o comando:

keysym keysym_velho = keysym1 keysym2 ...

Nesse caso, o xmodmap procura por todos keycodes que atualmente estão associados ao keysym_velho, e lhes atribui os valores novos. Isso é particularmente útil quando se deseja apenas associar valores novos às combinações com AltGr, sem alterar o valor puro da tecla:

! O primeiro par ccedilla/Ccedilla para o Mode_switch, o segundo para o ISO_Level3_Shift.
keysym c = c C ccedilla Ccedilla ccedilla Ccedilla

Para modificações que alteram o valor puro da tecla, entretanto, usar o comando keysym pode não ser uma boa idéia. Suponha que você deseja trocar os parênteses e os colchetes de lugar no teclado:

keysym 9 = 9 bracketleft
keysym 0 = 0 bracketright
keysym bracketleft = parenleft braceleft
keysym bracketright = parenright braceright

A aplicação dessas modificações no keymap original funciona (o xmodmap é esperto o suficiente para primeiro avaliar todos os lados esquerdos do =, e depois efetuar as mudanças, de modo que a alteração das duas primeiras linhas não interfere no funcionamento das duas últimas numa mesma execução). Porém, se esse arquivo de keymap for carregado uma segunda vez, catástrofes acontecem: as duas primeiras linhas fazem o que deveriam, mas as duas últimas associam parênteses a todas as teclas que atualmente estão mapeadas para colchetes, ou seja, as teclas 9 e 0! Como os comandos são avaliados na ordem, as duas últimas linhas sobrepõem o mapeamento de 9 e 0, de modo que tanto 9 e 0 quanto as teclas de colchetes agora contêm parênteses!

Evidentemente, o problema só surge se você carregar o arquivo duas vezes. Porém, enquanto você está experimentando com keymaps, você provavelmente vai querer fazer alterações e recarregar o arquivo de keymap diversas vezes sem ter que reiniciar o X. Em qualquer caso, parece melhor escrever um keymap que faz a coisa certa quando é carregado múltiplas vezes do que encontrar surpresas no futuro e levar mil instantes para entender o que está acontecendo.

Moral da história: só use o keysym para alterar valores secundários das teclas, e isso só se você tiver certeza de que keysym_velho só ocorre uma vez no keymap, ou que você queira realmente alterar todas as teclas associadas ao keysym_velho.

Modifiers

Remapear modificadores (Ctrl, Alt, Shift, Caps_Lock, JanelinhaFeliz, etc.) envolve alguns detalhes mais.

O X mantém uma tabela de modificadores; se você executar xmodmap sem parâmetros, ele imprimirá essa tabela. Essa tabela associa keycodes a modificadores. Os modificadores reconhecidos são control, shift (auto-explicativos), lock (correspondente ao Caps_Lock), e mod1 até mod5 (que não têm significado pré-definido). Sempre que você altera o mapeamento de uma tecla associada a modificadores, você deve lembrar de reajustar a tabela de modificadores também. Para controlar essa tabela, o xmodmap provê os comandos add, remove e clear.

add modifier_name = keysym ... adiciona as teclas associadas aos keysyms especificados à lista de teclas que ativam o modificador. Se você executar um comando do tipo add shift = a, o efeito será que a tecla A, além de funcionar como um A normal, também passará a funcionar como um Shift: se você segurar a tecla A enquanto digita outras teclas, o efeito será o mesmo de segurar Shift ao mesmo tempo que as outras teclas.

remove modifier_name = keysym ... desassocia todas as teclas associadas aos keysyms especificados do modificador em questão. clear modifier_name desassocia todas as teclas do modificador. O fato de que os comandos trabalham com keysyms mas a tabela trabalha com keycodes gera todo tipo de bizarrice na manipulação de keymaps. No geral, o mais simples, prático e indolor é, sempre que se remapeia teclas associadas a modificadores, limpar os modificadores em questão, fazer o remapeamento e adicionar as teclas novamente.

Exemplo: para inverter a posição usual do Ctrl da esquerda e do Caps_Lock:

clear lock
clear control
! Keycodes do Ctrl e Caps_Lock obtidos com o xev. Os valores podem ser diferentes no
! seu teclado / versão do X.
keycode 66 = Control_L
keycode 37 = Caps_Lock
add lock = Caps_Lock
! Não esqueça de readicionar o Ctrl da direita, que foi apagado no clear.
add control = Control_L Control_R

Opções de linha de comando do xmodmap

Além do xmodmap arquivo, para carregar um arquivo com comandos, o xmodmap suporta algumas outras opções. Em primeiro lugar, usando - como arquivo, o xmodmap lê da stdin. Isso é primariamente útil se por alguma razão você quiser gerar um keymap por script e alimentá-lo ao xmodmap. Além disso, o xmodmap suporta uma opção -e comando, que permite executar um comando de keymap diretamente a partir da linha de comando. Por exemplo:

xmodmap -e 'keysym a = a A ae AE'

Cada comando consiste de apenas um comando de keymap, mas a opção -e pode ser repetida.

Outro comando útil é xmodmap -pke, que imprime o keymap atual no formato que o xmodmap lê. Assim, você pode salvar o keymap atual com xmodmap -pke >.xmodmaprc e carregá-lo posteriormente (em outra sessão do X) com xmodmap .xmodmaprc. Isso é útil se você fez diversas modificações no layout pela linha de comando e deseja salvar o estado atual sem ter que encontrar todos os comandos dados e agrupá-los em um arquivo. (O xmodmap -pke não imprime linhas add, remove e clear, entretanto.) Isso também é útil para salvar o keymap atual antes de começar a bagunçá-lo, para poder retornar a um estado consistente sem ter que reiniciar o X. Para isso, é bom criar um meio de invocar o xmodmap seu-teclado-original sem ter que usar o teclado (e.g., com um atalho no seu ambiente gráfico favorito).

Carregando o keymap na inicialização

Todas as modificações feitas na keymap table duram apenas até o servidor X morrer; na próxima inicialização, a tabela volta a ser o que era. Para tornar as modificações permanentes, você precisa carregá-las toda vez que sua sessão X inicia. Para isso, você deverá colocar o comando xmodmap ~/seu-arquivo-com-comandos-de-keymap em algum lugar que seja executado pelo seu ambiente gráfico na inicialização. Se você usa o xinit para carregar a interface gráfica, o arquivo é o .xinitrc, no seu home. Alguns ambientes gráficos carregam o .xsessionrc. Alguns permitem que você adicione comandos para serem executados na inicialização em alguma configuração do ambiente. Finalmente, se não estou enganado, o GNOME detecta por conta arquivos de nome .xmodmaprc e assemelhados na inicialização e pergunta se você deseja carregá-los na inicialização da primeira vez que os encontra.

Notas soltas

Na transição do driver de teclado kbd para evdev (i.e., da época do Debian 4 para o Debian 5, embora ainda seja possível usar o driver kbd hoje em dia, desde que seja instalado o pacote xserver-xorg-input-kbd), algumas teclas mudaram de keycode. Em particular, o AltGr mudou de 113 para 108, e as setas e algumas outras teclas especiais também mudaram. Isso pode causar algum problema na hora de migrar de uma versão velha do X para uma mais recente.

Eu prometi links com informações sobre o Xkb. Eis o que eu encontrei (e ainda não li, mostly):

EOF.

3 comentários

Main menu

Posts recentes

Comentários recentes

Tags

comp (114) prog (51) life (44) unix (32) random (27) lang (27) about (24) mind (22) mundane (21) pldesign (20) in-english (19) lisp (17) web (17) ramble (15) img (13) rant (12) privacy (10) scheme (8) freedom (8) lash (7) music (7) esperanto (7) bash (7) academia (7) home (6) mestrado (6) shell (6) conlang (5) copyright (5) misc (5) worldly (4) book (4) php (4) latex (4) editor (4) politics (4) etymology (3) wrong (3) android (3) film (3) tour-de-scheme (3) kbd (3) c (3) security (3) emacs (3) network (3) poem (2) cook (2) physics (2) comic (2) llvm (2) treta (2) lows (2) audio (1) wm (1) philosophy (1) kindle (1) pointless (1) perl (1)

Elsewhere

Quod vide


Copyright © 2010-2018 Vítor De Araújo
O conteúdo deste blog, a menos que de outra forma especificado, pode ser utilizado segundo os termos da licença Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.

Powered by Blognir.