Skip to content

Commit

Permalink
feat: Add support for automatic location retrieval using geoclue2
Browse files Browse the repository at this point in the history
  • Loading branch information
bcyran committed Jan 19, 2025
1 parent 53684c5 commit 1ca3442
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 20 deletions.
34 changes: 28 additions & 6 deletions src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ use std::time::Duration;
use std::{env, path::Path};
use std::{str::FromStr, thread};

use anyhow::Result;
use anyhow::{anyhow, bail, Context};
use anyhow::{Ok, Result};
use chrono::prelude::*;
use log::debug;

use crate::cli::Appearance;
use crate::config::Config;
use crate::geo::Coords;
use crate::geoclue;
use crate::heif;
use crate::info::ImageInfo;
use crate::loader::WallpaperLoader;
Expand All @@ -21,6 +23,8 @@ use crate::setter::set_wallpaper;
use crate::wallpaper::{self, properties::Properties, Wallpaper};
use crate::{cache::LastWallpaper, schedule::current_image_index_appearance};

const GEOCLUE_TIMEOUT: Duration = Duration::from_secs(1);

pub fn info<P: AsRef<Path>>(path: P) -> Result<()> {
validate_wallpaper_file(&path)?;
print!("{}", ImageInfo::from_image(&path)?);
Expand Down Expand Up @@ -48,10 +52,6 @@ pub fn set<P: AsRef<Path>>(
let wall_path = get_effective_wall_path(path.as_ref())?;
let wallpaper = WallpaperLoader::new().load(&wall_path);

if matches!(wallpaper.properties, Properties::Solar(_)) && user_appearance.is_none() {
config.validate_for_solar()?;
};

let current_image_index = current_image_index(&wallpaper, &config, user_appearance)?;
if previous_image_index == Some(current_image_index) {
debug!("current image is the same as the previous one, skipping update");
Expand Down Expand Up @@ -168,7 +168,29 @@ fn current_image_index(
)),
Properties::H24(ref props) => current_image_index_h24(&props.time_info, now.time()),
Properties::Solar(ref props) => {
current_image_index_solar(&props.solar_info, &now, config.try_get_location()?)
current_image_index_solar(&props.solar_info, &now, &try_get_location(config)?)
}
}
}

fn try_get_location(config: &Config) -> Result<Coords> {
let maybe_location = match (config.geoclue_enabled(), config.geoclue_preferred()) {
(true, true) => {
geoclue::get_location(GEOCLUE_TIMEOUT).or_else(|_| config.try_get_location())
}
(true, false) => config
.try_get_location()
.or_else(|_| geoclue::get_location(GEOCLUE_TIMEOUT)),
(false, _) => config.try_get_location(),
};

maybe_location.with_context(|| {
format!(
concat!(
"Using wallpapers with solar schedule requires your approximate location information. ",
"Please enable geoclue2 or provide the location manually in the configuration file at {}."
),
Config::find_path().unwrap().display()
)
})
}
56 changes: 43 additions & 13 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ const CONFIG_FILE_NAME: &str = "config.toml";
const DEFAULT_CONFIG_FILE_CONTENT: &str = "\
# Configuration file for timewall
# Dynamic location service
# [geoclue]
# enable = true
# prefer = false
# Set your geographical location coordinates here
# [location]
# lat = 51.11
Expand Down Expand Up @@ -48,9 +53,37 @@ impl Default for Daemon {
}
}

#[derive(Deserialize, Serialize, Clone, Copy, Debug)]
pub struct Geoclue {
#[serde(default = "Geoclue::enable_default_value")]
pub enable: bool,
#[serde(default = "Geoclue::prefer_default_value")]
pub prefer: bool,
}

impl Geoclue {
const fn enable_default_value() -> bool {
true
}

const fn prefer_default_value() -> bool {
false
}
}

impl Default for Geoclue {
fn default() -> Self {
Self {
enable: Self::enable_default_value(),
prefer: Self::prefer_default_value(),
}
}
}

#[derive(Deserialize, Serialize, Debug)]
pub struct Config {
pub daemon: Option<Daemon>,
pub geoclue: Option<Geoclue>,
pub location: Option<Coords>,
pub setter: Option<Setter>,
}
Expand All @@ -59,6 +92,7 @@ impl Default for Config {
fn default() -> Self {
Self {
daemon: Some(Daemon::default()),
geoclue: Some(Geoclue::default()),
location: None,
setter: None,
}
Expand Down Expand Up @@ -123,20 +157,16 @@ impl Config {
self.daemon.unwrap_or_default().update_interval_seconds
}

pub fn try_get_location(&self) -> Result<&Coords> {
self.location
.as_ref()
.ok_or_else(|| anyhow!("location not set in the configuration"))
pub fn geoclue_enabled(&self) -> bool {
self.geoclue.unwrap_or_default().enable
}

pub fn validate_for_solar(&self) -> Result<()> {
if self.location.is_none() {
let config_path = Self::find_path()?;
bail!(
"using wallpapers with solar schedule requires setting your location in the configuration file at {}",
config_path.display()
);
}
Ok(())
pub fn geoclue_preferred(&self) -> bool {
self.geoclue.unwrap_or_default().prefer
}

pub fn try_get_location(&self) -> Result<Coords> {
self.location
.ok_or_else(|| anyhow!("location not set in the configuration"))
}
}
2 changes: 1 addition & 1 deletion src/geo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub enum Hemisphere {
Southern,
}

#[derive(Deserialize, Serialize, PartialEq, Debug)]
#[derive(Deserialize, Serialize, PartialEq, Debug, Clone, Copy)]
pub struct Coords {
pub lat: f64,
pub lon: f64,
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod cli;
mod config;
mod constants;
mod geo;
mod geoclue;
mod heif;
mod info;
mod loader;
Expand Down
3 changes: 3 additions & 0 deletions tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ pub const IMAGE_SET_MESSAGE: &str = "Set: ";
pub const COMMAND_RUN_MESSAGE: &str = "Run: ";

pub const CONFIG_WITH_LOCATION: &str = r"
[geoclue]
enable = false
[location]
lat = 52.2297
lon = 21.0122
Expand Down

0 comments on commit 1ca3442

Please sign in to comment.