diff --git a/crates/muninn-daemon/src/main.rs b/crates/muninn-daemon/src/main.rs index 5cf089f..de2d77a 100644 --- a/crates/muninn-daemon/src/main.rs +++ b/crates/muninn-daemon/src/main.rs @@ -2820,6 +2820,108 @@ fn windows_ps_move_bluetooth_addresses(handle: *mut std::ffi::c_void) -> Option< )) } +#[cfg(windows)] +fn windows_ps_move_paired_host_address(path: &str) -> Option { + if let Some(address) = windows_ps_move_paired_host_address_from_path(path) { + return Some(address); + } + let key = windows_ps_move_physical_key(path)?; + windows_ps_move_pairing_collection_paired_host_address(&key) +} + +#[cfg(windows)] +fn windows_ps_move_paired_host_address_from_path(path: &str) -> Option { + use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; + use windows_sys::Win32::Storage::FileSystem::{ + CreateFileW, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, + }; + + let wide = wide_null(path); + let handle = unsafe { + CreateFileW( + wide.as_ptr(), + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + std::ptr::null_mut(), + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + std::ptr::null_mut(), + ) + }; + if handle == INVALID_HANDLE_VALUE { + return None; + } + let paired_host = windows_ps_move_bluetooth_addresses(handle).map(|addresses| addresses.1); + unsafe { windows_sys::Win32::Foundation::CloseHandle(handle) }; + paired_host +} + +#[cfg(windows)] +fn windows_ps_move_pairing_collection_paired_host_address(physical_key: &str) -> Option { + use windows_sys::Win32::Devices::DeviceAndDriverInstallation::{ + DIGCF_DEVICEINTERFACE, DIGCF_PRESENT, SP_DEVICE_INTERFACE_DATA, + SetupDiDestroyDeviceInfoList, SetupDiEnumDeviceInterfaces, SetupDiGetClassDevsW, + }; + use windows_sys::Win32::Devices::HumanInterfaceDevice::HidD_GetHidGuid; + use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; + + let mut hid_guid = unsafe { std::mem::zeroed() }; + unsafe { HidD_GetHidGuid(&mut hid_guid) }; + let info_set = unsafe { + SetupDiGetClassDevsW( + &hid_guid, + std::ptr::null(), + std::ptr::null_mut(), + DIGCF_PRESENT | DIGCF_DEVICEINTERFACE, + ) + }; + if info_set == INVALID_HANDLE_VALUE as isize { + return None; + } + + let mut index = 0; + let mut paired_host = None; + loop { + let mut interface_data = SP_DEVICE_INTERFACE_DATA { + cbSize: std::mem::size_of::() as u32, + InterfaceClassGuid: unsafe { std::mem::zeroed() }, + Flags: 0, + Reserved: 0, + }; + let ok = unsafe { + SetupDiEnumDeviceInterfaces( + info_set, + std::ptr::null_mut(), + &hid_guid, + index, + &mut interface_data, + ) + }; + if ok == 0 { + break; + } + index += 1; + + let Some(path) = (unsafe { windows_hid_interface_path(info_set, &mut interface_data) }) + else { + continue; + }; + if !path.to_ascii_lowercase().contains("&col02#") { + continue; + } + if windows_ps_move_physical_key(&path).as_deref() != Some(physical_key) { + continue; + } + paired_host = windows_ps_move_paired_host_address_from_path(&path); + if paired_host.is_some() { + break; + } + } + + unsafe { SetupDiDestroyDeviceInfoList(info_set) }; + paired_host +} + #[cfg(windows)] fn windows_claim_ps_move_host(host: &[u8; 6]) -> Result { use windows_sys::Win32::Devices::DeviceAndDriverInstallation::{ @@ -3889,17 +3991,18 @@ fn current_move_identity_records_from_sources( sources: &[MoveStateSource], ) -> Result> { let observed_at = timestamp()?; - let bluetooth_host_address = move_host_address_for_claim(options).unwrap_or_default(); let mut identities = sources .iter() .map(|source| { let (state, detail) = move_identity_source_state_and_detail(&source.hidraw_path); + let bluetooth_host_address = + move_identity_bluetooth_host_address(options, &source.hidraw_path); MuninnMoveIdentityRecord { identity_id: format!("{}:{}:move-identity", options.host_id, source.move_id), host_id: options.host_id.clone(), move_id: source.move_id.clone(), source_path: source.hidraw_path.clone(), - bluetooth_host_address: bluetooth_host_address.clone(), + bluetooth_host_address, state, detail, observed_at: observed_at.clone(), @@ -3919,6 +4022,25 @@ fn current_move_identity_records_from_sources( Ok(identities) } +fn move_identity_bluetooth_host_address(options: &Options, source_path: &str) -> String { + move_host_address_for_claim(options) + .or_else(|| paired_bluetooth_host_address_for_source(source_path)) + .unwrap_or_default() +} + +#[cfg(windows)] +fn paired_bluetooth_host_address_for_source(source_path: &str) -> Option { + let path = source_path + .strip_prefix(WINDOWS_PS_MOVE_SOURCE_PREFIX) + .unwrap_or(source_path); + windows_ps_move_paired_host_address(path) +} + +#[cfg(not(windows))] +fn paired_bluetooth_host_address_for_source(_source_path: &str) -> Option { + None +} + #[cfg(unix)] fn current_bluetooth_move_identity_records( options: &Options, @@ -5220,6 +5342,33 @@ MODALIAS=input:b0005v054Cp03D5e0220 let _ = fs::remove_file(store_path); } + #[test] + fn current_move_identity_uses_configured_bluetooth_host() { + let options = Options::parse( + [ + "move-identity-status", + "--host", + "starfire", + "--move-host", + "5C:93:A2:9C:A8:A8", + ] + .into_iter() + .map(String::from), + ) + .unwrap(); + let identities = current_move_identity_records_from_sources( + &options, + &[MoveStateSource { + move_id: "move-000704a800d0".to_string(), + hidraw_path: "windows-psmove://silent".to_string(), + }], + ) + .unwrap(); + + assert_eq!(identities.len(), 1); + assert_eq!(identities[0].bluetooth_host_address, "5C:93:A2:9C:A8:A8"); + } + #[test] fn move_identity_status_reads_reopened_store() { let store_path = std::env::temp_dir().join(format!(