Corretor de layout Xswitcher para Linux: etapa dois

Como publicação anterior (xswitcher na fase de “prova de conceito”) recebeu muitos comentários construtivos (que é bom), continuei a gastar meu tempo livre desenvolvendo o projeto. Agora quero passar um pouco do seu... A segunda etapa não será muito usual: proposta/discussão do desenho da configuração.

Corretor de layout Xswitcher para Linux: etapa dois

De alguma forma, os programadores normais acham incrivelmente chato configurar todos esses controles.

Para não ser infundado, aqui dentro está um exemplo do que estou tratando.
No geral, Apache Kafka e ZooKeeper foram concebidos de forma excelente (e bem implementados).
- Configuração? Mas é chato! XML idiota (porque está “pronto para usar”).
- Ah, você também quer um ACL? Mas é tão chato! Tap-blooper... Algo assim.

Mas no meu trabalho é exatamente o oposto. Certo (infelizmente, quase nunca na primeira vez) o modelo construído permite que você continue mais fácil e naturalmente (Quase) montar um diagrama.

Recentemente me deparei com um artigo no Habré sobre o trabalho árduo dos cientistas de dados...
Acontece que este momento é plenamente realizado para eles. E na minha prática, como dizem, “versão light”. Modelos de vários volumes, programadores experientes com OOP prontos, etc. - tudo isso aparecerá mais tarde, quando/se ele decolar. Mas o designer precisa começar em algum lugar aqui e agora.

Vá direto ao ponto. Tomei TOML como base sintática deste cidadão.

Porque ele (TOML) por um lado, editável por humanos. Por outro lado, é traduzido 1:1 em qualquer uma das sintaxes mais comuns: XML, JSON, YAML.
Além disso, a implementação que usei em “github.com/BurntSushi/toml”, embora não seja a mais moderna (ainda sintaxe 1.4), é sintaticamente compatível com o mesmo JSON (“integrado”).

Ou seja, se desejar, você pode simplesmente dizer “vá pelo mato com aquele seu TOML, quero XXX” e “corrija” o código com apenas uma linha.

Assim, se você quiser escrever algumas janelas para configurar o xswitcher (Eu não tenho certeza) Nenhum problema é esperado “com essa sua maldita configuração”.

Para todos os outros, a sintaxe é baseada em “chave = valor” (e literalmente algumas opções mais complicadas, como = [some, that, array]) eu acho
intuitivamente conveniente.
O que é interessante é que "queimado" na mesma época (por volta de 2013). Só que, ao contrário de mim, o autor do TOML entrou na escala adequada.

Portanto, agora é mais fácil para mim ajustar sua implementação para me adequar, e não vice-versa.

Em geral, usamos TOML (muito semelhante ao antigo Windows INI). E temos uma configuração na qual descrevemos como anexar uma série de ganchos dependendo do conjunto dos códigos de leitura mais recentes do teclado. Abaixo, peça por peça, está o que aconteceu até agora. E uma explicação de por que decidi assim.

0. Abstrações básicas

  • Digitalize designações de código. Definitivamente, algo precisa ser feito sobre isso, uma vez que códigos simplesmente digitais não são absolutamente legíveis por humanos (sou só eu loloswitcher).
    Sacudi “ecodes.go” de “golang-evdev” (eu estava com preguiça de olhar a fonte original, embora o autor a tenha indicado de forma bastante cultural). Corrigi um pouco (por enquanto) algo que era bastante assustador. Como “LEFTBRACE” → “L_BRACE”.
  • Além disso, ele introduziu o conceito de “chaves de estado”. Já a gramática regular utilizada não permite passagens longas. (Mas permite verificar com sobrecarga mínima. Se você usar apenas gravação “direta”.)
  • Haverá um “desduplicador” integrado do que é pressionado. Assim, o estado "repeat"=2 será escrito um vezes

1. Seção de modelos

[Templates] # "@name@" to simplify expressions
 # Words can consist of these chars (regex)
 "WORD" = "([0-9A-Z`;']|[LR]_BRACE|COMMA|DOT|SLASH|KP[0-9])"

Em que consiste uma palavra da linguagem humana com notação fonética? (seja uma questão de grafemas, também conhecidos como “hieróglifos”)? Algum tipo de “lençol” terrível. Portanto, apresento imediatamente o conceito de “modelo”.

2. O que fazer quando algo é clicado (outro código de verificação chegou)

