rizzoo 0.6.7

a cross platform color generation tool implementing Material 3 Expressive
use crate::config::WallpaperConfiguration;
use crate::error::LRatio;
use std::path::Path;

#[cfg(target_os = "linux")]
mod feh;
#[cfg(target_os = "linux")]
mod swaybg;

#[cfg(target_os = "macos")]
mod mac;

#[cfg(target_os = "windows")]
mod windows;

pub fn set(path: &Path, config: Option<&WallpaperConfiguration>) -> Result<String, LRatio> {
    if let Some(cfg) = config {
        if let Some(cmd) = &cfg.command {
            run_hook(cmd, path)?;
            return Ok(format!("[{}]", cmd));
        }
    }

    // url-images check
    if !path.exists() {
        return Err(LRatio::WallpaperSet(format!(
            "file missing {}",
            path.display()
        )));
    }

    // Platform-specific auto-detection
    auto_detect_set(path)
}

pub fn is_enabled(config: Option<&WallpaperConfiguration>) -> bool {
    config.and_then(|c| c.set).unwrap_or(false)
}

fn auto_detect_set(path: &Path) -> Result<String, LRatio> {
    #[cfg(target_os = "macos")]
    return mac::set(path).map(|_| "macos".to_string());

    #[cfg(target_os = "windows")]
    return windows::set(path).map(|_| "windows".to_string());

    #[cfg(target_os = "linux")]
    return linux_set(path);

    #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
    Err(LRatio::NoCompositorDetected)
}

#[cfg(target_os = "linux")]
fn linux_set(path: &Path) -> Result<String, LRatio> {
    use std::env;
    use std::os::unix::fs::FileTypeExt;

    fn is_wayland_active() -> bool {
        if let Ok(display) = env::var("WAYLAND_DISPLAY") {
            if let Ok(runtime_dir) = env::var("XDG_RUNTIME_DIR") {
                let socket_path = Path::new(&runtime_dir).join(display);
                return std::fs::metadata(socket_path)
                    .map(|meta| meta.file_type().is_socket())
                    .unwrap_or(false);
            }
        }
        false
    }

    fn is_x11_active() -> bool {
        if Path::new("/tmp/.X11-unix").exists() {
            return true;
        }
        env::var("DISPLAY").is_ok()
    }

    fn is_bin_in_path(bin: &str) -> bool {
        std::process::Command::new("which")
            .arg(bin)
            .output()
            .map(|out| out.status.success())
            .unwrap_or(false)
    }

    struct Setter {
        name: &'static str,
        is_available: fn() -> bool,
        set: fn(&Path) -> Result<(), LRatio>,
    }

    let is_wayland = is_wayland_active();
    let is_x11 = is_x11_active();

    let wallpaper_setters: Vec<Setter> = if is_wayland {
        vec![Setter {
            name: "swaybg",
            is_available: || is_bin_in_path("swaybg"),
            set: swaybg::set,
        }]
    } else if is_x11 {
        vec![Setter {
            name: "feh",
            is_available: || is_bin_in_path("feh"),
            set: feh::set,
        }]
    } else {
        return Err(LRatio::NoCompositorDetected);
    };

    for setter in wallpaper_setters.iter().filter(|b| (b.is_available)()) {
        match (setter.set)(path) {
            Ok(()) => return Ok(setter.name.to_string()),
            Err(e) => log::warn!(
                "{}",
                LRatio::WallpaperSet(format!("{} failed: {}", setter.name, e))
            ),
        }
    }

    Err(LRatio::NoCompositorDetected)
}

fn run_hook(hook: &str, path: &Path) -> Result<(), LRatio> {
    let resolved = hook.replace("{{ image }}", &path.display().to_string());
    let output = std::process::Command::new("sh")
        .arg("-c")
        .arg(&resolved)
        .output()
        .map_err(|e| LRatio::WallpaperHook(format!("failed to execute hook: {e}")))?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        if !stderr.trim().is_empty() {
            log::warn!(
                "{}",
                LRatio::WallpaperHook(format!("hook stderr: {stderr}"))
            );
        }
    }
    Ok(())
}