Skip to content

Commit

Permalink
functional but messy
Browse files Browse the repository at this point in the history
  • Loading branch information
HolbyFPV committed Nov 14, 2024
1 parent fedd501 commit dc4c6ec
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 73 deletions.
2 changes: 1 addition & 1 deletion examples/boilerplate-solid-ts/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Zebar</title>
<script type="module" crossorigin src="./assets/index-CTFXa6CN.js"></script>
<script type="module" crossorigin src="./assets/index-0iu8mdoa.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-DhTpoJOY.css">
</head>
<body>
Expand Down
4 changes: 3 additions & 1 deletion examples/boilerplate-solid-ts/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ function App() {

return (
<div class="app">
<div class="chip">Audio volume: {output.audio?.volume}</div>
<div class="chip">
vol: {output.audio?.volume} dev: {output.audio?.currentDevice}
</div>
<div class="chip">CPU usage: {output.cpu?.usage}</div>
<div class="chip">
Battery charge: {output.battery?.chargePercent}
Expand Down
4 changes: 3 additions & 1 deletion packages/desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
] }

Expand Down
237 changes: 167 additions & 70 deletions packages/desktop/src/providers/audio/audio_provider.rs
Original file line number Diff line number Diff line change
@@ -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<mpsc::Sender<ProviderResult>> =
OnceLock::new();

static AUDIO_STATUS: OnceLock<AudioOutput> = OnceLock::new();
static AUDIO_STATE: OnceLock<Arc<Mutex<AudioOutput>>> = OnceLock::new();

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
Expand All @@ -33,24 +35,36 @@ 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,
}

pub struct AudioProvider {
_config: AudioProviderConfig,
}

#[async_trait]
impl Provider for AudioProvider {
async fn run(&self, emit_result_tx: Sender<ProviderResult>) {
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(
Expand All @@ -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));
Expand All @@ -92,66 +114,141 @@ impl AudioProvider {
}
}

#[async_trait]
impl Provider for AudioProvider {
async fn run(&self, emit_result_tx: Sender<ProviderResult>) {
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<Mutex<Vec<(IAudioEndpointVolumeCallback, IAudioEndpointVolume)>>>,
}

#[derive(Clone)]
#[windows::core::implement(
IAudioEndpointVolumeCallback,
IAudioSessionNotification
IMMNotificationClient,
IAudioEndpointVolumeCallback
)]
struct MediaDeviceEventHandler {}
struct MediaDeviceEventHandler {
enumerator: IMMDeviceEnumerator,
device_state: Arc<DeviceState>,
}

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<String> {
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<f32> {
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(())
}
}

0 comments on commit dc4c6ec

Please sign in to comment.