use anyhow::Result;
#[cfg(target_os = "linux")]
use std::env;
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub enum Platform {
Linux(LinuxDisplayServer),
MacOS,
Windows,
}
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub enum LinuxDisplayServer {
X11,
Wayland(WaylandCompositor),
}
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub enum WaylandCompositor {
Sway,
Hyprland,
Gnome,
Kde,
Generic,
}
#[derive(Debug)]
pub enum PlatformStatus {
Ready,
MissingDependency(()),
UnsupportedPlatform,
}
pub fn detect_platform() -> Result<Platform> {
#[cfg(target_os = "linux")]
return detect_linux_platform();
#[cfg(target_os = "macos")]
return Ok(Platform::MacOS);
#[cfg(target_os = "windows")]
return Ok(Platform::Windows);
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
return Err(anyhow!("Unsupported platform"));
}
#[cfg(target_os = "linux")]
fn detect_linux_platform() -> Result<Platform> {
let wayland_display = env::var("WAYLAND_DISPLAY").ok();
let xdg_session_type = env::var("XDG_SESSION_TYPE").ok();
let xdg_current_desktop = env::var("XDG_CURRENT_DESKTOP").ok();
let x11_display = env::var("DISPLAY").ok();
tracing::debug!(
"Platform detection: WAYLAND_DISPLAY={:?}, XDG_SESSION_TYPE={:?}, XDG_CURRENT_DESKTOP={:?}, DISPLAY={:?}",
wayland_display,
xdg_session_type,
xdg_current_desktop,
x11_display
);
if wayland_display.is_some() || xdg_session_type.as_deref() == Some("wayland") {
let compositor = detect_wayland_compositor();
tracing::debug!("Detected Wayland compositor: {:?}", compositor);
return Ok(Platform::Linux(LinuxDisplayServer::Wayland(compositor)));
}
if x11_display.is_some() || xdg_session_type.as_deref() == Some("x11") {
tracing::debug!("Detected X11 display server");
return Ok(Platform::Linux(LinuxDisplayServer::X11));
}
tracing::warn!("Could not detect display server, assuming X11");
Ok(Platform::Linux(LinuxDisplayServer::X11))
}
#[cfg(target_os = "linux")]
fn detect_wayland_compositor() -> WaylandCompositor {
if let Ok(desktop) = env::var("XDG_CURRENT_DESKTOP") {
let desktop = desktop.to_lowercase();
if desktop.contains("sway") {
return WaylandCompositor::Sway;
}
if desktop.contains("hyprland") {
return WaylandCompositor::Hyprland;
}
if desktop.contains("gnome") {
return WaylandCompositor::Gnome;
}
if desktop.contains("kde") {
return WaylandCompositor::Kde;
}
}
if env::var("SWAYSOCK").is_ok() {
return WaylandCompositor::Sway;
}
if env::var("HYPRLAND_INSTANCE_SIGNATURE").is_ok() {
return WaylandCompositor::Hyprland;
}
WaylandCompositor::Generic
}
pub fn check_platform_dependencies() -> PlatformStatus {
match detect_platform() {
Ok(Platform::MacOS) => {
if which::which("wallpaper").is_ok() || which::which("swiftc").is_ok() || which::which("osascript").is_ok() {
PlatformStatus::Ready
} else {
PlatformStatus::MissingDependency(())
}
}
Ok(Platform::Linux(_)) => {
let available_backends = detect_available_linux_backends();
if available_backends.is_empty() {
PlatformStatus::MissingDependency(())
} else {
PlatformStatus::Ready
}
}
Ok(Platform::Windows) => {
PlatformStatus::Ready
}
Err(_) => PlatformStatus::UnsupportedPlatform,
}
}
#[cfg(target_os = "linux")]
fn detect_available_linux_backends() -> Vec<String> {
let backends = vec!["swww", "awww", "swaybg", "hyprpaper", "feh", "nitrogen", "xwallpaper"];
backends
.into_iter()
.filter(|backend| which::which(backend).is_ok())
.map(String::from)
.collect()
}
#[cfg(not(target_os = "linux"))]
fn detect_available_linux_backends() -> Vec<String> {
vec![]
}
impl std::fmt::Display for Platform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Platform::Linux(LinuxDisplayServer::X11) => write!(f, "Linux (X11)"),
Platform::Linux(LinuxDisplayServer::Wayland(compositor)) => {
write!(f, "Linux (Wayland - {})", compositor)
}
Platform::MacOS => write!(f, "macOS"),
Platform::Windows => write!(f, "Windows"),
}
}
}
pub fn detect_dark_mode() -> Option<bool> {
#[cfg(target_os = "linux")]
return detect_dark_mode_linux();
#[cfg(target_os = "macos")]
return detect_dark_mode_macos();
#[cfg(target_os = "windows")]
return detect_dark_mode_windows();
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
return None;
}
#[cfg(target_os = "linux")]
fn detect_dark_mode_linux() -> Option<bool> {
if let Ok(output) = std::process::Command::new("gsettings")
.args(["get", "org.gnome.desktop.interface", "color-scheme"])
.output()
&& output.status.success()
{
let stdout = String::from_utf8_lossy(&output.stdout);
if stdout.contains("prefer-dark") {
tracing::debug!("System dark mode detected via gsettings (GNOME)");
return Some(true);
} else if stdout.contains("default") || stdout.contains("prefer-light") {
tracing::debug!("System light mode detected via gsettings (GNOME)");
return Some(false);
}
}
if let Ok(output) = std::process::Command::new("kreadconfig5")
.args(["--group", "General", "--key", "ColorScheme"])
.output()
&& output.status.success()
{
let stdout = String::from_utf8_lossy(&output.stdout);
let is_dark = stdout.to_lowercase().contains("dark");
tracing::debug!("System {} mode detected via kreadconfig5 (KDE)", if is_dark { "dark" } else { "light" });
return Some(is_dark);
}
tracing::debug!("Could not detect system dark mode on Linux");
None
}
#[cfg(target_os = "macos")]
fn detect_dark_mode_macos() -> Option<bool> {
if let Ok(output) = std::process::Command::new("defaults")
.args(["read", "-g", "AppleInterfaceStyle"])
.output()
{
let is_dark = output.status.success();
tracing::debug!("System {} mode detected via defaults (macOS)", if is_dark { "dark" } else { "light" });
return Some(is_dark);
}
tracing::debug!("Could not detect system dark mode on macOS");
None
}
#[cfg(target_os = "windows")]
fn detect_dark_mode_windows() -> Option<bool> {
if let Ok(output) = std::process::Command::new("reg")
.args([
"query",
r"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize",
"/v",
"AppsUseLightTheme",
])
.output()
{
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
let is_dark = stdout.contains("0x0");
tracing::debug!("System {} mode detected via registry (Windows)", if is_dark { "dark" } else { "light" });
return Some(is_dark);
}
}
tracing::debug!("Could not detect system dark mode on Windows");
None
}
impl std::fmt::Display for WaylandCompositor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WaylandCompositor::Sway => write!(f, "Sway"),
WaylandCompositor::Hyprland => write!(f, "Hyprland"),
WaylandCompositor::Gnome => write!(f, "GNOME"),
WaylandCompositor::Kde => write!(f, "KDE"),
WaylandCompositor::Generic => write!(f, "Generic"),
}
}
}