1613 lines
65 KiB
Markdown
1613 lines
65 KiB
Markdown
# Capítulo 26 — Input en Linux: Teclado, Mouse y Joystick con evdev, libudev y libinput
|
||
|
||
> Capítulo avanzado, pero **escrito para que se entienda de verdad**. Se apoya en file descriptors
|
||
> (`22-file-descriptors`), punteros (`07`), memoria dinámica (`09`) y structs (`08`).
|
||
>
|
||
> Este capítulo te lleva de "leer el teclado con `getchar()`" a leer eventos crudos de
|
||
> `/dev/input/eventN` directamente desde el kernel, enumerar dispositivos con libudev, y abstraer
|
||
> todo en una librería que use libinput para unificar teclado, mouse y joystick.
|
||
|
||
---
|
||
|
||
## Cómo leer este capítulo
|
||
|
||
Hasta ahora leste el teclado con `getchar()` o `scanf()`. Eso es lo que hace la **capa más alta**
|
||
del sistema: decile al usuario "acá escribiste 'A'", punto. Pero en Linux, el teclado no es magia:
|
||
es un archivo `/dev/input/eventN` por cada dispositivo, y dentro hay eventos crudos con código de
|
||
tecla, tipo de evento, timestamp y valor.
|
||
|
||
Este capítulo va de **cero absoluto** (qué es un evento evdev) hasta una **librería completa** que
|
||
capture teclado, mouse y joystick con una API uniforme: `on_key_down(keycode)`,
|
||
`on_mouse_button(button, pressed)`, `on_joystick_axis(axis, value)`.
|
||
|
||
Está dividido en **ocho partes**. Después de cada concepto hay una o más prácticas —uno o más
|
||
ejercicios— para que los uses ya mismo. **No las saltee**: son la mitad del capítulo.
|
||
|
||
| Parte | De qué habla | La idea en una frase |
|
||
|---|---|---|
|
||
| **1** | ¿Cómo llega una pulsación al teclado? | El kernel genera un evento → lo escribe en `/dev/input/eventN` → tu programa lo lee como bytes crudos. |
|
||
| **2** | El formato de `input_event` | Cada evento es un struct con `timeval`, `type`, `code`, `value`: leé eventos del kernel sin intermediarios. |
|
||
| **3** | Leer el teclado crudo con `ioctl` | `EVIOCGNAME` y `EVIOCGBIT`: abrí, configurá y obtené el nombre del dispositivo conectado a tu teclado. |
|
||
| **4** | Enumerar dispositivos con libudev (método fácil) | No busques a mano: libudev lee udev y te dice qué fd es qué dispositivo, su modalias, su path completo. |
|
||
| **5** | El protocolo de eventos evdev | Tipos `EV_KEY`, `EV_REL`, `EV_ABS`: entendé la diferencia entre botón pulsado, movimiento relativo y eje analógico. |
|
||
| **6** | Mouse: movimiento relativo y botones | `EV_REL` con `REL_X`, `REL_Y`, `REL_WHEEL` + `EV_KEY` con `BTN_LEFT`, `BTN_RIGHT`: leé el mouse crudo. |
|
||
| **7** | Joystick: ejes absolutos y botones | `EV_ABS` con `ABS_X`, `ABS_Y`, `ABS_Z`, `ABS_RZ`, `ABS_THROTTLE`: mapeá posiciones analógicas de un gamepad crudo. |
|
||
| **8** | Ejercicio jefe: capturador universal de input | Librería unificada que abstraiga teclado, mouse y joystick en una API con callbacks por evento. |
|
||
|
||
> **Regla de oro del libro:** el capítulo explica y muestra **cómo funciona** cada syscall/ioctl/función,
|
||
> pero **los ejercicios no traen la solución**. El enunciado te da el problema; la respuesta la escribís vos.
|
||
|
||
---
|
||
|
||
# PARTE 1 — ¿Cómo llega una pulsación al teclado?
|
||
|
||
Antes de tocar código, entendamos qué pasa cuando presionás una tecla. Porque lo que sucede entre
|
||
tu dedo y la pantalla es **más largo** de lo que creés — y más interesante todavía.
|
||
|
||
## 1.1 El camino de un "A" desde tu dedo hasta la terminal
|
||
|
||
Imaginate que tu teclado es una **fábrica de eventos**. Cada vez que presionás una tecla, algo
|
||
termina en pantalla. Pero ¿qué pasa en el medio? Acá está el recorrido completo:
|
||
|
||
```text
|
||
dedo → matriz de switches (hardware del teclado) → controlador USB/HID → kernel input subsystem → /dev/input/eventN → tu programa
|
||
```
|
||
|
||
Cada flecha es un **paso**. Uno detrás del otro. Si cualquiera falla, no llegás a la pantalla.
|
||
|
||
La primera parada es el **hardware del teclado**: una matriz de switches (mecánicos o de goma) que
|
||
cierra circuitos eléctricos cuando presionás una tecla. El controlador del teclado lee esa matriz
|
||
(una vez por cada milisegundo más o menos) y genera un "scancode": un número que identifica qué
|
||
switch se cerró, sin importar qué letra imprima.
|
||
|
||
El scancode viaja por USB (o Bluetooth, o PS/2 legacy) hasta el **kernel**, que es donde empieza
|
||
la magia de Linux: el **input subsystem**.
|
||
|
||
## 1.2 El input subsystem: el traductor universal de Linux
|
||
|
||
El kernel input subsystem es el subsistema del kernel que **normaliza todos los dispositivos de
|
||
entrada en un formato uniforme**, sin importar si es USB, Bluetooth, PS/2 o serial:
|
||
|
||
> **Todo dispositivo de entrada se convierte en eventos.** No hay diferencia entre "presionar tecla"
|
||
> y "mover mouse": ambos son secuencias de structs `input_event` escritos en file descriptors
|
||
> especiales. Esos archivos viven en `/dev/input/`.
|
||
|
||
El input subsystem tiene tres capas que trabajan en cadena:
|
||
|
||
| Capa | Qué hace | Ejemplo |
|
||
|---|---|---|
|
||
| **HID driver** | Traduce protocolos (USB HID, PS/2, Bluetooth HOGP) a eventos genéricos del kernel | Tu teclado USB envía reports HID → el driver los traduce a `EV_KEY` |
|
||
| **Input core** | Normaliza eventos y los pone en `/dev/input/eventN` | Unifica teclas de 104 teclas, teclados multimedia, laptops con touchpad |
|
||
| **Userspace** | Tu programa lee los eventos desde el fd | `read(event_fd, &buf, sizeof(buf))` |
|
||
|
||
La capa HID driver es la que conoce el protocolo específico de cada dispositivo. Un teclado USB
|
||
usa el protocolo "HID" (Human Interface Device), un joystick Bluetooth usa "HOGP" (HID Over
|
||
GATT Profile), un touchpad PS/2 usa su propio protocolo. **El driver traduce todo a eventos genéricos**
|
||
que el input core puede manejar uniformemente.
|
||
|
||
El input core es el cerebro: recibe los eventos del driver, les asigna un tipo (`EV_KEY`, `EV_REL`,
|
||
`EV_ABS`) y los escribe en el fd correspondiente de `/dev/input/`.
|
||
|
||
Y vos estás en la última capa: leyendo esos eventos con `read()`.
|
||
|
||
> **La analogía del traductor:** imaginá que el HID driver es un traductor en una conferencia
|
||
> internacional. Recibe palabras en 20 idiomas distintos (USB, Bluetooth, PS/2) y las traduce a
|
||
> una lengua común (eventos genéricos). El input core es el sistema de subtítulos que muestra esas
|
||
> palabras traducidas en la pantalla (`/dev/input/eventN`). Y vos sos el público: no te importa
|
||
> qué idioma hablaba cada persona, solo leés los subtítulos.
|
||
|
||
## 1.3 Los archivos `/dev/input/`
|
||
|
||
```bash
|
||
$ ls /dev/input/
|
||
by-id/ by-path/ event0 event1 event10 js0 mice mouse0
|
||
```
|
||
|
||
Cada `eventN` es un **file descriptor de caracteres** que el kernel usa para enviar eventos de
|
||
entrada. Pero no todos los eventos van al mismo archivo: el kernel decide qué dispositivo va a
|
||
qué `eventN` basándose en el tipo de hardware.
|
||
|
||
| Path | Qué contiene | Ejemplo típico |
|
||
|---|---|---|
|
||
| `/dev/input/event0` | Eventos genéricos (teclado, mouse, etc.) | Teclado USB principal |
|
||
| `/dev/input/event1` | Más eventos genéricos | Mouse o touchpad |
|
||
| `/dev/input/js0` | Joysticks antiguos (legacy) | Gamepad antiguo |
|
||
| `/dev/input/mice` | Mouse PS/2 legacy (agregado de varios ratones) | Laptop con trackpad |
|
||
| `/dev/input/by-id/` | Links simbólicos estables por ID físico | `usb-...-event-kbd` |
|
||
| `/dev/input/by-path/` | Links simbólicos estables por path físico | `pci-0000:00:14.0-usb-event` |
|
||
|
||
> **No hardcodees `/dev/input/event0`.** Cambia según el orden de conexión USB, qué dispositivos
|
||
> estaban conectados al arrancar, si usás Bluetooth o no, etc. Siempre buscá dinámicamente.
|
||
|
||
### 1.3.1 Por qué los links por-id y by-path existen
|
||
|
||
`/dev/input/event0` es **inestable**: si desconectás el teclado y lo volvé a conectar, puede pasar
|
||
a ser `event5`. Si conectás un segundo teclado, todos los números se desplazan.
|
||
|
||
Los links en `by-id/` y `by-path/` son **estables**: apuntan al mismo dispositivo físico siempre,
|
||
sin importar qué número de event tenga. Son útiles cuando necesitás abrir un dispositivo específico
|
||
(sin ambigüedad).
|
||
|
||
```bash
|
||
$ ls -la /dev/input/by-id/
|
||
total 0
|
||
lrwxrwxrwx 1 root root 10 jun 15 10:30 usb-Logitech_G502_HERO-event-mouse -> ../event7
|
||
```
|
||
|
||
Si desconectás y reconectás el mouse, el link sigue apuntando al mismo hardware — aunque el event
|
||
subyacente cambie de número.
|
||
|
||
> **Para este capítulo** no necesitamos links estables: vamos a enumerar todos los dispositivos activos
|
||
> y procesarlos. Pero si escribís un daemon o un servicio que arranca con el sistema, usá
|
||
> `by-id/` o `by-path/`.
|
||
|
||
## 1.4 Lo que te llevás de esta parte
|
||
|
||
- **El input subsystem normaliza todo.** Sin importar el protocolo (USB, Bluetooth, PS/2), el kernel
|
||
convierte cada dispositivo en eventos genéricos.
|
||
- **Los eventos viven en `/dev/input/eventN`.** Cada dispositivo tiene uno o más event nodes.
|
||
- **`event0` es inestable.** Usá `by-id/`, `by-path/`, o mejor aún: libudev para enumerar dinámicamente.
|
||
|
||
### Práctica rápida de la Parte 1
|
||
|
||
#### Ejercicio 26.1 — Listar dispositivos de input
|
||
|
||
**Conceptos** · `opendir`, `readdir`, `stat`, `/dev/input/event*`
|
||
|
||
Leé el contenido de `/dev/input/` y listá todos los archivos que empiecen con `event`. Para cada
|
||
uno, mostrá:
|
||
|
||
- El nombre del archivo (ej: `event5`)
|
||
- Si es un dispositivo de caracteres (`c` para character device)
|
||
- Su tamaño (siempre 0, porque son devices — no tiene dato útil, es una trampa común)
|
||
|
||
**Salida esperada:**
|
||
```
|
||
Dispositivos en /dev/input/:
|
||
event0 [char dev]
|
||
event1 [char dev]
|
||
event2 [char dev]
|
||
mice [regular file]
|
||
mouse0 [char dev]
|
||
...
|
||
```
|
||
|
||
**Criterios de aceptación:**
|
||
- [ ] Recorrés `/dev/input/` con `opendir`/`readdir`, no con `system("ls")`.
|
||
- [ ] Usás `stat()` para verificar si es character device (`S_ISCHR`).
|
||
- [ ] Mostrás todos los entries del directorio, no solo `eventN`.
|
||
|
||
---
|
||
|
||
# PARTE 2 — El formato de `input_event`
|
||
|
||
## 2.1 La estructura más importante del input en Linux
|
||
|
||
Todo evento de entrada viaja como un `struct input_event`:
|
||
|
||
```c
|
||
#include <linux/input.h>
|
||
|
||
struct timeval {
|
||
time_t tv_sec; // segundos desde epoch (Unix timestamp)
|
||
suseconds_t tv_usec; // microsegundos dentro del segundo
|
||
};
|
||
|
||
struct input_event {
|
||
struct timeval time; // cuándo ocurrió el evento (con microsegundos de precisión)
|
||
__uint16_t type; // qué tipo de evento es: tecla, movimiento, sincronización...
|
||
__uint16_t code; // cuál código dentro del tipo: qué tecla, qué botón, qué eje
|
||
__s32 value; // el valor: 0/1 para pulsado/soltado, delta para movimiento, posición para ejes
|
||
};
|
||
```
|
||
|
||
Un solo `read()` lee **un evento completo**. Siempre. No hay eventos incompletos ni fragmentados:
|
||
|
||
```c
|
||
struct input_event ev;
|
||
while (read(fd, &ev, sizeof(ev)) == sizeof(ev)) {
|
||
// procesar evento
|
||
}
|
||
```
|
||
|
||
> **¿Por qué `timeval` y no `clock_gettime`?** Porque el kernel usa un reloj interno para timestampear
|
||
> cada evento en el momento exacto en que ocurre. No es un reloj arbitrario: es el tiempo del sistema,
|
||
> con microsegundos de precisión. Podés convertirlo a cualquier formato después.
|
||
|
||
## 2.2 Los tres campos decodificados
|
||
|
||
Cada evento tiene exactamente **cuatro campos**, pero solo `type`, `code` y `value` son los que te
|
||
importan para interpretar qué pasó:
|
||
|
||
| Campo | Tipo | Qué significa | Ejemplo de valor |
|
||
|---|---|---|---|
|
||
| **`time`** | `struct timeval` | Cuándo ocurrió el evento | `tv_sec=1700000000, tv_usec=123456` |
|
||
| **`type`** | `uint16_t` | Categoría del evento: tecla, movimiento relativo, eje absoluto | `EV_KEY` (1), `EV_REL` (3), `EV_ABS` (3) |
|
||
| **`code`** | `uint16_t` | Identificador dentro de la categoría | `KEY_A`, `REL_X`, `ABS_X` |
|
||
| **`value`** | `int32_t` | El dato concreto del evento | `1` (presionado), `-5` (delta), `32768` (posición) |
|
||
|
||
> **La regla de oro de `input_event`:** el significado de `value` depende completamente de `type` y
|
||
> `code`. Un `value=1` significa "presionado" para `EV_KEY`, pero tiene otro significado para
|
||
> `EV_ABS` (valor absoluto del eje) y no existe para `EV_SYN`. **Siempre mirá los tres campos juntos.**
|
||
|
||
## 2.3 Valores según el tipo de evento
|
||
|
||
### EV_KEY: teclas y botones
|
||
|
||
| `code` | `value = 0` | `value = 1` | `value = 2` |
|
||
|---|---|---|---|
|
||
| `KEY_A`, `KEY_ENTER`, `BTN_LEFT`... | Soltado (release) | Presionado (press) | Repeat (mantener presionado) |
|
||
|
||
Para `EV_KEY`, el valor siempre es 0, 1 o 2:
|
||
|
||
```
|
||
tecla suelta → value=0 (release)
|
||
tecla pulsada → value=1 (press)
|
||
tecla manteniendo → value=2 (repeat, generado automáticamente por el kernel tras ~500ms)
|
||
```
|
||
|
||
El "repeat" (`value=2`) es un evento del **kernel**, no del hardware. El kernel detecta que la tecla
|
||
sigue presionada y genera eventos de repeat automáticamente. Podés desactivarlos con `ioctl`, pero
|
||
normalmente los ignorás (ya sabés que la tecla sigue presionada por el press anterior).
|
||
|
||
### EV_REL: movimiento relativo (mouse)
|
||
|
||
| `code` | `value` | Significado |
|
||
|---|---|---|
|
||
| `REL_X` | `+5` | Mouse se movió 5 unidades a la derecha |
|
||
| `REL_Y` | `-3` | Mouse se movió 3 unidades arriba |
|
||
| `REL_WHEEL` | `+1` | Scroll: una línea hacia abajo (alejado) |
|
||
| `REL_WHEEL` | `-1` | Scroll: una línea hacia arriba (hacia el usuario) |
|
||
| `REL_HWHEEL` | `+2` | Scroll horizontal: 2 pasos a la derecha |
|
||
|
||
Para `EV_REL`, el valor es siempre un **delta**: cuánto se movió desde el último evento, sin ninguna
|
||
posición absoluta de referencia. Un mouse USB reporta deltas típicamente entre -128 y +127 por
|
||
evento. Si movés el mouse rápido, podés recibir deltas grandes o múltiples eventos REL_X seguidos.
|
||
|
||
> **Los deltas no suman para dar una posición absoluta.** Si querés la posición del cursor en pantalla,
|
||
> tenés que acumularlos vos: `cursor_x += delta_x`, `cursor_y += delta_y`. Pero cuidado: si el mouse
|
||
> se mueve entre frames (no leés lo suficientemente rápido), podés recibir decenas de eventos REL
|
||
> acumulados.
|
||
|
||
### EV_ABS: posición absoluta (joystick, touchpad)
|
||
|
||
| `code` | `value` | Significado |
|
||
|---|---|---|
|
||
| `ABS_X` | `32768` | Eje X en el centro (rango típico: 0–65535 o -32768 a +32767) |
|
||
| `ABS_Y` | `100` | Eje Y muy arriba |
|
||
| `ABS_Z` | `65535` | Trigger izquierdo presionado a fondo (si el rango es 0–65535) |
|
||
|
||
Para `EV_ABS`, el valor es una **posición absoluta** dentro de un rango definido por el dispositivo.
|
||
Si un joystick reporta `ABS_X = 32768`, significa "en el centro", sin importar si el usuario movió
|
||
el stick antes o no. Es como tener un termómetro que te dice "estamos a 25°C", no "la temperatura
|
||
subió 2 grados".
|
||
|
||
> **Ojo con los rangos de ABS:** algunos joysticks reportan `-32768` a `+32767`, otros `0` a `65535`,
|
||
> y hay devices baratos que reportan rangos completamente distintos (ej: `1000` a `64000`).
|
||
> **Nunca asumas el rango** — consultá con `EVIOCGABS` (llegamos a esa parte).
|
||
|
||
### EV_SYN: sincronización
|
||
|
||
| `code` | `value` | Significado |
|
||
|---|---|---|
|
||
| `SYN_REPORT` | `0` | Todos los eventos anteriores forman un frame completo |
|
||
| `SYN_CONFIG` | `0` | La configuración del dispositivo cambió |
|
||
| `SYN_MT_REPORT` | `0` | Fin de un reporte multitouch (para pantallas táctiles) |
|
||
|
||
`EV_SYN` es el "punto y coma" de los eventos. El kernel agrupa eventos relacionados con un SYN_REPORT:
|
||
|
||
```
|
||
EV_KEY + KEY_A → value=1 (presionaste A)
|
||
EV_SYN + SYN_REPORT → value=0 (este frame está completo)
|
||
EV_KEY + KEY_A → value=0 (solto A)
|
||
EV_SYN + SYN_REPORT → value=0 (este frame también)
|
||
```
|
||
|
||
En la práctica, **no necesitás procesar SYN_REPORT** a menos que estés construyendo algo sensible
|
||
a frames completos (un compositor de ventana, un juego competitivo). Para lectura simple, ignoralo.
|
||
|
||
> **¿Por qué existen los SYN?** Porque el kernel puede recibir múltiples eventos del mismo dispositivo
|
||
> en un solo `read()`. Sin SYN, no sabrías si dos eventos están relacionados o son independientes.
|
||
> SYN_REPORT dice: "todo lo que leíste antes de este byte es un frame atómico".
|
||
|
||
## 2.4 Decodificar un evento paso a paso
|
||
|
||
Acá tenés un ejemplo mínimo que imprime cada evento en texto legible:
|
||
|
||
```c
|
||
#include <stdio.h>
|
||
#include <linux/input.h>
|
||
#include <fcntl.h>
|
||
#include <unistd.h>
|
||
|
||
void print_event(struct input_event *ev) {
|
||
printf("time=%ld.%06d type=%-4u code=%-5u value=%-6d\n",
|
||
ev->tv_sec, ev->tv_usec / 1000, // timestamp en ms
|
||
ev->type, // tipo numérico
|
||
ev->code, // código numérico
|
||
ev->value); // valor
|
||
}
|
||
|
||
int main(void) {
|
||
int fd = open("/dev/input/event3", O_RDONLY | O_NONBLOCK);
|
||
if (fd < 0) {
|
||
perror("open");
|
||
return 1;
|
||
}
|
||
|
||
struct input_event ev;
|
||
while (read(fd, &ev, sizeof(ev)) == sizeof(ev)) {
|
||
print_event(&ev);
|
||
}
|
||
|
||
close(fd);
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
> **¿Por qué `O_NONBLOCK`?** Porque si el dispositivo no genera eventos (no tocás nada), `read()`
|
||
> se bloquearía indefinidamente. Con `O_NONBLOCK`, devuelve `-1` con `errno=EAGAIN` cuando no hay
|
||
> datos — así podés hacer otras cosas o usar `poll()`/`select()` para esperar de forma eficiente.
|
||
|
||
## 2.5 Lo que te llevás de esta parte
|
||
|
||
- **Todo evento es un `input_event`:** struct con timestamp, tipo, código y valor.
|
||
- **`read()` devuelve un evento completo siempre.** No hay eventos parciales.
|
||
- **El significado de `value` depende de `type`.** 0/1 para teclas, delta para movimiento relativo,
|
||
posición absoluta para ejes.
|
||
- **`EV_SYN` separa frames.** No lo procesás directamente, pero sabé que existe.
|
||
|
||
### Práctica rápida de la Parte 2
|
||
|
||
#### Ejercicio 26.2 — Lectura cruda de eventos de teclado
|
||
|
||
**Conceptos** · `open`, `read`, `struct input_event`, `O_NONBLOCK`
|
||
|
||
Encontrá el fd del teclado (pista: tiene `EV_KEY` en su bitmask con `EVIOCGBIT(fd, EV_MAX)`),
|
||
abrilo **en modo no bloqueante**, y leé 20 eventos mostrados:
|
||
|
||
```
|
||
time=1700000000.123456 type=1 code=30 value=1 → KEY_A pressed
|
||
time=1700000000.134567 type=1 code=30 value=0 → KEY_A released
|
||
```
|
||
|
||
**Criterios de aceptación:**
|
||
- [ ] Abrís el fd con `O_NONBLOCK`.
|
||
- [ ] Mostrás solo los primeros 20 eventos de tipo `EV_KEY` (type == EV_KEY).
|
||
- [ ] Traducís `value`: 0 = "released", 1 = "pressed", 2 = "repeat".
|
||
|
||
---
|
||
|
||
# PARTE 3 — Leer el teclado crudo con `ioctl`
|
||
|
||
## 3.1 ioctl del input subsystem
|
||
|
||
Los `ioctl` del subsistema de entrada se usan con el fd de un dispositivo `/dev/input/eventN`.
|
||
Son los comandos más útiles:
|
||
|
||
| ioctl | Qué hace | Ejemplo de uso |
|
||
|---|---|---|
|
||
| `EVIOCGNAME(sizeof(name))` | Obtiene el nombre descriptivo del dispositivo | "AT Translated Set 2 Keyboard" |
|
||
| `EVIOCGVERSION(version)` | Obtiene la versión del driver | Major/minor/patch |
|
||
| `EVIOCGBIT(ev, size, data)` | Obtén el bitmask de eventos soportados | ¿Qué tipos reporta este device? |
|
||
| `EVIOCGID(fd, &id)` | Info del dispositivo (bus/vendor/product/version) | USB vendor/product ID |
|
||
| `EVIOCGABS(code, &abs)` | Obtiene parámetros de un eje absoluto | min/max/resolution/fuzz/flat |
|
||
| `EVIOCSREP(delay, rate)` | Configura delay y rate de repeat de teclas | Cuánto esperar antes de repetir |
|
||
| `EVIOCGRAB(fd, 1)` | Grab: nadie más recibe eventos mientras tengas el fd abierto | Secuestrar el dispositivo |
|
||
|
||
> **`EVIOCGBIT(ev, size, data)`:** este es el comando más importante para descubrir qué hace un
|
||
> dispositivo. Le pedís al kernel "dame los bits de tipos de evento soportados por este fd" y el
|
||
> kernel te devuelve un bitmask donde cada bit corresponde a un tipo (`EV_KEY`, `EV_REL`, etc.).
|
||
|
||
### 3.1.1 EVIOCGNAME: saber qué device es cuál
|
||
|
||
```c
|
||
char name[256] = {0};
|
||
ioctl(fd, EVIOCGNAME(sizeof(name)), name);
|
||
printf("Device name: %s\n", name);
|
||
// Output: "AT Translated Set 2 Keyboard"
|
||
// "Logitech G502 HERO Gaming Mouse"
|
||
// "Xbox Wireless Controller"
|
||
```
|
||
|
||
`EVIOCGNAME` te devuelve una cadena legible por humanos que identifica el dispositivo. Es mucho más
|
||
útil que adivinar cuál es el teclado mirando `/dev/input/eventN`.
|
||
|
||
### 3.1.2 EVIOCGBIT: descubrí qué hace un dispositivo
|
||
|
||
Este es el comando que usás para **descubrir qué tipo de eventos reporta cada dispositivo**. Te devuelve
|
||
un bitmask donde cada bit activo significa que el dispositivo genera ese tipo de evento:
|
||
|
||
```c
|
||
#include <linux/input.h>
|
||
#include <sys/ioctl.h>
|
||
|
||
unsigned long types[BITS_TO_LONGS(EV_MAX)];
|
||
ioctl(fd, EVIOCGBIT(0, sizeof(types)), types);
|
||
|
||
// test_bit verifica si un bit está seteado en el bitmask
|
||
int has_keys = test_bit(EV_KEY, types);
|
||
int has_rel = test_bit(EV_REL, types);
|
||
int has_abs = test_bit(EV_ABS, types);
|
||
int has_msc = test_bit(EV_MSC, types);
|
||
|
||
printf("EV_KEY: %s EV_REL: %s EV_ABS: %s EV_MSC: %s\n",
|
||
has_keys ? "YES" : "no",
|
||
has_rel ? "YES" : "no",
|
||
has_abs ? "YES" : "no",
|
||
has_msc ? "YES" : "no");
|
||
```
|
||
|
||
> **`BITS_TO_LONGS(n)`** es una macro que calcula cuántos `unsigned long` necesitás para almacenar
|
||
> `n` bits. Es equivalente a `(n + 31) / 32`. La usá siempre con `EVIOCGBIT`.
|
||
>
|
||
> **`test_bit(bit, array)`** verifica si el bit en la posición `bit` está activo (seteado a 1). Es
|
||
> una macro que hace operaciones bitwise sobre el array devuelto por `EVIOCGBIT`.
|
||
|
||
## 3.2 Clasificar dispositivos: keyboard vs mouse vs joystick
|
||
|
||
Con `EVIOCGBIT`, podés distinguir qué tipo de dispositivo es cada uno:
|
||
|
||
```c
|
||
InputDeviceType classify_device(int fd) {
|
||
unsigned long types[BITS_TO_LONGS(EV_MAX)];
|
||
ioctl(fd, EVIOCGBIT(0, sizeof(types)), types);
|
||
|
||
int has_keys = test_bit(EV_KEY, types);
|
||
int has_rel = test_bit(EV_REL, types);
|
||
int has_abs = test_bit(EV_ABS, types);
|
||
|
||
// Reglas de clasificación:
|
||
if (has_keys && !has_rel && !has_abs) {
|
||
return DEVICE_KEYBOARD; // solo teclas → keyboard
|
||
}
|
||
else if (has_keys && has_rel && !has_abs) {
|
||
return DEVICE_MOUSE; // botones + movimiento relativo, sin ejes absolutos → mouse
|
||
}
|
||
else if (has_keys && has_rel && has_abs) {
|
||
return DEVICE_GAMEPAD; // tiene botones Y ejes absolutos → gamepad/joystick
|
||
}
|
||
else if (!has_keys && has_abs) {
|
||
return DEVICE_JOYSTICK; // solo ejes absolutos (ej: un acelerador independiente) → joystick
|
||
}
|
||
else {
|
||
return DEVICE_UNKNOWN; // no clasificado con las reglas conocidas
|
||
}
|
||
}
|
||
```
|
||
|
||
> **Las reglas de clasificación no son perfectas.** Un touchpad tiene `EV_KEY` (click), `EV_REL` (movimiento),
|
||
> y a veces `EV_ABS` (gestos multitouch). Lo importante es entender el patrón: **mirá qué bits están
|
||
> activos en el bitmask** y decidí basado en esa combinación.
|
||
|
||
## 3.3 El modo grab: secuestrar un dispositivo
|
||
|
||
`EVIOCGRAB(fd, 1)` hace que **ningún otro programa** reciba eventos de este fd mientras tu proceso
|
||
lo tenga abierto con el grab activo. Es lo que usan herramientas legítimas como `showkeys`, `xdotool`,
|
||
o juegos en pantalla completa — pero también es la base de los keyloggers maliciosos.
|
||
|
||
```c
|
||
// Grabar teclado: solo mi programa recibe eventos
|
||
if (ioctl(fd, EVIOCGRAB, 1) < 0) {
|
||
perror("EVIOCGRAB failed");
|
||
// Probablemente necesitás root o pertenecer al grupo 'input'
|
||
}
|
||
|
||
// ... leer eventos sin interferencia de otros programas ...
|
||
|
||
// Soltar teclado: otros programas vuelven a recibir eventos
|
||
ioctl(fd, EVIOCGRAB, 0);
|
||
```
|
||
|
||
> **Necesitás root (o permiso del grupo `input`) para hacer grab.** Sin ellos, podés leer pero no
|
||
> excluir a otros programas. Es una medida de seguridad: nadie debería poder secuestrar tu teclado
|
||
> sin tu permiso explícito.
|
||
|
||
## 3.4 EVIOCGABS: parámetros de ejes absolutos
|
||
|
||
Cada eje absoluto tiene un **rango** y **parámetros de calibración**. Podés consultarlo con `EVIOCGABS`:
|
||
|
||
```c
|
||
struct input_absinfo abs;
|
||
ioctl(fd, EVIOCGABS(ABS_X), &abs);
|
||
|
||
printf("Eje ABS_X:\n");
|
||
printf(" min: %d\n", abs.minimum); // valor mínimo del eje
|
||
printf(" max: %d\n", abs.maximum); // valor máximo del eje
|
||
printf(" fuzz: %d\n", abs.fuzz); // ruido aceptable (ignorar cambios menores)
|
||
printf(" flat: %d\n", abs.flat); // zona muerta central (deadzone)
|
||
printf(" resolution %d\n", abs.resolution); // unidades por milímetro (o grado, etc.)
|
||
```
|
||
|
||
> **No asumas que todos los ejes van de `-32768` a `+32767`.** Joysticks baratos, pads de guitarra,
|
||
> volantes — cada uno puede tener rangos distintos. Siempre consultá con `EVIOCGABS`.
|
||
|
||
## 3.5 Lo que te llevás de esta parte
|
||
|
||
- **ioctl son la API de bajo nivel del kernel para dispositivos.** Con `EVIOCGBIT` descubrí qué
|
||
hace un dispositivo, con `EVIOCGNAME` su nombre descriptivo, con `EVIOCGABS` los parámetros de ejes.
|
||
- **La clasificación se basa en bits:** solo `EV_KEY` = keyboard, `EV_KEY + EV_REL` sin `EV_ABS` = mouse,
|
||
`EV_ABS` presente = joystick/gamepad.
|
||
- **EVIOCGRAB secuestra el dispositivo.** Nadie más recibe eventos mientras tengas el grab activo.
|
||
Requiere root o grupo `input`.
|
||
|
||
### Práctica rápida de la Parte 3
|
||
|
||
#### Ejercicio 26.3 — Descubrir tipos de dispositivos del input
|
||
|
||
**Conceptos** · `ioctl`, `EVIOCGBIT`, bitmask con `test_bit`, `open`
|
||
|
||
Abrí cada `/dev/input/eventN` y determiná qué tipo de dispositivo es (teclado, mouse, touchpad,
|
||
joystick) usando los bitmasks de eventos soportados. Mostrá:
|
||
|
||
```
|
||
event0 → keyboard ("AT Translated Set 2 Keyboard")
|
||
event1 → mouse ("SynPS/2 Synaptics TouchPad")
|
||
event3 → gamepad ("Xbox Wireless Controller")
|
||
```
|
||
|
||
**Criterios de aceptación:**
|
||
- [ ] Usás `EVIOCGBIT` para cada fd.
|
||
- [ ] Clasificás correctamente: keyboard (solo EV_KEY), mouse (EV_REL + EV_KEY sin EV_ABS),
|
||
joystick/gamepad (EV_ABS presente).
|
||
- [ ] Mostrás el nombre del dispositivo con `EVIOCGNAME`.
|
||
- [ ] Manejás errores si un fd no se puede abrir (permisos, dispositivo eliminado entre tanto).
|
||
|
||
---
|
||
|
||
# PARTE 4 — Enumerar dispositivos con libudev (método fácil)
|
||
|
||
## 4.1 ¿Por qué usar libudev en vez de leer `/dev/input/`?
|
||
|
||
Leer `/dev/input/eventN` a mano tiene problemas serios:
|
||
|
||
- No sabés **qué dispositivo es** sin abrirlo y hacer `ioctl`.
|
||
- No sabés **cuándo se conectó o desconectó** un dispositivo nuevo (hotplug).
|
||
- No tenés acceso a **propiedades del dispositivo**: vendor ID, product ID, modalias, path físico.
|
||
- No podés saber si el usuario tiene permiso para leerlo sin intentarlo y ver si falla.
|
||
|
||
libudev resuelve todo eso: lee el sysfs y udev database para darte metadatos de cada dispositivo
|
||
**antes de abrirlo**. Es la capa de abstracción correcta sobre `/dev/input/`.
|
||
|
||
> **La analogía del directorio telefónico:** leer `/dev/input/` es como buscar números de teléfono
|
||
> en un listín sin nombres, solo números. libudev es el directorio con nombres, direcciones y
|
||
> categorías: "Logitech G502 → mouse gaming → vendor 0x046d". Sabés qué estás buscando antes de
|
||
> marcar.
|
||
|
||
## 4.2 El flujo básico de libudev
|
||
|
||
```c
|
||
#include <libudev.h>
|
||
|
||
// 1. Crear el contexto udev (una sola vez)
|
||
struct udev *udev = udev_new();
|
||
if (!udev) {
|
||
perror("udev_new failed");
|
||
return 1;
|
||
}
|
||
|
||
// 2. Crear un enumerador (buscador)
|
||
struct udev_enumerate *enumerate = udev_enumerate_new(udev);
|
||
|
||
// 3. Agregar filtros: solo dispositivos del subsistema "input"
|
||
udev_enumerate_add_match_subsystem(enumerate, "input");
|
||
|
||
// 4. Escanear y obtener la lista
|
||
udev_enumerate_scan_devices(enumerate);
|
||
|
||
// 5. Recorrer los resultados
|
||
struct udev_list_entry *entry;
|
||
udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(enumerate)) {
|
||
const char *syspath = udev_list_entry_get_name(entry);
|
||
|
||
// Para cada entry, crear un device object con todos sus metadatos.
|
||
struct udev_device *dev = udev_device_new_from_syspath(udev, syspath);
|
||
|
||
// Obtener propiedades del dispositivo (si están disponibles):
|
||
const char *devname = udev_device_get_devnode(dev); // /dev/input/eventX
|
||
const char *name = udev_device_get_property_value(dev, "ID_INPUT");
|
||
const char *vendor = udev_device_get_property_value(dev, "ID_VENDOR_id");
|
||
const char *product = udev_device_get_property_value(dev, "ID_MODEL_id");
|
||
const char *modalias = udev_device_get_property_value(dev, "modalias");
|
||
|
||
printf("Device: %s (%s) vendor=%s product=%s\n",
|
||
devname, modalias ? modalias : "unknown",
|
||
vendor ? vendor : "?", product ? product : "?");
|
||
|
||
// Liberar el device object (es referencia-contada)
|
||
udev_device_unref(dev);
|
||
}
|
||
|
||
// 6. Liberar recursos
|
||
udev_enumerate_unref(enumerate);
|
||
udev_unref(udev);
|
||
```
|
||
|
||
> **Siempre liberá los objetos.** Cada `udev_device_new_from_syspath()` devuelve un objeto con
|
||
> referencia contada. Llamá a `udev_device_unref(dev)` cuando termines, y `udev_unref(udev)`
|
||
> al final del programa para liberar el contexto global.
|
||
|
||
## 4.3 Propiedades clave de libudev
|
||
|
||
| Propiedad | Qué dice | Ejemplo |
|
||
|---|---|---|
|
||
| `ID_INPUT` | "1" si es un dispositivo de entrada | Todos los `/dev/input/eventN` |
|
||
| `ID_INPUT_KEYBOARD` | "1" si tiene teclas | Teclados, laptops |
|
||
| `ID_INPUT_MOUSE` | "1" si es un ratón | Mouse USB, trackpad |
|
||
| `ID_INPUT_JOYSTICK` | "1" si es gamepad | DualSense, Logitech |
|
||
| `ID_INPUT_TOUCHPAD` | "1" si es touchpad | Synaptics, ELAN |
|
||
| `modalias` | Tipo de dispositivo (USB/PS/2/HID) | `"input:test-0G0M..."` |
|
||
| `DEVNAME` | Path al device node | `/dev/input/event5` |
|
||
|
||
> **Las propiedades udev son las más confiables** para clasificar dispositivos. Si `ID_INPUT_KEYBOARD`
|
||
> está presente y vale "1", el dispositivo ES un teclado — no hay que adivinarlo con bits.
|
||
|
||
## 4.4 Fallback: detectar tipo con ioctl (cuando udev no alcanza)
|
||
|
||
A veces udev no tiene la propiedad que necesitás: dispositivos genéricos, contenedores sin
|
||
udevd corriendo, o devices antiguos. En esos casos, **caés al kernel como red de seguridad**:
|
||
|
||
```c
|
||
InputDeviceType detect_by_ioctl(const char *devpath) {
|
||
int fd = open(devpath, O_RDONLY | O_NONBLOCK);
|
||
if (fd < 0) return DEVICE_UNKNOWN;
|
||
|
||
unsigned long types[BITS_TO_LONGS(EV_MAX)];
|
||
ioctl(fd, EVIOCGBIT(0, sizeof(types)), types);
|
||
|
||
int has_keys = test_bit(EV_KEY, types);
|
||
int has_rel = test_bit(EV_REL, types);
|
||
int has_abs = test_bit(EV_ABS, types);
|
||
|
||
// Reglas de clasificación:
|
||
if (has_keys && !has_rel && !has_abs) {
|
||
close(fd);
|
||
return DEVICE_KEYBOARD; // solo teclas → keyboard
|
||
}
|
||
else if (has_keys && has_rel && !has_abs) {
|
||
close(fd);
|
||
return DEVICE_MOUSE; // botones + relativo sin absoluto → mouse
|
||
}
|
||
else if (has_abs) {
|
||
close(fd);
|
||
return DEVICE_JOYSTICK; // tiene ejes absolutos → joystick/gamepad
|
||
}
|
||
|
||
close(fd);
|
||
return DEVICE_UNKNOWN;
|
||
}
|
||
```
|
||
|
||
> **Siempre empezá por la abstracción más alta.** Udev es la capa fácil — chequeá sus propiedades
|
||
> primero. Si no clasifica, usá ioctl como fallback. Este patrón (abstracción → raw) se repite en
|
||
> muchos subsistemas de Linux.
|
||
|
||
## 4.5 Monitorizar hotplug con libudev
|
||
|
||
libudev permite escuchar eventos en tiempo real: cuando conectás o desconectás un dispositivo:
|
||
|
||
```c
|
||
// Crear un monitor que escuche eventos del kernel udevd
|
||
struct udev_monitor *mon = udev_monitor_new_from_netlink(udev, "udev");
|
||
udev_monitor_filter_add_match_subsystem_devtype(mon, "input", NULL);
|
||
udev_monitor_enable_receiving(mon);
|
||
|
||
// El fd del monitor se puede usar con poll()/select() junto con los fds de input:
|
||
struct pollfd pfds[3];
|
||
pfds[0].fd = udev_monitor_get_fd(mon); // hotplug events
|
||
pfds[1].fd = keyboard_fd; // teclado
|
||
pfds[2].fd = mouse_fd; // mouse
|
||
// ... más fds para otros dispositivos ...
|
||
|
||
int ret = poll(pfds, num_fds, -1); // espera indefinida
|
||
if (pfds[0].revents & POLLIN) {
|
||
// Nuevo dispositivo conectado o desconectado!
|
||
struct udev_device *hotplug_dev = udev_monitor_receive_device(mon);
|
||
// ... procesar hotplug ...
|
||
udev_device_unref(hotplug_dev);
|
||
}
|
||
```
|
||
|
||
> **El monitor udev se multiplexa con poll() junto con los fds de input.** Esto es clave para un
|
||
> programa que escucha tanto eventos de dispositivos como cambios de hotplug sin hilos.
|
||
|
||
## 4.6 Lo que te llevás de esta parte
|
||
|
||
- **libudev es la capa correcta sobre `/dev/input/`.** Te da nombres, vendors, tipos y hotplug
|
||
antes de que abras un solo fd.
|
||
- **Las propiedades `ID_INPUT_*` clasifican dispositivos.** No hay adivinar con bits.
|
||
- **ioctl como fallback siempre.** Si udev no tiene la propiedad, usá `EVIOCGBIT` para clasificar.
|
||
- **Hotplug con `udev_monitor`.** Escuchá conexiones/desconexiones en tiempo real con `poll()`.
|
||
|
||
### Práctica de la Parte 4
|
||
|
||
#### Ejercicio 26.4 — Enumerar y clasificar dispositivos con libudev (con fallback ioctl)
|
||
|
||
**Conceptos** · `libudev`, `udev_enumerate`, propiedades de dispositivos, **`EVIOCGBIT` como fallback**, bitmask
|
||
|
||
Escribí un programa que liste **todos los dispositivos de entrada conectados**, usando libudev, mostrando:
|
||
- Nombre del dispositivo (`DEVNAME`)
|
||
- Tipo clasificado con el método primario y secundario
|
||
- Vendor y Product ID (si están disponibles)
|
||
|
||
**Flujo de clasificación por dispositivo (ordenado por prioridad):**
|
||
|
||
1. **Método A — libudev:** Chequeá propiedades udev en este orden:
|
||
- `ID_INPUT_KEYBOARD == "1"` → `[keyboard]`
|
||
- `ID_INPUT_MOUSE == "1"` → `[mouse]`
|
||
- `ID_INPUT_JOYSTICK == "1"` → `[joystick]`
|
||
- `ID_INPUT_TOUCHPAD == "1"` → `[touchpad]`
|
||
|
||
2. **Método B — ioctl fallback:** Si udev no clasificó, abrí el fd con `DEVNAME` y leé los tipos soportados:
|
||
- Solo `EV_KEY` → `[keyboard]`
|
||
- `EV_KEY + EV_REL` sin `EV_ABS` → `[mouse]`
|
||
- `EV_ABS` presente → `[joystick]`
|
||
- Nada claro → `[other]`
|
||
|
||
**Salida esperada:**
|
||
```
|
||
Dispositivos en /dev/input/:
|
||
event0 [char dev]
|
||
event1 [char dev]
|
||
event2 [char dev]
|
||
mice [regular file]
|
||
mouse0 [char dev]
|
||
...
|
||
|
||
📡 Dispositivos de entrada clasificados:
|
||
[keyboard] AT Translated Set 2 Keyboard → /dev/input/event3 (udev)
|
||
[mouse] Logitech G502 HERO → /dev/input/event7 (udev)
|
||
[joystick] Xbox Wireless Controller → /dev/input/event12 (udev)
|
||
[other] USB Generic HID Device → /dev/input/event9 (ioctl fallback)
|
||
```
|
||
|
||
**Criterios de aceptación:**
|
||
- [ ] Usás `libudev` como método principal de enumeración y clasificación.
|
||
- [ ] Implementás el fallback con `EVIOCGBIT` que se ejecuta solo cuando udev no clasifica.
|
||
- [ ] Mostrás entre paréntesis el método usado: `(udev)` o `(ioctl fallback)`.
|
||
- [ ] Manejás el caso donde no hay dispositivos conectados (output: "No hay dispositivos de entrada").
|
||
|
||
---
|
||
|
||
# PARTE 5 — El protocolo de eventos evdev
|
||
|
||
## 5.1 Tipos de eventos en detalle
|
||
|
||
El kernel input subsystem usa un sistema de tipos y códigos bien definido. Acá están los que vas a usar:
|
||
|
||
### EV_KEY (tipo = 1): Teclas y botones
|
||
|
||
| Código | Significado |
|
||
|---|---|
|
||
| `KEY_A` ... `KEY_Z` | Teclas A-Z |
|
||
| `KEY_0` ... `KEY_9` | Dígitos |
|
||
| `KEY_ENTER`, `KEY_ESC`, `KEY_TAB`, `KEY_BACKSPACE` | Teclas de control |
|
||
| `KEY_LEFTSHIFT`, `KEY_RIGHTSHIFT`, `KEY_LEFTCTRL`, `KEY_RIGHTCTRL` | Modificadores |
|
||
| `KEY_LEFTALT`, `KEY_RIGHTALT`, `KEY_LEFTMETA`, `KEY_RIGHTMETA` | Modificadores |
|
||
| `BTN_LEFT`, `BTN_MIDDLE`, `BTN_RIGHT`, `BTN_SIDE`, `BTN_EXTRA` | Botones del mouse |
|
||
| `BTN_TRIGGER`, `BTN_THUMB`, `BTN_TOP`, `BTN_TOP2` | Botones del joystick |
|
||
|
||
### EV_REL (tipo = 3): Movimiento relativo
|
||
|
||
| Código | Significado |
|
||
|---|---|
|
||
| `REL_X` | Movimiento horizontal del mouse |
|
||
| `REL_Y` | Movimiento vertical del mouse |
|
||
| `REL_WHEEL` | Scroll vertical |
|
||
| `REL_HWHEEL` | Scroll horizontal |
|
||
| `REL_RX`, `REL_RY`, `REL_RZ` | Rotación relativa (joystick giratorio) |
|
||
|
||
### EV_ABS (tipo = 3): Posición absoluta
|
||
|
||
| Código | Significado |
|
||
|---|---|
|
||
| `ABS_X`, `ABS_Y` | Joystick X/Y (stick izquierdo) |
|
||
| `ABS_Z`, `ABS_RZ` | Trigger izquierdo/derecho (L2/R2 o RT/LT) |
|
||
| `ABS_THROTTLE`, `ABS_RUDDER` | Throttle y rudder (volante) |
|
||
| `ABS_HAT0X`, `ABS_HAT0Y` | D-pad (cruceta direccional) |
|
||
|
||
### EV_SYN (tipo = 0): Sincronización
|
||
|
||
| Código | Significado |
|
||
|---|---|
|
||
| `SYN_REPORT` | Todos los eventos anteriores forman un frame completo |
|
||
| `SYN_CONFIG` | La configuración del dispositivo cambió |
|
||
| `SYN_MT_REPORT` | Fin de un reporte multitouch (para pantallas táctiles) |
|
||
|
||
## 5.2 Decodificar un evento paso a paso
|
||
|
||
Acá tenés un ejemplo mínimo que traduce cada evento en texto legible:
|
||
|
||
```c
|
||
#include <stdio.h>
|
||
#include <linux/input.h>
|
||
#include <string.h>
|
||
|
||
void decode_event(struct input_event *ev) {
|
||
switch (ev->type) {
|
||
case EV_KEY: {
|
||
// Traducir códigos de tecla a nombre legible
|
||
const char *key_name = "unknown";
|
||
|
||
if (ev->code >= KEY_A && ev->code <= KEY_Z)
|
||
key_name = "A+"; // Se calcula en runtime: 'A' + (ev->code - KEY_A)
|
||
|
||
// Para EV_KEY, value indica estado: 0=soltado, 1=presionado, 2=repeat
|
||
const char *state;
|
||
if (ev->value == 0) state = "released";
|
||
else if (ev->value == 1) state = "pressed";
|
||
else state = "repeat";
|
||
|
||
printf("EV_KEY %-8s → %s\n", key_name, state);
|
||
break;
|
||
}
|
||
|
||
case EV_REL: {
|
||
// Para EV_REL, value es un delta (cuánto se movió desde el último evento)
|
||
printf("EV_REL %-8s %+d\n",
|
||
ev->code == REL_X ? "REL_X" :
|
||
ev->code == REL_Y ? "REL_Y" :
|
||
ev->code == REL_WHEEL ? "REL_WHEEL" : "?",
|
||
ev->value);
|
||
break;
|
||
}
|
||
|
||
case EV_ABS: {
|
||
// Para EV_ABS, value es una posición absoluta dentro del rango del dispositivo
|
||
printf("EV_ABS %-8s → %d\n",
|
||
ev->code == ABS_X ? "ABS_X" : "?",
|
||
ev->value);
|
||
break;
|
||
}
|
||
|
||
case EV_SYN: {
|
||
// Frame completado — todos los eventos de este tick están procesados
|
||
printf("EV_SYN SYN_REPORT → frame completo\n");
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
> **Nunca procesás un evento aislado.** Siempre mirás el patrón: `EV_KEY + KEY_A (1)` seguido de
|
||
> `EV_SYN` significa "el usuario presionó A en este frame". El SYN es el delimitador de cada tick.
|
||
|
||
## 5.3 Combinaciones de teclas: detectar modificadores
|
||
|
||
Una combinación como `Ctrl+C` es simplemente dos `EV_KEY` con `value=1`:
|
||
1. `KEY_LEFTCTRL` pressed (value=1)
|
||
2. `KEY_C` pressed (value=1)
|
||
|
||
Para detectar combinaciones, mantenés un bitmask de teclas presionadas:
|
||
|
||
```c
|
||
#define KEY_STATE_WORDS ((KEY_MAX / 32) + 1)
|
||
uint32_t key_state[KEY_STATE_WORDS]; // bitmask de teclas actualmente presionadas
|
||
|
||
void update_key_state(struct input_event *ev) {
|
||
if (ev->type != EV_KEY) return;
|
||
|
||
int word = ev->code / 32;
|
||
int bit = ev->code % 32;
|
||
|
||
// Solo procesamos press (1) y release (0), ignoramos repeat (2)
|
||
if (ev->value == 1) {
|
||
key_state[word] |= (1U << bit); // presionó → setear bit
|
||
} else if (ev->value == 0) {
|
||
key_state[word] &= ~(1U << bit); // soltó → limpiar bit
|
||
}
|
||
}
|
||
|
||
// Verificar Ctrl+C:
|
||
int is_ctrl_c(void) {
|
||
return (key_state[KEY_LEFTCTRL / 32] & (1U << (KEY_LEFTCTRL % 32))) &&
|
||
(key_state['C' - KEY_A + ('A' / 32)] & (1U << (('C' - KEY_A) % 32)));
|
||
}
|
||
|
||
// Verificar cualquier combinación:
|
||
int is_key_combo(uint16_t key1, uint16_t key2) {
|
||
int w1 = key1 / 32, b1 = key1 % 32;
|
||
int w2 = key2 / 32, b2 = key2 % 32;
|
||
return (key_state[w1] & (1U << b1)) && (key_state[w2] & (1U << b2));
|
||
}
|
||
```
|
||
|
||
> **Ojo con el repeat:** los eventos de "repeat" (`value=2`) NO deben agregar ni quitar del bitmask —
|
||
> ya está presionado. Solo procesá `value == 1` para el primer press y `value == 0` para release.
|
||
|
||
## 5.4 Lo que te llevás de esta parte
|
||
|
||
- **Los tipos son tres: KEY, REL, ABS.** KEY = botones/teclas (0/1/repeat), REL = deltas
|
||
(movimiento del mouse), ABS = posiciones absolutas (joystick).
|
||
- **SYN_REPORT separa frames.** Todo lo que viene antes de un SYN_REPORT pertenece al mismo tick.
|
||
- **Las combinaciones se detectan con bitmask.** Mantenés un array de `uint32_t` donde cada bit
|
||
representa una tecla: presionado o no.
|
||
|
||
### Práctica de la Parte 5
|
||
|
||
#### Ejercicio 26.5 — Decodificador universal de eventos evdev
|
||
|
||
**Conceptos** · `EV_KEY`, `EV_REL`, `EV_ABS`, bitmask, lectura cruda de `/dev/input/eventN`
|
||
|
||
Escribí un programa que lea eventos de **todos los dispositivos de input** (usando el ejercicio 26.4
|
||
para enumerar y clasificar) y decodifique cada evento en texto legible:
|
||
|
||
```
|
||
[05:12:33.456789] event3 KEY A pressed
|
||
[05:12:33.512345] event3 KEY A released
|
||
[05:12:34.000001] event7 REL_X +3
|
||
[05:12:34.000002] event7 REL_Y -1
|
||
[05:12:34.100000] event7 BTN_LEFT pressed
|
||
[05:12:34.200000] event12 ABS_X +128
|
||
[05:12:34.200001] event12 ABS_Y -64
|
||
[05:12:34.300000] event12 ABS_Z +32767
|
||
[05:12:34.300001] event12 BTN_TRIGGER pressed
|
||
```
|
||
|
||
**Criterios de aceptación:**
|
||
- [ ] Enumerás todos los `eventN` con libudev (ejercicio 26.4).
|
||
- [ ] Decodificás correctamente tipos: KEY, REL, ABS.
|
||
- [ ] Traducís códigos a nombres legibles (`KEY_A`, `BTN_LEFT`, `ABS_X`).
|
||
- [ ] Para EV_KEY, mostrás "pressed"/"released"/"repeat".
|
||
- [ ] Para EV_REL, mostrás el delta con signo `+N`/`-N`.
|
||
|
||
---
|
||
|
||
# PARTE 6 — Mouse: movimiento relativo y botones
|
||
|
||
## 6.1 El mouse como dispositivo evdev
|
||
|
||
Un mouse USB típico genera eventos en **varios event nodes** (uno para el mouse base, otro opcional
|
||
para botones adicionales). Los eventos clave son:
|
||
|
||
```
|
||
Mouse base → /dev/input/eventN:
|
||
EV_KEY + BTN_LEFT/BTN_MIDDLE/BTN_RIGHT → botones principales
|
||
EV_REL + REL_X → movimiento X
|
||
EV_REL + REL_Y → movimiento Y
|
||
EV_REL + REL_WHEEL → scroll vertical
|
||
EV_REL + REL_HWHEEL → scroll horizontal
|
||
|
||
Mouse gaming → /dev/input/eventM (secundario):
|
||
EV_KEY + BTN_SIDE/BTN_EXTRA → botones laterales
|
||
```
|
||
|
||
## 6.2 Leer el mouse crudo
|
||
|
||
Acá tenés un ejemplo que lee y decodifica eventos de un mouse:
|
||
|
||
```c
|
||
#include <linux/input.h>
|
||
|
||
int main(void) {
|
||
int fd = open("/dev/input/event7", O_RDONLY | O_NONBLOCK);
|
||
if (fd < 0) return 1;
|
||
|
||
struct input_event ev;
|
||
int dx = 0, dy = 0, scroll = 0;
|
||
|
||
while (read(fd, &ev, sizeof(ev)) == sizeof(ev)) {
|
||
switch (ev.type) {
|
||
case EV_KEY:
|
||
switch (ev.code) {
|
||
case BTN_LEFT:
|
||
printf("Botón izquierdo: %s\n",
|
||
ev.value ? "pressed" : "released");
|
||
break;
|
||
case BTN_RIGHT:
|
||
printf("Botón derecho: %s\n",
|
||
ev.value ? "pressed" : "released");
|
||
break;
|
||
case BTN_MIDDLE:
|
||
printf("Botón central: %s\n",
|
||
ev.value ? "pressed" : "released");
|
||
break;
|
||
}
|
||
break;
|
||
|
||
case EV_REL:
|
||
switch (ev.code) {
|
||
case REL_X:
|
||
dx += ev.value;
|
||
printf("Mouse X delta: %+d (acumulado: %d)\n",
|
||
ev.value, dx);
|
||
break;
|
||
case REL_Y:
|
||
dy += ev.value;
|
||
printf("Mouse Y delta: %+d (acumulado: %d)\n",
|
||
ev.value, dy);
|
||
break;
|
||
case REL_WHEEL:
|
||
scroll += ev.value;
|
||
printf("Scroll: %+d (acumulado: %d)\n",
|
||
ev.value, scroll);
|
||
break;
|
||
}
|
||
break;
|
||
|
||
case EV_SYN:
|
||
// Frame completo — imprimir estado actual
|
||
printf("[cursor dx=%+d dy=%+d | buttons: L?M?R? | scroll=%d]\n",
|
||
dx, dy, scroll);
|
||
break;
|
||
}
|
||
}
|
||
|
||
close(fd);
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
## 6.3 El botón de rueda (scroll) como tecla
|
||
|
||
Curiosamente, `BTN_SIDE` y `BTN_EXTRA` se comportan como botones normales (`EV_KEY`). Solo el scroll
|
||
vertical (`REL_WHEEL`) es relativo porque mide **cuántas líneas se movió**, no una posición absoluta.
|
||
|
||
> **Nota sobre la dirección del scroll:** Los valores de `REL_WHEEL` son opuestos a lo que mucha
|
||
> gente espera:
|
||
> - `+1`: scroll hacia abajo (alejado del usuario, "down")
|
||
> - `-1`: scroll hacia arriba (hacia el usuario, "up")
|
||
>
|
||
> Esto viene del protocolo X11 original y se mantiene por compatibilidad.
|
||
|
||
## 6.4 Lo que te llevás de esta parte
|
||
|
||
- **El mouse reporta movimiento como deltas REL**, no posiciones absolutas. Tenés que acumularlos.
|
||
- **Los botones son eventos KEY con BTN_LEFT, BTN_RIGHT, etc.** Value=1 = presionado, value=0 = soltado.
|
||
- **REL_WHEEL tiene dirección opuesta a lo intuitivo** por compatibilidad histórica con X11.
|
||
|
||
### Práctica de la Parte 6
|
||
|
||
#### Ejercicio 26.6 — Monitor de mouse en tiempo real
|
||
|
||
**Conceptos** · `EV_KEY` + botones, `EV_REL` + movimiento, acumulación de deltas
|
||
|
||
Leé el dispositivo mouse desde `/dev/input/eventN` y mostrá en cada evento:
|
||
- Estado actual del cursor (acumulación de deltas X/Y)
|
||
- Estado de cada botón (presionado o no)
|
||
- Posición acumulada del scroll
|
||
|
||
```
|
||
Mouse state: dx=+42 dy=-7 | Buttons: [L:down M:up R:down] | Scroll: +3
|
||
Mouse state: dx=+15 dy=-2 | Buttons: [L:up M:up R:down] | Scroll: +3
|
||
Mouse state: dx=+15 dy=-2 | Buttons: [L:up M:up R:up] | Scroll: +4
|
||
```
|
||
|
||
**Criterios de aceptación:**
|
||
- [ ] Detectás automáticamente el dispositivo mouse por sus propiedades udev (ejercicio 26.4).
|
||
- [ ] Acumulás deltas de X/Y correctamente (no reseteás entre eventos).
|
||
- [ ] Mostrás el estado de botones como bitmap: `[L:down M:up R:down]`.
|
||
|
||
---
|
||
|
||
# PARTE 7 — Joystick: ejes absolutos y botones
|
||
|
||
## 7.1 El joystick como dispositivo evdev
|
||
|
||
Un gamepad moderno (Xbox, PlayStation, Logitech) expone múltiples ejes y botones a través del
|
||
mismo `/dev/input/eventN`:
|
||
|
||
```
|
||
Joystick → /dev/input/eventN:
|
||
EV_ABS + ABS_X → Eje horizontal izquierdo (stick L)
|
||
EV_ABS + ABS_Y → Eje vertical izquierdo (stick L)
|
||
EV_ABS + ABS_Z → Trigger izquierdo (L2 en PS, RT en Xbox)
|
||
EV_ABS + ABS_RZ → Trigger derecho (R2 en PS, LT en Xbox)
|
||
EV_ABS + ABS_THROTTLE→ Pedal (si hay volantes)
|
||
EV_ABS + ABS_HAT0X,Y → D-pad (cruceta direccional)
|
||
|
||
Joystick buttons:
|
||
EV_KEY + BTN_SOUTH → Botón "A" / "B" según región
|
||
EV_KEY + BTN_EAST → Botón "B" / "A"
|
||
EV_KEY + BTN_NORTH → Botón "X" / "Y"
|
||
EV_KEY + BTN_WEST → Botón "Y" / "X"
|
||
EV_KEY + BTN_TL → L1 (shoulder izquierdo)
|
||
EV_KEY + BTN_TR → R1 (shoulder derecho)
|
||
EV_KEY + BTN_START → Botón "Start" / "Options"
|
||
EV_KEY + BTN_SELECT → Botón "Select" / "Share"
|
||
EV_KEY + BTN_THUMB → L3 (click del stick izquierdo)
|
||
EV_KEY + BTN_THUMB2 → R3 (click del stick derecho)
|
||
```
|
||
|
||
## 7.2 Calibrar ejes con `EVIOCGABS`
|
||
|
||
Cada eje tiene un **rango** y **parámetros de calibración**. Podés consultarlo con `EVIOCGABS`:
|
||
|
||
```c
|
||
struct input_absinfo abs;
|
||
ioctl(fd, EVIOCGABS(ABS_X), &abs);
|
||
|
||
printf("Eje ABS_X:\n");
|
||
printf(" min: %d\n", abs.minimum); // valor mínimo del eje
|
||
printf(" max: %d\n", abs.maximum); // valor máximo del eje
|
||
printf(" fuzz: %d\n", abs.fuzz); // ruido aceptable (ignorar cambios menores)
|
||
printf(" flat: %d\n", abs.flat); // zona muerta central (deadzone)
|
||
printf(" resolution %d\n", abs.resolution); // unidades por mm (o grado, etc.)
|
||
```
|
||
|
||
> **No asumas que todos los ejes van de `-32768` a `+32767`.** Joysticks baratos, pads de guitarra,
|
||
> volantes — cada uno puede tener rangos distintos. Siempre consultá con `EVIOCGABS`.
|
||
|
||
### 7.2.1 La deadzone: ignorar el drift del stick central
|
||
|
||
Casi todos los joysticks tienen una **deadzone**: una zona pequeña alrededor del centro donde el eje
|
||
no debería moverse, pero por imperfecciones del hardware sí reporta valores pequeños (drift). El
|
||
kernel reporta esta zona en `abs.flat`:
|
||
|
||
```c
|
||
int is_centered(int abs_value, struct input_absinfo *abs) {
|
||
// Si el valor está dentro de la deadzone, lo consideramos centrado
|
||
return abs(abs_value) < abs->flat;
|
||
}
|
||
|
||
// Normalizar un eje al rango [-100, +100] ignorando la deadzone:
|
||
int normalize_axis(int raw_value, struct input_absinfo *abs) {
|
||
// Si está en la deadzone, retornar 0
|
||
if (is_centered(raw_value, abs)) return 0;
|
||
|
||
// Mapear al rango [-100, +100], manteniendo el signo
|
||
int range = abs->maximum - abs->minimum;
|
||
float normalized = ((float)(raw_value - abs->minimum) / range - 0.5f) * 200.0f;
|
||
|
||
// Clamp a [-100, +100]
|
||
if (normalized > 100.0f) normalized = 100.0f;
|
||
if (normalized < -100.0f) normalized = -100.0f;
|
||
|
||
return (int)normalized;
|
||
}
|
||
```
|
||
|
||
> **¿Por qué normalizar?** Porque un joystick puede reportar `ABS_X` de `-32768` a `+32767`, otro
|
||
> de `0` a `65535`, y otro de `1000` a `64000`. Normalizar al rango `[-100, +100]` te da un valor
|
||
> independiente del dispositivo.
|
||
|
||
### 7.2.2 Mapear botones del joystick a nombres legibles
|
||
|
||
```c
|
||
const char* get_key_name(uint16_t code) {
|
||
switch (code) {
|
||
case BTN_SOUTH: return "SOUTH"; // A/B según región
|
||
case BTN_EAST: return "EAST"; // B/A
|
||
case BTN_NORTH: return "NORTH"; // X/Y
|
||
case BTN_WEST: return "WEST"; // Y/X
|
||
case BTN_TL: return "TL"; // L1 (shoulder izq)
|
||
case BTN_TR: return "TR"; // R1 (shoulder der)
|
||
case BTN_START: return "START";
|
||
case BTN_SELECT: return "SELECT";
|
||
case BTN_THUMB: return "THUMB"; // L3 (stick click izq)
|
||
case BTN_THUMB2: return "THUMB2"; // R3 (stick click der)
|
||
default: return "UNKNOWN";
|
||
}
|
||
}
|
||
|
||
const char* get_axis_name(uint16_t code) {
|
||
switch (code) {
|
||
case ABS_X: return "X (L-stick H)";
|
||
case ABS_Y: return "Y (L-stick V)";
|
||
case ABS_Z: return "Z (LT trigger)";
|
||
case ABS_RZ: return "RZ (RT trigger)";
|
||
case ABS_HAT0X: return "HAT0X (D-pad H)";
|
||
case ABS_HAT0Y: return "HAT0Y (D-pad V)";
|
||
default: return "UNKNOWN";
|
||
}
|
||
}
|
||
```
|
||
|
||
## 7.3 Leer el joystick crudo en acción
|
||
|
||
Acá tenés un ejemplo completo de lectura de un gamepad:
|
||
|
||
```c
|
||
#include <linux/input.h>
|
||
|
||
#define MAX_AXES 12
|
||
|
||
typedef struct {
|
||
int fd;
|
||
char name[256];
|
||
struct input_absinfo abs_info[MAX_AXES]; // parámetros de cada eje
|
||
uint32_t key_state[(KEY_MAX / 32) + 1]; // bitmask de botones
|
||
} JoystickContext;
|
||
|
||
void init_joystick(JoystickContext *ctx, const char *path) {
|
||
ctx->fd = open(path, O_RDONLY | O_NONBLOCK);
|
||
ioctl(ctx->fd, EVIOCGNAME(sizeof(ctx->name)), ctx->name);
|
||
|
||
// Obtener parámetros de cada eje soportado
|
||
for (int i = ABS_X; i <= MAX_AXES; i++) {
|
||
ioctl(ctx->fd, EVIOCGABS(i), &ctx->abs_info[i - ABS_X]);
|
||
}
|
||
}
|
||
|
||
void poll_joystick(JoystickContext *ctx) {
|
||
struct input_event ev;
|
||
|
||
while (read(ctx->fd, &ev, sizeof(ev)) == sizeof(ev)) {
|
||
switch (ev.type) {
|
||
case EV_KEY: {
|
||
int word = ev.code / 32;
|
||
int bit = ev.code % 32;
|
||
if (ev.value == 1) {
|
||
ctx->key_state[word] |= (1U << bit); // press
|
||
} else if (ev.value == 0) {
|
||
ctx->key_state[word] &= ~(1U << bit); // release
|
||
}
|
||
printf("Joystick %-20s %-8s %s\n",
|
||
ctx->name, get_key_name(ev.code),
|
||
ev.value == 1 ? "pressed" : "released");
|
||
break;
|
||
}
|
||
|
||
case EV_ABS: {
|
||
int norm = normalize_axis(ev.value, &ctx->abs_info[ev.code - ABS_X]);
|
||
printf("Joystick %-20s %-15s raw=%+6d norm=%+4d\n",
|
||
ctx->name, get_axis_name(ev.code), ev.value, norm);
|
||
break;
|
||
}
|
||
|
||
case EV_SYN:
|
||
// Frame completo
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 7.4 Lo que te llevás de esta parte
|
||
|
||
- **El joystick usa ABS para ejes y KEY para botones.** Los ejes reportan posiciones absolutas, no deltas.
|
||
- **Siempre consultá `EVIOCGABS`.** El rango puede variar entre dispositivos — nunca asumas `-32768` a `+32767`.
|
||
- **La deadzone es real y medible.** Usá `abs.flat` para ignorar el drift del stick centrado.
|
||
|
||
### Práctica de la Parte 7
|
||
|
||
#### Ejercicio 26.7 — Monitor de joystick en tiempo real
|
||
|
||
**Conceptos** · `EV_ABS`, `EV_KEY`, `EVIOCGABS`, deadzone, normalización
|
||
|
||
Leé el dispositivo joystick desde `/dev/input/eventN` y mostrá para cada evento:
|
||
- Estado de cada eje (valor raw + valor normalizado a `[-100, +100]`)
|
||
- Estado de cada botón (presionado o soltado)
|
||
- La deadzone calculada con `EVIOCGABS`
|
||
|
||
```
|
||
Joystick: Xbox Wireless Controller
|
||
ABS_X raw=+32800 norm= +1 flat=12 (centrado, drift ignorado)
|
||
ABS_Y raw=-64 norm= -1 flat=12 (cerca del centro)
|
||
ABS_Z raw=+65535 norm=+100 flat=0 (trigger a fondo)
|
||
BTN_SOUTH pressed → Botón A presionado
|
||
BTN_TR released → R1 soltado
|
||
```
|
||
|
||
**Criterios de aceptación:**
|
||
- [ ] Usás `EVIOCGABS` para obtener el rango y deadzone de cada eje.
|
||
- [ ] Normalizás valores al rango `[-100, +100]` respetando la deadzone.
|
||
- [ ] Mostrás tanto valor raw como normalizado.
|
||
- [ ] Detectás automáticamente el dispositivo joystick por sus propiedades udev (ejercicio 26.4).
|
||
|
||
---
|
||
|
||
# PARTE 8 — Ejercicio jefe: capturador universal de input con libinput
|
||
|
||
Llegó el momento de juntar **todo** lo que aprendiste en un solo programa: enumeración de
|
||
dispositivos con libudev, lectura cruda de eventos evdev, clasificación keyboard/mouse/joystick,
|
||
y ahora la capa final: **libinput**.
|
||
|
||
## 8.1 ¿Por qué libinput?
|
||
|
||
Llegados a este punto ya podés leer eventos crudos de cualquier dispositivo. Pero leer `EV_KEY`,
|
||
`EV_REL` y `EV_ABS` manualmente tiene problemas:
|
||
|
||
- **No hace calibración automática:** la deadzone del joystick, el sensitivity del mouse... todo lo hacés vos.
|
||
- **No normaliza entre dispositivos:** un Xbox Controller y un DualSense reportan ejes distintos.
|
||
- **No maneja gestos:** scroll, pinch-to-zoom, tap-to-click... todo hay que calcularlo a mano.
|
||
- **No abstrae el backend:** si querés que tu app funcione en X11, Wayland o TTY, tenés que cambiar código.
|
||
|
||
**libinput resuelve todo eso.** Es la biblioteca de freedesktop.org para manejo de input devices:
|
||
|
||
> **libinput es lo que usa GNOME, KDE y Wayland por debajo.** Es el estándar de facto en Linux
|
||
> para manejar teclado, mouse y touchpad. Y además soporta joysticks desde la versión 1.26+.
|
||
|
||
Con libinput, no leés `input_event` directamente: creás un `libinput_handle`, le pasás los fds de
|
||
los dispositivos, y recibís **eventos semánticos**: `LIBINPUT_EVENT_KEYBOARD_KEY`,
|
||
`LIBINPUT_EVENT_POINTER_MOTION`, `LIBINPUT_EVENT_TOUCH_DOWN`...
|
||
|
||
## 8.2 El flujo básico de libinput
|
||
|
||
```c
|
||
#include <libinput.h>
|
||
|
||
// 1. Crear un contexto libinput (una sola vez)
|
||
struct libinput *li = libinput_create_context(&libinput_interface, NULL);
|
||
|
||
// 2. Crear una "tablet" que agrupa dispositivos del mismo tipo
|
||
struct libinput_tablet *tablet;
|
||
|
||
// 3. Agregar dispositivos al contexto:
|
||
// libinput necesita los fds de los dispositivos para poll().
|
||
// Usá libudev_get_fd() para obtener el fd desde un device node udev.
|
||
libinput_path_add_device(li, "/dev/input/event7");
|
||
|
||
// 4. Activar el dispositivo:
|
||
libinput_device_enable(libinput_device_from_tablet(tablet));
|
||
|
||
// 5. Bucle principal: leer eventos con libinput_dispatch() + libinput_get_event()
|
||
while (1) {
|
||
// poll() espera en los fds que libinput registró
|
||
if (poll(NULL, 0, -1) < 0) break;
|
||
|
||
// libinput lee todos los eventos disponibles y los devuelve uno por uno
|
||
struct libinput_event *event = libinput_get_event(li);
|
||
if (!event) break;
|
||
|
||
// Procesar el evento según su tipo:
|
||
switch (libinput_event_get_type(event)) {
|
||
case LIBINPUT_EVENT_KEYBOARD_KEY:
|
||
handle_key_event(event);
|
||
break;
|
||
case LIBINPUT_EVENT_POINTER_MOTION:
|
||
handle_mouse_motion(event);
|
||
break;
|
||
case LIBINPUT_EVENT_POINTER_BUTTON:
|
||
handle_mouse_button(event);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
// IMPORTANTE: libinput necesita que confirmes que procesaste el evento
|
||
libinput_event_destroy(event);
|
||
}
|
||
|
||
// 6. Limpieza
|
||
libinput_remove_device(libinput_device_from_tablet(tablet), NULL);
|
||
libinput_unref(li);
|
||
```
|
||
|
||
> **`libinput_dispatch()` + `poll()`:** Este es el patrón fundamental de libinput. No leés eventos
|
||
> directamente del fd — en vez de eso, llamás a `libinput_dispatch()` que lee todos los eventos
|
||
> disponibles y los pone en una cola interna. Después, `libinput_get_event()` saca un evento de la
|
||
> cola uno por uno. Entre medio, usás `poll()` para esperar efficientemente cuando no hay eventos.
|
||
|
||
## 8.3 Teclado con libinput
|
||
|
||
```c
|
||
void handle_key_event(struct libinput_event *event) {
|
||
struct libinput_event_keyboard *k = libinput_event_get_keyboard_event(event);
|
||
|
||
// Timestamp en milisegundos (libinput lo normaliza por vos)
|
||
uint32_t time_ms = libinput_event_keyboard_get_time(k);
|
||
|
||
// Código de tecla: libinput devuelve el code de evdev directamente
|
||
uint32_t key = libinput_event_keyboard_get_key(k);
|
||
|
||
// Estado: down (presionado) o up (soltado)
|
||
enum libinput_config_keyboard_repeating_state repeat;
|
||
if (libinput_event_keyboard_get_key_state(k) == LIBINPUT_KEY_STATE_PRESSED) {
|
||
printf("[%u] KEY 0x%04x DOWN\n", time_ms, key);
|
||
} else {
|
||
printf("[%u] KEY 0x%04x UP\n", time_ms, key);
|
||
}
|
||
}
|
||
```
|
||
|
||
> **libinput devuelve los codes de tecla directamente como evdev codes.** No necesitás traducción:
|
||
> `KEY_A`, `KEY_ENTER`, `KEY_LEFTCTRL` son los mismos valores que ya conocés.
|
||
|
||
## 8.4 Mouse con libinput
|
||
|
||
```c
|
||
void handle_mouse_motion(struct libinput_event *event) {
|
||
struct libinput_event_pointer *p = libinput_event_get_pointer_event(event);
|
||
|
||
// Delta de movimiento en mm (libinput normaliza la sensibilidad del device)
|
||
double dx_mm = libinput_event_pointer_get_dx(p);
|
||
double dy_mm = libinput_event_pointer_get_dy(p);
|
||
|
||
// ¿Hubo un cambio real de posición?
|
||
if (libinput_event_pointer_has_axis(p, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) {
|
||
double scroll_v = libinput_event_pointer_get_scroll_value_virt(p);
|
||
printf("Scroll vertical: %.2f\n", scroll_v);
|
||
}
|
||
|
||
// Convertir mm a unidades lógicas (libinput lo hace automáticamente con dx/dy)
|
||
printf("Motion: %.2f x %.2f mm\n", dx_mm, dy_mm);
|
||
}
|
||
|
||
void handle_mouse_button(struct libinput_event *event) {
|
||
struct libinput_event_pointer *p = libinput_event_get_pointer_event(event);
|
||
|
||
// Botón presionado o soltado
|
||
enum libinput_btn btn = libinput_event_pointer_get_button(p);
|
||
enum libinput_button_state state = libinput_event_pointer_get_button_state(p);
|
||
|
||
// btn es BTN_LEFT, BTN_RIGHT, etc. — los mismos codes de evdev
|
||
printf("Button %s: %s\n",
|
||
btn == BTN_LEFT ? "LEFT" : btn == BTN_RIGHT ? "RIGHT" : "OTHER",
|
||
state == LIBINPUT_BUTTON_STATE_PRESSED ? "pressed" : "released");
|
||
}
|
||
```
|
||
|
||
> **libinput normaliza la sensibilidad del mouse.** Diferentes mice reportan deltas en unidades
|
||
> distintas (píxeles por movimiento, counts de rueda, etc.). libinput convierte todo a **mm**
|
||
> y te devuelve `dx`/`dy` en esas unidades. La conversión de sensitivity es interna.
|
||
|
||
## 8.5 Joystick con libinput
|
||
|
||
Desde la versión 1.26+, libinput soporta joysticks/gamspads:
|
||
|
||
```c
|
||
void handle_joystick_event(struct libinput_event *event) {
|
||
struct libinput_event_gesture *g;
|
||
|
||
// libinput maneja la deadzone y normalización por vos
|
||
double value = libinput_event_gesture_get_distance(g); // [0.0, 1.0] normalizado
|
||
|
||
// Estado del botón: DOWN o UP
|
||
uint32_t button = libinput_event_gesture_get_button(g);
|
||
enum libinput_button_state state = libinput_event_gesture_get_button_state(g);
|
||
}
|
||
```
|
||
|
||
> **No todos los devices soportados por evdev son soportados por libinput.** libinput filtra los
|
||
> dispositivos que conoce. Si un dispositivo genérico no es reconocido, simplemente no aparecerá.
|
||
|
||
## 8.6 Estructura del ejercicio jefe
|
||
|
||
Escribí una **librería de input en C** que unifique la captura de teclado, mouse y joystick con
|
||
una API simple basada en callbacks:
|
||
|
||
```c
|
||
// Declaración de callbacks (el usuario los implementa)
|
||
typedef void (*key_event_fn)(uint32_t time_ms, uint16_t key_code, int pressed);
|
||
typedef void (*mouse_button_fn)(uint32_t time_ms, uint16_t button, int pressed);
|
||
typedef void (*mouse_motion_fn)(uint32_t time_ms, double dx_mm, double dy_mm);
|
||
typedef void (*joystick_axis_fn)(uint32_t time_ms, int axis_id, float value);
|
||
typedef void (*joystick_button_fn)(uint32_t time_ms, uint16_t button, int pressed);
|
||
|
||
// Estructura principal de la librería
|
||
typedef struct {
|
||
struct libinput *li;
|
||
key_event_fn on_key_down;
|
||
key_event_fn on_key_up;
|
||
mouse_button_fn on_mouse_button;
|
||
mouse_motion_fn on_mouse_motion;
|
||
joystick_axis_fn on_joystick_axis;
|
||
joystick_button_fn on_joystick_button;
|
||
int running; // flag para el loop principal
|
||
} input_ctx_t;
|
||
|
||
// API pública de la librería:
|
||
int input_init(input_ctx_t *ctx); // Inicializar
|
||
void input_set_callbacks(input_ctx_t *ctx, key_event_fn kdown, ...); // Configurar callbacks
|
||
void input_poll(input_ctx_t *ctx); // Bucle principal (bloqueante)
|
||
void input_shutdown(input_ctx_t *ctx); // Limpieza
|
||
```
|
||
|
||
**Requisitos obligatorios:**
|
||
|
||
1. **Inicialización automática:** `input_init()` debe encontrar todos los dispositivos de entrada
|
||
conectados (teclado, mouse, joystick) usando libudev y agregarlos al contexto libinput.
|
||
Si un dispositivo no es soportado por libinput, debe ignorarlo silenciosamente.
|
||
|
||
2. **Callbacks configurables:** El usuario define qué hacer cuando llega un evento. La librería
|
||
NO imprime nada por sí misma — solo llama a los callbacks. Los callbacks reciben:
|
||
- `time_ms`: timestamp en milisegundos (normalizado por libinput)
|
||
- Datos específicos del tipo de evento
|
||
|
||
3. **Bucle principal con poll():** `input_poll()` usa `poll()` en el fd de libinput para esperar
|
||
eventos eficientemente. Mientras no haya eventos, el thread duerme. Cuando llega un evento,
|
||
lo dispatchea y llama al callback correspondiente.
|
||
|
||
4. **Manejo de hotplug:** Si se conecta o desconecta un dispositivo durante la ejecución:
|
||
- Conexión → agregar automáticamente al contexto libinput
|
||
- Desconexión → remover del contexto y liberar recursos
|
||
|
||
5. **Sin fugas de memoria:** Todos los objetos libinput deben ser referenciados correctamente.
|
||
Al llamar a `input_shutdown()`, todo el contexto debe quedar liberado.
|
||
|
||
**Ejemplo de uso (cómo lo usa el usuario):**
|
||
|
||
```c
|
||
// El usuario implementa sus callbacks:
|
||
void on_key_down(uint32_t time_ms, uint16_t key_code, int pressed) {
|
||
printf("[%u] KEY DOWN 0x%04x\n", time_ms, key_code);
|
||
}
|
||
|
||
void on_mouse_button(uint32_t time_ms, uint16_t button, int pressed) {
|
||
if (pressed)
|
||
printf("[%u] MOUSE BTN %d DOWN\n", time_ms, button);
|
||
}
|
||
|
||
int main(void) {
|
||
input_ctx_t ctx = {0};
|
||
input_set_callbacks(&ctx, on_key_down, NULL, on_mouse_button, NULL, NULL, NULL);
|
||
|
||
if (input_init(&ctx) < 0) {
|
||
fprintf(stderr, "Failed to initialize input\n");
|
||
return 1;
|
||
}
|
||
|
||
printf("Capturing input... Press Ctrl+C to exit.\n");
|
||
input_poll(&ctx); // Bucle principal — bloqueante
|
||
|
||
input_shutdown(&ctx);
|
||
return 0;
|
||
}
|
||
```
|
||
|
||
**Criterios de aceptación:**
|
||
|
||
### Funcionalidad básica (obligatorio)
|
||
- [ ] `input_init()` encuentra y registra automáticamente todos los dispositivos de input.
|
||
- [ ] Los callbacks se invocan correctamente para cada tipo de evento (key, mouse, joystick).
|
||
- [ ] `input_poll()` bloquea eficientemente con `poll()` cuando no hay eventos.
|
||
- [ ] `input_shutdown()` limpia todo sin fugas.
|
||
|
||
### Manejo de combinaciones
|
||
- [ ] Se detectan combinaciones de teclas: Ctrl+C, Alt+Tab, Meta+E (implementar un detector básico).
|
||
- [ ] El callback de key_down y key_up se invocan en el orden correcto del evento evdev.
|
||
|
||
### Robustez
|
||
- [ ] Maneja dispositivos que aparecen/desaparecen entre polls (hotplug).
|
||
- [ ] Si libinput no soporta un dispositivo, lo ignora sin crash.
|
||
- [ ] No hay fugas de memoria verificadas con valgrind o sanitizers.
|
||
|
||
### Código limpio
|
||
- [ ] La API es simple y consistente: `init()`, `poll()`, `shutdown()`.
|
||
- [ ] Los callbacks son typedefs claros, no funciones crudas con firmas confusas.
|
||
- [ ] La librería encapsula libinput — el usuario final nunca ve `struct libinput_*` directamente.
|
||
|
||
## 8.7 Compilación del ejercicio jefe
|
||
|
||
```bash
|
||
# Necesitás: libinput-dev, libudev-dev
|
||
gcc input.c exercise_jefe.c -o input_demo \
|
||
$(pkg-config --cflags --libs libinput libudev)
|
||
```
|
||
|
||
> **Verificación con valgrind:** Corré `valgrind --leak-check=full ./input_demo` para confirmar
|
||
> que no hay fugas de memoria. Libinput maneja la mayoría, pero si creás estructuras propias
|
||
> (como `input_ctx_t`), asegurate de liberar todo en `input_shutdown()`.
|
||
|
||
## 8.8 Lo que te llevás de este capítulo
|
||
|
||
Resumen rápido de lo que aprendiste (y las herramientas que ahora tenés):
|
||
|
||
- **El input subsystem es uniforme.** Teclado, mouse, joystick — todo viaja como `input_event`
|
||
por `/dev/input/eventN`. Un solo formato para todos los dispositivos.
|
||
|
||
- **Evdev es la capa más baja:** leés `struct input_event`, decodificás type/code/value a mano.
|
||
Potente pero detallista. Ideal cuando necesitás control total.
|
||
|
||
- **libudev es la capa de enumeración:** te dice qué dispositivo es cuál, vendor/product, hotplug.
|
||
Siempre usala antes de abrir un fd.
|
||
|
||
- **ioctl completa el picture:** `EVIOCGBIT` descubre qué hace un device, `EVIOCGNAME` su nombre,
|
||
`EVIOCGABS` los parámetros de ejes. Es la red de seguridad cuando udev no alcanza.
|
||
|
||
- **libinput es la capa de abstracción más alta:** normaliza sensibilidad, deadzones, gestures.
|
||
Usala para aplicaciones reales; usá evdev directo para herramientas de bajo nivel.
|
||
|
||
> **Felicidades.** Acabás de escribir un sistema completo de captura de input en Linux — desde los
|
||
> bytes crudos del kernel hasta una API de alto nivel con callbacks. Eso significa que entendés más
|
||
> del subsistema de input de Linux que el 95% de los desarrolladores de videojuegos y apps, porque
|
||
> la mayoría usa SDL o Raylib sin saber qué hay debajo.
|
||
|
||
---
|
||
|
||
## ¿Qué sigue?
|
||
|
||
Este capítulo cierra el recorrido por input en Linux desde cero absoluto hasta libinput. Los próximos
|
||
capítulos podrían explorar:
|
||
|
||
- **Input hotplug con daemon:** Un servicio que arranca con el sistema y mantiene lista una tabla de
|
||
dispositivos activos para reutilizarlos entre sesiones.
|
||
- **Event injection:** Escribir eventos de input simulados con `libevdev` o `uinput`.
|
||
- **Evdev raw mode:** Desactivar el proceso automático de repeat del kernel y controlar los delays
|
||
manualmente.
|
||
- **Joystick calibration wizard:** Una herramienta interactiva para detectar deadzones y rangos
|
||
exactos de cada eje.
|
||
|
||
¡Tu elección! El capítulo sobre input en Linux está completo — las ideas quedan a partir de ahí.
|