Support for multiple audio devices. such as when using voicemeeter
improved display name and icon support fixed issues with games like halo master chief collection, which threw exceptions when getting the display name
This commit is contained in:
parent
2b10b6d7a6
commit
4c1ccd9025
286
src/FocusVolumeControl/AudioHelpers/AppxPackage.cs
Normal file
286
src/FocusVolumeControl/AudioHelpers/AppxPackage.cs
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices.ComTypes;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FocusVolumeControl.AudioHelpers;
|
||||||
|
|
||||||
|
public sealed class AppxPackage
|
||||||
|
{
|
||||||
|
private AppxPackage()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Path { get; private set; }
|
||||||
|
public string Logo { get; private set; }
|
||||||
|
public string DisplayName { get; private set; }
|
||||||
|
|
||||||
|
public static AppxPackage FromProcess(Process process)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return FromProcess(process.Handle);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AppxPackage FromProcess(int processId)
|
||||||
|
{
|
||||||
|
const int QueryLimitedInformation = 0x1000;
|
||||||
|
IntPtr hProcess = OpenProcess(QueryLimitedInformation, false, processId);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return FromProcess(hProcess);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (hProcess != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
CloseHandle(hProcess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static AppxPackage FromProcess(IntPtr hProcess)
|
||||||
|
{
|
||||||
|
if (hProcess == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = 0;
|
||||||
|
GetPackageFullName(hProcess, ref len, null);
|
||||||
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sb = new StringBuilder(len);
|
||||||
|
string fullName = GetPackageFullName(hProcess, ref len, sb) == 0 ? sb.ToString() : null;
|
||||||
|
if (string.IsNullOrEmpty(fullName)) // not an AppX
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var package = QueryPackageInfo(fullName, PackageConstants.PACKAGE_FILTER_HEAD).First();
|
||||||
|
|
||||||
|
return package;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<AppxPackage> QueryPackageInfo(string fullName, PackageConstants flags)
|
||||||
|
{
|
||||||
|
IntPtr infoRef;
|
||||||
|
OpenPackageInfoByFullName(fullName, 0, out infoRef);
|
||||||
|
if (infoRef != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
IntPtr infoBuffer = IntPtr.Zero;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int len = 0;
|
||||||
|
int count;
|
||||||
|
GetPackageInfo(infoRef, flags, ref len, IntPtr.Zero, out count);
|
||||||
|
if (len > 0)
|
||||||
|
{
|
||||||
|
var factory = (IAppxFactory)new AppxFactory();
|
||||||
|
infoBuffer = Marshal.AllocHGlobal(len);
|
||||||
|
int res = GetPackageInfo(infoRef, flags, ref len, infoBuffer, out count);
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var info = (PACKAGE_INFO)Marshal.PtrToStructure(infoBuffer + i * Marshal.SizeOf(typeof(PACKAGE_INFO)), typeof(PACKAGE_INFO));
|
||||||
|
var package = new AppxPackage();
|
||||||
|
package.Path = Marshal.PtrToStringUni(info.path);
|
||||||
|
|
||||||
|
// read manifest
|
||||||
|
string manifestPath = System.IO.Path.Combine(package.Path, "AppXManifest.xml");
|
||||||
|
const int STGM_SHARE_DENY_NONE = 0x40;
|
||||||
|
|
||||||
|
SHCreateStreamOnFileEx(manifestPath, STGM_SHARE_DENY_NONE, 0, false, IntPtr.Zero, out var strm);
|
||||||
|
if (strm != null)
|
||||||
|
{
|
||||||
|
var reader = factory.CreateManifestReader(strm);
|
||||||
|
var properties = reader.GetProperties();
|
||||||
|
|
||||||
|
properties.GetStringValue("DisplayName", out var displayName);
|
||||||
|
package.DisplayName = displayName;
|
||||||
|
|
||||||
|
properties.GetStringValue("Logo", out var logo);
|
||||||
|
package.Logo = logo;
|
||||||
|
|
||||||
|
/*
|
||||||
|
var apps = reader.GetApplications();
|
||||||
|
while (apps.GetHasCurrent())
|
||||||
|
{
|
||||||
|
var app = apps.GetCurrent();
|
||||||
|
var appx = new AppxApp(app);
|
||||||
|
appx.Description = GetStringValue(app, "Description");
|
||||||
|
appx.DisplayName = GetStringValue(app, "DisplayName");
|
||||||
|
appx.EntryPoint = GetStringValue(app, "EntryPoint");
|
||||||
|
appx.Executable = GetStringValue(app, "Executable");
|
||||||
|
appx.Id = GetStringValue(app, "Id");
|
||||||
|
appx.Logo = GetStringValue(app, "Logo");
|
||||||
|
appx.SmallLogo = GetStringValue(app, "SmallLogo");
|
||||||
|
appx.StartPage = GetStringValue(app, "StartPage");
|
||||||
|
appx.Square150x150Logo = GetStringValue(app, "Square150x150Logo");
|
||||||
|
appx.Square30x30Logo = GetStringValue(app, "Square30x30Logo");
|
||||||
|
appx.BackgroundColor = GetStringValue(app, "BackgroundColor");
|
||||||
|
appx.ForegroundText = GetStringValue(app, "ForegroundText");
|
||||||
|
appx.WideLogo = GetStringValue(app, "WideLogo");
|
||||||
|
appx.Wide310x310Logo = GetStringValue(app, "Wide310x310Logo");
|
||||||
|
appx.ShortName = GetStringValue(app, "ShortName");
|
||||||
|
appx.Square310x310Logo = GetStringValue(app, "Square310x310Logo");
|
||||||
|
appx.Square70x70Logo = GetStringValue(app, "Square70x70Logo");
|
||||||
|
appx.MinWidth = GetStringValue(app, "MinWidth");
|
||||||
|
package._apps.Add(appx);
|
||||||
|
apps.MoveNext();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
Marshal.ReleaseComObject(strm);
|
||||||
|
}
|
||||||
|
yield return package;
|
||||||
|
}
|
||||||
|
Marshal.ReleaseComObject(factory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (infoBuffer != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(infoBuffer);
|
||||||
|
}
|
||||||
|
ClosePackageInfo(infoRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Guid("5842a140-ff9f-4166-8f5c-62f5b7b0c781"), ComImport]
|
||||||
|
private class AppxFactory
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("BEB94909-E451-438B-B5A7-D79E767B75D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
private interface IAppxFactory
|
||||||
|
{
|
||||||
|
void _VtblGap0_2(); // skip 2 methods
|
||||||
|
IAppxManifestReader CreateManifestReader(IStream inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("4E1BD148-55A0-4480-A3D1-15544710637C"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
private interface IAppxManifestReader
|
||||||
|
{
|
||||||
|
void _VtblGap0_1(); // skip 1 method
|
||||||
|
IAppxManifestProperties GetProperties();
|
||||||
|
void _VtblGap1_5(); // skip 5 methods
|
||||||
|
IAppxManifestApplicationsEnumerator GetApplications();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("9EB8A55A-F04B-4D0D-808D-686185D4847A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
private interface IAppxManifestApplicationsEnumerator
|
||||||
|
{
|
||||||
|
IAppxManifestApplication GetCurrent();
|
||||||
|
bool GetHasCurrent();
|
||||||
|
bool MoveNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("5DA89BF4-3773-46BE-B650-7E744863B7E8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
internal interface IAppxManifestApplication
|
||||||
|
{
|
||||||
|
[PreserveSig]
|
||||||
|
int GetStringValue([MarshalAs(UnmanagedType.LPWStr)] string name, [MarshalAs(UnmanagedType.LPWStr)] out string vaue);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("03FAF64D-F26F-4B2C-AAF7-8FE7789B8BCA"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
private interface IAppxManifestProperties
|
||||||
|
{
|
||||||
|
[PreserveSig]
|
||||||
|
int GetBoolValue([MarshalAs(UnmanagedType.LPWStr)] string name, out bool value);
|
||||||
|
[PreserveSig]
|
||||||
|
int GetStringValue([MarshalAs(UnmanagedType.LPWStr)] string name, [MarshalAs(UnmanagedType.LPWStr)] out string vaue);
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||||
|
private static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, int cchOutBuf, IntPtr ppvReserved);
|
||||||
|
|
||||||
|
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||||
|
private static extern int SHCreateStreamOnFileEx(string fileName, int grfMode, int attributes, bool create, IntPtr reserved, out IStream stream);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
private static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
private static extern bool CloseHandle(IntPtr hObject);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||||
|
private static extern int OpenPackageInfoByFullName(string packageFullName, int reserved, out IntPtr packageInfoReference);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||||
|
private static extern int GetPackageInfo(IntPtr packageInfoReference, PackageConstants flags, ref int bufferLength, IntPtr buffer, out int count);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||||
|
private static extern int ClosePackageInfo(IntPtr packageInfoReference);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||||
|
private static extern int GetPackageFullName(IntPtr hProcess, ref int packageFullNameLength, StringBuilder packageFullName);
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
private enum PackageConstants
|
||||||
|
{
|
||||||
|
PACKAGE_FILTER_ALL_LOADED = 0x00000000,
|
||||||
|
PACKAGE_PROPERTY_FRAMEWORK = 0x00000001,
|
||||||
|
PACKAGE_PROPERTY_RESOURCE = 0x00000002,
|
||||||
|
PACKAGE_PROPERTY_BUNDLE = 0x00000004,
|
||||||
|
PACKAGE_FILTER_HEAD = 0x00000010,
|
||||||
|
PACKAGE_FILTER_DIRECT = 0x00000020,
|
||||||
|
PACKAGE_FILTER_RESOURCE = 0x00000040,
|
||||||
|
PACKAGE_FILTER_BUNDLE = 0x00000080,
|
||||||
|
PACKAGE_INFORMATION_BASIC = 0x00000000,
|
||||||
|
PACKAGE_INFORMATION_FULL = 0x00000100,
|
||||||
|
PACKAGE_PROPERTY_DEVELOPMENT_MODE = 0x00010000,
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||||
|
private struct PACKAGE_INFO
|
||||||
|
{
|
||||||
|
public int reserved;
|
||||||
|
public int flags;
|
||||||
|
public IntPtr path;
|
||||||
|
public IntPtr packageFullName;
|
||||||
|
public IntPtr packageFamilyName;
|
||||||
|
public PACKAGE_ID packageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||||
|
private struct PACKAGE_ID
|
||||||
|
{
|
||||||
|
public int reserved;
|
||||||
|
public AppxPackageArchitecture processorArchitecture;
|
||||||
|
public ushort VersionRevision;
|
||||||
|
public ushort VersionBuild;
|
||||||
|
public ushort VersionMinor;
|
||||||
|
public ushort VersionMajor;
|
||||||
|
public IntPtr name;
|
||||||
|
public IntPtr publisher;
|
||||||
|
public IntPtr resourceId;
|
||||||
|
public IntPtr publisherId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AppxPackageArchitecture
|
||||||
|
{
|
||||||
|
x86 = 0,
|
||||||
|
Arm = 5,
|
||||||
|
x64 = 9,
|
||||||
|
Neutral = 11,
|
||||||
|
Arm64 = 12
|
||||||
|
}
|
@ -3,14 +3,17 @@ using FocusVolumeControl.AudioSessions;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace FocusVolumeControl;
|
namespace FocusVolumeControl.AudioHelpers;
|
||||||
|
|
||||||
public class AudioHelper
|
public class AudioHelper
|
||||||
{
|
{
|
||||||
|
NameAndIconHelper _nameAndIconHelper = new NameAndIconHelper();
|
||||||
|
|
||||||
static object _lock = new object();
|
static object _lock = new object();
|
||||||
int[] _currentProcesses;
|
int[] _currentProcesses;
|
||||||
|
|
||||||
@ -26,106 +29,64 @@ public class AudioHelper
|
|||||||
|
|
||||||
public IAudioSession FindSession(List<Process> processes)
|
public IAudioSession FindSession(List<Process> processes)
|
||||||
{
|
{
|
||||||
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;
|
|
||||||
|
|
||||||
|
|
||||||
manager.GetSessionEnumerator(out var sessionEnumerator);
|
|
||||||
|
|
||||||
var results = new ActiveAudioSessionWrapper();
|
var results = new ActiveAudioSessionWrapper();
|
||||||
var currentIndex = int.MaxValue;
|
Process bestProcessMatch = null;
|
||||||
|
|
||||||
sessionEnumerator.GetCount(out var count);
|
var deviceEnumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
|
deviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active, out var deviceCollection);
|
||||||
|
deviceCollection.GetCount(out var numDevices);
|
||||||
|
for (int d = 0; d < numDevices; d++)
|
||||||
{
|
{
|
||||||
sessionEnumerator.GetSession(i, out var session);
|
deviceCollection.Item(d, out var device);
|
||||||
|
|
||||||
session.GetProcessId(out var sessionProcessId);
|
Guid iid = typeof(IAudioSessionManager2).GUID;
|
||||||
var audioProcess = Process.GetProcessById(sessionProcessId);
|
device.Activate(ref iid, 0, IntPtr.Zero, out var m);
|
||||||
|
var manager = (IAudioSessionManager2)m;
|
||||||
|
|
||||||
var index = processes.FindIndex(x => x.Id == sessionProcessId || x.ProcessName == audioProcess?.ProcessName);
|
|
||||||
|
|
||||||
if (index > -1)
|
manager.GetSessionEnumerator(out var sessionEnumerator);
|
||||||
|
|
||||||
|
var currentIndex = int.MaxValue;
|
||||||
|
|
||||||
|
sessionEnumerator.GetCount(out var count);
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
//processes will be ordered from best to worst (starts with the app, goes to parent)
|
sessionEnumerator.GetSession(i, out var session);
|
||||||
//so we want the display name and executable path to come from the process that is closest to the front of the list
|
|
||||||
//but we want all matching sessions so things like discord work right
|
session.GetProcessId(out var sessionProcessId);
|
||||||
if (index < currentIndex)
|
var audioProcess = Process.GetProcessById(sessionProcessId);
|
||||||
|
|
||||||
|
var index = processes.FindIndex(x => x.Id == sessionProcessId || x.ProcessName == audioProcess?.ProcessName);
|
||||||
|
|
||||||
|
if (index > -1)
|
||||||
{
|
{
|
||||||
(results.DisplayName, results.ExecutablePath) = GetInfo(audioProcess);
|
//processes will be ordered from best to worst (starts with the app, goes to parent)
|
||||||
|
//so we want the display name and executable path to come from the process that is closest to the front of the list
|
||||||
|
//but we want all matching sessions so things like discord work right
|
||||||
|
if (index < currentIndex)
|
||||||
|
{
|
||||||
|
bestProcessMatch = audioProcess;
|
||||||
|
currentIndex = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
//some apps like discord have multiple volume processes.
|
||||||
|
//and some apps will be on multiple devices
|
||||||
|
//so we add all sessions so we can keep them in sync
|
||||||
|
results.AddSession(session);
|
||||||
|
|
||||||
currentIndex = index;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//some apps like discord have multiple volume processes.
|
|
||||||
results.AddSession(session);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(bestProcessMatch != null)
|
||||||
|
{
|
||||||
|
_nameAndIconHelper.SetProcessInfo(bestProcessMatch, results);
|
||||||
|
}
|
||||||
|
|
||||||
return results.Any() ? results : null;
|
return results.Any() ? results : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
(string name, string path) GetInfo(Process process)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var module = process.MainModule;
|
|
||||||
var displayName = module.FileVersionInfo.FileDescription;
|
|
||||||
if (string.IsNullOrEmpty(displayName))
|
|
||||||
{
|
|
||||||
displayName = process.ProcessName;
|
|
||||||
}
|
|
||||||
|
|
||||||
var executablePath = module.FileName;
|
|
||||||
|
|
||||||
return (displayName, executablePath);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return (process.ProcessName, GetExecutablePathBackup(process));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string GetExecutablePathBackup(Process process)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string pathToExe = string.Empty;
|
|
||||||
|
|
||||||
if (process != null)
|
|
||||||
{
|
|
||||||
//use query limited information handle instead of process.handle to prevent permission errors
|
|
||||||
var handle = Native.OpenProcess(0x00001000, false, process.Id);
|
|
||||||
|
|
||||||
var buffer = new StringBuilder(1024);
|
|
||||||
var bufferSize = (uint)buffer.Capacity + 1;
|
|
||||||
var success = Native.QueryFullProcessImageName(handle, 0, buffer, ref bufferSize);
|
|
||||||
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
return buffer.ToString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var error = Marshal.GetLastWin32Error();
|
|
||||||
Logger.Instance.LogMessage(TracingLevel.ERROR, $"Error = {error} getting process name");
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public IAudioSession GetActiveSession(FallbackBehavior fallbackBehavior)
|
public IAudioSession GetActiveSession(FallbackBehavior fallbackBehavior)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
@ -218,59 +179,71 @@ public class AudioHelper
|
|||||||
|
|
||||||
public void ResetAll()
|
public void ResetAll()
|
||||||
{
|
{
|
||||||
var deviceEnumerator = (CoreAudio)new MMDeviceEnumerator();
|
var deviceEnumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
|
||||||
|
|
||||||
deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out var device);
|
deviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active, out var deviceCollection);
|
||||||
|
|
||||||
Guid iid = typeof(IAudioSessionManager2).GUID;
|
deviceCollection.GetCount(out var numDevices);
|
||||||
device.Activate(ref iid, 0, IntPtr.Zero, out var m);
|
for (int d = 0; d < numDevices; d++)
|
||||||
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);
|
deviceCollection.Item(d, out var device);
|
||||||
|
|
||||||
var volume = (ISimpleAudioVolume)session;
|
Guid iid = typeof(IAudioSessionManager2).GUID;
|
||||||
var guid = Guid.Empty;
|
device.Activate(ref iid, 0, IntPtr.Zero, out var m);
|
||||||
volume.SetMasterVolume(1, ref guid);
|
var manager = (IAudioSessionManager2)m;
|
||||||
volume.SetMute(false, ref guid);
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAudioSession GetSystemSounds()
|
public IAudioSession GetSystemSounds()
|
||||||
{
|
{
|
||||||
var deviceEnumerator = (CoreAudio)new MMDeviceEnumerator();
|
var deviceEnumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
|
||||||
|
deviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active, out var deviceCollection);
|
||||||
|
|
||||||
deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out var device);
|
deviceCollection.GetCount(out var numDevices);
|
||||||
|
for (int d = 0; d < numDevices; d++)
|
||||||
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);
|
deviceCollection.Item(d, out var device);
|
||||||
|
|
||||||
if (session.IsSystemSoundsSession() == 0)
|
|
||||||
|
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++)
|
||||||
{
|
{
|
||||||
return new SystemSoundsAudioSession(session);
|
sessionEnumerator.GetSession(i, out var session);
|
||||||
|
|
||||||
|
if (session.IsSystemSoundsSession() == 0)
|
||||||
|
{
|
||||||
|
return new SystemSoundsAudioSession(session);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
public IAudioSession GetSystemVolume()
|
public IAudioSession GetSystemVolume()
|
||||||
{
|
{
|
||||||
var deviceEnumerator = (CoreAudio)new MMDeviceEnumerator();
|
var deviceEnumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
|
||||||
|
|
||||||
deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out var device);
|
deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia, out var device);
|
||||||
|
|
||||||
Guid iid = typeof(IAudioEndpointVolume).GUID;
|
Guid iid = typeof(IAudioEndpointVolume).GUID;
|
||||||
device.Activate(ref iid, 0, IntPtr.Zero, out var o);
|
device.Activate(ref iid, 0, IntPtr.Zero, out var o);
|
116
src/FocusVolumeControl/AudioHelpers/NameAndIconHelper.cs
Normal file
116
src/FocusVolumeControl/AudioHelpers/NameAndIconHelper.cs
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
using BarRaider.SdTools;
|
||||||
|
using FocusVolumeControl.AudioSessions;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace FocusVolumeControl.AudioHelpers;
|
||||||
|
|
||||||
|
public class NameAndIconHelper
|
||||||
|
{
|
||||||
|
public (string name, string icon) GetProcessInfo(Process process)
|
||||||
|
{
|
||||||
|
//i know this is dumb, but its only used by the sound browser, not real prod code
|
||||||
|
var blah = new ActiveAudioSessionWrapper();
|
||||||
|
SetProcessInfo(process, blah);
|
||||||
|
return (blah.DisplayName, blah.IconPath ?? blah.ExecutablePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetProcessInfo(Process process, ActiveAudioSessionWrapper results)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//appx packages are installed from the windows store. eg, itunes
|
||||||
|
var appx = AppxPackage.FromProcess(process);
|
||||||
|
if (appx == null)
|
||||||
|
{
|
||||||
|
//usingg process.MainModule.FileVersionInfo sometimes throws permission exceptions
|
||||||
|
//we get the file version info with a limited query flag to avoid that
|
||||||
|
var fileVersionInfo = GetFileVersionInfo(process);
|
||||||
|
|
||||||
|
results.DisplayName = process.MainWindowTitle;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(results.DisplayName))
|
||||||
|
{
|
||||||
|
results.DisplayName = fileVersionInfo?.FileDescription;
|
||||||
|
if (string.IsNullOrEmpty(results.DisplayName))
|
||||||
|
{
|
||||||
|
results.DisplayName = process.ProcessName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results.ExecutablePath = fileVersionInfo?.FileName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
results.DisplayName = appx.DisplayName;
|
||||||
|
results.IconPath = Path.Combine(appx.Path, appx.Logo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
//if anything threw an exception, set the display name to the process name, and just let the
|
||||||
|
// icon/executable path be blank and the stream deck will just show the default icon
|
||||||
|
if (string.IsNullOrEmpty(results.DisplayName))
|
||||||
|
{
|
||||||
|
results.DisplayName = process.ProcessName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileVersionInfo GetFileVersionInfo(Process process)
|
||||||
|
{
|
||||||
|
var path = GetExecutablePathWithPInvoke(process);
|
||||||
|
if (!string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
return FileVersionInfo.GetVersionInfo(path);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetExecutablePathWithPInvoke(Process process)
|
||||||
|
{
|
||||||
|
IntPtr processHandle = IntPtr.Zero;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string pathToExe = string.Empty;
|
||||||
|
|
||||||
|
if (process != null)
|
||||||
|
{
|
||||||
|
//use query limited information handle instead of process.handle to prevent permission errors
|
||||||
|
processHandle = Native.OpenProcess(0x00001000, false, process.Id);
|
||||||
|
|
||||||
|
var buffer = new StringBuilder(1024);
|
||||||
|
var bufferSize = (uint)buffer.Capacity + 1;
|
||||||
|
var success = Native.QueryFullProcessImageName(processHandle, 0, buffer, ref bufferSize);
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
return buffer.ToString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var error = Marshal.GetLastWin32Error();
|
||||||
|
Logger.Instance.LogMessage(TracingLevel.ERROR, $"Error = {error} getting process name");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if(processHandle != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Native.CloseHandle(processHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace FocusVolumeControl;
|
namespace FocusVolumeControl.AudioHelpers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A utility class to determine a process parent.
|
/// A utility class to determine a process parent.
|
@ -11,6 +11,7 @@ public sealed class ActiveAudioSessionWrapper : IAudioSession
|
|||||||
{
|
{
|
||||||
public string DisplayName { get; set; }
|
public string DisplayName { get; set; }
|
||||||
public string ExecutablePath { get; set; }
|
public string ExecutablePath { get; set; }
|
||||||
|
public string IconPath { get; set; }
|
||||||
private List<IAudioSessionControl2> Sessions { get; } = new List<IAudioSessionControl2>();
|
private List<IAudioSessionControl2> Sessions { get; } = new List<IAudioSessionControl2>();
|
||||||
private IEnumerable<ISimpleAudioVolume> Volume => Sessions.Cast<ISimpleAudioVolume>();
|
private IEnumerable<ISimpleAudioVolume> Volume => Sessions.Cast<ISimpleAudioVolume>();
|
||||||
|
|
||||||
@ -22,8 +23,17 @@ public sealed class ActiveAudioSessionWrapper : IAudioSession
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tmp = Icon.ExtractAssociatedIcon(ExecutablePath);
|
if(!string.IsNullOrEmpty(IconPath))
|
||||||
_icon = Tools.ImageToBase64(tmp.ToBitmap(), true);
|
{
|
||||||
|
var tmp = (Bitmap)Bitmap.FromFile(IconPath);
|
||||||
|
tmp.MakeTransparent();
|
||||||
|
_icon = Tools.ImageToBase64(tmp, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var tmp = Icon.ExtractAssociatedIcon(ExecutablePath);
|
||||||
|
_icon = Tools.ImageToBase64(tmp.ToBitmap(), true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Data;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace FocusVolumeControl.AudioSessions;
|
namespace FocusVolumeControl.AudioSessions;
|
||||||
@ -6,44 +7,66 @@ namespace FocusVolumeControl.AudioSessions;
|
|||||||
|
|
||||||
[ComImport]
|
[ComImport]
|
||||||
[Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
|
[Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
|
||||||
internal class MMDeviceEnumerator
|
public class MMDeviceEnumerator
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum EDataFlow
|
public enum DataFlow
|
||||||
{
|
{
|
||||||
eRender,
|
Render,
|
||||||
eCapture,
|
Capture,
|
||||||
eAll,
|
All,
|
||||||
EDataFlow_enum_count
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum ERole
|
public enum Role
|
||||||
{
|
{
|
||||||
eConsole,
|
Console,
|
||||||
eMultimedia,
|
Multimedia,
|
||||||
eCommunications,
|
Communications,
|
||||||
ERole_enum_count
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum DeviceState : uint
|
||||||
|
{
|
||||||
|
Active = 1 << 0,
|
||||||
|
Disabled = 1 << 1,
|
||||||
|
NotPresent = 1 << 2,
|
||||||
|
Unplugged = 1 << 3,
|
||||||
|
MaskAll = 0xFu
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E")]
|
||||||
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
public interface IMMDeviceCollection
|
||||||
|
{
|
||||||
|
[PreserveSig]
|
||||||
|
int GetCount(out int nDevices);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int Item(int nDevice, out IMMDevice Device);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
internal interface CoreAudio
|
public interface IMMDeviceEnumerator
|
||||||
{
|
{
|
||||||
int NotImpl1();
|
|
||||||
|
|
||||||
[PreserveSig]
|
[PreserveSig]
|
||||||
int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppDevice);
|
int EnumAudioEndpoints(DataFlow dataFlow, DeviceState StateMask, out IMMDeviceCollection deviceCollection);
|
||||||
|
|
||||||
|
[PreserveSig]
|
||||||
|
int GetDefaultAudioEndpoint(DataFlow dataFlow, Role role, out IMMDevice device);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
internal interface IMMDevice
|
public interface IMMDevice
|
||||||
{
|
{
|
||||||
[PreserveSig]
|
[PreserveSig]
|
||||||
int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
|
int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
[Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
internal interface IAudioSessionManager2
|
public interface IAudioSessionManager2
|
||||||
{
|
{
|
||||||
int NotImpl1();
|
int NotImpl1();
|
||||||
int NotImpl2();
|
int NotImpl2();
|
||||||
@ -53,7 +76,7 @@ internal interface IAudioSessionManager2
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
[Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
internal interface IAudioSessionEnumerator
|
public interface IAudioSessionEnumerator
|
||||||
{
|
{
|
||||||
[PreserveSig]
|
[PreserveSig]
|
||||||
int GetCount(out int SessionCount);
|
int GetCount(out int SessionCount);
|
||||||
@ -63,7 +86,7 @@ internal interface IAudioSessionEnumerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
[Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
internal interface ISimpleAudioVolume
|
public interface ISimpleAudioVolume
|
||||||
{
|
{
|
||||||
[PreserveSig]
|
[PreserveSig]
|
||||||
int SetMasterVolume(float fLevel, ref Guid EventContext);
|
int SetMasterVolume(float fLevel, ref Guid EventContext);
|
||||||
|
@ -54,8 +54,9 @@
|
|||||||
<Reference Include="WindowsBase" />
|
<Reference Include="WindowsBase" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="AudioHelpers\AppxPackage.cs" />
|
||||||
<Compile Include="AudioSessions\ActiveAudioSessionWrapper.cs" />
|
<Compile Include="AudioSessions\ActiveAudioSessionWrapper.cs" />
|
||||||
<Compile Include="AudioHelper.cs" />
|
<Compile Include="AudioHelpers\AudioHelper.cs" />
|
||||||
<Compile Include="AudioSessions\CoreAudio.cs" />
|
<Compile Include="AudioSessions\CoreAudio.cs" />
|
||||||
<Compile Include="AudioSessions\VolumeHelpers.cs" />
|
<Compile Include="AudioSessions\VolumeHelpers.cs" />
|
||||||
<Compile Include="AudioSessions\SystemSoundsAudioSession.cs" />
|
<Compile Include="AudioSessions\SystemSoundsAudioSession.cs" />
|
||||||
@ -63,9 +64,10 @@
|
|||||||
<Compile Include="DialAction.cs" />
|
<Compile Include="DialAction.cs" />
|
||||||
<Compile Include="AudioSessions\IAudioSession.cs" />
|
<Compile Include="AudioSessions\IAudioSession.cs" />
|
||||||
<Compile Include="FallbackBehavior.cs" />
|
<Compile Include="FallbackBehavior.cs" />
|
||||||
|
<Compile Include="AudioHelpers\NameAndIconHelper.cs" />
|
||||||
<Compile Include="UI\ISDConnectionExtensions.cs" />
|
<Compile Include="UI\ISDConnectionExtensions.cs" />
|
||||||
<Compile Include="Native.cs" />
|
<Compile Include="Native.cs" />
|
||||||
<Compile Include="ParentProcessUtilities.cs" />
|
<Compile Include="AudioHelpers\ParentProcessUtilities.cs" />
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="UI\UIState.cs" />
|
<Compile Include="UI\UIState.cs" />
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using FocusVolumeControl.AudioHelpers;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -67,4 +68,8 @@ public class Native
|
|||||||
[DllImport("kernel32.dll")]
|
[DllImport("kernel32.dll")]
|
||||||
public static extern IntPtr OpenProcess(uint processAccess, bool inheritHandle, int processId);
|
public static extern IntPtr OpenProcess(uint processAccess, bool inheritHandle, int processId);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
public static extern bool CloseHandle(IntPtr hObject);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<select class="sdpi-item-value sdProperty" id="fallbackBehavior" oninput="setSettings()">
|
<select class="sdpi-item-value sdProperty" id="fallbackBehavior" oninput="setSettings()">
|
||||||
<option value="0">System Sounds</option>
|
<option value="0">System Sounds</option>
|
||||||
<option value="1">Previous App</option>
|
<option value="1">Previous App</option>
|
||||||
<option value="2">Main System Volume</option>
|
<option value="2">Default Output Device Volume</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -38,7 +38,7 @@
|
|||||||
<p>If you look at windows volume mixer, you will see that not all applications can have their volume controlled. The fallback behavior controls what happens when you are in an application that doesn't show up in the volume mixer</p>
|
<p>If you look at windows volume mixer, you will see that not all applications can have their volume controlled. The fallback behavior controls what happens when you are in an application that doesn't show up in the volume mixer</p>
|
||||||
<p>* System Sounds - Switch to system sounds. This will control windows sound effects such as when an error sound plays. If you're in an application that is making beeping sounds, this will often allow you to control those sounds while leaving things like your music/videos alone</p>
|
<p>* System Sounds - Switch to system sounds. This will control windows sound effects such as when an error sound plays. If you're in an application that is making beeping sounds, this will often allow you to control those sounds while leaving things like your music/videos alone</p>
|
||||||
<p>* Previous App - Use the last app that had a volume control. This can result in the stream deck not changing after you have quit an application.</p>
|
<p>* Previous App - Use the last app that had a volume control. This can result in the stream deck not changing after you have quit an application.</p>
|
||||||
<p>* Main System Volume - Switch to the main volume control for the system. This will change the volume of all applications</p>
|
<p>* Default Output Device Volume - Switch to the main volume control for the default output device. This will change the volume of the default output device. This is usually volume for all applications, unless you override the output device for specific applications.</p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
"Name": "Focused Application Volume",
|
"Name": "Focused Application Volume",
|
||||||
"Description": "Control the volume of the focused application",
|
"Description": "Control the volume of the focused application",
|
||||||
"URL": "https://github.com/dlprows/FocusVolumeControl",
|
"URL": "https://github.com/dlprows/FocusVolumeControl",
|
||||||
"Version": "1.1.2",
|
"Version": "1.2.0",
|
||||||
"CodePath": "FocusVolumeControl",
|
"CodePath": "FocusVolumeControl",
|
||||||
"Category": "Volume Control [dlprows]",
|
"Category": "Volume Control [dlprows]",
|
||||||
"Icon": "Images/pluginIcon",
|
"Icon": "Images/pluginIcon",
|
||||||
|
@ -6,14 +6,16 @@
|
|||||||
xmlns:local="clr-namespace:SoundBrowser"
|
xmlns:local="clr-namespace:SoundBrowser"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="MainWindow" Height="800" Width="800">
|
Title="MainWindow" Height="800" Width="800">
|
||||||
<Grid>
|
<ScrollViewer>
|
||||||
<Grid.RowDefinitions>
|
<Grid>
|
||||||
<RowDefinition Height="auto"/>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="auto"/>
|
||||||
</Grid.RowDefinitions>
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<TextBlock x:Name="_tf" Grid.Row="0">current</TextBlock>
|
<TextBlock x:Name="_tf" Grid.Row="0">current</TextBlock>
|
||||||
<TextBlock x:Name="_tf2" Grid.Row="1">list</TextBlock>
|
<TextBlock x:Name="_tf2" Grid.Row="1">list</TextBlock>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
</Window>
|
</Window>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using CoreAudio;
|
using FocusVolumeControl;
|
||||||
using FocusVolumeControl;
|
using FocusVolumeControl.AudioHelpers;
|
||||||
|
using FocusVolumeControl.AudioSessions;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@ -56,18 +57,11 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
foreach (var p in processes)
|
foreach (var p in processes)
|
||||||
{
|
{
|
||||||
|
var (displayName, _) = (new NameAndIconHelper()).GetProcessInfo(p);
|
||||||
|
|
||||||
sb.AppendLine($"pid: {p.Id}");
|
sb.AppendLine($"pid: {p.Id}");
|
||||||
sb.AppendLine($"\tprocessName: {p.ProcessName}");
|
sb.AppendLine($"\tprocessName: {p.ProcessName}");
|
||||||
try
|
sb.AppendLine($"\tDisplayName: {displayName}");
|
||||||
{
|
|
||||||
sb.AppendLine($"\tFileDescription: {p!.MainModule!.FileVersionInfo.FileDescription}");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
sb.AppendLine("\tFileDescription: ##ERROR##");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,26 +87,39 @@ public partial class MainWindow : Window
|
|||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine("-------------------------------------------------------------------------------");
|
sb.AppendLine("-------------------------------------------------------------------------------");
|
||||||
|
|
||||||
var deviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid());
|
var deviceEnumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
|
||||||
|
|
||||||
using var device = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
|
deviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active, out var deviceCollection);
|
||||||
using var manager = device.AudioSessionManager2;
|
deviceCollection.GetCount(out var num);
|
||||||
|
|
||||||
var sessions = manager!.Sessions;
|
for(int i = 0; i < num; i++)
|
||||||
|
|
||||||
foreach (var session in sessions!)
|
|
||||||
{
|
{
|
||||||
var audioProcess = Process.GetProcessById((int)session.ProcessID);
|
deviceCollection.Item(i, out var device);
|
||||||
|
//todo: put the device name in the output
|
||||||
|
sb.AppendLine("----");
|
||||||
|
|
||||||
var displayName = audioProcess!.MainModule!.FileVersionInfo.FileDescription;
|
Guid iid = typeof(IAudioSessionManager2).GUID;
|
||||||
|
device.Activate(ref iid, 0, IntPtr.Zero, out var m);
|
||||||
|
var manager = (IAudioSessionManager2)m;
|
||||||
|
|
||||||
sb.AppendLine($"pid: {audioProcess.Id}");
|
|
||||||
sb.AppendLine($"\tprocessName: {audioProcess.ProcessName}");
|
manager.GetSessionEnumerator(out var sessionEnumerator);
|
||||||
sb.AppendLine($"\tsession: {displayName}");
|
|
||||||
|
|
||||||
|
sessionEnumerator.GetCount(out var count);
|
||||||
|
for (int s = 0; s < count; s++)
|
||||||
|
{
|
||||||
|
sessionEnumerator.GetSession(s, out var session);
|
||||||
|
|
||||||
|
session.GetProcessId(out var processId);
|
||||||
|
var audioProcess = Process.GetProcessById(processId);
|
||||||
|
|
||||||
|
var (displayName, _) = (new NameAndIconHelper()).GetProcessInfo(audioProcess);
|
||||||
|
sb.AppendLine($"pid: {audioProcess.Id}\t\t processName: {displayName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_tf2.Text = sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
_tf2.Text = sb.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,6 @@
|
|||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="CoreAudio" Version="1.27.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\FocusVolumeControl\FocusVolumeControl.csproj" />
|
<ProjectReference Include="..\FocusVolumeControl\FocusVolumeControl.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
Loading…
Reference in New Issue
Block a user