use super::ids::IdMap;
use super::props::take_pwstr;
use crate::mtp::{DeviceEvent, ObjectHandle};
use futures::channel::mpsc::UnboundedSender;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::{Arc, Mutex};
use windows::core::{implement, Ref, GUID};
use windows::Win32::Devices::PortableDevices::{
IPortableDeviceEventCallback, IPortableDeviceEventCallback_Impl, IPortableDeviceValues,
WPD_EVENT_DEVICE_RESET, WPD_EVENT_OBJECT_ADDED, WPD_EVENT_OBJECT_REMOVED,
WPD_EVENT_OBJECT_UPDATED, WPD_EVENT_PARAMETER_EVENT_ID, WPD_OBJECT_ID,
};
#[implement(IPortableDeviceEventCallback)]
pub(crate) struct WpdEventSink {
tx: UnboundedSender<DeviceEvent>,
ids: Arc<Mutex<IdMap>>,
}
impl WpdEventSink {
pub(crate) fn new(tx: UnboundedSender<DeviceEvent>, ids: Arc<Mutex<IdMap>>) -> Self {
Self { tx, ids }
}
fn dispatch(&self, params: &IPortableDeviceValues) {
let Ok(guid) = (unsafe { params.GetGuidValue(&WPD_EVENT_PARAMETER_EVENT_ID) }) else {
return; };
if let Some(event) = self.map_event(guid, params) {
let _ = self.tx.unbounded_send(event);
}
}
fn object_handle(&self, params: &IPortableDeviceValues) -> Option<ObjectHandle> {
let id = unsafe {
params
.GetStringValue(&WPD_OBJECT_ID)
.map(|p| take_pwstr(p))
.ok()
}?;
if id.is_empty() {
return None;
}
Some(self.ids.lock().expect("idmap poisoned").object(&id))
}
fn map_event(&self, guid: GUID, params: &IPortableDeviceValues) -> Option<DeviceEvent> {
if guid == WPD_EVENT_OBJECT_ADDED {
Some(DeviceEvent::ObjectAdded {
handle: self.object_handle(params)?,
})
} else if guid == WPD_EVENT_OBJECT_REMOVED {
Some(DeviceEvent::ObjectRemoved {
handle: self.object_handle(params)?,
})
} else if guid == WPD_EVENT_OBJECT_UPDATED {
Some(DeviceEvent::ObjectInfoChanged {
handle: self.object_handle(params)?,
})
} else if guid == WPD_EVENT_DEVICE_RESET {
Some(DeviceEvent::DeviceReset)
} else {
Some(DeviceEvent::Unknown {
#[allow(clippy::cast_possible_truncation)]
code: (guid.data1 & 0xFFFF) as u16,
params: [0, 0, 0],
})
}
}
}
impl IPortableDeviceEventCallback_Impl for WpdEventSink_Impl {
fn OnEvent(&self, peventparameters: Ref<IPortableDeviceValues>) -> windows::core::Result<()> {
let _ = catch_unwind(AssertUnwindSafe(|| {
if let Ok(params) = peventparameters.ok() {
self.dispatch(params);
}
}));
Ok(())
}
}