|
1 | 1 | use std::{ |
2 | 2 | env, |
3 | | - fs::{self, remove_file}, |
| 3 | + fs::remove_file, |
4 | 4 | io, |
5 | 5 | os::unix::net::UnixListener, |
6 | 6 | process::exit, |
7 | | - sync::{ |
8 | | - mpsc::{channel, Receiver, RecvTimeoutError}, |
9 | | - Mutex, |
10 | | - }, |
| 7 | + sync::mpsc::{channel, Receiver, Sender}, |
11 | 8 | thread, |
12 | | - time::Duration, |
13 | 9 | }; |
14 | 10 |
|
15 | | -use acpi::{BacklightAcpiDevice, ACPI_DEVICES_PATH}; |
16 | | -use backlight_ipc::{BacklightCommand, DEFAULT_UNIX_SOCKET_PATH}; |
17 | | -use ddc::BacklightDdcDevice; |
| 11 | +use backlight_ipc::{BacklightCommand, BacklightMode, DEFAULT_UNIX_SOCKET_PATH}; |
| 12 | +use monitors::auto_refresh_monitors_list; |
18 | 13 |
|
19 | 14 | mod acpi; |
20 | 15 | mod ddc; |
| 16 | +mod monitors; |
21 | 17 |
|
22 | | -/// Holds the current backlight state of all monitors. |
23 | | -/// |
24 | | -/// Querying/writing to backlight devices is expensive, we try to cache as many things as possible |
25 | | -/// in order to avoid unnecessary I/O. |
26 | | -static MONITORS: Mutex<Vec<Box<dyn BacklightDevice + Send>>> = Mutex::new(Vec::new()); |
27 | | -const MONITORS_REFRESH_INTERVAL: Duration = Duration::from_secs(60); |
28 | | - |
29 | | -trait BacklightDevice { |
30 | | - fn name(&self) -> String; |
31 | | - fn set_brightness(&mut self, percent: u8) -> anyhow::Result<()>; |
32 | | - fn get_brightness(&self) -> u8; |
33 | | -} |
34 | | - |
35 | | -fn refresh_monitors_list() { |
36 | | - // Don't modify the global MONITORS variable directly to avoid locking the mutex for too long. |
37 | | - let mut new_monitors: Vec<Box<dyn BacklightDevice + Send>> = Vec::new(); |
38 | | - |
39 | | - for ddc_device in ddc_hi::Display::enumerate() { |
40 | | - match BacklightDdcDevice::new(ddc_device) { |
41 | | - Ok(monitor) => new_monitors.push(Box::new(monitor)), |
42 | | - Err(err) => eprintln!("Failed to retrieve DDC backlight monitor: {err}"), |
43 | | - } |
44 | | - } |
45 | | - |
46 | | - match fs::read_dir(ACPI_DEVICES_PATH) { |
47 | | - Ok(dir) => { |
48 | | - for entry in dir { |
49 | | - match entry { |
50 | | - Ok(file) => match BacklightAcpiDevice::new(file.path()) { |
51 | | - Ok(monitor) => new_monitors.push(Box::new(monitor)), |
52 | | - Err(err) => println!("Failed to retrieve ACPI backlight monitor: {err}"), |
53 | | - }, |
54 | | - Err(err) => { |
55 | | - eprintln!("Unable to read entry from {ACPI_DEVICES_PATH}: {err}"); |
56 | | - } |
57 | | - } |
58 | | - } |
59 | | - } |
60 | | - Err(err) => { |
61 | | - eprintln!("{ACPI_DEVICES_PATH}: {err}"); |
62 | | - // fallthrough |
63 | | - } |
64 | | - } |
65 | | - |
66 | | - let mut monitors = MONITORS.lock().unwrap(); |
67 | | - monitors.clear(); |
68 | | - monitors.extend(new_monitors); |
69 | | -} |
70 | | - |
71 | | -fn set_brightness_percent(percent: u8) -> anyhow::Result<()> { |
72 | | - for monitor in MONITORS.lock().unwrap().iter_mut() { |
73 | | - monitor.set_brightness(percent)?; |
74 | | - } |
75 | | - Ok(()) |
76 | | -} |
77 | | - |
78 | | -fn increase_brightness_percent(percent: u8) -> anyhow::Result<()> { |
79 | | - for monitor in MONITORS.lock().unwrap().iter_mut() { |
80 | | - let mut new_brightness = monitor.get_brightness() + percent; |
81 | | - |
82 | | - if new_brightness > 100 { |
83 | | - new_brightness = 100; |
84 | | - } |
85 | | - |
86 | | - monitor.set_brightness(new_brightness)?; |
87 | | - } |
88 | | - Ok(()) |
89 | | -} |
90 | | - |
91 | | -fn decrease_brightness_percent(percent: u8) -> anyhow::Result<()> { |
92 | | - for monitor in MONITORS.lock().unwrap().iter_mut() { |
93 | | - let mut new_brightness = monitor.get_brightness() as isize - percent as isize; |
94 | | - |
95 | | - // Don't allow setting the brightness to 0 to prevent the monitor from being completely black. |
96 | | - if new_brightness < 1 { |
97 | | - new_brightness = 1; |
98 | | - } |
99 | | - |
100 | | - monitor.set_brightness(new_brightness as u8)?; |
101 | | - } |
102 | | - Ok(()) |
103 | | -} |
104 | | - |
105 | | -fn handle_commands(cmd_receiver: Receiver<BacklightCommand>) { |
| 18 | +fn handle_commands( |
| 19 | + cmd_receiver: Receiver<BacklightCommand>, |
| 20 | +) -> ! { |
106 | 21 | loop { |
107 | | - refresh_monitors_list(); |
108 | | - |
109 | | - loop { |
110 | | - match cmd_receiver.recv_timeout(MONITORS_REFRESH_INTERVAL) { |
111 | | - Ok(command) => { |
112 | | - let result = match command { |
113 | | - BacklightCommand::SetBrightness(percent) => set_brightness_percent(percent), |
114 | | - BacklightCommand::IncreaseBrightness(percent) => { |
115 | | - increase_brightness_percent(percent) |
116 | | - } |
117 | | - BacklightCommand::DecreaseBrightness(percent) => { |
118 | | - decrease_brightness_percent(percent) |
119 | | - } |
120 | | - BacklightCommand::Refresh => { |
121 | | - refresh_monitors_list(); |
122 | | - Ok(()) |
123 | | - } |
124 | | - }; |
125 | | - |
126 | | - if let Err(err) = result { |
127 | | - eprintln!("Command handling failed: {err}"); |
128 | | - } |
129 | | - } |
130 | | - Err(err) => match err { |
131 | | - RecvTimeoutError::Timeout => break, |
132 | | - RecvTimeoutError::Disconnected => panic!("channel disconnected"), |
133 | | - }, |
| 22 | + let command = cmd_receiver |
| 23 | + .recv() |
| 24 | + .expect("Failed to receive command from cmd channel"); |
| 25 | + let result = match command { |
| 26 | + BacklightCommand::SetBrightness(percent) => monitors::set_brightness_percent(percent), |
| 27 | + BacklightCommand::IncreaseBrightness(percent) => { |
| 28 | + monitors::increase_brightness_percent(percent) |
| 29 | + } |
| 30 | + BacklightCommand::DecreaseBrightness(percent) => { |
| 31 | + monitors::decrease_brightness_percent(percent) |
| 32 | + } |
| 33 | + BacklightCommand::Refresh => { |
| 34 | + monitors::refresh_monitors_list(); |
| 35 | + Ok(()) |
134 | 36 | } |
| 37 | + }; |
| 38 | + |
| 39 | + if let Err(err) = result { |
| 40 | + eprintln!("Command handling failed: {err}"); |
135 | 41 | } |
136 | 42 | } |
137 | 43 | } |
@@ -171,8 +77,13 @@ fn main() { |
171 | 77 | let (cmd_sender, cmd_receiver) = channel(); |
172 | 78 |
|
173 | 79 | let command_handler_thread = thread::spawn(move || handle_commands(cmd_receiver)); |
| 80 | + let auto_refresh_monitors_thread = thread::spawn(move || auto_refresh_monitors_list()); |
174 | 81 |
|
175 | 82 | for stream in listener.incoming() { |
| 83 | + if auto_refresh_monitors_thread.is_finished() { |
| 84 | + panic!("auto refresh monitors thread is gone"); |
| 85 | + } |
| 86 | + |
176 | 87 | if command_handler_thread.is_finished() { |
177 | 88 | panic!("command handler thread is gone"); |
178 | 89 | } |
|
0 commit comments