diff --git a/crates/apps/mirada-compositor/README.md b/crates/apps/mirada-compositor/README.md index 87010bc..ac43c2f 100644 --- a/crates/apps/mirada-compositor/README.md +++ b/crates/apps/mirada-compositor/README.md @@ -51,6 +51,42 @@ fino: azul la que tiene el foco, gris las demás. - `MIRADA_DRM_TIMEOUT=` — cierra el compositor solo tras N segundos (0 o sin definir = sin tope). +## Como sesión de escritorio + +Para usar carmen como tu escritorio de verdad — entrar a una sesión, no +sólo probarlo: + +1. Compila e instala los binarios en el `PATH`: + + ```sh + cargo build --release -p mirada-compositor -p mirada-ctl + sudo install -m755 target/release/mirada-compositor \ + target/release/mirada-ctl /usr/local/bin/ + sudo install -m755 session/mirada-session /usr/local/bin/ + ``` + +2. Arranca desde una TTY: + + ```sh + mirada-session + ``` + + O regístralo en un gestor de login copiando `session/carmen.desktop` + a `/usr/share/wayland-sessions/` — aparecerá carmen como sesión. + +3. **Autoarranque** — los programas que quieras al iniciar van en + `~/.config/mirada/autostart`, uno por línea (`#` comenta). Tienes un + ejemplo en `session/autostart.example`: + + ```sh + mkdir -p ~/.config/mirada + cp crates/apps/mirada-compositor/session/autostart.example \ + ~/.config/mirada/autostart + ``` + +Dentro de la sesión, `Ctrl+Alt+F1…F12` salta a otra TTY y vuelve sin +romper carmen. + ## Dos modos - **Autónomo** (por defecto) — lleva un `Desktop` (de `mirada-brain`) diff --git a/crates/apps/mirada-compositor/session/autostart.example b/crates/apps/mirada-compositor/session/autostart.example new file mode 100644 index 0000000..4084be8 --- /dev/null +++ b/crates/apps/mirada-compositor/session/autostart.example @@ -0,0 +1,13 @@ +# Autoarranque de carmen — cópialo a ~/.config/mirada/autostart +# +# Un comando por línea; se lanza al arrancar el compositor, con +# WAYLAND_DISPLAY ya puesto. Las líneas en blanco y las que empiezan +# por # se ignoran. Cada línea se pasa a `sh -c`, así que valen las +# variables, las tuberías y el `&` final no hace falta. + +# Una terminal para empezar. +foot + +# Ejemplos (descoméntalos si los quieres): +# mirada-ctl layout spiral +# wbg ~/fondo.png diff --git a/crates/apps/mirada-compositor/session/carmen.desktop b/crates/apps/mirada-compositor/session/carmen.desktop new file mode 100644 index 0000000..f81b0e2 --- /dev/null +++ b/crates/apps/mirada-compositor/session/carmen.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Name=carmen +Comment=Compositor Wayland teselante (mirada) +Exec=mirada-session +Type=Application +DesktopNames=carmen diff --git a/crates/apps/mirada-compositor/session/mirada-session b/crates/apps/mirada-compositor/session/mirada-session new file mode 100755 index 0000000..c7cfa80 --- /dev/null +++ b/crates/apps/mirada-compositor/session/mirada-session @@ -0,0 +1,22 @@ +#!/bin/sh +# mirada-session — arranca carmen (el compositor mirada) como una sesión +# de escritorio. Pensado para lanzarse desde una TTY o desde un gestor de +# login (greetd, ly, …). +# +# Instálalo en el PATH (p. ej. /usr/local/bin/mirada-session) junto al +# binario `mirada-compositor`. + +# Carmen es un compositor Wayland. +export XDG_SESSION_TYPE=wayland +export XDG_CURRENT_DESKTOP=carmen +export XDG_SESSION_DESKTOP=carmen + +# Que las apps GUI prefieran sus backends Wayland. +export MOZ_ENABLE_WAYLAND=1 +export QT_QPA_PLATFORM="wayland;xcb" +export SDL_VIDEODRIVER=wayland +export _JAVA_AWT_WM_NONREPARENTING=1 + +# El backend DRM toma la TTY entera. Los programas de arranque van en +# ~/.config/mirada/autostart (uno por línea). +exec mirada-compositor --drm diff --git a/crates/apps/mirada-compositor/src/drm_backend.rs b/crates/apps/mirada-compositor/src/drm_backend.rs index 72ac07b..065a53b 100644 --- a/crates/apps/mirada-compositor/src/drm_backend.rs +++ b/crates/apps/mirada-compositor/src/drm_backend.rs @@ -116,8 +116,15 @@ const BTN_RIGHT: u32 = 0x111; struct DrmState { app: App, display: Display, + /// El dispositivo DRM — se conserva para pausarlo y reactivarlo al + /// conmutar de VT. + drm: DrmDevice, compositor: Compositor, renderer: GlesRenderer, + /// Contexto `libinput` — se suspende y reanuda al conmutar de VT. + libinput: Libinput, + /// `false` mientras la sesión está cedida a otra VT — no se compone. + active: bool, /// `true` entre que se encola un page-flip y llega su VBlank. pending_flip: bool, keymap_path: Option, @@ -139,6 +146,9 @@ struct DrmState { impl DrmState { /// Compone el cursor y las ventanas y, si hubo cambios, encola el cuadro. fn render(&mut self) { + if !self.active { + return; // la sesión está en otra VT — no tocamos la GPU + } if self.pending_flip { return; // aún esperamos el VBlank del cuadro anterior } @@ -258,6 +268,34 @@ impl DrmState { } } + /// La sesión se cede a otra VT (`Ctrl+Alt+Fn`): suelta la GPU y deja + /// de leer el ratón y el teclado, para no chocar con quien ahora + /// manda en la pantalla. + fn pause_session(&mut self) { + self.active = false; + self.drm.pause(); + self.libinput.suspend(); + println!("mirada-compositor · sesión cedida a otra VT."); + } + + /// La sesión vuelve a esta VT: recupera la GPU y la entrada, reinicia + /// el estado del compositor y repinta. + fn resume_session(&mut self) { + if self.libinput.resume().is_err() { + eprintln!("mirada-compositor · libinput.resume falló."); + } + if let Err(e) = self.drm.activate(false) { + eprintln!("mirada-compositor · drm.activate falló: {e}"); + } + if let Err(e) = self.compositor.reset_state() { + eprintln!("mirada-compositor · compositor.reset_state falló: {e}"); + } + self.active = true; + self.pending_flip = false; + self.render(); + println!("mirada-compositor · sesión recuperada."); + } + /// Tarea periódica: Cerebro enlazado, recarga del keymap, API de /// control, composición y vaciado hacia los clientes. fn tick(&mut self) { @@ -696,6 +734,9 @@ pub fn run() -> Result<(), Box> { std::env::set_var("WAYLAND_DISPLAY", &socket_name); println!(" escuchando en WAYLAND_DISPLAY={socket_name}"); + // Autoarranque: los programas de `~/.config/mirada/autostart`. + crate::spawn_autostart(); + // App de arranque: si `MIRADA_STARTUP` trae un comando, se lanza como // hijo (hereda `WAYLAND_DISPLAY`) — cómodo para probar sin saltar de VT. if let Ok(cmd) = std::env::var("MIRADA_STARTUP") { @@ -708,11 +749,11 @@ pub fn run() -> Result<(), Box> { EventLoop::try_new().map_err(|e| format!("calloop falló: {e}"))?; let handle = event_loop.handle(); - // Sesión: pausa/activación al cambiar de VT. + // Sesión: pausa/activación al conmutar de VT. handle - .insert_source(session_notifier, |event, _, _state| match event { - SessionEvent::PauseSession => println!("mirada-compositor · sesión en pausa."), - SessionEvent::ActivateSession => println!("mirada-compositor · sesión activa."), + .insert_source(session_notifier, |event, _, state: &mut DrmState| match event { + SessionEvent::PauseSession => state.pause_session(), + SessionEvent::ActivateSession => state.resume_session(), }) .map_err(|e| format!("insert session: {e}"))?; @@ -729,11 +770,14 @@ pub fn run() -> Result<(), Box> { }) .map_err(|e| format!("insert drm: {e}"))?; - // Teclado y ratón vía libinput. + // Teclado y ratón vía libinput. Guardamos un clon del contexto (es + // un manejador con contador de referencias) para suspenderlo y + // reanudarlo al conmutar de VT. let mut libinput = Libinput::new_with_udev(LibinputSessionInterface::from(session.clone())); libinput .udev_assign_seat(&seat_name) .map_err(|()| "libinput: no pude asignar el seat")?; + let libinput_handle = libinput.clone(); handle .insert_source(LibinputInputBackend::new(libinput), |event, _meta, state| { state.handle_input(event); @@ -800,8 +844,11 @@ pub fn run() -> Result<(), Box> { let mut state = DrmState { app, display, + drm, compositor, renderer, + libinput: libinput_handle, + active: true, pending_flip: false, keymap_path, keymap_watch, diff --git a/crates/apps/mirada-compositor/src/main.rs b/crates/apps/mirada-compositor/src/main.rs index c3712ef..e3fd7a5 100644 --- a/crates/apps/mirada-compositor/src/main.rs +++ b/crates/apps/mirada-compositor/src/main.rs @@ -630,8 +630,8 @@ fn cursor_hotspot(surface: &WlSurface) -> (i32, i32) { /// Lanza un comando como proceso hijo, vía `sh -c`. El hijo hereda el /// entorno —`WAYLAND_DISPLAY` incluido—, así que el cliente que abra se -/// conecta a este compositor. Lo usan la acción `spawn:…` del keymap y -/// la variable `MIRADA_STARTUP`. +/// conecta a este compositor. Lo usan la acción `spawn:…` del keymap, la +/// variable `MIRADA_STARTUP` y el autoarranque. fn spawn_command(cmd: &str) { let cmd = cmd.trim(); if cmd.is_empty() { @@ -643,6 +643,36 @@ fn spawn_command(cmd: &str) { } } +/// La ruta del archivo de autoarranque del usuario, +/// `~/.config/mirada/autostart` — junto al keymap y las reglas. +fn autostart_path() -> Option { + Keymap::default_path().and_then(|p| p.parent().map(|d| d.join("autostart"))) +} + +/// Lanza los programas del archivo de autoarranque: un comando por +/// línea, `#` comenta y las líneas en blanco se saltan. Sin archivo, no +/// hace nada. Se llama una vez al arrancar, con el socket ya abierto. +fn spawn_autostart() { + let Some(path) = autostart_path() else { + return; + }; + let Ok(text) = std::fs::read_to_string(&path) else { + return; // no hay archivo de autoarranque + }; + let mut n = 0; + for line in text.lines() { + let line = line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + spawn_command(line); + n += 1; + } + if n > 0 { + println!("mirada-compositor · autoarranque: {n} programa(s) desde {}", path.display()); + } +} + /// Carga las reglas de ventana del usuario, o ninguna si no hay archivo. fn load_user_rules() -> Rules { match Rules::default_path() { diff --git a/crates/modules/mirada/SDD.md b/crates/modules/mirada/SDD.md index d879b55..39787c8 100644 --- a/crates/modules/mirada/SDD.md +++ b/crates/modules/mirada/SDD.md @@ -214,13 +214,21 @@ Cerebro: **autónomo** (`Desktop` embebido) o **enlazado** (`MIRADA_SOCKET` 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. **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; conmutación de VT, hotplug | +| `mirada-input` | repetición de teclas, gestos; hotplug de monitores | +| barra de estado | `wlr-layer-shell` + un cliente que dibuje la barra | | `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 3520d9d..bc09868 100644 --- a/vamos.txt +++ b/vamos.txt @@ -1006,6 +1006,8 @@ Super+arrastre mueve la ventana (botón izq.) o la redimensiona (der.) — al arrastrarla pasa a flotar. Fuerza xdg-decoration ServerSide y no dibuja marco: las ventanas teseladas van sin barra de título. Lanzar programas: acción spawn: del keymap (Super+Shift+Return → spawn:foot por defecto). + Conmutación de VT: Ctrl+Alt+Fn salta a otra TTY y vuelve sin romper la sesión (pausa DRM + libinput). + Sesión: ~/.config/mirada/autostart (un comando por línea) + script session/mirada-session + carmen.desktop. Ver crates/apps/mirada-compositor/README.md.