use alloc::string::String;
use super::{defaults, LinuxCustomization, ScrollbarPreferences, ScrollbarVisibility};
use crate::corety::{AzString, OptionString};
#[cfg(feature = "io")]
fn query_xdg_portal() -> Option<(u32, Option<(f64, f64, f64)>)> {
use std::io::{Read, Write};
use std::os::unix::net::UnixStream;
use std::time::Duration;
let bus_addr = std::env::var("DBUS_SESSION_BUS_ADDRESS").ok()?;
let path = bus_addr
.strip_prefix("unix:path=")?;
let path = path.split(',').next()?;
let mut stream = UnixStream::connect(path).ok()?;
stream.set_read_timeout(Some(Duration::from_secs(2))).ok()?;
stream.set_write_timeout(Some(Duration::from_secs(2))).ok()?;
let uid = unsafe { libc_getuid() };
let auth_msg = alloc::format!("\0AUTH EXTERNAL {}\r\nBEGIN\r\n", hex_encode_uid(uid));
stream.write_all(auth_msg.as_bytes()).ok()?;
let mut buf = [0u8; 256];
let n = stream.read(&mut buf).ok()?;
let resp = core::str::from_utf8(&buf[..n]).ok()?;
if !resp.contains("OK") { return None; }
let hello_msg = build_dbus_method_call(
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"Hello",
&[],
1,
);
stream.write_all(&hello_msg).ok()?;
let mut resp_buf = vec![0u8; 4096];
let _ = stream.read(&mut resp_buf);
let read_msg = build_dbus_method_call(
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.Settings",
"Read",
&[
DValue::String("org.freedesktop.appearance"),
DValue::String("color-scheme"),
],
2,
);
stream.write_all(&read_msg).ok()?;
let mut resp_buf = vec![0u8; 4096];
let n = stream.read(&mut resp_buf).ok()?;
let color_scheme = parse_uint32_from_variant_response(&resp_buf[..n]).unwrap_or(0);
let accent_msg = build_dbus_method_call(
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.Settings",
"Read",
&[
DValue::String("org.freedesktop.appearance"),
DValue::String("accent-color"),
],
3,
);
stream.write_all(&accent_msg).ok()?;
let mut resp_buf2 = vec![0u8; 4096];
let n2 = stream.read(&mut resp_buf2).unwrap_or(0);
let accent = parse_rgb_from_variant_response(&resp_buf2[..n2]);
Some((color_scheme, accent))
}
enum DValue<'a> {
String(&'a str),
}
fn build_dbus_method_call(
destination: &str,
path: &str,
interface: &str,
method: &str,
args: &[DValue<'_>],
serial: u32,
) -> alloc::vec::Vec<u8> {
let mut body = alloc::vec::Vec::new();
let mut sig = String::new();
for arg in args {
match arg {
DValue::String(s) => {
sig.push('s');
let bytes = s.as_bytes();
body.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
body.extend_from_slice(bytes);
body.push(0); while body.len() % 4 != 0 { body.push(0); }
}
}
}
let mut header_fields = alloc::vec::Vec::new();
append_header_field(&mut header_fields, 1, 'o', path);
append_header_field(&mut header_fields, 2, 's', interface);
append_header_field(&mut header_fields, 3, 's', method);
append_header_field(&mut header_fields, 6, 's', destination);
if !sig.is_empty() {
while header_fields.len() % 8 != 0 { header_fields.push(0); }
header_fields.push(8); header_fields.push(1); header_fields.push(b'g');
header_fields.push(0); let sig_bytes = sig.as_bytes();
header_fields.push(sig_bytes.len() as u8);
header_fields.extend_from_slice(sig_bytes);
header_fields.push(0);
}
while header_fields.len() % 8 != 0 { header_fields.push(0); }
let mut msg = alloc::vec::Vec::new();
msg.push(b'l'); msg.push(1); msg.push(0); msg.push(1); msg.extend_from_slice(&(body.len() as u32).to_le_bytes());
msg.extend_from_slice(&serial.to_le_bytes());
msg.extend_from_slice(&(header_fields.len() as u32).to_le_bytes());
msg.extend_from_slice(&header_fields);
while msg.len() % 8 != 0 { msg.push(0); }
msg.extend_from_slice(&body);
msg
}
fn append_header_field(buf: &mut alloc::vec::Vec<u8>, code: u8, sig: char, value: &str) {
while buf.len() % 8 != 0 { buf.push(0); }
buf.push(code);
buf.push(1); buf.push(sig as u8);
buf.push(0); while buf.len() % 4 != 0 { buf.push(0); }
let bytes = value.as_bytes();
buf.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
buf.extend_from_slice(bytes);
buf.push(0);
}
fn parse_uint32_from_variant_response(data: &[u8]) -> Option<u32> {
if data.len() < 16 { return None; }
let body_len = u32::from_le_bytes(data[4..8].try_into().ok()?) as usize;
let header_fields_len = u32::from_le_bytes(data[12..16].try_into().ok()?) as usize;
let body_start = 16 + header_fields_len;
let body_start = (body_start + 7) & !7;
if body_start + body_len > data.len() { return None; }
let body = &data[body_start..body_start + body_len];
if body.len() >= 4 {
let val = u32::from_le_bytes(body[body.len()-4..].try_into().ok()?);
if val <= 2 { return Some(val); }
}
None
}
fn parse_rgb_from_variant_response(_data: &[u8]) -> Option<(f64, f64, f64)> {
None
}
extern "C" { fn getuid() -> u32; }
unsafe fn libc_getuid() -> u32 { getuid() }
fn hex_encode_uid(uid: u32) -> String {
let uid_str = alloc::format!("{}", uid);
let mut hex = String::new();
for b in uid_str.bytes() {
hex.push_str(&alloc::format!("{:02x}", b));
}
hex
}
#[cfg(feature = "io")]
fn gsettings_get(schema: &str, key: &str) -> Option<String> {
use std::process::{Command, Stdio};
let out = Command::new("gsettings")
.args(["get", schema, key])
.stdout(Stdio::piped())
.stderr(Stdio::null())
.output()
.ok()?;
if out.status.success() {
Some(String::from_utf8_lossy(&out.stdout).trim().trim_matches('\'').to_string())
} else {
None
}
}
#[cfg(feature = "io")]
fn discover_linux_extras(style: &mut super::SystemStyle) {
if let Some(icon) = gsettings_get("org.gnome.desktop.interface", "icon-theme") {
style.linux.icon_theme = OptionString::Some(icon.into());
}
if let Some(cursor) = gsettings_get("org.gnome.desktop.interface", "cursor-theme") {
style.linux.cursor_theme = OptionString::Some(cursor.into());
}
if let Some(size_s) = gsettings_get("org.gnome.desktop.interface", "cursor-size") {
if let Ok(sz) = size_s.parse::<u32>() {
style.linux.cursor_size = sz;
}
}
if let Some(gtk) = gsettings_get("org.gnome.desktop.interface", "gtk-theme") {
style.linux.gtk_theme = OptionString::Some(gtk.into());
}
if let Some(layout) = gsettings_get("org.gnome.desktop.wm.preferences", "button-layout") {
style.linux.titlebar_button_layout = OptionString::Some(layout.clone().into());
if layout.starts_with(':') {
style.metrics.titlebar.button_side = super::TitlebarButtonSide::Right;
} else {
style.metrics.titlebar.button_side = super::TitlebarButtonSide::Left;
}
}
if style.linux.cursor_theme.is_none() {
if let Ok(t) = std::env::var("XCURSOR_THEME") {
style.linux.cursor_theme = OptionString::Some(t.into());
}
}
if style.linux.cursor_size == 0 {
if let Ok(s) = std::env::var("XCURSOR_SIZE") {
if let Ok(sz) = s.parse::<u32>() {
style.linux.cursor_size = sz;
}
}
}
if let Some(anim_s) = gsettings_get("org.gnome.desktop.interface", "enable-animations") {
let enabled = anim_s.trim() != "false";
style.animation.animations_enabled = enabled;
if !enabled {
style.prefers_reduced_motion = crate::dynamic_selector::BoolCondition::True;
style.accessibility.prefers_reduced_motion = true;
}
}
if let Some(ev) = gsettings_get("org.gnome.desktop.sound", "event-sounds") {
style.audio.event_sounds_enabled = ev.trim() != "false";
}
if let Some(inp) = gsettings_get("org.gnome.desktop.sound", "input-feedback-sounds") {
style.audio.input_feedback_sounds_enabled = inp.trim() != "false";
}
if let Some(v) = gsettings_get("org.gnome.desktop.interface", "menus-have-icons") {
style.visual_hints.show_menu_images = v.trim() != "false";
}
if let Some(v) = gsettings_get("org.gnome.desktop.interface", "buttons-have-icons") {
style.visual_hints.show_button_images = v.trim() != "false";
}
if let Some(v) = gsettings_get("org.gnome.desktop.interface", "toolbar-style") {
style.visual_hints.toolbar_style = match v.trim() {
"text" => super::ToolbarStyle::TextOnly,
"both" => super::ToolbarStyle::TextBelowIcon,
"both-horiz" => super::ToolbarStyle::TextBesideIcon,
_ => super::ToolbarStyle::IconsOnly,
};
}
if let Some(blink) = gsettings_get("org.gnome.desktop.interface", "cursor-blink") {
if blink.trim() == "false" {
style.input.caret_blink_rate_ms = 0;
}
}
if let Some(blink_time) = gsettings_get("org.gnome.desktop.interface", "cursor-blink-time") {
if let Ok(ms) = blink_time.trim().parse::<u32>() {
style.input.caret_blink_rate_ms = ms;
}
}
}
pub(super) fn discover() -> super::SystemStyle {
let mut style = defaults::gnome_adwaita_light();
#[cfg(feature = "io")]
{
if let Some((color_scheme, accent_rgb)) = query_xdg_portal() {
match color_scheme {
1 => { style = defaults::gnome_adwaita_dark(); } 2 => { style = defaults::gnome_adwaita_light(); } _ => {} }
if let Some((r, g, b)) = accent_rgb {
style.colors.accent = OptionColorU::Some(crate::props::basic::color::ColorU::new_rgb(
(r.clamp(0.0, 1.0) * 255.0) as u8,
(g.clamp(0.0, 1.0) * 255.0) as u8,
(b.clamp(0.0, 1.0) * 255.0) as u8,
));
}
}
}
#[cfg(feature = "io")]
{
discover_linux_extras(&mut style);
}
#[cfg(feature = "io")]
{
style.platform = super::Platform::Linux(super::detect_linux_desktop_env());
style.language = super::detect_system_language();
}
#[cfg(not(feature = "io"))]
{
style.platform = super::Platform::Linux(
super::DesktopEnvironment::Other(AzString::from_const_str("unknown")),
);
}
style
}