1use std::fmt;
5use std::future::Future;
6use std::sync::{Arc, RwLock, Weak};
7
8use hidpp::channel::HidppChannel;
9use thiserror::Error;
10
11use crate::backend::BackendError;
12use crate::reprog_controls::{self, ReprogControlsV4};
13use crate::thumbwheel::Thumbwheel;
14use crate::{ChannelRegistry, DeviceRoute, SharedChannel};
15
16pub type CaptureChannel = Arc<RwLock<Option<SharedChannel>>>;
19
20#[derive(Debug, Error)]
22pub enum GestureError {
23 #[error("HID transport error")]
25 Hid(#[from] BackendError),
26 #[error("no connected device matched the capture route")]
28 DeviceNotFound,
29 #[error("device at index {0:#04x} did not respond to HID++")]
31 DeviceUnreachable(u8),
32 #[error("HID++ protocol error: {0}")]
34 Hidpp(String),
35}
36
37#[derive(Clone, Copy)]
40pub(crate) struct ArmedReporting {
41 pub(crate) cid: u16,
42 pub(crate) original: reprog_controls::CidReporting,
43}
44
45pub(crate) struct ReprogRestore {
47 feature_index: u8,
48 controls: Vec<ArmedReporting>,
49}
50
51impl ReprogRestore {
52 pub(crate) fn new(feature_index: u8, controls: Vec<ArmedReporting>) -> Option<Self> {
53 (!controls.is_empty()).then_some(Self {
54 feature_index,
55 controls,
56 })
57 }
58}
59
60#[derive(Clone, Copy)]
61pub(crate) enum CaptureStop {
62 Shutdown,
64 ChannelChanged,
66}
67
68#[derive(Clone, Copy)]
70enum RetiredChannelPolicy {
71 ReplacementOnly,
74 CurrentAllowed,
77}
78
79#[must_use = "a pending restore must be retained until firmware ownership is released"]
81pub enum CaptureSessionOutcome {
82 Restored,
84 RestorePending(PendingCaptureRestore),
87}
88
89#[derive(Debug, Error)]
92#[error("{error}")]
93pub struct CaptureSessionFailure {
94 #[source]
95 error: GestureError,
96 pending_restore: Option<PendingCaptureRestore>,
97}
98
99impl CaptureSessionFailure {
100 pub(crate) fn clean(error: GestureError) -> Self {
101 Self {
102 error,
103 pending_restore: None,
104 }
105 }
106
107 pub(crate) fn with_pending(error: GestureError, pending: PendingCaptureRestore) -> Self {
108 Self {
109 error,
110 pending_restore: Some(pending),
111 }
112 }
113
114 #[must_use]
116 pub fn into_parts(self) -> (GestureError, Option<PendingCaptureRestore>) {
117 (self.error, self.pending_restore)
118 }
119}
120
121impl From<GestureError> for CaptureSessionFailure {
122 fn from(error: GestureError) -> Self {
123 Self::clean(error)
124 }
125}
126
127pub struct PendingCaptureRestore {
133 route: DeviceRoute,
134 retired_channel: Weak<HidppChannel>,
135 retired_policy: RetiredChannelPolicy,
136 reprog: Option<ReprogRestore>,
137 thumb_index: Option<u8>,
138}
139
140impl fmt::Debug for PendingCaptureRestore {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 f.debug_struct("PendingCaptureRestore")
143 .field("route", &self.route)
144 .field(
145 "reporting_count",
146 &self
147 .reprog
148 .as_ref()
149 .map_or(0, |reprog| reprog.controls.len()),
150 )
151 .field("has_thumbwheel", &self.thumb_index.is_some())
152 .finish_non_exhaustive()
153 }
154}
155
156impl PendingCaptureRestore {
157 pub(crate) fn new(
158 retired: &SharedChannel,
159 reprog: Option<ReprogRestore>,
160 thumb_index: Option<u8>,
161 ) -> Option<Self> {
162 if reprog.is_none() && thumb_index.is_none() {
163 return None;
164 }
165 Some(Self {
166 route: retired.route().clone(),
167 retired_channel: Arc::downgrade(retired.channel()),
168 retired_policy: RetiredChannelPolicy::ReplacementOnly,
169 reprog,
170 thumb_index,
171 })
172 }
173
174 pub async fn retry(self, registry: &ChannelRegistry) -> CaptureSessionOutcome {
180 let Some(current) = registry.lookup(&self.route) else {
181 return CaptureSessionOutcome::RestorePending(self);
182 };
183 if matches!(self.retired_policy, RetiredChannelPolicy::ReplacementOnly)
184 && self
185 .retired_channel
186 .upgrade()
187 .is_some_and(|retired| Arc::ptr_eq(current.channel(), &retired))
188 {
189 return CaptureSessionOutcome::RestorePending(self);
190 }
191 let restored = self.restore_on(¤t).await;
192 if restored && registry.is_current(¤t) {
193 CaptureSessionOutcome::Restored
194 } else {
195 CaptureSessionOutcome::RestorePending(self)
196 }
197 }
198
199 pub(crate) async fn restore_standalone(self, retired: &SharedChannel) -> CaptureSessionOutcome {
202 if self.restore_on(retired).await {
203 CaptureSessionOutcome::Restored
204 } else {
205 CaptureSessionOutcome::RestorePending(self)
206 }
207 }
208
209 pub(crate) fn allow_current_channel(mut self) -> Self {
212 self.retired_policy = RetiredChannelPolicy::CurrentAllowed;
213 self
214 }
215
216 async fn restore_on(&self, current: &SharedChannel) -> bool {
217 let channel = Arc::clone(current.channel());
218 let device_index = current.device_index();
219 let mut restored = true;
220 if let Some(reprog) = &self.reprog {
221 let controls =
222 ReprogControlsV4::new(channel.clone(), device_index, reprog.feature_index);
223 for &reporting in &reprog.controls {
224 restored &= restore_reporting(&controls, reporting, "captured control").await;
225 }
226 }
227 if let Some(feature_index) = self.thumb_index {
228 let thumbwheel = Thumbwheel::new(channel, device_index, feature_index);
229 restored &= restore_result(thumbwheel.undivert().await, "thumb wheel");
230 }
231 restored
232 }
233}
234
235pub(crate) async fn rollback_capture_start(
238 error: GestureError,
239 pending: Option<PendingCaptureRestore>,
240 retired: &SharedChannel,
241 registry: Option<&ChannelRegistry>,
242) -> CaptureSessionFailure {
243 let Some(pending) = pending else {
244 return CaptureSessionFailure::clean(error);
245 };
246 let pending = pending.allow_current_channel();
247 let outcome = match registry {
248 Some(registry) => pending.retry(registry).await,
249 None => pending.restore_standalone(retired).await,
250 };
251 match outcome {
252 CaptureSessionOutcome::Restored => CaptureSessionFailure::clean(error),
253 CaptureSessionOutcome::RestorePending(pending) => {
254 CaptureSessionFailure::with_pending(error, pending)
255 }
256 }
257}
258
259pub(crate) async fn restore_after_stop(
261 stop: CaptureStop,
262 pending: Option<PendingCaptureRestore>,
263 retired: &SharedChannel,
264 registry: Option<&ChannelRegistry>,
265) -> CaptureSessionOutcome {
266 let Some(pending) = pending else {
267 return CaptureSessionOutcome::Restored;
268 };
269 match stop {
270 CaptureStop::Shutdown => {
271 let pending = pending.allow_current_channel();
272 match registry {
273 Some(registry) => pending.retry(registry).await,
274 None => pending.restore_standalone(retired).await,
275 }
276 }
277 CaptureStop::ChannelChanged => match registry {
278 Some(registry) => pending.retry(registry).await,
279 None => CaptureSessionOutcome::RestorePending(pending),
280 },
281 }
282}
283
284pub(crate) fn stop_for_current_publication(
287 registry: Option<&ChannelRegistry>,
288 retired: &SharedChannel,
289) -> CaptureStop {
290 if registry.is_some_and(|registry| !registry.is_current(retired)) {
291 CaptureStop::ChannelChanged
292 } else {
293 CaptureStop::Shutdown
295 }
296}
297
298pub(crate) async fn wait_for_channel_change(
302 registry: Option<&ChannelRegistry>,
303 retired: &SharedChannel,
304) -> CaptureStop {
305 let Some(registry) = registry else {
306 return std::future::pending().await;
307 };
308 let mut changes = registry.subscribe();
309 loop {
310 if !registry.is_current(retired) {
311 return CaptureStop::ChannelChanged;
312 }
313 if changes.changed().await.is_err() {
314 return std::future::pending().await;
318 }
319 }
320}
321
322pub(crate) async fn drop_listener_after<T, R>(listener: T, teardown: impl Future<Output = R>) -> R {
324 let result = teardown.await;
325 drop(listener);
326 result
327}
328
329pub(crate) fn divert_change(
331 reporting: reprog_controls::CidReporting,
332 raw_xy: bool,
333) -> reprog_controls::CidReportingChange {
334 reprog_controls::CidReportingChange {
335 diverted: Some(true),
336 raw_xy: Some(raw_xy),
337 remap: reporting.remap,
338 ..Default::default()
339 }
340}
341
342pub(crate) async fn restore_reporting(
345 controls: &ReprogControlsV4,
346 reporting: ArmedReporting,
347 what: &str,
348) -> bool {
349 let result = controls
350 .set_cid_reporting_full(reporting.cid, undivert_change(reporting.original))
351 .await
352 .map(|_| ());
353 restore_result(result, what)
354}
355
356pub(crate) fn restore_result<E: fmt::Display>(result: Result<(), E>, what: &str) -> bool {
357 if let Err(error) = result {
358 tracing::warn!(%error, control = what, "failed to restore control mapping");
359 false
360 } else {
361 true
362 }
363}
364
365pub(crate) fn undivert_change(
367 reporting: reprog_controls::CidReporting,
368) -> reprog_controls::CidReportingChange {
369 reprog_controls::CidReportingChange {
370 diverted: Some(false),
371 raw_xy: Some(false),
372 remap: reporting.remap,
373 ..Default::default()
374 }
375}