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:
parent
0f7f1fffcd
commit
7abbc92080
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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 "";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user