1# modules/mirada/ — Compositor Wayland (carmen)
2
3**Propósito.** Un compositor Wayland teselante. La decisión de diseño
4central es partirlo en dos procesos, el **Cerebro** y el **Cuerpo**:
5
6- **El Cuerpo** (`mirada-compositor`, sobre `smithay`) habla Wayland con
7 los clientes, posee el hardware (DRM/GPU/libinput) y compone las
8 superficies reales. Los píxeles nunca salen de él — composición
9 *zero-copy*.
10- **El Cerebro** (la app `mirada`, GPUI) decide *dónde* va cada ventana
11 —pura aritmética de rectángulos— y orquesta el escritorio: layouts,
12 atajos, focos, escritorios virtuales.
13
14Los dos hablan por un socket Unix con un contrato mínimo de enums. Así,
15toda la lógica espacial es agnóstica de Wayland y se prueba sin un
16servidor gráfico; el Cuerpo queda reducido a "habla el protocolo y
17ejecuta operaciones de geometría".
18
19## Crates
20
21| crate | tipo | rol |
22| ------------------ | -------- | ---------------------------------------------------------------------- |
23| `mirada-layout` | lib | Motor de teselado: `Rect`, modos de layout, `Workspace` (ventanas, foco) |
24| `mirada-protocol` | lib | Contrato Cerebro↔Cuerpo: `BrainCommand`/`BodyEvent` + marco de cable |
25| `mirada-brain` | lib | Orquestador del escritorio: `Desktop`, eventos→comandos, atajos |
26| `mirada-link` | lib | Transporte: el socket Unix con hilo lector + canal |
27| `mirada-body` | lib | Contabilidad del Cuerpo: `BodyState`, traduce comandos a `BodyOp` |
28| `mirada` (app) | bin/GPUI | El Cerebro: ventana que tesela el escritorio y manda geometría |
29| `mirada-compositor`| bin/smithay | El Cuerpo: compositor Wayland real (backend `winit`, anidado) |
30| `mirada-ctl` (app) | bin/CLI | Control externo del Cerebro (estilo `swaymsg`): acciones y consultas |
31
32## Flujo
33
34```text
35 mirada-layout ─► mirada-protocol ─► mirada-brain ─► [app mirada · Cerebro]
36 │ │
37 │ mirada-link
38 │ │
39 └────► mirada-body ─► [mirada-compositor · Cuerpo]
40```
41
42- El Cuerpo reporta hardware/clientes con `BodyEvent` (salida conectada,
43 ventana abierta, atajo pulsado…).
44- El Cerebro (`Desktop::on_event`) recalcula y emite `BrainCommand`
45 (`Place` con la geometría completa, `Close`, `GrabKeys`…).
46- El Cuerpo (`BodyState::apply`) traduce cada comando a `BodyOp`
47 concretas y sólo emite lo que de verdad cambia.
48
49## Detalle por crate
50
51- **`mirada-layout`** — `Rect` + `split` (reparto exacto de píxeles),
52 `LayoutMode` con 7 modos (`MasterStack`, `CenteredMaster`, `Spiral`
53 —espiral de Fibonacci—, `Grid`, `Columns`, `Rows`, `Monocle`) y
54 `LayoutMode::next()` para el ciclo, `Workspace` con foco cíclico,
55 reordenado y `promote_focused`. `LayoutParams` lleva `master_ratio` y
56 `master_count` (`nmaster`); *smart gaps* (una sola ventana va a
57 sangre). Determinista.
58- **`mirada-protocol`** — `WindowPlacement`, los enums `BrainCommand` y
59 `BodyEvent`, el marco `postcard` con prefijo `u32` LE
60 (`write_frame`/`read_frame`, guard `MAX_FRAME`) y el puente
61 `placements(&Workspace, Rect)`.
62- **`mirada-brain`** — `Desktop`: salidas, 9 escritorios virtuales,
63 registro de ventanas. `on_event(BodyEvent) -> Vec<BrainCommand>`;
64 `DesktopAction` + `Keymap` configurable (`set_keymap` en caliente) +
65 `Rules` de ventana. **Multi-monitor**: cada `Output` muestra un
66 escritorio; `relayout()` tesela todas las salidas en un solo `Place`.
67- **`mirada-link`** — `Link<Out,In>` sobre socket Unix; hilo lector de
68 fondo + canal `mpsc` para sondeo no bloqueante. `BrainLink`/`BodyLink`,
69 `connected_pair` (socketpair), `connect`/`listen` por ruta.
70- **`mirada-body`** — `BodyState`: salidas + superficies; `apply` traduce
71 `BrainCommand`→`BodyOp` (idempotente), los mutadores del backend
72 devuelven los `BodyEvent` a mandar. Ejemplo `headless`: un Cuerpo sin
73 gráficos guiado por stdin para ejercitar el bucle entero.
74- **`mirada` (app)** — envuelve `Desktop` y lo pinta (barra de
75 escritorios + modo + foco, lienzo teselado). El lienzo dibuja **todas
76 las salidas a escala**, cada una con su marco. Con `MIRADA_SOCKET`
77 conecta a un Cuerpo; sin él corre en **simulación** (ventanas
78 sintéticas, teclado de la propia ventana). Pips de escritorio y
79 ventanas clicables.
80- **`mirada-ctl` (app)** — CLI de control: parsea la acción de los
81 argumentos (`DesktopAction: FromStr`) y la manda al Cerebro por el
82 socket de control; `windows` y `actions` para consultar.
83
84## Atajos de teclado configurables
85
86El keymap vive **sólo en el Cerebro** (`mirada-brain::Keymap`). El Cuerpo
87nunca lo ve: recibe únicamente la lista de cadenas a interceptar en un
88`GrabKeys`, hace un `Vec::contains` ciego y devuelve la combinación
89pulsada como `Keybind`; es `Desktop` quien la traduce a `DesktopAction`.
90Esa separación —*qué* interceptar (lista barata, Cuerpo) vs. *qué
91significa* (el mapa, Cerebro)— hace innecesario cualquier candado o
92`Arc`: el mapa es monohilo y la lista se reemplaza de golpe.
93
94- **Disco** — RON de texto en `~/.config/mirada/keymap.ron`, editable a
95 mano y versionable. La app lo crea documentado en el primer arranque;
96 si está corrupto, avisa y usa el de por defecto sin pisar el archivo.
97- **Cable** — sólo viaja la lista de cadenas (`GrabKeys`), vía el marco
98 `postcard` que ya existe. No hay formato binario de configuración.
99- **Vocabulario** — la acción es una cadena estable (`"focus-next"`,
100 `"layout:grid"`, `"workspace:3"`): `DesktopAction: Display + FromStr`.
101- **Recarga en caliente** — `Keymap::watch` (sobre `notify`) vigila el
102 archivo; al cambiar, el dueño del `Desktop` recarga, llama a
103 `set_keymap` y reenvía el `GrabKeys`. Sin reiniciar.
104- **Configurador** — no hay ejecutable aparte: el editor de texto del
105 usuario, y la app `mirada` (que a futuro puede dibujar un editor visual
106 sobre el mismo API `Keymap`). `cargo run -p mirada-brain --example
107 keymap-default` imprime el archivo por defecto.
108
109## API de acciones
110
111Toda acción de escritorio converge en un único embudo:
112`Desktop::apply(DesktopAction) -> Vec<BrainCommand>`. El keymap no es más
113que un front-end (`Keybind` → `lookup` → `apply`); hay otros tres:
114
115- **`DesktopAction::FocusWindow(WindowId)`** — direccionamiento directo de
116 una ventana (no sólo ciclar con `FocusNext`/`Prev`); si está en otro
117 escritorio, salta a él. Lo usan la taskbar y `mirada-ctl`.
118- **Ventanas flotantes** — `ToggleFloat` (`Super+f`) saca la enfocada del
119 teselado a un rectángulo libre (centrado, 60 %); `Workspace` guarda las
120 flotantes aparte, `layout()` las pone al final y `WindowPlacement`
121 /`BodyOp::Configure` llevan `floating: bool` para que el Cuerpo las
122 componga por encima.
123- **Pantalla completa** — `ToggleFullscreen` (`Super+Shift+f`): la ventana
124 cubre toda la salida (sin gap), oculta al resto y se lleva el foco;
125 `Workspace.fullscreen: Option<WindowId>`, y el Cuerpo le fija el estado
126 `xdg_toplevel Fullscreen`. También atiende la petición del propio
127 cliente (`xdg set_fullscreen` → `BodyEvent::FullscreenRequest`), así
128 que un reproductor o un juego entran a pantalla completa solos.
129- **Multi-monitor** — cada `Output` muestra un escritorio distinto;
130 `SwitchWorkspace` actúa sobre la salida enfocada (y la intercambia si
131 el escritorio pedido ya lo muestra otra salida); `FocusOutputNext`
132 (`Super+o`) mueve el foco entre monitores. El foco del teclado es
133 único — sólo la ventana enfocada de la salida enfocada.
134- **Scratchpad** — `SendToScratchpad` guarda la ventana enfocada (sale
135 del teselado, en ningún escritorio); `ToggleScratchpad` (`` Super+` ``)
136 la invoca flotando y centrada en el escritorio actual, o la oculta —
137 estilo terminal desplegable. `Desktop.scratchpad: Vec<WindowId>`;
138 `mirada-ctl windows` la lista como `esc scratch`.
139- **Layout y área maestra por el API** — los 7 modos se intercambian
140 (`SetLayout`/`CycleLayout`, `mirada-ctl layout spiral`); el área
141 maestra se redimensiona (`grow`/`shrink-master`, `Super+l`/`Super+h`);
142 `inc`/`dec-master` cambian `nmaster` (`Super+,`/`Super+.`); y
143 `promote-to-master` lleva la enfocada al puesto maestro (`Super+Return`
144 — `combo_string` ya canoniza teclas con nombre: `Return`, `Tab`, `F5`…).
145- **Lanzar programas** — `DesktopAction::Spawn(String)` (forma textual
146 `spawn:<comando>`, `Super+Shift+Return` → `spawn:foot` por defecto)
147 produce un `BrainCommand::Spawn`; el Cuerpo lo ejecuta con `sh -c`, y
148 el hijo hereda `WAYLAND_DISPLAY`. `DesktopAction` deja de ser `Copy`
149 por llevar el comando.
150- **Lanzador de aplicaciones** — `mirada-launcher` (app aparte, sin
151 dependencias): escanea los `.desktop` XDG y lanza el elegido desde una
152 lista de terminal que se filtra escribiendo. El keymap ata `Super+p` a
153 `spawn:foot -e mirada-launcher`.
154- **HUD interactivo** (app `mirada`) — los pips de escritorio y las
155 ventanas del lienzo son clicables: clic = `apply` de la acción.
156- **`mirada-ctl`** — control externo por línea de comandos
157 (`mirada-ctl focus-next`, `workspace 3`, `windows`). Habla con el
158 Cerebro por un socket Unix aparte; el módulo `mirada-brain::ctl` define
159 `CtlRequest`/`CtlReply` (marco `postcard`), `CtlServer`/`CtlConn` y
160 `send_request`. El Cerebro (la app `mirada` siempre; `mirada-compositor`
161 sólo embebido) abre el socket y atiende en su bucle. `DesktopAction`
162 viaja como enum serializado: contrato tipado de punta a punta.
163
164`cargo run -p mirada-brain --example headless-ctl` levanta un Cerebro sin
165gráficos para ejercitar `mirada-ctl` en modo desatendido.
166
167## Reglas de ventana
168
169`mirada-brain::rules` decide qué hacer con una ventana **al abrirse**:
170config declarativa en RON (`~/.config/mirada/rules.ron`), mismo patrón
171que el keymap. Cada `Rule` casa por subcadena de `app_id` y/o `title`
172(sin distinguir mayúsculas; vacío = cualquiera) y aplica un destino:
173`workspace` (1..9) y/o `floating`. Gana la primera regla que case.
174
175El `Desktop` consulta `Rules::resolve` en cada `WindowOpened` — el evento
176ya trae `app_id`/`title` — y manda la ventana a su escritorio, flotando
177si toca. Se carga al arrancar (la primera vez se escribe una plantilla
178con ejemplos comentados); las reglas afectan a las ventanas futuras, no
179a las ya abiertas.
180
181## Dependencias
182
183- Todos los `lib` con `#![forbid(unsafe_code)]`. Cero Wayland, cero
184 `smithay` en los seis crates de arriba.
185- El acoplamiento a Wayland/hardware vive sólo en `mirada-compositor`.
186
187## Estado
188
189Implementado y verde: `mirada-layout` (32 tests), `mirada-protocol`
190(11), `mirada-brain` (65), `mirada-link` (7), `mirada-body` (14), las
191apps `mirada` y `mirada-compositor` (compilan; verificación visual
192manual) y `mirada-ctl` (CLI, probado vía el ejemplo `headless-ctl`).
193
194El **Cuerpo** ya existe: `mirada-compositor` es un compositor Wayland
195teselante real sobre `smithay`. Habla `wl_compositor`/`xdg_shell`/
196`wl_shm`/`wl_seat`/`wl_output`/`wl_data_device`/`xdg-decoration`/
197`zwp_linux_dmabuf`, compone las superficies de los clientes con
198`GlesRenderer` y aplica la geometría del Cerebro. Fuerza decoración
199`ServerSide` y no dibuja ninguna: las ventanas teseladas van sin marco
200(nada de barras de título de cliente). Con `zwp_linux_dmabuf` los
201clientes que pintan por GPU (apps GPUI, navegadores acelerados) pueden
202conectarse — el `GlesRenderer` importa sus búferes DMA-BUF al componer.
203Reusa `mirada-body` (contabilidad) y `mirada-link` (cable). Dos modos de
204Cerebro: **autónomo** (`Desktop` embebido) o **enlazado** (`MIRADA_SOCKET`
205→ la app `mirada`).
206
207**Dos backends gráficos** (`main()` elige; `--winit`/`--drm` lo fuerzan):
208
209- **`winit`** — corre anidado, una ventana en la sesión gráfica actual.
210- **`drm`** (`drm_backend.rs`) — corre nativo sobre una TTY, sin sesión
211 anfitriona: `libseat` (sesión), `udev` (GPU), `DrmDevice` + GBM + EGL +
212 `DrmCompositor`, `libinput` (teclado y ratón), bucle `calloop`.
213 Verificado en hardware: sesión, render, teclado, atajos, clientes,
214 salida limpia. El ratón: el cursor toma la superficie que pide el
215 cliente (`wl_pointer.set_cursor` → `cursor_image`) y cae a un cuadrado
216 (`SolidColorRenderElement` `Kind::Cursor`) por defecto; el foco sigue
217 al puntero (`BodyEvent::PointerEntered`) y clics y rueda van a la
218 ventana debajo. Todo en un enum `Frame` de elementos de render.
219 `Super`+arrastre mueve/redimensiona: el Cuerpo calcula el rectángulo y
220 emite `BodyEvent::WindowFloatTo { id, rect }`; el Cerebro hace flotar
221 la ventana ahí (`Workspace::set_floating`). Cada ventana lleva un marco
222 de 2 px (4 `SolidColorBuffer` por ventana, `Id` estable): azul si tiene
223 el foco, gris si no.
224- **Conmutación de VT** — `Ctrl+Alt+Fn` salta a otra TTY: el
225 `SessionEvent` de `libseat` pausa el `DrmDevice` y suspende `libinput`;
226 al volver, los reactiva, llama a `DrmCompositor::reset_state` y
227 repinta. Mientras está cedida, `render()` no toca la GPU.
228- **Sesión** — `~/.config/mirada/autostart` (un comando por línea) se
229 lanza al arrancar el backend DRM; el script `session/mirada-session` y
230 `session/carmen.desktop` integran carmen con un gestor de login.
231- **Acople del shell** — una ventana con `app_id = "carmen.shell"` no se
232 tesela: carmen le reserva una franja de 40 px al pie de la salida y la
233 pinta sobre todo. La reserva viaja como `BodyEvent::OutputResized`, que
234 encoge el área útil del Cerebro **sin** perder el escritorio que
235 muestra (a diferencia de quitar y volver a añadir la salida). Es el
236 anclaje de la futura `shuma-shell` en modo launcher.
237
238**Pendiente** — refinamientos del Cuerpo:
239
240| capa pendiente | rol |
241| ------------------ | ---------------------------------------------------------- |
242| puntero en `winit` | ratón en el backend anidado (hoy sólo el backend DRM) |
243| `mirada-input` | repetición de teclas, gestos; hotplug de monitores |
244| `shuma-shell` | modo launcher: falta la config para esconder/reubicar |
245| `wlr-layer-shell` | barras externas tipo waybar, fondos, notificaciones |
246| `mirada-sandbox` | aislamiento de clientes sobre `arje-incarnate` |
247
248`shuma-shell --launcher` ya corre como el shell de carmen: abre una
249ventana sin barra de título con `app_id = "carmen.shell"` (el acople la
250reconoce) y dibuja una barra compacta — glifo, la línea de comandos de
251`shuma-line`, la **barra de ventanas abiertas** (las consulta por el
252socket de control de carmen con `ListWindows`, un clic enfoca con
253`FocusWindow`) y el estado del último comando. Un clic en el estado
254**despliega el cajón de resultados**: la ventana del shell crece hacia
255arriba (`Window::resize`) y carmen la ancla al pie — `render_loc`
256coloca la ventana-shell por su tamaño real, así su cajón se solapa
257sobre las teseladas sin re-teselar. Falta la config para esconder y
258reubicar la barra.
259
260CRIU (congelar/restaurar ventanas) queda anotado como futuro.
SDD.md
modules/mirada/ — Compositor Wayland (carmen)
Propósito. Un compositor Wayland teselante. La decisión de diseño
central es partirlo en dos procesos, el Cerebro y el Cuerpo:
- El Cuerpo (
mirada-compositor, sobre smithay) habla Wayland con
los clientes, posee el hardware (DRM/GPU/libinput) y compone las
superficies reales. Los píxeles nunca salen de él — composición
zero-copy.
- El Cerebro (la app
mirada, GPUI) decide dónde va cada ventana
—pura aritmética de rectángulos— y orquesta el escritorio: layouts,
atajos, focos, escritorios virtuales.
Los dos hablan por un socket Unix con un contrato mínimo de enums. Así,
toda la lógica espacial es agnóstica de Wayland y se prueba sin un
servidor gráfico; el Cuerpo queda reducido a "habla el protocolo y
ejecuta operaciones de geometría".
Crates
| crate | tipo | rol |
mirada-layout | lib | Motor de teselado: Rect, modos de layout, Workspace (ventanas, foco) |
mirada-protocol | lib | Contrato Cerebro↔Cuerpo: BrainCommand/BodyEvent + marco de cable |
mirada-brain | lib | Orquestador del escritorio: Desktop, eventos→comandos, atajos |
mirada-link | lib | Transporte: el socket Unix con hilo lector + canal |
mirada-body | lib | Contabilidad del Cuerpo: BodyState, traduce comandos a BodyOp |
mirada (app) | bin/GPUI | El Cerebro: ventana que tesela el escritorio y manda geometría |
mirada-compositor | bin/smithay | El Cuerpo: compositor Wayland real (backend winit, anidado) |
mirada-ctl (app) | bin/CLI | Control externo del Cerebro (estilo swaymsg): acciones y consultas |
Flujo
mirada-layout ─► mirada-protocol ─► mirada-brain ─► [app mirada · Cerebro]
│ │
│ mirada-link
│ │
└────► mirada-body ─► [mirada-compositor · Cuerpo]
- El Cuerpo reporta hardware/clientes con
BodyEvent (salida conectada,
ventana abierta, atajo pulsado…).
- El Cerebro (
Desktop::on_event) recalcula y emite BrainCommand
(Place con la geometría completa, Close, GrabKeys…).
- El Cuerpo (
BodyState::apply) traduce cada comando a BodyOp
concretas y sólo emite lo que de verdad cambia.
Detalle por crate
mirada-layout — Rect + split (reparto exacto de píxeles),
LayoutMode con 7 modos (MasterStack, CenteredMaster, Spiral
—espiral de Fibonacci—, Grid, Columns, Rows, Monocle) y
LayoutMode::next() para el ciclo, Workspace con foco cíclico,
reordenado y promote_focused. LayoutParams lleva master_ratio y
master_count (nmaster); smart gaps (una sola ventana va a
sangre). Determinista.
mirada-protocol — WindowPlacement, los enums BrainCommand y
BodyEvent, el marco postcard con prefijo u32 LE
(write_frame/read_frame, guard MAX_FRAME) y el puente
placements(&Workspace, Rect).
mirada-brain — Desktop: salidas, 9 escritorios virtuales,
registro de ventanas. on_event(BodyEvent) -> Vec<BrainCommand>;
DesktopAction + Keymap configurable (set_keymap en caliente) +
Rules de ventana. Multi-monitor: cada Output muestra un
escritorio; relayout() tesela todas las salidas en un solo Place.
mirada-link — Link<Out,In> sobre socket Unix; hilo lector de
fondo + canal mpsc para sondeo no bloqueante. BrainLink/BodyLink,
connected_pair (socketpair), connect/listen por ruta.
mirada-body — BodyState: salidas + superficies; apply traduce
BrainCommand→BodyOp (idempotente), los mutadores del backend
devuelven los BodyEvent a mandar. Ejemplo headless: un Cuerpo sin
gráficos guiado por stdin para ejercitar el bucle entero.
mirada (app) — envuelve Desktop y lo pinta (barra de
escritorios + modo + foco, lienzo teselado). El lienzo dibuja todas
las salidas a escala, cada una con su marco. Con MIRADA_SOCKET
conecta a un Cuerpo; sin él corre en simulación (ventanas
sintéticas, teclado de la propia ventana). Pips de escritorio y
ventanas clicables.
mirada-ctl (app) — CLI de control: parsea la acción de los
argumentos (DesktopAction: FromStr) y la manda al Cerebro por el
socket de control; windows y actions para consultar.
Atajos de teclado configurables
El keymap vive sólo en el Cerebro (mirada-brain::Keymap). El Cuerpo
nunca lo ve: recibe únicamente la lista de cadenas a interceptar en un
GrabKeys, hace un Vec::contains ciego y devuelve la combinación
pulsada como Keybind; es Desktop quien la traduce a DesktopAction.
Esa separación —qué interceptar (lista barata, Cuerpo) vs. qué
significa (el mapa, Cerebro)— hace innecesario cualquier candado o
Arc: el mapa es monohilo y la lista se reemplaza de golpe.
- Disco — RON de texto en
~/.config/mirada/keymap.ron, editable a
mano y versionable. La app lo crea documentado en el primer arranque;
si está corrupto, avisa y usa el de por defecto sin pisar el archivo.
- Cable — sólo viaja la lista de cadenas (
GrabKeys), vía el marco
postcard que ya existe. No hay formato binario de configuración.
- Vocabulario — la acción es una cadena estable (
"focus-next",
"layout:grid", "workspace:3"): DesktopAction: Display + FromStr.
- Recarga en caliente —
Keymap::watch (sobre notify) vigila el
archivo; al cambiar, el dueño del Desktop recarga, llama a
set_keymap y reenvía el GrabKeys. Sin reiniciar.
- Configurador — no hay ejecutable aparte: el editor de texto del
usuario, y la app
mirada (que a futuro puede dibujar un editor visual
sobre el mismo API Keymap). cargo run -p mirada-brain --example keymap-default imprime el archivo por defecto.
API de acciones
Toda acción de escritorio converge en un único embudo:
Desktop::apply(DesktopAction) -> Vec<BrainCommand>. El keymap no es más
que un front-end (Keybind → lookup → apply); hay otros tres:
DesktopAction::FocusWindow(WindowId) — direccionamiento directo de
una ventana (no sólo ciclar con FocusNext/Prev); si está en otro
escritorio, salta a él. Lo usan la taskbar y mirada-ctl.
- Ventanas flotantes —
ToggleFloat (Super+f) saca la enfocada del
teselado a un rectángulo libre (centrado, 60 %); Workspace guarda las
flotantes aparte, layout() las pone al final y WindowPlacement
/BodyOp::Configure llevan floating: bool para que el Cuerpo las
componga por encima.
- Pantalla completa —
ToggleFullscreen (Super+Shift+f): la ventana
cubre toda la salida (sin gap), oculta al resto y se lleva el foco;
Workspace.fullscreen: Option<WindowId>, y el Cuerpo le fija el estado
xdg_toplevel Fullscreen. También atiende la petición del propio
cliente (xdg set_fullscreen → BodyEvent::FullscreenRequest), así
que un reproductor o un juego entran a pantalla completa solos.
- Multi-monitor — cada
Output muestra un escritorio distinto;
SwitchWorkspace actúa sobre la salida enfocada (y la intercambia si
el escritorio pedido ya lo muestra otra salida); FocusOutputNext
(Super+o) mueve el foco entre monitores. El foco del teclado es
único — sólo la ventana enfocada de la salida enfocada.
- Scratchpad —
SendToScratchpad guarda la ventana enfocada (sale
del teselado, en ningún escritorio); ToggleScratchpad (Super+`)
la invoca flotando y centrada en el escritorio actual, o la oculta —
estilo terminal desplegable. Desktop.scratchpad: Vec<WindowId>;
mirada-ctl windows la lista como esc scratch.
- Layout y área maestra por el API — los 7 modos se intercambian
(
SetLayout/CycleLayout, mirada-ctl layout spiral); el área
maestra se redimensiona (grow/shrink-master, Super+l/Super+h);
inc/dec-master cambian nmaster (Super+,/Super+.); y
promote-to-master lleva la enfocada al puesto maestro (Super+Return
— combo_string ya canoniza teclas con nombre: Return, Tab, F5…).
- Lanzar programas —
DesktopAction::Spawn(String) (forma textual
spawn:<comando>, Super+Shift+Return → spawn:foot por defecto)
produce un BrainCommand::Spawn; el Cuerpo lo ejecuta con sh -c, y
el hijo hereda WAYLAND_DISPLAY. DesktopAction deja de ser Copy
por llevar el comando.
- Lanzador de aplicaciones —
mirada-launcher (app aparte, sin
dependencias): escanea los .desktop XDG y lanza el elegido desde una
lista de terminal que se filtra escribiendo. El keymap ata Super+p a
spawn:foot -e mirada-launcher.
- HUD interactivo (app
mirada) — los pips de escritorio y las
ventanas del lienzo son clicables: clic = apply de la acción.
mirada-ctl — control externo por línea de comandos
(mirada-ctl focus-next, workspace 3, windows). Habla con el
Cerebro por un socket Unix aparte; el módulo mirada-brain::ctl define
CtlRequest/CtlReply (marco postcard), CtlServer/CtlConn y
send_request. El Cerebro (la app mirada siempre; mirada-compositor
sólo embebido) abre el socket y atiende en su bucle. DesktopAction
viaja como enum serializado: contrato tipado de punta a punta.
cargo run -p mirada-brain --example headless-ctl levanta un Cerebro sin
gráficos para ejercitar mirada-ctl en modo desatendido.
Reglas de ventana
mirada-brain::rules decide qué hacer con una ventana al abrirse:
config declarativa en RON (~/.config/mirada/rules.ron), mismo patrón
que el keymap. Cada Rule casa por subcadena de app_id y/o title
(sin distinguir mayúsculas; vacío = cualquiera) y aplica un destino:
workspace (1..9) y/o floating. Gana la primera regla que case.
El Desktop consulta Rules::resolve en cada WindowOpened — el evento
ya trae app_id/title — y manda la ventana a su escritorio, flotando
si toca. Se carga al arrancar (la primera vez se escribe una plantilla
con ejemplos comentados); las reglas afectan a las ventanas futuras, no
a las ya abiertas.
Dependencias
- Todos los
lib con #![forbid(unsafe_code)]. Cero Wayland, cero
smithay en los seis crates de arriba.
- El acoplamiento a Wayland/hardware vive sólo en
mirada-compositor.
Estado
Implementado y verde: mirada-layout (32 tests), mirada-protocol
(11), mirada-brain (65), mirada-link (7), mirada-body (14), las
apps mirada y mirada-compositor (compilan; verificación visual
manual) y mirada-ctl (CLI, probado vía el ejemplo headless-ctl).
El Cuerpo ya existe: mirada-compositor es un compositor Wayland
teselante real sobre smithay. Habla wl_compositor/xdg_shell/
wl_shm/wl_seat/wl_output/wl_data_device/xdg-decoration/
zwp_linux_dmabuf, compone las superficies de los clientes con
GlesRenderer y aplica la geometría del Cerebro. Fuerza decoración
ServerSide y no dibuja ninguna: las ventanas teseladas van sin marco
(nada de barras de título de cliente). Con zwp_linux_dmabuf los
clientes que pintan por GPU (apps GPUI, navegadores acelerados) pueden
conectarse — el GlesRenderer importa sus búferes DMA-BUF al componer.
Reusa mirada-body (contabilidad) y mirada-link (cable). Dos modos de
Cerebro: autónomo (Desktop embebido) o enlazado (MIRADA_SOCKET
→ la app mirada).
Dos backends gráficos (main() elige; --winit/--drm lo fuerzan):
winit — corre anidado, una ventana en la sesión gráfica actual.
drm (drm_backend.rs) — corre nativo sobre una TTY, sin sesión
anfitriona: libseat (sesión), udev (GPU), DrmDevice + GBM + EGL +
DrmCompositor, libinput (teclado y ratón), bucle calloop.
Verificado en hardware: sesión, render, teclado, atajos, clientes,
salida limpia. El ratón: el cursor toma la superficie que pide el
cliente (wl_pointer.set_cursor → cursor_image) y cae a un cuadrado
(SolidColorRenderElement Kind::Cursor) por defecto; el foco sigue
al puntero (BodyEvent::PointerEntered) y clics y rueda van a la
ventana debajo. Todo en un enum Frame de elementos de render.
Super+arrastre mueve/redimensiona: el Cuerpo calcula el rectángulo y
emite BodyEvent::WindowFloatTo { id, rect }; el Cerebro hace flotar
la ventana ahí (Workspace::set_floating). Cada ventana lleva un marco
de 2 px (4 SolidColorBuffer por ventana, Id estable): azul si tiene
el foco, gris si no.
- Conmutación de VT —
Ctrl+Alt+Fn salta a otra TTY: el
SessionEvent de libseat pausa el DrmDevice y suspende libinput;
al volver, los reactiva, llama a DrmCompositor::reset_state y
repinta. Mientras está cedida, render() no toca la GPU.
- Sesión —
~/.config/mirada/autostart (un comando por línea) se
lanza al arrancar el backend DRM; el script session/mirada-session y
session/carmen.desktop integran carmen con un gestor de login.
- Acople del shell — una ventana con
app_id = "carmen.shell" no se
tesela: carmen le reserva una franja de 40 px al pie de la salida y la
pinta sobre todo. La reserva viaja como BodyEvent::OutputResized, que
encoge el área útil del Cerebro sin perder el escritorio que
muestra (a diferencia de quitar y volver a añadir la salida). Es el
anclaje de la futura shuma-shell en modo launcher.
Pendiente — refinamientos del Cuerpo:
| capa pendiente | rol |
puntero en winit | ratón en el backend anidado (hoy sólo el backend DRM) |
mirada-input | repetición de teclas, gestos; hotplug de monitores |
shuma-shell | modo launcher: falta la config para esconder/reubicar |
wlr-layer-shell | barras externas tipo waybar, fondos, notificaciones |
mirada-sandbox | aislamiento de clientes sobre arje-incarnate |
shuma-shell --launcher ya corre como el shell de carmen: abre una
ventana sin barra de título con app_id = "carmen.shell" (el acople la
reconoce) y dibuja una barra compacta — glifo, la línea de comandos de
shuma-line, la barra de ventanas abiertas (las consulta por el
socket de control de carmen con ListWindows, un clic enfoca con
FocusWindow) y el estado del último comando. Un clic en el estado
despliega el cajón de resultados: la ventana del shell crece hacia
arriba (Window::resize) y carmen la ancla al pie — render_loc
coloca la ventana-shell por su tamaño real, así su cajón se solapa
sobre las teseladas sin re-teselar. Falta la config para esconder y
reubicar la barra.
CRIU (congelar/restaurar ventanas) queda anotado como futuro.