From 36542ea0b96f5ab16ec6da4ac634381c8e58ec61 Mon Sep 17 00:00:00 2001 From: Michael Murphy Date: Fri, 2 Aug 2024 17:59:09 +0200 Subject: [PATCH] feat: automatic dates; timezone setting support --- Cargo.lock | 370 ++++++++++++++++++++++--- cosmic-settings/Cargo.toml | 6 +- cosmic-settings/src/pages/input/mod.rs | 8 +- cosmic-settings/src/pages/time/date.rs | 292 ++++++++++++++++--- pages/time/Cargo.toml | 14 - pages/time/src/lib.rs | 50 ---- 6 files changed, 593 insertions(+), 147 deletions(-) delete mode 100644 pages/time/Cargo.toml delete mode 100644 pages/time/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 80ad6ab..d3496ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -287,7 +287,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd884d7c72877a94102c3715f3b1cd09ff4fac28221add3e57cfbe25c236d093" dependencies = [ - "async-fs 2.1.2", + "async-fs", "async-net", "enumflags2", "futures-channel", @@ -377,18 +377,6 @@ dependencies = [ "slab", ] -[[package]] -name = "async-fs" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "blocking", - "futures-lite 1.13.0", -] - [[package]] name = "async-fs" version = "2.1.2" @@ -1485,7 +1473,6 @@ dependencies = [ "cosmic-settings-config", "cosmic-settings-page", "cosmic-settings-system", - "cosmic-settings-time", "cosmic-settings-wallpaper", "derivative", "derive_setters", @@ -1498,6 +1485,7 @@ dependencies = [ "hostname1-zbus", "i18n-embed", "i18n-embed-fl", + "icu", "image 0.25.2", "itertools 0.13.0", "itoa", @@ -1513,6 +1501,7 @@ dependencies = [ "static_init", "sunrise", "tachyonix", + "timedate-zbus", "tokio", "tracing", "tracing-subscriber", @@ -1571,16 +1560,6 @@ dependencies = [ "sysinfo", ] -[[package]] -name = "cosmic-settings-time" -version = "0.1.0" -dependencies = [ - "icu_calendar", - "icu_timezone", - "timedate-zbus", - "zbus 3.15.2", -] - [[package]] name = "cosmic-settings-wallpaper" version = "0.1.0" @@ -2163,6 +2142,17 @@ dependencies = [ "toml 0.5.11", ] +[[package]] +name = "fixed_decimal" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0febbeb1118a9ecdee6e4520ead6b54882e843dd0592ad233247dbee84c53db8" +dependencies = [ + "displaydoc", + "smallvec", + "writeable", +] + [[package]] name = "flate2" version = "1.0.30" @@ -3136,6 +3126,30 @@ dependencies = [ "objc2", ] +[[package]] +name = "icu" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff5e3018d703f168b00dcefa540a65f1bbc50754ae32f3f5f0e43fe5ee51502" +dependencies = [ + "icu_calendar", + "icu_casemap", + "icu_collator", + "icu_collections", + "icu_datetime", + "icu_decimal", + "icu_experimental", + "icu_list", + "icu_locid", + "icu_locid_transform", + "icu_normalizer", + "icu_plurals", + "icu_properties", + "icu_provider", + "icu_segmenter", + "icu_timezone", +] + [[package]] name = "icu_calendar" version = "1.5.2" @@ -3159,6 +3173,171 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e009b7f0151ee6fb28c40b1283594397e0b7183820793e9ace3dcd13db126d0" +[[package]] +name = "icu_casemap" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff0c8ae9f8d31b12e27fc385ff9ab1f3cd9b17417c665c49e4ec958c37da75f" +dependencies = [ + "displaydoc", + "icu_casemap_data", + "icu_collections", + "icu_locid", + "icu_properties", + "icu_provider", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_casemap_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d57966d5ab748f74513be4046867f9a20e801e2775d41f91d04a0f560b61f08" + +[[package]] +name = "icu_collator" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d370371887d31d56f361c3eaa15743e54f13bc677059c9191c77e099ed6966b2" +dependencies = [ + "displaydoc", + "icu_collator_data", + "icu_collections", + "icu_locid_transform", + "icu_normalizer", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "zerovec", +] + +[[package]] +name = "icu_collator_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee3f88741364b7d6269cce6827a3e6a8a2cf408a78f766c9224ab479d5e4ae5" + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_datetime" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d115efb85e08df3fd77e77f52e7e087545a783fffba8be80bfa2102f306b1780" +dependencies = [ + "displaydoc", + "either", + "fixed_decimal", + "icu_calendar", + "icu_datetime_data", + "icu_decimal", + "icu_locid", + "icu_locid_transform", + "icu_plurals", + "icu_provider", + "icu_timezone", + "litemap", + "smallvec", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_datetime_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ba7e7f7a01269b9afb0a39eff4f8676f693b55f509b3120e43a0350a9f88bea" + +[[package]] +name = "icu_decimal" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8fd98f86ec0448d85e1edf8884e4e318bb2e121bd733ec929a05c0a5e8b0eb" +dependencies = [ + "displaydoc", + "fixed_decimal", + "icu_decimal_data", + "icu_locid_transform", + "icu_provider", + "writeable", +] + +[[package]] +name = "icu_decimal_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d424c994071c6f5644f999925fc868c85fec82295326e75ad5017bc94b41523" + +[[package]] +name = "icu_experimental" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "844ad7b682a165c758065d694bc4d74ac67f176da1c499a04d85d492c0f193b7" +dependencies = [ + "displaydoc", + "fixed_decimal", + "icu_collections", + "icu_decimal", + "icu_experimental_data", + "icu_locid", + "icu_locid_transform", + "icu_normalizer", + "icu_pattern", + "icu_plurals", + "icu_properties", + "icu_provider", + "litemap", + "num-bigint", + "num-rational", + "num-traits", + "smallvec", + "tinystr", + "writeable", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_experimental_data" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c178b9a34083fca5bd70d61f647575335e9c197d0f30c38e8ccd187babc69d0" + +[[package]] +name = "icu_list" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfeda1d7775b6548edd4e8b7562304a559a91ed56ab56e18961a053f367c365" +dependencies = [ + "displaydoc", + "icu_list_data", + "icu_locid_transform", + "icu_provider", + "regex-automata 0.2.0", + "writeable", +] + +[[package]] +name = "icu_list_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1825170d2c6679cb20dbd96a589d034e49f698aed9a2ef4fafc9a0101ed298f" + [[package]] name = "icu_locid" version = "1.5.0" @@ -3192,6 +3371,84 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_pattern" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f36aafd098d6717de34e668a8120822275c1fba22b936e757b7de8a2fd7e4" +dependencies = [ + "displaydoc", + "either", + "writeable", + "yoke", + "zerofrom", +] + +[[package]] +name = "icu_plurals" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a70e7c025dbd5c501b0a5c188cd11666a424f0dadcd4f0a95b7dafde3b114" +dependencies = [ + "displaydoc", + "fixed_decimal", + "icu_locid_transform", + "icu_plurals_data", + "icu_provider", + "zerovec", +] + +[[package]] +name = "icu_plurals_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3e8f775b215d45838814a090a2227247a7431d74e9156407d9c37f6ef0f208" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + [[package]] name = "icu_provider" version = "1.5.0" @@ -3220,6 +3477,28 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "icu_segmenter" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a717725612346ffc2d7b42c94b820db6908048f39434504cb130e8b46256b0de" +dependencies = [ + "core_maths", + "displaydoc", + "icu_collections", + "icu_locid", + "icu_provider", + "icu_segmenter_data", + "utf8_iter", + "zerovec", +] + +[[package]] +name = "icu_segmenter_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f739ee737260d955e330bc83fdeaaf1631f7fb7ed218761d3c04bb13bb7d79df" + [[package]] name = "icu_timezone" version = "1.5.0" @@ -5015,6 +5294,15 @@ dependencies = [ "regex-syntax 0.6.29", ] +[[package]] +name = "regex-automata" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9368763f5a9b804326f3af749e16f9abf378d227bcdee7634b13d8f17793782" +dependencies = [ + "memchr", +] + [[package]] name = "regex-automata" version = "0.4.7" @@ -6024,10 +6312,9 @@ dependencies = [ [[package]] name = "timedate-zbus" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "609052c48ae1d9e27999ac4a159843a290bffab5fdc13856889b940aefc185b7" +source = "git+https://github.com/pop-os/dbus-settings-bindings#cd21ddcb1b5cbfc80ab84b34d3c8b1ff3d81179a" dependencies = [ - "zbus 3.15.2", + "zbus 4.4.0", ] [[package]] @@ -6532,12 +6819,24 @@ dependencies = [ "tiny-skia-path", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + [[package]] name = "utf8-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -7339,11 +7638,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + [[package]] name = "writeable" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +dependencies = [ + "either", +] [[package]] name = "wyz" @@ -7515,15 +7823,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" dependencies = [ "async-broadcast 0.5.1", - "async-executor", - "async-fs 1.6.0", - "async-io 1.13.0", - "async-lock 2.8.0", "async-process 1.8.1", "async-recursion", - "async-task", "async-trait", - "blocking", "byteorder", "derivative", "enumflags2", @@ -7558,7 +7860,7 @@ checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" dependencies = [ "async-broadcast 0.7.1", "async-executor", - "async-fs 2.1.2", + "async-fs", "async-io 2.3.3", "async-lock 3.4.0", "async-process 2.2.3", diff --git a/cosmic-settings/Cargo.toml b/cosmic-settings/Cargo.toml index e56e47e..6bc3a81 100644 --- a/cosmic-settings/Cargo.toml +++ b/cosmic-settings/Cargo.toml @@ -20,7 +20,6 @@ cosmic-randr.workspace = true cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon" } cosmic-settings-page = { path = "../page" } cosmic-settings-system = { path = "../pages/system" } -cosmic-settings-time = { path = "../pages/time" } cosmic-settings-wallpaper = { path = "../pages/wallpapers" } derivative = "2.2.0" derive_setters = "0.1.6" @@ -31,6 +30,7 @@ futures = { package = "futures-lite", version = "2.3.0" } generator = "=0.8.1" hostname-validator = "1.1.1" hostname1-zbus = { git = "https://github.com/pop-os/dbus-settings-bindings" } +timedate-zbus = { git = "https://github.com/pop-os/dbus-settings-bindings" } i18n-embed-fl = "0.8.0" image = "0.25" itertools = "0.13.0" @@ -55,6 +55,10 @@ zbus = { version = "4.4.0", features = ["tokio"] } tachyonix = "0.3.0" slab = "0.4.9" +[dependencies.icu] +version = "1.4.0" +features = ["experimental", "compiled_data", "icu_datetime_experimental"] + [dependencies.i18n-embed] version = "0.14.1" features = ["fluent-system", "desktop-requester"] diff --git a/cosmic-settings/src/pages/input/mod.rs b/cosmic-settings/src/pages/input/mod.rs index dcf4bd4..a9bb464 100644 --- a/cosmic-settings/src/pages/input/mod.rs +++ b/cosmic-settings/src/pages/input/mod.rs @@ -1,7 +1,7 @@ use crate::app; use cosmic::{ cosmic_config::{self, ConfigGet, ConfigSet}, - iced, + Command, }; use cosmic_comp_config::input::{ AccelConfig, AccelProfile, ClickMethod, InputConfig, ScrollConfig, ScrollMethod, TapButtonMap, @@ -93,7 +93,7 @@ impl Page { } #[allow(clippy::too_many_lines)] - pub fn update(&mut self, message: Message) -> iced::Command { + pub fn update(&mut self, message: Message) -> Command { match message { Message::SetAcceleration(value, touchpad) => { let profile = if value { @@ -152,7 +152,7 @@ impl Page { select_model.activate(entity); let Some(left_entity) = select_model.entity_at(1) else { - return cosmic::Command::none(); + return Command::none(); }; let left_handed = select_model.active() == left_entity; @@ -173,7 +173,7 @@ impl Page { } } - cosmic::Command::none() + Command::none() } } diff --git a/cosmic-settings/src/pages/time/date.rs b/cosmic-settings/src/pages/time/date.rs index 04e1410..02fb446 100644 --- a/cosmic-settings/src/pages/time/date.rs +++ b/cosmic-settings/src/pages/time/date.rs @@ -1,53 +1,85 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only +use std::str::FromStr; + +use chrono::{Datelike, Timelike}; use cosmic::{ cosmic_config::{self, ConfigGet, ConfigSet}, - iced::{widget::horizontal_space, Length}, - widget::{dropdown, settings}, - Apply, + widget::{self, dropdown, settings}, + Apply, Command, }; use cosmic_settings_page::Section; use cosmic_settings_page::{self as page, section}; +use icu::{ + calendar::{DateTime, Iso}, + datetime::DateTimeFormatter, + locid::Locale, +}; use slab::Slab; use slotmap::SlotMap; +pub use timedate_zbus::TimeDateProxy; use tracing::error; crate::cache_dynamic_lazy! { static WEEKDAYS: [String; 4] = [fl!("time-format", "friday"), fl!("time-format", "saturday"), fl!("time-format", "sunday"), fl!("time-format", "monday")]; } +#[derive(Debug, Clone)] +pub struct Info { + pub ntp_enabled: bool, + pub timezone_id: Option, + pub timezone_list: Vec, +} + pub struct Page { - config: cosmic_config::Config, - auto: bool, - auto_timezone: bool, - military_time: bool, + cosmic_applet_config: cosmic_config::Config, first_day_of_week: usize, + military_time: bool, + ntp_enabled: bool, show_date_in_top_panel: bool, + local_time: Option>, + timezone: Option, + timezone_list: Vec, + formatted_date: String, } impl Default for Page { fn default() -> Self { - let config = cosmic_config::Config::new("com.system76.CosmicAppletTime", 1).unwrap(); - let military_time = config.get("military_time").unwrap_or_else(|err| { - error!(?err, "Failed to read config 'military_time'"); - false - }); - let first_day_of_week = config.get("first_day_of_week").unwrap_or_else(|err| { - error!(?err, "Failed to read config 'first_day_of_week'"); - 6 - }); - let show_date_in_top_panel = config.get("show_date_in_top_panel").unwrap_or_else(|err| { - error!(?err, "Failed to read config 'show_date_in_top_panel'"); - true - }); + let cosmic_applet_config = + cosmic_config::Config::new("com.system76.CosmicAppletTime", 1).unwrap(); + + let military_time = cosmic_applet_config + .get("military_time") + .unwrap_or_else(|err| { + error!(?err, "Failed to read config 'military_time'"); + false + }); + + let first_day_of_week = cosmic_applet_config + .get("first_day_of_week") + .unwrap_or_else(|err| { + error!(?err, "Failed to read config 'first_day_of_week'"); + 6 + }); + + let show_date_in_top_panel = cosmic_applet_config + .get("show_date_in_top_panel") + .unwrap_or_else(|err| { + error!(?err, "Failed to read config 'show_date_in_top_panel'"); + true + }); + Self { - config, - auto: false, - auto_timezone: false, - military_time, + cosmic_applet_config, first_day_of_week, + formatted_date: String::new(), + local_time: None, + military_time, + ntp_enabled: false, show_date_in_top_panel, + timezone: None, + timezone_list: Vec::new(), } } } @@ -69,42 +101,168 @@ impl page::Page for Page { .title(fl!("time-date")) .description(fl!("time-date", "desc")) } + + fn on_enter( + &mut self, + _page: cosmic_settings_page::Entity, + _sender: tokio::sync::mpsc::Sender, + ) -> Command { + cosmic::command::future(async move { + let client = match zbus::Connection::system().await { + Ok(client) => client, + Err(why) => { + return Message::Error(why.to_string()); + } + }; + + let timedate_proxy = match TimeDateProxy::new(&client).await { + Ok(timedate_proxy) => timedate_proxy, + Err(why) => { + return Message::Error(why.to_string()); + } + }; + + let can_ntp = timedate_proxy.can_ntp().await.unwrap_or_default(); + let ntp_enabled = can_ntp && timedate_proxy.ntp().await.unwrap_or_default(); + let timezone_list = timedate_proxy.list_timezones().await.unwrap_or_default(); + + let timezone = timedate_proxy.timezone().await.unwrap_or_default(); + + Message::Refresh(Info { + ntp_enabled, + timezone_id: timezone_list.iter().position(|tz| tz == &timezone), + timezone_list, + }) + }) + .map(crate::pages::Message::DateAndTime) + } } impl Page { - pub fn update(&mut self, message: Message) { + pub fn update(&mut self, message: Message) -> Command { match message { - Message::Automatic(enable) => self.auto = enable, - Message::AutomaticTimezone(enable) => self.auto_timezone = enable, + Message::Automatic(enable) => { + self.ntp_enabled = enable; + + tokio::task::spawn(async move { + let client = match zbus::Connection::system().await { + Ok(client) => client, + Err(why) => { + tracing::error!(?why, "zbus client error"); + return; + } + }; + + let timedate_proxy = match TimeDateProxy::new(&client).await { + Ok(timedate_proxy) => timedate_proxy, + Err(why) => { + tracing::error!(?why, "zbus client error"); + return; + } + }; + + _ = timedate_proxy.set_ntp(enable, true).await; + }); + } + Message::MilitaryTime(enable) => { self.military_time = enable; - if let Err(err) = self.config.set("military_time", enable) { + self.update_local_time(); + + if let Err(err) = self.cosmic_applet_config.set("military_time", enable) { error!(?err, "Failed to set config 'military_time'"); } } + Message::FirstDayOfWeek(weekday) => { self.first_day_of_week = weekday; - if let Err(err) = self.config.set("first_day_of_week", weekday) { + + if let Err(err) = self.cosmic_applet_config.set("first_day_of_week", weekday) { error!(?err, "Failed to set config 'first_day_of_week'"); } } + Message::ShowDate(enable) => { self.show_date_in_top_panel = enable; - if let Err(err) = self.config.set("show_date_in_top_panel", enable) { + + if let Err(err) = self + .cosmic_applet_config + .set("show_date_in_top_panel", enable) + { error!(?err, "Failed to set config 'show_date_in_top_panel'"); } } + + Message::Timezone(timezone_id) => { + self.timezone = Some(timezone_id); + + if let Some(timezone) = self.timezone_list.get(timezone_id).cloned() { + return cosmic::command::future(async move { + let client = match zbus::Connection::system().await { + Ok(client) => client, + Err(why) => { + return Message::Error(why.to_string()); + } + }; + + let timedate_proxy = match TimeDateProxy::new(&client).await { + Ok(timedate_proxy) => timedate_proxy, + Err(why) => { + return Message::Error(why.to_string()); + } + }; + + match timedate_proxy.set_timezone(&timezone, true).await { + Ok(_) => Message::UpdateTime, + Err(why) => Message::Error(why.to_string()), + } + }) + .map(crate::pages::Message::DateAndTime) + .map(crate::Message::PageMessage); + } + } + + Message::Error(why) => { + tracing::error!(why, "failed to set timezone"); + } + + Message::UpdateTime => self.update_local_time(), + + Message::Refresh(info) => { + self.ntp_enabled = info.ntp_enabled; + self.timezone_list = info.timezone_list; + self.timezone = info.timezone_id; + + self.update_local_time(); + } + + Message::None => (), + } + + Command::none() + } + + pub fn update_local_time(&mut self) { + self.local_time = Some(update_local_time()); + + self.formatted_date = match self.local_time { + Some(ref time) => format_date(time, self.military_time), + None => fl!("unknown"), } } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub enum Message { Automatic(bool), - AutomaticTimezone(bool), + Error(String), MilitaryTime(bool), + None, FirstDayOfWeek(usize), + Refresh(Info), ShowDate(bool), + Timezone(usize), + UpdateTime, } impl page::AutoBind for Page {} @@ -122,11 +280,11 @@ fn date() -> Section { settings::view_section(§ion.title) .add( settings::item::builder(&*section.descriptions[auto]) - .toggler(page.auto, Message::Automatic), + .toggler(page.ntp_enabled, Message::Automatic), ) .add(settings::item( &*section.descriptions[title], - horizontal_space(Length::Fill), + widget::text(&page.formatted_date), )) .apply(cosmic::Element::from) .map(crate::pages::Message::DateAndTime) @@ -183,8 +341,6 @@ fn format() -> Section { fn timezone() -> Section { let mut descriptions = Slab::new(); - let auto = descriptions.insert(fl!("time-zone", "auto")); - let auto_info = descriptions.insert(fl!("time-zone", "auto-info")); let time_zone = descriptions.insert(fl!("time-zone")); Section::default() @@ -192,18 +348,66 @@ fn timezone() -> Section { .descriptions(descriptions) .view::(move |_binder, page, section| { settings::view_section(§ion.title) - // Automatic timezone toggle - .add( - settings::item::builder(&*section.descriptions[auto]) - .description(&*section.descriptions[auto_info]) - .toggler(page.auto_timezone, Message::AutomaticTimezone), - ) // Time zone select .add( - settings::item::builder(&*section.descriptions[time_zone]) - .control(horizontal_space(Length::Fill)), + settings::item::builder(&*section.descriptions[time_zone]).control( + widget::dropdown(&page.timezone_list, page.timezone, Message::Timezone), + ), ) .apply(cosmic::Element::from) .map(crate::pages::Message::DateAndTime) }) } + +fn locale() -> Result> { + let locale = std::env::var("LANG")?; + let locale = locale + .split('.') + .next() + .ok_or(format!("Can't split the locale {locale}"))?; + + let locale = Locale::from_str(locale).map_err(|e| format!("{e:?}"))?; + Ok(locale) +} + +fn format_date(date: &DateTime, military: bool) -> String { + let Ok(locale) = locale() else { + return String::new(); + }; + + let mut bag = icu::datetime::options::components::Bag::empty(); + + bag.year = Some(icu::datetime::options::components::Year::Numeric); + bag.day = Some(icu::datetime::options::components::Day::NumericDayOfMonth); + bag.month = Some(icu::datetime::options::components::Month::Long); + bag.hour = Some(icu::datetime::options::components::Numeric::Numeric); + bag.minute = Some(icu::datetime::options::components::Numeric::Numeric); + bag.preferences = Some(icu::datetime::options::preferences::Bag::from_hour_cycle( + if military { + icu::datetime::options::preferences::HourCycle::H23 + } else { + icu::datetime::options::preferences::HourCycle::H12 + }, + )); + + let dtf = DateTimeFormatter::try_new_experimental(&locale.into(), bag.into()).unwrap(); + + dtf.format(&date.to_any()) + .expect("can't format value") + .to_string() +} + +fn update_local_time() -> DateTime { + let now = chrono::Local::now(); + + DateTime::try_new_gregorian_datetime( + now.year(), + now.month() as u8, + now.day() as u8, + now.hour() as u8, + now.minute() as u8, + now.second() as u8, + ) + .unwrap() + .to_iso() +} diff --git a/pages/time/Cargo.toml b/pages/time/Cargo.toml deleted file mode 100644 index ac1fb7f..0000000 --- a/pages/time/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "cosmic-settings-time" -version = "0.1.0" -edition = "2021" - -[dependencies] -icu_calendar = "1.5.2" -icu_timezone = "1.5.0" -timedate-zbus = "0.1.0" - -[dependencies.zbus] -version = "3.15.2" -default-features = false -features = ["tokio"] diff --git a/pages/time/src/lib.rs b/pages/time/src/lib.rs deleted file mode 100644 index 67ed577..0000000 --- a/pages/time/src/lib.rs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2023 System76 -// SPDX-License-Identifier: GPL-3.0-only - -use std::time::SystemTime; - -use icu_calendar::{types::IsoSecond, DateTime, Iso}; -use icu_timezone::CustomTimeZone; -use timedate_zbus::TimeDateProxy; - -pub struct Info { - pub can_ntp: bool, - pub timezone: CustomTimeZone, - pub local_time: DateTime, -} - -impl Info { - pub async fn load(proxy: &TimeDateProxy<'_>) -> Option { - let can_ntp = proxy.can_ntp().await.unwrap_or_default(); - - let Ok(timezone) = proxy - .timezone() - .await - .unwrap_or_default() - .parse::() - else { - return None; - }; - - let Ok(duration) = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) else { - return None; - }; - - let seconds = duration.as_secs(); - - let Ok(iso_seconds) = IsoSecond::try_from((seconds % 60) as u8) else { - return None; - }; - - #[allow(clippy::cast_possible_truncation)] - let mut local_time = DateTime::from_minutes_since_local_unix_epoch((seconds / 60) as i32); - - local_time.time.second = iso_seconds; - - Some(Info { - can_ntp, - timezone, - local_time, - }) - } -}