diff --git a/Cargo.lock b/Cargo.lock index 93eb26c..2f2f648 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,7 +50,7 @@ dependencies = [ "bitflags", "clap_derive", "clap_lex", - "indexmap 1.9.1", + "indexmap", "once_cell", "strsim", "termcolor", @@ -102,12 +102,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - [[package]] name = "getrandom" version = "0.2.7" @@ -125,12 +119,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" - [[package]] name = "heck" version = "0.4.0" @@ -162,17 +150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" -dependencies = [ - "equivalent", - "hashbrown 0.14.3", + "hashbrown", ] [[package]] @@ -350,15 +328,6 @@ dependencies = [ "syn 2.0.57", ] -[[package]] -name = "serde_spanned" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" -dependencies = [ - "serde", -] - [[package]] name = "strsim" version = "0.10.0" @@ -402,40 +371,6 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" -[[package]] -name = "toml" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" -dependencies = [ - "indexmap 2.2.6", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - [[package]] name = "unicode-ident" version = "1.0.2" @@ -458,7 +393,6 @@ dependencies = [ "pretty_env_logger", "rand", "serde", - "toml", ] [[package]] @@ -497,12 +431,3 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "winnow" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" -dependencies = [ - "memchr", -] diff --git a/Cargo.toml b/Cargo.toml index 40ee708..7691c8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,4 +20,3 @@ rand = "0.8.5" clap = { version = "3.2.11", features = ["derive", "cargo"] } ctrlc = { version = "3.2.2", features = ["termination"] } serde = { version = "1.0.197", features = ["derive"] } -toml = "0.8.12" diff --git a/src/daemon.rs b/src/daemon.rs index c201e04..c1b192e 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -33,8 +33,8 @@ pub struct DaemonArgs { #[clap(short, long, value_parser, value_name = "DIRECTORY")] wallpaper_directory: Option, /// Time in seconds between wallpaper changes - #[clap(short, long, parse(try_from_str = parse_duration))] - interval: Option, + #[clap(short, long, parse(try_from_str = parse_duration), default_value = "60")] + interval: Duration, /// File descriptor to write to to signal readiness #[clap(long)] fd: Option, @@ -47,19 +47,19 @@ pub struct DaemonArgs { /// calls 'sh -c ${wallpaper_change_command}' /// %wallpaper% gets replaced with the path to the wallpaper #[clap(long)] - wallpaper_change_command: Option, + pub wallpaper_change_command: Option, /// Command to call after changing the wallpaper /// calls 'sh -c ${wallpaper_post_change_command}' /// %wallpaper% gets replaced with the path to the wallpaper #[clap(long)] - wallpaper_post_change_command: Option, + pub wallpaper_post_change_command: Option, /// How many cycles of delay to keep #[clap(long)] - wallpaper_post_change_offset: Option, + pub wallpaper_post_change_offset: Option, } #[derive(Serialize, Deserialize)] -struct Config { +pub struct Config { /// Image to show by default default_image: PathBuf, /// Directory to search for images @@ -72,13 +72,13 @@ struct Config { /// Command to call to change the wallpaper /// calls 'sh -c ${wallpaper_change_command}' /// %wallpaper% gets replaced with the path to the wallpaper - wallpaper_change_command: String, + pub wallpaper_change_command: String, /// Command to call after changing the wallpaper /// calls 'sh -c ${wallpaper_post_change_command}' /// %wallpaper% gets replaced with the path to the wallpaper - wallpaper_post_change_command: Option, + pub wallpaper_post_change_command: Option, /// How many cycles of delay to keep - wallpaper_post_change_offset: Option, + pub wallpaper_post_change_offset: Option, } impl Default for Config { @@ -96,75 +96,115 @@ impl Default for Config { } } +impl FromStr for Config { + type Err = (); + + fn from_str(s: &str) -> Result { + let mut config = Config::default(); + for line in s.lines() { + let split: Vec<&str> = line.split("=").collect(); + let left = split[0].trim(); + let right = split[1] + .trim() + .trim_start_matches('"') + .trim_end_matches('"') + .trim(); + match left { + "default_image" => config.default_image = right.into(), + "wallpaper_directory" => config.wallpaper_directory = right.into(), + "interval" => { + if let Ok(seconds) = u64::from_str(right) { + config.interval = seconds; + } else { + error!("Couldn't parse interval in config file"); + } + } + "history_length" => { + if let Ok(length) = usize::from_str(right) { + config.history_length = length; + } else { + error!("Couldn't parse history_length in config file"); + } + } + "mode" => { + config.mode = match right { + "Random" => NextImage::Random, + "Linear" => NextImage::Linear, + "Static" => NextImage::Static, + _ => { + error!("Unknown wallpaper mode: {right}"); + config.mode + } + } + } + "wallpaper_change_command" => config.wallpaper_change_command = right.into(), + "wallpaper_post_change_command" => { + config.wallpaper_post_change_command = Some(right.into()) + } + "wallpaper_post_change_offset" => { + if let Ok(offset) = usize::from_str(right) { + config.wallpaper_post_change_offset = Some(offset); + } else { + error!("Couldn't parse wallpaper_post_change_offset"); + } + } + _ => { + error!("{left} isn't a valid key"); + } + } + } + Ok(config) + } +} + fn parse_duration(arg: &str) -> Result { let seconds = arg.parse()?; Ok(std::time::Duration::from_secs(seconds)) } +#[derive(Debug)] +struct UnixSocketWithDrop { + path: PathBuf, + socket: UnixListener, +} + +impl Drop for UnixSocketWithDrop { + fn drop(&mut self) { + std::fs::remove_file(&self.path).unwrap(); + info!("Removing socket file"); + } +} + pub fn start_daemon(args: DaemonArgs) { - debug!("Command run was:\n{:?}", &args); - - let config_file = args.config.unwrap_or_else(|| { - let mut dotconfig = std::env::var("XDG_CONFIG_HOME") - .map(PathBuf::from) - .unwrap_or_else(|_arg| { - let mut home = PathBuf::from(std::env::var("HOME").unwrap()); - home.push(".config"); - home - }); - - dotconfig.push("wallpaperd"); - dotconfig.push("wallpaperd.toml"); - dotconfig - }); + let config_file = get_config_file(&args); let config = if config_file.is_file() { - // Parse config with serde let config = fs::read_to_string(config_file).expect("Couldn't read config file"); - let config: Config = toml::from_str(&config).expect("Invalid config"); + // Can't fail + let config = Config::from_str(&config).unwrap(); config } else { Config::default() }; - let socket = args.socket.unwrap_or_else(|| { - if let Ok(path) = std::env::var("XDG_RUNTIME_DIR") { - let mut pathbuf = PathBuf::new(); - pathbuf.push(path); - pathbuf.push("wallpaperd"); - pathbuf - } else { - PathBuf::from_str("/tmp/wallpaperd").unwrap() - } - }); + let wallpaper_cmds = WallpaperCommands::new(&args, &config); + + let socket = get_socket(&args); + info!("Binding socket {:?}", socket); - let s = socket.clone(); + let s = socket.path.clone(); ctrlc::set_handler(move || { if fs::remove_file(&s).is_err() { error!("Couldn't delete socket file"); } - exit(1); + exit(0); }) .expect("Error setting signal hooks"); - let time = args - .interval - .unwrap_or(Duration::from_secs(config.interval)); - - let wallpaper_cmds = WallpaperCommands { - wallpaper_cmd: args - .wallpaper_change_command - .unwrap_or(config.wallpaper_change_command), - wallpaper_post_cmd: args - .wallpaper_post_change_command - .or(config.wallpaper_post_change_command), - wallpaper_post_offset: args - .wallpaper_post_change_offset - .or(config.wallpaper_post_change_offset), - }; + let incoming = socket.socket.incoming(); let data = Arc::new(Mutex::new(State::new( - time, + args.interval, args.wallpaper_directory .unwrap_or(config.wallpaper_directory), args.default.unwrap_or(config.default_image), @@ -173,10 +213,6 @@ pub fn start_daemon(args: DaemonArgs) { args.history_length.unwrap_or(config.history_length), ))); - info!("Binding socket {:?}", socket); - let listener = UnixListener::bind(&socket).unwrap(); - let incoming = listener.incoming(); - if args.fd.is_some() { let mut file = unsafe { File::from_raw_fd(args.fd.unwrap()) }; writeln!(&mut file).unwrap(); @@ -194,12 +230,45 @@ pub fn start_daemon(args: DaemonArgs) { } } } +} - if fs::remove_file(&socket).is_err() { - error!("Couldn't delete socket file"); - exit(1); - } - exit(0); +fn get_config_file(args: &DaemonArgs) -> PathBuf { + args.config.as_ref().map_or_else( + || { + let mut dotconfig = std::env::var("XDG_CONFIG_HOME") + .map(PathBuf::from) + .unwrap_or_else(|_arg| { + let mut home = PathBuf::from(std::env::var("HOME").unwrap()); + home.push(".config"); + home + }); + + dotconfig.push("wallpaperd"); + dotconfig.push("wallpaperd.toml"); + dotconfig + }, + |val| val.to_owned(), + ) +} + +fn get_socket(args: &DaemonArgs) -> UnixSocketWithDrop { + let path = args.socket.as_ref().map_or_else( + || { + if let Ok(path) = std::env::var("XDG_RUNTIME_DIR") { + let mut pathbuf = PathBuf::new(); + pathbuf.push(path); + pathbuf.push("wallpaperd"); + pathbuf + } else { + PathBuf::from_str("/tmp/wallpaperd").unwrap() + } + }, + |val| val.to_owned(), + ); + + let socket = UnixListener::bind(&path).unwrap(); + + UnixSocketWithDrop { path, socket } } fn read_from_stream(mut stream: &UnixStream) -> String { diff --git a/src/state.rs b/src/state.rs index 76f1ba0..f4476a8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -5,6 +5,8 @@ use rand::Rng; use serde::{Deserialize, Serialize}; use std::{collections::VecDeque, fs, path::PathBuf, process::Command, time::Duration}; +use crate::daemon::{Config, DaemonArgs}; + #[derive(Debug)] struct History { previous: VecDeque, // Never empty @@ -58,6 +60,29 @@ pub struct WallpaperCommands { pub wallpaper_post_offset: Option, } +impl WallpaperCommands { + pub fn new(args: &DaemonArgs, config: &Config) -> Self { + let wallpaper_cmd = args + .wallpaper_change_command + .as_ref() + .unwrap_or(&config.wallpaper_change_command); + let wallpaper_post_cmd = args + .wallpaper_post_change_command + .as_ref() + .or(config.wallpaper_post_change_command.as_ref()); + let wallpaper_post_offset = args + .wallpaper_post_change_offset + .as_ref() + .or(config.wallpaper_post_change_offset.as_ref()); + + WallpaperCommands { + wallpaper_cmd: wallpaper_cmd.to_owned(), + wallpaper_post_cmd: wallpaper_post_cmd.cloned(), + wallpaper_post_offset: wallpaper_post_offset.cloned(), + } + } +} + /// Global object to store the current state #[derive(Debug)] pub struct State {