From d2e0cf48303a222e45a2a22723de78666832d1ca Mon Sep 17 00:00:00 2001 From: sergio Date: Wed, 20 May 2026 22:44:39 +0000 Subject: [PATCH] =?UTF-8?q?feat(mirada):=20mirada-compositor=20=E2=80=94?= =?UTF-8?q?=20el=20Cuerpo,=20compositor=20Wayland=20sobre=20smithay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Compositor Wayland teselante real sobre smithay, backend winit (corre anidado como ventana dentro de la sesión X11/Wayland actual). Habla wl_compositor/xdg_shell/wl_shm/wl_seat/wl_data_device y compone las superficies de los clientes con GlesRenderer. Dos modos: autónomo (Cerebro Desktop embebido, un solo proceso) o enlazado (MIRADA_SOCKET → la app mirada decide la geometría). Reusa mirada-body para la contabilidad y mirada-link para el cable. Actualiza el SDD: el Cuerpo deja de ser pendiente. Añade README. Co-Authored-By: Claude Opus 4.7 --- Cargo.toml | 1 + crates/apps/mirada-compositor/Cargo.toml | 19 + crates/apps/mirada-compositor/README.md | 61 +++ crates/apps/mirada-compositor/src/main.rs | 604 ++++++++++++++++++++++ crates/modules/mirada/SDD.md | 33 +- vamos.txt | 18 + 6 files changed, 724 insertions(+), 12 deletions(-) create mode 100644 crates/apps/mirada-compositor/Cargo.toml create mode 100644 crates/apps/mirada-compositor/README.md create mode 100644 crates/apps/mirada-compositor/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 7a5b8f4..a40aef7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -286,6 +286,7 @@ members = [ "crates/apps/matilda", "crates/apps/yachay", "crates/apps/mirada", + "crates/apps/mirada-compositor", ] [workspace.package] diff --git a/crates/apps/mirada-compositor/Cargo.toml b/crates/apps/mirada-compositor/Cargo.toml new file mode 100644 index 0000000..e7547a0 --- /dev/null +++ b/crates/apps/mirada-compositor/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "mirada-compositor" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +authors.workspace = true +publish.workspace = true +description = "mirada — el Cuerpo del compositor: un compositor Wayland teselante sobre smithay (backend winit, nested). Tesela con un Cerebro embebido o uno externo por mirada-link." + +[[bin]] +name = "mirada-compositor" +path = "src/main.rs" + +[dependencies] +mirada-brain = { path = "../../modules/mirada/mirada-brain" } +mirada-body = { path = "../../modules/mirada/mirada-body" } +mirada-link = { path = "../../modules/mirada/mirada-link" } +smithay = "0.7" diff --git a/crates/apps/mirada-compositor/README.md b/crates/apps/mirada-compositor/README.md new file mode 100644 index 0000000..c49d6ef --- /dev/null +++ b/crates/apps/mirada-compositor/README.md @@ -0,0 +1,61 @@ +# mirada-compositor — el Cuerpo de carmen + +Un compositor Wayland teselante real, sobre [`smithay`]. Es el **Cuerpo** +de la arquitectura Cerebro↔Cuerpo de `mirada` (ver +`crates/modules/mirada/SDD.md`): habla el protocolo Wayland con los +clientes, compone sus superficies y aplica la geometría que decide el +Cerebro. + +Backend `winit`: corre **anidado** — una ventana dentro de tu sesión +gráfica actual, X11 o Wayland. No toca DRM/KMS, así que es seguro de +arrancar sin dejar la sesión. + +## Dos modos + +- **Autónomo** (por defecto) — lleva un `Desktop` (de `mirada-brain`) + embebido. Es un compositor teselante completo en un solo proceso. + + ```sh + cargo run -p mirada-compositor + ``` + +- **Enlazado** — el Cuerpo escucha en un socket y la app `mirada` (el + Cerebro GPUI) se conecta y decide la geometría. + + ```sh + # terminal 1 — el Cuerpo + MIRADA_SOCKET=/tmp/mirada.sock cargo run -p mirada-compositor + # terminal 2 — el Cerebro + MIRADA_SOCKET=/tmp/mirada.sock cargo run -p mirada + ``` + +## Probarlo + +Al arrancar imprime el `WAYLAND_DISPLAY` que abrió. Lanza cualquier +cliente Wayland contra él: + +```sh +WAYLAND_DISPLAY=wayland-1 foot # o weston-terminal, alacritty, … +``` + +Las ventanas se teselan solas. El teclado, con la ventana del compositor +enfocada, maneja el escritorio con atajos `Super+…` (los que registra el +Cerebro: foco `Super+j/k`, layout `Super+Tab`, escritorios `Super+1..9`). +Cierra la ventana del compositor para salir. + +## Qué implementa + +`wl_compositor`, `xdg_shell` (toplevels y popups), `wl_shm`, `wl_seat` +(teclado) y `wl_data_device` (selección). Composición con `GlesRenderer`. + +Reusa `mirada-body` para la contabilidad de salidas y superficies, y +`mirada-link` para el cable hacia un Cerebro externo. Toda la lógica +espacial es agnóstica de Wayland y vive en los crates de +`crates/modules/mirada/`. + +## Pendiente + +Backend nativo DRM/libinput (de ventana anidada a sesión real), +puntero/ratón completo y aislamiento de clientes. Ver el SDD. + +[`smithay`]: https://github.com/Smithay/smithay diff --git a/crates/apps/mirada-compositor/src/main.rs b/crates/apps/mirada-compositor/src/main.rs new file mode 100644 index 0000000..67094cd --- /dev/null +++ b/crates/apps/mirada-compositor/src/main.rs @@ -0,0 +1,604 @@ +//! `mirada-compositor` — el Cuerpo del compositor carmen. +//! +//! Un compositor Wayland teselante real, sobre `smithay`, con backend +//! `winit`: corre **anidado** como una ventana dentro de tu sesión +//! gráfica actual (X11 o Wayland). Habla el protocolo Wayland con los +//! clientes, compone sus superficies y aplica la geometría que decide el +//! Cerebro. +//! +//! Dos modos: +//! +//! - **Autónomo** (por defecto): lleva un [`Desktop`] embebido — es un +//! compositor teselante completo en un solo proceso. Lánzalo y abre +//! clientes; el teclado (`Super+…`) maneja el escritorio. +//! - **Enlazado** (`MIRADA_SOCKET=/ruta`): el Cuerpo escucha ahí y la +//! app `mirada` (el Cerebro GPUI) se conecta; la geometría viaja por +//! [`mirada_link`]. +//! +//! Cómo probarlo en un Linux real: ver `crates/apps/mirada-compositor/README.md`. + +use std::sync::Arc; +use std::time::Instant; + +use smithay::backend::input::{InputEvent, KeyState, KeyboardKeyEvent}; +use smithay::backend::renderer::element::surface::{ + render_elements_from_surface_tree, WaylandSurfaceRenderElement, +}; +use smithay::backend::renderer::element::Kind; +use smithay::backend::renderer::gles::GlesRenderer; +use smithay::backend::renderer::utils::{draw_render_elements, on_commit_buffer_handler}; +use smithay::backend::renderer::{Color32F, Frame, Renderer}; +use smithay::backend::winit::{self, WinitEvent}; +use smithay::input::keyboard::{xkb, FilterResult, KeyboardHandle, Keysym, ModifiersState}; +use smithay::input::{Seat, SeatHandler, SeatState}; +use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel; +use smithay::reexports::wayland_server::backend::{ClientData, ClientId, DisconnectReason}; +use smithay::reexports::wayland_server::protocol::wl_buffer; +use smithay::reexports::wayland_server::protocol::wl_seat; +use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; +use smithay::reexports::wayland_server::{Client, Display, ListeningSocket}; +use smithay::reexports::winit::platform::pump_events::PumpStatus; +use smithay::utils::{Rectangle, SERIAL_COUNTER}; +use smithay::utils::{Serial, Transform}; +use smithay::wayland::buffer::BufferHandler; +use smithay::wayland::compositor::{ + with_states, with_surface_tree_downward, CompositorClientState, CompositorHandler, + CompositorState, SurfaceAttributes, TraversalAction, +}; +use smithay::wayland::selection::data_device::{ + ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler, +}; +use smithay::wayland::selection::SelectionHandler; +use smithay::wayland::shell::xdg::{ + PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState, + XdgToplevelSurfaceData, +}; +use smithay::wayland::shm::{ShmHandler, ShmState}; +use smithay::{ + delegate_compositor, delegate_data_device, delegate_seat, delegate_shm, delegate_xdg_shell, +}; + +use mirada_body::{BodyOp, BodyState}; +use mirada_brain::{BodyEvent, BrainCommand, Desktop}; +use mirada_link::BodyLink; + +// --------------------------------------------------------------------- +// Estado +// --------------------------------------------------------------------- + +/// De dónde salen las decisiones de geometría. +enum Brain { + /// El compositor lleva su propio `Desktop` — proceso único. + Embedded(Desktop), + /// Un Cerebro externo (la app `mirada`) por socket. + Linked(BodyLink), +} + +/// Una ventana de cliente que el compositor gestiona. +struct ManagedWindow { + id: u64, + toplevel: ToplevelSurface, + surface: WlSurface, + /// Esquina superior-izquierda en píxeles, según el Cerebro. + loc: (i32, i32), + visible: bool, +} + +/// El estado global del compositor. +struct App { + compositor_state: CompositorState, + xdg_shell_state: XdgShellState, + shm_state: ShmState, + seat_state: SeatState, + data_device_state: DataDeviceState, + seat: Seat, + keyboard: Option>, + + /// Ventanas gestionadas, en orden de aparición. + windows: Vec, + /// La contabilidad del Cuerpo (mirada-body). + body: BodyState, + /// El Cerebro: embebido o enlazado. + brain: Brain, + /// Atajos globales a interceptar (los registra el Cerebro). + grabs: Vec, + /// Atajo capturado en el último evento de teclado, pendiente de enviar. + pending_keybind: Option, + next_id: u64, + running: bool, +} + +impl App { + /// Inyecta un evento del Cuerpo en el Cerebro y aplica su respuesta. + fn brain_feed(&mut self, event: BodyEvent) { + let cmds = match &mut self.brain { + Brain::Embedded(desktop) => desktop.on_event(event), + Brain::Linked(link) => { + let _ = link.send(&event); + Vec::new() + } + }; + self.apply_commands(cmds); + } + + /// Drena los comandos de un Cerebro enlazado (no hace nada si es embebido). + fn brain_poll(&mut self) { + let cmds = match &self.brain { + Brain::Linked(link) => link.drain(), + Brain::Embedded(_) => Vec::new(), + }; + if !cmds.is_empty() { + self.apply_commands(cmds); + } + } + + /// Traduce los comandos del Cerebro a operaciones y las ejecuta. + fn apply_commands(&mut self, cmds: Vec) { + for cmd in cmds { + let ops = self.body.apply(cmd); + for op in ops { + self.exec_op(op); + } + } + } + + /// Ejecuta una operación concreta sobre las superficies reales. + fn exec_op(&mut self, op: BodyOp) { + match op { + BodyOp::Configure { id, rect, visible } => { + if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) { + w.loc = (rect.x, rect.y); + w.visible = visible; + w.toplevel.with_pending_state(|s| { + s.size = Some((rect.w.max(1), rect.h.max(1)).into()); + }); + w.toplevel.send_pending_configure(); + } + } + BodyOp::Focus(id) => { + let mut target = None; + for w in &self.windows { + let active = w.id == id; + if active { + target = Some(w.surface.clone()); + } + w.toplevel.with_pending_state(|s| { + if active { + s.states.set(xdg_toplevel::State::Activated); + } else { + s.states.unset(xdg_toplevel::State::Activated); + } + }); + w.toplevel.send_pending_configure(); + } + if let Some(kb) = self.keyboard.clone() { + kb.set_focus(self, target, SERIAL_COUNTER.next_serial()); + } + } + BodyOp::Unfocus => { + if let Some(kb) = self.keyboard.clone() { + kb.set_focus(self, Option::::None, SERIAL_COUNTER.next_serial()); + } + } + BodyOp::CloseClient(id) | BodyOp::KillClient(id) => { + if let Some(w) = self.windows.iter().find(|w| w.id == id) { + w.toplevel.send_close(); + } + } + BodyOp::SetGrabs(keys) => self.grabs = keys, + BodyOp::SetCursor(_) => {} + BodyOp::Shutdown => self.running = false, + } + } + + /// Registra un toplevel recién creado y avisa al Cerebro. + fn register_toplevel(&mut self, toplevel: ToplevelSurface) { + let surface = toplevel.wl_surface().clone(); + let id = self.next_id; + self.next_id += 1; + + let (app_id, title) = with_states(&surface, |states| { + states + .data_map + .get::() + .and_then(|d| d.lock().ok()) + .map(|d| { + ( + d.app_id.clone().unwrap_or_default(), + d.title.clone().unwrap_or_default(), + ) + }) + .unwrap_or_default() + }); + let app_id = if app_id.is_empty() { "cliente".into() } else { app_id }; + let title = if title.is_empty() { format!("ventana {id}") } else { title }; + + self.windows.push(ManagedWindow { + id, + toplevel, + surface, + loc: (0, 0), + visible: false, + }); + let ev = self.body.open_surface(id, app_id, title); + self.brain_feed(ev); + } +} + +// --------------------------------------------------------------------- +// Handlers de protocolo +// --------------------------------------------------------------------- + +impl CompositorHandler for App { + fn compositor_state(&mut self) -> &mut CompositorState { + &mut self.compositor_state + } + + fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState { + &client.get_data::().unwrap().compositor_state + } + + fn commit(&mut self, surface: &WlSurface) { + on_commit_buffer_handler::(surface); + } +} + +impl BufferHandler for App { + fn buffer_destroyed(&mut self, _buffer: &wl_buffer::WlBuffer) {} +} + +impl ShmHandler for App { + fn shm_state(&self) -> &ShmState { + &self.shm_state + } +} + +impl XdgShellHandler for App { + fn xdg_shell_state(&mut self) -> &mut XdgShellState { + &mut self.xdg_shell_state + } + + fn new_toplevel(&mut self, surface: ToplevelSurface) { + surface.with_pending_state(|s| { + s.states.set(xdg_toplevel::State::Activated); + }); + surface.send_configure(); + self.register_toplevel(surface); + } + + fn toplevel_destroyed(&mut self, surface: ToplevelSurface) { + let pos = self + .windows + .iter() + .position(|w| w.surface == *surface.wl_surface()); + if let Some(pos) = pos { + let id = self.windows.remove(pos).id; + if let Some(ev) = self.body.close_surface(id) { + self.brain_feed(ev); + } + } + } + + fn title_changed(&mut self, surface: ToplevelSurface) { + let id = self + .windows + .iter() + .find(|w| w.surface == *surface.wl_surface()) + .map(|w| w.id); + let Some(id) = id else { return }; + let title = with_states(surface.wl_surface(), |states| { + states + .data_map + .get::() + .and_then(|d| d.lock().ok()) + .and_then(|d| d.title.clone()) + .unwrap_or_default() + }); + if let Some(ev) = self.body.retitle_surface(id, title) { + self.brain_feed(ev); + } + } + + fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) { + let _ = surface.send_configure(); + } + + fn grab(&mut self, _surface: PopupSurface, _seat: wl_seat::WlSeat, _serial: Serial) {} + + fn reposition_request( + &mut self, + _surface: PopupSurface, + _positioner: PositionerState, + _token: u32, + ) { + } +} + +impl SelectionHandler for App { + type SelectionUserData = (); +} + +impl DataDeviceHandler for App { + fn data_device_state(&self) -> &DataDeviceState { + &self.data_device_state + } +} +impl ClientDndGrabHandler for App {} +impl ServerDndGrabHandler for App { + fn send(&mut self, _mime_type: String, _fd: std::os::unix::io::OwnedFd, _seat: Seat) {} +} + +impl SeatHandler for App { + type KeyboardFocus = WlSurface; + type PointerFocus = WlSurface; + type TouchFocus = WlSurface; + + fn seat_state(&mut self) -> &mut SeatState { + &mut self.seat_state + } + + fn focus_changed(&mut self, _seat: &Seat, _focused: Option<&WlSurface>) {} + fn cursor_image( + &mut self, + _seat: &Seat, + _image: smithay::input::pointer::CursorImageStatus, + ) { + } +} + +delegate_compositor!(App); +delegate_xdg_shell!(App); +delegate_shm!(App); +delegate_seat!(App); +delegate_data_device!(App); + +// --------------------------------------------------------------------- +// Datos por cliente +// --------------------------------------------------------------------- + +#[derive(Default)] +struct ClientState { + compositor_state: CompositorClientState, +} +impl ClientData for ClientState { + fn initialized(&self, _id: ClientId) {} + fn disconnected(&self, _id: ClientId, _reason: DisconnectReason) {} +} + +// --------------------------------------------------------------------- +// Utilidades +// --------------------------------------------------------------------- + +/// Construye la cadena de un atajo (`"Super+Shift+j"`) desde el estado de +/// modificadores y el keysym, con el mismo formato que el mapa de teclas +/// de [`mirada_brain`]. `None` si no es una tecla mapeable. +fn combo_string(mods: &ModifiersState, sym: Keysym) -> Option { + let utf = xkb::keysym_to_utf8(sym); + let key = utf.trim_end_matches('\0'); + let name = if key == " " { + "space".to_string() + } else { + let mut chars = key.chars(); + let c = chars.next()?; + if chars.next().is_some() || !c.is_ascii_graphic() { + return None; + } + c.to_ascii_lowercase().to_string() + }; + let mut combo = String::new(); + if mods.logo { + combo.push_str("Super+"); + } + if mods.ctrl { + combo.push_str("Ctrl+"); + } + if mods.shift { + combo.push_str("Shift+"); + } + if mods.alt { + combo.push_str("Alt+"); + } + combo.push_str(&name); + Some(combo) +} + +/// Despacha los callbacks de frame de un árbol de superficies: avisa a +/// cada cliente de que puede dibujar el siguiente cuadro. +fn send_frames_surface_tree(surface: &WlSurface, time: u32) { + with_surface_tree_downward( + surface, + (), + |_, _, &()| TraversalAction::DoChildren(()), + |_surf, states, &()| { + for callback in states + .cached_state + .get::() + .current() + .frame_callbacks + .drain(..) + { + callback.done(time); + } + }, + |_, _, &()| true, + ); +} + +// --------------------------------------------------------------------- +// Bucle principal +// --------------------------------------------------------------------- + +fn run() -> Result<(), Box> { + let mut display: Display = Display::new()?; + let dh = display.handle(); + + let mut seat_state = SeatState::new(); + let seat = seat_state.new_wl_seat(&dh, "mirada"); + + // Elige el Cerebro: enlazado si `MIRADA_SOCKET` está puesto. + let brain = match std::env::var("MIRADA_SOCKET") { + Ok(path) => { + println!("mirada-compositor · esperando al Cerebro en {path} …"); + let link = BodyLink::listen(&path)?; + println!("mirada-compositor · Cerebro conectado."); + Brain::Linked(link) + } + Err(_) => { + println!("mirada-compositor · modo autónomo (Cerebro embebido)."); + Brain::Embedded(Desktop::new()) + } + }; + + let mut state = App { + compositor_state: CompositorState::new::(&dh), + xdg_shell_state: XdgShellState::new::(&dh), + shm_state: ShmState::new::(&dh, Vec::new()), + seat_state, + data_device_state: DataDeviceState::new::(&dh), + seat, + keyboard: None, + windows: Vec::new(), + body: BodyState::new(), + brain, + grabs: Vec::new(), + pending_keybind: None, + next_id: 1, + running: true, + }; + + let keyboard = state.seat.add_keyboard(Default::default(), 200, 25)?; + state.keyboard = Some(keyboard.clone()); + + // En modo embebido, el propio Desktop dicta los atajos a interceptar. + if let Brain::Embedded(desktop) = &state.brain { + let grab = desktop.grab_keys(); + state.apply_commands(vec![grab]); + } + + let listener = ListeningSocket::bind_auto("wayland", 1..32)?; + let socket_name = listener + .socket_name() + .and_then(|s| s.to_str()) + .unwrap_or("wayland-?") + .to_string(); + std::env::set_var("WAYLAND_DISPLAY", &socket_name); + println!("mirada-compositor · escuchando en WAYLAND_DISPLAY={socket_name}"); + println!(" lanza un cliente: WAYLAND_DISPLAY={socket_name} foot"); + + let (mut backend, mut winit) = winit::init::()?; + let start = Instant::now(); + let mut clients = Vec::new(); + + // Salida inicial = el tamaño de la ventana winit. + { + let size = backend.window_size(); + let ev = state.body.add_output(0, size.w, size.h); + state.brain_feed(ev); + } + + while state.running { + // 1 · Eventos del backend (teclado, redimensión, cierre). + let status = winit.dispatch_new_events(|event| match event { + WinitEvent::CloseRequested => state.running = false, + WinitEvent::Resized { size, .. } => { + let ev = state.body.remove_output(0); + state.brain_feed(ev); + let ev = state.body.add_output(0, size.w, size.h); + state.brain_feed(ev); + } + WinitEvent::Input(InputEvent::Keyboard { event }) => { + let code = event.key_code(); + let key_state = event.state(); + let pressed = key_state == KeyState::Pressed; + let time = start.elapsed().as_millis() as u32; + keyboard.clone().input::<(), _>( + &mut state, + code, + key_state, + SERIAL_COUNTER.next_serial(), + time, + |st, mods, handle| { + if !pressed { + return FilterResult::Forward; + } + if let Some(combo) = combo_string(mods, handle.modified_sym()) { + if st.grabs.contains(&combo) { + st.pending_keybind = Some(combo); + return FilterResult::Intercept(()); + } + } + FilterResult::Forward + }, + ); + if let Some(combo) = state.pending_keybind.take() { + let ev = state.body.keybind(combo); + state.brain_feed(ev); + } + } + _ => {} + }); + if let PumpStatus::Exit(_) = status { + break; + } + + // 2 · Comandos de un Cerebro enlazado. + state.brain_poll(); + + // 3 · Composición de las superficies en sus rectángulos. + let size = backend.window_size(); + let damage: Rectangle = Rectangle::from_size(size); + { + let (renderer, mut framebuffer) = backend.bind().unwrap(); + let elements: Vec> = state + .windows + .iter() + .filter(|w| w.visible) + .flat_map(|w| { + render_elements_from_surface_tree( + renderer, + &w.surface, + w.loc, + 1.0, + 1.0, + Kind::Unspecified, + ) + }) + .collect(); + let mut frame = renderer + .render(&mut framebuffer, size, Transform::Flipped180) + .unwrap(); + frame + .clear(Color32F::new(0.05, 0.05, 0.08, 1.0), &[damage]) + .unwrap(); + draw_render_elements(&mut frame, 1.0, &elements, &[damage]).unwrap(); + let _ = frame.finish().unwrap(); + } + + // 4 · Callbacks de frame + clientes nuevos + flush. + let time = start.elapsed().as_millis() as u32; + for w in &state.windows { + send_frames_surface_tree(&w.surface, time); + } + if let Some(stream) = listener.accept()? { + let client = display + .handle() + .insert_client(stream, Arc::new(ClientState::default())) + .unwrap(); + clients.push(client); + } + display.dispatch_clients(&mut state)?; + display.flush_clients()?; + + backend.submit(Some(&[damage])).unwrap(); + } + + println!("mirada-compositor · adiós."); + Ok(()) +} + +fn main() { + if let Err(e) = run() { + eprintln!("mirada-compositor · error: {e}"); + std::process::exit(1); + } +} diff --git a/crates/modules/mirada/SDD.md b/crates/modules/mirada/SDD.md index 96be630..08d2534 100644 --- a/crates/modules/mirada/SDD.md +++ b/crates/modules/mirada/SDD.md @@ -26,6 +26,7 @@ ejecuta operaciones de geometría". | `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) | ## Flujo @@ -72,8 +73,7 @@ ejecuta operaciones de geometría". - Todos los `lib` con `#![forbid(unsafe_code)]`. Cero Wayland, cero `smithay` en los seis crates de arriba. -- El acoplamiento a hardware vive sólo en `mirada-compositor` - (pendiente). +- El acoplamiento a Wayland/hardware vive sólo en `mirada-compositor`. ## Estado @@ -81,15 +81,24 @@ Implementado y verde: `mirada-layout` (22 tests), `mirada-protocol` (9), `mirada-brain` (17), `mirada-link` (7), `mirada-body` (13), y la app `mirada` (compila; verificación visual manual). -**Pendiente** — la capa que toca hardware/protocolo, no verificable en -modo desatendido: +El **Cuerpo** ya existe: `mirada-compositor` es un compositor Wayland +teselante real sobre `smithay`, con backend `winit` — corre **anidado** +como una ventana dentro de la sesión gráfica actual. Habla +`wl_compositor`/`xdg_shell`/`wl_shm`/`wl_seat`/`wl_data_device`, compone +las superficies de los clientes con `GlesRenderer` y aplica la geometría +del Cerebro. Reusa `mirada-body` (contabilidad) y `mirada-link` (cable). +Dos modos: **autónomo** (Cerebro `Desktop` embebido, un solo proceso) o +**enlazado** (`MIRADA_SOCKET` → la app `mirada` decide la geometría). +Compila y pasa clippy; verificación visual manual — ver +`crates/apps/mirada-compositor/README.md`. -| crate pendiente | rol | -| ------------------- | --------------------------------------------------------- | -| `mirada-compositor` | el Cuerpo: `smithay` — superficies, buffers, salidas, DRM | -| `mirada-input` | libinput → teclado/ratón, intercepción de atajos | -| `mirada-sandbox` | aislamiento de clientes sobre `arje-incarnate` | +**Pendiente** — refinamientos del Cuerpo, no verificables en modo +desatendido: -El Cuerpo reusará `mirada-body` para su contabilidad y `mirada-link` -para el cable; sólo le falta el *backend* `smithay`. CRIU -(congelar/restaurar ventanas) queda anotado como futuro. +| capa pendiente | rol | +| -------------------- | -------------------------------------------------------- | +| backend DRM/libinput | de `winit` anidado a sesión nativa: superficies KMS, GPU | +| `mirada-input` | puntero/ratón completo, repetición de teclas, gestos | +| `mirada-sandbox` | aislamiento de clientes sobre `arje-incarnate` | + +CRIU (congelar/restaurar ventanas) queda anotado como futuro. diff --git a/vamos.txt b/vamos.txt index 1e31f34..096862e 100644 --- a/vamos.txt +++ b/vamos.txt @@ -985,4 +985,22 @@ + Flujo end-to-end demoable hoy sin gráficos: + cargo run -p mirada-body --example headless -- /tmp/mirada.sock # el Cuerpo + MIRADA_SOCKET=/tmp/mirada.sock cargo run -p mirada # el Cerebro + Sin MIRADA_SOCKET, mirada corre en simulación: ventanas sintéticas y teclado propio (n abre, j/k foco, tab cicla layout, 1-9 escritorios). + + + + El Cuerpo de verdad — mirada-compositor: + Compositor Wayland teselante real sobre smithay, backend winit (corre anidado, una ventana dentro de tu sesión X11/Wayland). + cargo run -p mirada-compositor # autónomo: Cerebro Desktop embebido, un solo proceso + WAYLAND_DISPLAY=wayland-1 foot # lanza clientes contra él (imprime su WAYLAND_DISPLAY al arrancar) + MIRADA_SOCKET=/tmp/mirada.sock cargo run -p mirada-compositor # enlazado: la app mirada (Cerebro GPUI) decide la geometría + Habla wl_compositor/xdg_shell/wl_shm/wl_seat/wl_data_device; compone con GlesRenderer. Reusa mirada-body y mirada-link. + Ver crates/apps/mirada-compositor/README.md. + + + +