gioser

tawasuyu/brahman

Rust 231TOML 70JSON 14HTML 12905 Definiciones

feat(mirada): el cursor toma la forma que pide el cliente

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>

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

Delta semántico · 6

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

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

5 archivos · +87 −30

diff --git a/crates/apps/mirada-compositor/README.md b/crates/apps/mirada-compositor/README.md
index 39a8d0d..87010bc 100644
--- a/crates/apps/mirada-compositor/README.md
+++ b/crates/apps/mirada-compositor/README.md
@@ -39,12 +39,13 @@ Corre directo sobre el hardware. Requiere una **TTY** (`Ctrl+Alt+F3`),
 una GPU con `/dev/dri`, y `seatd` o `logind` para la sesión. Toma la
 pantalla completa; sal con `Super+Shift+e` o `Ctrl+C`.
 
-Lleva teclado y ratón por `libinput`: el ratón mueve un cursor de
-software, el foco sigue al puntero y los clics y la rueda llegan a la
-ventana que tienes debajo. **`Super`+arrastre** con el botón izquierdo
-mueve una ventana, con el derecho la redimensiona — al arrastrarla, la
-ventana pasa a flotar. Cada ventana lleva un marco fino: azul la que
-tiene el foco, gris las demás.
+Lleva teclado y ratón por `libinput`: el foco sigue al puntero y los
+clics y la rueda llegan a la ventana que tienes debajo. El cursor toma
+la forma que pide el cliente (la «I» sobre texto, una mano…) y cae a un
+cuadrado por defecto sobre el escritorio. **`Super`+arrastre** con el
+botón izquierdo mueve una ventana, con el derecho la redimensiona — al
+arrastrarla, la ventana pasa a flotar. Cada ventana lleva un marco
+fino: azul la que tiene el foco, gris las demás.
 
 - `MIRADA_STARTUP=<cmd>` — lanza una app al arrancar (`MIRADA_STARTUP=foot`).
 - `MIRADA_DRM_TIMEOUT=<s>` — cierra el compositor solo tras N segundos
diff --git a/crates/apps/mirada-compositor/src/drm_backend.rs b/crates/apps/mirada-compositor/src/drm_backend.rs
index cf94d9c..72ac07b 100644
--- a/crates/apps/mirada-compositor/src/drm_backend.rs
+++ b/crates/apps/mirada-compositor/src/drm_backend.rs
@@ -42,7 +42,7 @@ use smithay::backend::session::libseat::LibSeatSession;
 use smithay::backend::session::{Event as SessionEvent, Session};
 use smithay::backend::udev;
 use smithay::input::keyboard::FilterResult;
-use smithay::input::pointer::{AxisFrame, ButtonEvent, MotionEvent};
+use smithay::input::pointer::{AxisFrame, ButtonEvent, CursorImageStatus, MotionEvent};
 use smithay::output::OutputModeSource;
 use smithay::reexports::calloop::generic::Generic;
 use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
@@ -53,7 +53,7 @@ use smithay::reexports::input::Libinput;
 use smithay::reexports::rustix::fs::OFlags;
 use smithay::reexports::wayland_server::{Display, ListeningSocket};
 use smithay::utils::{
-    DeviceFd, Logical, Physical, Point, Rectangle, Scale, Size, Transform, SERIAL_COUNTER,
+    DeviceFd, IsAlive, Logical, Physical, Point, Rectangle, Scale, Size, Transform, SERIAL_COUNTER,
 };
 
 use mirada_brain::{BodyEvent, CtlReply, Keymap, Rect};
