tawasuyu
⎇ 6ad6d08fa80fc6796e79f3f522830914077728f7 crates/ente-localed-compat/src/main.rs Rust · 188 líneas · 5.9 KB Historia Quién tocó qué crudo

4 D-Bus shims sustituyendo systemd: hostnamed, timedated, localed, journald 6ad6d08fa · Sergio ·

1//! ente-localed-compat: shim de `org.freedesktop.locale1`.
2//!
3//! GNOME settings panel "Region & Language" llama aquí. Properties leen
4//! /etc/locale.conf y /etc/vconsole.conf; setters log + forward.
5
6use ente_bus::{BusClient, BusRequest, BusResponse};
7use ente_card::Capability;
8use std::sync::Mutex;
9use tokio::signal::unix::{signal, SignalKind};
10use tracing::{info, warn};
11use tracing_subscriber::EnvFilter;
12use zbus::{fdo, interface};
13
14const BUS_NAME: &str = "org.freedesktop.locale1";
15const OBJ_PATH: &str = "/org/freedesktop/locale1";
16
17#[tokio::main(flavor = "current_thread")]
18async fn main() -> anyhow::Result<()> {
19    init_tracing();
20    info!("ente-localed-compat: arrancando");
21    announce_to_fractal().await;
22
23    let manager = LocaleManager::default();
24    let conn_result = zbus::connection::Builder::system()
25        .and_then(|b| b.name(BUS_NAME))
26        .and_then(|b| b.serve_at(OBJ_PATH, manager));
27    match conn_result {
28        Ok(builder) => match builder.build().await {
29            Ok(_conn) => {
30                info!(name = BUS_NAME, "name acquired, sirviendo");
31                wait_for_term().await
32            }
33            Err(e) => {
34                warn!(?e, "build conn falló — modo idle");
35                wait_for_term().await
36            }
37        },
38        Err(e) => {
39            warn!(?e, "builder D-Bus falló — modo idle");
40            wait_for_term().await
41        }
42    }
43}
44
45#[derive(Default)]
46struct LocaleManager {
47    transient_locale: Mutex<Option<Vec<String>>>,
48}
49
50#[interface(name = "org.freedesktop.locale1")]
51impl LocaleManager {
52    /// Locale actual como array de "KEY=value" (LANG=en_US.UTF-8, LC_TIME=...).
53    /// Default: leer /etc/locale.conf.
54    #[zbus(property)]
55    async fn locale(&self) -> Vec<String> {
56        if let Some(v) = self.transient_locale.lock().unwrap().clone() {
57            return v;
58        }
59        match std::fs::read_to_string("/etc/locale.conf") {
60            Ok(c) => c.lines()
61                .filter(|l| !l.trim().is_empty() && !l.starts_with('#'))
62                .map(|s| s.trim().to_string())
63                .collect(),
64            Err(_) => vec!["LANG=C.UTF-8".into()],
65        }
66    }
67
68    #[zbus(property)]
69    async fn x11layout(&self) -> String {
70        read_kv("/etc/X11/xorg.conf.d/00-keyboard.conf", "XkbLayout").unwrap_or_default()
71    }
72
73    #[zbus(property)]
74    async fn x11model(&self) -> String {
75        read_kv("/etc/X11/xorg.conf.d/00-keyboard.conf", "XkbModel").unwrap_or_default()
76    }
77
78    #[zbus(property)]
79    async fn x11variant(&self) -> String {
80        read_kv("/etc/X11/xorg.conf.d/00-keyboard.conf", "XkbVariant").unwrap_or_default()
81    }
82
83    #[zbus(property)]
84    async fn x11options(&self) -> String {
85        read_kv("/etc/X11/xorg.conf.d/00-keyboard.conf", "XkbOptions").unwrap_or_default()
86    }
87
88    #[zbus(property)]
89    async fn vconsole_keymap(&self) -> String {
90        read_vconsole("KEYMAP").unwrap_or_default()
91    }
92
93    #[zbus(property)]
94    async fn vconsole_keymap_toggle(&self) -> String {
95        read_vconsole("KEYMAP_TOGGLE").unwrap_or_default()
96    }
97
98    async fn set_locale(&self, locale: Vec<String>, _interactive: bool) -> fdo::Result<()> {
99        info!(?locale, "SetLocale (stub: no persistimos a /etc/locale.conf)");
100        *self.transient_locale.lock().unwrap() = Some(locale);
101        Ok(())
102    }
103
104    async fn set_vconsole_keymap(
105        &self,
106        keymap: String,
107        keymap_toggle: String,
108        _convert: bool,
109        _interactive: bool,
110    ) -> fdo::Result<()> {
111        info!(%keymap, %keymap_toggle, "SetVConsoleKeymap (stub)");
112        Ok(())
113    }
114
115    async fn set_x11_keyboard(
116        &self,
117        layout: String,
118        model: String,
119        variant: String,
120        options: String,
121        _convert: bool,
122        _interactive: bool,
123    ) -> fdo::Result<()> {
124        info!(%layout, %model, %variant, %options, "SetX11Keyboard (stub)");
125        Ok(())
126    }
127}
128
129fn read_kv(path: &str, key: &str) -> Option<String> {
130    let content = std::fs::read_to_string(path).ok()?;
131    for line in content.lines() {
132        let trimmed = line.trim();
133        if trimmed.starts_with(&format!("Option \"{key}\"")) || trimmed.starts_with(key) {
134            // Best-effort parse: tomar lo que está entre comillas.
135            if let Some(start) = trimmed.find('"') {
136                let rest = &trimmed[start + 1..];
137                if let Some(end) = rest.find('"') {
138                    return Some(rest[..end].to_string());
139                }
140            }
141        }
142    }
143    None
144}
145
146fn read_vconsole(key: &str) -> Option<String> {
147    let content = std::fs::read_to_string("/etc/vconsole.conf").ok()?;
148    for line in content.lines() {
149        if let Some((k, v)) = line.split_once('=') {
150            if k.trim() == key {
151                return Some(v.trim().trim_matches('"').to_string());
152            }
153        }
154    }
155    None
156}
157
158async fn announce_to_fractal() {
159    if let Ok(mut client) = BusClient::from_env().await {
160        let req = BusRequest::Announce {
161            capabilities: vec![Capability::Endpoint {
162                interface: ente_card::InterfaceId([0xa2; 16]),
163                version: 1,
164            }],
165        };
166        match client.call(req).await {
167            Ok(BusResponse::Ok) => info!("Announce → bus interno OK"),
168            Ok(other) => warn!(?other, "Announce respuesta inesperada"),
169            Err(e) => warn!(?e, "Announce falló"),
170        }
171    }
172}
173
174async fn wait_for_term() -> anyhow::Result<()> {
175    let mut term = signal(SignalKind::terminate())?;
176    let mut int_ = signal(SignalKind::interrupt())?;
177    tokio::select! {
178        _ = term.recv() => info!("SIGTERM"),
179        _ = int_.recv() => info!("SIGINT"),
180    }
181    Ok(())
182}
183
184fn init_tracing() {
185    let filter = EnvFilter::try_from_default_env()
186        .unwrap_or_else(|_| EnvFilter::new("ente_localed_compat=info"));
187    tracing_subscriber::fmt().with_env_filter(filter).with_target(true).init();
188}