3
3
4
4
use std:: { borrow:: Cow , str:: FromStr } ;
5
5
6
- use chrono:: { Datelike , DurationRound , Timelike } ;
6
+ use chrono:: { Datelike , Timelike } ;
7
7
use cosmic:: {
8
8
app,
9
9
applet:: { cosmic_panel_config:: PanelAnchor , menu_button, padded_control} ,
@@ -25,6 +25,7 @@ use cosmic::{
25
25
Command , Element , Theme ,
26
26
} ;
27
27
use timedate_zbus:: TimeDateProxy ;
28
+ use tokio:: { sync:: watch, time} ;
28
29
29
30
use icu:: {
30
31
calendar:: DateTime ,
@@ -56,6 +57,7 @@ pub struct Window {
56
57
rectangle : Rectangle ,
57
58
token_tx : Option < calloop:: channel:: Sender < TokenRequest > > ,
58
59
config : TimeAppletConfig ,
60
+ show_seconds_tx : watch:: Sender < bool > ,
59
61
locale : Locale ,
60
62
}
61
63
@@ -134,7 +136,10 @@ impl cosmic::Application for Window {
134
136
// variable but never updated
135
137
// Instead of using the local timezone, we will store an offset that is updated if the
136
138
// 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 ) ;
138
143
139
144
(
140
145
Self {
@@ -147,6 +152,7 @@ impl cosmic::Application for Window {
147
152
rectangle : Rectangle :: default ( ) ,
148
153
token_tx : None ,
149
154
config : TimeAppletConfig :: default ( ) ,
155
+ show_seconds_tx,
150
156
locale,
151
157
} ,
152
158
Command :: none ( ) ,
@@ -166,16 +172,56 @@ impl cosmic::Application for Window {
166
172
}
167
173
168
174
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
+ }
179
225
} )
180
226
}
181
227
@@ -219,9 +265,10 @@ impl cosmic::Application for Window {
219
265
} )
220
266
}
221
267
268
+ let show_seconds_rx = self . show_seconds_tx . subscribe ( ) ;
222
269
Subscription :: batch ( vec ! [
223
270
rectangle_tracker_subscription( 0 ) . map( |e| Message :: Rectangle ( e. 1 ) ) ,
224
- time_subscription( ) . map ( |_| Message :: Tick ) ,
271
+ time_subscription( show_seconds_rx ) ,
225
272
activation_token_subscription( 0 ) . map( Message :: Token ) ,
226
273
timezone_subscription( ) ,
227
274
self . core. watch_config( Self :: APP_ID ) . map( |u| {
@@ -356,6 +403,15 @@ impl cosmic::Application for Window {
356
403
Command :: none ( )
357
404
}
358
405
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
+ } ) ;
359
415
self . config = c;
360
416
Command :: none ( )
361
417
}
@@ -396,6 +452,10 @@ impl cosmic::Application for Window {
396
452
397
453
time_bag. hour = Some ( components:: Numeric :: Numeric ) ;
398
454
time_bag. minute = Some ( components:: Numeric :: Numeric ) ;
455
+ time_bag. second = self
456
+ . config
457
+ . show_seconds
458
+ . then_some ( components:: Numeric :: Numeric ) ;
399
459
400
460
let hour_cycle = if self . config . military_time {
401
461
preferences:: HourCycle :: H23
0 commit comments