Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/snapshot.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::errors::{Result, RixiError};
use crate::paths;
use crate::registry;
use crate::wallpaper;

/// Create a snapshot of all files that are about to be overwritten.
/// Returns the snapshot timestamp string used as the directory name.
Expand Down Expand Up @@ -29,6 +30,9 @@ pub fn create_snapshot(components: &[String]) -> Result<String> {
}
}

// Snapshot currently managed wallpaper so rollback can re-apply it.
wallpaper::snapshot_current(&snapshot_dir)?;

Ok(timestamp)
}

Expand Down Expand Up @@ -69,5 +73,9 @@ pub fn restore_snapshot(snapshot_id: &str) -> Result<Vec<String>> {
}
}

if wallpaper::restore_from_snapshot(&snapshot_dir)? {
restored.push("wallpaper".to_string());
}

Ok(restored)
}
139 changes: 112 additions & 27 deletions src/wallpaper.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
use colored::Colorize;
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::process::Command;

use crate::errors::Result;
use crate::manifest::WallpaperConfig;

#[derive(Debug, Clone, Serialize, Deserialize)]
struct WallpaperState {
file_name: String,
setter: String,
}

fn wallpaper_dir() -> std::path::PathBuf {
crate::paths::data_dir().join("wallpaper")
}

fn wallpaper_state_file() -> std::path::PathBuf {
wallpaper_dir().join("state.toml")
}

/// Apply wallpaper using the configured setter.
/// `rice_path` is the rice source directory (wallpaper file paths are relative to it).
pub fn apply(config: &WallpaperConfig, rice_path: &Path) -> Result<()> {
Expand All @@ -20,7 +35,7 @@ pub fn apply(config: &WallpaperConfig, rice_path: &Path) -> Result<()> {
}

// Copy wallpaper to rixi data dir for persistence
let dest = crate::paths::data_dir().join("wallpaper");
let dest = wallpaper_dir();
crate::paths::ensure_dir(&dest)?;
let filename = wallpaper_src
.file_name()
Expand All @@ -30,35 +45,11 @@ pub fn apply(config: &WallpaperConfig, rice_path: &Path) -> Result<()> {

let wall_path = dest_file.to_string_lossy().to_string();

let result = match config.setter.as_str() {
"feh" => run_setter("feh", &["--bg-scale", &wall_path]),
"nitrogen" => run_setter("nitrogen", &["--set-zoom-fill", "--save", &wall_path]),
"hyprpaper" => {
// hyprpaper uses its config file, so we just signal a reload
run_setter("hyprctl", &["hyprpaper", "reload"])
}
"swww" => run_setter("swww", &["img", &wall_path]),
"swaybg" => {
// Kill existing swaybg, start new one
let _ = Command::new("pkill")
.arg("swaybg")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status();
run_setter("swaybg", &["-i", &wall_path])
}
other => {
println!(
" {} Unknown wallpaper setter: {}",
"✗".yellow(),
other
);
return Ok(());
}
};
let result = run_configured_setter(&config.setter, &wall_path);

match result {
Ok(true) => {
save_wallpaper_state(filename.to_string_lossy().as_ref(), &config.setter)?;
println!(
" {} Wallpaper set via {}",
"✓".green().bold(),
Expand All @@ -84,6 +75,100 @@ pub fn apply(config: &WallpaperConfig, rice_path: &Path) -> Result<()> {
Ok(())
}

/// Snapshot the current managed wallpaper state and file, if available.
pub fn snapshot_current(snapshot_dir: &Path) -> Result<()> {
let state_path = wallpaper_state_file();
if !state_path.exists() {
return Ok(());
}

let content = std::fs::read_to_string(&state_path)?;
let state: WallpaperState = match toml::from_str(&content) {
Ok(state) => state,
Err(_) => return Ok(()),
};

let wallpaper_file = wallpaper_dir().join(&state.file_name);
if !wallpaper_file.exists() {
return Ok(());
}

let snapshot_wallpaper_dir = snapshot_dir.join("wallpaper");
crate::paths::ensure_dir(&snapshot_wallpaper_dir)?;
std::fs::copy(&wallpaper_file, snapshot_wallpaper_dir.join(&state.file_name))?;
std::fs::copy(&state_path, snapshot_wallpaper_dir.join("state.toml"))?;

Ok(())
}

/// Restore wallpaper from a snapshot and re-apply it.
/// Returns true if wallpaper was restored and re-applied.
pub fn restore_from_snapshot(snapshot_dir: &Path) -> Result<bool> {
let snapshot_wallpaper_dir = snapshot_dir.join("wallpaper");
let snapshot_state = snapshot_wallpaper_dir.join("state.toml");
if !snapshot_state.exists() {
return Ok(false);
}

let content = std::fs::read_to_string(&snapshot_state)?;
let state: WallpaperState = match toml::from_str(&content) {
Ok(state) => state,
Err(_) => return Ok(false),
};

let snap_wallpaper_file = snapshot_wallpaper_dir.join(&state.file_name);
if !snap_wallpaper_file.exists() {
return Ok(false);
}

let live_wallpaper_dir = wallpaper_dir();
crate::paths::ensure_dir(&live_wallpaper_dir)?;
let live_wallpaper_file = live_wallpaper_dir.join(&state.file_name);
std::fs::copy(&snap_wallpaper_file, &live_wallpaper_file)?;

let live_state = toml::to_string_pretty(&state)
.map_err(|e| crate::errors::RixiError::Other(format!("Failed to serialize wallpaper state: {}", e)))?;
std::fs::write(wallpaper_state_file(), live_state)?;

let wall_path = live_wallpaper_file.to_string_lossy().to_string();
let _ = run_configured_setter(&state.setter, &wall_path);

Ok(true)
}

fn save_wallpaper_state(file_name: &str, setter: &str) -> Result<()> {
let state = WallpaperState {
file_name: file_name.to_string(),
setter: setter.to_string(),
};
let state_toml = toml::to_string_pretty(&state)
.map_err(|e| crate::errors::RixiError::Other(format!("Failed to serialize wallpaper state: {}", e)))?;
std::fs::write(wallpaper_state_file(), state_toml)?;
Ok(())
}

fn run_configured_setter(setter: &str, wall_path: &str) -> std::io::Result<bool> {
match setter {
"feh" => run_setter("feh", &["--bg-scale", wall_path]),
"nitrogen" => run_setter("nitrogen", &["--set-zoom-fill", "--save", wall_path]),
"hyprpaper" => {
// hyprpaper uses its config file, so we just signal a reload
run_setter("hyprctl", &["hyprpaper", "reload"])
}
"swww" => run_setter("swww", &["img", wall_path]),
"swaybg" => {
// Kill existing swaybg, start new one
let _ = Command::new("pkill")
.arg("swaybg")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status();
run_setter("swaybg", &["-i", wall_path])
}
_ => Ok(false),
}
}

/// Run a wallpaper setter command. Returns Ok(true) on success.
fn run_setter(cmd: &str, args: &[&str]) -> std::io::Result<bool> {
Command::new(cmd)
Expand Down