pub(crate) fn read_portal_u32(namespace: &str, key: &str) -> Option<u32> {
let output = std::process::Command::new("busctl")
.args([
"--user",
"call",
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.Settings",
"ReadOne",
"ss",
namespace,
key,
])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout);
stdout.split_whitespace().last()?.parse::<u32>().ok()
}
pub(crate) fn read_gsettings(schema: &str, key: &str) -> Option<String> {
let output = std::process::Command::new("gsettings")
.args(["get", schema, key])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let value = String::from_utf8_lossy(&output.stdout).trim().to_string();
Some(value.trim_matches('\'').to_string())
}
pub(crate) fn read_portal_rgb(namespace: &str, key: &str) -> Option<(f64, f64, f64)> {
let output = std::process::Command::new("busctl")
.args([
"--user",
"call",
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.Settings",
"ReadOne",
"ss",
namespace,
key,
])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let stdout = String::from_utf8_lossy(&output.stdout);
let parts: Vec<&str> = stdout.split_whitespace().collect();
if parts.len() >= 3 {
let b = parts[parts.len() - 1].parse::<f64>().ok()?;
let g = parts[parts.len() - 2].parse::<f64>().ok()?;
let r = parts[parts.len() - 3].parse::<f64>().ok()?;
if (0.0..=1.0).contains(&r) && (0.0..=1.0).contains(&g) && (0.0..=1.0).contains(&b) {
Some((r, g, b))
} else {
None
}
} else {
None
}
}
#[allow(dead_code)]
pub(crate) enum Desktop {
Gnome,
Kde,
Cinnamon,
Other(String),
}
pub(crate) fn detect_desktop() -> Desktop {
let xdg = std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_default();
let upper = xdg.to_uppercase();
if upper.contains("GNOME") {
Desktop::Gnome
} else if upper.contains("KDE") {
Desktop::Kde
} else if upper.contains("CINNAMON") || upper.contains("X-CINNAMON") {
Desktop::Cinnamon
} else {
Desktop::Other(xdg)
}
}
pub(crate) fn parse_busctl_bool(stdout: &str) -> Option<bool> {
let mut tokens = stdout.split_whitespace();
if tokens.next()? != "b" {
return None;
}
let value = match tokens.next()? {
"true" => true,
"false" => false,
_ => return None,
};
if tokens.next().is_some() {
return None;
}
Some(value)
}
pub(crate) fn read_dbus_bool_property(
service: &str,
path: &str,
interface: &str,
property: &str,
) -> Option<bool> {
let output = std::process::Command::new("busctl")
.args(["--user", "get-property", service, path, interface, property])
.output()
.ok()?;
if !output.status.success() {
return None;
}
parse_busctl_bool(&String::from_utf8_lossy(&output.stdout))
}
#[cfg(test)]
mod tests {
use super::parse_busctl_bool;
#[test]
fn busctl_bool_replies_parse() {
assert_eq!(parse_busctl_bool("b true\n"), Some(true));
assert_eq!(parse_busctl_bool("b false\n"), Some(false));
assert_eq!(parse_busctl_bool(" b true "), Some(true));
}
#[test]
fn non_boolean_replies_are_unknown_not_false() {
assert_eq!(parse_busctl_bool("u 1\n"), None);
assert_eq!(parse_busctl_bool("s \"true\"\n"), None);
assert_eq!(parse_busctl_bool(""), None);
assert_eq!(parse_busctl_bool("b\n"), None);
assert_eq!(parse_busctl_bool("b 1\n"), None);
assert_eq!(parse_busctl_bool("b true extra\n"), None);
}
}