2023-09-16 15:34:45 -06:00
using FocusVolumeControl.AudioSessions ;
2023-08-06 13:46:36 -06:00
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.Linq ;
2023-09-16 15:34:45 -06:00
using System.Runtime.InteropServices ;
2023-08-06 13:46:36 -06:00
2023-08-20 20:52:48 -06:00
namespace FocusVolumeControl ;
public class AudioHelper
2023-08-06 13:46:36 -06:00
{
2023-09-16 15:34:45 -06:00
static object _lock = new object ( ) ;
2023-08-20 20:52:48 -06:00
List < Process > _currentProcesses ;
2023-09-16 15:34:45 -06:00
public IAudioSession Current { get ; private set ; }
public void ResetCache ( )
{
lock ( _lock )
{
Current = null ;
}
}
2023-08-20 20:52:48 -06:00
public IAudioSession FindSession ( List < Process > processes )
2023-08-06 21:51:04 -06:00
{
2023-09-16 15:34:45 -06:00
var deviceEnumerator = ( CoreAudio ) new MMDeviceEnumerator ( ) ;
2023-08-06 13:46:36 -06:00
2023-09-16 15:34:45 -06:00
deviceEnumerator . GetDefaultAudioEndpoint ( EDataFlow . eRender , ERole . eMultimedia , out var device ) ;
2023-08-06 13:46:36 -06:00
2023-09-16 15:34:45 -06:00
Guid iid = typeof ( IAudioSessionManager2 ) . GUID ;
device . Activate ( ref iid , 0 , IntPtr . Zero , out var m ) ;
var manager = ( IAudioSessionManager2 ) m ;
2023-08-06 13:46:36 -06:00
2023-08-06 21:51:04 -06:00
2023-09-16 15:34:45 -06:00
manager . GetSessionEnumerator ( out var sessionEnumerator ) ;
var results = new ActiveAudioSessionWrapper ( ) ;
sessionEnumerator . GetCount ( out var count ) ;
for ( int i = 0 ; i < count ; i + + )
2023-08-20 20:52:48 -06:00
{
2023-09-16 15:34:45 -06:00
sessionEnumerator . GetSession ( i , out var session ) ;
session . GetProcessId ( out var sessionProcessId ) ;
var audioProcess = Process . GetProcessById ( sessionProcessId ) ;
2023-08-06 21:51:04 -06:00
2023-09-16 15:34:45 -06:00
if ( processes . Any ( x = > x . Id = = sessionProcessId | | x . ProcessName = = audioProcess ? . ProcessName ) )
2023-08-06 21:51:04 -06:00
{
2023-08-20 20:52:48 -06:00
try
2023-08-06 21:51:04 -06:00
{
2023-08-20 20:52:48 -06:00
var displayName = audioProcess . MainModule . FileVersionInfo . FileDescription ;
if ( string . IsNullOrEmpty ( displayName ) )
2023-08-06 21:51:04 -06:00
{
2023-08-20 20:52:48 -06:00
displayName = audioProcess . ProcessName ;
2023-08-06 21:51:04 -06:00
}
2023-09-16 15:34:45 -06:00
results . DisplayName = displayName ;
2023-08-20 20:52:48 -06:00
}
catch
{
2023-09-16 15:34:45 -06:00
results . DisplayName ? ? = audioProcess . ProcessName ;
2023-08-20 20:52:48 -06:00
}
2023-08-06 21:51:04 -06:00
2023-09-16 15:34:45 -06:00
results . ExecutablePath ? ? = audioProcess . MainModule . FileName ;
2023-08-06 21:51:04 -06:00
2023-08-20 20:52:48 -06:00
//some apps like discord have multiple volume processes.
2023-09-16 15:34:45 -06:00
results . AddSession ( session ) ;
2023-08-06 21:51:04 -06:00
}
}
2023-08-06 13:46:36 -06:00
2023-09-16 15:34:45 -06:00
return results . Any ( ) ? results : null ;
2023-09-13 20:46:05 -06:00
}
2023-09-16 15:34:45 -06:00
2023-08-20 20:52:48 -06:00
public IAudioSession GetActiveSession ( FallbackBehavior fallbackBehavior )
{
lock ( _lock )
2023-08-06 21:51:04 -06:00
{
var processes = GetPossibleProcesses ( ) ;
2023-08-06 13:46:36 -06:00
2023-08-20 20:52:48 -06:00
if ( _currentProcesses = = null | | ! _currentProcesses . SequenceEqual ( processes ) )
{
2023-09-16 15:34:45 -06:00
Current = FindSession ( processes ) ;
2023-08-20 20:52:48 -06:00
}
2023-09-16 15:34:45 -06:00
if ( Current = = null )
2023-08-20 20:52:48 -06:00
{
2023-09-16 15:34:45 -06:00
if ( fallbackBehavior = = FallbackBehavior . SystemSounds & & Current is not SystemSoundsAudioSession )
2023-08-20 20:52:48 -06:00
{
2023-09-16 15:34:45 -06:00
Current = GetSystemSounds ( ) ;
2023-08-20 20:52:48 -06:00
}
2023-09-16 15:34:45 -06:00
else if ( fallbackBehavior = = FallbackBehavior . SystemVolume & & Current is not SystemVolumeAudioSession )
2023-08-20 20:52:48 -06:00
{
2023-09-16 15:34:45 -06:00
Current = GetSystemVolume ( ) ;
2023-08-20 20:52:48 -06:00
}
}
2023-08-06 13:46:36 -06:00
2023-08-06 21:51:04 -06:00
_currentProcesses = processes ;
2023-09-16 15:34:45 -06:00
return Current ;
2023-08-06 21:51:04 -06:00
}
2023-08-20 20:52:48 -06:00
}
2023-08-06 21:51:04 -06:00
2023-08-20 20:52:48 -06:00
/// <summary>
/// Get the list of processes that might be currently selected
/// This includes getting the child window's processes
///
/// This helps to find the audo process for windows store apps whose process is "ApplicationFrameHost.exe"
///
/// The list may optionally include a parent process, because that helps thing steam to be more reliable because the steamwebhelper (ui) is a child of steam.exe
///
/// According to deej, getting the ForegroundWindow and enumerating steam windows should work, but it doesn't seem to work for me without including the parent process
/// https://github.com/omriharel/deej/blob/master/pkg/deej/util/util_windows.go#L22
///
/// but the parent process is sometimes useless (explorer, svchost, etc) so i filter some of them out because i felt like it when i wrote the code
///
/// I also experimented with grabbing the parent process and enumerating through the windows to see if that would help, but any time the parent process was an unexpected process (explorer) it could blow up. so i decided not to bother for now
/// </summary>
/// <returns></returns>
public List < Process > GetPossibleProcesses ( )
{
var handle = Native . GetForegroundWindow ( ) ;
if ( handle = = IntPtr . Zero )
2023-08-06 21:51:04 -06:00
{
2023-08-20 20:52:48 -06:00
return null ;
}
2023-08-06 21:51:04 -06:00
2023-08-20 20:52:48 -06:00
var ids = Native . GetProcessesOfChildWindows ( handle ) ;
Native . GetWindowThreadProcessId ( handle , out var pid ) ;
ids . Insert ( 0 , pid ) ;
var processes = ids . Distinct ( )
. Select ( x = > Process . GetProcessById ( x ) )
. ToList ( ) ;
try
{
var blah = ParentProcessUtilities . GetParentProcess ( pid ) ;
if ( blah ! = null & & blah . ProcessName ! = "explorer" & & blah . ProcessName ! = "svchost" )
2023-08-06 21:51:04 -06:00
{
2023-08-20 20:52:48 -06:00
processes . Add ( blah ) ;
2023-08-06 21:51:04 -06:00
}
2023-08-20 20:52:48 -06:00
}
catch
{
}
2023-08-06 21:51:04 -06:00
2023-08-20 20:52:48 -06:00
return processes ;
2023-08-06 21:51:04 -06:00
2023-08-20 20:52:48 -06:00
}
2023-08-06 21:51:04 -06:00
2023-08-20 20:52:48 -06:00
public void ResetAll ( )
{
2023-09-16 15:34:45 -06:00
var deviceEnumerator = ( CoreAudio ) new MMDeviceEnumerator ( ) ;
2023-08-06 21:51:04 -06:00
2023-09-16 15:34:45 -06:00
deviceEnumerator . GetDefaultAudioEndpoint ( EDataFlow . eRender , ERole . eMultimedia , out var device ) ;
2023-08-20 20:52:48 -06:00
2023-09-16 15:34:45 -06:00
Guid iid = typeof ( IAudioSessionManager2 ) . GUID ;
device . Activate ( ref iid , 0 , IntPtr . Zero , out var m ) ;
var manager = ( IAudioSessionManager2 ) m ;
manager . GetSessionEnumerator ( out var sessionEnumerator ) ;
sessionEnumerator . GetCount ( out var count ) ;
for ( int i = 0 ; i < count ; i + + )
{
sessionEnumerator . GetSession ( i , out var session ) ;
var volume = ( ISimpleAudioVolume ) session ;
var guid = Guid . Empty ;
volume . SetMasterVolume ( 1 , ref guid ) ;
volume . SetMute ( false , ref guid ) ;
2023-08-20 20:52:48 -06:00
}
}
public IAudioSession GetSystemSounds ( )
{
2023-09-16 15:34:45 -06:00
var deviceEnumerator = ( CoreAudio ) new MMDeviceEnumerator ( ) ;
deviceEnumerator . GetDefaultAudioEndpoint ( EDataFlow . eRender , ERole . eMultimedia , out var device ) ;
Guid iid = typeof ( IAudioSessionManager2 ) . GUID ;
device . Activate ( ref iid , 0 , IntPtr . Zero , out var m ) ;
var manager = ( IAudioSessionManager2 ) m ;
2023-08-20 20:52:48 -06:00
2023-08-06 13:46:36 -06:00
2023-09-16 15:34:45 -06:00
manager . GetSessionEnumerator ( out var sessionEnumerator ) ;
2023-08-06 13:46:36 -06:00
2023-09-16 15:34:45 -06:00
sessionEnumerator . GetCount ( out var count ) ;
for ( int i = 0 ; i < count ; i + + )
2023-08-20 20:52:48 -06:00
{
2023-09-16 15:34:45 -06:00
sessionEnumerator . GetSession ( i , out var session ) ;
if ( session . IsSystemSoundsSession ( ) = = 0 )
2023-08-20 20:52:48 -06:00
{
2023-09-16 15:34:45 -06:00
return new SystemSoundsAudioSession ( session ) ;
2023-08-20 20:52:48 -06:00
}
2023-08-06 21:51:04 -06:00
}
2023-08-20 20:52:48 -06:00
return null ;
2023-08-06 21:51:04 -06:00
}
2023-08-20 20:52:48 -06:00
public IAudioSession GetSystemVolume ( )
{
2023-09-16 15:34:45 -06:00
var deviceEnumerator = ( CoreAudio ) new MMDeviceEnumerator ( ) ;
deviceEnumerator . GetDefaultAudioEndpoint ( EDataFlow . eRender , ERole . eMultimedia , out var device ) ;
Guid iid = typeof ( IAudioEndpointVolume ) . GUID ;
device . Activate ( ref iid , 0 , IntPtr . Zero , out var o ) ;
var endpointVolume = ( IAudioEndpointVolume ) o ;
2023-08-20 20:52:48 -06:00
2023-09-16 15:34:45 -06:00
return new SystemVolumeAudioSession ( endpointVolume ) ;
2023-08-20 20:52:48 -06:00
}
2023-08-06 13:46:36 -06:00
}