Teclado personalizado com símbolos especiais

Eu sempre tive vontade de digitar símbolos que não estão no teclado normal, e sempre fico pensando como os símbolos existentes foram escolhidos. Por que o teclado brasileiro (ABNT ou ABNT2 — a diferença ninguém conhece) precisa de ¬? Pra mim só serve para escrever o emoticon ¬_¬. Tive que pesquisar para descobrir que ele é uma das maneiras de escrever o operador lógico “não”. Não seria muito mais útil ter travessão, aspas curvas, e flechinhas como ←→? Por que temos = e não ≠, ÷, ×? O símbolo do Euro € também faz falta desde o começo dos anos 2000.

Outra vontade minha era de poder escrever em diversas línguas do mundo sem ficar limitado aos acentos de português, espanhol, alemão e francês. Quero poder escrever o nome daquele músico com å no nome, ou colocar acentos em consoantes como fazem as línguas do leste europeu: ŕ, ś, ź, č. E, é claro, escrever em Esperanto com ĉ ĝ ĥ ĵ ŝ ŭ.

Antes do Unicode se espalhar por tudo, até dava para entender que o teclado ficaria só no ASCII e Latin-1 (e mesmo assim, um subconjunto disso). Mas agora com Unicode, só falta um jeito fácil de digitar tudo isso, sem precisar de mapa de caracteres!

Então quando descobri o Microsoft Keyboard Layout Creator, fui logo criar o meu próprio layout cheio de símbolos adicionais! Já uso há vários anos esse layout que eu criei e finalmente resolvi escrever um post no blog sobre ele.

Eu o chamei de “Teclado Português Brasileiro Multilíngüe”. Primeiro, o link para download está aqui:

https://bitbucket.org/marcuscf/configs/downloads/

Basta baixar o zip, descompactá-lo, e executar o setup.exe que está lá. Depois, selecione-o como seu layout padrão e pronto!

Segundo, uma visão geral de tudo que está disponível no teclado usando a tecla AltGr e  combinando acentos diversos. Clica ali e dá uma olhada em tudo que dá pra escrever sem o mapa de caracteres:

https://bitbucket.org/marcuscf/configs/src/master/ms_keyboard_layout_creator/README.md?fileviewer=file-view-default

Experimente colocar acentos em consoantes e teste combinações diversas de letras e símbolos com AltGr e AltGr+Shift.

Um dos principais motivos para eu demorar a divulgar o teclado era que eu não tinha uma referência completa atualizada. Agora com esse arquivo de instruções não falta mais nada.

No mesmo repositório Git acima, se vocês procurarem bem, encontrarão algumas configurações para o XKB, no Linux. Mas elas são bem mais simples, alterando uma meia dúzia de teclas próximas dos dígitos 9, 0, e Backspace para ficarem semelhantes ao que eu uso no Windows.

Mesmo eu usando mais (X)Ubuntu do que Windows no computador de casa, achei o layout padrão do Linux bem mais rico que o do Windows então só fiz alguns ajustes na posição das aspas curvas, adicionei travessão —, meia-risca –, diferente ≠, e coloquei os símbolos ÷× no teclado numérico nas combinações AltGr+/ e AltGr+* (isso eu queria ter feito no Windows também mas não consegui com o MS Keyboard Layout Creator).

O próximo passo talvez seja ampliar e documentar a configuração para Linux… Sabe-se lá quando. Enquanto isso, eu não digito mais sem meu layout multilíngüe. Por mim, podia até se tornar o novo padrão brasileiro :-)

 

Anúncios

Clonando partições (3)

Não sei por que ainda me presto pra isso (dá uma trabalheira até consertar tudo!), mas me aventurei de novo no mundo da clonagem de discos e configuração de boot. Nisso me lembrei de algumas dicas que esqueci de colocar nos posts anteriores.

Identificar todos os discos e partições

No Linux, use o comando

sudo blkid

para ver todas as suas partições. É muito útil para detectar partições clonadas: os IDs vão estar repetidos e isso pode dar uma grande confusão em diversos sistemas operacionais e durante o boot. Deve ser por isso que a maioria dos tutoriais sugere nunca conectar o disco antigo e o novo ao mesmo tempo, pelo menos não antes de formatar (o que muda os IDs) o antigo quando o novo já estiver funcionando.

Isso também é útil para se achar nas diversas formas de identificar os discos e partições. Em alguns casos estamos acostumados com /dev/sda1, mas essa identificação pode mudar dependendo de onde você conectar cada disco (e vai ser completamente diferente no Grub), então você pode ver o UUID (utilizado mais seguidamente no Linux) e o PARTUUID (utilizado mais seguidamente no Windows).

