Dev

Corrigir os acentos no chat do Elvenar (macOS) com um userscript

No chat do Elvenar, no macOS, o acento aparece antes da letra (´a em vez de á). Explica a causa, como a confirmar na consola do browser e como instalar um userscript de menos de 50 linhas que a resolve.

No macOS, no browser, o chat do Elvenar mostra ´a em vez de á. Em todos os outros separadores o teclado funciona: só o jogo falha. A tecla do acento escreve o símbolo, a vogal seguinte escreve-se à parte, e nunca se obtém uma letra acentuada. Uma frase como olá como estás? sai ol´a como est´as?.

Este tutorial explica porque acontece, como confirmar o diagnóstico na consola do browser e como instalar um userscript de menos de 50 linhas que corrige o comportamento. O script só atua no campo de texto do chat do jogo.

Requisitos: Chrome no macOS com o layout de teclado Português, e a extensão Tampermonkey.

O sintoma

O comportamento reproduz-se sempre da mesma forma:

  1. carregar na tecla do acento;
  2. carregar numa vogal;
  3. o chat mostra o acento e a vogal separados, ´a, em vez do carácter composto á.

Não há maneira de acentuar uma letra no chat. O problema não está no teclado, nem na configuração do macOS, nem no browser: o mesmo teclado produz á em qualquer outra página.

Porque acontece

Teclas mortas e composição de texto

No macOS com layout Português, o acento é uma tecla morta: não escreve nada sozinha, espera pela letra seguinte e só então produz o carácter final. O browser trata esta sequência como uma composição de texto, o mesmo mecanismo que se usa para escrever em chinês ou japonês (IME, Input Method Editor). Numa página normal, a composição de ´ com a produz esta sequência de eventos, e o campo de texto fica com á.

O que o Elvenar faz de diferente

O Elvenar é feito em OpenFL e desenha tudo num <canvas>. Um canvas não recebe texto do teclado, por isso o jogo usa um único <input type="text"> escondido, dentro do elemento #openfl-content, e lê dele o que o jogador escreve.

O jogo limpa o valor desse <input> a cada evento input. Para uma tecla normal isto é inofensivo. Para uma composição, é fatal: a composição tem estado interno no browser, e alterar o valor do campo a meio dela cancela-a.

A sequência observada ao carregar no acento e depois no a é:

keydown  Dead
compositionstart
compositionupdate  ´
input    insertCompositionText  ´
keydown  a
input    insertText  a

O que falta nesta sequência é revelador: não há compositionend, e o carácter composto á nunca chega a existir. O evento input com o ´ faz o jogo limpar o campo, a composição é cancelada, e o a seguinte entra como uma tecla normal (insertText). O ´ já tinha sido lido pelo jogo, daí o resultado ´a.

As teclas geradas por script, por exemplo por ferramentas de automação, não passam pelo IME do macOS, por isso não reproduzem o erro. Só um teclado real o revela.

Confirmar o diagnóstico

Quem quiser ver a sequência no seu próprio browser pode registá-la na consola (Ferramentas de programador, separador Consola) com a página do jogo aberta:

['keydown', 'compositionstart', 'compositionupdate', 'compositionend', 'beforeinput', 'input']
  .forEach((tipo) => window.addEventListener(tipo, (e) => {
    console.log(tipo, e.key ?? '', e.data ?? '', e.inputType ?? '', e.isComposing);
  }, true));

Com o cursor no chat, carrega no acento e numa vogal. Se o diagnóstico estiver certo, aparece compositionstart e compositionupdate, nunca compositionend, e o input seguinte é um insertText simples com a vogal. Numa página sem o problema, a sequência termina com compositionend e o carácter composto.

A solução

A estratégia é impedir que o jogo veja a composição, para não a poder cancelar, e entregar-lhe só o resultado final. O script faz duas coisas, apenas no <input> do chat:

  1. Esconde do jogo os eventos de composição e os eventos de teclado e de entrada gerados durante ela. Sem esses eventos, o jogo não limpa o campo a meio, e o browser consegue terminar a composição e produzir o á.
  2. No compositionend, entrega ao jogo só o texto final, como se fosse uma inserção normal: escreve o valor no campo e dispara um evento input do tipo insertText.

Todo o resto da página fica intacto.

O script

O script também está disponível para instalação direta em elvenar-acentos.user.js. O código completo:

// ==UserScript==
// @name         Elvenar – acentos (teclas mortas)
// @namespace    https://fuzzalab.pt/
// @version      1.0
// @downloadURL  https://fuzzalab.pt/downloads/elvenar-acentos.user.js
// @updateURL    https://fuzzalab.pt/downloads/elvenar-acentos.user.js
// @description  Corrige acentos no chat do Elvenar: o jogo cancelava a composição da tecla morta.
// @match        https://*.elvenar.com/game*
// @run-at       document-start
// @grant        none
// ==/UserScript==
(() => {
  // O OpenFL usa um <input> escondido dentro de #openfl-content e limpa-lhe o valor a cada
  // evento 'input', o que cancela a composição (´ + a) a meio. Escondemos a composição do
  // jogo e, no fim, entregamos-lhe só o texto final (á) como uma inserção normal.
  const isChat = (t) => t && t.tagName === 'INPUT' && t.parentElement && t.parentElement.id === 'openfl-content';
  let composing = false;
  const on = (type, fn) => window.addEventListener(type, fn, true);

  on('compositionstart', (e) => {
    if (!isChat(e.target)) return;
    composing = true;
    e.stopImmediatePropagation();
  });
  on('compositionupdate', (e) => {
    if (isChat(e.target)) e.stopImmediatePropagation();
  });
  on('compositionend', (e) => {
    if (!isChat(e.target)) return;
    composing = false;
    e.stopImmediatePropagation();
    const text = e.data;
    if (!text) return;
    const input = e.target;
    input.value = text;
    input.dispatchEvent(new InputEvent('input', { data: text, inputType: 'insertText', bubbles: true }));
  });
  ['keydown', 'keypress', 'keyup', 'beforeinput', 'input'].forEach((type) =>
    on(type, (e) => {
      if (!isChat(e.target)) return;
      if (composing || e.isComposing || e.keyCode === 229 ||
          e.inputType === 'insertCompositionText' || e.inputType === 'insertFromComposition') {
        e.stopImmediatePropagation();
      }
    })
  );
})();

