Anda di halaman 1dari 6

Implementando um teclado virtual no Linux

http://sergioprado.org/2011/12/12/implementando-um-teclado-virtual-no-linux/ Em 12/12/2011, em Linguagem C, Linux embarcado, por Sergio Prado


A idia de escrever este artigo surgiu da necessidade de um projeto que trabalhei algumas semanas atrs. O objetivo era implementar um teclado virtual, de forma que um processo ou aplicao pudesse simular o pressionamento de uma tecla, sem que esta tecla tivesse sido realmente pressionada em um teclado fsico. At a tudo bem, qualquer biblioteca ou toolkit grfico decente (X11, DirectFB, Qt, etc) possui algum mecanismo para emular entrada de teclado. Mas o objetivo era ser independente de biblioteca grfica, e funcionar tambm em ambiente somente texto. Ou seja, precisavamos de algo implementado dentro do kernel. Mas no vamos colocar a carroa na frente dos bois! Antes de pensar na soluo, vamos pensar no problema PURA MGICA? No, no mgica. Mas uma tecla pressionada dentro do kernel do Linux passa por algumas camadas at voc v-la ecoando na tela do seu monitor. CAMADA 0: HARDWARE Tudo comea no hardware, claro. A controladora do teclado conectado ao seu PC varre e decodifica a matriz de teclas e cuida de detalhes como o controle de debouncing. Quando voc pressiona uma tecla, essa tecla transformada em um cdigo chamado de scancode. Cada tecla possui um scancode quando pressionada e outro quando liberada (o bit mais significativo setado). Por exemplo, a letra x emite o scancode 0x2d quando pressionada e 0xad quando liberada (no meu teclado USB). Se voc quiser, pode usar a ferramenta showkey para fazer o dump e exibir o scancode das teclas pressionadas no seu PC:
$ sudo showkey -s press any key (program terminates 10s after last keypress)... 0x2d 0xad

Portanto, para cada tecla pressionada, uma interrupo gerada para a CPU, e os scancodes so enviados via barramento de comunicao, dependendo do hardware do seu teclado (PS2, USB, etc). Logo aps, a rotina de tratamento de interrupo do teclado acionada, sendo responsvel por receber e tratar estes scancodes, conforme veremos a seguir. CAMADA 1: DEVICE DRIVER Aqui j estamos no kernel do Linux. Um device driver vai conversar com o hardware do seu teclado, receber e tratar os scancodes. E dependendo do hardware que voc esta usando (teclado USB, PS2, etc), um diferente device driver ser o responsvel por ler estes scancodes. Por exemplo, a implementao do teclado PS2 padro encontra-se nos fontes do kernel em drivers/input/keyboard/atkbd.c. Vai l dar uma olhada, eu espero! Aps ler os scancodes, o device driver ir convert-los em um outro cdigo chamado keycodes. Cuidado para no confundir! Scancode um cdigo dependente do hardware do teclado e tratado pelo device driver. Keycode um cdigo que representa uma tecla dentro de um sistema Linux.

Voc j viu que cada tecla pressionada gera dois scancodes (pressionada e liberada). Mas ela possui um nico keycode. Por exemplo, no meu PC, quando pressiono a tecla x, gerado o keycode 45:
$ sudo showkey -k press any key (program terminates 10s after last keypress)... keycode 45 press keycode 45 release

Mas como ento o kernel diferencia teclas pressionadas e liberadas se o keycode o mesmo? Fcil. O device driver gera dois eventos para o kernel. Um para teclas pressionadas e outro para teclas liberadas. E estes eventos so enviados para camada input do kernel. CAMADA 2: INPUT SUBSYSTEM nesta camada que percebemos toda a capacidade de modularizao do kernel do Linux. A camada input foi criada para abstrair a captura de eventos de dispositivos de entrada como mouse, teclado, joysticks, touch screens, etc. Enquanto que cada um destes dispositivos possui seu respectivo device driver, cada device driver exporta os eventos para a camada input. Por sua vez, a camada input trata e exporta estes eventos em um formato padro para arquivos de dispositivo em /dev/input/:
$ ls /dev/input event0 event1 event2 event3 event4 event5 mice mouse0

