tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
chore: refresco desde el monorepo — 128 miembros, los 76 widgets y los 11 módulos
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}