feat: automatic dates; timezone setting support
This commit is contained in:
parent
a9dda39526
commit
36542ea0b9
6 changed files with 593 additions and 147 deletions
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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<app::Message> {
|
||||
pub fn update(&mut self, message: Message) -> Command<app::Message> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,53 +1,85 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// 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<usize>,
|
||||
pub timezone_list: Vec<String>,
|
||||
}
|
||||
|
||||
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<DateTime<Iso>>,
|
||||
timezone: Option<usize>,
|
||||
timezone_list: Vec<String>,
|
||||
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<crate::pages::Message> 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<crate::pages::Message>,
|
||||
) -> Command<crate::pages::Message> {
|
||||
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<crate::Message> {
|
||||
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<crate::pages::Message> for Page {}
|
||||
|
|
@ -122,11 +280,11 @@ fn date() -> Section<crate::pages::Message> {
|
|||
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<crate::pages::Message> {
|
|||
fn timezone() -> Section<crate::pages::Message> {
|
||||
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<crate::pages::Message> {
|
|||
.descriptions(descriptions)
|
||||
.view::<Page>(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<Locale, Box<dyn std::error::Error>> {
|
||||
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<Iso>, 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<Iso> {
|
||||
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()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue