Improve sound browser to make it have more stuff in it

improve sound browser debuggability by making it capture the window earlier so you can set a breakpoint and not have it just get VS info

put in code to try to handle helldivers 2
This commit is contained in:
dlprows 2024-02-24 23:46:00 -07:00
parent 0f7f1fffcd
commit 7abbc92080
9 changed files with 319 additions and 22 deletions

View File

@ -162,9 +162,9 @@ public class AudioHelper
/// 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()
public List<Process> GetPossibleProcesses(IntPtr? handleOverride = null)
{
var handle = Native.GetForegroundWindow();
var handle = handleOverride ?? Native.GetForegroundWindow();
if (handle == IntPtr.Zero)
{
@ -174,7 +174,25 @@ public class AudioHelper
var ids = Native.GetProcessesOfChildWindows(handle);
Native.GetWindowThreadProcessId(handle, out var pid);
if(ids.Count == 0 && pid == 0)
{
foreach(var p in Process.GetProcesses())
{
if(p.MainWindowHandle == handle)
{
if(p.MainWindowTitle == "HELLDIVERS™ 2")
{
ids = FindAudioSessionByProcessName("helldivers2");
break;
}
}
}
}
else
{
ids.Insert(0, pid);
}
var processes = ids.Distinct()
.Select(x => Process.GetProcessById(x))
@ -287,4 +305,79 @@ public class AudioHelper
return new SystemVolumeAudioSession(endpointVolume);
}
static Dictionary<string, List<int>> _audioSessionNameCache = new Dictionary<string, List<int>>();
private List<int> GetFromNameCacheIfPossible(string processName)
{
if(_audioSessionNameCache.TryGetValue(processName, out var result))
{
if(result.Count!= 0)
{
foreach(var pid in result)
{
var p = GetProcessById(pid);
if(p == null || p.ProcessName != processName)
{
_audioSessionNameCache.Remove(processName);
return null;
}
}
return result;
}
}
return null;
}
private List<int> FindAudioSessionByProcessName(string processName)
{
var cached = GetFromNameCacheIfPossible(processName);
if(cached != null)
{
return cached;
}
var results = new List<int>();
Process bestProcessMatch = null;
var deviceEnumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
deviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active, out var deviceCollection);
deviceCollection.GetCount(out var numDevices);
for (int d = 0; d < numDevices; d++)
{
deviceCollection.Item(d, out var device);
Guid iid = typeof(IAudioSessionManager2).GUID;
device.Activate(ref iid, CLSCTX.ALL, IntPtr.Zero, out var m);
var manager = (IAudioSessionManager2)m;
device.GetId(out var currentDeviceId);
manager.GetSessionEnumerator(out var sessionEnumerator);
var currentIndex = int.MaxValue;
sessionEnumerator.GetCount(out var count);
for (int i = 0; i < count; i++)
{
sessionEnumerator.GetSession(i, out var session);
session.GetDisplayName(out var displayName);
session.GetProcessId(out var sessionProcessId);
var audioProcess = GetProcessById(sessionProcessId);
if(audioProcess != null && audioProcess.ProcessName == processName)
{
results.Add(sessionProcessId);
}
}
}
results = results.Distinct().ToList();
_audioSessionNameCache[processName] = results;
return results;
}
}

View File

