Compare commits
	
		
			11 Commits
		
	
	
		
			v1.2.0-pre
			...
			playWithAp
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f1d7aeed5d | |||
| 0f7f1fffcd | |||
| aa905fe443 | |||
| 160bedd461 | |||
| 68d5154756 | |||
| 8eebf1af47 | |||
| 6de76da8ad | |||
| 48161b5c2e | |||
| d1df235af0 | |||
| 6aaa32cf92 | |||
| 4ca0ad021f | 
| @ -109,6 +109,13 @@ public sealed class AppxPackage | |||||||
| 							var properties = reader.GetProperties(); | 							var properties = reader.GetProperties(); | ||||||
|  |  | ||||||
| 							properties.GetStringValue("DisplayName", out var displayName); | 							properties.GetStringValue("DisplayName", out var displayName); | ||||||
|  |  | ||||||
|  | 							if(displayName.StartsWith("ms-resource:")) | ||||||
|  | 							{ | ||||||
|  | 								var packageFullName = Marshal.PtrToStringUni(info.packageFullName); | ||||||
|  | 								displayName = LoadResourceString(fullName, displayName); | ||||||
|  |  | ||||||
|  | 							} | ||||||
| 							package.DisplayName = displayName; | 							package.DisplayName = displayName; | ||||||
|  |  | ||||||
| 							properties.GetStringValue("Logo", out var logo); | 							properties.GetStringValue("Logo", out var logo); | ||||||
| @ -158,6 +165,40 @@ public sealed class AppxPackage | |||||||
| 				ClosePackageInfo(infoRef); | 				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(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,12 +1,9 @@ | |||||||
| using BarRaider.SdTools; | using FocusVolumeControl.AudioSessions; | ||||||
| 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; |  | ||||||
|  |  | ||||||
| namespace FocusVolumeControl.AudioHelpers; | namespace FocusVolumeControl.AudioHelpers; | ||||||
|  |  | ||||||
| @ -16,6 +13,7 @@ public class AudioHelper | |||||||
|  |  | ||||||
| 	static object _lock = new object(); | 	static object _lock = new object(); | ||||||
| 	int[] _currentProcesses; | 	int[] _currentProcesses; | ||||||
|  | 	int _retryFallbackCount = 0; | ||||||
|  |  | ||||||
| 	public IAudioSession Current { get; private set; } | 	public IAudioSession Current { get; private set; } | ||||||
|  |  | ||||||
| @ -27,8 +25,21 @@ public class AudioHelper | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	private Process GetProcessById(int id) | ||||||
|  | 	{ | ||||||
|  | 		try | ||||||
|  | 		{ | ||||||
|  | 			return Process.GetProcessById(id); | ||||||
|  | 		} | ||||||
|  | 		catch | ||||||
|  | 		{ | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	public IAudioSession FindSession(List<Process> processes) | 	public IAudioSession FindSession(List<Process> processes) | ||||||
| 	{ | 	{ | ||||||
|  | 		//var blah = new AudioPolicyConfigFactoryImplFor21H2(); | ||||||
| 		var results = new ActiveAudioSessionWrapper(); | 		var results = new ActiveAudioSessionWrapper(); | ||||||
| 		Process bestProcessMatch = null; | 		Process bestProcessMatch = null; | ||||||
|  |  | ||||||
| @ -41,9 +52,10 @@ public class AudioHelper | |||||||
| 			deviceCollection.Item(d, out var device); | 			deviceCollection.Item(d, out var device); | ||||||
|  |  | ||||||
| 			Guid iid = typeof(IAudioSessionManager2).GUID; | 			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; | 			var manager = (IAudioSessionManager2)m; | ||||||
|  |  | ||||||
|  | 			device.GetId(out var currentDeviceId); | ||||||
|  |  | ||||||
| 			manager.GetSessionEnumerator(out var sessionEnumerator); | 			manager.GetSessionEnumerator(out var sessionEnumerator); | ||||||
|  |  | ||||||
| @ -55,7 +67,22 @@ public class AudioHelper | |||||||
| 				sessionEnumerator.GetSession(i, out var session); | 				sessionEnumerator.GetSession(i, out var session); | ||||||
|  |  | ||||||
| 				session.GetProcessId(out var sessionProcessId); | 				session.GetProcessId(out var sessionProcessId); | ||||||
| 				var audioProcess = Process.GetProcessById(sessionProcessId); | 				var audioProcess = GetProcessById(sessionProcessId); | ||||||
|  |  | ||||||
|  | 				if(audioProcess == null) | ||||||
|  | 				{ | ||||||
|  | 					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); | 				var index = processes.FindIndex(x => x.Id == sessionProcessId || x.ProcessName == audioProcess?.ProcessName); | ||||||
|  |  | ||||||
| @ -68,7 +95,14 @@ public class AudioHelper | |||||||
| 					{ | 					{ | ||||||
| 						bestProcessMatch = audioProcess; | 						bestProcessMatch = audioProcess; | ||||||
| 						currentIndex = index; | 						currentIndex = index; | ||||||
|  |  | ||||||
|  | 						if(string.IsNullOrEmpty(results.DisplayName)) | ||||||
|  | 						{ | ||||||
|  | 							session.GetDisplayName(out var displayName); | ||||||
|  | 							results.DisplayName = displayName; | ||||||
| 						} | 						} | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  |  | ||||||
| 					//some apps like discord have multiple volume processes. | 					//some apps like discord have multiple volume processes. | ||||||
| 					//and some apps will be on multiple devices | 					//and some apps will be on multiple devices | ||||||
| @ -92,12 +126,20 @@ public class AudioHelper | |||||||
| 		lock (_lock) | 		lock (_lock) | ||||||
| 		{ | 		{ | ||||||
| 			var processes = GetPossibleProcesses(); | 			var processes = GetPossibleProcesses(); | ||||||
| 			var processIds = processes.Select(x => x.Id).ToArray(); | 			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); | 				Current = FindSession(processes); | ||||||
| 			} | 			} | ||||||
|  | 			else if(Current is SystemSoundsAudioSession || Current is SystemVolumeAudioSession) | ||||||
|  | 			{ | ||||||
|  | 				_retryFallbackCount++; | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			if (Current == null) | 			if (Current == null) | ||||||
| 			{ | 			{ | ||||||
| @ -138,7 +180,7 @@ public class AudioHelper | |||||||
|  |  | ||||||
| 		if (handle == IntPtr.Zero) | 		if (handle == IntPtr.Zero) | ||||||
| 		{ | 		{ | ||||||
| 			return null; | 			return new List<Process>(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		var ids = Native.GetProcessesOfChildWindows(handle); | 		var ids = Native.GetProcessesOfChildWindows(handle); | ||||||
| @ -150,6 +192,11 @@ public class AudioHelper | |||||||
| 						   .Select(x => Process.GetProcessById(x)) | 						   .Select(x => Process.GetProcessById(x)) | ||||||
| 						   .ToList(); | 						   .ToList(); | ||||||
|  |  | ||||||
|  | 		if(processes.FirstOrDefault()?.ProcessName == "explorer") | ||||||
|  | 		{ | ||||||
|  | 			return new List<Process>(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		try | 		try | ||||||
| 		{ | 		{ | ||||||
| 			//note. in instances where you launch a game from steam. this ends up mapping the process to both steam and to the game. which is unfortunate | 			//note. in instances where you launch a game from steam. this ends up mapping the process to both steam and to the game. which is unfortunate | ||||||
| @ -189,7 +236,7 @@ public class AudioHelper | |||||||
| 			deviceCollection.Item(d, out var device); | 			deviceCollection.Item(d, out var device); | ||||||
|  |  | ||||||
| 			Guid iid = typeof(IAudioSessionManager2).GUID; | 			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; | 			var manager = (IAudioSessionManager2)m; | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -220,7 +267,7 @@ public class AudioHelper | |||||||
|  |  | ||||||
|  |  | ||||||
| 			Guid iid = typeof(IAudioSessionManager2).GUID; | 			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; | 			var manager = (IAudioSessionManager2)m; | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -246,10 +293,130 @@ public class AudioHelper | |||||||
| 		deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia, 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, CLSCTX.ALL, IntPtr.Zero, out var o); | ||||||
| 		var endpointVolume = (IAudioEndpointVolume)o; | 		var endpointVolume = (IAudioEndpointVolume)o; | ||||||
|  |  | ||||||
| 		return new SystemVolumeAudioSession(endpointVolume); | 		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 BarRaider.SdTools; | ||||||
|  | using FocusVolumeControl.AudioSession; | ||||||
| using FocusVolumeControl.AudioSessions; | using FocusVolumeControl.AudioSessions; | ||||||
|  | using FocusVolumeControl.UI; | ||||||
| using System; | using System; | ||||||
| using System.Diagnostics; | using System.Diagnostics; | ||||||
|  | using System.Drawing; | ||||||
| using System.IO; | using System.IO; | ||||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||||
| using System.Text; | using System.Text; | ||||||
| @ -10,12 +13,12 @@ namespace FocusVolumeControl.AudioHelpers; | |||||||
|  |  | ||||||
| public class NameAndIconHelper | 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 | 		//i know this is dumb, but its only used by the sound browser, not real prod code | ||||||
| 		var blah = new ActiveAudioSessionWrapper(); | 		var blah = new ActiveAudioSessionWrapper(); | ||||||
| 		SetProcessInfo(process, blah); | 		SetProcessInfo(process, blah); | ||||||
| 		return (blah.DisplayName, blah.IconPath ?? blah.ExecutablePath); | 		return blah.DisplayName; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public void SetProcessInfo(Process process, ActiveAudioSessionWrapper results) | 	public void SetProcessInfo(Process process, ActiveAudioSessionWrapper results) | ||||||
| @ -26,10 +29,13 @@ public class NameAndIconHelper | |||||||
| 			var appx = AppxPackage.FromProcess(process); | 			var appx = AppxPackage.FromProcess(process); | ||||||
| 			if (appx == null) | 			if (appx == null) | ||||||
| 			{ | 			{ | ||||||
| 				//usingg process.MainModule.FileVersionInfo sometimes throws permission exceptions | 				//using process.MainModule.FileVersionInfo sometimes throws permission exceptions | ||||||
| 				//we get the file version info with a limited query flag to avoid that | 				//we get the file version info with a limited query flag to avoid that | ||||||
| 				var fileVersionInfo = GetFileVersionInfo(process); | 				var fileVersionInfo = GetFileVersionInfo(process); | ||||||
|  |  | ||||||
|  | 				//if the display name is already set, then it came from the display name of the audio session | ||||||
|  | 				if (string.IsNullOrEmpty(results.DisplayName)) | ||||||
|  | 				{ | ||||||
| 					results.DisplayName = process.MainWindowTitle; | 					results.DisplayName = process.MainWindowTitle; | ||||||
|  |  | ||||||
| 					if (string.IsNullOrEmpty(results.DisplayName)) | 					if (string.IsNullOrEmpty(results.DisplayName)) | ||||||
| @ -40,13 +46,29 @@ public class NameAndIconHelper | |||||||
| 							results.DisplayName = process.ProcessName; | 							results.DisplayName = process.ProcessName; | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
| 				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 | 			else | ||||||
| 			{ | 			{ | ||||||
| 				results.DisplayName = appx.DisplayName; | 				results.DisplayName = appx.DisplayName; | ||||||
| 				results.IconPath = Path.Combine(appx.Path, appx.Logo); | 				results.IconWrapper = new AppxIcon(Path.Combine(appx.Path, appx.Logo)); | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		catch { } | 		catch { } | ||||||
|  | |||||||
| @ -4,44 +4,31 @@ using System.Linq; | |||||||
| using BarRaider.SdTools; | using BarRaider.SdTools; | ||||||
| using System.Drawing; | using System.Drawing; | ||||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||||
|  | using FocusVolumeControl.UI; | ||||||
|  | using BitFaster.Caching.Lru; | ||||||
|  | using FocusVolumeControl.AudioSession; | ||||||
|  |  | ||||||
| namespace FocusVolumeControl.AudioSessions; | namespace FocusVolumeControl.AudioSessions; | ||||||
|  |  | ||||||
| public sealed class ActiveAudioSessionWrapper : IAudioSession | public sealed class ActiveAudioSessionWrapper : IAudioSession | ||||||
| { | { | ||||||
| 	public string DisplayName { get; set; } | 	public string DisplayName { 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>(); | ||||||
|  |  | ||||||
| 	string _icon; | 	public IconWrapper? IconWrapper { get; set; } | ||||||
|  |  | ||||||
| 	public string GetIcon() | 	public string GetIcon() | ||||||
| 	{ |  | ||||||
| 		if (string.IsNullOrEmpty(_icon)) |  | ||||||
| 	{ | 	{ | ||||||
| 		try | 		try | ||||||
| 		{ | 		{ | ||||||
| 				if(!string.IsNullOrEmpty(IconPath)) | 			return IconWrapper?.GetIconData() ?? IconWrapper.FallbackIconData; | ||||||
| 				{ |  | ||||||
| 					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 | ||||||
| 		{ | 		{ | ||||||
| 				_icon = "Images/encoderIcon"; | 			return IconWrapper.FallbackIconData; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 		return _icon; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	public bool Any() | 	public bool Any() | ||||||
| 	{ | 	{ | ||||||
|  | |||||||
| @ -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 | public enum DataFlow | ||||||
| { | { | ||||||
| 	Render, | 	Render, | ||||||
| @ -35,6 +61,13 @@ public enum DeviceState : uint | |||||||
| 	MaskAll = 0xFu | 	MaskAll = 0xFu | ||||||
| } | } | ||||||
|  |  | ||||||
|  | public enum AudioSessionState | ||||||
|  | { | ||||||
|  | 	AudioSessionStateInactive = 0, | ||||||
|  | 	AudioSessionStateActive = 1, | ||||||
|  | 	AudioSessionStateExpired = 2 | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| [Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E")] | [Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E")] | ||||||
| [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||||||
| @ -62,7 +95,13 @@ public interface IMMDeviceEnumerator | |||||||
| public 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, 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)] | [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)] | [Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||||||
| public interface IAudioSessionControl2 | public interface IAudioSessionControl2 | ||||||
| { | { | ||||||
| 	// IAudioSessionControl | 	//elgato seems to use this to determine whether the icon should be black and white | ||||||
| 	[PreserveSig] | 	[PreserveSig] | ||||||
| 	int NotImpl0(); | 	int GetState(out AudioSessionState audioSessionState); | ||||||
|  |  | ||||||
| 	[PreserveSig] | 	[PreserveSig] | ||||||
| 	int GetDisplayName([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); | 	int GetDisplayName([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); | ||||||
| @ -134,7 +173,7 @@ public interface IAudioSessionControl2 | |||||||
|  |  | ||||||
| 	// IAudioSessionControl2 | 	// IAudioSessionControl2 | ||||||
| 	[PreserveSig] | 	[PreserveSig] | ||||||
| 	int GetSessionIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); | 	uint GetSessionIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); | ||||||
|  |  | ||||||
| 	[PreserveSig] | 	[PreserveSig] | ||||||
| 	int GetSessionInstanceIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); | 	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; | 	PluginSettings settings; | ||||||
| 	AudioHelper _audioHelper = new AudioHelper(); | 	static AudioHelper _audioHelper = new AudioHelper(); | ||||||
| 	UIState _previousState; | 	UIState _previousState; | ||||||
|  |  | ||||||
| 	public DialAction(ISDConnection connection, InitialPayload payload) : base(connection, payload) | 	public DialAction(ISDConnection connection, InitialPayload payload) : base(connection, payload) | ||||||
|  | |||||||
| @ -55,6 +55,7 @@ | |||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <Compile Include="AudioHelpers\AppxPackage.cs" /> |     <Compile Include="AudioHelpers\AppxPackage.cs" /> | ||||||
|  |     <Compile Include="AudioSessions\IconWrapper.cs" /> | ||||||
|     <Compile Include="AudioSessions\ActiveAudioSessionWrapper.cs" /> |     <Compile Include="AudioSessions\ActiveAudioSessionWrapper.cs" /> | ||||||
|     <Compile Include="AudioHelpers\AudioHelper.cs" /> |     <Compile Include="AudioHelpers\AudioHelper.cs" /> | ||||||
|     <Compile Include="AudioSessions\CoreAudio.cs" /> |     <Compile Include="AudioSessions\CoreAudio.cs" /> | ||||||
| @ -65,11 +66,13 @@ | |||||||
|     <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="AudioHelpers\NameAndIconHelper.cs" /> | ||||||
|  |     <Compile Include="UI\IconExtraction.cs" /> | ||||||
|     <Compile Include="UI\ISDConnectionExtensions.cs" /> |     <Compile Include="UI\ISDConnectionExtensions.cs" /> | ||||||
|     <Compile Include="Native.cs" /> |     <Compile Include="Native.cs" /> | ||||||
|     <Compile Include="AudioHelpers\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\JavaIconExtractor.cs" /> | ||||||
|     <Compile Include="UI\UIState.cs" /> |     <Compile Include="UI\UIState.cs" /> | ||||||
|     <Compile Include="UI\ValueWithOpacity.cs" /> |     <Compile Include="UI\ValueWithOpacity.cs" /> | ||||||
|     <Compile Include="WindowChangedEventLoop.cs" /> |     <Compile Include="WindowChangedEventLoop.cs" /> | ||||||
| @ -96,6 +99,9 @@ | |||||||
|     </Content> |     </Content> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|  |     <PackageReference Include="BitFaster.Caching"> | ||||||
|  |       <Version>2.2.1</Version> | ||||||
|  |     </PackageReference> | ||||||
|     <PackageReference Include="IsExternalInit"> |     <PackageReference Include="IsExternalInit"> | ||||||
|       <Version>1.0.3</Version> |       <Version>1.0.3</Version> | ||||||
|       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||||
|  | |||||||
| @ -6,8 +6,10 @@ internal class Program | |||||||
| { | { | ||||||
| 	static void Main(string[] args) | 	static void Main(string[] args) | ||||||
| 	{ | 	{ | ||||||
|  | #if DEBUG | ||||||
| 		// Uncomment this line of code to allow for debugging | 		// Uncomment this line of code to allow for debugging | ||||||
| 		//while (!System.Diagnostics.Debugger.IsAttached) { System.Threading.Thread.Sleep(100); } | 		//while (!System.Diagnostics.Debugger.IsAttached) { System.Threading.Thread.Sleep(100); } | ||||||
|  | #endif | ||||||
|  |  | ||||||
| 		SDWrapper.Run(args); | 		SDWrapper.Run(args); | ||||||
| 	} | 	} | ||||||
|  | |||||||
							
								
								
									
										267
									
								
								src/FocusVolumeControl/UI/IconExtraction.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										267
									
								
								src/FocusVolumeControl/UI/IconExtraction.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,267 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Drawing; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Runtime.InteropServices; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  |  | ||||||
|  | namespace FocusVolumeControl.UI | ||||||
|  | { | ||||||
|  | 	internal class IconExtraction | ||||||
|  | 	{ | ||||||
|  | 		public static Bitmap GetIcon(string path) | ||||||
|  | 		{ | ||||||
|  | 			var index = GetIconIndex(path); | ||||||
|  | 			var handle = GetIconHandle(index); | ||||||
|  |  | ||||||
|  | 			using var icon = (Icon)Icon.FromHandle(handle).Clone(); | ||||||
|  |  | ||||||
|  | 			Shell32.DestroyIcon(handle); | ||||||
|  | 			return icon.ToBitmap(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		static int GetIconIndex(string pszFile) | ||||||
|  | 		{ | ||||||
|  | 			SHFILEINFO sfi = new SHFILEINFO(); | ||||||
|  | 			Shell32.SHGetFileInfo(pszFile, 0, ref sfi, (uint)Marshal.SizeOf(sfi), (uint)(SHGFI.SysIconIndex | SHGFI.LargeIcon | SHGFI.UseFileAttributes)); | ||||||
|  | 			return sfi.iIcon; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// 256*256 | ||||||
|  | 		static IntPtr GetIconHandle(int iImage) | ||||||
|  | 		{ | ||||||
|  | 			IImageList spiml = null; | ||||||
|  | 			Guid guil = new Guid(IID_IImageList2);//or IID_IImageList | ||||||
|  |  | ||||||
|  | 			Shell32.SHGetImageList(Shell32.SHIL_EXTRALARGE, ref guil, ref spiml); | ||||||
|  | 			IntPtr hIcon = IntPtr.Zero; | ||||||
|  | 			spiml.GetIcon(iImage, Shell32.ILD_TRANSPARENT | Shell32.ILD_IMAGE, ref hIcon); | ||||||
|  |  | ||||||
|  | 			return hIcon; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		const string IID_IImageList = "46EB5926-582E-4017-9FDF-E8998DAA0950"; | ||||||
|  | 		const string IID_IImageList2 = "192B9D83-50FC-457B-90A0-2B82A8B5DAE1"; | ||||||
|  |  | ||||||
|  | 		public static class Shell32 | ||||||
|  | 		{ | ||||||
|  |  | ||||||
|  | 			public const int SHIL_LARGE = 0x0; | ||||||
|  | 			public const int SHIL_SMALL = 0x1; | ||||||
|  | 			public const int SHIL_EXTRALARGE = 0x2; | ||||||
|  | 			public const int SHIL_SYSSMALL = 0x3; | ||||||
|  | 			public const int SHIL_JUMBO = 0x4; | ||||||
|  | 			public const int SHIL_LAST = 0x4; | ||||||
|  |  | ||||||
|  | 			public const int ILD_TRANSPARENT = 0x00000001; | ||||||
|  | 			public const int ILD_IMAGE = 0x00000020; | ||||||
|  |  | ||||||
|  | 			[DllImport("shell32.dll", EntryPoint = "#727")] | ||||||
|  | 			public extern static int SHGetImageList(int iImageList, ref Guid riid, ref IImageList ppv); | ||||||
|  |  | ||||||
|  | 			[DllImport("user32.dll", EntryPoint = "DestroyIcon", SetLastError = true)] | ||||||
|  | 			public static extern int DestroyIcon(IntPtr hIcon); | ||||||
|  |  | ||||||
|  | 			//[DllImport("shell32.dll")] | ||||||
|  | 			//public static extern uint SHGetIDListFromObject([MarshalAs(UnmanagedType.IUnknown)] object iUnknown, out IntPtr ppidl); | ||||||
|  |  | ||||||
|  | 			[DllImport("Shell32.dll")] | ||||||
|  | 			public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		[Flags] | ||||||
|  | 		enum SHGFI : uint | ||||||
|  | 		{ | ||||||
|  | 			/// <summary>get icon</summary> | ||||||
|  | 			Icon = 0x000000100, | ||||||
|  | 			/// <summary>get display name</summary> | ||||||
|  | 			DisplayName = 0x000000200, | ||||||
|  | 			/// <summary>get type name</summary> | ||||||
|  | 			TypeName = 0x000000400, | ||||||
|  | 			/// <summary>get attributes</summary> | ||||||
|  | 			Attributes = 0x000000800, | ||||||
|  | 			/// <summary>get icon location</summary> | ||||||
|  | 			IconLocation = 0x000001000, | ||||||
|  | 			/// <summary>return exe type</summary> | ||||||
|  | 			ExeType = 0x000002000, | ||||||
|  | 			/// <summary>get system icon index</summary> | ||||||
|  | 			SysIconIndex = 0x000004000, | ||||||
|  | 			/// <summary>put a link overlay on icon</summary> | ||||||
|  | 			LinkOverlay = 0x000008000, | ||||||
|  | 			/// <summary>show icon in selected state</summary> | ||||||
|  | 			Selected = 0x000010000, | ||||||
|  | 			/// <summary>get only specified attributes</summary> | ||||||
|  | 			Attr_Specified = 0x000020000, | ||||||
|  | 			/// <summary>get large icon</summary> | ||||||
|  | 			LargeIcon = 0x000000000, | ||||||
|  | 			/// <summary>get small icon</summary> | ||||||
|  | 			SmallIcon = 0x000000001, | ||||||
|  | 			/// <summary>get open icon</summary> | ||||||
|  | 			OpenIcon = 0x000000002, | ||||||
|  | 			/// <summary>get shell size icon</summary> | ||||||
|  | 			ShellIconSize = 0x000000004, | ||||||
|  | 			/// <summary>pszPath is a pidl</summary> | ||||||
|  | 			PIDL = 0x000000008, | ||||||
|  | 			/// <summary>use passed dwFileAttribute</summary> | ||||||
|  | 			UseFileAttributes = 0x000000010, | ||||||
|  | 			/// <summary>apply the appropriate overlays</summary> | ||||||
|  | 			AddOverlays = 0x000000020, | ||||||
|  | 			/// <summary>Get the index of the overlay in the upper 8 bits of the iIcon</summary> | ||||||
|  | 			OverlayIndex = 0x000000040, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		[StructLayout(LayoutKind.Sequential)] | ||||||
|  | 		public struct SHFILEINFO | ||||||
|  | 		{ | ||||||
|  | 			public const int NAMESIZE = 80; | ||||||
|  | 			public IntPtr hIcon; | ||||||
|  | 			public int iIcon; | ||||||
|  | 			public uint dwAttributes; | ||||||
|  | 			[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] | ||||||
|  | 			public string szDisplayName; | ||||||
|  | 			[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] | ||||||
|  | 			public string szTypeName; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		[StructLayout(LayoutKind.Sequential)] | ||||||
|  | 		public struct RECT | ||||||
|  | 		{ | ||||||
|  | 			public int left, top, right, bottom; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		[StructLayout(LayoutKind.Sequential)] | ||||||
|  | 		public struct POINT | ||||||
|  | 		{ | ||||||
|  | 			int x; | ||||||
|  | 			int y; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		[StructLayout(LayoutKind.Sequential)] | ||||||
|  | 		public struct IMAGELISTDRAWPARAMS | ||||||
|  | 		{ | ||||||
|  | 			public int cbSize; | ||||||
|  | 			public IntPtr himl; | ||||||
|  | 			public int i; | ||||||
|  | 			public IntPtr hdcDst; | ||||||
|  | 			public int x; | ||||||
|  | 			public int y; | ||||||
|  | 			public int cx; | ||||||
|  | 			public int cy; | ||||||
|  | 			public int xBitmap;    // x offest from the upperleft of bitmap | ||||||
|  | 			public int yBitmap;    // y offset from the upperleft of bitmap | ||||||
|  | 			public int rgbBk; | ||||||
|  | 			public int rgbFg; | ||||||
|  | 			public int fStyle; | ||||||
|  | 			public int dwRop; | ||||||
|  | 			public int fState; | ||||||
|  | 			public int Frame; | ||||||
|  | 			public int crEffect; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		[StructLayout(LayoutKind.Sequential)] | ||||||
|  | 		public struct IMAGEINFO | ||||||
|  | 		{ | ||||||
|  | 			public IntPtr hbmImage; | ||||||
|  | 			public IntPtr hbmMask; | ||||||
|  | 			public int Unused1; | ||||||
|  | 			public int Unused2; | ||||||
|  | 			public RECT rcImage; | ||||||
|  | 		} | ||||||
|  | 		[ComImportAttribute()] | ||||||
|  | 		[GuidAttribute("46EB5926-582E-4017-9FDF-E8998DAA0950")] | ||||||
|  | 		[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] | ||||||
|  | 		public interface IImageList | ||||||
|  | 		{ | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int Add(IntPtr hbmImage, IntPtr hbmMask, ref int pi); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int ReplaceIcon(int i, IntPtr hicon, ref int pi); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int SetOverlayImage(int iImage, int iOverlay); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int Replace(int i, IntPtr hbmImage, IntPtr hbmMask); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int AddMasked(IntPtr hbmImage, int crMask, ref int pi); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int Draw(ref IMAGELISTDRAWPARAMS pimldp); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int Remove(int i); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int GetIcon(int i, int flags, ref IntPtr picon); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int GetImageInfo(int i, ref IMAGEINFO pImageInfo); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int Copy(int iDst, IImageList punkSrc, int iSrc, int uFlags); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int Merge(int i1, IImageList punk2, int i2, int dx, int dy, ref Guid riid, ref IntPtr ppv); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int Clone(ref Guid riid, ref IntPtr ppv); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int GetImageRect(int i, ref RECT prc); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int GetIconSize(ref int cx, ref int cy); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int SetIconSize(int cx, int cy); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int GetImageCount(ref int pi); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int SetImageCount(int uNewCount); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int SetBkColor(int clrBk, ref int pclr); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int GetBkColor(ref int pclr); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int BeginDrag(int iTrack, int dxHotspot, int dyHotspot); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int EndDrag(); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int DragEnter(IntPtr hwndLock, int x, int y); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int DragLeave(IntPtr hwndLock); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int DragMove(int x, int y); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int SetDragCursorImage(ref IImageList punk, int iDrag, int dxHotspot, int dyHotspot); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int DragShowNolock(int fShow); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int GetDragImage(ref POINT ppt, ref POINT pptHotspot, ref Guid riid, ref IntPtr ppv); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int GetItemFlags(int i, ref int dwFlags); | ||||||
|  |  | ||||||
|  | 			[PreserveSig] | ||||||
|  | 			int GetOverlayImage(int iOverlay, ref int piIndex); | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										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; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -39,12 +39,24 @@ namespace FocusVolumeControl | |||||||
|  |  | ||||||
| 		public event Action WindowChanged; | 		public event Action WindowChanged; | ||||||
|  |  | ||||||
| 		private void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) | 		CancellationTokenSource? _cancellationTokenSource = null; | ||||||
|  |  | ||||||
|  | 		private async void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) | ||||||
| 		{ | 		{ | ||||||
| 			try | 			try | ||||||
| 			{ | 			{ | ||||||
|  | 				//debounce the window changed events by 100 ms because if you click mouse over an application on the start bar | ||||||
|  | 				//and then click on the preview window, it will quickly go from current -> fallback -> new app | ||||||
|  | 				//which can often result in it getting stuck on the fallback app | ||||||
|  | 				_cancellationTokenSource?.Cancel(); | ||||||
|  | 				_cancellationTokenSource = new CancellationTokenSource(); | ||||||
|  | 				await Task.Delay(100, _cancellationTokenSource.Token); | ||||||
| 				WindowChanged?.Invoke(); | 				WindowChanged?.Invoke(); | ||||||
| 			} | 			} | ||||||
|  | 			catch (TaskCanceledException) | ||||||
|  | 			{ | ||||||
|  | 				//ignored | ||||||
|  | 			} | ||||||
| 			catch (Exception ex) | 			catch (Exception ex) | ||||||
| 			{ | 			{ | ||||||
| 				Logger.Instance.LogMessage(TracingLevel.ERROR, $"Unexpected Error in EventHandler:\n {ex}"); | 				Logger.Instance.LogMessage(TracingLevel.ERROR, $"Unexpected Error in EventHandler:\n {ex}"); | ||||||
|  | |||||||
| @ -57,7 +57,7 @@ public partial class MainWindow : Window | |||||||
|  |  | ||||||
| 			foreach (var p in processes) | 			foreach (var p in processes) | ||||||
| 			{ | 			{ | ||||||
| 				var (displayName, _) = (new NameAndIconHelper()).GetProcessInfo(p); | 				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}"); | ||||||
| @ -92,14 +92,16 @@ public partial class MainWindow : Window | |||||||
| 		deviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active, out var deviceCollection); | 		deviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active, out var deviceCollection); | ||||||
| 		deviceCollection.GetCount(out var num); | 		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); | 			deviceCollection.Item(i, out var device); | ||||||
| 			//todo: put the device name in the output | 			//todo: put the device name in the output | ||||||
| 			sb.AppendLine("----"); | 			sb.AppendLine($"----"); | ||||||
|  |  | ||||||
|  |  | ||||||
| 			Guid iid = typeof(IAudioSessionManager2).GUID; | 			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; | 			var manager = (IAudioSessionManager2)m; | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -112,9 +114,10 @@ public partial class MainWindow : Window | |||||||
| 				sessionEnumerator.GetSession(s, out var session); | 				sessionEnumerator.GetSession(s, out var session); | ||||||
|  |  | ||||||
| 				session.GetProcessId(out var processId); | 				session.GetProcessId(out var processId); | ||||||
|  | 				session.GetIconPath(out var path); | ||||||
| 				var audioProcess = Process.GetProcessById(processId); | 				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}"); | 				sb.AppendLine($"pid: {audioProcess.Id}\t\t processName: {displayName}"); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user