openlogi_device/write/
backlight.rs1use std::sync::Arc;
2
3use hidpp::{
4 device::Device,
5 feature::{
6 CreatableFeature,
7 backlight::{
8 BacklightFeature, BacklightMode as FirmwareMode, BacklightStatus as FirmwareStatus,
9 SetBacklightConfig,
10 },
11 },
12};
13use tracing::debug;
14
15use crate::backend::HidBackend;
16use crate::backlight::{BacklightMode, BacklightState, BacklightStatus};
17use crate::channel::route::DeviceRoute;
18
19use super::{HidppOperation, WriteError, classify_hidpp_error, open_feature, with_route};
20
21fn mode_from_firmware(mode: FirmwareMode) -> BacklightMode {
25 match mode {
26 FirmwareMode::Automatic => BacklightMode::Automatic,
27 FirmwareMode::TemporaryManual => BacklightMode::TemporaryManual,
28 FirmwareMode::PermanentManual => BacklightMode::PermanentManual,
29 _ => BacklightMode::None,
30 }
31}
32
33fn mode_to_firmware(mode: BacklightMode) -> FirmwareMode {
40 match mode {
41 BacklightMode::None => FirmwareMode::None,
42 BacklightMode::Automatic | BacklightMode::TemporaryManual => FirmwareMode::Automatic,
43 BacklightMode::PermanentManual => FirmwareMode::PermanentManual,
44 }
45}
46
47fn status_from_firmware(status: FirmwareStatus) -> BacklightStatus {
51 match status {
52 FirmwareStatus::DisabledBySoftware => BacklightStatus::DisabledBySoftware,
53 FirmwareStatus::DisabledByCriticalBattery => BacklightStatus::DisabledByCriticalBattery,
54 FirmwareStatus::AlsSaturated => BacklightStatus::AlsSaturated,
55 FirmwareStatus::TemporaryManual => BacklightStatus::TemporaryManual,
56 FirmwareStatus::PermanentManual => BacklightStatus::PermanentManual,
57 _ => BacklightStatus::AlsAutomatic,
58 }
59}
60
61async fn read_state(feature: &BacklightFeature) -> Result<BacklightState, WriteError> {
64 let config = feature.get_backlight_config().await.map_err(|e| {
65 classify_hidpp_error(e, HidppOperation::ReadBacklight, BacklightFeature::ID)
66 })?;
67 let info = feature.get_backlight_info().await.map_err(|e| {
68 classify_hidpp_error(e, HidppOperation::ReadBacklight, BacklightFeature::ID)
69 })?;
70 Ok(BacklightState {
71 enabled: config.enabled,
72 mode: mode_from_firmware(config.mode),
73 status: status_from_firmware(info.status),
74 current_level: info.current_level,
75 nb_levels: info.nb_levels,
76 })
77}
78
79pub async fn get_backlight(
84 backend: &dyn HidBackend,
85 route: &DeviceRoute,
86) -> Result<BacklightState, WriteError> {
87 let index = route.device_index();
88 with_route(backend, route, move |channel| async move {
89 let mut device = Device::new(Arc::clone(&channel), index)
90 .await
91 .map_err(|_| WriteError::DeviceUnreachable { index })?;
92 let feature = open_feature::<BacklightFeature>(&mut device).await?;
93 read_state(&feature).await
94 })
95 .await
96}
97
98pub async fn set_backlight_enabled(
120 backend: &dyn HidBackend,
121 route: &DeviceRoute,
122 enabled: bool,
123) -> Result<BacklightState, WriteError> {
124 let index = route.device_index();
125 with_route(backend, route, move |channel| async move {
126 let mut device = Device::new(Arc::clone(&channel), index)
127 .await
128 .map_err(|_| WriteError::DeviceUnreachable { index })?;
129 let feature = open_feature::<BacklightFeature>(&mut device).await?;
130
131 let current = feature.get_backlight_config().await.map_err(|e| {
132 classify_hidpp_error(e, HidppOperation::ReadBacklight, BacklightFeature::ID)
133 })?;
134
135 feature
136 .set_backlight_config(SetBacklightConfig {
137 enabled,
138 options: current.options,
139 mode: mode_to_firmware(mode_from_firmware(current.mode)),
140 effect: None,
143 current_level: current.current_level,
144 duration_hands_out: current.duration_hands_out,
145 duration_hands_in: current.duration_hands_in,
146 duration_powered: current.duration_powered,
147 })
148 .await
149 .map_err(|e| {
150 classify_hidpp_error(e, HidppOperation::WriteBacklight, BacklightFeature::ID)
151 })?;
152
153 debug!(index, enabled, "wrote backlight enable");
154 read_state(&feature).await
155 })
156 .await
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn firmware_modes_round_trip_through_openlogi_modes() {
165 assert_eq!(
166 mode_from_firmware(FirmwareMode::Automatic),
167 BacklightMode::Automatic
168 );
169 assert_eq!(
170 mode_from_firmware(FirmwareMode::PermanentManual),
171 BacklightMode::PermanentManual
172 );
173 assert_eq!(mode_from_firmware(FirmwareMode::None), BacklightMode::None);
174 }
175
176 #[test]
177 fn temporary_manual_is_downgraded_because_software_cannot_write_it() {
178 assert_eq!(
179 mode_from_firmware(FirmwareMode::TemporaryManual),
180 BacklightMode::TemporaryManual
181 );
182 assert_eq!(
183 mode_to_firmware(BacklightMode::TemporaryManual),
184 FirmwareMode::Automatic
185 );
186 }
187
188 #[test]
189 fn writable_modes_survive_the_read_write_round_trip() {
190 for mode in [FirmwareMode::None, FirmwareMode::PermanentManual] {
191 assert_eq!(mode_to_firmware(mode_from_firmware(mode)), mode);
192 }
193 }
194
195 #[test]
196 fn software_disable_status_is_mapped() {
197 assert_eq!(
198 status_from_firmware(FirmwareStatus::DisabledBySoftware),
199 BacklightStatus::DisabledBySoftware
200 );
201 assert_eq!(
202 status_from_firmware(FirmwareStatus::AlsSaturated),
203 BacklightStatus::AlsSaturated
204 );
205 }
206}