gioser

tawasuyu/brahman

Rust 476TOML 190Markdown 50JSON 176602 Definiciones

feat(mirada): sesión de escritorio — autostart y conmutación de VT

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>

5ede927d3432c0c895e863b39ca7eb5f73b78213 · por sergio · (hace 5 meses) · padre 58e72c3d0 · ver el árbol en este commit

Delta semántico · 7

crates/apps/mirada-compositor/src/drm_backend.rs

crates/apps/mirada-compositor/src/main.rs

8 archivos · +172 −8

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=<s>` — 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<App>,
+    /// 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<std::path::PathBuf>,
@@ -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<dyn Error>> {
     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<dyn Error>> {
         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<dyn Error>> {
         })
         .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<dyn Error>> {
     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<std::path::PathBuf> {
+    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:<comando> 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.