diff --git a/Cargo.lock b/Cargo.lock index b3e0f03..3557156 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -870,9 +870,9 @@ checksum = "77e53693616d3075149f4ead59bdeecd204ac6b8192d8969757601b74bddf00f" [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1293,6 +1293,7 @@ dependencies = [ "anyhow", "ashpd 0.7.0", "async-channel", + "chrono", "clap", "color-eyre", "cosmic-bg-config", @@ -1324,6 +1325,7 @@ dependencies = [ "serde", "slotmap", "static_init", + "sunrise", "tokio", "tracing", "tracing-subscriber", @@ -5234,6 +5236,15 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +[[package]] +name = "sunrise" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3217c5830416956b1f2dc731f526150a82c144ebe83d2f0e78853c8356a22ada" +dependencies = [ + "chrono", +] + [[package]] name = "svg_fmt" version = "0.4.2" @@ -5529,10 +5540,22 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "socket2 0.5.6", + "tokio-macros", "tracing", "windows-sys 0.48.0", ] +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + [[package]] name = "toml" version = "0.5.11" diff --git a/Cargo.toml b/Cargo.toml index 8d256dc..d640454 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,8 @@ members = ["cosmic-settings", "page", "pages/*"] default-members = ["cosmic-settings"] resolver = "2" rust-version = "1.71.0" +sunrise_sunset = "1.0.1" + [workspace.dependencies.libcosmic] git = "https://github.com/pop-os/libcosmic" diff --git a/cosmic-settings/Cargo.toml b/cosmic-settings/Cargo.toml index 2595bad..82569b7 100644 --- a/cosmic-settings/Cargo.toml +++ b/cosmic-settings/Cargo.toml @@ -6,6 +6,7 @@ license = "GPL-3.0" rust-version = "1.65.0" [dependencies] +tokio = { version = "1.35.1", features = ["macros"] } async-channel = "2.1.1" color-eyre = "0.6.2" cosmic-bg-config = { workspace = true } @@ -20,16 +21,17 @@ dirs = "5.0.1" generator = "=0.7.5" i18n-embed-fl = "0.7.0" itertools = "0.12.0" -libcosmic = {workspace = true} +libcosmic = { workspace = true } once_cell = "1.19.0" regex = "1.10.3" rust-embed = "8.2.0" slotmap = "1.0.7" -tokio = "1.35.1" downcast-rs = "1.2.0" cosmic-comp-config = { workspace = true } # TODO: migrate this dependency to the pages/desktop crate. cosmic-panel-config = { workspace = true } +chrono = "0.4.37" +sunrise = "1.0.1" tracing = "0.1.40" tracing-subscriber = "0.3.18" url = "2.5.0" @@ -41,7 +43,7 @@ serde = { version = "1.0.196", features = ["derive"] } ashpd = { version = "0.7", default-features = false } ron = "0.8" static_init = "1.0.3" -clap = {version = "4.4.18", features = ["derive"] } +clap = { version = "4.4.18", features = ["derive"] } itoa = "1.0.10" futures = { package = "futures-lite", version = "2.2.0" } diff --git a/cosmic-settings/src/app.rs b/cosmic-settings/src/app.rs index b7627c5..a1dacf4 100644 --- a/cosmic-settings/src/app.rs +++ b/cosmic-settings/src/app.rs @@ -225,6 +225,11 @@ impl cosmic::Application for SettingsApp { }); Subscription::batch(vec![ + crate::subscription::daytime().map(|daytime| { + Message::PageMessage(pages::Message::Appearance(appearance::Message::Daytime( + daytime, + ))) + }), wayland_events, // Watch for changes to installed desktop entries desktop_files(0).map(|_| Message::DesktopInfo), diff --git a/cosmic-settings/src/pages/desktop/appearance.rs b/cosmic-settings/src/pages/desktop/appearance.rs index 923e03e..adc152b 100644 --- a/cosmic-settings/src/pages/desktop/appearance.rs +++ b/cosmic-settings/src/pages/desktop/appearance.rs @@ -76,8 +76,12 @@ pub struct Page { theme_builder_needs_update: bool, theme_builder_config: Option, + auto_switch_descs: [Cow<'static, str>; 4], + tk: CosmicTk, tk_config: Option, + + day_time: bool, } impl Default for Page { @@ -192,6 +196,13 @@ impl theme_builder, tk_config, tk, + day_time: true, + auto_switch_descs: [ + fl!("auto-switch", "sunrise").into(), + fl!("auto-switch", "sunset").into(), + fl!("auto-switch", "next-sunrise").into(), + fl!("auto-switch", "next-sunset").into(), + ], } } } @@ -275,6 +286,7 @@ pub enum Message { StartImport, UseDefaultWindowHint(bool), WindowHintSize(spin_button::Message), + Daytime(bool), } #[derive(Debug, Clone, Copy)] @@ -434,10 +446,6 @@ impl Page { let ret = match message { Message::DarkMode(enabled) => { if let Some(config) = self.theme_mode_config.as_ref() { - // must disable auto switch if the user manually switches the theme - if let Err(err) = self.theme_mode.set_auto_switch(config, false) { - tracing::error!(?err, "Error setting auto switch"); - } if let Err(err) = self.theme_mode.set_is_dark(config, enabled) { tracing::error!(?err, "Error setting dark mode"); } @@ -821,6 +829,10 @@ impl Page { } Command::none() } + Message::Daytime(day_time) => { + self.day_time = day_time; + Command::none() + } }; if self.theme_builder_needs_update { @@ -879,9 +891,9 @@ impl Page { fn reload_theme_mode(&mut self) { let icon_themes = std::mem::take(&mut self.icon_themes); let icon_theme_active = self.icon_theme_active.take(); - + let day_time = self.day_time; *self = Self::from((self.theme_mode_config.clone(), self.theme_mode)); - + self.day_time = day_time; self.icon_themes = icon_themes; self.icon_theme_active = icon_theme_active; } @@ -1023,26 +1035,25 @@ pub fn mode_and_colors() -> Section { .descriptions(vec![ // 0 fl!("auto-switch").into(), - fl!("auto-switch", "desc").into(), - //2 + //1 fl!("accent-color").into(), - //3 + //2 fl!("app-background").into(), - //4 + //3 fl!("container-background").into(), fl!("container-background", "desc").into(), fl!("container-background", "desc-detail").into(), fl!("container-background", "reset").into(), - // 8 + // 7 fl!("text-tint").into(), fl!("text-tint", "desc").into(), - // 10 + // 9 fl!("control-tint").into(), fl!("control-tint", "desc").into(), - // 12 + // 11 fl!("window-hint-accent-toggle").into(), fl!("window-hint-accent").into(), - // 14 + // 13 fl!("dark").into(), fl!("light").into(), ]) @@ -1067,7 +1078,7 @@ pub fn mode_and_colors() -> Section { .padding([8, 0]) .selected(page.theme_mode.is_dark) .on_press(Message::DarkMode(true)), - text(&*descriptions[14]) + text(&*descriptions[13]) ] .spacing(8) .width(Length::FillPortion(1)) @@ -1082,7 +1093,7 @@ pub fn mode_and_colors() -> Section { .selected(!page.theme_mode.is_dark) .padding([8, 0]) .on_press(Message::DarkMode(false)), - text(&*descriptions[15]) + text(&*descriptions[14]) ] .spacing(8) .width(Length::FillPortion(1)) @@ -1097,7 +1108,18 @@ pub fn mode_and_colors() -> Section { ) .add( settings::item::builder(&*descriptions[0]) - .description(&*descriptions[1]) + .description( + if page.day_time && !page.theme_mode.is_dark { + &page.auto_switch_descs[0] + } else if !page.day_time && page.theme_mode.is_dark { + &page.auto_switch_descs[1] + } else if page.day_time && page.theme_mode.is_dark { + &page.auto_switch_descs[2] + } else { + &page.auto_switch_descs[3] + } + .clone(), + ) .toggler(page.theme_mode.auto_switch, Message::Autoswitch), ) .add( diff --git a/cosmic-settings/src/subscription/daytime.rs b/cosmic-settings/src/subscription/daytime.rs new file mode 100644 index 0000000..c077fdb --- /dev/null +++ b/cosmic-settings/src/subscription/daytime.rs @@ -0,0 +1,80 @@ +use std::any::TypeId; + +use ashpd::desktop::location::{Location, LocationProxy}; +use chrono::Datelike; +use cosmic::iced::{ + self, + futures::{channel::mpsc::Sender, future, SinkExt, StreamExt}, +}; +use sunrise::sunrise_sunset; +use tokio::select; + +pub fn daytime() -> cosmic::iced::Subscription { + struct Sunset; + iced::subscription::channel(TypeId::of::(), 2, |tx| async { + if let Err(err) = inner(tx).await { + tracing::error!("Sunset subscription error: {:?}", err); + } + future::pending().await + }) +} + +enum Event { + Daytime, + LocationUpdated(Location), +} + +async fn inner(mut tx: Sender) -> anyhow::Result<()> { + let location_proxy = LocationProxy::new().await?; + let mut updates = location_proxy.receive_location_updated().await?; + + let mut next = updates.next().await.map(Event::LocationUpdated); + let mut loc = None; + + while let Some(e) = next { + match e { + Event::LocationUpdated(l) => { + loc = Some(l); + } + Event::Daytime => {} + }; + let Some(loc) = loc.as_ref() else { + break; + }; + let (lat, long) = (loc.latitude(), loc.longitude()); + let now = chrono::Local::now(); + let date = now.date_naive(); + let (sunrise, sunset) = sunrise_sunset(lat, long, date.year(), date.month0(), date.day0()); + let now_in_seconds = now.timestamp(); + let daytime = now_in_seconds >= sunrise && now_in_seconds <= sunset; + tx.send(daytime).await?; + + let sleep = if daytime { + sunset - now_in_seconds + } else if now_in_seconds < sunset { + sunrise - now_in_seconds + } else { + let tmrw = now + chrono::Duration::days(1); + let tmrw_date = tmrw.date_naive(); + let (tmrw_sunrise, _) = sunrise_sunset( + lat, + long, + tmrw_date.year(), + tmrw_date.month0(), + tmrw_date.day0(), + ); + tmrw_sunrise - now_in_seconds + }; + next = select! { + () = tokio::time::sleep(tokio::time::Duration::from_secs(sleep as u64)) => { + Some(Event::Daytime) + }, + l = updates.next() => if let Some(l) = l { + Some(Event::LocationUpdated(l)) + } else { + break; + } + }; + } + Err(anyhow::anyhow!("Location updates ended unexpectedly.")) +} diff --git a/cosmic-settings/src/subscription/mod.rs b/cosmic-settings/src/subscription/mod.rs index 004dc76..cfcbf48 100644 --- a/cosmic-settings/src/subscription/mod.rs +++ b/cosmic-settings/src/subscription/mod.rs @@ -1,2 +1,4 @@ mod desktop_files; pub use desktop_files::*; +mod daytime; +pub use daytime::*; diff --git a/i18n/en/cosmic_settings.ftl b/i18n/en/cosmic_settings.ftl index 456df78..d5f3941 100644 --- a/i18n/en/cosmic_settings.ftl +++ b/i18n/en/cosmic_settings.ftl @@ -33,7 +33,10 @@ window-hint-accent = Active window hint color window-hint-accent-toggle = Use theme accent color as active window hint auto-switch = Automatically switch from Light to Dark mode - .desc = Switches to Light mode at sunrise + .sunrise = Switches to Light mode at sunrise + .sunset = Switches to Light mode at sunset + .next-sunrise = Switches to Light mode at next sunrise + .next-sunset = Switches to Light mode at next sunset container-background = Container background .desc-detail = Container background color is used for navigation sidebar, side drawer, dialogs and similar widgets. By default, it is automatically derived from the Application or window background.