mod accum;
mod arm;
use std::sync::{Arc, Mutex, PoisonError};
use hidpp::protocol::v20;
use openlogi_core::binding::{ButtonId, GestureDirection};
use tokio::sync::mpsc;
use tracing::info;
use crate::SharedChannel;
use crate::channel::route::DeviceRoute;
pub use super::capture::CaptureHost;
use super::capture::{ArmedCapture, Liveness, run_capture};
use accum::CaptureAccum;
pub(crate) use arm::enumerate_controls;
use arm::{ArmedControls, ArmedThumbwheel, arm_controls};
pub use super::capture_restore::{
CaptureChannelSlot, CaptureError, CaptureSessionFailure, CaptureSessionOutcome,
PendingCaptureRestore,
};
use crate::reprog_controls::{self, ReprogControlsV4};
use crate::thumbwheel::{self, WheelResolution};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CapturedInput {
Gesture(ButtonId, GestureDirection),
ButtonDown(ButtonId),
Scroll {
increments: i16,
resolution: WheelResolution,
},
ThumbwheelDirection {
positive_is_forward: bool,
},
ButtonUp(ButtonId),
ButtonPulse(ButtonId),
}
pub const DIVERTABLE_STANDARD_BUTTONS: [(u16, ButtonId); 9] = {
let [
back,
back_multiplatform,
back_multiplatform_alt,
back_generic,
] = reprog_controls::BACK_CIDS;
let [forward, forward_multiplatform] = reprog_controls::FORWARD_CIDS;
[
(0x0052, ButtonId::MiddleClick),
(back, ButtonId::Back),
(back_multiplatform, ButtonId::Back),
(back_multiplatform_alt, ButtonId::Back),
(back_generic, ButtonId::Back),
(forward, ButtonId::Forward),
(forward_multiplatform, ButtonId::Forward),
(0x005b, ButtonId::WheelTiltLeft),
(0x005d, ButtonId::WheelTiltRight),
]
};
pub const GESTURE_SOURCE_BUTTONS: [(u16, ButtonId); 2] = [
(reprog_controls::GESTURE_BUTTON_CID, ButtonId::GestureButton),
(reprog_controls::HAPTIC_PANEL_CID, ButtonId::HapticPanel),
];
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CaptureSpec {
pub capture_thumbwheel: bool,
pub divert_gesture_sources: Vec<u16>,
pub divert_gesture_buttons: Vec<(u16, ButtonId)>,
pub divert_buttons: Vec<(u16, ButtonId)>,
}
pub async fn run_capture_session(
route: DeviceRoute,
spec: CaptureSpec,
host: CaptureHost<'_>,
) -> Result<CaptureSessionOutcome, CaptureSessionFailure> {
let shared = host.channel_for(&route)?;
let armed = arm_controls(&shared, &spec, host.registry).await?;
if let Some(direction) = armed.thumbwheel_direction() {
let _ = host.sink.send(direction);
}
Ok(run_capture(shared, GestureCapture::new(armed), host).await)
}
struct GestureCapture {
armed: ArmedControls,
accum: Arc<Mutex<CaptureAccum>>,
}
impl GestureCapture {
fn new(armed: ArmedControls) -> Self {
Self {
armed,
accum: Arc::default(),
}
}
}
impl ArmedCapture for GestureCapture {
const NAME: &'static str = "control";
const LIVENESS: Liveness = Liveness::Watched;
fn log_active(&self, device_index: u8, wake_rearm: bool) {
let armed = &self.armed;
info!(
index = device_index,
gesture_sources = armed.gesture_cids.len(),
gesture_buttons = armed.gesture_button_cids.len(),
dpi_buttons = armed.dpi_cids.len(),
buttons = armed.button_cids.len(),
thumbwheel = armed.thumb.is_some(),
wake_rearm,
"control capture active"
);
}
fn report_handler(
&self,
device_index: u8,
sink: mpsc::UnboundedSender<CapturedInput>,
) -> impl Fn(&v20::Message) + Send + Sync + 'static {
let armed = &self.armed;
let accum = Arc::clone(&self.accum);
let reprog_index = armed.reprog.as_ref().map(ReprogControlsV4::feature_index);
let gesture_cids = armed.gesture_cids.clone();
let gesture_button_set = armed.gesture_button_cids.clone();
let thumb_index = armed
.thumb
.as_ref()
.map(|thumb| thumb.wheel.feature_index());
let thumb_resolution = armed
.thumb
.as_ref()
.map_or(WheelResolution::UNKNOWN, ArmedThumbwheel::resolution);
let dpi_set = armed.dpi_cids.clone();
let button_set = armed.button_cids.clone();
move |msg| {
if let Some(idx) = reprog_index
&& let Some(event) = reprog_controls::decode_event(msg, device_index, idx)
{
let mut acc = accum.lock().unwrap_or_else(PoisonError::into_inner);
acc.on_event(
event,
&gesture_cids,
&dpi_set,
&gesture_button_set,
&button_set,
&sink,
);
return;
}
if let Some(idx) = thumb_index
&& let Some(event) = thumbwheel::decode_event(msg, device_index, idx)
&& let Some(input) = thumbwheel_input(event, thumb_resolution)
{
let _ = sink.send(input);
}
}
}
fn reset_input_state(&self) {
*self.accum.lock().unwrap_or_else(PoisonError::into_inner) = CaptureAccum::default();
}
async fn rearm(&self) {
self.armed.rearm().await;
}
fn into_pending(self, retired: &SharedChannel) -> Option<PendingCaptureRestore> {
self.armed.into_pending(retired)
}
}
fn thumbwheel_input(
event: thumbwheel::ThumbwheelEvent,
resolution: WheelResolution,
) -> Option<CapturedInput> {
if event.rotation != 0 {
return Some(CapturedInput::Scroll {
increments: event.rotation,
resolution,
});
}
if event.rotation_status.is_rolling() {
return None;
}
event
.single_tap
.then_some(CapturedInput::ButtonPulse(ButtonId::Thumbwheel))
}
#[cfg(test)]
mod tests;