openlogi_device/write/
backlight.rs1use std::sync::Arc;
2
3use hidpp::{
4 channel::HidppChannel,
5 device::Device,
6 feature::{
7 CreatableFeature,
8 backlight::{
9 BacklightFeature, BacklightMode as FirmwareMode, BacklightStatus as FirmwareStatus,
10 SetBacklightConfig,
11 },
12 },
13};
14use tracing::debug;
15
16use crate::SharedChannel;
17use crate::backend::HidBackend;
18use crate::backlight::{BacklightMode, BacklightState, BacklightStatus};
19use crate::channel::route::DeviceRoute;
20
21use super::{HidppOperation, WriteError, classify_hidpp_error, open_feature, with_route};
22
23fn mode_from_firmware(mode: FirmwareMode) -> BacklightMode {
27 match mode {
28 FirmwareMode::Automatic => BacklightMode::Automatic,
29 FirmwareMode::TemporaryManual => BacklightMode::TemporaryManual,
30 FirmwareMode::PermanentManual => BacklightMode::PermanentManual,
31 _ => BacklightMode::None,
32 }
33}
34
35fn mode_to_firmware(mode: BacklightMode) -> FirmwareMode {
42 match mode {
43 BacklightMode::None => FirmwareMode::None,
44 BacklightMode::Automatic | BacklightMode::TemporaryManual => FirmwareMode::Automatic,
45 BacklightMode::PermanentManual => FirmwareMode::PermanentManual,
46 }
47}
48
49fn status_from_firmware(status: FirmwareStatus) -> BacklightStatus {
53 match status {
54 FirmwareStatus::DisabledBySoftware => BacklightStatus::DisabledBySoftware,
55 FirmwareStatus::DisabledByCriticalBattery => BacklightStatus::DisabledByCriticalBattery,
56 FirmwareStatus::AlsSaturated => BacklightStatus::AlsSaturated,
57 FirmwareStatus::TemporaryManual => BacklightStatus::TemporaryManual,
58 FirmwareStatus::PermanentManual => BacklightStatus::PermanentManual,
59 _ => BacklightStatus::AlsAutomatic,
60 }
61}
62
63async fn read_state(feature: &BacklightFeature) -> Result<BacklightState, WriteError> {
66 let config = feature.get_backlight_config().await.map_err(|e| {
67 classify_hidpp_error(e, HidppOperation::ReadBacklight, BacklightFeature::ID)
68 })?;
69 let info = feature.get_backlight_info().await.map_err(|e| {
70 classify_hidpp_error(e, HidppOperation::ReadBacklight, BacklightFeature::ID)
71 })?;
72 Ok(BacklightState {
73 enabled: config.enabled,
74 mode: mode_from_firmware(config.mode),
75 status: status_from_firmware(info.status),
76 current_level: info.current_level,
77 nb_levels: info.nb_levels,
78 })
79}
80
81pub async fn get_backlight(
86 backend: &dyn HidBackend,
87 route: &DeviceRoute,
88) -> Result<BacklightState, WriteError> {
89 let index = route.device_index();
90 with_route(backend, route, move |channel| async move {
91 get_backlight_on_channel(&channel, index).await
92 })
93 .await
94}
95
96pub async fn get_backlight_on(shared: &SharedChannel) -> Result<BacklightState, WriteError> {
98 get_backlight_on_channel(shared.channel(), shared.device_index()).await
99}
100
101async fn get_backlight_on_channel(
102 channel: &Arc<HidppChannel>,
103 index: u8,
104) -> Result<BacklightState, WriteError> {
105 let mut device = Device::new(Arc::clone(channel), index)
106 .await
107 .map_err(|_| WriteError::DeviceUnreachable { index })?;
108 let feature = open_feature::<BacklightFeature>(&mut device).await?;
109 read_state(&feature).await
110}
111
112pub async fn set_backlight_enabled(
134 backend: &dyn HidBackend,
135 route: &DeviceRoute,
136 enabled: bool,
137) -> Result<BacklightState, WriteError> {
138 let index = route.device_index();
139 with_route(backend, route, move |channel| async move {
140 let mut device = Device::new(Arc::clone(&channel), index)
141 .await
142 .map_err(|_| WriteError::DeviceUnreachable { index })?;
143 let feature = open_feature::<BacklightFeature>(&mut device).await?;
144
145 let current = feature.get_backlight_config().await.map_err(|e| {
146 classify_hidpp_error(e, HidppOperation::ReadBacklight, BacklightFeature::ID)
147 })?;
148
149 feature
150 .set_backlight_config(SetBacklightConfig {
151 enabled,
152 options: current.options,
153 mode: mode_to_firmware(mode_from_firmware(current.mode)),
154 effect: None,
157 current_level: current.current_level,
158 duration_hands_out: current.duration_hands_out,
159 duration_hands_in: current.duration_hands_in,
160 duration_powered: current.duration_powered,
161 })
162 .await
163 .map_err(|e| {
164 classify_hidpp_error(e, HidppOperation::WriteBacklight, BacklightFeature::ID)
165 })?;
166
167 debug!(index, enabled, "wrote backlight enable");
168 read_state(&feature).await
169 })
170 .await
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn firmware_modes_round_trip_through_openlogi_modes() {
179 assert_eq!(
180 mode_from_firmware(FirmwareMode::Automatic),
181 BacklightMode::Automatic
182 );
183 assert_eq!(
184 mode_from_firmware(FirmwareMode::PermanentManual),
185 BacklightMode::PermanentManual
186 );
187 assert_eq!(mode_from_firmware(FirmwareMode::None), BacklightMode::None);
188 }
189
190 #[test]
191 fn temporary_manual_is_downgraded_because_software_cannot_write_it() {
192 assert_eq!(
193 mode_from_firmware(FirmwareMode::TemporaryManual),
194 BacklightMode::TemporaryManual
195 );
196 assert_eq!(
197 mode_to_firmware(BacklightMode::TemporaryManual),
198 FirmwareMode::Automatic
199 );
200 }
201
202 #[test]
203 fn writable_modes_survive_the_read_write_round_trip() {
204 for mode in [FirmwareMode::None, FirmwareMode::PermanentManual] {
205 assert_eq!(mode_to_firmware(mode_from_firmware(mode)), mode);
206 }
207 }
208
209 #[test]
210 fn software_disable_status_is_mapped() {
211 assert_eq!(
212 status_from_firmware(FirmwareStatus::DisabledBySoftware),
213 BacklightStatus::DisabledBySoftware
214 );
215 assert_eq!(
216 status_from_firmware(FirmwareStatus::AlsSaturated),
217 BacklightStatus::AlsSaturated
218 );
219 }
220}