From dc4c6ecf7c47965bab0f646ffb50a5ba5308e13e Mon Sep 17 00:00:00 2001 From: holby Date: Thu, 14 Nov 2024 18:02:24 -0500 Subject: [PATCH] functional but messy --- examples/boilerplate-solid-ts/dist/index.html | 2 +- examples/boilerplate-solid-ts/src/index.tsx | 4 +- packages/desktop/Cargo.toml | 4 +- .../src/providers/audio/audio_provider.rs | 237 ++++++++++++------ 4 files changed, 174 insertions(+), 73 deletions(-) diff --git a/examples/boilerplate-solid-ts/dist/index.html b/examples/boilerplate-solid-ts/dist/index.html index 53d75ec6..78026713 100644 --- a/examples/boilerplate-solid-ts/dist/index.html +++ b/examples/boilerplate-solid-ts/dist/index.html @@ -5,7 +5,7 @@ Zebar - + diff --git a/examples/boilerplate-solid-ts/src/index.tsx b/examples/boilerplate-solid-ts/src/index.tsx index ecd2eea4..233df7ae 100644 --- a/examples/boilerplate-solid-ts/src/index.tsx +++ b/examples/boilerplate-solid-ts/src/index.tsx @@ -21,7 +21,9 @@ function App() { return (
-
Audio volume: {output.audio?.volume}
+
+ vol: {output.audio?.volume} dev: {output.audio?.currentDevice} +
CPU usage: {output.cpu?.usage}
Battery charge: {output.battery?.chargePercent} diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index b80c0464..e90d71e2 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -42,15 +42,17 @@ komorebi-client = { git = "https://github.com/LGUG2Z/komorebi", tag = "v0.1.28" windows-core = "0.58" windows = { version = "0.58", features = [ "implement", + "Win32_Devices_FunctionDiscovery", "Win32_Globalization", "Win32_Media", "Win32_Media_Audio", "Win32_Media_Audio_Endpoints", "Win32_System_Console", "Win32_System_SystemServices", - "Win32_UI_WindowsAndMessaging", "Win32_UI_Input_KeyboardAndMouse", + "Win32_UI_Shell_PropertiesSystem", "Win32_UI_TextServices", + "Win32_UI_WindowsAndMessaging", "Win32_NetworkManagement_WiFi", ] } diff --git a/packages/desktop/src/providers/audio/audio_provider.rs b/packages/desktop/src/providers/audio/audio_provider.rs index 6a742b41..a66f060a 100644 --- a/packages/desktop/src/providers/audio/audio_provider.rs +++ b/packages/desktop/src/providers/audio/audio_provider.rs @@ -1,30 +1,32 @@ -use std::sync::OnceLock; +use std::sync::{Arc, Mutex, OnceLock}; use async_trait::async_trait; use serde::{Deserialize, Serialize}; use tokio::sync::mpsc::{self, Sender}; use windows::Win32::{ + Devices::FunctionDiscovery::PKEY_Device_FriendlyName, Media::Audio::{ - eMultimedia, eRender, + eMultimedia, eRender, EDataFlow, ERole, Endpoints::{ IAudioEndpointVolume, IAudioEndpointVolumeCallback, IAudioEndpointVolumeCallback_Impl, }, - IAudioSessionControl, IAudioSessionNotification, - IAudioSessionNotification_Impl, IMMDeviceEnumerator, - MMDeviceEnumerator, + IMMDevice, IMMDeviceEnumerator, IMMNotificationClient, + IMMNotificationClient_Impl, MMDeviceEnumerator, DEVICE_STATE, }, System::Com::{ CoCreateInstance, CoInitializeEx, CLSCTX_ALL, COINIT_MULTITHREADED, + STGM_READ, }, + UI::Shell::PropertiesSystem::IPropertyStore, }; +use windows_core::PCWSTR; use crate::providers::{Provider, ProviderOutput, ProviderResult}; static PROVIDER_TX: OnceLock> = OnceLock::new(); - -static AUDIO_STATUS: OnceLock = OnceLock::new(); +static AUDIO_STATE: OnceLock>> = OnceLock::new(); #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -33,7 +35,7 @@ pub struct AudioProviderConfig {} #[derive(Debug, Clone, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AudioOutput { - pub device: String, + pub current_device: String, pub volume: f32, } @@ -41,16 +43,28 @@ pub struct AudioProvider { _config: AudioProviderConfig, } +#[async_trait] +impl Provider for AudioProvider { + async fn run(&self, emit_result_tx: Sender) { + if let Err(err) = self.create_audio_manager(emit_result_tx.clone()) { + emit_result_tx + .send(Err(err).into()) + .await + .expect("Error emitting media provider err."); + } + } +} + impl AudioProvider { pub fn new(config: AudioProviderConfig) -> Self { Self { _config: config } } fn emit_volume() { - let tx = PROVIDER_TX.get().expect("Error getting provider tx"); - let output = AUDIO_STATUS.get().expect("Error getting audio status"); - tx.try_send(Ok(ProviderOutput::Audio(output.clone())).into()) - .expect("Error sending audio status"); + if let Some(tx) = PROVIDER_TX.get() { + let output = AUDIO_STATE.get().unwrap().lock().unwrap().clone(); + let _ = tx.try_send(Ok(ProviderOutput::Audio(output)).into()); + } } fn create_audio_manager( @@ -61,29 +75,37 @@ impl AudioProvider { .set(emit_result_tx.clone()) .expect("Error setting provider tx in focused window provider"); - // TODO is this the best way to initialize this - let _ = AUDIO_STATUS.set(AudioOutput { - device: "n/a".to_string(), - volume: 0.0, - }); - unsafe { - // Initialize COM library let _ = CoInitializeEx(None, COINIT_MULTITHREADED); - // Get the audio endpoint volume interface let enumerator: IMMDeviceEnumerator = CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL)?; + + let handler = MediaDeviceEventHandler::new(enumerator.clone()); + let device_notification_callback = + IMMNotificationClient::from(handler.clone()); + let default_device = enumerator.GetDefaultAudioEndpoint(eRender, eMultimedia)?; - let endpoint_volume: IAudioEndpointVolume = - default_device.Activate(CLSCTX_ALL, None)?; + if let Ok(name) = + MediaDeviceEventHandler::get_device_name(&default_device) + { + println!("Default audio render device: {}", name); + } - // Register the volume change callback - let device_volume_callback = - IAudioEndpointVolumeCallback::from(MediaDeviceEventHandler {}); - endpoint_volume - .RegisterControlChangeNotify(&device_volume_callback)?; + let initial_device = + MediaDeviceEventHandler::get_device_name(&default_device)?; + let initial_volume = + handler.setup_volume_monitoring(&default_device)?; + AUDIO_STATE.set(Arc::new(Mutex::new(AudioOutput { + current_device: initial_device, + volume: initial_volume, + }))).expect("Error setting initial device volume"); + Self::emit_volume(); + + enumerator.RegisterEndpointNotificationCallback( + &device_notification_callback, + )?; loop { std::thread::sleep(std::time::Duration::from_secs(1)); @@ -92,66 +114,141 @@ impl AudioProvider { } } -#[async_trait] -impl Provider for AudioProvider { - async fn run(&self, emit_result_tx: Sender) { - if let Err(err) = self.create_audio_manager(emit_result_tx.clone()) { - emit_result_tx - .send(Err(err).into()) - .await - .expect("Erroring emitting media provider err."); - } - } +struct DeviceState { + volume_callbacks: + Arc>>, } +#[derive(Clone)] #[windows::core::implement( - IAudioEndpointVolumeCallback, - IAudioSessionNotification + IMMNotificationClient, + IAudioEndpointVolumeCallback )] -struct MediaDeviceEventHandler {} +struct MediaDeviceEventHandler { + enumerator: IMMDeviceEnumerator, + device_state: Arc, +} + +impl MediaDeviceEventHandler { + fn new(enumerator: IMMDeviceEnumerator) -> Self { + Self { + enumerator, + device_state: Arc::new(DeviceState { + volume_callbacks: Arc::new(Mutex::new(Vec::new())), + }), + } + } + + fn get_device_name(device: &IMMDevice) -> windows_core::Result { + unsafe { + let store: IPropertyStore = device.OpenPropertyStore(STGM_READ)?; + let value = store.GetValue(&PKEY_Device_FriendlyName)?; + Ok(value.to_string()) + } + } + + fn setup_volume_monitoring( + &self, + device: &IMMDevice, + ) -> windows_core::Result { + unsafe { + let endpoint_volume: IAudioEndpointVolume = + device.Activate(CLSCTX_ALL, None)?; + let handler = MediaDeviceEventHandler::new(self.enumerator.clone()); + let volume_callback = IAudioEndpointVolumeCallback::from(handler); + let inital_volume = endpoint_volume.GetMasterVolumeLevelScalar()?; + endpoint_volume.RegisterControlChangeNotify(&volume_callback)?; + self + .device_state + .volume_callbacks + .lock() + .unwrap() + .push((volume_callback, endpoint_volume)); + Ok(inital_volume) + } + } +} impl IAudioEndpointVolumeCallback_Impl for MediaDeviceEventHandler_Impl { fn OnNotify( &self, data: *mut windows::Win32::Media::Audio::AUDIO_VOLUME_NOTIFICATION_DATA, ) -> windows_core::Result<()> { - println!("Volume notification"); if let Some(data) = unsafe { data.as_ref() } { - // TODO: surely theres a better way to do this without the clone - AUDIO_STATUS - .set(AudioOutput { - device: AUDIO_STATUS.get().expect("msg").device.clone(), - volume: data.fMasterVolume, - }) - .expect("Error setting audio status"); - println!("Volume update: {}", data.fMasterVolume,); - AudioProvider::emit_volume(); + if let Some(state) = AUDIO_STATE.get() { + if let Ok(mut output) = state.lock() { + output.volume = data.fMasterVolume; + println!("Volume update: {}", data.fMasterVolume); + } + AudioProvider::emit_volume(); + } } Ok(()) } } -impl IAudioSessionNotification_Impl for MediaDeviceEventHandler_Impl { - fn OnSessionCreated( +impl IMMNotificationClient_Impl for MediaDeviceEventHandler_Impl { + fn OnDeviceStateChanged( &self, - _new_session: Option<&IAudioSessionControl>, - ) -> windows::core::Result<()> { - let name = unsafe { - _new_session - .unwrap() - .GetDisplayName() - .unwrap() - .to_string() - .unwrap() - }; - AUDIO_STATUS - .set(AudioOutput { - device: name.clone(), - volume: AUDIO_STATUS.get().expect("msg").volume, - }) - .expect("Error setting audio status"); - println!("New session created: {}", name); - AudioProvider::emit_volume(); + pwstrDeviceId: &PCWSTR, + dwNewState: DEVICE_STATE, + ) -> windows_core::Result<()> { + let device_id = unsafe { pwstrDeviceId.to_string()? }; + println!( + "Device state changed: {} - State: {:?}", + device_id, dwNewState + ); + Ok(()) + } + + fn OnDeviceAdded( + &self, + pwstrDeviceId: &PCWSTR, + ) -> windows_core::Result<()> { + let device_id = unsafe { pwstrDeviceId.to_string()? }; + println!("Device added: {}", device_id); + Ok(()) + } + + fn OnDeviceRemoved( + &self, + pwstrDeviceId: &PCWSTR, + ) -> windows_core::Result<()> { + let device_id = unsafe { pwstrDeviceId.to_string()? }; + println!("Device removed: {}", device_id); + Ok(()) + } + + fn OnDefaultDeviceChanged( + &self, + flow: EDataFlow, + role: ERole, + pwstrDefaultDeviceId: &PCWSTR, + ) -> windows_core::Result<()> { + unsafe { + if flow == eRender && role == eMultimedia { + let device = self.enumerator.GetDevice(*pwstrDefaultDeviceId)?; + if let Ok(name) = MediaDeviceEventHandler::get_device_name(&device) + { + println!("Default device changed to: {}", name); + self.setup_volume_monitoring(&device)?; + if let Ok(mut output) = AUDIO_STATE.get().unwrap().lock() { + output.current_device = name; + } + AudioProvider::emit_volume(); + } + } + } + Ok(()) + } + + fn OnPropertyValueChanged( + &self, + pwstrDeviceId: &PCWSTR, + key: &windows::Win32::UI::Shell::PropertiesSystem::PROPERTYKEY, + ) -> windows_core::Result<()> { + let device_id = unsafe { pwstrDeviceId.to_string()? }; + println!("Property changed: {} - Key: {:?}", device_id, key); Ok(()) } }