1pub mod event;
19pub mod types;
20
21#[cfg(test)]
22mod tests;
23
24use std::sync::Arc;
25
26pub use event::RgbEffectsEvent;
27pub use types::{
28 ActivityEventType, CLUSTER_EFFECT_PARAM_COUNT, DisplayPersistencyCapabilities,
29 EventsNotificationFlags, LED_BIN_PARAM_COUNT, LedBinIndex, ONBOARD_INFO_PARAM_COUNT,
30 PowerModeTarget, RgbClusterInfo, RgbDeviceInfo, RgbEffectInfo, RgbExtCapabilities,
31 RgbNvCapabilities, RgbNvConfig, RgbPersistence, RgbPowerMode, RgbPowerModeConfig, RgbSwControl,
32 SlotInfoType, SwControlFlags,
33};
34
35use self::types::{ALL_CLUSTERS, ALL_EFFECTS, GetOrSet, be16};
36use crate::{
37 channel::{HidppChannel, MessageListenerGuard},
38 event::EventEmitter,
39 feature::{CreatableFeature, EmittingFeature, Feature, FeatureEndpoint, event_payload},
40 protocol::v20::Hidpp20Error,
41};
42
43const TYPE_GENERAL_INFO: u8 = 0x00;
45const TYPE_ONBOARD_EFFECT: u8 = 0x01;
47const GET_BACKUP: u8 = 0x02;
49const POWER_TARGET_SHIFT: u8 = 2;
51
52pub struct RgbEffectsFeature {
54 endpoint: FeatureEndpoint,
56
57 emitter: Arc<EventEmitter<RgbEffectsEvent>>,
59
60 _msg_listener: MessageListenerGuard,
62}
63
64impl CreatableFeature for RgbEffectsFeature {
65 const ID: u16 = 0x8071;
66 const STARTING_VERSION: u8 = 0;
67
68 fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
69 let emitter = Arc::new(EventEmitter::new());
70
71 let listener = chan.add_msg_listener_guarded({
72 let emitter = Arc::clone(&emitter);
73
74 move |raw, matched| {
75 let Some((func, payload)) =
76 event_payload(raw, matched, device_index, feature_index)
77 else {
78 return;
79 };
80 if let Some(event) = event::decode_event(func.to_lo(), &payload) {
81 emitter.emit(event);
82 }
83 }
84 });
85
86 Self {
87 endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
88 emitter,
89 _msg_listener: listener,
90 }
91 }
92}
93
94impl Feature for RgbEffectsFeature {}
95
96impl EmittingFeature<RgbEffectsEvent> for RgbEffectsFeature {
97 fn listen(&self) -> async_channel::Receiver<RgbEffectsEvent> {
98 self.emitter.create_receiver()
99 }
100}
101
102impl RgbEffectsFeature {
103 pub async fn get_device_info(&self) -> Result<RgbDeviceInfo, Hidpp20Error> {
105 let payload = self
106 .endpoint
107 .call(0, [ALL_CLUSTERS, ALL_EFFECTS, TYPE_GENERAL_INFO])
108 .await?
109 .extend_payload();
110 Ok(RgbDeviceInfo::from_payload(&payload))
111 }
112
113 pub async fn get_cluster_info(
116 &self,
117 cluster_index: u8,
118 ) -> Result<RgbClusterInfo, Hidpp20Error> {
119 let payload = self
120 .endpoint
121 .call(0, [cluster_index, ALL_EFFECTS, TYPE_GENERAL_INFO])
122 .await?
123 .extend_payload();
124 Ok(RgbClusterInfo::from_payload(&payload))
125 }
126
127 pub async fn get_effect_info(
130 &self,
131 cluster_index: u8,
132 cluster_effect_index: u8,
133 ) -> Result<RgbEffectInfo, Hidpp20Error> {
134 let payload = self
135 .endpoint
136 .call(0, [cluster_index, cluster_effect_index, TYPE_GENERAL_INFO])
137 .await?
138 .extend_payload();
139 Ok(RgbEffectInfo::from_payload(&payload))
140 }
141
142 pub async fn get_onboard_effect_info(
148 &self,
149 cluster_index: u8,
150 cluster_effect_index: u8,
151 slot: u8,
152 slot_info_type: SlotInfoType,
153 ) -> Result<[u8; ONBOARD_INFO_PARAM_COUNT], Hidpp20Error> {
154 let mut args = [0; 16];
155 args[..5].copy_from_slice(&[
156 cluster_index,
157 cluster_effect_index,
158 TYPE_ONBOARD_EFFECT,
159 slot,
160 slot_info_type.into(),
161 ]);
162 let payload = self.endpoint.call_long(0, args).await?.extend_payload();
163 let mut params = [0; ONBOARD_INFO_PARAM_COUNT];
164 params.copy_from_slice(&payload[3..3 + ONBOARD_INFO_PARAM_COUNT]);
165 Ok(params)
166 }
167
168 pub async fn set_rgb_cluster_effect(
175 &self,
176 cluster_index: u8,
177 cluster_effect_index: u8,
178 params: [u8; CLUSTER_EFFECT_PARAM_COUNT],
179 persistence: RgbPersistence,
180 power_mode: PowerModeTarget,
181 ) -> Result<(), Hidpp20Error> {
182 let mut args = [0; 16];
183 args[0] = cluster_index;
184 args[1] = cluster_effect_index;
185 args[2..2 + CLUSTER_EFFECT_PARAM_COUNT].copy_from_slice(¶ms);
186 args[12] = persistence.bits() | (u8::from(power_mode) << POWER_TARGET_SHIFT);
187 self.endpoint.call_long(1, args).await?;
188 Ok(())
189 }
190
191 pub async fn set_multi_led_cluster_pattern(
193 &self,
194 cluster_index: u8,
195 pattern: u8,
196 ) -> Result<(), Hidpp20Error> {
197 self.endpoint.call(2, [cluster_index, pattern, 0]).await?;
198 Ok(())
199 }
200
201 pub async fn get_nv_config(
203 &self,
204 capability: RgbNvCapabilities,
205 ) -> Result<RgbNvConfig, Hidpp20Error> {
206 let [cap_hi, cap_lo] = capability.bits().to_be_bytes();
207 let payload = self
208 .endpoint
209 .call(3, [GetOrSet::Get.into(), cap_hi, cap_lo])
210 .await?
211 .extend_payload();
212 Ok(RgbNvConfig {
213 capability: RgbNvCapabilities::from_bits_retain(be16(&payload, 1)),
214 state: payload[3],
215 param1: payload[4],
216 param2: payload[5],
217 })
218 }
219
220 pub async fn set_nv_config(
222 &self,
223 capability: RgbNvCapabilities,
224 state: u8,
225 param1: u8,
226 param2: u8,
227 ) -> Result<(), Hidpp20Error> {
228 let [cap_hi, cap_lo] = capability.bits().to_be_bytes();
229 let mut args = [0; 16];
230 args[..6].copy_from_slice(&[GetOrSet::Set.into(), cap_hi, cap_lo, state, param1, param2]);
231 self.endpoint.call_long(3, args).await?;
232 Ok(())
233 }
234
235 pub async fn get_led_bin_info(
239 &self,
240 cluster_index: u8,
241 led_bin_index: LedBinIndex,
242 backup: bool,
243 ) -> Result<[u8; LED_BIN_PARAM_COUNT], Hidpp20Error> {
244 let get_or_set = if backup {
245 GET_BACKUP
246 } else {
247 GetOrSet::Get.into()
248 };
249 let payload = self
250 .endpoint
251 .call(4, [get_or_set, cluster_index, led_bin_index.into()])
252 .await?
253 .extend_payload();
254 let mut params = [0; LED_BIN_PARAM_COUNT];
255 params.copy_from_slice(&payload[3..3 + LED_BIN_PARAM_COUNT]);
256 Ok(params)
257 }
258
259 pub async fn set_led_bin_info(
261 &self,
262 cluster_index: u8,
263 led_bin_index: LedBinIndex,
264 params: [u8; LED_BIN_PARAM_COUNT],
265 ) -> Result<(), Hidpp20Error> {
266 let mut args = [0; 16];
267 args[0] = GetOrSet::Set.into();
268 args[1] = cluster_index;
269 args[2] = led_bin_index.into();
270 args[3..3 + LED_BIN_PARAM_COUNT].copy_from_slice(¶ms);
271 self.endpoint.call_long(4, args).await?;
272 Ok(())
273 }
274
275 pub async fn get_sw_control(&self) -> Result<RgbSwControl, Hidpp20Error> {
277 let payload = self
278 .endpoint
279 .call(5, [GetOrSet::Get.into(), 0, 0])
280 .await?
281 .extend_payload();
282 Ok(RgbSwControl {
283 control: SwControlFlags::from_bits_retain(payload[1]),
284 events: EventsNotificationFlags::from_bits_retain(payload[2]),
285 })
286 }
287
288 pub async fn set_sw_control(
290 &self,
291 control: SwControlFlags,
292 events: EventsNotificationFlags,
293 ) -> Result<(), Hidpp20Error> {
294 self.endpoint
295 .call(5, [GetOrSet::Set.into(), control.bits(), events.bits()])
296 .await?;
297 Ok(())
298 }
299
300 pub async fn set_effect_sync_correction(
304 &self,
305 cluster_index: u8,
306 drift_value: i16,
307 ) -> Result<(), Hidpp20Error> {
308 let [drift_hi, drift_lo] = drift_value.to_be_bytes();
309 let mut args = [0; 16];
310 args[..4].copy_from_slice(&[cluster_index, 0, drift_hi, drift_lo]);
311 self.endpoint.call_long(6, args).await?;
312 Ok(())
313 }
314
315 pub async fn get_power_mode_config(&self) -> Result<RgbPowerModeConfig, Hidpp20Error> {
317 let payload = self
318 .endpoint
319 .call(7, [GetOrSet::Get.into(), 0, 0])
320 .await?
321 .extend_payload();
322 Ok(RgbPowerModeConfig::from_payload(&payload))
323 }
324
325 pub async fn set_power_mode_config(
327 &self,
328 config: RgbPowerModeConfig,
329 ) -> Result<(), Hidpp20Error> {
330 let [flags_hi, flags_lo] = config.flags.to_be_bytes();
331 let [psave_hi, psave_lo] = config.no_activity_timeout_to_power_save.to_be_bytes();
332 let [off_hi, off_lo] = config.no_activity_timeout_to_off.to_be_bytes();
333 let mut args = [0; 16];
334 args[..7].copy_from_slice(&[
335 GetOrSet::Set.into(),
336 flags_hi,
337 flags_lo,
338 psave_hi,
339 psave_lo,
340 off_hi,
341 off_lo,
342 ]);
343 self.endpoint.call_long(7, args).await?;
344 Ok(())
345 }
346
347 pub async fn get_power_mode(&self) -> Result<RgbPowerMode, Hidpp20Error> {
349 let payload = self
350 .endpoint
351 .call(8, [GetOrSet::Get.into(), 0, 0])
352 .await?
353 .extend_payload();
354 RgbPowerMode::try_from(payload[1]).map_err(|_| Hidpp20Error::UnsupportedResponse)
355 }
356
357 pub async fn set_power_mode(&self, mode: RgbPowerMode) -> Result<(), Hidpp20Error> {
360 self.endpoint
361 .call(8, [GetOrSet::Set.into(), mode.into(), 0])
362 .await?;
363 Ok(())
364 }
365
366 pub async fn shutdown(&self) -> Result<(), Hidpp20Error> {
370 self.endpoint.call(9, [0; 3]).await?;
371 Ok(())
372 }
373}