Cada um destes arquivos de dispositivo representam um dispositivo de entrada. Voc pode usar a ferramenta evtest para verificar qual o dispositivo relacionado determinado arquivo:
$ sudo evtest /dev/input/event4 Input driver version is 1.0.1 Input device ID: bus 0x3 vendor 0x1c4f product 0x2 version 0x110 Input device name: "USB USB Keykoard" ...

Veja que, na minha mquina, /dev/input/event4 o arquivo de dispositivo que gera os eventos do teclado USB. A ferramenta evtest tambm capaz de monitorar os eventos de dispositivos de entrada. Execute novamente o comando acima, pressione uma tecla e veja os eventos que foram exportados para userspace atravs deste arquivo de dispositivo:
... Event: Event: Event: Event: ... time time time time 1323720735.849223, 1323720735.849225, 1323720735.905222, 1323720776.880210, type 1 (Key), code 45 -------------- Report type 1 (Key), code 45 -------------- Report (X), Sync (X), Sync value 1 -----------value 0 ------------

Perceba o keycode 45 para a tecla x e os valores 1 para tecla pressionada e 0 para tecla liberada. Por fim, as bibliotecas grficas monitoram os eventos nestes arquivos de dispositivos e exportam estes eventos para as aplicaes. E assim voc v uma tecla ecoando em seu terminal! Linux ou no um dos SOMLTT sistemas operacionais mais legais de todos os tempos? :-) Apenas um detalhe aqui: no caso do teclado, alm de exportar os eventos para /dev/input/, a camada input tambm exporta os eventos de teclas pressionadas para a

camada TTY ativa no momento. Desta forma, se voc estiver em um terminal conectado /dev/tty2, por exemplo, ir receber este evento. Se quiser saber mais sobre a camada TTY, leia o artigo Por dentro da console em sistemas Linux. VOLTANDO AO NOSSO PROBLEMA Depois de todas estas explicaes, espero que voc no tenha se esquecido do nosso problema original: emular um teclado virtual independente de biblioteca grfica, e que funcione em modo texto. Agora ficou mais fcil pensar numa soluo, no verdade? Reveja as camadas. A camada 0 hardware, e no temos hardware no nosso caso. A camada 2 genrica, e no uma boa prtica mexer nela. Potanto, na camada 1 que trabalharemos. Vamos desenvolver um device driver (no nosso caso, um mdulo do kernel, j que no falaremos com nenhum hardware) que ir gerar eventos de teclado para a camada input. Simples assim! A SOLUO Para no complicar muito, vamos escrever um modulo simples que ir emular a digitao de uma frase a cada 10 segundos. Assim que carregado, uma frase ser digitada pelo mdulo a cada 10 segundos. E para deixar o trabalho mais divertido, a frase ser segmentation fault! Sem mais enrolao, este o cdigo completo do nosso mdulo do kernel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include #include #include #include #include #include #include #include #include #include #include #include "linux/fs.h" "linux/cdev.h" "linux/module.h" "linux/kernel.h" "linux/delay.h" "linux/kthread.h" "linux/device.h" "linux/slab.h" "linux/tty.h" "linux/tty_flip.h" "linux/kbd_kern.h" "linux/input.h"

/* vtkbd kernel thread struct */ static struct task_struct *vtkbd_thread_task; /* vtkbd input device structure */ static struct input_dev *vtkbd_input_dev; const char str_keys[] = { KEY_S, KEY_E, KEY_G, KEY_M, KEY_E, KEY_N, KEY_T, KEY_A, KEY_T, KEY_I, KEY_O, KEY_N, KEY_SPACE, KEY_F, KEY_A, KEY_U, KEY_L, KEY_T, KEY_ENTER }; /* kernel thread */ static int vtkbd_thread(void *unused) { int i; while (!kthread_should_stop()) { for (i = 0; i < sizeof(str_keys); i++) { input_report_key(vtkbd_input_dev, str_keys[i], 1); input_report_key(vtkbd_input_dev, str_keys[i], 0);

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96

input_sync(vtkbd_input_dev); } /* wait 10 seconds */ msleep(10000);

} }

