El cursor dejaba de ser un cuadrado fijo. Ahora honra
`wl_pointer.set_cursor`: sobre el texto de una terminal sale la «I»,
sobre un enlace la mano, etc. — la forma la dibuja el cliente en una
superficie y el compositor la compone.
- `App` guarda un `cursor_status: CursorImageStatus`; el handler
`SeatHandler::cursor_image` lo actualiza.
- `render()` lo interpreta: `Surface` → compone el árbol de la
superficie del cursor en `pointer_loc - hotspot` (helper
`cursor_hotspot`, vía `CursorImageSurfaceData`); `Named` o sin tema →
el cuadrado de siempre; `Hidden` → nada.
- Sobre el escritorio pelado (sin cliente debajo) el cursor vuelve al
de por defecto, para que no se quede con la «I» de la última ventana.
- La superficie del cursor también recibe frame-callbacks (cursores
animados).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sin decoración, las ventanas se confundían entre sí. Ahora el backend
DRM dibuja un marco fino alrededor de cada ventana: azul la que tiene
el foco del teclado, gris las demás.
- `ManagedWindow` gana `focused: bool` (lo fija `exec_op` al atender
`BodyOp::Focus`/`Unfocus`) y `borders: [SolidColorBuffer; 4]` — un
búfer por lado, cada uno con su `Id` estable para el seguimiento de
daño; `SolidColorBuffer` sube su contador sólo si tamaño o color
cambian, así un marco quieto no fuerza recomposición.
- El enum `Frame` pasa de `Cursor` a `Solid`: una variante de color
sólido que sirve para el cursor y para los marcos (dos variantes con
el mismo tipo chocarían en el `From` que genera `render_elements!`).
- `render()` en dos pasos: refresca los búferes (tamaño = contenido,
color = foco) y luego arma los elementos. El marco va metido hacia
adentro, sobre el borde de la superficie, así no pisa al vecino.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Un escritorio sin forma de abrir una terminal no es usable. Ahora el
keymap puede lanzar programas:
- `mirada-protocol`: nuevo `BrainCommand::Spawn(String)`.
- `mirada-brain`: `DesktopAction::Spawn(String)` con forma textual
`spawn:<comando>` (`Display`/`FromStr`); `Desktop::apply` la traduce
a `BrainCommand::Spawn`. El keymap por defecto trae
`Super+Shift+Return` → `spawn:foot`. `DesktopAction` deja de ser
`Copy` (lleva el comando) — `Keymap::lookup` clona en vez de copiar.
- `mirada-body`: `BodyOp::Spawn(String)`.
- `mirada-compositor`: `exec_op` ejecuta el spawn con un helper
`spawn_command` (`sh -c`, hereda `WAYLAND_DISPLAY`), que también
recoge el lanzamiento de `MIRADA_STARTUP` — antes duplicado.
`spawn:foot --title x` también funciona desde `mirada-ctl`. Tests
nuevos del round-trip textual y del flujo atajo→comando.
Nota: un keymap.ron ya existente no recibe el atajo nuevo; hay que
añadir la línea a mano o borrar el archivo para regenerarlo.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`Super`+arrastre interactivo en el backend DRM: botón izquierdo mueve
la ventana, botón derecho la redimensiona. Al arrastrarla, la ventana
pasa a flotar — comportamiento estilo dwm.
La verdad geométrica vive en el Cerebro, así que el arrastre viaja
hasta él:
- `mirada-protocol`: nuevo `BodyEvent::WindowFloatTo { id, rect }`.
- `mirada-brain`: `Desktop::on_event` lo atiende — busca el escritorio
de la ventana y la hace flotar en ese rectángulo
(`Workspace::set_floating`). Dos tests nuevos.
- `mirada-compositor`: `DragGrab`/`DragMode` en `App`; `handle_input`
arranca el arrastre con `Super`+botón sobre una ventana
(`keyboard.modifier_state().logo`), traga los botones mientras dura y
lo cierra al soltar. `drag_update` recalcula el rectángulo (mover =
esquina sigue al puntero; redimensionar = esquina inferior-derecha,
con un mínimo de 120 px) y emite `WindowFloatTo`. Durante el arrastre
el puntero no llega al cliente.
De paso, arregla un test de `mirada-link` que construía un
`WindowPlacement` sin los campos `floating`/`fullscreen`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El backend DRM del Cuerpo deja de ser sólo-teclado: `libinput` ahora
mueve un cursor de software y reenvía clics y rueda a los clientes.
- Enum `Frame` (vía `render_elements!`) que mezcla superficies de
cliente y un `SolidColorRenderElement` para el cursor, marcado
`Kind::Cursor` y compuesto encima de todo.
- `handle_input` atiende `PointerMotion`/`PointerMotionAbsolute`/
`PointerButton`/`PointerAxis`; el puntero se acota a la salida.
- Foco-sigue-ratón: `window_at` hace el test de impacto (flotantes
sobre teseladas, contra el rectángulo real de la superficie) y, al
cambiar de ventana, emite `BodyEvent::PointerEntered`.
- `surface_px_size` en main.rs — tamaño presentado de una superficie,
reusado por el test de impacto.
Compila + clippy limpio; pendiente de verificar en hardware.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Un cliente que presenta una superficie más pequeña que su celda (p. ej.
un terminal que redondea a celdas de texto enteras) dejaba el hueco todo
a un lado. Ahora ManagedWindow recuerda el tamaño de la celda y
render_loc() centra la superficie en el sobrante. Lo usan los dos
backends (winit y DRM).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El backend DRM funciona en hardware (sesión, render, teclado, atajos,
clientes, salida limpia). Diagnosticado: la franja al pie con `foot` es
que `foot` redondea su superficie a celdas de texto enteras (1920×1040
de 1080) — comportamiento del cliente, no del compositor.
Se retira la instrumentación: log por tecla, censo de dispositivos y
volcado de tamaños de superficie cada 2 s. El tope de tiempo
(MIRADA_DRM_TIMEOUT) pasa a estar desactivado por defecto — Super+Shift+e
y Ctrl+C son la salida normal.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El panel sólo ofrece modos 1920×1080, así que la franja negra no es del
modo. Para localizarla, el bucle DRM registra cada ~2 s la posición y el
tamaño real de cada superficie — así se ve si el cliente llena la
pantalla o se queda corto.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El borde negro seguía: la marca PREFERRED del panel no es fiable (a
veces apunta a un modo menor). Ahora se elige el modo de mayor área
(a igualdad, mayor refresco), y se registran todos los modos del
conector para diagnóstico.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El backend DRM cogía conn.modes()[0], que no es el modo preferido —
en un panel 16:10 (1920×1200) suele ser un 1920×1080, dejando una
franja negra abajo.
Ahora elige el modo marcado PREFERRED (el nativo) y, si ninguno lo
está, el de mayor área.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
foot (y casi todo cliente Wayland) aborta con «no monitors available»
si el compositor no anuncia ningún wl_output. carmen no lo hacía.
- OutputHandler para App + delegate_output!.
- announce_output(): crea un Output, lo publica como global wl_output y
le fija el modo. Helper compartido por los dos backends.
- winit y DRM lo llaman con su tamaño/modo real.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Primera prueba 2b en hardware: sesión, render, teclado y atajos
funcionan — Super+Shift+e cierra limpio. Faltaba ver una ventana de
cliente.
- MIRADA_STARTUP: si trae un comando, el backend DRM lo lanza como hijo
al arrancar (hereda WAYLAND_DISPLAY) — así se prueba un cliente sin
saltar de VT.
- Logs: cada cliente Wayland que se conecta, y el nº de ventanas en
pantalla cuando cambia — para confirmar la cadena cliente→ventana.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tras la primera prueba en hardware (el bucle arranca y compone el fondo,
pero el teclado no responde y no había forma de salir):
- Salida garantizada: el backend DRM se cierra solo a los 60 s (env
MIRADA_DRM_TIMEOUT, 0 lo desactiva). Así una prueba nunca deja la
pantalla atrapada aunque el teclado falle.
- handle_input instrumentado: registra cada dispositivo de entrada que
libinput descubre y cada tecla con su combo y si es un atajo — para
diagnosticar por qué no llega la entrada.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Última fase del backend DRM: el bucle Wayland completo. Con esto
`mirada-compositor --drm` es un escritorio funcionando sobre una TTY,
sin sesión anfitriona.
main.rs: el armado del estado se extrae a build_app() -> Setup, que
comparten los dos backends (winit intacto).
drm_backend.rs — fase 2b sobre el pipeline de la 2a:
- DrmState: el estado que comparten los callbacks de calloop.
- bucle calloop con cinco fuentes: VBlank (DrmDeviceNotifier),
teclado (libinput), clientes Wayland nuevos (ListeningSocket),
peticiones de los clientes (poll fd del Display) y un timer ~60 Hz.
- render(): compone las ventanas de App en el DrmCompositor, encola el
page-flip y reparte los frame-callbacks; el VBlank libera el flip.
- handle_input(): teclado libinput → interceptación de atajos (misma
combo_string que winit) → keybind al Cerebro.
- tick(): Cerebro enlazado, recarga de keymap, mirada-ctl, composición.
- registra la salida con el modo del monitor; abre el socket Wayland.
Compila y pasa clippy aquí; se ejecuta y depura en hardware por logs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sobre el bring-up de la fase 1, drm_backend.rs monta ahora el pipeline
gráfico completo y lo prueba:
- elige la salida conectada (conector + CRTC + modo)
- GBM + EGL + GlesRenderer
- GbmAllocator + GbmFramebufferExporter + DrmCompositor para esa salida
- bucle calloop sincronizado al VBlank (DrmDeviceNotifier): pinta la
pantalla de colores ~6 s y para (con tope de 10 s anti-cuelgue)
Es un test de hardware: si la pantalla cambia de color, EGL, GBM, el
modeset y el page-flip funcionan. Compila y pasa clippy aquí; se
ejecuta y depura en la máquina con GPU por logs. La fase 2b será el
bucle Wayland completo (clientes + libinput + composición de ventanas).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
mirada-compositor gana un segundo backend para correr sobre una TTY
pelada, sin sesión gráfica anfitriona. main() elige: --winit / --drm,
o automático (con DISPLAY/WAYLAND_DISPLAY → winit anidado; sin ellos →
DRM). run() pasa a llamarse run_winit().
drm_backend.rs — fase 1 (bring-up), construida para verificarse en
hardware real por etapas:
- abre la sesión con libseat (acceso a DRM/input sin root)
- localiza la GPU primaria (udev::primary_gpu)
- abre el dispositivo DRM por la sesión
- enumera los conectores y sus modos
Todo instrumentado con logs para diagnosticar sin el hardware delante.
La composición (GBM + EGL + GlesRenderer + DrmCompositor + libinput +
bucle calloop) es la fase 2. El backend winit queda intacto.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>