aboutsummaryrefslogtreecommitdiff
path: root/clock/src/lib.rs
blob: 3c551a662123b3084f91d4000942e1fe94446ce4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
use serde::{Serialize, Deserialize};
use swaystatus_plugin::*;
use std::sync::mpsc::*;

pub struct ClockPlugin;
pub struct ClockRunnable<'c> {
    config : &'c ClockConfig,
    from_main : Receiver<MessagesFromMain>, 
    to_main : Box<dyn MsgModuleToMain +'c>
}

impl<'c> ClockRunnable<'c> {
    fn print_current_time_with_format(&self) -> String {
        let now = chrono::offset::Local::now();
        now.format(&self.config.format).to_string()
    }

    /// Simple, non-synchronized loop. Just sleeps the configured duration between sending the
    /// current time to the main module. Because thread sleeping is inaccurrate, this will alias
    /// sooner or later. Probably sooner.
    fn simple_loop(&self, timeout : std::time::Duration) {
        loop {
            self.to_main.send_update(Ok(self.print_current_time_with_format())).expect("Clock plugin tried to send the current time to the main program, but the main program doesn't listen any more.");
            match self.from_main.recv_timeout(timeout) {
                Ok(MessagesFromMain::Refresh) | Err(RecvTimeoutError::Timeout) => {},
                Ok(MessagesFromMain::Quit) | Err(RecvTimeoutError::Disconnected) => { break; },
            }
        }
    }

    fn fix_rounding_error_if_bad_refresh(fraction_of_thirty_mins : u128) {
        let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).expect("System time before beginning of UNIX epoch?!?");
        let now_millis = now.as_millis();
        let now_millis_up = now_millis + 1;
        let now_fraction_millis = now_millis * fraction_of_thirty_mins;
        let now_fraction_millis_up = now_millis_up * fraction_of_thirty_mins;
        if now_fraction_millis_up / 1800000 != now_fraction_millis / 1800000 {
            std::thread::sleep(std::time::Duration::from_millis(1));
        }
    }


    fn synchronized_loop(&self, fraction_of_thirty_mins : u128) {
        Self::fix_rounding_error_if_bad_refresh(fraction_of_thirty_mins);
        loop {
             self.to_main.send_update(Ok(self.print_current_time_with_format())).expect("Clock plugin tried to send the current time to the main program, but the main program doesn't listen any more.");
             let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).expect("System time before beginning of UNIX epoch?!?");
             
             let now_millis = now.as_millis() +1; //+1 for rounding up. 
             let now_fraction_millis = now_millis * fraction_of_thirty_mins;
             let now_fraction = now_fraction_millis / (1800000);
             let target_fraction = now_fraction + 1; //Adds one fraction_of_thirty_mins
             let target_rounded_fraction_millis = target_fraction * 1800000;
             let target_millis = target_rounded_fraction_millis / fraction_of_thirty_mins;
             let timeout_millis = target_millis - now_millis +1; //the 1 from above again, this time to ensure timeout_millis is actually rounded _up_
             let timeout = std::time::Duration::from_millis(timeout_millis as u64);
             match self.from_main.recv_timeout(timeout) {
                 Ok(MessagesFromMain::Refresh) => {
                     Self::fix_rounding_error_if_bad_refresh(fraction_of_thirty_mins)
                 },
                 Err(RecvTimeoutError::Timeout) => {},
                 Ok(MessagesFromMain::Quit) | Err(RecvTimeoutError::Disconnected) => { break; },
             }
        }
    }
}

impl<'c> SwayStatusModuleRunnable for ClockRunnable<'c> {
    fn run(&self) {
        match self.config.refresh_rate {
            ClockRefreshRate::NotSynchronized { seconds } => {
                self.simple_loop(std::time::Duration::from_secs_f32(seconds.abs()));
            },
            ClockRefreshRate::UTCSynchronized { updates_per_thirty_minutes }=> {
                self.synchronized_loop(std::cmp::max(updates_per_thirty_minutes,1) as u128);
            }
        }
    }
}