[ActionKeys]
 # Collect key and do the test for command sequence
 # !!! Repeat codes (code=2) must be collected once per key!
 Add = ["1..0", "=", "BS", "Q..]", "L_CTRL..CAPS", "N_LOCK", "S_LOCK",
        "KP7..KPDOT", "R_CTRL", "KPSLASH", "R_ALT", "KPEQUAL..PAUSE",
        "KPCOMMA", "L_META..COMPOSE", "KPLEFTPAREN", "KPRIGHTPAREN"]

 # Drop all collected keys, including this.  This is default action.
 Drop = ["ESC", "-", "TAB", "ENTER", "KPENTER", "LINEFEED..POWER"]
 # Store extra map for these keys, when any is in "down" state.
 # State is checked via "OFF:"|"ON:" conditions in action.
 # (Also, state of these keys must persist between buffer drops.)
 # ??? How to deal with CAPS and "LOCK"-keys ???
 StateKeys = ["L_CTRL", "L_SHIFT", "L_ALT", "L_META", "CAPS", "N_LOCK", "S_LOCK",
              "R_CTRL", "R_SHIFT", "R_ALT", "R_META"]

 # Test only, but don't collect.
 # E.g., I use F12 instead of BREAK on dumb laptops whith shitty keyboards (new ThinkPads)
 Test = ["F1..F10", "ZENKAKUHANKAKU", "102ND", "F11", "F12",
          "RO..KPJPCOMMA", "SYSRQ", "SCALE", "HANGEUL..YEN",
          "STOP..SCROLLDOWN", "NEW..MAX"]

Existem 768 códigos no total. (Mas “por precaução” inseri a captura de “surpresas” no código xswitcher).
Dentro, descrevi o preenchimento do array com links para funções “o que fazer”. Em golang isso é (De repente) Acabou sendo conveniente e óbvio.

  • Pretendo reduzir o “Drop” ao mínimo neste lugar. A favor de um processamento mais flexível (mostrarei abaixo).

3. Tabela com classes de janela

# Some behaviour can depend on application currently doing the input.
[[WindowClasses]]
 # VNC, VirtualBox, qemu etc. emulates there input independently, so never intercept.
 # With the exception of some stupid VNC clients, which does high-level (layout-based) keyboard input.
 Regex = "^VirtualBox"
 Actions = "" # Do nothing while focus stays in VirtualBox

[[WindowClasses]]
 Regex = "^konsole"
 # In general, mouse clicks leads to unpredictable (at the low-level where xswitcher resides) cursor jumps.
 # So, it's good choise to drop all buffers after click.
 # But some windows, e.g. terminals, can stay out of this problem.
 MouseClickDrops = 0
 Actions = "Actions"

[[WindowClasses]] # Default behaviour: no Regex (or wildcard like ".")
 MouseClickDrops = 1
 Actions = "Actions"

As linhas da tabela estão entre colchetes duplos com seu nome. Não poderia ter sido mais fácil logo de cara. Dependendo da janela atualmente ativa, você pode selecionar as seguintes opções:

  • Seu próprio conjunto de “teclas de atalho” “Ações =…”. Se não/vazio, não faça nada.
  • Alternar “MouseClickDrops” - o que fazer quando um clique do mouse é detectado. Como no ponto em que o xswitcher está ativado não há detalhes sobre “onde eles clicam”, redefinimos o buffer por padrão. Mas em terminais (por exemplo) você não precisa fazer isso (geralmente).

4. Uma (ou várias) sequências de cliques acionam um ou outro gancho

# action = [ regex1, regex2, ... ]
# "CLEAN" state: all keys are released
[Actions]
# Inverse regex is hard to understand, so extract negation to external condition.
# Expresions will be checked in direct order, one-by-one. Condition succceds when ALL results are True.
 # Maximum key sequence length, extra keys will be dropped. More length - more CPU.
 SeqLength = 8
 # Drop word buffer and start collecting new one
 NewWord = [ "OFF:(CTRL|ALT|META)  SEQ:(((BACK)?SPACE|[LR]_SHIFT):[01],)*(@WORD@:1)", # "@WORD@:0" then collects the char
             "SEQ:(@WORD@:2,@WORD@:0)", # Drop repeated char at all: unlikely it needs correction
             "SEQ:((KP)?MINUS|(KP)?ENTER|ESC|TAB)" ] # Be more flexible: chars line "-" can start new word, but must not completelly invalidate buffer!
 # Drop all buffers
 NewSentence = [ "SEQ:(ENTER:0)" ]

 # Single char must be deleted by single BS, so there is need in compose sequence detector.
 Compose = [ "OFF:(CTRL|L_ALT|META|SHIFT)  SEQ:(R_ALT:1,(R_ALT:2,)?(,@WORD@:1,@WORD@:0){2},R_ALT:0)" ]

 "Action.RetypeWord" = [ "OFF:(CTRL|ALT|META|SHIFT)  SEQ:(PAUSE:0)" ]
 "Action.CyclicSwitch" = [ "OFF:(R_CTRL|ALT|META|SHIFT)  SEQ:(L_CTRL:1,L_CTRL:0)" ] # Single short LEFT CONTROL
 "Action.Respawn" = [ "OFF:(CTRL|ALT|META|SHIFT)  SEQ:(S_LOCK:2,S_LOCK:0)" ] # Long-pressed SCROLL LOCK

 "Action.Layout0" = [ "OFF:(CTRL|ALT|META|R_SHIFT)  SEQ:(L_SHIFT:1,L_SHIFT:0)" ] # Single short LEFT SHIFT
 "Action.Layout1" = [ "OFF:(CTRL|ALT|META|L_SHIFT)  SEQ:(R_SHIFT:1,R_SHIFT:0)" ] # Single short RIGHT SHIFT

 "Action.Hook1" = [ "OFF:(CTRL|R_ALT|META|SHIFT)  SEQ:(L_ALT:1,L_ALT:0)" ]

