diff --git a/book/26-input-devices.md b/book/26-input-devices.md new file mode 100644 index 0000000..09e0c24 --- /dev/null +++ b/book/26-input-devices.md @@ -0,0 +1,1612 @@ +# 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 + +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 +#include +#include +#include + +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 +#include + +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 + +// 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 +#include +#include + +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 + +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 + +#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 + +// 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í. diff --git a/dist/26-input-devices.pdf b/dist/26-input-devices.pdf new file mode 100644 index 0000000..a7daeb3 Binary files /dev/null and b/dist/26-input-devices.pdf differ