Nota rápida 1: para alterar esses IDs, use os comandos tune2fs (com a opção -U) e ntfslabel (com a opção -​-new-serial). Consulte o manual.

Atualizar configurações de boot

No Linux, é indispensável usar

sudo update-grub

Esse comando é conhecido e bem básico, mas menciono aqui para ficar mais completo.

Um problema que eu tive é que não adianta nada o Grub estar atualizado apontando para o novo Windows se dentro da partição do Windows novo (no tal de BCD) ainda há ligações para a instalação antiga no outro disco. O processo de boot vira um ping-pong e você vai parar na instalação antiga. Talvez software mais especializado tome conta disso, mas o que eu usei não me ajudou.

Foi aí que aprendi a usar o bcdedit. Vou dar alguns exemplos.

Para listar sistemas operacionais configurados para boot, digite simplesmente:

bcdedit

na linha de comando, ou

bcdedit /enum ACTIVE

Para listar a mesma coisa, mas mostrando IDs completos em vez de palavras fáceis como {default}, {current}, {bootmgr}, use:

bcdedit /v

ou

bcdedit /enum ACTIVE /v

Listar tudo que estiver cadastrado no BCD (pode ser com /v ou sem /v):

bcdedit /enum ALL

Listar os registros de outra partição, que não é a atual:

bcdedit /store "E:\Boot\BCD"

Essas opção /store é útil para acessar as configurações de um Windows que não é aquele que está executando.

Copiar uma entrada de sistema operacional para editá-la depois:

bcdedit /store "E:\Boot\BCD" /copy {... o id de origem desejado ...} /d "Windows 10 SSD"

O ID gerado será exibido na tela e você poderá editá-lo com:

bcdedit /store "E:\Boot\BCD" /set {... o novo id gerado ...} device partition=E:
bcdedit /store "E:\Boot\BCD" /set {... o novo id gerado ...} osdevice partition=E:

Combinando os comandos acima e lendo com atenção a ajuda do bcdedit (ex.: bcdedit /? ou bcdedit /? /set) você já pode alterar o que quiser para apontar para as novas partições. Combinando com o Grub atualizado e com as partições sem IDs repetidos, dá para configurar seu boot de vários jeitos.

Nota rápida 2: também é muito útil aprender a usar a função Load Hive do regedit para editar o registro de um Windows de outra partição. Assim você pode trocar as letras C: e E: uma pela outra, por exemplo. Existem várias explicações de como fazer, uma delas está aqui: https://superuser.com/questions/1167523/installed-a-new-ssd-windows-still-boots-from-the-old-one

Primeiros passos com Rust

Introdução

