#![allow(clippy::multiple_crate_versions)]
mod backends;
mod cli;
mod daemon;
mod daemon_lock;
mod theme;
mod traits;
mod wallpaper;
use crate::backends::{hyprland::HyprlandBackend, mango::MangoBackend, sway::SwayBackend};
use crate::daemon::run_loop;
use crate::daemon_lock::session_key;
use crate::traits::Backend;
use crate::wallpaper::WallpaperCache;
use anyhow::Context;
use clap::Parser;
use cli::{BackendType, Cli, Command, Config, RendererType};
use randpaper_ipc::find_socket;
use randpaper_lib::layer;
use std::sync::{Arc, atomic::AtomicBool};
use tokio::net::UnixStream;
use tokio::process::Command as TokioCommand;
pub async fn send_ipc_command(cmd: &str) -> anyhow::Result<()> {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
let socket_path = find_socket(&session_key())?;
let mut stream = UnixStream::connect(&socket_path)
.await
.with_context(|| format!("Failed to connect to socket at {}", socket_path.display()))?;
stream.write_all(cmd.as_bytes()).await?;
stream.shutdown().await?;
if cmd == "status" {
let mut reply = String::new();
stream.read_to_string(&mut reply).await?;
if !reply.trim().is_empty() {
println!("{reply}");
}
}
Ok(())
}
async fn oneshot_mode(config: &Config) -> anyhow::Result<()> {
log::info!("One-shot mode: picking wallpaper once and exiting");
let cache = WallpaperCache::new(&config.wallpaper_dir)?;
let monitors = match config.backend {
BackendType::Hyprland => HyprlandBackend.get_active_monitors().await?,
BackendType::Mango => MangoBackend.get_active_monitors().await?,
BackendType::Sway => {
let backend = SwayBackend {
outputs_override: config.outputs.clone(),
};
backend.get_active_monitors().await?
}
};
let img = cache.pick_random();
theme::update_theme_file(img)?;
match config.renderer {
RendererType::Swaybg => {
let mut args = Vec::new();
for monitor in &monitors {
let img = cache.pick_random();
let abs_path = img.canonicalize()?;
args.extend_from_slice(&[
"-o".to_string(),
monitor.clone(),
"-m".to_string(),
"fill".to_string(),
"-i".to_string(),
abs_path.to_string_lossy().to_string(),
]);
}
let _ = TokioCommand::new("pkill")
.args(["-x", "swaybg"])
.status()
.await
.context("oneshot: pkill -x swaybg")?;
TokioCommand::new("swaybg")
.args(&args)
.spawn()
.context("oneshot: spawn swaybg")?;
}
RendererType::Awww => {
let awww_bin = daemon::detect_awww_binary().await; daemon::ensure_awww_daemon(&awww_bin).await?;
let step = config.transition_step.to_string();
let fps = config.transition_fps.to_string();
for monitor in &monitors {
let img = cache.pick_random();
TokioCommand::new(&awww_bin)
.arg("img")
.arg(img)
.arg("-o")
.arg(monitor)
.arg("--transition-type")
.arg(&config.transition_type)
.arg("--transition-step")
.arg(&step)
.arg("--transition-fps")
.arg(&fps)
.status()
.await
.with_context(|| format!("oneshot: awww img -o {monitor}"))?;
}
}
RendererType::Native => {
let mut handles = Vec::new();
for monitor in monitors {
let img_path = cache.pick_random().to_path_buf();
handles.push(std::thread::spawn(move || {
crate::layer::render_wallpaper(
&img_path,
Some(&monitor),
&Arc::new(AtomicBool::new(false)),
)
.expect("native renderer failed");
}));
}
for h in handles {
h.join().ok();
}
}
}
log::info!("Wallpaper and theme updated. Exiting.");
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli_args = Cli::parse();
env_logger::init();
if let Some(cmd) = &cli_args.command {
return match cmd {
Command::Next => send_ipc_command("next").await,
Command::Pause => send_ipc_command("pause").await,
Command::Resume => send_ipc_command("resume").await,
Command::Status => send_ipc_command("status").await,
};
}
let config = Config::from_cli(cli_args)?;
crate::theme::ensure_theme_exists()?;
if !config.daemon {
return oneshot_mode(&config).await;
}
if config.time.is_none() {
anyhow::bail!("--daemon requires time to be set (config.toml or --time)");
}
let Some(_guard) = crate::daemon_lock::single_instance_guard()? else {
return Ok(());
};
match config.backend {
BackendType::Hyprland => {
log::info!("Using Hyprland backend");
run_loop(config, HyprlandBackend).await?;
}
BackendType::Mango => {
log::info!("Using MangoWM backend");
run_loop(config, MangoBackend).await?;
}
BackendType::Sway => {
log::info!("Using Sway backend");
let backend = SwayBackend {
outputs_override: config.outputs.clone(),
};
run_loop(config, backend).await?;
}
}
Ok(())
}