Compare commits
	
		
			10 Commits
		
	
	
		
			v1.1.1
			...
			ca634f8d3c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ca634f8d3c | |||
| bbb0e55ed6 | |||
| 520659ac52 | |||
| 609a7bdb65 | |||
| 13fdfde3e5 | |||
| bbad79b4f3 | |||
| 708180dc8e | |||
| 5711ace990 | |||
| d89c8b1ffa | |||
| f94052e54b | 
| @ -1,16 +1,18 @@ | ||||
| using FocusVolumeControl.AudioSessions; | ||||
| using BarRaider.SdTools; | ||||
| using FocusVolumeControl.AudioSessions; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Diagnostics; | ||||
| using System.Linq; | ||||
| using System.Runtime.InteropServices; | ||||
| using System.Text; | ||||
|  | ||||
| namespace FocusVolumeControl; | ||||
|  | ||||
| public class AudioHelper | ||||
| { | ||||
| 	static object _lock = new object(); | ||||
| 	List<Process> _currentProcesses; | ||||
| 	int[] _currentProcesses; | ||||
|  | ||||
| 	public IAudioSession Current { get; private set; } | ||||
|  | ||||
| @ -36,6 +38,7 @@ public class AudioHelper | ||||
| 		manager.GetSessionEnumerator(out var sessionEnumerator); | ||||
|  | ||||
| 		var results = new ActiveAudioSessionWrapper(); | ||||
| 		var currentIndex = int.MaxValue; | ||||
|  | ||||
| 		sessionEnumerator.GetCount(out var count); | ||||
| 		for (int i = 0; i < count; i++) | ||||
| @ -45,23 +48,19 @@ public class AudioHelper | ||||
| 			session.GetProcessId(out var sessionProcessId); | ||||
| 			var audioProcess = Process.GetProcessById(sessionProcessId); | ||||
|  | ||||
| 			if (processes.Any(x => x.Id == sessionProcessId || x.ProcessName == audioProcess?.ProcessName)) | ||||
| 			{ | ||||
| 				try | ||||
| 				{ | ||||
| 					var displayName = audioProcess.MainModule.FileVersionInfo.FileDescription; | ||||
| 					if (string.IsNullOrEmpty(displayName)) | ||||
| 					{ | ||||
| 						displayName = audioProcess.ProcessName; | ||||
| 					} | ||||
| 					results.DisplayName = displayName; | ||||
| 				} | ||||
| 				catch | ||||
| 				{ | ||||
| 					results.DisplayName ??= audioProcess.ProcessName; | ||||
| 				} | ||||
| 			var index = processes.FindIndex(x => x.Id == sessionProcessId || x.ProcessName == audioProcess?.ProcessName); | ||||
|  | ||||
| 				results.ExecutablePath ??= audioProcess.MainModule.FileName; | ||||
| 			if (index > -1) | ||||
| 			{ | ||||
| 				//processes will be ordered from best to worst (starts with the app, goes to parent) | ||||
| 				//so we want the display name and executable path to come from the process that is closest to the front of the list | ||||
| 				//but we want all matching sessions so things like discord work right | ||||
| 				if (index < currentIndex) | ||||
| 				{ | ||||
| 					(results.DisplayName, results.ExecutablePath) = GetInfo(audioProcess); | ||||
|  | ||||
| 					currentIndex = index; | ||||
| 				} | ||||
|  | ||||
| 				//some apps like discord have multiple volume processes. | ||||
| 				results.AddSession(session); | ||||
| @ -72,14 +71,69 @@ public class AudioHelper | ||||
| 		return results.Any() ? results : null; | ||||
| 	} | ||||
|  | ||||
| 	(string name, string path) GetInfo(Process process) | ||||
| 	{ | ||||
| 		try | ||||
| 		{ | ||||
| 			var module = process.MainModule; | ||||
| 			var displayName = module.FileVersionInfo.FileDescription; | ||||
| 			if (string.IsNullOrEmpty(displayName)) | ||||
| 			{ | ||||
| 				displayName = process.ProcessName; | ||||
| 			} | ||||
|  | ||||
| 			var executablePath = module.FileName; | ||||
|  | ||||
| 			return (displayName, executablePath); | ||||
| 		} | ||||
| 		catch | ||||
| 		{ | ||||
| 			return (process.ProcessName, GetExecutablePathBackup(process)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	string GetExecutablePathBackup(Process process) | ||||
| 	{ | ||||
| 		try | ||||
| 		{ | ||||
| 			string pathToExe = string.Empty; | ||||
|  | ||||
| 			if (process != null) | ||||
| 			{ | ||||
| 				//use query limited information handle instead of process.handle to prevent permission errors | ||||
| 				var handle = Native.OpenProcess(0x00001000, false, process.Id); | ||||
|  | ||||
| 				var buffer = new StringBuilder(1024); | ||||
| 				var bufferSize = (uint)buffer.Capacity + 1; | ||||
| 				var success = Native.QueryFullProcessImageName(handle, 0, buffer, ref bufferSize); | ||||
|  | ||||
| 				if (success) | ||||
| 				{ | ||||
| 					return buffer.ToString(); | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					var error = Marshal.GetLastWin32Error(); | ||||
| 					Logger.Instance.LogMessage(TracingLevel.ERROR, $"Error = {error} getting process name"); | ||||
| 					return ""; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		catch | ||||
| 		{ | ||||
| 		} | ||||
| 		return ""; | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	public IAudioSession GetActiveSession(FallbackBehavior fallbackBehavior) | ||||
| 	{ | ||||
| 		lock (_lock) | ||||
| 		{ | ||||
| 			var processes = GetPossibleProcesses(); | ||||
| 			var processIds = processes.Select(x => x.Id).ToArray(); | ||||
|  | ||||
| 			if (_currentProcesses == null || !_currentProcesses.SequenceEqual(processes)) | ||||
| 			if (_currentProcesses == null || !_currentProcesses.SequenceEqual(processIds)) | ||||
| 			{ | ||||
| 				Current = FindSession(processes); | ||||
| 			} | ||||
| @ -96,7 +150,7 @@ public class AudioHelper | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			_currentProcesses = processes; | ||||
| 			_currentProcesses = processIds; | ||||
| 			return Current; | ||||
| 		} | ||||
| 	} | ||||
| @ -137,10 +191,21 @@ public class AudioHelper | ||||
|  | ||||
| 		try | ||||
| 		{ | ||||
| 			var blah = ParentProcessUtilities.GetParentProcess(pid); | ||||
| 			if (blah != null && blah.ProcessName != "explorer" && blah.ProcessName != "svchost") | ||||
| 			//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 | ||||
| 			//The problem is that if you don't use the parent processes, then the actual steam window won't get recognized. But if you do, then games will map to steam. | ||||
| 			// | ||||
| 			//Additionally, I group all audio processes that match instead of just the most specific, or the first, etc. Because Discord uses two processes, one for voice chat, and one for discord sounds. | ||||
| 			// | ||||
| 			//Steam and Discord are both very common, and end up butting heads in the algorithm. | ||||
| 			//I want to avoid special cases, but since steam and discord are both so common, i'm making an exception. | ||||
| 			var parentProcess = ParentProcessUtilities.GetParentProcess(pid); | ||||
| 			if (parentProcess != null  | ||||
| 				&& parentProcess.ProcessName != "explorer"  | ||||
| 				&& parentProcess.ProcessName != "svchost" | ||||
| 				&& (parentProcess.ProcessName == "steam" && processes.Any(x => x.ProcessName == "steamwebhelper")) //only include steam if the parent process is the steamwebhelper | ||||
| 				) | ||||
| 			{ | ||||
| 				processes.Add(blah); | ||||
| 				processes.Add(parentProcess); | ||||
| 			} | ||||
| 		} | ||||
| 		catch | ||||
|  | ||||
| @ -27,7 +27,7 @@ public sealed class ActiveAudioSessionWrapper : IAudioSession | ||||
| 			} | ||||
| 			catch | ||||
| 			{ | ||||
| 				_icon = "Image/encoderIcon"; | ||||
| 				_icon = "Images/encoderIcon"; | ||||
| 			} | ||||
| 		} | ||||
| 		return _icon; | ||||
|  | ||||
| @ -10,6 +10,11 @@ namespace FocusVolumeControl.AudioSessions | ||||
| 	{ | ||||
| 		public static float GetAdjustedVolume(float startingVolume, int step, int ticks) | ||||
| 		{ | ||||
| 			if(step <= 0) | ||||
| 			{ | ||||
| 				step = 1; | ||||
| 			} | ||||
|  | ||||
| 			var level = startingVolume; | ||||
|  | ||||
| 			level += 0.01f * step * ticks; | ||||
|  | ||||
| @ -26,20 +26,13 @@ public class DialAction : EncoderBase | ||||
| 		{ | ||||
| 			PluginSettings instance = new PluginSettings(); | ||||
| 			instance.FallbackBehavior = FallbackBehavior.SystemSounds; | ||||
| 			instance.StepSize = 1; | ||||
| 			return instance; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private PluginSettings settings; | ||||
|  | ||||
| 	IntPtr _foregroundWindowChangedEvent; | ||||
| 	Native.WinEventDelegate _delegate; | ||||
|  | ||||
| 	PluginSettings settings; | ||||
| 	AudioHelper _audioHelper = new AudioHelper(); | ||||
|  | ||||
| 	Thread _thread; | ||||
| 	Dispatcher _dispatcher; | ||||
|  | ||||
| 	UIState _previousState; | ||||
|  | ||||
| 	public DialAction(ISDConnection connection, InitialPayload payload) : base(connection, payload) | ||||
| @ -54,19 +47,7 @@ public class DialAction : EncoderBase | ||||
| 			settings = payload.Settings.ToObject<PluginSettings>(); | ||||
| 		} | ||||
|  | ||||
| 		_thread = new Thread(() => | ||||
| 		{ | ||||
| 			Logger.Instance.LogMessage(TracingLevel.DEBUG, "Registering for events"); | ||||
| 			_delegate = new Native.WinEventDelegate(WinEventProc); | ||||
| 			_foregroundWindowChangedEvent = Native.RegisterForForegroundWindowChangedEvent(_delegate); | ||||
|  | ||||
| 			Logger.Instance.LogMessage(TracingLevel.DEBUG, "Starting Dispatcher"); | ||||
| 			_dispatcher = Dispatcher.CurrentDispatcher; | ||||
| 			Dispatcher.Run(); | ||||
| 			Logger.Instance.LogMessage(TracingLevel.DEBUG, "Dispatcher Stopped"); | ||||
| 		}); | ||||
| 		_thread.SetApartmentState(ApartmentState.STA); | ||||
| 		_thread.Start(); | ||||
| 		WindowChangedEventLoop.Instance.WindowChanged += WindowChanged; | ||||
|  | ||||
| 		var session = _audioHelper.GetActiveSession(settings.FallbackBehavior); | ||||
| 		_ = UpdateStateIfNeeded(session); | ||||
| @ -74,19 +55,15 @@ public class DialAction : EncoderBase | ||||
|  | ||||
| 	public override void Dispose() | ||||
| 	{ | ||||
| 		Logger.Instance.LogMessage(TracingLevel.DEBUG, "Disposing"); | ||||
| 		if (_foregroundWindowChangedEvent != IntPtr.Zero) | ||||
| 		{ | ||||
| 			Native.UnhookWinEvent(_foregroundWindowChangedEvent); | ||||
| 		} | ||||
| 		_dispatcher.InvokeShutdown(); | ||||
| 		//Logger.Instance.LogMessage(TracingLevel.DEBUG, "Disposing"); | ||||
| 		WindowChangedEventLoop.Instance.WindowChanged -= WindowChanged; | ||||
| 	} | ||||
|  | ||||
| 	public override async void DialDown(DialPayload payload) | ||||
| 	{ | ||||
| 		try | ||||
| 		{ | ||||
| 			Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Down"); | ||||
| 			//Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Down"); | ||||
| 			await ToggleMuteAsync(); | ||||
| 		} | ||||
| 		catch (Exception ex) | ||||
| @ -100,7 +77,7 @@ public class DialAction : EncoderBase | ||||
| 	{ | ||||
| 		try | ||||
| 		{ | ||||
| 			Logger.Instance.LogMessage(TracingLevel.INFO, "Touch Press"); | ||||
| 			//Logger.Instance.LogMessage(TracingLevel.INFO, "Touch Press"); | ||||
| 			if (payload.IsLongPress) | ||||
| 			{ | ||||
| 				await ResetAllAsync(); | ||||
| @ -120,7 +97,7 @@ public class DialAction : EncoderBase | ||||
| 	{ | ||||
| 		try | ||||
| 		{ | ||||
| 			Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Rotate"); | ||||
| 			//Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Rotate"); | ||||
| 			//dial rotated. ticks positive for right, negative for left | ||||
| 			var activeSession = _audioHelper.Current; | ||||
| 			if (activeSession != null) | ||||
| @ -258,7 +235,7 @@ public class DialAction : EncoderBase | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	public void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) | ||||
| 	public void WindowChanged() | ||||
| 	{ | ||||
| 		try | ||||
| 		{ | ||||
| @ -266,7 +243,7 @@ public class DialAction : EncoderBase | ||||
| 		} | ||||
| 		catch (Exception ex) | ||||
| 		{ | ||||
| 			Logger.Instance.LogMessage(TracingLevel.ERROR, $"Unexpected Error in DialDown:\n {ex}"); | ||||
| 			Logger.Instance.LogMessage(TracingLevel.ERROR, $"Unexpected Error in Window Down:\n {ex}"); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -70,6 +70,7 @@ | ||||
|     <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
|     <Compile Include="UI\UIState.cs" /> | ||||
|     <Compile Include="UI\ValueWithOpacity.cs" /> | ||||
|     <Compile Include="WindowChangedEventLoop.cs" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <None Include="App.config" /> | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Runtime.InteropServices; | ||||
| using System.Text; | ||||
|  | ||||
| namespace FocusVolumeControl; | ||||
|  | ||||
| @ -59,4 +60,11 @@ public class Native | ||||
| 	[DllImport("ntdll.dll")] | ||||
| 	public static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ParentProcessUtilities processInformation, int processInformationLength, out int returnLength); | ||||
|  | ||||
|  | ||||
| 	[DllImport("Kernel32.dll")] | ||||
| 	public static extern bool QueryFullProcessImageName(IntPtr hProcess, uint flags, StringBuilder buffer, ref uint bufferSize); | ||||
|  | ||||
| 	[DllImport("kernel32.dll")] | ||||
| 	public static extern IntPtr OpenProcess(uint processAccess, bool inheritHandle, int processId); | ||||
|  | ||||
| } | ||||
|  | ||||
							
								
								
									
										54
									
								
								src/FocusVolumeControl/WindowChangedEventLoop.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/FocusVolumeControl/WindowChangedEventLoop.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| using BarRaider.SdTools; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Runtime.InteropServices; | ||||
| using System.Text; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
| using System.Windows.Threading; | ||||
|  | ||||
| namespace FocusVolumeControl | ||||
| { | ||||
| 	internal class WindowChangedEventLoop | ||||
| 	{ | ||||
| 		private static readonly Lazy<WindowChangedEventLoop> _lazy = new Lazy<WindowChangedEventLoop>(() => new WindowChangedEventLoop()); | ||||
| 		public static WindowChangedEventLoop Instance => _lazy.Value; | ||||
|  | ||||
| 		readonly Thread _thread; | ||||
| 		Dispatcher _dispatcher; | ||||
|  | ||||
| 		IntPtr _foregroundWindowChangedEvent; | ||||
| 		Native.WinEventDelegate _delegate; | ||||
|  | ||||
| 		private WindowChangedEventLoop() | ||||
| 		{ | ||||
| 			_thread = new Thread(() => | ||||
| 			{ | ||||
| 				Logger.Instance.LogMessage(TracingLevel.DEBUG, "Starting Window Changed Event Loop"); | ||||
| 				_delegate = new Native.WinEventDelegate(WinEventProc); | ||||
| 				_foregroundWindowChangedEvent = Native.RegisterForForegroundWindowChangedEvent(_delegate); | ||||
|  | ||||
| 				_dispatcher = Dispatcher.CurrentDispatcher; | ||||
| 				Dispatcher.Run(); | ||||
| 				Logger.Instance.LogMessage(TracingLevel.DEBUG, "Window Changed Event Loop Stopped"); | ||||
| 			}); | ||||
| 			_thread.SetApartmentState(ApartmentState.STA); | ||||
| 			_thread.Start(); | ||||
| 		} | ||||
|  | ||||
| 		public event Action WindowChanged; | ||||
|  | ||||
| 		private void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) | ||||
| 		{ | ||||
| 			try | ||||
| 			{ | ||||
| 				WindowChanged?.Invoke(); | ||||
| 			} | ||||
| 			catch (Exception ex) | ||||
| 			{ | ||||
| 				Logger.Instance.LogMessage(TracingLevel.ERROR, $"Unexpected Error in EventHandler:\n {ex}"); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @ -33,7 +33,7 @@ | ||||
|   "Name": "Focused Application Volume", | ||||
|   "Description": "Control the volume of the focused application", | ||||
|   "URL": "https://github.com/dlprows/FocusVolumeControl", | ||||
|   "Version": "1.1.1", | ||||
|   "Version": "1.1.2", | ||||
|   "CodePath": "FocusVolumeControl", | ||||
|   "Category": "Volume Control [dlprows]", | ||||
|   "Icon": "Images/pluginIcon", | ||||
|  | ||||
		Reference in New Issue
	
	Block a user