From ec24fc62d5a59a1ae7af0536fb656a2261628b41 Mon Sep 17 00:00:00 2001 From: SARDONYX-sard <68905624+SARDONYX-sard@users.noreply.github.com> Date: Thu, 23 Jan 2025 16:50:11 +0900 Subject: [PATCH] fix: fix `isis_connected` getter --- .../windows/device_info/buffer/errors.rs | 18 +++++ .../device_info/{buffer.rs => buffer/mod.rs} | 36 +++------- .../{dev_prop.rs => device_property.rs} | 0 .../src/device/windows/device_info/mod.rs | 38 ++++------- .../bluetooth/src/device/windows/inspect.rs | 57 ---------------- .../src/device/windows/inspect/errors.rs | 11 ++++ .../src/device/windows/inspect/impls.rs | 56 ++++++++++++++++ .../src/device/windows/inspect/mod.rs | 65 +++++++++++++++++++ crates/bluetooth/src/device/windows/watch.rs | 24 +++---- cspell.jsonc | 2 + 10 files changed, 184 insertions(+), 123 deletions(-) create mode 100644 crates/bluetooth/src/device/windows/device_info/buffer/errors.rs rename crates/bluetooth/src/device/windows/device_info/{buffer.rs => buffer/mod.rs} (81%) rename crates/bluetooth/src/device/windows/device_info/{dev_prop.rs => device_property.rs} (100%) delete mode 100644 crates/bluetooth/src/device/windows/inspect.rs create mode 100644 crates/bluetooth/src/device/windows/inspect/errors.rs create mode 100644 crates/bluetooth/src/device/windows/inspect/impls.rs create mode 100644 crates/bluetooth/src/device/windows/inspect/mod.rs diff --git a/crates/bluetooth/src/device/windows/device_info/buffer/errors.rs b/crates/bluetooth/src/device/windows/device_info/buffer/errors.rs new file mode 100644 index 0000000..4f99f68 --- /dev/null +++ b/crates/bluetooth/src/device/windows/device_info/buffer/errors.rs @@ -0,0 +1,18 @@ +use snafu::Snafu; +use windows::core::Error; + +use crate::device::windows::device_info::device_property::DevPropType; + +/// Custom error type using snafu +#[derive(Debug, Snafu)] +pub enum DevicePropertyError { + #[snafu(display("Failed to retrieve device property: {}", source))] + DevicePropertyError { source: Error }, + + #[snafu(display( + "Expected device property type {}, but got {}", + DevPropType::from_u32(*expected).map_or("Unknown", |t|t.as_str()), + DevPropType::from_u32(*actual).map_or("Unknown", |t|t.as_str()), + ))] + TypeError { actual: u32, expected: u32 }, +} diff --git a/crates/bluetooth/src/device/windows/device_info/buffer.rs b/crates/bluetooth/src/device/windows/device_info/buffer/mod.rs similarity index 81% rename from crates/bluetooth/src/device/windows/device_info/buffer.rs rename to crates/bluetooth/src/device/windows/device_info/buffer/mod.rs index 4d45e7b..e1b2ab1 100644 --- a/crates/bluetooth/src/device/windows/device_info/buffer.rs +++ b/crates/bluetooth/src/device/windows/device_info/buffer/mod.rs @@ -3,22 +3,20 @@ // ref // - https://github.com/kevinmehall/nusb/blob/v0.1.12/src/platform/windows_winusb/cfgmgr32.rs +mod errors; + use chrono::{DateTime, Utc}; -use snafu::Snafu; -use windows::{ - core::Error, - Win32::{ - Devices::Properties::{ - DEVPROPTYPE, DEVPROP_TYPE_BOOLEAN, DEVPROP_TYPE_BYTE, DEVPROP_TYPE_DOUBLE, - DEVPROP_TYPE_FILETIME, DEVPROP_TYPE_FLOAT, DEVPROP_TYPE_INT16, DEVPROP_TYPE_INT32, - DEVPROP_TYPE_INT64, DEVPROP_TYPE_SBYTE, DEVPROP_TYPE_STRING, DEVPROP_TYPE_UINT16, - DEVPROP_TYPE_UINT32, DEVPROP_TYPE_UINT64, - }, - Foundation::FILETIME, +use windows::Win32::{ + Devices::Properties::{ + DEVPROPTYPE, DEVPROP_TYPE_BOOLEAN, DEVPROP_TYPE_BYTE, DEVPROP_TYPE_DOUBLE, + DEVPROP_TYPE_FILETIME, DEVPROP_TYPE_FLOAT, DEVPROP_TYPE_INT16, DEVPROP_TYPE_INT32, + DEVPROP_TYPE_INT64, DEVPROP_TYPE_SBYTE, DEVPROP_TYPE_STRING, DEVPROP_TYPE_UINT16, + DEVPROP_TYPE_UINT32, DEVPROP_TYPE_UINT64, }, + Foundation::FILETIME, }; -use super::dev_prop::DevPropType; +pub use errors::DevicePropertyError; pub trait DeviceProperty: Sized { type Buffer; @@ -31,20 +29,6 @@ pub trait DeviceProperty: Sized { ) -> Result; } -/// Custom error type using snafu -#[derive(Debug, Snafu)] -pub enum DevicePropertyError { - #[snafu(display("Failed to retrieve device property: {}", source))] - DevicePropertyError { source: Error }, - - #[snafu(display( - "Expected device property type {}, but got {}", - DevPropType::from_u32(*expected).map_or("Unknown", |t|t.as_str()), - DevPropType::from_u32(*actual).map_or("Unknown", |t|t.as_str()), - ))] - TypeError { actual: u32, expected: u32 }, -} - // Add similar implementations for other types: macro_rules! impl_device_property { ($type:ty, $buffer:ty, $($prop:ident),+) => { diff --git a/crates/bluetooth/src/device/windows/device_info/dev_prop.rs b/crates/bluetooth/src/device/windows/device_info/device_property.rs similarity index 100% rename from crates/bluetooth/src/device/windows/device_info/dev_prop.rs rename to crates/bluetooth/src/device/windows/device_info/device_property.rs diff --git a/crates/bluetooth/src/device/windows/device_info/mod.rs b/crates/bluetooth/src/device/windows/device_info/mod.rs index 22c5ad5..bb69cf9 100644 --- a/crates/bluetooth/src/device/windows/device_info/mod.rs +++ b/crates/bluetooth/src/device/windows/device_info/mod.rs @@ -3,8 +3,8 @@ // - https://stackoverflow.com/questions/71736070/how-to-get-bluetooth-device-battery-percentage-using-powershell-on-windows mod buffer; -mod dev_prop; mod device_instance; +mod device_property; use device_instance::DeviceInstance; use snafu::Snafu; @@ -108,31 +108,19 @@ impl BluetoothDeviceInfo { /// - is_connected /// - last_used /// - last_updated - pub(crate) fn update_info(&mut self) -> Result<(), BluetoothDeviceInfoError> { - let device = DeviceInstance::new(self.device_instance); - - self.battery_level = device.get_device_property(&DEVPKEY_Bluetooth_Battery)?; - self.last_updated = LocalTime::now(); - - // NOTE: `is_connected` & `last_used` must be taken by device_search to get a decent value, so the information is merged. - let sys_device = { - let mut devices = match super::device_searcher::get_bluetooth_devices() { - Ok(devices) => devices, - Err(err) => { - tracing::error!("{err}"); - return Ok(()); - } - }; - devices.remove(&self.address) + pub(crate) fn update_info( + &mut self, + is_connected: bool, + ) -> Result<(), BluetoothDeviceInfoError> { + self.battery_level = { + let device = DeviceInstance::new(self.device_instance); + device.get_device_property(&DEVPKEY_Bluetooth_Battery)? }; - // NOTE: `is_connected` & `last_used` must be taken by device_search to get a decent value, so the information is merged. - self.is_connected = sys_device - .as_ref() - .map(|device| device.is_connected) - .unwrap_or_default(); - self.last_used = sys_device - .map(|device| device.last_used) - .unwrap_or_default(); + self.is_connected = is_connected; + if is_connected { + self.last_used = LocalTime::now(); + } + self.last_updated = LocalTime::now(); Ok(()) } diff --git a/crates/bluetooth/src/device/windows/inspect.rs b/crates/bluetooth/src/device/windows/inspect.rs deleted file mode 100644 index c1ba292..0000000 --- a/crates/bluetooth/src/device/windows/inspect.rs +++ /dev/null @@ -1,57 +0,0 @@ -use windows::core::{IInspectable, Interface as _, HSTRING}; -use windows::Foundation::Collections::IKeyValuePair; -use windows::Foundation::IReference; - -use crate::device::device_info::LocalTime; - -/// print property key and value. -/// -/// # Errors -/// If failed to type cast -pub fn reveal_value(prop: IKeyValuePair) -> windows::core::Result<()> { - let key = prop.Key()?; - let value = prop.Value()?; - - match value.GetRuntimeClassName()?.to_string().as_str() { - "Windows.Foundation.IReference`1" => { - let val: bool = value.cast::>()?.Value()?; - println!("{} = {} (Boolean)", key, val); - } - "Windows.Foundation.IReference`1" => { - let val: HSTRING = value.cast::>()?.Value()?; - println!("{} = {} (String)", key, val); - } - "Windows.Foundation.IReference`1" => { - let val: u8 = value.cast::>()?.Value()?; - println!("{} = {} (UInt8)", key, val); - } - "Windows.Foundation.IReference`1" => { - let val = value - .cast::>()? - .Value()?; - - let utc_time = windows_datetime_to_chrono(val.UniversalTime); - println!("{} = {:?} (DateTime)", key, LocalTime::from_utc(&utc_time)); - } - unknown => { - println!("{key} = "); - } - } - - Ok(()) -} - -fn windows_datetime_to_chrono(universal_time: i64) -> chrono::DateTime { - use chrono::TimeZone as _; - // Windows FILETIME epoch (1601-01-01) to Unix epoch (1970-01-01) in 100ns units - const EPOCH_DIFFERENCE_100NS: i64 = 11_644_473_600 * 10_000_000; - // Adjust to Unix epoch - let unix_time_100ns = universal_time - EPOCH_DIFFERENCE_100NS; - // Convert 100ns to seconds and nanoseconds - let seconds = unix_time_100ns / 10_000_000; - let nanoseconds = (unix_time_100ns % 10_000_000) * 100; - // Create chrono::DateTime - chrono::Utc - .timestamp_opt(seconds, nanoseconds as u32) - .unwrap() -} diff --git a/crates/bluetooth/src/device/windows/inspect/errors.rs b/crates/bluetooth/src/device/windows/inspect/errors.rs new file mode 100644 index 0000000..61e7d3d --- /dev/null +++ b/crates/bluetooth/src/device/windows/inspect/errors.rs @@ -0,0 +1,11 @@ +#[derive(Debug, snafu::Snafu)] +pub enum RevealError { + /// Failed to cast key '{key}' to the expected type '{expected_type}' + TypeCastError { key: String, expected_type: String }, + /// Unknown type for key '{key}': {unknown_type} + UnknownTypeError { key: String, unknown_type: String }, + + #[snafu(transparent)] + #[cfg(target_os = "windows")] + Error { source: windows::core::Error }, +} diff --git a/crates/bluetooth/src/device/windows/inspect/impls.rs b/crates/bluetooth/src/device/windows/inspect/impls.rs new file mode 100644 index 0000000..2a2ba49 --- /dev/null +++ b/crates/bluetooth/src/device/windows/inspect/impls.rs @@ -0,0 +1,56 @@ +use windows::core::{IInspectable, Interface as _, HSTRING}; +use windows::Foundation::IReference; + +use crate::device::device_info::LocalTime; + +pub trait RevealValue: Sized { + /// Reveals the value from an IInspectable. + /// + /// # Errors + /// Returns an error if the value cannot be cast to the expected type. + fn reveal(value: &IInspectable) -> windows::core::Result; +} + +impl RevealValue for bool { + fn reveal(value: &IInspectable) -> windows::core::Result { + value.cast::>()?.Value() + } +} + +impl RevealValue for String { + fn reveal(value: &IInspectable) -> windows::core::Result { + Ok(value.cast::>()?.Value()?.to_string()) + } +} + +impl RevealValue for u8 { + fn reveal(value: &IInspectable) -> windows::core::Result { + value.cast::>()?.Value() + } +} + +impl RevealValue for LocalTime { + fn reveal(value: &IInspectable) -> windows::core::Result { + let val = value + .cast::>()? + .Value()?; + let utc_time = windows_datetime_to_chrono(val.UniversalTime); + Ok(Self::from_utc(&utc_time)) + } +} + +fn windows_datetime_to_chrono(universal_time: i64) -> chrono::DateTime { + use chrono::TimeZone as _; + + // Windows FILETIME epoch (1601-01-01) to Unix epoch (1970-01-01) in 100ns units + const EPOCH_DIFFERENCE_100NS: i64 = 11_644_473_600 * 10_000_000; + // Adjust to Unix epoch + let unix_time_100ns = universal_time - EPOCH_DIFFERENCE_100NS; + // Convert 100ns to seconds and nanoseconds + let seconds = unix_time_100ns / 10_000_000; + let nanoseconds = (unix_time_100ns % 10_000_000) * 100; + // Create chrono::DateTime + chrono::Utc + .timestamp_opt(seconds, nanoseconds as u32) + .unwrap() +} diff --git a/crates/bluetooth/src/device/windows/inspect/mod.rs b/crates/bluetooth/src/device/windows/inspect/mod.rs new file mode 100644 index 0000000..28c4f00 --- /dev/null +++ b/crates/bluetooth/src/device/windows/inspect/mod.rs @@ -0,0 +1,65 @@ +mod errors; +mod impls; + +use windows::core::{IInspectable, HSTRING}; +use windows::Foundation::Collections::IKeyValuePair; + +pub use errors::RevealError; +pub use impls::RevealValue; + +/// print property key and value. +/// +/// # Errors +/// If failed to type cast +pub fn reveal_value(prop: IKeyValuePair) -> Result +where + T: RevealValue, +{ + let key = prop.Key()?; + let value = prop.Value()?; + + let runtime_class_name = value.GetRuntimeClassName()?.to_string(); + match runtime_class_name.as_str() { + "Windows.Foundation.IReference`1" => T::reveal(&value).map_or_else( + |_| { + Err(RevealError::TypeCastError { + key: key.to_string(), + expected_type: "Boolean".to_string(), + }) + }, + Ok, + ), + "Windows.Foundation.IReference`1" => T::reveal(&value).map_or_else( + |_| { + Err(RevealError::TypeCastError { + key: key.to_string(), + expected_type: "String".to_string(), + }) + }, + Ok, + ), + "Windows.Foundation.IReference`1" => T::reveal(&value).map_or_else( + |_| { + Err(RevealError::TypeCastError { + key: key.to_string(), + expected_type: "UInt8".to_string(), + }) + }, + Ok, + ), + "Windows.Foundation.IReference`1" => T::reveal(&value) + .map_or_else( + |_| { + Err(RevealError::TypeCastError { + key: key.to_string(), + expected_type: "DateTime".to_string(), + }) + }, + Ok, + ), + unknown => Err(RevealError::UnknownTypeError { + key: key.to_string(), + unknown_type: unknown.to_string(), + }), + } +} diff --git a/crates/bluetooth/src/device/windows/watch.rs b/crates/bluetooth/src/device/windows/watch.rs index 7d90e6b..0e8a0e3 100644 --- a/crates/bluetooth/src/device/windows/watch.rs +++ b/crates/bluetooth/src/device/windows/watch.rs @@ -9,6 +9,7 @@ use windows::Foundation::TypedEventHandler; use super::address_parser::id_to_address; use super::device_info::get_bluetooth_devices; +use super::inspect::RevealValue as _; use crate::device::device_info::Devices; use crate::BluetoothDeviceInfo; @@ -97,21 +98,14 @@ impl Watcher { match DEVICES.get_mut(&address) { Some(mut dev) => { - // for prop in &map { - // dbg!(address); - // type_to_value(prop)?; - // } - - // Not use this pattern - // ``` - // let map = device.Properties()?; - // let is_connected = map.HasKey(&HSTRING::from(IS_CONNECTED)).unwrap_or_default(); - // ``` - // Why? - // There were times when the information from props was unreliable. - // The information is obtained when connected, but for some reason this value may not be obtained when not connected. - - match dev.value_mut().update_info() { + let map = device.Properties()?; + + let is_connected = map + .Lookup(&HSTRING::from(IS_CONNECTED)) + .map(|inspect| bool::reveal(&inspect).unwrap_or_default()) + .unwrap_or_default(); + + match dev.value_mut().update_info(is_connected) { Ok(()) => update_fn(dev.value()), Err(err) => tracing::error!("{err}"), } diff --git a/cspell.jsonc b/cspell.jsonc index 570b367..77d0289 100644 --- a/cspell.jsonc +++ b/cspell.jsonc @@ -15,6 +15,8 @@ "HKEY", "HRESULT", "HSTRING", + "impls", + "Inspectable", "LOCALMFG", "PCSTR", "PCWSTR",