Skip to content

Commit 93e3b0f

Browse files
joshuamegnauth54mmstick
authored andcommitted
feat(time): Option to show seconds on the clock
Closes: #496 Requires: pop-os/cosmic-settings#509 I based some of this code off of `i3status-rust` to double check that I handled it efficiently.
1 parent f9c01f5 commit 93e3b0f

File tree

2 files changed

+75
-13
lines changed

2 files changed

+75
-13
lines changed

cosmic-applet-time/src/config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use cosmic::cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, Cosmi
77
#[version = 1]
88
pub struct TimeAppletConfig {
99
pub military_time: bool,
10+
pub show_seconds: bool,
1011
pub first_day_of_week: u8,
1112
pub show_date_in_top_panel: bool,
1213
pub show_weekday: bool,
@@ -16,6 +17,7 @@ impl Default for TimeAppletConfig {
1617
fn default() -> Self {
1718
Self {
1819
military_time: false,
20+
show_seconds: false,
1921
first_day_of_week: 6,
2022
show_date_in_top_panel: true,
2123
show_weekday: false,

cosmic-applet-time/src/window.rs

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
use std::{borrow::Cow, str::FromStr};
55

6-
use chrono::{Datelike, DurationRound, Timelike};
6+
use chrono::{Datelike, Timelike};
77
use cosmic::{
88
app,
99
applet::{cosmic_panel_config::PanelAnchor, menu_button, padded_control},
@@ -25,6 +25,7 @@ use cosmic::{
2525
Command, Element, Theme,
2626
};
2727
use timedate_zbus::TimeDateProxy;
28+
use tokio::{sync::watch, time};
2829

2930
use icu::{
3031
calendar::DateTime,
@@ -56,6 +57,7 @@ pub struct Window {
5657
rectangle: Rectangle,
5758
token_tx: Option<calloop::channel::Sender<TokenRequest>>,
5859
config: TimeAppletConfig,
60+
show_seconds_tx: watch::Sender<bool>,
5961
locale: Locale,
6062
}
6163

@@ -134,7 +136,10 @@ impl cosmic::Application for Window {
134136
// variable but never updated
135137
// Instead of using the local timezone, we will store an offset that is updated if the
136138
// timezone is ever externally changed
137-
let now: chrono::DateTime<chrono::FixedOffset> = chrono::Local::now().fixed_offset();
139+
let now = chrono::Local::now().fixed_offset();
140+
141+
// Synch `show_seconds` from the config within the time subscription
142+
let (show_seconds_tx, _) = watch::channel(true);
138143

139144
(
140145
Self {
@@ -147,6 +152,7 @@ impl cosmic::Application for Window {
147152
rectangle: Rectangle::default(),
148153
token_tx: None,
149154
config: TimeAppletConfig::default(),
155+
show_seconds_tx,
150156
locale,
151157
},
152158
Command::none(),
@@ -166,16 +172,56 @@ impl cosmic::Application for Window {
166172
}
167173

168174
fn subscription(&self) -> Subscription<Message> {
169-
fn time_subscription() -> Subscription<()> {
170-
subscription::unfold("time-sub", (), move |()| async move {
171-
let now = chrono::Local::now();
172-
let update_delay = chrono::TimeDelta::minutes(1);
173-
174-
let duration = ((now + update_delay).duration_trunc(update_delay).unwrap() - now)
175-
.to_std()
176-
.unwrap();
177-
tokio::time::sleep(duration).await;
178-
((), ())
175+
fn time_subscription(mut show_seconds: watch::Receiver<bool>) -> Subscription<Message> {
176+
subscription::channel("time-sub", 1, |mut output| async move {
177+
// Mark this receiver's state as changed so that it always receives an initial
178+
// update during the loop below
179+
// This allows us to avoid duplicating code from the loop
180+
show_seconds.mark_changed();
181+
let mut period = 1;
182+
let mut timer = time::interval(time::Duration::from_secs(period));
183+
timer.set_missed_tick_behavior(time::MissedTickBehavior::Skip);
184+
185+
loop {
186+
tokio::select! {
187+
_ = timer.tick() => {
188+
#[cfg(debug_assertions)]
189+
if let Err(err) = output.send(Message::Tick).await {
190+
tracing::error!(?err, "Failed sending tick request to applet");
191+
}
192+
#[cfg(not(debug_assertions))]
193+
let _ = output.send(Message::Tick).await;
194+
195+
// Calculate a delta if we're ticking per minute to keep ticks stable
196+
// Based on i3status-rust
197+
let current = chrono::Local::now().second() as u64 % period;
198+
if current != 0 {
199+
timer.reset_after(time::Duration::from_secs(period - current));
200+
}
201+
},
202+
// Update timer if the user toggles show_seconds
203+
Ok(()) = show_seconds.changed() => {
204+
let seconds = *show_seconds.borrow_and_update();
205+
if seconds {
206+
period = 1;
207+
// Subsecond precision isn't needed; skip calculating offset
208+
let period = time::Duration::from_secs(period);
209+
let start = time::Instant::now() + period;
210+
timer = time::interval_at(start, period);
211+
} else {
212+
period = 60;
213+
let delta = time::Duration::from_secs(period - chrono::Utc::now().second() as u64 % period);
214+
let now = time::Instant::now();
215+
// Start ticking from the next minute to update the time properly
216+
let start = now + delta;
217+
let period = time::Duration::from_secs(period);
218+
timer = time::interval_at(start, period);
219+
}
220+
221+
timer.set_missed_tick_behavior(time::MissedTickBehavior::Skip);
222+
}
223+
}
224+
}
179225
})
180226
}
181227

@@ -219,9 +265,10 @@ impl cosmic::Application for Window {
219265
})
220266
}
221267

268+
let show_seconds_rx = self.show_seconds_tx.subscribe();
222269
Subscription::batch(vec![
223270
rectangle_tracker_subscription(0).map(|e| Message::Rectangle(e.1)),
224-
time_subscription().map(|_| Message::Tick),
271+
time_subscription(show_seconds_rx),
225272
activation_token_subscription(0).map(Message::Token),
226273
timezone_subscription(),
227274
self.core.watch_config(Self::APP_ID).map(|u| {
@@ -356,6 +403,15 @@ impl cosmic::Application for Window {
356403
Command::none()
357404
}
358405
Message::ConfigChanged(c) => {
406+
// Don't interrupt the tick subscription unless necessary
407+
self.show_seconds_tx.send_if_modified(|show_seconds| {
408+
if *show_seconds == c.show_seconds {
409+
false
410+
} else {
411+
*show_seconds = c.show_seconds;
412+
true
413+
}
414+
});
359415
self.config = c;
360416
Command::none()
361417
}
@@ -396,6 +452,10 @@ impl cosmic::Application for Window {
396452

397453
time_bag.hour = Some(components::Numeric::Numeric);
398454
time_bag.minute = Some(components::Numeric::Numeric);
455+
time_bag.second = self
456+
.config
457+
.show_seconds
458+
.then_some(components::Numeric::Numeric);
399459

400460
let hour_cycle = if self.config.military_time {
401461
preferences::HourCycle::H23

0 commit comments

Comments
 (0)