return(0); /* driver initialization */ static int __init vtkbd_init(void) { static const char *name = "Virtual Keyboard"; int i; /* allocate input device */ vtkbd_input_dev = input_allocate_device(); if (!vtkbd_input_dev) { printk("vtkbd_init: Error on input_allocate_device!\n"); return -ENOMEM; } /* set input device name */ vtkbd_input_dev->name = name; /* enable key events */ set_bit(EV_KEY, vtkbd_input_dev->evbit); for (i = 0; i < 256; i++) set_bit(i, vtkbd_input_dev->keybit); /* register input device */ input_register_device(vtkbd_input_dev); /* start thread */ vtkbd_thread_task = kthread_run(vtkbd_thread, NULL, "%s", "vtkbd_thread"); printk("Virtual Keyboard driver initialized.\n"); return 0; } /* driver exit */ void __exit vtkbd_exit(void) { /* stop thread */ kthread_stop(vtkbd_thread_task); /* unregister input device */ input_unregister_device(vtkbd_input_dev); printk("Virtual Keyboard driver.\n"); } module_init(vtkbd_init); module_exit(vtkbd_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Sergio Prado sergio.prado@embeddedlabworks.com"); MODULE_DESCRIPTION("Virtual Keyboard driver");

O mdulo tem basicamente 3 funes: vtkbd_init() para inicializar o mdulo, vtkbd_exit() para fazer a limpeza ao descarregar o mdulo e vtkbd_thread() que faz a mgica de digitar a frase periodicamente. Na funo de inicializao, o primeiro passo alocar um dispositivo de input com a funo input_allocate_device() na linha 54. Essa funo vai devolver uma estrutura que vai nos possibilitar conversar com a camada input e gerar os eventos de teclado. Nas linhas 64 a 66 habilitamos a gerao de eventos em todas as teclas (veja o loop) e na linha 69 registramos o dispositivo de input com a funo input_register_device(). Por ltimo, criamos e iniciamos a thread do kernel que ir fazer o trabalho sujo. Se voc quiser ler mais sobre Kernel Threads, leia o artigo Linux Device Drivers Trabalhando com Kernel Threads Na funo de limpeza, paramos a thread e removemos o dispositivo de input que registramos na inicializao. A thread vtkbd_thread() bem simples e faz toda a mgica. Ela basicamente um loop infinito que, a cada 10 segundos, gera os eventos para emular a digitao. O vetor str_keys[] contm os keycodes para a frase segmentation fault. Esses keycodes esto definidos em include/linux/input.h. Para cada keycode, geramos dois eventos com a funo input_report_key(), simulando a tecla pressionada na linha 34 e a tecla liberada na linha 35 (veja os parmetros 1 e 0, respectivamente). Por ltimo, executamos a funo input_sync(), notificando a camada input da existncia de novos eventos a serem tratados. Simples, no? Para compilar, crie um Makefile com o contedo abaixo:
KVER := $(shell uname -r) KDIR := /usr/src/linux-headers-$(KVER) PWD := $(shell pwd) obj-m += vtkbd.o default: clean: @rm -Rf *.o *.ko *.mod.c modules.order Module.symvers Module.markers $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

E compile:
$ make

Para carregar o mdulo:


$ sudo insmod vtkbd.ko

E para remover o mdulo:


$ sudo rmmod vtkbd

Como o mdulo ficar digitando segmentation fault a cada 10 segundos, este o tempo que voc ter para digitar o comando de remoo do mdulo antes dele bagunar seu shell! E lembre-se de que voc esta trabalhando em kernel space, com acesso total e irrestrito memria e I/Os. Portanto, o ideal aqui trabalhar em uma mquina virtual. mais seguro e pode evitar muitos acidentes, principalmente para aqueles que esto comeando os estudos do Kernel do Linux. PEGADINHA DO MALANDRO

Agora pegue este mdulo, e quando seu amigo deixar a seo aberta para tomar um caf, instale e cronometre o tempo que ele vai levar para encontrar a causa do segmentation fault! Isso se ele encontrar :) Um abrao, Sergio Prado