use crate::{ time_track::Time, errors::{ConfigFileErrors, Errors} };
use chrono::{Local, Timelike, Utc};
use clokwerk::{Scheduler, TimeUnits};
use std::{env, error::Error, process, process::Command, sync::Arc, thread::sleep, time::Duration};
use walkdir::{IntoIter, WalkDir};
use run_script::ScriptOptions;
use sun_times;
use unicase::UniCase;
#[cfg(windows)]
use std::ffi::OsStr;
#[cfg(windows)]
use std::{io, iter, os::raw::c_void, os::windows::ffi::OsStrExt};
#[cfg(windows)]
use winapi::um::winuser::{
SystemParametersInfoW, SPIF_SENDCHANGE, SPIF_UPDATEINIFILE, SPI_SETDESKWALLPAPER,
};
pub mod errors;
pub mod time_track;
const FULL_DAY: Time = Time {
hours: 24,
mins: 0,
total_mins: 0,
};
const MIDNIGHT: Time = Time {
hours: 0,
mins: 0,
total_mins: 0,
};
pub fn wallpaper_current_time(
dir: &str,
progs: Arc<Option<Vec<String>>>,
times: &[Time],
backend: Arc<Option<String>>,
min_depth: usize,
) -> Result<(), Box<dyn Error>> {
let dir_iter = sorted_dir_iter(dir, min_depth);
let dir_count = sorted_dir_iter(dir, min_depth);
let dir_count: usize = dir_count.count();
let mut commands_vec: Vec<Command> = vec![];
let mut times_iter = times.iter();
let curr_time = Time::new(Local::now().hour() * 60 + Local::now().minute());
let loop_time = times_iter.next();
let mut next_time = times_iter.next().unwrap_or(&FULL_DAY);
let mut filepath_set: String = String::new();
let mut last_image = String::new();
let first_time = times[0].to_owned();
let mut loop_time = error_checking(times, loop_time, dir_count)?;
for file in dir_iter {
if loop_time > *next_time {
loop_time = MIDNIGHT;
}
if loop_time == FULL_DAY && *next_time == FULL_DAY {
return Err(Errors::ConfigFileError(ConfigFileErrors::FileTimeMismatch).into());
}
let filepath_temp = file.map_err(|_| Errors::FilePathError)?;
let filepath_temp = filepath_temp.path();
let last_image_temp = filepath_temp.to_str().unwrap();
last_image = last_image_temp.to_owned();
if curr_time >= loop_time && curr_time < *next_time {
filepath_set.push_str(match filepath_temp.to_str() {
Some(filepath) => Ok(filepath),
None => Err(Errors::FilePathError),
}?);
commands_vec_loader(&filepath_set, Arc::clone(&progs), &mut commands_vec);
}
loop_time = *next_time;
next_time = times_iter.next().unwrap_or(&first_time);
}
if filepath_set.is_empty() {
de_command_spawn(&last_image, backend)?;
commands_vec_loader(&last_image, Arc::clone(&progs), &mut commands_vec);
filepath_set = last_image;
} else {
de_command_spawn(&filepath_set, backend)?;
}
if let Some(progs) = progs.as_deref() {
let mut prog_iter = progs.iter();
for curr_command in commands_vec.iter_mut() {
curr_command
.spawn()
.map_err(|_| Errors::ProgramRunError(String::from(prog_iter.next().unwrap())))?;
println!(
"The image {} has been sent as an argument to the specified program",
filepath_set
);
}
}
Ok(())
}
pub fn wallpaper_listener(
dir: String,
dir_count: usize,
progs: Arc<Option<Vec<String>>>,
times_arg: Option<Vec<Time>>,
backend: Arc<Option<String>>,
min_depth: usize,
) -> Result<(), Box<dyn Error>> {
let (_, step_time, mut loop_time, mut times) = listener_setup(dir.as_str());
let step_time = step_time?;
let mut scheduler = Scheduler::new();
let mut sched_addto = scheduler.every(1.day()).at("0:00");
match times_arg {
None => {
for _ in 1..=dir_count {
times.push(loop_time);
loop_time += step_time;
}
}
Some(t) => times = t,
}
wallpaper_current_time(
&dir,
Arc::clone(&progs),
×,
Arc::clone(&backend),
min_depth,
)?;
for time in × {
let time_fmt = format!("{:02}:{:02}", time.hours, time.mins);
sched_addto = sched_addto.and_every(1.day()).at(time_fmt.as_str());
}
let sched_closure = move || {
let result = wallpaper_current_time(
&dir,
Arc::clone(&progs),
×,
Arc::clone(&backend),
min_depth,
);
match result {
Ok(s) => s,
Err(e) => {
eprintln!("{}", e);
process::exit(1);
}
}
};
sched_addto.run(sched_closure);
loop {
scheduler.run_pending();
sleep(Duration::from_millis(1000));
}
}
fn commands_vec_loader(
filepath_set: &str,
progs: Arc<Option<Vec<String>>>,
commands_vec: &mut Vec<Command>,
) {
let mut wall_sent = false;
if let Some(prog_vec) = progs.as_deref() {
for prog_str in prog_vec.iter() {
let mut prog_split = prog_str.split_whitespace();
let mut curr_command = Command::new(prog_split.next().unwrap());
for word in prog_split {
if word == "!WALL" {
curr_command.arg(filepath_set);
wall_sent = true;
} else {
curr_command.arg(word);
}
}
if wall_sent == false {
curr_command.arg(filepath_set);
wall_sent = true;
}
commands_vec.push(curr_command);
}
}
}
pub fn listener_setup(dir: &str) -> (usize, Result<Time, Errors>, Time, Vec<Time>) {
let dir_count = WalkDir::new(dir).into_iter().count() - 1;
let step_time = if dir_count == 0 {
Err(Errors::NoFilesFoundError(dir.to_string()))
} else {
Ok(Time::new(((24.0 / dir_count as f32) * 60.0) as u32))
};
let loop_time = Time::default();
let times: Vec<Time> = Vec::new();
(dir_count, step_time, loop_time, times)
}
pub fn print_schedule(dir: &str, dir_count: usize, min_depth: usize) -> Result<(), Box<dyn Error>> {
let mut dir_iter = sorted_dir_iter(dir, min_depth);
let step_time = Time::new(((24.0 / dir_count as f32) * 60.0) as u32);
let mut loop_time = Time::default();
let mut i = 0;
if 1440 % dir_count != 0 || dir_count == 0 {
return Err(Errors::CountCompatError(dir_count).into());
}
dir_iter.next();
let mut dir_iter = sorted_dir_iter(dir, min_depth);
while i < 24 * 60 {
println!(
"Image: {:?} Time: {}",
dir_iter.next().unwrap()?.file_name(),
loop_time.twelve_hour()
);
i += step_time.total_mins;
loop_time += step_time;
}
Ok(())
}
pub fn sorted_dir_iter(dir: &str, min_depth: usize) -> IntoIter {
WalkDir::new(dir)
.sort_by(|a, b| {
alphanumeric_sort::compare_str(
a.path().to_str().expect("Sorting directory files failed"),
b.path().to_str().expect("Sorting directory files failed"),
)
})
.min_depth(min_depth)
.into_iter()
}
fn error_checking(
times: &[Time],
loop_time: Option<&Time>,
dir_count: usize,
) -> Result<Time, Box<dyn Error>> {
let times_iter_err = times.iter();
let start_range = times
.iter()
.next()
.ok_or(Errors::ConfigFileError(ConfigFileErrors::Empty))?;
let mut start_range_other = times.iter().next().unwrap();
let mut curr_range = start_range.to_owned();
let mut curr_range_other = start_range.to_owned();
let mut other_inited = false;
let mut checked = vec![];
for time in times_iter_err {
if *time > *start_range && *time > curr_range {
curr_range = *time;
} else if *time > *start_range && *time < curr_range {
return Err(Errors::ConfigFileError(ConfigFileErrors::OutOfOrder).into());
} else if *time < *start_range {
if !other_inited {
curr_range = FULL_DAY;
start_range_other = time;
curr_range_other = *time;
other_inited = true
} else if *time > *start_range_other && *time > curr_range_other {
curr_range_other = *time;
} else {
return Err(Errors::ConfigFileError(ConfigFileErrors::OutOfOrder).into());
}
}
if time.total_mins >= 24 * 60 {
return Err(Errors::ConfigFileError(ConfigFileErrors::OutOfRange).into());
}
if checked.contains(time) {
return Err(Errors::ConfigFileError(ConfigFileErrors::DuplicatesFound).into());
}
checked.push(*time);
}
if times.len() != dir_count {
return Err(Errors::ConfigFileError(ConfigFileErrors::FileTimeMismatch).into());
}
let loop_time = match loop_time {
None => Err(Errors::ConfigFileError(ConfigFileErrors::Empty)),
Some(time) => Ok(time),
}?;
Ok(*loop_time)
}
#[cfg(windows)]
fn de_command_spawn(
filepath_set: &str,
backend: Arc<Option<String>>,
) -> Result<(), Box<dyn Error>> {
if backend.is_some() {
eprintln!("NOTE: You are unable to select a backend on windows");
}
unsafe {
let file = OsStr::new(filepath_set)
.encode_wide()
.chain(iter::once(0))
.collect::<Vec<u16>>();
let successful = SystemParametersInfoW(
SPI_SETDESKWALLPAPER,
0,
file.as_ptr() as *mut c_void,
SPIF_UPDATEINIFILE | SPIF_SENDCHANGE,
) == 1;
if successful {
println!("{} has been set as your wallpaper", filepath_set);
Ok(())
} else {
Err(io::Error::last_os_error().into())
}
}
}
#[cfg(not(windows))]
fn de_command_spawn(
filepath_set: &str,
backend: Arc<Option<String>>,
) -> Result<(), Box<dyn Error>> {
let backend = backend.as_deref();
let gnome = vec![
UniCase::new("pantheon"),
UniCase::new("gnome"),
UniCase::new("gnome-xorg"),
UniCase::new("ubuntu"),
UniCase::new("deepin"),
UniCase::new("pop"),
UniCase::new("ubuntu:gnome"),
];
let mate = UniCase::new("mate");
let kde = vec![
UniCase::new("plasma"),
UniCase::new("neon"),
UniCase::new("kde"),
UniCase::new("/usr/share/xsessions/plasma"),
];
let lxde = UniCase::new("lxde");
let xfce = vec![
UniCase::new("xfce"),
UniCase::new("xubuntu"),
UniCase::new("xfce session"),
];
let curr_de = env::var("XDG_CURRENT_DESKTOP");
let curr_de = match curr_de {
Err(_) => String::from("Other"),
Ok(de) => de,
};
let mut curr_de = UniCase::new(curr_de.as_str());
let mut feh_handle = Command::new("feh");
let feh_handle = feh_handle.arg("--bg-scale").arg(filepath_set);
let mut gnome_handle = Command::new("gsettings");
let gnome_handle = gnome_handle
.arg("set")
.arg("org.gnome.desktop.background")
.arg("picture-uri")
.arg(format!("'file://{}'", filepath_set));
let kde_script_beg = r#"
qdbus org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.evaluateScript "
var allDesktops = desktops();
print (allDesktops);
for (i=0;i<allDesktops.length;i++) {
d = allDesktops[i];
d.wallpaperPlugin = 'org.kde.image';
d.currentConfigGroup = Array('Wallpaper',
'org.kde.image',
'General');
d.writeConfig('Image', 'file://"#;
let kde_script_end = r#"')
}""#;
let kde_script = format!("{}{}{}", kde_script_beg, filepath_set, kde_script_end);
let mut lxde_handle = Command::new("pcmanfm");
let lxde_handle = lxde_handle.arg("--set-wallpaper").arg(filepath_set);
let mut mate_handle = Command::new("gsettings set org.mate.background picture-filename");
let mate_handle = mate_handle
.arg("set")
.arg("org.mate.background")
.arg("picture-uri")
.arg(format!("'file://{}'", filepath_set));
let xfce_script_beg = r#"xfconf-query -c xfce4-desktop \
-p /backdrop/screen0/monitor0/workspace0/last-image \
-s ""#;
let xfce_script_alt_beg = r#"xfconf-query -c xfce4-desktop \
-p /backdrop/screen0/monitor0/workspace0/last-image \
-s ""#;
let xfce_script_end = r#"""#;
let xfce_script = format!("{}{}{}", xfce_script_beg, filepath_set, xfce_script_end);
let xfce_script_alt = format!("{}{}{}", xfce_script_alt_beg, filepath_set, xfce_script_end);
let mut cust_backend = false;
if let Some(back) = backend {
curr_de = UniCase::new(back);
cust_backend = true;
}
if gnome.contains(&curr_de) {
gnome_handle
.spawn()
.map_err(|_| Errors::ProgramRunError(String::from("Gnome Wallpaper Adjuster")))?;
} else if lxde == curr_de {
lxde_handle
.spawn()
.map_err(|_| Errors::ProgramRunError(String::from("LXDE Wallpaper Adjuster")))?;
} else if mate == curr_de {
mate_handle
.spawn()
.map_err(|_| Errors::ProgramRunError(String::from("Mate Wallpaper Adjuster")))?;
} else if kde.contains(&curr_de) {
run_script::run(kde_script.as_str(), &vec![], &ScriptOptions::new())
.map_err(|_| Errors::ProgramRunError(String::from("KDE Wallpaper Adjuster")))?;
} else if xfce.contains(&curr_de) {
run_script::run(xfce_script.as_str(), &vec![], &ScriptOptions::new())
.map_err(|_| Errors::ProgramRunError(String::from("XFCE Wallpaper Adjuster")))?;
run_script::run(xfce_script_alt.as_str(), &vec![], &ScriptOptions::new())
.map_err(|_| Errors::ProgramRunError(String::from("XFCE Wallpaper Adjuster")))?;
} else if !cust_backend || curr_de == UniCase::new("feh") {
feh_handle
.spawn()
.map_err(|_| Errors::ProgramRunError(String::from("Feh")))?;
} else {
return Err(Errors::BackendNotFoundError(curr_de.to_string()).into());
}
println!("{} has been set as your wallpaper", filepath_set);
Ok(())
}
pub fn sun_timings(
lat: f64,
lon: f64,
elevation: f64,
dir_count_day: u32,
dir_count_night: u32,
) -> Vec<Time> {
let mut times: Vec<Time> = vec![];
let (sunrise, sunset) = sun_times::sun_times(Utc::today(), lat, lon, elevation);
let (sunset, sunrise) = (sunset.with_timezone(&Local), sunrise.with_timezone(&Local));
let sunset = Time::new((sunset.hour() * 60) + sunset.minute());
let sunrise = Time::new((sunrise.hour() * 60) + sunrise.minute());
let step_time_day = Time::new((sunset.total_mins - sunrise.total_mins) / dir_count_day);
let step_time_night =
Time::new((1440 - (sunset.total_mins - sunrise.total_mins)) / dir_count_night);
let mut loop_time_night: Time;
let mut loop_time_day = sunrise.to_owned();
while loop_time_day < sunset {
if loop_time_day >= FULL_DAY {
times.push(loop_time_day - FULL_DAY);
} else {
times.push(loop_time_day);
}
loop_time_day += step_time_day;
}
loop_time_night = loop_time_day.to_owned() + step_time_night;
while loop_time_night < (sunrise + Time::new(1440)) {
if loop_time_night >= FULL_DAY {
times.push(loop_time_night - FULL_DAY);
} else {
times.push(loop_time_night);
}
loop_time_night += step_time_night;
}
times
}