Os ganchos são divididos em dois tipos. Integrado, com nomes autoexplicativos (NewWord, NewSentence, Compose) e programável.

Os nomes programáveis ​​começam com “Ação”. Porque TOML v1.4, nomes com pontos devem estar entre aspas.

Cada seção deve ser descrita abaixo com o mesmo nome.

Para não impressionar as pessoas com frequentadores regulares “nus” (por experiência própria, seus escrevertalvez um em cada dez profissionais), implemento imediatamente sintaxe adicional.

  • "DESLIGADO:" (ou "LIGADO:") antes de regexp (expressão regular) exigem que os seguintes botões sejam liberados (ou pressionados).
    A seguir, farei uma expressão regular “injusta”. Com verificação separada de peças entre tubos "|". Para reduzir o número de registros como "[LR]_SHIFT" (onde isso claramente não é necessário).
  • "SEQ:" Se a condição anterior for atendida (ou ausente), então verificamos uma expressão regular “normal”. Para obter detalhes, envio imediatamente para ^W a biblioteca “regexp”. Porque ainda não me preocupei em descobrir o grau de compatibilidade com meu pcre favorito (“compatível com Perl”).
  • A expressão é escrita na forma "BUTTON_1: CÓDIGO1, BOTÃO_2: CÓDIGO2" etc., na ordem em que os códigos de digitalização são recebidos.
  • A verificação é sempre “ajustada” ao final da sequência, portanto não há necessidade de adicionar “$” ao final.
  • Todas as verificações em uma linha são realizadas uma após a outra e são combinados por “I”. Mas como o valor é descrito como uma matriz, você pode escrever uma verificação alternativa após a vírgula. Se isso for necessário por algum motivo.
  • Valor "SeqComprimento = 8" limita o tamanho do buffer no qual todas as verificações são executadas. Porque Nunca encontrei (até agora) recursos infinitos em minha vida.

5. Configuração dos ganchos descritos na seção anterior

# Action is the array, so actions could be chained (m.b., infinitely... Have I to check this?).
# For each action type, extra named parameters could be collected. Invalid parameters will be ignored(?).
[Action.RetypeWord] # Switch layout, drop last word and type it again
 Action = [ "Action.CyclicSwitch", "RetypeWord" ] # Call Switch() between layouts tuned below, then RetypeWord()

[Action.CyclicSwitch] # Cyclic layout switching
 Action = [ "Switch" ] # Internal layout switcher func
 Layouts = [0, 1]

[Action.Layout0] # Direct layout selection
 Action = [ "Layout" ] # Internal layout selection func
 Layout = 0

[Action.Layout1] # Direct layout selection
 Action = [ "Layout" ] # Internal layout selection func
 Layout = 1

[Action.Respawn] # Completely respawn xswitcher. Reload config as well
 Action = [ "Respawn" ]

[Action.Hook1] # Run external commands
  Action = [ "Exec" ]
  Exec = "/path/to/exec -a -b --key_x"
  Wait = 1
  SendBuffer = "Word" # External hook can process collected buffer by it's own means.

O principal aqui é "Ação = [Matriz]". Semelhante à seção anterior, há um conjunto limitado de ações integradas. E a possibilidade de acoplamento não é limitada em princípio (escreva “Action.XXX” e não tenha preguiça de escrever outra seção para ele).
Em particular, a redigitação de uma palavra no layout corrigido é dividida em duas partes: “altere o layout conforme especificado lá” и “redigitar” (“RetypeWord”).

Os parâmetros restantes são gravados no “dicionário” ("mapa" em golang) para uma determinada ação, sua lista depende do que está escrito em “Ação”.

Várias ações diferentes podem ser descritas em um heap (Seções). Ou você pode separá-lo. Como mostrei acima.

Eu imediatamente configurei a ação “Exec” para executar o script externo. Com a opção de enviar o buffer gravado para stdin.

  • “Wait = 1” – aguarde a conclusão do processo em execução.
  • Provavelmente, “para a pilha” você desejará colocar mais pessoas no ambiente. informações como o nome da classe de janela da qual foi interceptado.
    “Você quer conectar seu manipulador? É aqui que você precisa ir.

Ufa (expirado). Parece que não esqueci nada.

Ops! Sim, eu não esqueci...
Onde está a configuração de lançamento? Em código rígido? Assim:

[ScanDevices]
 # Must exist on start. Self-respawn in case it is younger then 30s
 Test = "/dev/input/event0"
 Respawn = 30
 # Search mask
 Search = "/dev/input/event*"
 # In my thinkPads there are such a pseudo-keyboards whith tons of unnecessary events
 Bypass = "(?i)Video|Camera" # "(?i)" obviously differs from "classic" pcre's.

Onde eu esqueci/cometi um erro? (de jeito nenhum sem isso), Eu realmente espero que os leitores atentos não tenham preguiça de cutucar o nariz.

Boa sorte!

Fonte: habr.com

Adicionar um comentário