Skip to content

Commit e3c8fd7

Browse files
committed
Implement pausing brightness changes for specified time
Fixes jacob-pro#46
1 parent cd9644c commit e3c8fd7

File tree

7 files changed

+149
-17
lines changed

7 files changed

+149
-17
lines changed

src/apply.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ pub fn apply_brightness(
9696
transition_mins: u32,
9797
location: Location,
9898
overrides: Vec<MonitorOverride>,
99+
force_day_brightness: bool,
99100
) -> ApplyResults {
100101
let overrides = overrides
101102
.iter()
@@ -132,9 +133,13 @@ pub fn apply_brightness(
132133

133134
if let Some(BrightnessValues {
134135
brightness_day,
135-
brightness_night,
136+
mut brightness_night,
136137
}) = monitor_values
137138
{
139+
if force_day_brightness {
140+
brightness_night = brightness_day;
141+
}
142+
138143
let brightness = calculate_brightness(
139144
brightness_day,
140145
brightness_night,

src/cli.rs

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ fn run(args: Args) -> anyhow::Result<()> {
4444
config.transition_mins,
4545
config.location.unwrap(),
4646
config.overrides,
47+
false,
4748
);
4849
let pretty = serde_json::to_string_pretty(&result).unwrap();
4950
println!("{}", pretty);

src/controller.rs

+63-5
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ use std::sync::{mpsc, Arc, RwLock};
77
use std::thread;
88
use std::thread::JoinHandle;
99
use std::time::{Duration, SystemTime, UNIX_EPOCH};
10+
use sunrise_sunset_calculator::SunriseSunsetParameters;
1011

1112
pub enum Message {
1213
Shutdown,
1314
Refresh(&'static str),
1415
Disable(&'static str),
1516
Enable(&'static str),
17+
Unpause(&'static str),
18+
Pause(&'static str, i64),
1619
}
1720

1821
pub struct BrightnessController {
@@ -56,12 +59,23 @@ fn run<F: Fn()>(
5659
) {
5760
log::info!("Starting BrightnessController");
5861
let mut enabled = true;
62+
// When paused, this will be set to the SystemTime until which updates are paused.
63+
let mut paused_until: Option<SystemTime> = None;
5964

6065
loop {
66+
// If we are paused, check whether the pause period has expired.
67+
if let Some(pause_time) = paused_until {
68+
if SystemTime::now() >= pause_time {
69+
log::info!("BrightnessController pause period expired, resuming updates");
70+
paused_until = None;
71+
}
72+
}
73+
6174
let timeout = if enabled {
6275
// Apply brightness using latest config
6376
let config = config.read().unwrap().clone();
64-
let result = apply(config);
77+
let is_paused = paused_until.is_some();
78+
let result = apply(config, is_paused);
6579
let timeout = calculate_timeout(&result);
6680

6781
// Update last result
@@ -76,7 +90,7 @@ fn run<F: Fn()>(
7690
// Sleep until receiving message or timeout
7791
let rx_result = match timeout {
7892
None => {
79-
log::info!("Brightness Worker sleeping indefinitely");
93+
log::info!("BrightnessController sleeping indefinitely");
8094
receiver.recv().map_err(|e| e.into())
8195
}
8296
Some(timeout) => {
@@ -97,7 +111,7 @@ fn run<F: Fn()>(
97111
break;
98112
}
99113
Ok(Message::Refresh(src)) => {
100-
log::info!("Refreshing due to '{src}'");
114+
log::info!("Refreshing BrightnessController due to '{src}'");
101115
}
102116
Ok(Message::Disable(src)) => {
103117
log::info!("Disabling BrightnessController due to '{src}'");
@@ -107,8 +121,22 @@ fn run<F: Fn()>(
107121
log::info!("Enabling BrightnessController due to '{src}'");
108122
enabled = true;
109123
}
124+
Ok(Message::Unpause(src)) => {
125+
log::info!("Unpausing BrightnessController due to '{src}'");
126+
paused_until = None;
127+
}
128+
Ok(Message::Pause(src, time)) => {
129+
log::info!("Pausing BrightnessController due to '{src}' for '{time}' seconds");
130+
let pause_time = if time < 0 {
131+
let config = config.read().unwrap().clone();
132+
compute_next_sunrise(config)
133+
} else {
134+
SystemTime::now() + Duration::from_secs(time as u64)
135+
};
136+
paused_until = Some(pause_time);
137+
}
110138
Err(RecvTimeoutError::Timeout) => {
111-
log::debug!("Refreshing due to timeout")
139+
log::debug!("Refreshing BrightnessController due to timeout")
112140
}
113141
Err(RecvTimeoutError::Disconnected) => panic!("Unexpected disconnection"),
114142
}
@@ -131,17 +159,47 @@ fn calculate_timeout(results: &Option<ApplyResults>) -> Option<SystemTime> {
131159
}
132160

133161
// Calculate and apply the brightness
134-
fn apply(config: SsbConfig) -> Option<ApplyResults> {
162+
fn apply(config: SsbConfig, force_day_brightness: bool) -> Option<ApplyResults> {
135163
if let Some(location) = config.location {
136164
Some(apply_brightness(
137165
config.brightness_day,
138166
config.brightness_night,
139167
config.transition_mins,
140168
location,
141169
config.overrides,
170+
force_day_brightness,
142171
))
143172
} else {
144173
log::warn!("Skipping apply because no location is configured");
145174
None
146175
}
147176
}
177+
178+
// Calculate the next sunrise time
179+
fn compute_next_sunrise(config: SsbConfig) -> SystemTime {
180+
if let Some(location) = config.location {
181+
let epoch_time_now = SystemTime::now()
182+
.duration_since(UNIX_EPOCH)
183+
.unwrap()
184+
.as_secs() as i64;
185+
let sun =
186+
SunriseSunsetParameters::new(epoch_time_now, location.latitude, location.longitude)
187+
.calculate()
188+
.unwrap();
189+
190+
// If the calculated sunrise is in the past, calculate tomorrow's sunrise.
191+
if sun.rise <= epoch_time_now {
192+
let tomorrow = epoch_time_now + 86400;
193+
let sun_tomorrow =
194+
SunriseSunsetParameters::new(tomorrow, location.latitude, location.longitude)
195+
.calculate()
196+
.unwrap();
197+
UNIX_EPOCH + Duration::from_secs(sun_tomorrow.rise as u64)
198+
} else {
199+
UNIX_EPOCH + Duration::from_secs(sun.rise as u64)
200+
}
201+
} else {
202+
log::warn!("Assuming next sunrise is in 12 hours because no location is configured");
203+
SystemTime::now() + Duration::from_secs(43200)
204+
}
205+
}

src/gui/brightness_settings.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,19 @@ impl Page for BrightnessSettingsPage {
9494
self.copy_to_config(&mut config);
9595
app_state
9696
.controller
97-
.send(Message::Refresh("Brightness change"))
97+
.send(Message::Unpause("UI Apply"))
98+
.unwrap();
99+
app_state
100+
.controller
101+
.send(Message::Refresh("UI Apply"))
98102
.unwrap();
99103
}
100104
if ui.button("Save").clicked() {
101105
let mut config = app_state.config.write().unwrap();
102106
self.copy_to_config(&mut config);
103107
app_state
104108
.controller
105-
.send(Message::Refresh("Brightness change"))
109+
.send(Message::Refresh("UI Save"))
106110
.unwrap();
107111
save_config(&mut config, &app_state.transitions);
108112
};

src/gui/mod.rs

+16
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ mod monitor_overrides;
66
mod status;
77

88
use crate::common::APP_NAME;
9+
use crate::controller::Message;
910
use crate::gui::app::SsbEguiApp;
1011
use crate::tray::read_icon;
1112
use egui_wgpu::wgpu::PowerPreference;
1213
use egui_winit::winit;
1314
use egui_winit::winit::event::{Event, WindowEvent};
1415
use egui_winit::winit::event_loop::{EventLoopProxy, EventLoopWindowTarget};
1516
use egui_winit::winit::window::Icon;
17+
use std::sync::mpsc::Sender;
1618
use std::sync::{Arc, Mutex};
1719
use std::time::Instant;
1820

@@ -33,6 +35,9 @@ pub enum NextPaint {
3335
pub enum UserEvent {
3436
// When the tray button is clicked - we should open or bring forward the window
3537
OpenWindow(&'static str),
38+
// When the tray button for a pause is clicked - it should pause changing brightness for the given duration.
39+
// When the duration is negative, it will be paused until sunrise.
40+
Pause(&'static str, i64),
3641
// When the tray exit button is clicked - the application should exit
3742
Exit(&'static str),
3843
// Hide the window if it is open
@@ -66,6 +71,7 @@ impl Drop for WgpuWinitRunning {
6671
pub struct WgpuWinitApp<F> {
6772
repaint_proxy: EventLoopProxy<UserEvent>,
6873
running: Option<WgpuWinitRunning>,
74+
controller: Sender<Message>,
6975
icon: Icon,
7076
app_factory: F,
7177
start_minimised: bool,
@@ -75,6 +81,7 @@ impl<F: Fn() -> SsbEguiApp> WgpuWinitApp<F> {
7581
pub fn new(
7682
event_loop: EventLoopProxy<UserEvent>,
7783
start_minimised: bool,
84+
controller: Sender<Message>,
7885
app_factory: F,
7986
) -> Self {
8087
if start_minimised {
@@ -85,6 +92,7 @@ impl<F: Fn() -> SsbEguiApp> WgpuWinitApp<F> {
8592
Self {
8693
repaint_proxy: event_loop,
8794
running: None,
95+
controller,
8896
icon,
8997
app_factory,
9098
start_minimised,
@@ -256,6 +264,14 @@ impl<F: Fn() -> SsbEguiApp> WgpuWinitApp<F> {
256264
}
257265
}
258266

267+
Event::UserEvent(UserEvent::Pause(src, time)) => {
268+
log::info!("Received Pause action from '{src}' for '{time}' seconds");
269+
self.controller
270+
.send(Message::Pause(src, time.clone()))
271+
.unwrap();
272+
NextPaint::Wait
273+
}
274+
259275
Event::UserEvent(UserEvent::RequestRepaint { when, frame_nr }) => {
260276
if self.frame_nr() == *frame_nr {
261277
NextPaint::RepaintAt(*when)

src/main.rs

+13-8
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,19 @@ fn main() {
8080
let _tray = tray::create(&event_loop);
8181

8282
let app_proxy = event_loop.create_proxy();
83-
let mut framework = WgpuWinitApp::new(event_loop.create_proxy(), args.minimised, move || {
84-
SsbEguiApp::new(
85-
app_proxy.clone(),
86-
controller.last_result.clone(),
87-
config.clone(),
88-
controller.sender.clone(),
89-
)
90-
});
83+
let mut framework = WgpuWinitApp::new(
84+
event_loop.create_proxy(),
85+
args.minimised,
86+
controller.sender.clone(),
87+
move || {
88+
SsbEguiApp::new(
89+
app_proxy.clone(),
90+
controller.last_result.clone(),
91+
config.clone(),
92+
controller.sender.clone(),
93+
)
94+
},
95+
);
9196

9297
let mut next_repaint_time = Some(Instant::now());
9398

src/tray.rs

+44-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@ use crate::common::APP_NAME;
22
use crate::gui::UserEvent;
33
use egui_winit::winit::event_loop::{EventLoop, EventLoopProxy};
44
use std::sync::{Arc, Mutex};
5-
use tray_icon::menu::{Menu, MenuEvent, MenuId, MenuItemBuilder};
5+
use tray_icon::menu::{Menu, MenuEvent, MenuId, MenuItemBuilder, SubmenuBuilder};
66
use tray_icon::{ClickType, Icon, TrayIcon, TrayIconBuilder, TrayIconEvent};
77

88
const MENU_ID_OPEN: &str = "OPEN";
99
const MENU_ID_EXIT: &str = "EXIT";
10+
//const MENU_ID_UNPAUSE: &str = "UNPAUSE"; // TODO: Add unpause when paused
11+
const MENU_ID_PAUSE_15M: &str = "PAUSE_15M";
12+
const MENU_ID_PAUSE_30M: &str = "PAUSE_30M";
13+
const MENU_ID_PAUSE_1H: &str = "PAUSE_1H";
14+
const MENU_ID_PAUSE_2H: &str = "PAUSE_2H";
15+
const MENU_ID_PAUSE_SUNRISE: &str = "PAUSE_SUNRISE";
1016

1117
pub fn read_icon() -> (Vec<u8>, png::OutputInfo) {
1218
let mut decoder = png::Decoder::new(include_bytes!("../assets/icon-256.png").as_slice());
@@ -44,6 +50,38 @@ fn create_internal(event_loop: EventLoopProxy<UserEvent>) -> TrayIcon {
4450
.id(MenuId::new(MENU_ID_OPEN))
4551
.enabled(true)
4652
.build(),
53+
&SubmenuBuilder::new()
54+
.text("Pause for…")
55+
.enabled(true)
56+
.items(&[
57+
&MenuItemBuilder::new()
58+
.text("Until next sunrise")
59+
.id(MenuId::new(MENU_ID_PAUSE_SUNRISE))
60+
.enabled(true)
61+
.build(),
62+
&MenuItemBuilder::new()
63+
.text("15 minutes")
64+
.id(MenuId::new(MENU_ID_PAUSE_15M))
65+
.enabled(true)
66+
.build(),
67+
&MenuItemBuilder::new()
68+
.text("30 minutes")
69+
.id(MenuId::new(MENU_ID_PAUSE_30M))
70+
.enabled(true)
71+
.build(),
72+
&MenuItemBuilder::new()
73+
.text("1 hour")
74+
.id(MenuId::new(MENU_ID_PAUSE_1H))
75+
.enabled(true)
76+
.build(),
77+
&MenuItemBuilder::new()
78+
.text("2 hours")
79+
.id(MenuId::new(MENU_ID_PAUSE_2H))
80+
.enabled(true)
81+
.build(),
82+
])
83+
.build()
84+
.unwrap(),
4785
&MenuItemBuilder::new()
4886
.text("Exit")
4987
.id(MenuId::new(MENU_ID_EXIT))
@@ -76,6 +114,11 @@ fn create_internal(event_loop: EventLoopProxy<UserEvent>) -> TrayIcon {
76114
let action = match event.id.0.as_str() {
77115
MENU_ID_OPEN => UserEvent::OpenWindow("Tray Button"),
78116
MENU_ID_EXIT => UserEvent::Exit("Tray Button"),
117+
MENU_ID_PAUSE_15M => UserEvent::Pause("Tray Button", 900),
118+
MENU_ID_PAUSE_30M => UserEvent::Pause("Tray Button", 1800),
119+
MENU_ID_PAUSE_1H => UserEvent::Pause("Tray Button", 3600),
120+
MENU_ID_PAUSE_2H => UserEvent::Pause("Tray Button", 7200),
121+
MENU_ID_PAUSE_SUNRISE => UserEvent::Pause("Tray Button", -1),
79122
_ => return,
80123
};
81124
menu_loop.lock().unwrap().send_event(action).unwrap();

0 commit comments

Comments
 (0)