use crate::{
Config, FileInfo, WallSwitchError, WallSwitchResult, dependencies::is_installed, exec_cmd,
};
use std::{
env, fs,
io::{self, Write},
path::PathBuf,
process::{Command, Stdio},
thread::sleep,
time::Duration,
};
pub fn set_awww_wallpaper(images: &[FileInfo], config: &Config) -> WallSwitchResult<()> {
let monitors = get_wayland_monitors(config)?;
ensure_daemon_running(config)?;
for (image, monitor) in images.iter().zip(monitors.iter()) {
let effect = get_transition_effect(config);
let mut cmd = Command::new("awww");
cmd.args(["img", "-o", monitor])
.arg(&image.path)
.args(["--transition-type", &effect])
.args([
"--transition-duration",
&config.transition_duration.to_string(),
])
.args(["--transition-fps", &config.transition_fps.to_string()])
.args(["--transition-angle", &config.transition_angle.to_string()])
.args(["--transition-pos", &config.transition_pos]);
if config.dry_run {
println!("[DRY-RUN] Would execute: {:?}", cmd);
} else {
let msg = format!("Apply awww wallpaper on {}", monitor);
exec_cmd(&mut cmd, config.verbose, &msg)?;
}
}
Ok(())
}
fn get_transition_effect(config: &Config) -> String {
if config.transition_type.to_lowercase() == "random" {
let effects = ["wipe", "fade", "center", "outer", "wave", "left", "right"];
let idx = (crate::rand() as usize) % effects.len();
effects[idx].to_string()
} else {
config.transition_type.clone()
}
}
fn ensure_daemon_running(config: &Config) -> WallSwitchResult<()> {
if is_daemon_alive() {
return Ok(());
}
if config.verbose {
println!("awww-daemon is down. Performing clean start...");
}
let _ = Command::new("killall").arg("awww-daemon").output();
clean_stale_sockets();
Command::new("awww-daemon")
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.map_err(|e| WallSwitchError::AwwwDaemonError(e.to_string()))?;
let mut elapsed = 0.0;
let step = 0.2;
let max_wait = 5.0;
while elapsed < max_wait {
if is_daemon_alive() {
if config.verbose {
println!("\nawww-daemon successfully initialized.");
}
return Ok(());
}
if config.verbose {
print!(
"\rWait to initialize awww-daemon. Time: {:0.1}/{:0.1}",
elapsed, max_wait
);
io::stdout().flush().ok();
}
sleep(Duration::from_secs_f32(step));
elapsed += step;
}
if config.verbose {
println!();
}
Err(WallSwitchError::AwwwDaemonError(
"Daemon failed to initialize within the timeout period.".into(),
))
}
fn is_daemon_alive() -> bool {
Command::new("awww")
.arg("query")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false)
}
fn clean_stale_sockets() {
let runtime_dir = env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string());
if let Ok(entries) = fs::read_dir(&runtime_dir) {
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().to_string();
if name.contains("awww") && name.ends_with(".sock") {
let _ = fs::remove_file(entry.path());
}
}
}
}
pub fn get_wayland_monitors(config: &Config) -> WallSwitchResult<Vec<String>> {
if is_installed("wlr-randr")
&& let Ok(output) = Command::new("wlr-randr").output()
{
let stdout = String::from_utf8_lossy(&output.stdout);
let monitors: Vec<String> = stdout
.lines()
.filter(|line| !line.starts_with(' ') && !line.is_empty())
.map(|line| line.split_whitespace().next().unwrap_or("").to_string())
.filter(|s| !s.is_empty())
.collect();
if !monitors.is_empty() {
return Ok(monitors);
}
}
let drm_path = PathBuf::from("/sys/class/drm");
let mut monitors = Vec::new();
if drm_path.exists()
&& let Ok(entries) = fs::read_dir(drm_path)
{
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().to_string();
if name.starts_with("card") && name.contains('-') {
let status_path = entry.path().join("status");
if let Ok(status) = fs::read_to_string(status_path)
&& status.trim() == "connected"
&& let Some(idx) = name.find('-')
{
monitors.push(name[idx + 1..].to_string());
}
}
}
}
if !monitors.is_empty() {
return Ok(monitors);
}
if config.verbose {
println!("All Wayland monitor detection methods failed. Using default outputs.");
}
Ok(vec!["DP-1".to_string(), "DP-2".to_string()])
}