Como o código funciona

O cabeçalho

  • @match https://*.elvenar.com/game* restringe o script às páginas do jogo, em qualquer subdomínio. Fora delas não corre.
  • @run-at document-start faz o script correr antes de qualquer código da página. Isto importa porque, quando dois ouvintes estão registados no mesmo alvo e na mesma fase, o primeiro registado corre primeiro. Ao registar os ouvintes antes do jogo, o script garante que corre antes dos do jogo.
  • @grant none desativa o isolamento do Tampermonkey. O script corre no contexto da própria página, o que é necessário para os seus ouvintes interferirem com os do jogo.
  • @downloadURL e @updateURL permitem ao Tampermonkey instalar e atualizar o script a partir do endereço indicado.

A identificação do campo do chat

const isChat = (t) => t && t.tagName === 'INPUT' && t.parentElement && t.parentElement.id === 'openfl-content';

A função devolve verdadeiro só quando o alvo do evento é um <input> filho direto de #openfl-content, o campo escondido do OpenFL. Todos os ouvintes começam por ela, o que garante que nenhum outro campo da página é afetado.

Os ouvintes na fase de captura

const on = (type, fn) => window.addEventListener(type, fn, true);

O terceiro argumento true regista o ouvinte na fase de captura, em que o evento desce da janela até ao alvo. Registado em window, o ouvinte do script é o primeiro a ver cada evento, antes dos ouvintes do jogo no próprio campo. É isso que permite a stopImmediatePropagation() impedir que o jogo chegue a ver o evento.

O estado da composição

A variável composing fica verdadeira entre compositionstart e compositionend. Os eventos compositionstart e compositionupdate são sempre suprimidos para o campo do chat.

A entrega do texto final

No compositionend, o script lê o carácter composto em e.data (por exemplo á), escreve-o em input.value e dispara um InputEvent do tipo insertText. Para o jogo, é indistinguível de alguém ter escrito a letra normalmente.

Os eventos de teclado e de entrada

O último bloco suprime keydown, keypress, keyup, beforeinput e input quando fazem parte da composição. Reconhece-os por quatro sinais: o estado composing, e.isComposing, o keyCode 229 (o valor que os browsers atribuem a teclas processadas por um IME) e os tipos de entrada insertCompositionText e insertFromComposition. Os eventos de teclas normais continuam a chegar ao jogo sem alteração.

Como instalar

  1. Instala o Tampermonkey no Chrome.
  2. Em chrome://extensions, abre os Detalhes do Tampermonkey e liga Permitir scripts de utilizador. Se esse interruptor não existir, liga o Modo de programador no canto superior direito. Sem isto, as versões recentes do Chrome não correm o script, e não aparece nenhum erro.
  3. Abre elvenar-acentos.user.js. O Tampermonkey mostra o ecrã de instalação: carrega em Instalar. Em alternativa, cria um script novo no painel do Tampermonkey e cola o código acima.
  4. Recarrega o Elvenar.

Verificar que funciona

Com o chat aberto, escreve olá como estás?, e confirma que os acentos saem na posição certa. Vale a pena testar também as combinações menos óbvias: é, ã, ç, e o acento seguido de espaço ou de uma consoante.

Se o resultado ainda for ´a, confirma que o ícone do Tampermonkey mostra o script como ativo na página do jogo e que o interruptor da etapa 2 está ligado.

Compatibilidade

  • Testado: Chrome no macOS, com o layout de teclado Português. Confirmado com á, é, ã, ç, e com o acento seguido de espaço ou de uma consoante.
  • Não testado: Windows, Linux, outros layouts de teclado, Firefox e Safari.

Resolução de problemas

  • O script não faz nada e não há erros. É o caso típico do interruptor da etapa 2 desligado. Sem ele, o Chrome não executa userscripts.
  • O script deixou de funcionar depois de uma atualização do jogo. Se o jogo mudar o nome ou a posição do <input> escondido, isChat deixa de reconhecer o campo e o script fica inerte, sem estragar nada. A correção é ajustar essa função ao novo seletor.
  • Erros de CORS na consola. Não vêm deste script. Vêm de outras extensões que também correm na página.
  • Outro layout ou outro browser. Um registo de eventos (keydown, compositionupdate, input) com os acentos a falhar, obtido com o código da secção “Confirmar o diagnóstico”, permite perceber se a sequência é a mesma e ajustar o script.

Atualizar e remover

Como o cabeçalho tem @updateURL, o Tampermonkey verifica periodicamente se existe uma versão mais recente no endereço indicado e atualiza o script. Para o remover, apaga-o no painel do Tampermonkey, ou desativa o interruptor correspondente.

Licença: usa, copia e adapta à vontade.

Mais sobre Elvenar

Se jogas Elvenar, experimenta também o Elvenarchy, o meu site de estatísticas de Elvenar: rankings de jogadores e irmandades, resultados de torneio com histórico desde 2016, Pináculo, Aventuras e ferramentas como o planeador de torneio, para 50 mundos. Conto como e porque o criei em Elvenarchy: as estatísticas de Elvenar que faltavam.

Partilhar

Comentários 0

Ainda sem comentários — sê o primeiro.