use std::fmt;
use std::future::Future;
use std::sync::{Arc, RwLock};
use hidpp::protocol::v20::Hidpp20Error;
use thiserror::Error;
use super::restore::{PendingRestore, RestoreOutcome, RestorePlan, SessionFailure};
use crate::backend::BackendError;
use crate::reprog_controls::{self, ReprogControlsV4};
use crate::thumbwheel::Thumbwheel;
use crate::{ChannelRegistry, IoSuspended, SharedChannel};
pub type CaptureChannelSlot = Arc<RwLock<Option<SharedChannel>>>;
#[derive(Debug, Error)]
pub enum CaptureError {
#[error("HID transport error")]
Hid(#[from] BackendError),
#[error("no connected device matched the capture route")]
DeviceNotFound,
#[error("device at index {0:#04x} did not respond to HID++")]
DeviceUnreachable(u8),
#[error("HID++ protocol error: {0}")]
Hidpp(String),
}
impl From<Hidpp20Error> for CaptureError {
fn from(error: Hidpp20Error) -> Self {
Self::Hidpp(format!("{error:?}"))
}
}
impl From<IoSuspended> for CaptureError {
fn from(error: IoSuspended) -> Self {
Self::Hid(error.into())
}
}
#[derive(Clone, Copy)]
pub(crate) struct ArmedReporting {
pub(crate) cid: u16,
pub(crate) original: reprog_controls::CidReporting,
}
pub(crate) struct ReprogRestore {
feature_index: u8,
controls: Vec<ArmedReporting>,
}
impl ReprogRestore {
pub(crate) fn new(feature_index: u8, controls: Vec<ArmedReporting>) -> Option<Self> {
(!controls.is_empty()).then_some(Self {
feature_index,
controls,
})
}
}
#[derive(Clone, Copy)]
pub(crate) enum CaptureStop {
Shutdown,
ChannelChanged,
}
pub struct CaptureRestorePlan {
reprog: Option<ReprogRestore>,
thumb_index: Option<u8>,
}
impl fmt::Debug for CaptureRestorePlan {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CaptureRestorePlan")
.field(
"reporting_count",
&self
.reprog
.as_ref()
.map_or(0, |reprog| reprog.controls.len()),
)
.field("has_thumbwheel", &self.thumb_index.is_some())
.finish()
}
}
impl RestorePlan for CaptureRestorePlan {
async fn restore_on(&self, current: &SharedChannel) -> bool {
let channel = Arc::clone(current.channel());
let device_index = current.device_index();
let mut restored = true;
if let Some(reprog) = &self.reprog {
let controls =
ReprogControlsV4::new(channel.clone(), device_index, reprog.feature_index);
for &reporting in &reprog.controls {
restored &= restore_reporting(&controls, reporting, "captured control").await;
}
}
if let Some(feature_index) = self.thumb_index {
let thumbwheel = Thumbwheel::new(channel, device_index, feature_index);
restored &= restore_result(thumbwheel.undivert().await, "thumb wheel");
}
restored
}
}
pub type PendingCaptureRestore = PendingRestore<CaptureRestorePlan>;
pub type CaptureSessionOutcome = RestoreOutcome<CaptureRestorePlan>;
pub type CaptureSessionFailure = SessionFailure<CaptureError, CaptureRestorePlan>;
impl PendingCaptureRestore {
pub(crate) fn new(
retired: &SharedChannel,
reprog: Option<ReprogRestore>,
thumb_index: Option<u8>,
) -> Option<Self> {
if reprog.is_none() && thumb_index.is_none() {
return None;
}
Some(Self::owing(
retired,
CaptureRestorePlan {
reprog,
thumb_index,
},
))
}
}
pub(crate) async fn restore_after_stop(
stop: CaptureStop,
pending: Option<PendingCaptureRestore>,
registry: &ChannelRegistry,
) -> CaptureSessionOutcome {
let Some(pending) = pending else {
return CaptureSessionOutcome::Restored;
};
match stop {
CaptureStop::Shutdown => pending.allow_current_channel().retry(registry).await,
CaptureStop::ChannelChanged => pending.retry(registry).await,
}
}
pub(crate) fn stop_for_current_publication(
registry: &ChannelRegistry,
retired: &SharedChannel,
) -> CaptureStop {
if registry.is_current(retired) {
CaptureStop::Shutdown
} else {
CaptureStop::ChannelChanged
}
}
pub(crate) async fn wait_for_channel_change(
registry: &ChannelRegistry,
retired: &SharedChannel,
) -> CaptureStop {
let mut changes = registry.subscribe();
loop {
if !registry.is_current(retired) {
return CaptureStop::ChannelChanged;
}
if changes.changed().await.is_err() {
return std::future::pending().await;
}
}
}
pub(crate) async fn drop_listener_after<T, R>(listener: T, teardown: impl Future<Output = R>) -> R {
let result = teardown.await;
drop(listener);
result
}
pub(crate) fn divert_change(
reporting: reprog_controls::CidReporting,
raw_xy: bool,
) -> reprog_controls::CidReportingChange {
reprog_controls::CidReportingChange {
diverted: Some(true),
raw_xy: Some(raw_xy),
remap: reporting.remap,
..Default::default()
}
}
pub(crate) async fn restore_reporting(
controls: &ReprogControlsV4,
reporting: ArmedReporting,
what: &str,
) -> bool {
let result = controls
.set_cid_reporting_full(reporting.cid, undivert_change(reporting.original))
.await
.map(|_| ());
restore_result(result, what)
}
pub(crate) fn restore_result<E: fmt::Display>(result: Result<(), E>, what: &str) -> bool {
if let Err(error) = result {
tracing::warn!(%error, control = what, "failed to restore control mapping");
false
} else {
true
}
}
pub(crate) fn undivert_change(
reporting: reprog_controls::CidReporting,
) -> reprog_controls::CidReportingChange {
reprog_controls::CidReportingChange {
diverted: Some(false),
raw_xy: Some(false),
remap: reporting.remap,
..Default::default()
}
}