tawasuyu

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 660Markdown 302TOML 157RON 312382 Sutichaykuna

⎇ main llimphi-layer/examples/sonda_capa_crece.rs Rust · 368 siq'ikuna · 13.2 KB Ñawpa willay Pi imata llamkarqan ch'uya

chore: refresco desde el monorepo (pin tawasuyu.git v0.3.0) b575c63f1 · Sergio ·

1//! **Sonda: qué hace el compositor con una capa que crece y todavía no pintó.**
2//!
3//! El defecto que persigue («la barra del input aparece arriba al desplegar el
4//! cajón, y a media pantalla al abrir el menú de inicio») dura un instante: una
5//! capa anclada ABAJO pide más alto (`set_size` + commit), el compositor
6//! reacomoda su geometría en el acto —el origen salta del pie al techo— y hasta
7//! que llega el buffer nuevo hay al menos un cuadro con el buffer VIEJO. Si el
8//! compositor lo dibuja en la esquina de la geometría nueva, esos 40 px de barra
9//! se ven **arriba de todo**.
10//!
11//! Cazar ese cuadro con pantallazos no funciona: dura menos que una captura, y
12//! no sale siempre. Esta sonda invierte el problema — se mete en ese estado **a
13//! propósito y se queda ahí** los segundos que uno le pida, sobre el compositor
14//! que ya está corriendo, sin reiniciar la sesión:
15//!
16//! 1. crea una capa anclada `BOTTOM|LEFT|RIGHT` de `--fine` px y la pinta;
17//! 2. pide `set_size(0, --large)` y **no pinta el buffer nuevo**;
18//! 3. se queda `--wait` segundos en ese estado (acking los `configure`);
19//! 4. recién entonces pinta el buffer grande y se va.
20//!
21//! Durante (3) el pantallazo contesta la pregunta sin ambigüedad: si la franja
22//! de color se ve **abajo**, el compositor pega el buffer a su borde anclado y el
23//! defecto está en otro lado; si se ve **arriba** (o a media pantalla), el que
24//! falla es el emplazamiento de la capa. Los dos colores son distintos a
25//! propósito: el fino es magenta, el grande es cian, así ninguna captura los
26//! confunde.
27//!
28//! ```bash
29//! cargo run -p llimphi-layer --example sonda_capa_crece -- --fine 40 --large 800 --wait 20
30//! ```
31
32use smithay_client_toolkit::{
33    compositor::{CompositorHandler, CompositorState},
34    delegate_compositor, delegate_layer, delegate_output, delegate_registry, delegate_shm,
35    output::{OutputHandler, OutputState},
36    registry::{ProvidesRegistryState, RegistryState},
37    registry_handlers,
38    shell::{
39        wlr_layer::{
40            Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler, LayerSurface,
41            LayerSurfaceConfigure,
42        },
43        WaylandSurface,
44    },
45    shm::{slot::SlotPool, Shm, ShmHandler},
46};
47use std::time::{Duration, Instant};
48use wayland_client::{
49    globals::registry_queue_init,
50    protocol::{wl_output, wl_shm, wl_surface},
51    Connection, QueueHandle,
52};
53
54/// En qué tramo de la secuencia va la sonda.
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56enum Paso {
57    /// Pintó la franja fina; espera para pedir el alto grande.
58    Fino,
59    /// Pidió el alto grande y **no** pinta: es el estado que se investiga.
60    Creciendo,
61    /// Pintó el buffer grande; espera para irse.
62    Grande,
63}
64
65struct Sonda {
66    registry: RegistryState,
67    output: OutputState,
68    shm: Shm,
69    pool: SlotPool,
70    capa: LayerSurface,
71    ancho: u32,
72    /// Alto del buffer que hay pintado AHORA (no el configurado).
73    alto_buffer: u32,
74    fino: u32,
75    grande: u32,
76    paso: Paso,
77    desde: Instant,
78    t0: Instant,
79    espera: u64,
80    salir: bool,
81    /// Dónde dejar los pantallazos que la sonda se saca a sí misma, y cuáles ya
82    /// sacó. Que los saque ELLA es la mitad que importa: un `sleep` de afuera
83    /// nunca cae dentro del estado, y el estado es todo el punto de la sonda.
84    shots: Option<String>,
85    shot_hecho: [bool; 3],
86    /// Último tamaño que mandó el compositor por `configure`.
87    ultimo_configure: (u32, u32),
88    /// Hay que encadenar otro frame-callback en esta vuelta del bucle.
89    pide_cuadro: bool,
90}
91
92impl Sonda {
93    /// Dispara `hapiy` sin esperarlo: el pantallazo tiene que salir DENTRO del
94    /// estado, y bloquear el bucle acá lo dejaría sin acking mientras tanto.
95    fn pantallazo(&mut self, n: usize, nombre: &str) {
96        if self.shot_hecho[n] {
97            return;
98        }
99        self.shot_hecho[n] = true;
100        let Some(dir) = self.shots.clone() else { return };
101        let ruta = format!("{dir}/{nombre}.png");
102        match std::process::Command::new("hapiy").arg("-o").arg(&ruta).spawn() {
103            Ok(_) => self.traza(&format!("pantallazo → {ruta}")),
104            Err(e) => eprintln!("sonda: no pude sacar el pantallazo: {e}"),
105        }
106    }
107
108    fn traza(&self, que: &str) {
109        println!(
110            "[{:>7.3}s] {que} · configure={}x{} buffer={}x{} paso={:?}",
111            self.t0.elapsed().as_secs_f32(),
112            self.ultimo_configure.0,
113            self.ultimo_configure.1,
114            self.ancho,
115            self.alto_buffer,
116            self.paso
117        );
118    }
119
120    /// Pinta una franja opaca del alto pedido: relleno + un borde blanco de 2 px
121    /// para que el canto se distinga del fondo en cualquier pantallazo.
122    fn pintar(&mut self, alto: u32, (r, g, b): (u8, u8, u8)) {
123        let (w, h) = (self.ancho.max(1), alto.max(1));
124        let stride = w as i32 * 4;
125        let Ok((buffer, lienzo)) =
126            self.pool.create_buffer(w as i32, h as i32, stride, wl_shm::Format::Argb8888)
127        else {
128            eprintln!("sonda: no pude crear el buffer {w}x{h}");
129            return;
130        };
131        for y in 0..h {
132            for x in 0..w {
133                let borde = y < 2 || y + 2 >= h || x < 2 || x + 2 >= w;
134                let px = ((y * w + x) * 4) as usize;
135                let (cr, cg, cb) = if borde { (255, 255, 255) } else { (r, g, b) };
136                // Argb8888 en little-endian: B, G, R, A.
137                lienzo[px] = cb;
138                lienzo[px + 1] = cg;
139                lienzo[px + 2] = cr;
140                lienzo[px + 3] = 255;
141            }
142        }
143        let surface = self.capa.wl_surface();
144        surface.damage_buffer(0, 0, w as i32, h as i32);
145        let _ = buffer.attach_to(surface);
146        surface.commit();
147        self.alto_buffer = h;
148        self.pide_cuadro = true;
149    }
150}
151
152impl CompositorHandler for Sonda {
153    fn scale_factor_changed(
154        &mut self,
155        _: &Connection,
156        _: &QueueHandle<Self>,
157        _: &wl_surface::WlSurface,
158        _: i32,
159    ) {
160    }
161    fn transform_changed(
162        &mut self,
163        _: &Connection,
164        _: &QueueHandle<Self>,
165        _: &wl_surface::WlSurface,
166        _: wl_output::Transform,
167    ) {
168    }
169    fn frame(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &wl_surface::WlSurface, _: u32) {
170        // Encadenar el pedido es lo que mantiene vivo el bucle. Es un commit
171        // **sin buffer**: durante «Creciendo» no puede colarse una pintada, que
172        // es la condición entera del experimento.
173        self.pide_cuadro = true;
174    }
175    fn surface_enter(
176        &mut self,
177        _: &Connection,
178        _: &QueueHandle<Self>,
179        _: &wl_surface::WlSurface,
180        _: &wl_output::WlOutput,
181    ) {
182    }
183    fn surface_leave(
184        &mut self,
185        _: &Connection,
186        _: &QueueHandle<Self>,
187        _: &wl_surface::WlSurface,
188        _: &wl_output::WlOutput,
189    ) {
190    }
191}
192
193impl OutputHandler for Sonda {
194    fn output_state(&mut self) -> &mut OutputState {
195        &mut self.output
196    }
197    fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
198    fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
199    fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
200}
201
202impl ShmHandler for Sonda {
203    fn shm_state(&mut self) -> &mut Shm {
204        &mut self.shm
205    }
206}
207
208impl LayerShellHandler for Sonda {
209    fn closed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &LayerSurface) {
210        self.salir = true;
211    }
212
213    fn configure(
214        &mut self,
215        _: &Connection,
216        _: &QueueHandle<Self>,
217        _: &LayerSurface,
218        cfg: LayerSurfaceConfigure,
219        _: u32,
220    ) {
221        let (w, h) = cfg.new_size;
222        self.ultimo_configure = (w, h);
223        if w > 0 {
224            self.ancho = w;
225        }
226        match self.paso {
227            // El primer `configure` es el que nos dice el ancho: recién ahí se
228            // puede pintar la franja fina.
229            Paso::Fino if self.alto_buffer == 0 => {
230                self.traza("configure inicial → pinto el fino (magenta)");
231                let fino = self.fino;
232                self.pintar(fino, (255, 0, 200));
233            }
234            // **El corazón de la sonda.** Llegó el `configure` con el alto grande
235            // y NO pintamos: así es exactamente como está la barra de pata durante
236            // el cuadro que se ve mal, pero sostenido en el tiempo.
237            Paso::Creciendo => {
238                self.traza("configure GRANDE — NO pinto (estado bajo observación)");
239            }
240            _ => self.traza("configure"),
241        }
242    }
243}
244
245delegate_compositor!(Sonda);
246delegate_output!(Sonda);
247delegate_shm!(Sonda);
248delegate_layer!(Sonda);
249delegate_registry!(Sonda);
250
251impl ProvidesRegistryState for Sonda {
252    fn registry(&mut self) -> &mut RegistryState {
253        &mut self.registry
254    }
255    registry_handlers![OutputState];
256}
257
258fn arg(nombre: &str, def: u64) -> u64 {
259    let args: Vec<String> = std::env::args().collect();
260    args.iter()
261        .position(|a| a == nombre)
262        .and_then(|i| args.get(i + 1))
263        .and_then(|v| v.parse().ok())
264        .unwrap_or(def)
265}
266
267fn main() {
268    let fino = arg("--fine", 40) as u32;
269    let grande = arg("--large", 800) as u32;
270    let espera = arg("--wait", 20);
271
272    let conn = Connection::connect_to_env().expect("sin compositor wayland");
273    let (globals, mut cola) = registry_queue_init(&conn).expect("registry");
274    let qh = cola.handle();
275
276    let compositor = CompositorState::bind(&globals, &qh).expect("wl_compositor");
277    let layer_shell = LayerShell::bind(&globals, &qh).expect("zwlr_layer_shell_v1");
278    let shm = Shm::bind(&globals, &qh).expect("wl_shm");
279    let pool = SlotPool::new(4096 * 64 * 4, &shm).expect("slot pool");
280
281    let surface = compositor.create_surface(&qh);
282    let capa = layer_shell.create_layer_surface(
283        &qh,
284        surface,
285        Layer::Overlay,
286        Some("sonda-capa-crece"),
287        None,
288    );
289    // Misma postura que la barra de pata: anclada ABAJO y a los dos costados,
290    // grosor fijo, sin zona exclusiva (flota sobre lo que haya).
291    capa.set_anchor(Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT);
292    capa.set_size(0, fino);
293    capa.set_exclusive_zone(0);
294    capa.set_keyboard_interactivity(KeyboardInteractivity::None);
295    capa.commit();
296
297    let ahora = Instant::now();
298    let mut sonda = Sonda {
299        registry: RegistryState::new(&globals),
300        output: OutputState::new(&globals, &qh),
301        shm,
302        pool,
303        capa,
304        ancho: 0,
305        alto_buffer: 0,
306        fino,
307        grande,
308        paso: Paso::Fino,
309        desde: ahora,
310        t0: ahora,
311        espera,
312        salir: false,
313        shots: std::env::args()
314            .position(|a| a == "--shots")
315            .and_then(|i| std::env::args().nth(i + 1)),
316        shot_hecho: [false; 3],
317        ultimo_configure: (0, 0),
318        pide_cuadro: false,
319    };
320
321    println!("sonda-capa-crece · fino={fino}px grande={grande}px espera={espera}s");
322    println!("  franja MAGENTA = el buffer fino · CIAN = el buffer grande");
323    println!("  la pregunta: durante «Creciendo», ¿el magenta se ve ABAJO o ARRIBA?");
324
325    // El primer callback: de acá en más se re-pide solo.
326    sonda.pide_cuadro = true;
327    while !sonda.salir {
328        if std::mem::take(&mut sonda.pide_cuadro) {
329            let s = sonda.capa.wl_surface().clone();
330            s.frame(&qh, s.clone());
331            s.commit();
332        }
333        cola.blocking_dispatch(&mut sonda).expect("dispatch");
334        match sonda.paso {
335            Paso::Fino if sonda.alto_buffer > 0 && sonda.desde.elapsed() >= Duration::from_secs(2) && !sonda.shot_hecho[0] => {
336                sonda.pantallazo(0, "sonda-fino");
337            }
338            Paso::Fino if sonda.alto_buffer > 0 && sonda.desde.elapsed() >= Duration::from_secs(3) => {
339                sonda.paso = Paso::Creciendo;
340                sonda.desde = Instant::now();
341                // El pedido, SIN buffer nuevo: es el commit que mueve la geometría.
342                sonda.capa.set_size(0, sonda.grande);
343                sonda.capa.commit();
344                sonda.traza("PEDÍ el alto grande (sin pintar) — estado bajo observación");
345            }
346            // Un segundo adentro del estado: ya se asentó el `configure` y el
347            // buffer sigue siendo el chico. Éste es EL pantallazo.
348            Paso::Creciendo if sonda.desde.elapsed() >= Duration::from_secs(1) && !sonda.shot_hecho[1] => {
349                sonda.pantallazo(1, "sonda-creciendo");
350            }
351            Paso::Creciendo if sonda.desde.elapsed() >= Duration::from_secs(sonda.espera) => {
352                sonda.paso = Paso::Grande;
353                sonda.desde = Instant::now();
354                let grande = sonda.grande;
355                sonda.pintar(grande, (0, 220, 255));
356                sonda.traza("pinté el buffer grande (cian)");
357            }
358            Paso::Grande if sonda.desde.elapsed() >= Duration::from_secs(1) && !sonda.shot_hecho[2] => {
359                sonda.pantallazo(2, "sonda-grande");
360            }
361            Paso::Grande if sonda.desde.elapsed() >= Duration::from_secs(3) => {
362                sonda.traza("listo, me voy");
363                sonda.salir = true;
364            }
365            _ => {}
366        }
367    }
368}