use std::sync::Arc;
use hidpp::{channel::HidppChannel, device::Device};
use openlogi_core::binding::ButtonId;
use tracing::{debug, warn};
use super::{CaptureSpec, CapturedInput};
use crate::reprog_controls::{self, ReprogControlsV4};
use crate::session::capture::open_device;
use crate::session::capture_restore::{
ArmedReporting, CaptureError, CaptureSessionFailure, PendingCaptureRestore, ReprogRestore,
ThumbwheelRestore, divert_change,
};
use crate::session::restore::rollback_start;
use crate::thumbwheel::{
self, ReportingMode, Thumbwheel, ThumbwheelInfo, WheelDirection, WheelResolution,
};
use crate::{ChannelRegistry, SharedChannel};
#[derive(Default)]
pub(super) struct ArmedControls {
pub(super) reprog: Option<ReprogControlsV4>,
pub(super) gesture_cids: Vec<u16>,
pub(super) gesture_button_cids: Vec<(u16, ButtonId)>,
pub(super) dpi_cids: Vec<u16>,
pub(super) button_cids: Vec<(u16, ButtonId)>,
reporting: Vec<ArmedReporting>,
pub(super) thumb: Option<ArmedThumbwheel>,
}
pub(super) struct ArmedThumbwheel {
pub(super) wheel: Thumbwheel,
info: Option<ThumbwheelInfo>,
restore: ThumbwheelRestore,
}
impl ArmedThumbwheel {
pub(super) fn resolution(&self) -> WheelResolution {
self.info
.map_or(WheelResolution::UNKNOWN, |info| info.resolution)
}
fn direction(&self) -> WheelDirection {
if self.info.is_some_and(|info| !info.positive_is_forward()) {
WheelDirection::Inverted
} else {
WheelDirection::Default
}
}
}
impl ArmedControls {
pub(super) fn thumbwheel_direction(&self) -> Option<CapturedInput> {
let positive_is_forward = self
.thumb
.as_ref()?
.info
.map(ThumbwheelInfo::positive_is_forward)?;
Some(CapturedInput::ThumbwheelDirection {
positive_is_forward,
})
}
pub(super) fn into_pending(self, retired: &SharedChannel) -> Option<PendingCaptureRestore> {
let Self {
reprog,
reporting,
thumb,
..
} = self;
let reprog =
reprog.and_then(|controls| ReprogRestore::new(controls.feature_index(), reporting));
PendingCaptureRestore::new(retired, reprog, thumb.as_ref().map(|thumb| thumb.restore))
}
pub(super) async fn rearm(&self) {
if let Some(rc) = self.reprog.as_ref() {
for &reporting in &self.reporting {
let raw_xy = self.gesture_cids.contains(&reporting.cid)
|| self
.gesture_button_cids
.iter()
.any(|&(cid, _)| cid == reporting.cid);
let change = divert_change(reporting.original, raw_xy);
if let Err(error) = rc.set_cid_reporting_full(reporting.cid, change).await {
warn!(
cid = format_args!("{:#06x}", reporting.cid),
?error,
"re-divert after wake failed"
);
}
}
}
if let Some(thumb) = self.thumb.as_ref()
&& let Err(error) = thumb.wheel.divert(thumb.direction()).await
{
warn!(?error, "thumb-wheel re-divert after wake failed");
}
}
}
pub(super) async fn arm_controls(
shared: &SharedChannel,
spec: &CaptureSpec,
registry: &ChannelRegistry,
) -> Result<ArmedControls, CaptureSessionFailure> {
let device = open_device(shared).await?;
let chan = shared.channel();
let slot = shared.device_index();
let mut armed = ArmedControls::default();
if let Err(error) = arm_controls_into(&device, chan, slot, spec, &mut armed).await {
let pending = armed.into_pending(shared);
return Err(rollback_start(error, pending, registry).await);
}
if armed.gesture_cids.is_empty()
&& armed.gesture_button_cids.is_empty()
&& armed.dpi_cids.is_empty()
&& armed.button_cids.is_empty()
&& armed.thumb.is_none()
{
debug!(slot, "no capturable controls — idle session");
}
Ok(armed)
}
pub(super) async fn arm_controls_into(
device: &Device,
chan: &Arc<HidppChannel>,
slot: u8,
spec: &CaptureSpec,
armed: &mut ArmedControls,
) -> Result<(), CaptureError> {
if let Some(info) = device
.root()
.get_feature(reprog_controls::FEATURE_ID)
.await?
{
let rc = ReprogControlsV4::new(Arc::clone(chan), slot, info.index);
let controls = enumerate_controls(&rc).await?;
armed.reprog = Some(rc.clone());
for &cid in &spec.divert_gesture_sources {
if controls
.iter()
.any(|c| c.cid == cid && c.is_divertable() && c.supports_raw_xy())
{
arm_reprog_control(&rc, cid, true, &mut armed.reporting).await?;
armed.gesture_cids.push(cid);
}
}
for &(cid, button) in &spec.divert_gesture_buttons {
if let Some(control) = controls.iter().find(|c| c.cid == cid)
&& control.is_divertable()
&& control.supports_raw_xy()
{
arm_reprog_control(&rc, cid, true, &mut armed.reporting).await?;
armed.gesture_button_cids.push((cid, button));
}
}
for &cid in &reprog_controls::DPI_MODE_SHIFT_CIDS {
let gesture_requested = spec
.divert_gesture_buttons
.iter()
.any(|&(gesture_cid, button)| gesture_cid == cid && button == ButtonId::DpiToggle);
if gesture_requested {
continue;
}
if controls.iter().any(|c| c.cid == cid && c.is_divertable()) {
arm_reprog_control(&rc, cid, false, &mut armed.reporting).await?;
armed.dpi_cids.push(cid);
}
}
for &(cid, button) in &spec.divert_buttons {
if armed.gesture_cids.contains(&cid)
|| armed
.gesture_button_cids
.iter()
.any(|&(gesture_cid, _)| gesture_cid == cid)
{
continue;
}
if controls.iter().any(|c| c.cid == cid && c.is_divertable()) {
arm_reprog_control(&rc, cid, false, &mut armed.reporting).await?;
armed.button_cids.push((cid, button));
}
}
}
if spec.capture_thumbwheel
&& let Some(info) = device.root().get_feature(thumbwheel::FEATURE_ID).await?
{
let tw = Thumbwheel::new(Arc::clone(chan), slot, info.index);
let wheel_info = match tw.get_info().await {
Ok(twinfo) => Some(twinfo),
Err(e) => {
warn!(error = ?e, "thumb wheel getInfo failed");
None
}
};
if wheel_info.is_some_and(|info| !info.supports_single_tap) {
debug!("thumb wheel reports no single tap — click not capturable");
}
let original = match tw.get_status().await {
Ok(status) => Some(status),
Err(e) => {
warn!(error = ?e, "thumb wheel getStatus failed; restore will use the default sign");
None
}
};
if original.is_some_and(|status| status.mode == ReportingMode::Diverted) {
debug!("thumb wheel was already diverted before arming");
}
armed.thumb = Some(ArmedThumbwheel {
restore: ThumbwheelRestore::new(tw.feature_index(), original),
wheel: tw,
info: wheel_info,
});
if let Some(thumb) = armed.thumb.as_ref() {
thumb.wheel.divert(thumb.direction()).await?;
}
}
Ok(())
}
async fn arm_reprog_control(
rc: &ReprogControlsV4,
cid: u16,
raw_xy: bool,
reporting: &mut Vec<ArmedReporting>,
) -> Result<(), CaptureError> {
let original = rc.get_cid_reporting(cid).await?;
if original.diverted {
debug!(cid, "control was already diverted before arming");
}
let change = divert_change(original, raw_xy);
reporting.push(ArmedReporting::pointer(cid, original));
rc.set_cid_reporting_full(cid, change).await?;
Ok(())
}
pub(crate) async fn enumerate_controls(
rc: &ReprogControlsV4,
) -> Result<Vec<reprog_controls::CtrlIdInfo>, CaptureError> {
let count = rc.get_count().await?;
let mut controls = Vec::with_capacity(usize::from(count));
for index in 0..count {
controls.push(rc.get_ctrl_id_info(index).await?);
}
Ok(controls)
}