Compare commits
	
		
			11 Commits
		
	
	
		
			v1.1.0
			...
			ca634f8d3c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ca634f8d3c | |||
| bbb0e55ed6 | |||
| 520659ac52 | |||
| 609a7bdb65 | |||
| 13fdfde3e5 | |||
| bbad79b4f3 | |||
| 708180dc8e | |||
| 5711ace990 | |||
| d89c8b1ffa | |||
| f94052e54b | |||
| ceb3494e43 | 
| @ -1,92 +1,157 @@ | ||||
| using CoreAudio; | ||||
| 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 | ||||
| { | ||||
| 	IAudioSession _current; | ||||
| 	List<Process> _currentProcesses; | ||||
|  | ||||
| 	public IAudioSession FindSession(List<Process> processes) | ||||
| 	{ | ||||
| 		var deviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid()); | ||||
|  | ||||
| 		using var device = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); | ||||
| 		using var manager = device.AudioSessionManager2; | ||||
|  | ||||
| 		var sessions = manager.Sessions; | ||||
|  | ||||
| 		var matchingSession = new ActiveAudioSessionWrapper(); | ||||
|  | ||||
| 		foreach (var session in sessions) | ||||
| 		{ | ||||
| 			var audioProcess = Process.GetProcessById((int)session.ProcessID); | ||||
|  | ||||
| 			if (processes.Any(x => x.Id == session.ProcessID || x.ProcessName == audioProcess?.ProcessName)) | ||||
| 			{ | ||||
| 				try | ||||
| 				{ | ||||
| 					var displayName = audioProcess.MainModule.FileVersionInfo.FileDescription; | ||||
| 					if (string.IsNullOrEmpty(displayName)) | ||||
| 					{ | ||||
| 						displayName = audioProcess.ProcessName; | ||||
| 					} | ||||
| 					matchingSession.DisplayName = displayName; | ||||
| 				} | ||||
| 				catch | ||||
| 				{ | ||||
| 					matchingSession.DisplayName ??= audioProcess.ProcessName; | ||||
| 				} | ||||
|  | ||||
| 				matchingSession.ExecutablePath ??= audioProcess.MainModule.FileName; | ||||
|  | ||||
| 				//some apps like discord have multiple volume processes. | ||||
| 				matchingSession.AddVolume(session.SimpleAudioVolume); | ||||
| 			} | ||||
| 		} | ||||
| 		return matchingSession.Any() ? matchingSession : null; | ||||
| 	} | ||||
|  | ||||
| 	static object _lock = new object(); | ||||
| 	int[] _currentProcesses; | ||||
|  | ||||
| 	public IAudioSession Current { get; private set; } | ||||
|  | ||||
| 	public void ResetCache() | ||||
| 	{ | ||||
| 		lock(_lock) | ||||
| 		lock (_lock) | ||||
| 		{ | ||||
| 			_current = null; | ||||
| 			Current = null; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	public IAudioSession FindSession(List<Process> processes) | ||||
| 	{ | ||||
| 		var deviceEnumerator = (CoreAudio)new MMDeviceEnumerator(); | ||||
|  | ||||
| 		deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out var device); | ||||
|  | ||||
| 		Guid iid = typeof(IAudioSessionManager2).GUID; | ||||
| 		device.Activate(ref iid, 0, IntPtr.Zero, out var m); | ||||
| 		var manager = (IAudioSessionManager2)m; | ||||
|  | ||||
|  | ||||
| 		manager.GetSessionEnumerator(out var sessionEnumerator); | ||||
|  | ||||
| 		var results = new ActiveAudioSessionWrapper(); | ||||
| 		var currentIndex = int.MaxValue; | ||||
|  | ||||
| 		sessionEnumerator.GetCount(out var count); | ||||
| 		for (int i = 0; i < count; i++) | ||||
| 		{ | ||||
| 			sessionEnumerator.GetSession(i, out var session); | ||||
|  | ||||
| 			session.GetProcessId(out var sessionProcessId); | ||||
| 			var audioProcess = Process.GetProcessById(sessionProcessId); | ||||
|  | ||||
| 			var index = processes.FindIndex(x => x.Id == sessionProcessId || x.ProcessName == audioProcess?.ProcessName); | ||||
|  | ||||
| 			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); | ||||
|  | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		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); | ||||
| 				Current = FindSession(processes); | ||||
| 			} | ||||
|  | ||||
| 			if(_current == null) | ||||
| 			if (Current == null) | ||||
| 			{ | ||||
| 				if(fallbackBehavior == FallbackBehavior.SystemSounds && _current is not SystemSoundsAudioSession) | ||||
| 				if (fallbackBehavior == FallbackBehavior.SystemSounds && Current is not SystemSoundsAudioSession) | ||||
| 				{ | ||||
| 					_current = GetSystemSounds(); | ||||
| 					Current = GetSystemSounds(); | ||||
| 				} | ||||
| 				else if(fallbackBehavior == FallbackBehavior.SystemVolume && _current is not SystemVolumeAudioSession) | ||||
| 				else if (fallbackBehavior == FallbackBehavior.SystemVolume && Current is not SystemVolumeAudioSession) | ||||
| 				{ | ||||
| 					_current = GetSystemVolume(); | ||||
| 					Current = GetSystemVolume(); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			_currentProcesses = processes; | ||||
| 			return _current; | ||||
| 			_currentProcesses = processIds; | ||||
| 			return Current; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @ -126,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 | ||||
| @ -142,47 +218,65 @@ public class AudioHelper | ||||
|  | ||||
| 	public void ResetAll() | ||||
| 	{ | ||||
| 		try | ||||
| 		var deviceEnumerator = (CoreAudio)new MMDeviceEnumerator(); | ||||
|  | ||||
| 		deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out var device); | ||||
|  | ||||
| 		Guid iid = typeof(IAudioSessionManager2).GUID; | ||||
| 		device.Activate(ref iid, 0, IntPtr.Zero, out var m); | ||||
| 		var manager = (IAudioSessionManager2)m; | ||||
|  | ||||
|  | ||||
| 		manager.GetSessionEnumerator(out var sessionEnumerator); | ||||
|  | ||||
| 		sessionEnumerator.GetCount(out var count); | ||||
| 		for (int i = 0; i < count; i++) | ||||
| 		{ | ||||
| 			var deviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid()); | ||||
| 			sessionEnumerator.GetSession(i, out var session); | ||||
|  | ||||
| 			using var device = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); | ||||
| 			using var manager = device.AudioSessionManager2; | ||||
|  | ||||
| 			foreach (var session in manager.Sessions) | ||||
| 			{ | ||||
| 				session.SimpleAudioVolume.MasterVolume = 1; | ||||
| 				session.SimpleAudioVolume.Mute = false; | ||||
| 			} | ||||
| 			var volume = (ISimpleAudioVolume)session; | ||||
| 			var guid = Guid.Empty; | ||||
| 			volume.SetMasterVolume(1, ref guid); | ||||
| 			volume.SetMute(false, ref guid); | ||||
| 		} | ||||
| 		catch { } | ||||
| 	} | ||||
|  | ||||
| 	public IAudioSession GetSystemSounds() | ||||
| 	{ | ||||
| 		var deviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid()); | ||||
| 		var deviceEnumerator = (CoreAudio)new MMDeviceEnumerator(); | ||||
|  | ||||
| 		using var device = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); | ||||
| 		using var manager = device.AudioSessionManager2; | ||||
| 		deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out var device); | ||||
|  | ||||
| 		var sessions = manager.Sessions; | ||||
| 		Guid iid = typeof(IAudioSessionManager2).GUID; | ||||
| 		device.Activate(ref iid, 0, IntPtr.Zero, out var m); | ||||
| 		var manager = (IAudioSessionManager2)m; | ||||
|  | ||||
| 		foreach (var session in sessions) | ||||
|  | ||||
| 		manager.GetSessionEnumerator(out var sessionEnumerator); | ||||
|  | ||||
| 		sessionEnumerator.GetCount(out var count); | ||||
| 		for (int i = 0; i < count; i++) | ||||
| 		{ | ||||
| 			if (session.IsSystemSoundsSession) | ||||
| 			sessionEnumerator.GetSession(i, out var session); | ||||
|  | ||||
| 			if (session.IsSystemSoundsSession() == 0) | ||||
| 			{ | ||||
| 				return new SystemSoundsAudioSession(session.SimpleAudioVolume); | ||||
| 				return new SystemSoundsAudioSession(session); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return null; | ||||
| 	} | ||||
| 	public IAudioSession GetSystemVolume() | ||||
| 	{ | ||||
| 		var deviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid()); | ||||
| 		var deviceEnumerator = (CoreAudio)new MMDeviceEnumerator(); | ||||
|  | ||||
| 		using var device = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); | ||||
| 		return new SystemVolumeAudioSession(device.AudioEndpointVolume); | ||||
| 		deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out var device); | ||||
|  | ||||
| 		Guid iid = typeof(IAudioEndpointVolume).GUID; | ||||
| 		device.Activate(ref iid, 0, IntPtr.Zero, out var o); | ||||
| 		var endpointVolume = (IAudioEndpointVolume)o; | ||||
|  | ||||
| 		return new SystemVolumeAudioSession(endpointVolume); | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| @ -1,17 +1,18 @@ | ||||
| using CoreAudio; | ||||
| using System; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using BarRaider.SdTools; | ||||
| using System.Drawing; | ||||
| using System.Runtime.InteropServices; | ||||
|  | ||||
| namespace FocusVolumeControl.AudioSessions; | ||||
|  | ||||
| public class ActiveAudioSessionWrapper : IAudioSession | ||||
| public sealed class ActiveAudioSessionWrapper : IAudioSession | ||||
| { | ||||
| 	public string DisplayName { get; set; } | ||||
| 	public string ExecutablePath { get; set; } | ||||
| 	private List<SimpleAudioVolume> Volume { get; } = new List<SimpleAudioVolume>(); | ||||
| 	private List<IAudioSessionControl2> Sessions { get; } = new List<IAudioSessionControl2>(); | ||||
| 	private IEnumerable<ISimpleAudioVolume> Volume => Sessions.Cast<ISimpleAudioVolume>(); | ||||
|  | ||||
| 	string _icon; | ||||
|  | ||||
| @ -26,7 +27,7 @@ public class ActiveAudioSessionWrapper : IAudioSession | ||||
| 			} | ||||
| 			catch | ||||
| 			{ | ||||
| 				_icon = "Image/encoderIcon"; | ||||
| 				_icon = "Images/encoderIcon"; | ||||
| 			} | ||||
| 		} | ||||
| 		return _icon; | ||||
| @ -36,11 +37,11 @@ public class ActiveAudioSessionWrapper : IAudioSession | ||||
| 	{ | ||||
| 		return Volume.Any(); | ||||
| 	} | ||||
| 	public int Count => Volume.Count; | ||||
| 	public int Count => Sessions.Count; | ||||
|  | ||||
| 	public void AddVolume(SimpleAudioVolume volume) | ||||
| 	public void AddSession(IAudioSessionControl2 session) | ||||
| 	{ | ||||
| 		Volume.Add(volume); | ||||
| 		Sessions.Add(session); | ||||
| 	} | ||||
|  | ||||
| 	public void ToggleMute() | ||||
| @ -51,28 +52,52 @@ public class ActiveAudioSessionWrapper : IAudioSession | ||||
| 		//when any volumes are unmuted, Volume.All will return false | ||||
| 		//so we set muted to true (opposite of Volume.All) | ||||
|  | ||||
| 		var muted = Volume.All(x => x.Mute); | ||||
| 		var muted = IsMuted(); | ||||
|  | ||||
| 		Volume.ForEach(x => x.Mute = !muted); | ||||
| 		foreach(var v in Volume) | ||||
| 		{ | ||||
| 			var guid = Guid.Empty; | ||||
| 			v.SetMute(!muted, ref guid); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	public bool IsMuted() | ||||
| 	{ | ||||
| 		return Volume.All(x => x.Mute); | ||||
| 		return Volume.All(x => | ||||
| 		{ | ||||
| 			x.GetMute(out var mute);  | ||||
| 			return mute; | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	public void IncrementVolumeLevel(int step, int ticks) | ||||
| 	{ | ||||
| 		//if you have more than one volume. they will all get set based on the first volume control | ||||
| 		var level = Volume.FirstOrDefault()?.MasterVolume ?? 0; | ||||
| 		var volume = Volume.FirstOrDefault(); | ||||
| 		var level = 0f; | ||||
| 		if (volume != null) | ||||
| 		{ | ||||
| 			volume.GetMasterVolume(out level); | ||||
| 		} | ||||
| 	 | ||||
| 		level = VolumeHelpers.GetAdjustedVolume(level, step, ticks); | ||||
| 		Volume.ForEach(x => x.MasterVolume = level); | ||||
|  | ||||
| 		foreach(var v in Volume) | ||||
| 		{ | ||||
| 			var guid = Guid.Empty; | ||||
| 			v.SetMasterVolume(level, ref guid); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	public int GetVolumeLevel() | ||||
| 	{ | ||||
| 		var level = Volume.FirstOrDefault()?.MasterVolume ?? 0; | ||||
| 		var volume = Volume.FirstOrDefault(); | ||||
| 		var level = 0f; | ||||
| 		if(volume != null) | ||||
| 		{ | ||||
| 			volume.GetMasterVolume(out level); | ||||
| 		} | ||||
|  | ||||
| 		return VolumeHelpers.GetVolumePercentage(level); | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
							
								
								
									
										306
									
								
								src/FocusVolumeControl/AudioSessions/CoreAudio.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										306
									
								
								src/FocusVolumeControl/AudioSessions/CoreAudio.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,306 @@ | ||||
| using System; | ||||
| using System.Runtime.InteropServices; | ||||
|  | ||||
| namespace FocusVolumeControl.AudioSessions; | ||||
|  | ||||
|  | ||||
| [ComImport] | ||||
| [Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] | ||||
| internal class MMDeviceEnumerator | ||||
| { | ||||
| } | ||||
|  | ||||
| internal enum EDataFlow | ||||
| { | ||||
| 	eRender, | ||||
| 	eCapture, | ||||
| 	eAll, | ||||
| 	EDataFlow_enum_count | ||||
| } | ||||
|  | ||||
| internal enum ERole | ||||
| { | ||||
| 	eConsole, | ||||
| 	eMultimedia, | ||||
| 	eCommunications, | ||||
| 	ERole_enum_count | ||||
| } | ||||
|  | ||||
| [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||||
| internal interface CoreAudio | ||||
| { | ||||
| 	int NotImpl1(); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppDevice); | ||||
| } | ||||
|  | ||||
| [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||||
| internal interface IMMDevice | ||||
| { | ||||
| 	[PreserveSig] | ||||
| 	int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface); | ||||
| } | ||||
|  | ||||
| [Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||||
| internal interface IAudioSessionManager2 | ||||
| { | ||||
| 	int NotImpl1(); | ||||
| 	int NotImpl2(); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int GetSessionEnumerator(out IAudioSessionEnumerator SessionEnum); | ||||
| } | ||||
|  | ||||
| [Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||||
| internal interface IAudioSessionEnumerator | ||||
| { | ||||
| 	[PreserveSig] | ||||
| 	int GetCount(out int SessionCount); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int GetSession(int SessionCount, out IAudioSessionControl2 Session); | ||||
| } | ||||
|  | ||||
| [Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||||
| internal interface ISimpleAudioVolume | ||||
| { | ||||
| 	[PreserveSig] | ||||
| 	int SetMasterVolume(float fLevel, ref Guid EventContext); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int GetMasterVolume(out float pfLevel); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int SetMute(bool bMute, ref Guid EventContext); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int GetMute(out bool pbMute); | ||||
| } | ||||
|  | ||||
| [Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||||
| public interface IAudioSessionControl2 | ||||
| { | ||||
| 	// IAudioSessionControl | ||||
| 	[PreserveSig] | ||||
| 	int NotImpl0(); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int GetDisplayName([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int SetDisplayName([MarshalAs(UnmanagedType.LPWStr)] string Value, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int GetIconPath([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int SetIconPath([MarshalAs(UnmanagedType.LPWStr)] string Value, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int GetGroupingParam(out Guid pRetVal); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int SetGroupingParam([MarshalAs(UnmanagedType.LPStruct)] Guid Override, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int NotImpl1(); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int NotImpl2(); | ||||
|  | ||||
| 	// IAudioSessionControl2 | ||||
| 	[PreserveSig] | ||||
| 	int GetSessionIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int GetSessionInstanceIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int GetProcessId(out int pRetVal); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int IsSystemSoundsSession(); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int SetDuckingPreference(bool optOut); | ||||
| } | ||||
|  | ||||
| // http://netcoreaudio.codeplex.com/SourceControl/latest#trunk/Code/CoreAudio/Interfaces/IAudioEndpointVolume.cs | ||||
| [Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||||
| public interface IAudioEndpointVolume | ||||
| { | ||||
| 	[PreserveSig] | ||||
| 	int NotImpl1(); | ||||
|  | ||||
| 	[PreserveSig] | ||||
| 	int NotImpl2(); | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Gets a count of the channels in the audio stream. | ||||
| 	/// </summary> | ||||
| 	/// <param name="channelCount">The number of channels.</param> | ||||
| 	/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns> | ||||
| 	[PreserveSig] | ||||
| 	int GetChannelCount( | ||||
| 		[Out][MarshalAs(UnmanagedType.U4)] out UInt32 channelCount); | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Sets the master volume level of the audio stream, in decibels. | ||||
| 	/// </summary> | ||||
| 	/// <param name="level">The new master volume level in decibels.</param> | ||||
| 	/// <param name="eventContext">A user context value that is passed to the notification callback.</param> | ||||
| 	/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns> | ||||
| 	[PreserveSig] | ||||
| 	int SetMasterVolumeLevel( | ||||
| 		[In][MarshalAs(UnmanagedType.R4)] float level, | ||||
| 		[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext); | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Sets the master volume level, expressed as a normalized, audio-tapered value. | ||||
| 	/// </summary> | ||||
| 	/// <param name="level">The new master volume level expressed as a normalized value between 0.0 and 1.0.</param> | ||||
| 	/// <param name="eventContext">A user context value that is passed to the notification callback.</param> | ||||
| 	/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns> | ||||
| 	[PreserveSig] | ||||
| 	int SetMasterVolumeLevelScalar( | ||||
| 		[In][MarshalAs(UnmanagedType.R4)] float level, | ||||
| 		[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext); | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Gets the master volume level of the audio stream, in decibels. | ||||
| 	/// </summary> | ||||
| 	/// <param name="level">The volume level in decibels.</param> | ||||
| 	/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns> | ||||
| 	[PreserveSig] | ||||
| 	int GetMasterVolumeLevel( | ||||
| 		[Out][MarshalAs(UnmanagedType.R4)] out float level); | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Gets the master volume level, expressed as a normalized, audio-tapered value. | ||||
| 	/// </summary> | ||||
| 	/// <param name="level">The volume level expressed as a normalized value between 0.0 and 1.0.</param> | ||||
| 	/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns> | ||||
| 	[PreserveSig] | ||||
| 	int GetMasterVolumeLevelScalar( | ||||
| 		[Out][MarshalAs(UnmanagedType.R4)] out float level); | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Sets the volume level, in decibels, of the specified channel of the audio stream. | ||||
| 	/// </summary> | ||||
| 	/// <param name="channelNumber">The channel number.</param> | ||||
| 	/// <param name="level">The new volume level in decibels.</param> | ||||
| 	/// <param name="eventContext">A user context value that is passed to the notification callback.</param> | ||||
| 	/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns> | ||||
| 	[PreserveSig] | ||||
| 	int SetChannelVolumeLevel( | ||||
| 		[In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber, | ||||
| 		[In][MarshalAs(UnmanagedType.R4)] float level, | ||||
| 		[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext); | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Sets the normalized, audio-tapered volume level of the specified channel in the audio stream. | ||||
| 	/// </summary> | ||||
| 	/// <param name="channelNumber">The channel number.</param> | ||||
| 	/// <param name="level">The new master volume level expressed as a normalized value between 0.0 and 1.0.</param> | ||||
| 	/// <param name="eventContext">A user context value that is passed to the notification callback.</param> | ||||
| 	/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns> | ||||
| 	[PreserveSig] | ||||
| 	int SetChannelVolumeLevelScalar( | ||||
| 		[In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber, | ||||
| 		[In][MarshalAs(UnmanagedType.R4)] float level, | ||||
| 		[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext); | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Gets the volume level, in decibels, of the specified channel in the audio stream. | ||||
| 	/// </summary> | ||||
| 	/// <param name="channelNumber">The zero-based channel number.</param> | ||||
| 	/// <param name="level">The volume level in decibels.</param> | ||||
| 	/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns> | ||||
| 	[PreserveSig] | ||||
| 	int GetChannelVolumeLevel( | ||||
| 		[In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber, | ||||
| 		[Out][MarshalAs(UnmanagedType.R4)] out float level); | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Gets the normalized, audio-tapered volume level of the specified channel of the audio stream. | ||||
| 	/// </summary> | ||||
| 	/// <param name="channelNumber">The zero-based channel number.</param> | ||||
| 	/// <param name="level">The volume level expressed as a normalized value between 0.0 and 1.0.</param> | ||||
| 	/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns> | ||||
| 	[PreserveSig] | ||||
| 	int GetChannelVolumeLevelScalar( | ||||
| 		[In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber, | ||||
| 		[Out][MarshalAs(UnmanagedType.R4)] out float level); | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Sets the muting state of the audio stream. | ||||
| 	/// </summary> | ||||
| 	/// <param name="isMuted">True to mute the stream, or false to unmute the stream.</param> | ||||
| 	/// <param name="eventContext">A user context value that is passed to the notification callback.</param> | ||||
| 	/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns> | ||||
| 	[PreserveSig] | ||||
| 	int SetMute( | ||||
| 		[In][MarshalAs(UnmanagedType.Bool)] Boolean isMuted, | ||||
| 		[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext); | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Gets the muting state of the audio stream. | ||||
| 	/// </summary> | ||||
| 	/// <param name="isMuted">The muting state. True if the stream is muted, false otherwise.</param> | ||||
| 	/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns> | ||||
| 	[PreserveSig] | ||||
| 	int GetMute( | ||||
| 		[Out][MarshalAs(UnmanagedType.Bool)] out Boolean isMuted); | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Gets information about the current step in the volume range. | ||||
| 	/// </summary> | ||||
| 	/// <param name="step">The current zero-based step index.</param> | ||||
| 	/// <param name="stepCount">The total number of steps in the volume range.</param> | ||||
| 	/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns> | ||||
| 	[PreserveSig] | ||||
| 	int GetVolumeStepInfo( | ||||
| 		[Out][MarshalAs(UnmanagedType.U4)] out UInt32 step, | ||||
| 		[Out][MarshalAs(UnmanagedType.U4)] out UInt32 stepCount); | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Increases the volume level by one step. | ||||
| 	/// </summary> | ||||
| 	/// <param name="eventContext">A user context value that is passed to the notification callback.</param> | ||||
| 	/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns> | ||||
| 	[PreserveSig] | ||||
| 	int VolumeStepUp( | ||||
| 		[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext); | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Decreases the volume level by one step. | ||||
| 	/// </summary> | ||||
| 	/// <param name="eventContext">A user context value that is passed to the notification callback.</param> | ||||
| 	/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns> | ||||
| 	[PreserveSig] | ||||
| 	int VolumeStepDown( | ||||
| 		[In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext); | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Queries the audio endpoint device for its hardware-supported functions. | ||||
| 	/// </summary> | ||||
| 	/// <param name="hardwareSupportMask">A hardware support mask that indicates the capabilities of the endpoint.</param> | ||||
| 	/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns> | ||||
| 	[PreserveSig] | ||||
| 	int QueryHardwareSupport( | ||||
| 		[Out][MarshalAs(UnmanagedType.U4)] out UInt32 hardwareSupportMask); | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Gets the volume range of the audio stream, in decibels. | ||||
| 	/// </summary> | ||||
| 	/// <param name="volumeMin">The minimum volume level in decibels.</param> | ||||
| 	/// <param name="volumeMax">The maximum volume level in decibels.</param> | ||||
| 	/// <param name="volumeStep">The volume increment level in decibels.</param> | ||||
| 	/// <returns>An HRESULT code indicating whether the operation passed of failed.</returns> | ||||
| 	[PreserveSig] | ||||
| 	int GetVolumeRange( | ||||
| 		[Out][MarshalAs(UnmanagedType.R4)] out float volumeMin, | ||||
| 		[Out][MarshalAs(UnmanagedType.R4)] out float volumeMax, | ||||
| 		[Out][MarshalAs(UnmanagedType.R4)] out float volumeStep); | ||||
| } | ||||
| @ -1,4 +1,6 @@ | ||||
| namespace FocusVolumeControl.AudioSessions; | ||||
| using System; | ||||
|  | ||||
| namespace FocusVolumeControl.AudioSessions; | ||||
|  | ||||
| public interface IAudioSession | ||||
| { | ||||
|  | ||||
| @ -1,33 +1,46 @@ | ||||
| using CoreAudio; | ||||
| using System; | ||||
| using System; | ||||
| using System.Runtime.InteropServices; | ||||
|  | ||||
| namespace FocusVolumeControl.AudioSessions; | ||||
|  | ||||
| internal class SystemSoundsAudioSession : IAudioSession | ||||
| internal sealed class SystemSoundsAudioSession : IAudioSession | ||||
| { | ||||
| 	public SystemSoundsAudioSession(SimpleAudioVolume volumeControl) | ||||
| 	public SystemSoundsAudioSession(IAudioSessionControl2 sessionControl) | ||||
| 	{ | ||||
| 		_volumeControl = volumeControl; | ||||
| 		_sessionControl = sessionControl; | ||||
| 		_volumeControl = (ISimpleAudioVolume)sessionControl; | ||||
| 	} | ||||
|  | ||||
| 	SimpleAudioVolume _volumeControl; | ||||
| 	IAudioSessionControl2 _sessionControl; | ||||
| 	ISimpleAudioVolume _volumeControl; | ||||
|  | ||||
| 	public string DisplayName => "System sounds"; | ||||
| 	public string GetIcon() => "Images/systemSounds"; | ||||
|  | ||||
| 	public void ToggleMute() | ||||
| 	{ | ||||
| 		_volumeControl.Mute = !_volumeControl.Mute; | ||||
| 		var guid = Guid.Empty; | ||||
| 		_volumeControl.SetMute(!IsMuted(), ref guid); | ||||
| 	} | ||||
|  | ||||
| 	public bool IsMuted() => _volumeControl.Mute; | ||||
| 	public bool IsMuted() | ||||
| 	{ | ||||
| 		_volumeControl.GetMute(out var mute); | ||||
| 		return mute; | ||||
| 	} | ||||
|  | ||||
| 	public void IncrementVolumeLevel(int step, int ticks) | ||||
| 	{ | ||||
| 		var level = VolumeHelpers.GetAdjustedVolume(_volumeControl.MasterVolume, step, ticks); | ||||
| 		_volumeControl.MasterVolume = level; | ||||
| 		_volumeControl.GetMasterVolume(out var level); | ||||
| 		level = VolumeHelpers.GetAdjustedVolume(level, step, ticks); | ||||
|  | ||||
| 		var guid = Guid.Empty; | ||||
| 		_volumeControl.SetMasterVolume(level, ref guid); | ||||
| 	} | ||||
|  | ||||
| 	public int GetVolumeLevel() => VolumeHelpers.GetVolumePercentage(_volumeControl.MasterVolume); | ||||
|  | ||||
| 	public int GetVolumeLevel() | ||||
| 	{ | ||||
| 		_volumeControl.GetMasterVolume(out var level); | ||||
| 		return VolumeHelpers.GetVolumePercentage(level); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -1,33 +1,41 @@ | ||||
| using CoreAudio; | ||||
| using System; | ||||
| using System; | ||||
| using System.Runtime.InteropServices; | ||||
|  | ||||
| namespace FocusVolumeControl.AudioSessions; | ||||
|  | ||||
| internal class SystemVolumeAudioSession : IAudioSession | ||||
| internal sealed class SystemVolumeAudioSession : IAudioSession | ||||
| { | ||||
| 	public SystemVolumeAudioSession(AudioEndpointVolume volumeControl) | ||||
| 	public SystemVolumeAudioSession(IAudioEndpointVolume volumeControl) | ||||
| 	{ | ||||
| 		_volumeControl = volumeControl; | ||||
| 	} | ||||
|  | ||||
| 	AudioEndpointVolume _volumeControl; | ||||
| 	IAudioEndpointVolume _volumeControl; | ||||
|  | ||||
| 	public string DisplayName => "System Volume"; | ||||
| 	public string GetIcon() => "Images/encoderIcon"; | ||||
|  | ||||
| 	public void ToggleMute() | ||||
| 	{ | ||||
| 		_volumeControl.Mute = !_volumeControl.Mute; | ||||
| 		_volumeControl.SetMute(!IsMuted(), Guid.Empty); | ||||
| 	} | ||||
|  | ||||
| 	public bool IsMuted() => _volumeControl.Mute; | ||||
| 	public bool IsMuted() | ||||
| 	{ | ||||
| 		_volumeControl.GetMute(out var mute); | ||||
| 		return mute; | ||||
| 	} | ||||
|  | ||||
| 	public void IncrementVolumeLevel(int step, int ticks) | ||||
| 	{ | ||||
| 		var level = VolumeHelpers.GetAdjustedVolume(_volumeControl.MasterVolumeLevelScalar, step, ticks); | ||||
| 		_volumeControl.MasterVolumeLevelScalar = level; | ||||
| 		_volumeControl.GetMasterVolumeLevelScalar(out var level); | ||||
| 		level = VolumeHelpers.GetAdjustedVolume(level, step, ticks); | ||||
| 		_volumeControl.SetMasterVolumeLevelScalar(level, Guid.Empty); | ||||
| 	} | ||||
|  | ||||
| 	public int GetVolumeLevel() => VolumeHelpers.GetVolumePercentage(_volumeControl.MasterVolumeLevelScalar); | ||||
|  | ||||
| 	public int GetVolumeLevel() | ||||
| 	{ | ||||
| 		_volumeControl.GetMasterVolumeLevelScalar(out var level); | ||||
| 		return VolumeHelpers.GetVolumePercentage(level); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -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,21 +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; | ||||
|  | ||||
| 	IAudioSession _currentAudioSession; | ||||
| 	PluginSettings settings; | ||||
| 	AudioHelper _audioHelper = new AudioHelper(); | ||||
|  | ||||
| 	Thread _thread; | ||||
| 	Dispatcher _dispatcher; | ||||
|  | ||||
| 	UIState _previousState; | ||||
|  | ||||
| 	public DialAction(ISDConnection connection, InitialPayload payload) : base(connection, payload) | ||||
| @ -48,46 +40,30 @@ public class DialAction : EncoderBase | ||||
| 		if (payload.Settings == null || payload.Settings.Count == 0) | ||||
| 		{ | ||||
| 			settings = PluginSettings.CreateDefaultSettings(); | ||||
| 			SaveSettings(); | ||||
| 			_ = SaveSettings(); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			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); | ||||
| 		WindowChangedEventLoop.Instance.WindowChanged += WindowChanged; | ||||
|  | ||||
| 			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(); | ||||
|  | ||||
| 		_currentAudioSession = settings.FallbackBehavior == FallbackBehavior.SystemSounds ? _audioHelper.GetSystemSounds() : _audioHelper.GetSystemVolume(); | ||||
| 		_ = UpdateStateIfNeeded(); | ||||
| 		var session = _audioHelper.GetActiveSession(settings.FallbackBehavior); | ||||
| 		_ = UpdateStateIfNeeded(session); | ||||
| 	} | ||||
|  | ||||
| 	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) | ||||
| @ -101,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(); | ||||
| @ -121,12 +97,13 @@ 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 | ||||
| 			if (_currentAudioSession != null) | ||||
| 			var activeSession = _audioHelper.Current; | ||||
| 			if (activeSession != null) | ||||
| 			{ | ||||
| 				_currentAudioSession.IncrementVolumeLevel(settings.StepSize, payload.Ticks); | ||||
| 				await UpdateStateIfNeeded(); | ||||
| 				activeSession.IncrementVolumeLevel(settings.StepSize, payload.Ticks); | ||||
| 				await UpdateStateIfNeeded(activeSession); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| @ -159,10 +136,11 @@ public class DialAction : EncoderBase | ||||
| 	{ | ||||
| 		try | ||||
| 		{ | ||||
| 			if (_currentAudioSession != null) | ||||
| 			var activeSession = _audioHelper.Current; | ||||
| 			if (activeSession != null) | ||||
| 			{ | ||||
| 				_currentAudioSession.ToggleMute(); | ||||
| 				await UpdateStateIfNeeded(); | ||||
| 				activeSession.ToggleMute(); | ||||
| 				await UpdateStateIfNeeded(activeSession); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| @ -184,12 +162,7 @@ public class DialAction : EncoderBase | ||||
| 			//called once every 1000ms and can be used for updating the title/image of the key | ||||
| 			var activeSession = _audioHelper.GetActiveSession(settings.FallbackBehavior); | ||||
|  | ||||
| 			if (activeSession != null) | ||||
| 			{ | ||||
| 				_currentAudioSession = activeSession; | ||||
| 			} | ||||
|  | ||||
| 			await UpdateStateIfNeeded(); | ||||
| 			await UpdateStateIfNeeded(activeSession); | ||||
| 		} | ||||
| 		catch (Exception ex) | ||||
| 		{ | ||||
| @ -198,14 +171,14 @@ public class DialAction : EncoderBase | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private async Task UpdateStateIfNeeded() | ||||
| 	private async Task UpdateStateIfNeeded(IAudioSession audioSession) | ||||
| 	{ | ||||
| 		try | ||||
| 		{ | ||||
| 			if (_currentAudioSession != null) | ||||
| 			if (audioSession != null) | ||||
| 			{ | ||||
|  | ||||
| 				var uiState = new UIState(_currentAudioSession); | ||||
| 				var uiState = new UIState(audioSession); | ||||
|  | ||||
| 				if (_previousState != null && uiState != null && | ||||
| 					uiState.Title == _previousState.Title && | ||||
| @ -241,7 +214,7 @@ public class DialAction : EncoderBase | ||||
| 		try | ||||
| 		{ | ||||
| 			Tools.AutoPopulateSettings(settings, payload.Settings); | ||||
| 			SaveSettings(); | ||||
| 			_ = SaveSettings(); | ||||
| 		} | ||||
| 		catch (Exception ex) | ||||
| 		{ | ||||
| @ -262,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 | ||||
| 		{ | ||||
| @ -270,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}"); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -56,6 +56,7 @@ | ||||
|   <ItemGroup> | ||||
|     <Compile Include="AudioSessions\ActiveAudioSessionWrapper.cs" /> | ||||
|     <Compile Include="AudioHelper.cs" /> | ||||
|     <Compile Include="AudioSessions\CoreAudio.cs" /> | ||||
|     <Compile Include="AudioSessions\VolumeHelpers.cs" /> | ||||
|     <Compile Include="AudioSessions\SystemSoundsAudioSession.cs" /> | ||||
|     <Compile Include="AudioSessions\SystemVolumeAudioSession.cs" /> | ||||
| @ -69,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" /> | ||||
| @ -92,9 +94,6 @@ | ||||
|     </Content> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="CoreAudio"> | ||||
|       <Version>1.27.0</Version> | ||||
|     </PackageReference> | ||||
|     <PackageReference Include="IsExternalInit"> | ||||
|       <Version>1.0.3</Version> | ||||
|       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
|  | ||||
| @ -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.0", | ||||
|   "Version": "1.1.2", | ||||
|   "CodePath": "FocusVolumeControl", | ||||
|   "Category": "Volume Control [dlprows]", | ||||
|   "Icon": "Images/pluginIcon", | ||||
|  | ||||
		Reference in New Issue
	
	Block a user