Skip to content

Commit

Permalink
Merge pull request #29 from SARDONYX-sard/feature/fix-is_connected-ge…
Browse files Browse the repository at this point in the history
…tter

fix: fix `is_connected` getter
  • Loading branch information
SARDONYX-sard authored Jan 23, 2025
2 parents 6f9f508 + ec24fc6 commit 8a36bf8
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 123 deletions.
18 changes: 18 additions & 0 deletions crates/bluetooth/src/device/windows/device_info/buffer/errors.rs
Original file line number Diff line number Diff line change
@@ -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 },
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,20 +29,6 @@ pub trait DeviceProperty: Sized {
) -> Result<Self, DevicePropertyError>;
}

/// 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),+) => {
Expand Down
38 changes: 13 additions & 25 deletions crates/bluetooth/src/device/windows/device_info/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(())
}
Expand Down
57 changes: 0 additions & 57 deletions crates/bluetooth/src/device/windows/inspect.rs

This file was deleted.

11 changes: 11 additions & 0 deletions crates/bluetooth/src/device/windows/inspect/errors.rs
Original file line number Diff line number Diff line change
@@ -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 },
}
56 changes: 56 additions & 0 deletions crates/bluetooth/src/device/windows/inspect/impls.rs
Original file line number Diff line number Diff line change
@@ -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<Self>;
}

impl RevealValue for bool {
fn reveal(value: &IInspectable) -> windows::core::Result<Self> {
value.cast::<IReference<Self>>()?.Value()
}
}

impl RevealValue for String {
fn reveal(value: &IInspectable) -> windows::core::Result<Self> {
Ok(value.cast::<IReference<HSTRING>>()?.Value()?.to_string())
}
}

impl RevealValue for u8 {
fn reveal(value: &IInspectable) -> windows::core::Result<Self> {
value.cast::<IReference<Self>>()?.Value()
}
}

impl RevealValue for LocalTime {
fn reveal(value: &IInspectable) -> windows::core::Result<Self> {
let val = value
.cast::<IReference<windows::Foundation::DateTime>>()?
.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<chrono::Utc> {
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()
}
65 changes: 65 additions & 0 deletions crates/bluetooth/src/device/windows/inspect/mod.rs
Original file line number Diff line number Diff line change
@@ -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<T>(prop: IKeyValuePair<HSTRING, IInspectable>) -> Result<T, RevealError>
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<Boolean>" => T::reveal(&value).map_or_else(
|_| {
Err(RevealError::TypeCastError {
key: key.to_string(),
expected_type: "Boolean".to_string(),
})
},
Ok,
),
"Windows.Foundation.IReference`1<String>" => T::reveal(&value).map_or_else(
|_| {
Err(RevealError::TypeCastError {
key: key.to_string(),
expected_type: "String".to_string(),
})
},
Ok,
),
"Windows.Foundation.IReference`1<UInt8>" => T::reveal(&value).map_or_else(
|_| {
Err(RevealError::TypeCastError {
key: key.to_string(),
expected_type: "UInt8".to_string(),
})
},
Ok,
),
"Windows.Foundation.IReference`1<Windows.Foundation.DateTime>" => 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(),
}),
}
}
24 changes: 9 additions & 15 deletions crates/bluetooth/src/device/windows/watch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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}"),
}
Expand Down
2 changes: 2 additions & 0 deletions cspell.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"HKEY",
"HRESULT",
"HSTRING",
"impls",
"Inspectable",
"LOCALMFG",
"PCSTR",
"PCWSTR",
Expand Down

0 comments on commit 8a36bf8

Please sign in to comment.