`mirada-compositor --greeter` arranca como gestor de login: lanza
mirada-greeter como proceso hijo, lee su stdout y, al recibir el
SessionTicket, muta de BodyMode::Greeter a BodyMode::Session sin
reiniciar el servidor Wayland — la «mutación atómica» del DM.
- BodyMode { Greeter, Session }: eje ortogonal a Brain (Embedded/Linked).
- modo greeter: sin atajos registrados, rechaza Spawn, sin autoarranque.
- traspaso (complete_greeter_handoff): registra los atajos y arranca la
sesión — el comando del tiquet, o el autoarranque del usuario.
- privilegios: el compositor corre como root; spawn_command baja a
setuid/setgid + grupos suplementarios del usuario autenticado.
- bandera ortogonal al backend (--greeter [--drm|--winit]); el tiquet
llega por un canal calloop en DRM y por mpsc en winit.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fase 3c: el shell muestra la salida de los comandos en un cajón que se
despliega hacia arriba sobre el escritorio.
carmen — la ventana del shell deja de tener un alto fijo: `render_loc`
la ancla al pie de la salida y la coloca por su **tamaño real**, así
puede crecer hacia arriba. La franja reservada sigue siendo la barra
(40 px); el cajón, al abrirse, se solapa sobre las teseladas sin
re-teselar. `render_loc` toma ahora el alto de la salida.
shuma-shell — un clic en el estado alterna `drawer_open`: la ventana
crece (`Window::resize`, que GPUI 0.2 expone) a barra + cajón, o
vuelve a sólo barra. El cajón reusa `render_run` para pintar los
últimos comandos y su salida, con scroll. `render_launcher` pasa a una
columna: cajón opcional arriba, barra abajo.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fase 2 del plan «shell»: carmen reconoce la ventana del shell y le
reserva su sitio, en vez de teselarla como una más.
Una ventana cuyo `app_id` es `carmen.shell` no entra en el teselado:
carmen le reserva una franja de 40 px al pie de la salida, la dimensiona
y la fija ahí, y la compone sobre todas las demás. El Cerebro tesela el
resto de ventanas en el área que queda.
- `mirada-protocol`: nuevo `BodyEvent::OutputResized { id, w, h }` — el
Cerebro cambia el área útil de una salida **sin** perder el escritorio
que muestra (a diferencia de quitar y volver a añadir la salida — que,
de paso, era un bug latente al redimensionar la ventana winit).
- `mirada-brain`: `Desktop` atiende `OutputResized` (test nuevo).
- `mirada-body`: `BodyState::resize_output`.
- `mirada-compositor`: `ManagedWindow.is_shell`, `App.output_size`,
`dock_shell`/`output_changed`; `register_toplevel` no registra el
shell en el Cerebro; al cerrarse libera la franja. El shell se compone
y se enfoca con el ratón aunque no viva en el Cerebro; no lleva marco.
El backend winit usa ahora `resize_output` al redimensionar.
GPUI no habla `wlr-layer-shell`, así que el acople es por `app_id`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fase 1 del plan «shell»: para que carmen pueda hospedar a `shuma-shell`
(y a cualquier app GPUI o navegador acelerado) hace falta que los
clientes con GPU puedan compartir su búfer de vídeo. carmen sólo hablaba
`wl_shm` (búferes de software) — por eso `foot` corría pero las apps
GPUI salían en negro.
- `App` lleva un `DmabufState`; `impl DmabufHandler` con `dmabuf_imported`
que acepta el búfer (el `GlesRenderer` ya importa DMA-BUF al componer,
vía `ImportAll`, así que la validación real ocurre al pintar).
- `delegate_dmabuf!(App)`.
- `announce_dmabuf` crea el global con los formatos de `dmabuf_formats()`
del renderer — se llama en ambos backends una vez creado el renderer.
Pendiente del plan: Fase 2 (`wlr-layer-shell`) y Fase 3 (modo launcher
de `shuma-shell` — barra + input + cajón de resultados).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Dos piezas para usar carmen como tu escritorio de verdad.
Conmutación de VT — `Ctrl+Alt+Fn` salta a otra TTY y vuelve sin romper
la sesión. El `SessionEvent` de `libseat` ahora hace trabajo de verdad:
- al ceder la VT, pausa el `DrmDevice` y suspende `libinput`; `render()`
no vuelve a tocar la GPU mientras la sesión esté cedida (`active`).
- al recuperarla, reanuda `libinput`, reactiva el `DrmDevice`, llama a
`DrmCompositor::reset_state` y repinta.
`DrmState` conserva ahora `drm` y un clon del contexto `libinput`.
Sesión — `~/.config/mirada/autostart` (un comando por línea, `#`
comenta) se lanza al arrancar el backend DRM, vía un `spawn_autostart`
que reusa `spawn_command`. Y `session/`: el script `mirada-session`
(fija el entorno XDG y exec del compositor) y `carmen.desktop` para
registrarlo en un gestor de login, más un `autostart.example`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>