A linguagem Rust (https://www.rust-lang.org) é uma linguagem de programação que traz umas ideias novas bem interessantes para tapar o abismo que existe entre o C e C++ e (quase) todas as outras linguagens populares, combinando alto desempenho e abstrações leves com segurança para que uma operação errada não corrompa a memória.

Só isso já seria interessante depois de mais de uma década de interpretadores, runtimesvirtual machines, JITs e tal. Mas o mais interessante é a seu conceito de empréstimo de ponteiros (os tais de borrows).

Depois que declararam a linguagem estável na versão 1.0 em 2015, achei que valeria a pena experimentar um pouco com ela. Resolvi fazer uma tradução do tutorial de Scheme daqui https://elmord.org/blog?entry=20160414-tour-de-scheme-2 para Rust. Um código simples é melhor para começar, porque o PdfPageCount que eu usava para comparar linguagens é meio assustador para iniciar numa linguagem totalmente nova.

O código está disponível em https://bitbucket.org/marcuscf/playground na pasta rust/agenda. Ele não é de forma alguma uma transcrição exata da versão Scheme: mudei o formato de arquivo para algo que eu pudesse manipular em poucas linhas de código, fundi funções que eu achei muito curtas (parece ser tradição em Scheme) e naturalmente a estrutura de dados usada não foi uma lista encadeada (outra tradição de Lisp, Scheme e demais linguagens funcionais, mas não é bem a coisa mais eficiente do mundo…). Aviso que não pensei muito na segurança na leitura de dados mal formatados. É só um teste da linguagem, não um programa super-robusto para controlar aviões!

Análise

Comecei da mesma forma que na versão Scheme, com funções de leitura do teclado e o menu principal. Até aí sem muitos problemas.

A primeira supresa veio na declaração da variável global “contatos”. Com let, erro. Vamos olhar o manual: para globais temos staticconst. Vai ser static então. Erro. Não pode ter static com destrutores (trait chamada Drop), não pode chamar funções complicadas na inicialização (pelo menos não na versão atual, segundo a mensagem de erro) e se declarar como mutável (mut), caímos na parte unsafe da linguagem. Até que está certo, uma variável global sendo alterada por múltiplas funções e múltiplas threads não é muito seguro. E Rust se esforça para garantir várias seguranças em relação a acesso concorrente em tempo de compilação!

Como variável global não é boa prática há umas boas décadas em qualquer linguagem de programação, não me importei em criar todas as funções recebendo um parâmetro do tipo &mut Vec e deixando a variável dentro da função main mesmo. Mas me importei, sim, em não saber como declarar algo que seja visível em todas as funções.

Por isso explorei a possibilidade de usar uma variável local da thread. Como o programa não seria multithreaded, seria o mesmo efeito de uma global. O modo como a variável deve ser obtida é interessante: com o método .with(), você passa uma função e somente dentro dessa função você poderá usar a variável. No fim, fiz isso num só local para testar como funcionava e no resto do programa continuei com a passagem de parâmetros como já estava fazendo antes.

A mutabilidade do Vec global eu consegui com a struct RefCell, porque a macro thread_local! não permite adicionar “mut” depois do static (e mesmo se permitisse, poderia haver outros problemas, como a exigência de blocos unsafe{}). No fim, a declaração ficou assim:

thread_local!(static CONTATOS: RefCell<Vec> = RefCell::new(vec![]));

(Caramba!)

e o uso (dentro de uma função) ficou assim:

CONTATOS.with(|contatos| { // ou |contatos: &RefCell|
    let contatos_mut = &mut *contatos.borrow_mut(); // o asterisco é opcional
    // (...)
});

(Caramba!)

Entendeu agora por que eu preferi continuar passando parâmetros em vez de fazer cada função obter a variável thread_local? Mas não deixei de usar a thread_local porque queria guardar um exemplo de forma de uso.

É, digamos que cada coisinha é bem explícita: a mutabilidade, os ponteiros, e cada vez que pegamos ponteiros emprestados (borrow). O asterisco ao fazer o borrow_mut() é opcional porque o resultado dessa função tem Deref automático. Uma das poucas coisas automáticas com ponteiros são a trait Deref e a chamada de método com o ponto. O resto todo é feito manualmente. À primeira vista me pareceu um design adequado: poucas coisas implícitas mas com alguns atalhos para os casos mais comuns.

As mensagens de erro do compilador são bem informativas. Apesar de não poderem exatamente ensinar a linguagem, elas informam muito bem o que o compilador estava esperando (um tipo, por exemplo) e onde o seu programa começou a diferir do esperado. Muitas vezes as mensagens são acompanhadas de dicas como “Quem sabe faltou importar o nome da trait” ou “Use tal sintaxe para solucionar esse tipo de situação”. Bem melhor que outros compiladores que só dizem algo como “Identificador desconhecido” ou “Tem algo errado mais ou menos aqui”.

A segunda surpresa foi o borrow checker, que é menos refinado do que eu pensava. Se você escreve uma chamada simples como

umaString.truncate(umaString.len() - 1)

ele já dá erro dizendo que o borrow mutável do truncate() não pode ser feito ao mesmo tempo que o borrow imutável do .len(). O mais lógico é que o len() fosse chamado e devolvesse o ponteiro para poder ser usado no truncate(), mas a implementação atual não leva isso em consideração. Para resolver, tem que chamar o len() antes, salvar o resultado numa variável e na instrução seguinte chamar o truncate().

No fim descobri que eu queria mesmo era chamar trim(), só não tinha encontrado o método na documentação até então. Até porque as linhas lidas pelo console no Windows estavam vindo com “\r\n” (e não só “\n”), então tirar o último caracter não era suficiente. Usei o trim() e era isso.

Se o exemplo acima pode ser solucionado com uma variável a mais, descobri diversas outras situações onde acontecia o contrário: a linguagem não permitia criar uma variável temporária, porque ela impedia de continuar usando a original até a temporária sair de escopo. Um exemplo disso está na função remover_contato(), onde eu queria fazer contatos[i].nome em passos separados (primeiro obter contatos[i], guardar numa variável, e depois obter o nome), mas para isso precisei criar um bloco na condição do if:

fn remover_contato(contatos: &mut Vec) {
    let nome_procurado = le_string("Nome a remover: ");
    for i in 0 .. contatos.len() { // não usei o for simples por causa da remoção
        if { // este bloco{} é necessário só por causa do escopo das variáveis
            let contato = &contatos[i];
            let nome_tmp = &contato.nome;
            nome_tmp == &nome_procurado
        } {
            contatos.swap_remove(i);
            return;
        }
    }
}

O bloco na condição do if serve para delimitar o escopo de contato e nome_tmp. Se eu não o utilizasse essas variáveis viveriam por todo o bloco do loop e eu não poderia fazer o swap_remove mais abaixo. Claro que neste caso não é nenhum problema escrever contato[i].nome numa expressão só, mas eu queria separar os pedaços para entender melhor a linguagem, e para casos onde essa separação de tarefas viesse a ser realmente necessária. Note também que a cada variável temporária usada aparecem uns &, pois temos que guardar os valores temporários por ponteiro, porque o default da linguagem é mover os valores, inutilizando a variável anterior. Esse default é útil para returns, e deve haver alguns outros casos também, já que li superficialmente na lista de discussão que moves explícitos tinham sido experimentados e deixavam a linguagem mais verbosa, com “move” pra tudo quanto é lado.

Uma curiosidade é que justamente enquanto testava esses casos, apareciam artigos na internet sobre esse mesmíssimo problema de borrows e como estão planejando solucioná-lo. Vejam estes links:

http://smallcultfollowing.com/babysteps/blog/2016/04/27/non-lexical-lifetimes-introduction/

http://smallcultfollowing.com/babysteps/blog/2016/05/04/non-lexical-lifetimes-based-on-liveness/

http://blog.rust-lang.org/2016/04/19/MIR.html

Conclusão

Neste pequeno programa pude conhecer apenas um pedaço da linguagem, mas já é muito mais conhecimento do que poderia obter apenas lendo sobre ela. Em geral achei a linguagem bastante amigável para sua categoria. Não é fácil como passar de Ruby para Python ou de C# para Java. Precisa voltar a pensar como em C++, com diversos tipos de referências, ponteiros, ponteiros para ponteiros e valores imediatos (quero dizer, aqueles tipos de objetos que ficam direto na pilha de chamadas ou são inseridos diretamente na struct em que estão contidos). Mas mesmo assim deve ser bem menos trabalhoso do que aprender as melhores práticas de como retornar strings de tamanho desconhecido em C sem estourar buffers nem deixar vazar memória.

CSS

Depois de um tempo sempre caindo em sites com explicações ruins sobre CSS e sempre esquecendo tudo minutos depois (explicações ruins não se fixam na minha memória), encontrei um ou dois sites quases bons (perdi os links, mas um deles deve ser este: http://css-tricks.com/almanac/
properties/d/display/
) e decidi colecionar minhas próprias explicações, para não precisar mais me contentar com explicações mais ou menos dali em diante.

O resumo abaixo não passou por um controle de qualidade completo™ (isto é, algumas coisas eu testei e outras não), mas deve ser razoavelmente útil, nem que seja para mim mesmo. Foquei este tira-dúvidas em 3 propriedades: display, position, e white-space.

display:

block

  1. Sempre começa numa nova linha.
  2. Dá pra manipular height, line-height e as margens top e bottom.
  3. A largura (width) default é 100% da largura da página, mas dá pra especificar outro valor.

Exemplos em que é o default: <div>, <p>, <h1>, <form>, <ul>, <ol>

inline

  1. Começa na mesma linha.
  2. Não dá pra manipular height, line-height nem as margens top e bottom, essas propriedades serão ignoradas (ver inline-block abaixo para a solução).
  3. A largura (width) é o tamanho do texto ou imagem contida no elemento e não é possível mudar.

Exemplos em que é o default: <span>, <a>, <label>, <strong>, <em>, <b>, <i>.

inline-block

  1. Começam na mesma linha
  2. É possível manipular height, width, etc.

Exemplos em que é o default: <button>, <select>. Há algumas divergências quanto a <img>, <input>, <textarea>, se eles devem ser inline-block ou apenas inline por default. Ver: http://stackoverflow.com/questions/21614938/html-element-which-defaults-to-displayinline-block

position:

static

É o default.

  1. A posição depende do fluxo normal dos elementos da página: seguindo a ordem de leitura de texto, com elementos block começando novas linhas, etc.
  2. Posicionamento com CSS com top, left, right, e bottom será ignorado.
  3. A única situação onde seria necessário usar position: static seria para remover outro posicionamento que foi adicionado em outro lugar. Isso ocorre raramente.

absolute

  1. Removido do fluxo normal da página. Não afeta a posição dos outros elementos e não é afetado por eles.
  2. Posicionado nas coordenadas especificadas (por exemplo, com top e left).
  3. As coordenadas usadas no posicionamento são a posição absoluta dentro do elemento contêiner.
  4. O elemento contêiner pode ser a página como um todo ou o primeiro elemento circundante que especifique  position: relative.

relative

  1. Aceita ser posicionado com top, left, etc.
  2. As coordenadas usadas no posicionamento são relativas à posição onde o elemento estaria no fluxo normal da página. Ou seja, é possível fazer coisas como: posição_normal + 10, posição_normal – 5, etc. apenas especificando relative e 10px, -5px, etc.
  3. Usar position: relative e não setar uma posição não vai alterar a localização do elemento, mas vai permitir duas coisas:
    1. usar z-index
    2. servir como elemento contêiner de elementos com position: absolute.

fixed

  1. Semelhante ao absolute.
  2. Não se move quando é usada a barra de rolagem.

white-space:

normal

  1. Seqüências de espaços viram um espaço só.
  2. Quebras de linha só quando necessário (quando o texto atinge a margem), mesmo que o fonte tenha suas próprias quebras.
  3. Quebras de linha nos <br>
  4. É o default.

nowrap

  1. Seqüências de espaços viram um espaço só.
  2. Não adiciona quebras de linha mesmo que necessário (gera linhas mais longas que a tela).
  3. Quebras de linha nos <br>.

pre

  1. Seqüências de espaços são preservadas.
  2. Não adiciona quebras quando necessário.
  3. Quebras de linha onde existirem no fonte.
  4. Quebras de linha nos <br>.
  5. Age como a tag <pre>.

pre-line

  1. Seqüências de espaços viram um espaço só.
  2. Quebras de linha quando necessário.
  3. Quebras de linha onde existirem no fonte
  4. Quebras de linha nos <br>.

pre-wrap

  1. Seqüências de espaços são preservadas.
  2. Quebras de linha quando necessário.
  3. Quebras de linha onde existirem no fonte.
  4. Quebras de linha nos <br>.

(aliás, o CSS fez aniversário de 20 anos recentemente)

Pessoas e seus algoritmos

O tímido

ambiente = getAmbiente()
fala = escolherFala(ambiente)
if fala is not None:
    falar(fala)
exit()

O extrovertido

fala = random()
while True:
    try:
        falar(fala)
    catch AmbienteException as ex:
        ambiente = ex.getAmbiente()
        fala = escolherFala(ambiente)
    else:
        fala = random()

O chato

while True:
    fala = random()
    try:
        falar(fala)
    catch AmbienteException as ex:
        pass # ignorar exceções e continuar tagarelando

Inglês é engraçado…

Peguei meu caderno, conectei um rato nele e virei sobre a máquina. Eu entendo um pouco de artigos duros, mas atualmente trabalho mais criando artigos moles. Aprendi a programar em Básico, mas agora eu programo em Montagem e Língua presa. Ainda bem que nunca precisei programar em Caxumba. Para compilar meus artigos moles eu dou alguns comandos na concha. Meu programa está correndo corretamente, mas agora eu preciso de um motorista para interpretar a entrada do bastão da alegria num jogo que estou fazendo. O motorista deve, de preferência, consumir pouca memória carneiro para não afetar o desempenho do jogo.

É assim que soa uma conversa sobre informática para um falante nativo de inglês?

Confissões de programador

Eu reformato o código dos outros para ler e “entender melhor”. Também renomeio variáveis.

E quando eu renomeio as variáveis para algo mais legível (principalmente variáveis booleanas num if), fico pensando “eu sou um gênio”. Ex.:
if(executouActionComSucesso) { avancarTela(); } é bem melhor do que
if(avancarProximaTela) { avancarTela(); }

Se eu escrevi algo conciso e inteligente, o código é expressivo. Se foi outra pessoa que escreveu, é ilegível.

Se eu escrevi código bagunçado, é porque preferi evitar uma solução over-engineered. Mas eu deixo um comentário assumindo a culpa.

A primeira vez que eu vejo um código de outra pessoa, logo penso é “WTF!!??”. Aí eu refatoro e vejo que minha solução não funciona. Então eu reverto minhas alterações.

Por isso eu acho que um dos comentários mais úteis do mundo é: “Eu tentei do jeito simples e não funcionou por isso, isso e aquilo. Então tive que fazer desse jeito mesmo.”

Uma vez quando eu fiz um switch que não precisava de um break num dos casos, eu pensei: “Uau, finalmente um bom caso para usar esse fall through do switch”. Aí eu percebi que não estava tratando uma situação, e não dava mais para usar o fall through. Revertendo… Isso me lembra o que o Douglas Crockford fala do fall through — simplesmente evite. Certamente se eu fizesse uma linguagem de programação, não copiaria do C essa necessidade de usar break. O caso mais comum deve ser o mais fácil de usar.