@ -17,6 +17,10 @@ public sealed class ActiveAudioSessionWrapper : IAudioSession
private IEnumerable<ISimpleAudioVolume> Volume => Sessions.Cast<ISimpleAudioVolume>();
public IconWrapper? IconWrapper { get; set; }
public IEnumerable<int> Pids => Sessions.Select(x =>
{
x.GetProcessId(out var pid); return pid;
});
public string GetIcon()
{

View File

@ -1,6 +1,7 @@
using System;
using System.Data;
using System.Runtime.InteropServices;
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
namespace FocusVolumeControl.AudioSessions;
@ -91,6 +92,91 @@ public interface IMMDeviceEnumerator
int GetDefaultAudioEndpoint(DataFlow dataFlow, Role role, out IMMDevice device);
}
public enum EStgmAccess
{
STGM_READ = 0x0
}
[Guid("886d8eeb-8cf2-4446-8d02-cdba1dbdcf99"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPropertyStore
{
[PreserveSig]
int GetCount(out int count);
[PreserveSig]
int GetAt(int iProp, out PropertyKey pkey);
[PreserveSig]
int GetValue(ref PropertyKey key, out PropVariant pv);
[PreserveSig]
int SetValue(ref PropertyKey key, ref PropVariant propvar);
[PreserveSig]
int Commit();
}
public struct PropertyKey
{
public Guid fmtId;
public int PId;
public PropertyKey(Guid fmtId, int pId)
{
this.fmtId = fmtId;
this.PId = pId;
}
}
public struct Blob
{
public int Length;
public IntPtr Data;
}
[StructLayout(LayoutKind.Explicit)]
public struct PropVariant
{
[FieldOffset(0)] short vt;
[FieldOffset(2)] short wReserved1;
[FieldOffset(4)] short wReserved2;
[FieldOffset(6)] short wReserved3;
[FieldOffset(8)] sbyte cVal;
[FieldOffset(8)] byte bVal;
[FieldOffset(8)] short iVal;
[FieldOffset(8)] ushort uiVal;
[FieldOffset(8)] int lVal;
[FieldOffset(8)] uint ulVal;
[FieldOffset(8)] long hVal;
[FieldOffset(8)] ulong uhVal;
[FieldOffset(8)] float fltVal;
[FieldOffset(8)] double dblVal;
[FieldOffset(8)] Blob blobVal;
[FieldOffset(8)] DateTime date;
[FieldOffset(8)] bool boolVal;
[FieldOffset(8)] int scode;
[FieldOffset(8)] FILETIME filetime;
[FieldOffset(8)] IntPtr everything_else;
public object Value
{
get
{
var ve = (VarEnum)vt;
if((VarEnum)vt == VarEnum.VT_LPWSTR)
{
return Marshal.PtrToStringUni(everything_else);
}
throw new Exception();
}
}
}
public static class PKey
{
public static readonly PropertyKey DeviceFriendlyName = new(new(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0), 14);
public static readonly PropertyKey DeviceFriendlyNameAttributes = new(new(0x80d81ea6, 0x7473, 0x4b0c, 0x82, 0x16, 0xef, 0xc1, 0x1a, 0x2c, 0x4c, 0x8b), 3);
public static readonly PropertyKey DeviceInterfaceFriendlyName = new(new(0x026e516e, 0xb814, 0x414b, 0x83, 0xcd, 0x85, 0x6d, 0x6f, 0xef, 0x48, 0x22), 2);
}
[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMMDevice
{
@ -98,7 +184,7 @@ public interface IMMDevice
int Activate(ref Guid iid, CLSCTX dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
[PreserveSig]
int NotImpl1();
int OpenPropertyStore(EStgmAccess stgmAccess, out IPropertyStore propertyStore);
[PreserveSig]
int GetId([Out, MarshalAs(UnmanagedType.LPWStr)] out string ppstrId);

View File

@ -1,4 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace FocusVolumeControl.AudioSessions;
@ -15,4 +17,6 @@ public interface IAudioSession
public void IncrementVolumeLevel(int step, int ticks);
public int GetVolumeLevel();
public IEnumerable<int> Pids { get; }
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace FocusVolumeControl.AudioSessions;
@ -15,6 +16,9 @@ internal sealed class SystemSoundsAudioSession : IAudioSession
ISimpleAudioVolume _volumeControl;
public string DisplayName => "System sounds";
public IEnumerable<int> Pids => new int[0];
public string GetIcon() => "Images/systemSounds";
public void ToggleMute()

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace FocusVolumeControl.AudioSessions;
@ -15,6 +16,8 @@ internal sealed class SystemVolumeAudioSession : IAudioSession
public string DisplayName => "System Volume";
public string GetIcon() => "Images/encoderIcon";
public IEnumerable<int> Pids => new int[0];
public void ToggleMute()
{
_volumeControl.SetMute(!IsMuted(), Guid.Empty);

View File

@ -12,9 +12,9 @@
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button x:Name="_pauseButton" Grid.Row="0" Click="PauseClicked"></Button>
<TextBlock x:Name="_tf" Grid.Row="0">current</TextBlock>
<TextBlock x:Name="_tf2" Grid.Row="1">list</TextBlock>
<TextBox x:Name="_tf" Grid.Row="1" IsReadOnly="True">current</TextBox>
</Grid>
</ScrollViewer>

View File

@ -1,10 +1,13 @@
using FocusVolumeControl;
using FocusVolumeControl.AudioHelpers;
using FocusVolumeControl.AudioSessions;
using NHotkey;
using NHotkey.Wpf;
using System;
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Input;
namespace SoundBrowser;
@ -16,6 +19,8 @@ public partial class MainWindow : Window
AudioHelper _audioHelper;
Native.WinEventDelegate _delegate;
bool _paused = false;
bool _doOnce = false;
public MainWindow()
{
@ -25,20 +30,93 @@ public partial class MainWindow : Window
//normally you can just pass a lambda, but for some reason, that seems to get garbage collected
_delegate = new Native.WinEventDelegate(WinEventProc);
Native.RegisterForForegroundWindowChangedEvent(_delegate);
HotkeyManager.Current.AddOrReplace("Pause", Key.P, ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Shift, OnPauseShortcut);
}
private void OnPauseShortcut(object? sender, HotkeyEventArgs e)
{
if( _paused )
{
_paused = false;
_pauseButton.Content = "Running - click to pause on next app";
}
else
{
_paused = true;
_doOnce = true;
_pauseButton.Content = "Paused - click to resume";
_handle = Native.GetForegroundWindow();
DoThing();
}
}
private void PauseClicked(object sender, RoutedEventArgs e)
{
_paused = !_paused;
if(_paused)
{
_pauseButton.Content = "Pausing on next app";
_doOnce = true;
}
else
{
_pauseButton.Content = "Running - click to pause on next app";
}
}
public void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
SetupCurrentAppFields();
SetupAllSessionFields();
_handle = Native.GetForegroundWindow();
DoThing();
}
private void SetupCurrentAppFields()
private void DoThing()
{
var handle = Native.GetForegroundWindow();
var sb = new StringBuilder();
if(_paused)
{
if(!_doOnce)
{
return;
}
_doOnce = false;
_pauseButton.Content = "Paused - click to resume";
}
else
{
_pauseButton.Content = "Running - click to pause on next app";
}
if (handle != IntPtr.Zero)
var sb = new StringBuilder();
SetupCurrentAppFields(sb);
sb.AppendLine("");
sb.AppendLine("-------------------------------------------------------------------------------");
sb.AppendLine("");
DetermineDefaultDevice(sb);
sb.AppendLine("");
SetupAllSessionFields(sb);
_tf.Text = sb.ToString();
Trace.WriteLine(sb.ToString());
}
private void DetermineDefaultDevice(StringBuilder sb)
{
var deviceEnumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia, out var device);
var name = GetDeviceName(device);
sb.AppendLine($"Default Audio Device: {name}");
}
IntPtr _handle;
private void SetupCurrentAppFields(StringBuilder sb)
{
if (_handle != IntPtr.Zero)
{
//use this in debug to help there be less events
@ -52,14 +130,15 @@ public partial class MainWindow : Window
}
*/
var processes = _audioHelper.GetPossibleProcesses();
var processes = _audioHelper.GetPossibleProcesses(_handle);
var session = _audioHelper.FindSession(processes);
sb.AppendLine("Possible Current Processes");
foreach (var p in processes)
{
var displayName = (new NameAndIconHelper()).GetProcessInfo(p);
sb.AppendLine($"pid: {p.Id}");
sb.AppendLine($"\tpid: {p.Id}");
sb.AppendLine($"\tprocessName: {p.ProcessName}");
sb.AppendLine($"\tDisplayName: {displayName}");
@ -69,6 +148,7 @@ public partial class MainWindow : Window
if (session != null)
{
sb.AppendLine("picked the following best match");
sb.AppendLine($"\tpid: {string.Join(", ", session.Pids)}");
sb.AppendLine($"\tsession: {session.DisplayName}");
sb.AppendLine($"\tvolume: {session.GetVolumeLevel()}");
}
@ -78,15 +158,10 @@ public partial class MainWindow : Window
}
}
_tf.Text = sb.ToString();
}
private void SetupAllSessionFields()
private void SetupAllSessionFields(StringBuilder sb)
{
_tf2.Text = "";
var sb = new StringBuilder();
sb.AppendLine("-------------------------------------------------------------------------------");
var deviceEnumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
deviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active, out var deviceCollection);
@ -97,7 +172,8 @@ public partial class MainWindow : Window
{
deviceCollection.Item(i, out var device);
//todo: put the device name in the output
sb.AppendLine($"----");
var name = GetDeviceName(device);
sb.AppendLine($"----{name}----");
Guid iid = typeof(IAudioSessionManager2).GUID;
@ -116,13 +192,36 @@ public partial class MainWindow : Window
session.GetProcessId(out var processId);
session.GetIconPath(out var path);
var audioProcess = Process.GetProcessById(processId);
if(audioProcess.Id == 0)
{
continue;
}
var displayName = (new NameAndIconHelper()).GetProcessInfo(audioProcess);
sb.AppendLine($"pid: {audioProcess.Id}\t\t processName: {displayName}");
}
_tf2.Text = sb.ToString();
sb.Append("\n");
}
}
private string GetDeviceName(IMMDevice device)
{
var fnkey = PKey.DeviceFriendlyName;
device.OpenPropertyStore(EStgmAccess.STGM_READ, out var propertyStore);
propertyStore.GetCount(out var count);
for(int i = 0; i < count; i++)
{
propertyStore.GetAt(i, out var pkey);
if(pkey.fmtId == fnkey.fmtId && pkey.PId == fnkey.PId)
{
propertyStore.GetValue(ref pkey, out var pvalue);
return (string)pvalue.Value;
}
}
return "";
}
}

View File

@ -7,6 +7,10 @@
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NHotkey.Wpf" Version="3.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FocusVolumeControl\FocusVolumeControl.csproj" />
</ItemGroup>