tawasuyu

tawasuyu/llimphi

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

Rust 660Markdown 302TOML 157RON 312382 Definiciones

⎇ main llimphi-ui/src/rueda.rs Rust · 117 líneas · 5.1 KB Historia Quién tocó qué crudo

chore: refresco desde el monorepo — 128 miembros, los 76 widgets y los 11 módulos b3f12e136 · Sergio ·

1// =============================================================================
2//  rueda :: sembrar el sentido de la rueda desde la sesión
3// -----------------------------------------------------------------------------
4//  `llimphi-entrada` define la política (dos sentidos, uno por clase de
5//  dispositivo) y las fronteras que fabrican un `WheelDelta` la aplican. Falta
6//  quién la FIJA, y ese es el runtime: la lee una vez al arrancar y la vuelve a
7//  fijar cuando el panel la cambia.
8//
9//  ## Por qué hay un dueño y no dos
10//
11//  El sentido se invierte UNA vez o no se invierte nunca: dos inversiones se
12//  cancelan y el ajuste no hace nada. Bajo `mirada` el dueño es el compositor,
13//  que se lo pide a libinput por dispositivo — y así alcanza también a firefox y
14//  a krita, que no saben nada de esta política. Bajo un escritorio ajeno
15//  (Hyprland, Windows, Android) no hay libinput nuestro que configurar y el
16//  dueño es este runtime.
17//
18//  Se distingue por `XDG_CURRENT_DESKTOP`, que es lo que el DM pone desde el
19//  `DesktopNames=mirada` de `mirada.desktop`. No es una heurística nuestra: es
20//  la variable estándar con la que cualquier portal/toolkit decide lo mismo.
21//  Si dice `mirada`, este runtime se queda **neutro** a propósito.
22//
23//  ## Por qué detrás de una feature
24//
25//  `llimphi-ui` es toolkit y `wawa-config` es el bus de config de un sistema
26//  operativo concreto: el toolkit publicable no puede depender de él. La feature
27//  `config-wawa` la enciende el `[workspace.dependencies]` de tawasuyu, de una
28//  vez para todas las apps del monorepo; fuera del monorepo llimphi sigue sin
29//  saber que wawa existe, y la política se fija a mano con
30//  [`llimphi_entrada::fijar_politica_rueda`].
31// =============================================================================
32
33/// `true` si la sesión ya invierte la rueda antes de que el evento llegue acá.
34/// Hoy: `mirada`, que la aplica en libinput por dispositivo.
35#[cfg(not(target_arch = "wasm32"))]
36pub fn la_sesion_ya_aplica_la_rueda() -> bool {
37    std::env::var("XDG_CURRENT_DESKTOP")
38        .map(|v| {
39            v.split(':')
40                .any(|d| d.trim().eq_ignore_ascii_case("mirada"))
41        })
42        .unwrap_or(false)
43}
44
45/// En el navegador no hay sesión de escritorio que aplique nada.
46#[cfg(target_arch = "wasm32")]
47pub fn la_sesion_ya_aplica_la_rueda() -> bool {
48    false
49}
50
51/// Siembra la política de rueda del proceso desde el bus de config del SO, y
52/// deja un watcher que la re-fija cuando el panel la cambia (en caliente, sin
53/// relanzar la app).
54///
55/// Idempotente: la llaman los dos runners (ventana y capa) y una app puede
56/// tener las dos cosas.
57#[cfg(all(feature = "config-wawa", not(target_arch = "wasm32")))]
58pub fn sembrar_politica_rueda() {
59    use llimphi_compositor::input::{fijar_politica_rueda, PoliticaRueda, SentidoRueda};
60    use std::sync::OnceLock;
61
62    static UNA_VEZ: OnceLock<()> = OnceLock::new();
63    // El watcher se suelta al soltar el guard: hay que retenerlo tanto como el
64    // proceso, o el primer cambio del panel no llegaría nunca.
65    static WATCHER: OnceLock<wawa_config::ConfigWatcher> = OnceLock::new();
66    if UNA_VEZ.set(()).is_err() {
67        return;
68    }
69
70    // Bajo mirada manda libinput. Fijamos neutro explícitamente (y no "no
71    // tocamos nada") para que una app que ya venía con otra política —un test,
72    // una app que la fijó a mano— no quede invirtiendo de más.
73    if la_sesion_ya_aplica_la_rueda() {
74        fijar_politica_rueda(PoliticaRueda::default());
75        return;
76    }
77
78    fn de_config(cfg: &wawa_config::WawaConfig) -> PoliticaRueda {
79        PoliticaRueda {
80            raton: SentidoRueda::desde_natural(cfg.entrada.raton_natural()),
81            touchpad: SentidoRueda::desde_natural(cfg.entrada.touchpad_natural()),
82        }
83    }
84
85    fijar_politica_rueda(de_config(&wawa_config::WawaConfig::load()));
86    if let Ok(w) = wawa_config::ConfigWatcher::spawn(|cfg| {
87        fijar_politica_rueda(de_config(&cfg));
88    }) {
89        let _ = WATCHER.set(w);
90    }
91}
92
93/// Sin la feature `config-wawa` (llimphi fuera del monorepo) o en el navegador:
94/// no hay bus que leer. La política queda en su default neutro y quien quiera
95/// otra la fija con `llimphi_entrada::fijar_politica_rueda`.
96#[cfg(not(all(feature = "config-wawa", not(target_arch = "wasm32"))))]
97pub fn sembrar_politica_rueda() {}
98
99#[cfg(test)]
100mod tests {
101    // `la_sesion_ya_aplica_la_rueda` lee una env del proceso, así que los dos
102    // casos van en UN test: dos tests tocando la misma env corren en paralelo y
103    // se pisan.
104    #[test]
105    #[cfg(not(target_arch = "wasm32"))]
106    fn mirada_se_reconoce_en_xdg_current_desktop() {
107        std::env::set_var("XDG_CURRENT_DESKTOP", "mirada");
108        assert!(super::la_sesion_ya_aplica_la_rueda());
109        // El formato real del DM es una lista separada por `:`.
110        std::env::set_var("XDG_CURRENT_DESKTOP", "wlroots:mirada");
111        assert!(super::la_sesion_ya_aplica_la_rueda());
112        std::env::set_var("XDG_CURRENT_DESKTOP", "Hyprland");
113        assert!(!super::la_sesion_ya_aplica_la_rueda());
114        std::env::remove_var("XDG_CURRENT_DESKTOP");
115        assert!(!super::la_sesion_ya_aplica_la_rueda());
116    }
117}