Compare commits
	
		
			6 Commits
		
	
	
		
			6de76da8ad
			...
			playWithAp
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f1d7aeed5d | |||
| 0f7f1fffcd | |||
| aa905fe443 | |||
| 160bedd461 | |||
| 68d5154756 | |||
| 8eebf1af47 | 
@ -109,6 +109,13 @@ public sealed class AppxPackage
 | 
			
		||||
							var properties = reader.GetProperties();
 | 
			
		||||
 | 
			
		||||
							properties.GetStringValue("DisplayName", out var displayName);
 | 
			
		||||
 | 
			
		||||
							if(displayName.StartsWith("ms-resource:"))
 | 
			
		||||
							{
 | 
			
		||||
								var packageFullName = Marshal.PtrToStringUni(info.packageFullName);
 | 
			
		||||
								displayName = LoadResourceString(fullName, displayName);
 | 
			
		||||
 | 
			
		||||
							}
 | 
			
		||||
							package.DisplayName = displayName;
 | 
			
		||||
 | 
			
		||||
							properties.GetStringValue("Logo", out var logo);
 | 
			
		||||
@ -158,6 +165,40 @@ public sealed class AppxPackage
 | 
			
		||||
				ClosePackageInfo(infoRef);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static string LoadResourceString(string packageFullName, string resource)
 | 
			
		||||
	{
 | 
			
		||||
		if (packageFullName == null)
 | 
			
		||||
			throw new ArgumentNullException("packageFullName");
 | 
			
		||||
 | 
			
		||||
		if (string.IsNullOrWhiteSpace(resource))
 | 
			
		||||
			return null;
 | 
			
		||||
 | 
			
		||||
		const string resourceScheme = "ms-resource:";
 | 
			
		||||
		if (!resource.StartsWith(resourceScheme))
 | 
			
		||||
			return null;
 | 
			
		||||
 | 
			
		||||
		string part = resource.Substring(resourceScheme.Length);
 | 
			
		||||
		string url;
 | 
			
		||||
 | 
			
		||||
		if (part.StartsWith("/"))
 | 
			
		||||
		{
 | 
			
		||||
			url = resourceScheme + "//" + part;
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			url = resourceScheme + "///resources/" + part;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		string source = string.Format("@{{{0}? {1}}}", packageFullName, url);
 | 
			
		||||
		var sb = new StringBuilder(1024);
 | 
			
		||||
		int i = SHLoadIndirectString(source, sb, sb.Capacity, IntPtr.Zero);
 | 
			
		||||
		if (i != 0)
 | 
			
		||||
			return null;
 | 
			
		||||
 | 
			
		||||
		return sb.ToString();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace FocusVolumeControl.AudioHelpers;
 | 
			
		||||
 | 
			
		||||
@ -12,6 +13,7 @@ public class AudioHelper
 | 
			
		||||
 | 
			
		||||
	static object _lock = new object();
 | 
			
		||||
	int[] _currentProcesses;
 | 
			
		||||
	int _retryFallbackCount = 0;
 | 
			
		||||
 | 
			
		||||
	public IAudioSession Current { get; private set; }
 | 
			
		||||
 | 
			
		||||
@ -37,6 +39,7 @@ public class AudioHelper
 | 
			
		||||
 | 
			
		||||
	public IAudioSession FindSession(List<Process> processes)
 | 
			
		||||
	{
 | 
			
		||||
		//var blah = new AudioPolicyConfigFactoryImplFor21H2();
 | 
			
		||||
		var results = new ActiveAudioSessionWrapper();
 | 
			
		||||
		Process bestProcessMatch = null;
 | 
			
		||||
 | 
			
		||||
@ -49,9 +52,10 @@ public class AudioHelper
 | 
			
		||||
			deviceCollection.Item(d, out var device);
 | 
			
		||||
 | 
			
		||||
			Guid iid = typeof(IAudioSessionManager2).GUID;
 | 
			
		||||
			device.Activate(ref iid, 0, IntPtr.Zero, out var m);
 | 
			
		||||
			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);
 | 
			
		||||
 | 
			
		||||
@ -70,6 +74,16 @@ public class AudioHelper
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				/*
 | 
			
		||||
				blah.GetPersistedDefaultAudioEndpoint(sessionProcessId, DataFlow.Render, Role.Multimedia, out var persistedDeviceId);
 | 
			
		||||
				persistedDeviceId = UnpackDeviceId(persistedDeviceId);
 | 
			
		||||
				if(!string.IsNullOrEmpty(persistedDeviceId) && persistedDeviceId != currentDeviceId)
 | 
			
		||||
				{
 | 
			
		||||
					continue;
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
				*/
 | 
			
		||||
 | 
			
		||||
				var index = processes.FindIndex(x => x.Id == sessionProcessId || x.ProcessName == audioProcess?.ProcessName);
 | 
			
		||||
 | 
			
		||||
				if (index > -1)
 | 
			
		||||
@ -114,10 +128,18 @@ public class AudioHelper
 | 
			
		||||
			var processes = GetPossibleProcesses();
 | 
			
		||||
			var processIds = processes?.Select(x => x.Id).ToArray();
 | 
			
		||||
 | 
			
		||||
			if (_currentProcesses == null || !_currentProcesses.SequenceEqual(processIds))
 | 
			
		||||
			//_currentProcesses null - first time getting sessions
 | 
			
		||||
			//_currentProcesses not equal to processIds - changed the active process
 | 
			
		||||
			//_retryFallbackCount - some processes like chrome or minecraft will start their audio process when they first try to do some sound stuff
 | 
			
		||||
			if (_currentProcesses == null || !_currentProcesses.SequenceEqual(processIds) || _retryFallbackCount == 5)
 | 
			
		||||
			{
 | 
			
		||||
				_retryFallbackCount = 0;
 | 
			
		||||
				Current = FindSession(processes);
 | 
			
		||||
			}
 | 
			
		||||
			else if(Current is SystemSoundsAudioSession || Current is SystemVolumeAudioSession)
 | 
			
		||||
			{
 | 
			
		||||
				_retryFallbackCount++;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (Current == null)
 | 
			
		||||
			{
 | 
			
		||||
@ -214,7 +236,7 @@ public class AudioHelper
 | 
			
		||||
			deviceCollection.Item(d, out var device);
 | 
			
		||||
 | 
			
		||||
			Guid iid = typeof(IAudioSessionManager2).GUID;
 | 
			
		||||
			device.Activate(ref iid, 0, IntPtr.Zero, out var m);
 | 
			
		||||
			device.Activate(ref iid, CLSCTX.ALL, IntPtr.Zero, out var m);
 | 
			
		||||
			var manager = (IAudioSessionManager2)m;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -245,7 +267,7 @@ public class AudioHelper
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
			Guid iid = typeof(IAudioSessionManager2).GUID;
 | 
			
		||||
			device.Activate(ref iid, 0, IntPtr.Zero, out var m);
 | 
			
		||||
			device.Activate(ref iid, CLSCTX.ALL, IntPtr.Zero, out var m);
 | 
			
		||||
			var manager = (IAudioSessionManager2)m;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -271,10 +293,130 @@ public class AudioHelper
 | 
			
		||||
		deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia, out var device);
 | 
			
		||||
 | 
			
		||||
		Guid iid = typeof(IAudioEndpointVolume).GUID;
 | 
			
		||||
		device.Activate(ref iid, 0, IntPtr.Zero, out var o);
 | 
			
		||||
		device.Activate(ref iid, CLSCTX.ALL, IntPtr.Zero, out var o);
 | 
			
		||||
		var endpointVolume = (IAudioEndpointVolume)o;
 | 
			
		||||
 | 
			
		||||
		return new SystemVolumeAudioSession(endpointVolume);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	class AudioPolicyConfigFactoryImplFor21H2
 | 
			
		||||
	{
 | 
			
		||||
		private readonly IAudioPolicyConfigFactoryVariantFor21H2 _factory;
 | 
			
		||||
 | 
			
		||||
		internal AudioPolicyConfigFactoryImplFor21H2()
 | 
			
		||||
		{
 | 
			
		||||
			var iid = typeof(IAudioPolicyConfigFactoryVariantFor21H2).GUID;
 | 
			
		||||
			Combase.RoGetActivationFactory("Windows.Media.Internal.AudioPolicyConfig", ref iid, out object factory);
 | 
			
		||||
			_factory = (IAudioPolicyConfigFactoryVariantFor21H2)factory;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public uint ClearAllPersistedApplicationDefaultEndpoints()
 | 
			
		||||
		{
 | 
			
		||||
			return _factory.ClearAllPersistedApplicationDefaultEndpoints();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public uint GetPersistedDefaultAudioEndpoint(int processId, DataFlow flow, Role role, out string deviceId)
 | 
			
		||||
		{
 | 
			
		||||
			return _factory.GetPersistedDefaultAudioEndpoint(processId, flow, role, out deviceId);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public uint SetPersistedDefaultAudioEndpoint(int processId, DataFlow flow, Role role, IntPtr deviceId)
 | 
			
		||||
		{
 | 
			
		||||
			return _factory.SetPersistedDefaultAudioEndpoint(processId, flow, role, deviceId);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	[Guid("ab3d4648-e242-459f-b02f-541c70306324")]
 | 
			
		||||
	[InterfaceType(ComInterfaceType.InterfaceIsIInspectable)]
 | 
			
		||||
	public interface IAudioPolicyConfigFactoryVariantFor21H2
 | 
			
		||||
	{
 | 
			
		||||
		int __incomplete__add_CtxVolumeChange();
 | 
			
		||||
		int __incomplete__remove_CtxVolumeChanged();
 | 
			
		||||
		int __incomplete__add_RingerVibrateStateChanged();
 | 
			
		||||
		int __incomplete__remove_RingerVibrateStateChange();
 | 
			
		||||
		int __incomplete__SetVolumeGroupGainForId();
 | 
			
		||||
		int __incomplete__GetVolumeGroupGainForId();
 | 
			
		||||
		int __incomplete__GetActiveVolumeGroupForEndpointId();
 | 
			
		||||
		int __incomplete__GetVolumeGroupsForEndpoint();
 | 
			
		||||
		int __incomplete__GetCurrentVolumeContext();
 | 
			
		||||
		int __incomplete__SetVolumeGroupMuteForId();
 | 
			
		||||
		int __incomplete__GetVolumeGroupMuteForId();
 | 
			
		||||
		int __incomplete__SetRingerVibrateState();
 | 
			
		||||
		int __incomplete__GetRingerVibrateState();
 | 
			
		||||
		int __incomplete__SetPreferredChatApplication();
 | 
			
		||||
		int __incomplete__ResetPreferredChatApplication();
 | 
			
		||||
		int __incomplete__GetPreferredChatApplication();
 | 
			
		||||
		int __incomplete__GetCurrentChatApplications();
 | 
			
		||||
		int __incomplete__add_ChatContextChanged();
 | 
			
		||||
		int __incomplete__remove_ChatContextChanged();
 | 
			
		||||
		[PreserveSig]
 | 
			
		||||
		uint SetPersistedDefaultAudioEndpoint(int processId, DataFlow flow, Role role, IntPtr deviceId);
 | 
			
		||||
		[PreserveSig]
 | 
			
		||||
		uint GetPersistedDefaultAudioEndpoint(int processId, DataFlow flow, Role role, [Out, MarshalAs(UnmanagedType.HString)] out string deviceId);
 | 
			
		||||
		[PreserveSig]
 | 
			
		||||
		uint ClearAllPersistedApplicationDefaultEndpoints();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[Guid("2a59116d-6c4f-45e0-a74f-707e3fef9258")]
 | 
			
		||||
	[InterfaceType(ComInterfaceType.InterfaceIsIInspectable)]
 | 
			
		||||
	public interface IAudioPolicyConfigFactoryVariantForDownlevel
 | 
			
		||||
	{
 | 
			
		||||
		int __incomplete__add_CtxVolumeChange();
 | 
			
		||||
		int __incomplete__remove_CtxVolumeChanged();
 | 
			
		||||
		int __incomplete__add_RingerVibrateStateChanged();
 | 
			
		||||
		int __incomplete__remove_RingerVibrateStateChange();
 | 
			
		||||
		int __incomplete__SetVolumeGroupGainForId();
 | 
			
		||||
		int __incomplete__GetVolumeGroupGainForId();
 | 
			
		||||
		int __incomplete__GetActiveVolumeGroupForEndpointId();
 | 
			
		||||
		int __incomplete__GetVolumeGroupsForEndpoint();
 | 
			
		||||
		int __incomplete__GetCurrentVolumeContext();
 | 
			
		||||
		int __incomplete__SetVolumeGroupMuteForId();
 | 
			
		||||
		int __incomplete__GetVolumeGroupMuteForId();
 | 
			
		||||
		int __incomplete__SetRingerVibrateState();
 | 
			
		||||
		int __incomplete__GetRingerVibrateState();
 | 
			
		||||
		int __incomplete__SetPreferredChatApplication();
 | 
			
		||||
		int __incomplete__ResetPreferredChatApplication();
 | 
			
		||||
		int __incomplete__GetPreferredChatApplication();
 | 
			
		||||
		int __incomplete__GetCurrentChatApplications();
 | 
			
		||||
		int __incomplete__add_ChatContextChanged();
 | 
			
		||||
		int __incomplete__remove_ChatContextChanged();
 | 
			
		||||
		[PreserveSig]
 | 
			
		||||
		uint SetPersistedDefaultAudioEndpoint(int processId, DataFlow flow, Role role, IntPtr deviceId);
 | 
			
		||||
		[PreserveSig]
 | 
			
		||||
		uint GetPersistedDefaultAudioEndpoint(int processId, DataFlow flow, Role role, [Out, MarshalAs(UnmanagedType.HString)] out string deviceId);
 | 
			
		||||
		[PreserveSig]
 | 
			
		||||
		uint ClearAllPersistedApplicationDefaultEndpoints();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static class Combase
 | 
			
		||||
	{
 | 
			
		||||
		[DllImport("combase.dll", PreserveSig = false)]
 | 
			
		||||
		public static extern void RoGetActivationFactory(
 | 
			
		||||
			[MarshalAs(UnmanagedType.HString)] string activatableClassId,
 | 
			
		||||
			[In] ref Guid iid,
 | 
			
		||||
			[Out, MarshalAs(UnmanagedType.IInspectable)] out Object factory);
 | 
			
		||||
 | 
			
		||||
		[DllImport("combase.dll", PreserveSig = false)]
 | 
			
		||||
		public static extern void WindowsCreateString(
 | 
			
		||||
			[MarshalAs(UnmanagedType.LPWStr)] string src,
 | 
			
		||||
			[In] uint length,
 | 
			
		||||
			[Out] out IntPtr hstring);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private const string DEVINTERFACE_AUDIO_RENDER = "#{e6327cad-dcec-4949-ae8a-991e976a79d2}";
 | 
			
		||||
	private const string DEVINTERFACE_AUDIO_CAPTURE = "#{2eef81be-33fa-4800-9670-1cd474972c3f}";
 | 
			
		||||
	private const string MMDEVAPI_TOKEN = @"\\?\SWD#MMDEVAPI#";
 | 
			
		||||
	private string UnpackDeviceId(string deviceId)
 | 
			
		||||
	{
 | 
			
		||||
		if (deviceId.StartsWith(MMDEVAPI_TOKEN)) deviceId = deviceId.Remove(0, MMDEVAPI_TOKEN.Length);
 | 
			
		||||
		if (deviceId.EndsWith(DEVINTERFACE_AUDIO_RENDER)) deviceId = deviceId.Remove(deviceId.Length - DEVINTERFACE_AUDIO_RENDER.Length);
 | 
			
		||||
		if (deviceId.EndsWith(DEVINTERFACE_AUDIO_CAPTURE)) deviceId = deviceId.Remove(deviceId.Length - DEVINTERFACE_AUDIO_CAPTURE.Length);
 | 
			
		||||
		return deviceId;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,10 @@
 | 
			
		||||
using BarRaider.SdTools;
 | 
			
		||||
using FocusVolumeControl.AudioSession;
 | 
			
		||||
using FocusVolumeControl.AudioSessions;
 | 
			
		||||
using FocusVolumeControl.UI;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using System.Text;
 | 
			
		||||
@ -10,12 +13,12 @@ namespace FocusVolumeControl.AudioHelpers;
 | 
			
		||||
 | 
			
		||||
public class NameAndIconHelper
 | 
			
		||||
{
 | 
			
		||||
	public (string name, string icon) GetProcessInfo(Process process)
 | 
			
		||||
	public string 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);
 | 
			
		||||
		return blah.DisplayName;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void SetProcessInfo(Process process, ActiveAudioSessionWrapper results)
 | 
			
		||||
@ -45,12 +48,27 @@ public class NameAndIconHelper
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				results.ExecutablePath = fileVersionInfo?.FileName;
 | 
			
		||||
				//for java apps (minecraft), the process will just have a java icon
 | 
			
		||||
				//and there's not just a file that you can get the real icon from
 | 
			
		||||
				//so you have to send some messages to the apps to get the icons.
 | 
			
		||||
				//but they will only be 32x32 (or smaller) so we only want to use this logic for java
 | 
			
		||||
				//because these will be lower resolution than the normal way of getting icons
 | 
			
		||||
				if (process.ProcessName == "javaw" || process.ProcessName == "java" || process.ProcessName == "dotnet")
 | 
			
		||||
				{
 | 
			
		||||
					var windowHandle = process.MainWindowHandle;
 | 
			
		||||
					var lazyIcon = () => JavaIconExtractor.GetWindowBigIconWithRetry(windowHandle);
 | 
			
		||||
					results.IconWrapper = new RawIcon(windowHandle.ToString(), lazyIcon);
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					results.IconWrapper = new NormalIcon(fileVersionInfo?.FileName);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				results.DisplayName = appx.DisplayName;
 | 
			
		||||
				results.IconPath = Path.Combine(appx.Path, appx.Logo);
 | 
			
		||||
				results.IconWrapper = new AppxIcon(Path.Combine(appx.Path, appx.Logo));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		catch { }
 | 
			
		||||
 | 
			
		||||
@ -6,55 +6,27 @@ using System.Drawing;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using FocusVolumeControl.UI;
 | 
			
		||||
using BitFaster.Caching.Lru;
 | 
			
		||||
using FocusVolumeControl.AudioSession;
 | 
			
		||||
 | 
			
		||||
namespace FocusVolumeControl.AudioSessions;
 | 
			
		||||
 | 
			
		||||
public sealed class ActiveAudioSessionWrapper : IAudioSession
 | 
			
		||||
{
 | 
			
		||||
	static ConcurrentLru<string, string> _iconCache = new ConcurrentLru<string, string>(10);
 | 
			
		||||
 | 
			
		||||
	public string DisplayName { get; set; }
 | 
			
		||||
	public string ExecutablePath { get; set; }
 | 
			
		||||
	public string IconPath { get; set; }
 | 
			
		||||
	private List<IAudioSessionControl2> Sessions { get; } = new List<IAudioSessionControl2>();
 | 
			
		||||
	private IEnumerable<ISimpleAudioVolume> Volume => Sessions.Cast<ISimpleAudioVolume>();
 | 
			
		||||
 | 
			
		||||
	string GetIconFromIconPath()
 | 
			
		||||
	{
 | 
			
		||||
		return _iconCache.GetOrAdd(IconPath, (key) =>
 | 
			
		||||
		{
 | 
			
		||||
			var tmp = (Bitmap)Bitmap.FromFile(IconPath);
 | 
			
		||||
			tmp.MakeTransparent();
 | 
			
		||||
			return Tools.ImageToBase64(tmp, true);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	string GetIconFromExecutablePath()
 | 
			
		||||
	{
 | 
			
		||||
		return _iconCache.GetOrAdd(ExecutablePath, (key) =>
 | 
			
		||||
		{
 | 
			
		||||
			var tmp = IconExtraction.GetIcon(ExecutablePath);
 | 
			
		||||
			//var tmp = Icon.ExtractAssociatedIcon(ExecutablePath);
 | 
			
		||||
			return Tools.ImageToBase64(tmp, true);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
	public IconWrapper? IconWrapper { get; set; }
 | 
			
		||||
 | 
			
		||||
	public string GetIcon()
 | 
			
		||||
	{
 | 
			
		||||
		try
 | 
			
		||||
		{
 | 
			
		||||
			if (!string.IsNullOrEmpty(IconPath))
 | 
			
		||||
			{
 | 
			
		||||
				return GetIconFromIconPath();
 | 
			
		||||
			}
 | 
			
		||||
			else
 | 
			
		||||
			{
 | 
			
		||||
				return GetIconFromExecutablePath();
 | 
			
		||||
			}
 | 
			
		||||
			return IconWrapper?.GetIconData() ?? IconWrapper.FallbackIconData;
 | 
			
		||||
		}
 | 
			
		||||
		catch
 | 
			
		||||
		{
 | 
			
		||||
			return "Images/encoderIcon";
 | 
			
		||||
			return IconWrapper.FallbackIconData;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,32 @@ public class MMDeviceEnumerator
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[Flags]
 | 
			
		||||
public enum CLSCTX : uint
 | 
			
		||||
{
 | 
			
		||||
	INPROC_SERVER = 0x1,
 | 
			
		||||
	INPROC_HANDLER = 0x2,
 | 
			
		||||
	LOCAL_SERVER = 0x4,
 | 
			
		||||
	INPROC_SERVER16 = 0x8,
 | 
			
		||||
	REMOTE_SERVER = 0x10,
 | 
			
		||||
	INPROC_HANDLER16 = 0x20,
 | 
			
		||||
	RESERVED1 = 0x40,
 | 
			
		||||
	RESERVED2 = 0x80,
 | 
			
		||||
	RESERVED3 = 0x100,
 | 
			
		||||
	RESERVED4 = 0x200,
 | 
			
		||||
	NO_CODE_DOWNLOAD = 0x400,
 | 
			
		||||
	RESERVED5 = 0x800,
 | 
			
		||||
	NO_CUSTOM_MARSHAL = 0x1000,
 | 
			
		||||
	ENABLE_CODE_DOWNLOAD = 0x2000,
 | 
			
		||||
	NO_FAILURE_LOG = 0x4000,
 | 
			
		||||
	DISABLE_AAA = 0x8000,
 | 
			
		||||
	ENABLE_AAA = 0x10000,
 | 
			
		||||
	FROM_DEFAULT_CONTEXT = 0x20000,
 | 
			
		||||
	INPROC = INPROC_SERVER | INPROC_HANDLER,
 | 
			
		||||
	SERVER = INPROC_SERVER | LOCAL_SERVER | REMOTE_SERVER,
 | 
			
		||||
	ALL = SERVER | INPROC_HANDLER
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public enum DataFlow
 | 
			
		||||
{
 | 
			
		||||
	Render,
 | 
			
		||||
@ -35,6 +61,13 @@ public enum DeviceState : uint
 | 
			
		||||
	MaskAll = 0xFu
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public enum AudioSessionState
 | 
			
		||||
{
 | 
			
		||||
	AudioSessionStateInactive = 0,
 | 
			
		||||
	AudioSessionStateActive = 1,
 | 
			
		||||
	AudioSessionStateExpired = 2
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E")]
 | 
			
		||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
 | 
			
		||||
@ -62,7 +95,13 @@ public interface IMMDeviceEnumerator
 | 
			
		||||
public interface IMMDevice
 | 
			
		||||
{
 | 
			
		||||
	[PreserveSig]
 | 
			
		||||
	int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
 | 
			
		||||
	int Activate(ref Guid iid, CLSCTX dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
 | 
			
		||||
 | 
			
		||||
	[PreserveSig]
 | 
			
		||||
	int NotImpl1();
 | 
			
		||||
	[PreserveSig]
 | 
			
		||||
	int GetId([Out, MarshalAs(UnmanagedType.LPWStr)] out string ppstrId);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
 | 
			
		||||
@ -104,9 +143,9 @@ public interface ISimpleAudioVolume
 | 
			
		||||
[Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
 | 
			
		||||
public interface IAudioSessionControl2
 | 
			
		||||
{
 | 
			
		||||
	// IAudioSessionControl
 | 
			
		||||
	//elgato seems to use this to determine whether the icon should be black and white
 | 
			
		||||
	[PreserveSig]
 | 
			
		||||
	int NotImpl0();
 | 
			
		||||
	int GetState(out AudioSessionState audioSessionState);
 | 
			
		||||
 | 
			
		||||
	[PreserveSig]
 | 
			
		||||
	int GetDisplayName([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
 | 
			
		||||
@ -134,7 +173,7 @@ public interface IAudioSessionControl2
 | 
			
		||||
 | 
			
		||||
	// IAudioSessionControl2
 | 
			
		||||
	[PreserveSig]
 | 
			
		||||
	int GetSessionIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
 | 
			
		||||
	uint GetSessionIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
 | 
			
		||||
 | 
			
		||||
	[PreserveSig]
 | 
			
		||||
	int GetSessionInstanceIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										103
									
								
								src/FocusVolumeControl/AudioSessions/IconWrapper.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/FocusVolumeControl/AudioSessions/IconWrapper.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,103 @@
 | 
			
		||||
using BarRaider.SdTools;
 | 
			
		||||
using BitFaster.Caching.Lru;
 | 
			
		||||
using FocusVolumeControl.UI;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
 | 
			
		||||
namespace FocusVolumeControl.AudioSession
 | 
			
		||||
{
 | 
			
		||||
	public abstract class IconWrapper
 | 
			
		||||
	{
 | 
			
		||||
		protected static ConcurrentLru<string, string> _iconCache = new ConcurrentLru<string, string>(10);
 | 
			
		||||
 | 
			
		||||
		public abstract string GetIconData();
 | 
			
		||||
 | 
			
		||||
		internal const string FallbackIconData = "Images/encoderIcon";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	internal class AppxIcon : IconWrapper
 | 
			
		||||
	{
 | 
			
		||||
		private readonly string _iconPath;
 | 
			
		||||
 | 
			
		||||
		public AppxIcon(string iconPath)
 | 
			
		||||
		{
 | 
			
		||||
			_iconPath = iconPath;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override string GetIconData()
 | 
			
		||||
		{
 | 
			
		||||
			if(string.IsNullOrEmpty(_iconPath))
 | 
			
		||||
			{
 | 
			
		||||
				return FallbackIconData;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return _iconCache.GetOrAdd(_iconPath, (key) =>
 | 
			
		||||
			{
 | 
			
		||||
				var tmp = (Bitmap)Bitmap.FromFile(_iconPath);
 | 
			
		||||
				tmp.MakeTransparent();
 | 
			
		||||
				return Tools.ImageToBase64(tmp, true);
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	internal class NormalIcon : IconWrapper
 | 
			
		||||
	{
 | 
			
		||||
		private readonly string _iconPath;
 | 
			
		||||
 | 
			
		||||
		public NormalIcon(string iconPath)
 | 
			
		||||
		{
 | 
			
		||||
			_iconPath = iconPath;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override string GetIconData()
 | 
			
		||||
		{
 | 
			
		||||
			if(string.IsNullOrEmpty(_iconPath))
 | 
			
		||||
			{
 | 
			
		||||
				return FallbackIconData;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return _iconCache.GetOrAdd(_iconPath, (key) =>
 | 
			
		||||
			{
 | 
			
		||||
				var tmp = IconExtraction.GetIcon(_iconPath);
 | 
			
		||||
				return Tools.ImageToBase64(tmp, true);
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	internal class RawIcon : IconWrapper
 | 
			
		||||
	{
 | 
			
		||||
		private readonly string _data;
 | 
			
		||||
 | 
			
		||||
		public RawIcon(string name, Func<Bitmap?> getIcon)
 | 
			
		||||
		{
 | 
			
		||||
			_data = _iconCache.GetOrAdd(name, (key) =>
 | 
			
		||||
			{
 | 
			
		||||
				var icon = getIcon();
 | 
			
		||||
				if (icon == null)
 | 
			
		||||
				{
 | 
			
		||||
					return FallbackIconData;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (icon.Height < 48 && icon.Width < 48)
 | 
			
		||||
				{
 | 
			
		||||
					using var newImage = new Bitmap(48, 48);
 | 
			
		||||
					newImage.MakeTransparent();
 | 
			
		||||
					using var graphics = Graphics.FromImage(newImage);
 | 
			
		||||
 | 
			
		||||
					graphics.DrawImage(icon, 4, 4, 40, 40);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
					return Tools.ImageToBase64(newImage, true);
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					return Tools.ImageToBase64(icon, true);
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public override string GetIconData() => _data;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -33,7 +33,7 @@ public class DialAction : EncoderBase
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	PluginSettings settings;
 | 
			
		||||
	AudioHelper _audioHelper = new AudioHelper();
 | 
			
		||||
	static AudioHelper _audioHelper = new AudioHelper();
 | 
			
		||||
	UIState _previousState;
 | 
			
		||||
 | 
			
		||||
	public DialAction(ISDConnection connection, InitialPayload payload) : base(connection, payload)
 | 
			
		||||
 | 
			
		||||
@ -55,6 +55,7 @@
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <Compile Include="AudioHelpers\AppxPackage.cs" />
 | 
			
		||||
    <Compile Include="AudioSessions\IconWrapper.cs" />
 | 
			
		||||
    <Compile Include="AudioSessions\ActiveAudioSessionWrapper.cs" />
 | 
			
		||||
    <Compile Include="AudioHelpers\AudioHelper.cs" />
 | 
			
		||||
    <Compile Include="AudioSessions\CoreAudio.cs" />
 | 
			
		||||
@ -71,6 +72,7 @@
 | 
			
		||||
    <Compile Include="AudioHelpers\ParentProcessUtilities.cs" />
 | 
			
		||||
    <Compile Include="Program.cs" />
 | 
			
		||||
    <Compile Include="Properties\AssemblyInfo.cs" />
 | 
			
		||||
    <Compile Include="UI\JavaIconExtractor.cs" />
 | 
			
		||||
    <Compile Include="UI\UIState.cs" />
 | 
			
		||||
    <Compile Include="UI\ValueWithOpacity.cs" />
 | 
			
		||||
    <Compile Include="WindowChangedEventLoop.cs" />
 | 
			
		||||
 | 
			
		||||
@ -6,8 +6,10 @@ internal class Program
 | 
			
		||||
{
 | 
			
		||||
	static void Main(string[] args)
 | 
			
		||||
	{
 | 
			
		||||
#if DEBUG
 | 
			
		||||
		// Uncomment this line of code to allow for debugging
 | 
			
		||||
		//while (!System.Diagnostics.Debugger.IsAttached) { System.Threading.Thread.Sleep(100); }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
		SDWrapper.Run(args);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										96
									
								
								src/FocusVolumeControl/UI/JavaIconExtractor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/FocusVolumeControl/UI/JavaIconExtractor.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,96 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Drawing;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace FocusVolumeControl.UI
 | 
			
		||||
{
 | 
			
		||||
	internal class JavaIconExtractor
 | 
			
		||||
	{
 | 
			
		||||
		const int WM_GETICON = 0x7F;
 | 
			
		||||
		const int WM_QUERYDRAGICON = 0x0037;
 | 
			
		||||
		//const int ICON_SMALL = 0; //(16x16)
 | 
			
		||||
		const int ICON_BIG = 1; //(32x32)
 | 
			
		||||
		const int SMTO_ABORTIFHUNG = 0x3;
 | 
			
		||||
		const int GCL_HICON = -14;
 | 
			
		||||
 | 
			
		||||
		[DllImport("User32.dll")]
 | 
			
		||||
		static extern int SendMessageTimeout(IntPtr hWnd, int uMsg, int wParam, int lParam, int fuFlags, int uTimeout, out int lpdwResult);
 | 
			
		||||
		[DllImport("User32.dll")]
 | 
			
		||||
		static extern int GetClassLong(IntPtr hWnd, int index);
 | 
			
		||||
 | 
			
		||||
		[DllImport("user32.dll", EntryPoint = "DestroyIcon", SetLastError = true)]
 | 
			
		||||
		public static extern int DestroyIcon(IntPtr hIcon);
 | 
			
		||||
 | 
			
		||||
		public static Bitmap? GetWindowBigIconWithRetry(IntPtr hWnd)
 | 
			
		||||
		{
 | 
			
		||||
			var retry = 5;
 | 
			
		||||
			var icon = GetWindowBigIcon(hWnd);
 | 
			
		||||
 | 
			
		||||
			while(icon == null || retry > 0)
 | 
			
		||||
			{
 | 
			
		||||
				Thread.Sleep(100);
 | 
			
		||||
				icon = GetWindowBigIcon(hWnd);
 | 
			
		||||
				retry--;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return icon;
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/// <summary>
 | 
			
		||||
		/// Retrieves a big icon (32*32) of a window
 | 
			
		||||
		/// </summary>
 | 
			
		||||
		/// <param name="hWnd"></param>
 | 
			
		||||
		/// <returns></returns>
 | 
			
		||||
		public static Bitmap? GetWindowBigIcon(IntPtr hWnd)
 | 
			
		||||
		{
 | 
			
		||||
			IntPtr hIcon = IntPtr.Zero;
 | 
			
		||||
			try
 | 
			
		||||
			{
 | 
			
		||||
				int result;
 | 
			
		||||
				SendMessageTimeout(hWnd, WM_GETICON, ICON_BIG, //big icon size
 | 
			
		||||
					0, SMTO_ABORTIFHUNG, 1000, out result);
 | 
			
		||||
 | 
			
		||||
				hIcon = new IntPtr(result);
 | 
			
		||||
				if (hIcon == IntPtr.Zero) //some applications don't respond to sendmessage, we have to use GetClassLong in that case
 | 
			
		||||
				{
 | 
			
		||||
					result = GetClassLong(hWnd, GCL_HICON); //big icon size
 | 
			
		||||
					hIcon = new IntPtr(result);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (hIcon == IntPtr.Zero)
 | 
			
		||||
				{
 | 
			
		||||
					SendMessageTimeout(hWnd, WM_QUERYDRAGICON, 0, 0, SMTO_ABORTIFHUNG, 1000, out result);
 | 
			
		||||
					hIcon = new IntPtr(result);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (hIcon == IntPtr.Zero)
 | 
			
		||||
				{
 | 
			
		||||
					return null;
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					using var tmp = (Icon)Icon.FromHandle(hIcon).Clone();
 | 
			
		||||
					return tmp.ToBitmap();
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			catch (Exception)
 | 
			
		||||
			{
 | 
			
		||||
			}
 | 
			
		||||
			finally
 | 
			
		||||
			{
 | 
			
		||||
				if(hIcon != IntPtr.Zero)
 | 
			
		||||
				{
 | 
			
		||||
					DestroyIcon(hIcon);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -57,7 +57,7 @@ public partial class MainWindow : Window
 | 
			
		||||
 | 
			
		||||
			foreach (var p in processes)
 | 
			
		||||
			{
 | 
			
		||||
				var (displayName, _) = (new NameAndIconHelper()).GetProcessInfo(p);
 | 
			
		||||
				var displayName = (new NameAndIconHelper()).GetProcessInfo(p);
 | 
			
		||||
 | 
			
		||||
				sb.AppendLine($"pid: {p.Id}");
 | 
			
		||||
				sb.AppendLine($"\tprocessName: {p.ProcessName}");
 | 
			
		||||
@ -92,14 +92,16 @@ public partial class MainWindow : Window
 | 
			
		||||
		deviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active, out var deviceCollection);
 | 
			
		||||
		deviceCollection.GetCount(out var num);
 | 
			
		||||
 | 
			
		||||
		for(int i = 0; i < num; i++)
 | 
			
		||||
 | 
			
		||||
		for (int i = 0; i < num; i++)
 | 
			
		||||
		{
 | 
			
		||||
			deviceCollection.Item(i, out var device);
 | 
			
		||||
			//todo: put the device name in the output
 | 
			
		||||
			sb.AppendLine("----");
 | 
			
		||||
			sb.AppendLine($"----");
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
			Guid iid = typeof(IAudioSessionManager2).GUID;
 | 
			
		||||
			device.Activate(ref iid, 0, IntPtr.Zero, out var m);
 | 
			
		||||
			device.Activate(ref iid, CLSCTX.ALL, IntPtr.Zero, out var m);
 | 
			
		||||
			var manager = (IAudioSessionManager2)m;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -112,9 +114,10 @@ public partial class MainWindow : Window
 | 
			
		||||
				sessionEnumerator.GetSession(s, out var session);
 | 
			
		||||
 | 
			
		||||
				session.GetProcessId(out var processId);
 | 
			
		||||
				session.GetIconPath(out var path);
 | 
			
		||||
				var audioProcess = Process.GetProcessById(processId);
 | 
			
		||||
 | 
			
		||||
				var (displayName, _) = (new NameAndIconHelper()).GetProcessInfo(audioProcess);
 | 
			
		||||
				var displayName = (new NameAndIconHelper()).GetProcessInfo(audioProcess);
 | 
			
		||||
				sb.AppendLine($"pid: {audioProcess.Id}\t\t processName: {displayName}");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user