/// How the clock should refresh. There are two modes of operation. NotSynchronizedSeconds just
/// sleeps the given number of seconds  (float) between updates. As the name implies, it's NOT
/// synchronized to UTC, not even at startup. This means, that if you set a refresh rate of 3600
/// seconds and start the program at 2:45, the clock will remain at 2:45 until it's actually
/// 3:45... This setting takes a floating point amount of seconds as parameter.
/// The other mode of operation is synchronized to UTC. It can be set to update every 30/x minutes,
/// where x is a number between 1 and 65535. This range limitation was chosen to ensure meaningful
/// input. The 30 minute maximum for time between updates was chosen because of time zones.
/// Synchronizing to UTC days is not useful, because UTC midnight will in general not correspond to
/// the local midnight time. Synchronizing to UTC hours is questionable for the same reasons,
/// given that there are some time zones offset by 30 minutes. This brings us to the largest
/// duration that actually makes sense to synchronize with UTC: 30 minutes. The maximum update
/// rate has its current value because of accuracy limitations. Thread sleep is inherently
/// imprecise. Depending on the hardware/software, the error can be up to milliseconds. For a
/// simple wall clock there is little point in investing CPU cycles for higher accuracy, so the
/// maximum update rate was chosen to be way below 1/ms. By coincidence a 16 bit integer fits the
/// range of reasonable values nicely.
#[derive(Serialize, Deserialize)]
#[serde(tag = "Synchronization")]
enum ClockRefreshRate {
    NotSynchronized {
        #[serde(rename = "Seconds")]
        seconds : f32
    },
    UTCSynchronized {
        #[serde(rename = "PerThirtyMinutes")]
        updates_per_thirty_minutes : u16
    }
}

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "PascalCase",default)]
struct ClockConfig {
    format : String,
    refresh_rate : ClockRefreshRate 
}

impl Default for ClockConfig {
    fn default() -> Self {
        ClockConfig {
            format : String::from("%R"), 
            refresh_rate : ClockRefreshRate::UTCSynchronized { updates_per_thirty_minutes: 1800 }
        }
    }
}

impl SwayStatusModuleInstance for ClockConfig {
     fn make_runnable<'p>(&'p self, to_main : Box<dyn MsgModuleToMain + 'p>) -> (Box<dyn SwayStatusModuleRunnable + 'p>, Box<dyn MsgMainToModule + 'p>) {
         let (sender_from_main, from_main) = channel();
         let runnable = ClockRunnable {
             config : &self,
             from_main,
             to_main
         };
         let s = SenderForMain(sender_from_main);
         (Box::new(runnable), Box::new(s))
     }
}

impl SwayStatusModule for ClockPlugin {
    fn get_name(&self) -> &str {
        "ClockPlugin"
    }
    fn deserialize_config<'de>(&self, deserializer : &mut (dyn erased_serde::Deserializer + 'de)) -> Result<Box<dyn SwayStatusModuleInstance>, erased_serde::Error> {
       let result : ClockConfig = erased_serde::deserialize(deserializer)?;
       Ok(Box::new(result))
    }
    fn get_default_config(&self) -> Box<dyn SwayStatusModuleInstance> {
        let config = ClockConfig::default();
        Box::new(config)
    }
}

impl ClockPlugin {
    fn new() -> ClockPlugin {
        ClockPlugin
    }
}

enum MessagesFromMain {
    Quit,
    Refresh
}

struct SenderForMain(Sender<MessagesFromMain>);

impl MsgMainToModule for SenderForMain {
    fn send_quit(&self) -> Result<(),()> {
        self.0.send(MessagesFromMain::Quit).map_err(|_| ())
    }
    fn send_refresh(&self) -> Result<(),()> {
        self.0.send(MessagesFromMain::Refresh).map_err(|_| ())
    }
}

declare_swaystatus_module!(ClockPlugin, ClockPlugin::new);