@@ -165,18 +165,40 @@ impl DrmState {
         let elements: Vec<Frame<GlesRenderer>> = {
             let mut out: Vec<Frame<GlesRenderer>> = Vec::new();
 
+            // El cursor — la superficie que pidió el cliente (la «I» del
+            // texto, una mano…), o el cuadrado por defecto si pidió un
+            // cursor con nombre y no hay tema. `Hidden` no pinta nada.
             let (cx, cy) = self.app.pointer_loc;
-            let cursor_rect = Rectangle::new(
-                Point::<i32, Physical>::from((cx.round() as i32, cy.round() as i32)),
-                Size::<i32, Physical>::from((CURSOR_SIZE, CURSOR_SIZE)),
-            );
-            out.push(Frame::Solid(SolidColorRenderElement::new(
-                self.cursor_id.clone(),
-                cursor_rect,
-                CommitCounter::default(),
-                CURSOR_COLOR,
-                Kind::Cursor,
-            )));
+            match &self.app.cursor_status {
+                CursorImageStatus::Hidden => {}
+                CursorImageStatus::Surface(surface) if surface.alive() => {
+                    let (hx, hy) = crate::cursor_hotspot(surface);
+                    let loc = (cx.round() as i32 - hx, cy.round() as i32 - hy);
+                    for el in render_elements_from_surface_tree(
+                        &mut self.renderer,
+                        surface,
+                        loc,
+                        1.0,
+                        1.0,
+                        Kind::Cursor,
+                    ) {
+                        out.push(Frame::Window(el));
+                    }
+                }
+                _ => {
+                    let cursor_rect = Rectangle::new(
+                        Point::<i32, Physical>::from((cx.round() as i32, cy.round() as i32)),
+                        Size::<i32, Physical>::from((CURSOR_SIZE, CURSOR_SIZE)),
+                    );
+                    out.push(Frame::Solid(SolidColorRenderElement::new(
+                        self.cursor_id.clone(),
+                        cursor_rect,
+                        CommitCounter::default(),
+                        CURSOR_COLOR,
+                        Kind::Cursor,
+                    )));
+                }
+            }
 
             let mut shown: Vec<_> = self.app.windows.iter().filter(|w| w.visible).collect();
             shown.sort_by_key(|w| !w.floating);
@@ -228,6 +250,12 @@ impl DrmState {
         for w in &self.app.windows {
             send_frames_surface_tree(&w.surface, time);
         }
+        // También a la superficie del cursor, por si es un cursor animado.
+        if let CursorImageStatus::Surface(surface) = &self.app.cursor_status {
+            if surface.alive() {
+                send_frames_surface_tree(surface, time);
+            }
+        }
     }
 
     /// Tarea periódica: Cerebro enlazado, recarga del keymap, API de
@@ -452,6 +480,13 @@ impl DrmState {
         );
         pointer.frame(&mut self.app);
 
+        // Sobre el escritorio pelado no manda ningún cliente: el cursor
+        // vuelve al de por defecto (si no, se queda con la «I» del texto
+        // de la última ventana).
+        if hit.is_none() {
+            self.app.cursor_status = CursorImageStatus::default_named();
+        }
+
         // Foco-sigue-ratón: al pasar a otra ventana, que el Cerebro la enfoque.
         let hovered = hit.map(|i| self.app.windows[i].id);
         if hovered != self.last_pointer_window {
diff --git a/crates/apps/mirada-compositor/src/main.rs b/crates/apps/mirada-compositor/src/main.rs
index 339b09b..c3712ef 100644
--- a/crates/apps/mirada-compositor/src/main.rs
+++ b/crates/apps/mirada-compositor/src/main.rs
@@ -33,7 +33,7 @@ use smithay::backend::renderer::utils::{
 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::pointer::PointerHandle;
+use smithay::input::pointer::{CursorImageStatus, CursorImageSurfaceData, PointerHandle};
 use smithay::input::{Seat, SeatHandler, SeatState};
 use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode;
 use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
@@ -140,6 +140,9 @@ struct App {
     pointer: Option<PointerHandle<Self>>,
     /// Posición del puntero en coordenadas globales.
     pointer_loc: (f64, f64),
+    /// Qué cursor pide el cliente enfocado — una superficie suya, un
+    /// cursor con nombre, u oculto. El backend lo pinta en consecuencia.
+    cursor_status: CursorImageStatus,
     /// Arrastre de ventana en curso (mover o redimensionar con el ratón).
     drag: Option<DragGrab>,
 
@@ -476,11 +479,11 @@ impl SeatHandler for App {
     }
 
     fn focus_changed(&mut self, _seat: &Seat<Self>, _focused: Option<&WlSurface>) {}
-    fn cursor_image(
-        &mut self,
-        _seat: &Seat<Self>,
-        _image: smithay::input::pointer::CursorImageStatus,
-    ) {
+
+    /// El cliente enfocado pidió un cursor — guardamos su petición; el
+    /// backend la pinta (su superficie, o el cuadrado si es con nombre).
+    fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {
+        self.cursor_status = image;
     }
 }
 
@@ -609,6 +612,22 @@ fn surface_px_size(w: &ManagedWindow) -> Option<(i32, i32)> {
         .map(|s| (s.w, s.h))
 }
 
+/// El punto caliente (hotspot) de una superficie de cursor: el píxel de
+/// la imagen que debe quedar bajo la posición real del puntero. `(0, 0)`
+/// si el cliente no lo declaró.
+fn cursor_hotspot(surface: &WlSurface) -> (i32, i32) {
+    with_states(surface, |states| {
+        states
+            .data_map
+            .get::<CursorImageSurfaceData>()
+            .map(|m| {
+                let h = m.lock().unwrap().hotspot;
+                (h.x, h.y)
+            })
+            .unwrap_or((0, 0))
+    })
+}
+
 /// 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
@@ -724,6 +743,7 @@ fn build_app() -> Result<Setup, Box<dyn std::error::Error>> {
         keyboard: None,
         pointer: None,
         pointer_loc: (0.0, 0.0),
+        cursor_status: CursorImageStatus::default_named(),
         drag: None,
         windows: Vec::new(),
         body: BodyState::new(),
diff --git a/crates/modules/mirada/SDD.md b/crates/modules/mirada/SDD.md
index d9a0574..d879b55 100644
--- a/crates/modules/mirada/SDD.md
+++ b/crates/modules/mirada/SDD.md
@@ -204,10 +204,11 @@ Cerebro: **autónomo** (`Desktop` embebido) o **enlazado** (`MIRADA_SOCKET`
   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 pinta un cursor de software (un
-  `SolidColorRenderElement` marcado `Kind::Cursor`, encima de todo en un
-  enum `Frame` de elementos de render); el foco sigue al puntero
-  (`BodyEvent::PointerEntered`) y clics y rueda van a la ventana debajo.
+  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
diff --git a/vamos.txt b/vamos.txt
index d128f1f..3520d9d 100644
--- a/vamos.txt
+++ b/vamos.txt
@@ -1001,7 +1001,7 @@
   MIRADA_SOCKET=/tmp/mirada.sock cargo run -p mirada-compositor     # enlazado: la app mirada (Cerebro GPUI) decide la geometría
   cargo run -p mirada-compositor -- --drm                          # nativo sobre TTY (MIRADA_STARTUP=foot lanza un cliente al arrancar)
   Habla wl_compositor/xdg_shell/wl_shm/wl_seat/wl_data_device; compone con GlesRenderer. Reusa mirada-body y mirada-link.
-  En --drm el ratón pinta un cursor de software, el foco sigue al puntero y clics/rueda van a la ventana debajo.
+  En --drm el foco sigue al puntero y clics/rueda van a la ventana debajo; el cursor toma la forma del cliente.
   Cada ventana lleva un marco fino: azul la enfocada, gris las demás.
   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.