⎇ 6ad6d08fa80fc6796e79f3f522830914077728f7
crates/ente-localed-compat/src/main.rs
Historia
Quién tocó qué
crudo
4 D-Bus shims sustituyendo systemd: hostnamed, timedated, localed, journald
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}