Compare commits
	
		
			8 Commits
		
	
	
		
			v1.1.0
			...
			609a7bdb65
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 609a7bdb65 | |||
| 13fdfde3e5 | |||
| bbad79b4f3 | |||
| 708180dc8e | |||
| 5711ace990 | |||
| d89c8b1ffa | |||
| f94052e54b | |||
| ceb3494e43 | 
| @ -1,67 +1,89 @@ | |||||||
| using CoreAudio; | 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.Linq; | using System.Linq; | ||||||
|  | using System.Runtime.InteropServices; | ||||||
|  |  | ||||||
| namespace FocusVolumeControl; | namespace FocusVolumeControl; | ||||||
|  |  | ||||||
| public class AudioHelper | public class AudioHelper | ||||||
| { | { | ||||||
| 	IAudioSession _current; | 	static object _lock = new object(); | ||||||
| 	List<Process> _currentProcesses; | 	List<Process> _currentProcesses; | ||||||
|  |  | ||||||
| 	public IAudioSession FindSession(List<Process> processes) | 	public IAudioSession Current { get; private set; } | ||||||
| 	{ |  | ||||||
| 		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(); |  | ||||||
|  |  | ||||||
| 	public void ResetCache() | 	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) | ||||||
|  | 				{ | ||||||
|  | 					try | ||||||
|  | 					{ | ||||||
|  | 						var displayName = audioProcess.MainModule.FileVersionInfo.FileDescription; | ||||||
|  | 						if (string.IsNullOrEmpty(displayName)) | ||||||
|  | 						{ | ||||||
|  | 							displayName = audioProcess.ProcessName; | ||||||
|  | 						} | ||||||
|  | 						results.DisplayName = displayName; | ||||||
|  | 					} | ||||||
|  | 					catch | ||||||
|  | 					{ | ||||||
|  | 						results.DisplayName = audioProcess.ProcessName; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					results.ExecutablePath = audioProcess.MainModule.FileName; | ||||||
|  |  | ||||||
|  | 					currentIndex = index; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				//some apps like discord have multiple volume processes. | ||||||
|  | 				results.AddSession(session); | ||||||
|  |  | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return results.Any() ? results : null; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
| 	public IAudioSession GetActiveSession(FallbackBehavior fallbackBehavior) | 	public IAudioSession GetActiveSession(FallbackBehavior fallbackBehavior) | ||||||
| 	{ | 	{ | ||||||
| 		lock (_lock) | 		lock (_lock) | ||||||
| @ -70,23 +92,23 @@ public class AudioHelper | |||||||
|  |  | ||||||
| 			if (_currentProcesses == null || !_currentProcesses.SequenceEqual(processes)) | 			if (_currentProcesses == null || !_currentProcesses.SequenceEqual(processes)) | ||||||
| 			{ | 			{ | ||||||
| 				_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; | 			_currentProcesses = processes; | ||||||
| 			return _current; | 			return Current; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @ -126,10 +148,21 @@ public class AudioHelper | |||||||
|  |  | ||||||
| 		try | 		try | ||||||
| 		{ | 		{ | ||||||
| 			var blah = ParentProcessUtilities.GetParentProcess(pid); | 			//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 | ||||||
| 			if (blah != null && blah.ProcessName != "explorer" && blah.ProcessName != "svchost") | 			//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 | 		catch | ||||||
| @ -142,47 +175,65 @@ public class AudioHelper | |||||||
|  |  | ||||||
| 	public void ResetAll() | 	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); | 			var volume = (ISimpleAudioVolume)session; | ||||||
| 			using var manager = device.AudioSessionManager2; | 			var guid = Guid.Empty; | ||||||
|  | 			volume.SetMasterVolume(1, ref guid); | ||||||
| 			foreach (var session in manager.Sessions) | 			volume.SetMute(false, ref guid); | ||||||
| 			{ |  | ||||||
| 				session.SimpleAudioVolume.MasterVolume = 1; |  | ||||||
| 				session.SimpleAudioVolume.Mute = false; |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
| 		catch { } |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public IAudioSession GetSystemSounds() | 	public IAudioSession GetSystemSounds() | ||||||
| 	{ | 	{ | ||||||
| 		var deviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid()); | 		var deviceEnumerator = (CoreAudio)new MMDeviceEnumerator(); | ||||||
|  |  | ||||||
| 		using var device = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); | 		deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out var device); | ||||||
| 		using var manager = device.AudioSessionManager2; |  | ||||||
|  |  | ||||||
| 		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; | 		return null; | ||||||
| 	} | 	} | ||||||
| 	public IAudioSession GetSystemVolume() | 	public IAudioSession GetSystemVolume() | ||||||
| 	{ | 	{ | ||||||
| 		var deviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid()); | 		var deviceEnumerator = (CoreAudio)new MMDeviceEnumerator(); | ||||||
|  |  | ||||||
| 		using var device = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); | 		deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out var device); | ||||||
| 		return new SystemVolumeAudioSession(device.AudioEndpointVolume); |  | ||||||
|  | 		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.Collections.Generic; | ||||||
| using System.Linq; | using System.Linq; | ||||||
| using BarRaider.SdTools; | using BarRaider.SdTools; | ||||||
| using System.Drawing; | using System.Drawing; | ||||||
|  | using System.Runtime.InteropServices; | ||||||
|  |  | ||||||
| namespace FocusVolumeControl.AudioSessions; | namespace FocusVolumeControl.AudioSessions; | ||||||
|  |  | ||||||
| public 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 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; | 	string _icon; | ||||||
|  |  | ||||||
| @ -36,11 +37,11 @@ public class ActiveAudioSessionWrapper : IAudioSession | |||||||
| 	{ | 	{ | ||||||
| 		return Volume.Any(); | 		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() | 	public void ToggleMute() | ||||||
| @ -51,28 +52,52 @@ public class ActiveAudioSessionWrapper : IAudioSession | |||||||
| 		//when any volumes are unmuted, Volume.All will return false | 		//when any volumes are unmuted, Volume.All will return false | ||||||
| 		//so we set muted to true (opposite of Volume.All) | 		//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() | 	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) | 	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 | 		//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); | 		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() | 	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); | 		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 | public interface IAudioSession | ||||||
| { | { | ||||||
|  | |||||||
| @ -1,33 +1,46 @@ | |||||||
| using CoreAudio; | using System; | ||||||
| using System; | using System.Runtime.InteropServices; | ||||||
|  |  | ||||||
| namespace FocusVolumeControl.AudioSessions; | 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 DisplayName => "System sounds"; | ||||||
| 	public string GetIcon() => "Images/systemSounds"; | 	public string GetIcon() => "Images/systemSounds"; | ||||||
|  |  | ||||||
| 	public void ToggleMute() | 	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) | 	public void IncrementVolumeLevel(int step, int ticks) | ||||||
| 	{ | 	{ | ||||||
| 		var level = VolumeHelpers.GetAdjustedVolume(_volumeControl.MasterVolume, step, ticks); | 		_volumeControl.GetMasterVolume(out var level); | ||||||
| 		_volumeControl.MasterVolume = 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; | namespace FocusVolumeControl.AudioSessions; | ||||||
|  |  | ||||||
| internal class SystemVolumeAudioSession : IAudioSession | internal sealed class SystemVolumeAudioSession : IAudioSession | ||||||
| { | { | ||||||
| 	public SystemVolumeAudioSession(AudioEndpointVolume volumeControl) | 	public SystemVolumeAudioSession(IAudioEndpointVolume volumeControl) | ||||||
| 	{ | 	{ | ||||||
| 		_volumeControl = volumeControl; | 		_volumeControl = volumeControl; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	AudioEndpointVolume _volumeControl; | 	IAudioEndpointVolume _volumeControl; | ||||||
|  |  | ||||||
| 	public string DisplayName => "System Volume"; | 	public string DisplayName => "System Volume"; | ||||||
| 	public string GetIcon() => "Images/encoderIcon"; | 	public string GetIcon() => "Images/encoderIcon"; | ||||||
|  |  | ||||||
| 	public void ToggleMute() | 	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) | 	public void IncrementVolumeLevel(int step, int ticks) | ||||||
| 	{ | 	{ | ||||||
| 		var level = VolumeHelpers.GetAdjustedVolume(_volumeControl.MasterVolumeLevelScalar, step, ticks); | 		_volumeControl.GetMasterVolumeLevelScalar(out var level); | ||||||
| 		_volumeControl.MasterVolumeLevelScalar = 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) | 		public static float GetAdjustedVolume(float startingVolume, int step, int ticks) | ||||||
| 		{ | 		{ | ||||||
|  | 			if(step <= 0) | ||||||
|  | 			{ | ||||||
|  | 				step = 1; | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			var level = startingVolume; | 			var level = startingVolume; | ||||||
|  |  | ||||||
| 			level += 0.01f * step * ticks; | 			level += 0.01f * step * ticks; | ||||||
|  | |||||||
| @ -26,21 +26,13 @@ public class DialAction : EncoderBase | |||||||
| 		{ | 		{ | ||||||
| 			PluginSettings instance = new PluginSettings(); | 			PluginSettings instance = new PluginSettings(); | ||||||
| 			instance.FallbackBehavior = FallbackBehavior.SystemSounds; | 			instance.FallbackBehavior = FallbackBehavior.SystemSounds; | ||||||
|  | 			instance.StepSize = 1; | ||||||
| 			return instance; | 			return instance; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	private PluginSettings settings; | 	PluginSettings settings; | ||||||
|  |  | ||||||
| 	IntPtr _foregroundWindowChangedEvent; |  | ||||||
| 	Native.WinEventDelegate _delegate; |  | ||||||
|  |  | ||||||
| 	IAudioSession _currentAudioSession; |  | ||||||
| 	AudioHelper _audioHelper = new AudioHelper(); | 	AudioHelper _audioHelper = new AudioHelper(); | ||||||
|  |  | ||||||
| 	Thread _thread; |  | ||||||
| 	Dispatcher _dispatcher; |  | ||||||
|  |  | ||||||
| 	UIState _previousState; | 	UIState _previousState; | ||||||
|  |  | ||||||
| 	public DialAction(ISDConnection connection, InitialPayload payload) : base(connection, payload) | 	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) | 		if (payload.Settings == null || payload.Settings.Count == 0) | ||||||
| 		{ | 		{ | ||||||
| 			settings = PluginSettings.CreateDefaultSettings(); | 			settings = PluginSettings.CreateDefaultSettings(); | ||||||
| 			SaveSettings(); | 			_ = SaveSettings(); | ||||||
| 		} | 		} | ||||||
| 		else | 		else | ||||||
| 		{ | 		{ | ||||||
| 			settings = payload.Settings.ToObject<PluginSettings>(); | 			settings = payload.Settings.ToObject<PluginSettings>(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		_thread = new Thread(() => | 		WindowChangedEventLoop.Instance.WindowChanged += WindowChanged; | ||||||
| 		{ |  | ||||||
| 			Logger.Instance.LogMessage(TracingLevel.DEBUG, "Registering for events"); |  | ||||||
| 			_delegate = new Native.WinEventDelegate(WinEventProc); |  | ||||||
| 			_foregroundWindowChangedEvent = Native.RegisterForForegroundWindowChangedEvent(_delegate); |  | ||||||
|  |  | ||||||
| 			Logger.Instance.LogMessage(TracingLevel.DEBUG, "Starting Dispatcher"); | 		var session = _audioHelper.GetActiveSession(settings.FallbackBehavior); | ||||||
| 			_dispatcher = Dispatcher.CurrentDispatcher; | 		_ = UpdateStateIfNeeded(session); | ||||||
| 			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(); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public override void Dispose() | 	public override void Dispose() | ||||||
| 	{ | 	{ | ||||||
| 		Logger.Instance.LogMessage(TracingLevel.DEBUG, "Disposing"); | 		//Logger.Instance.LogMessage(TracingLevel.DEBUG, "Disposing"); | ||||||
| 		if (_foregroundWindowChangedEvent != IntPtr.Zero) | 		WindowChangedEventLoop.Instance.WindowChanged -= WindowChanged; | ||||||
| 		{ |  | ||||||
| 			Native.UnhookWinEvent(_foregroundWindowChangedEvent); |  | ||||||
| 		} |  | ||||||
| 		_dispatcher.InvokeShutdown(); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public override async void DialDown(DialPayload payload) | 	public override async void DialDown(DialPayload payload) | ||||||
| 	{ | 	{ | ||||||
| 		try | 		try | ||||||
| 		{ | 		{ | ||||||
| 			Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Down"); | 			//Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Down"); | ||||||
| 			await ToggleMuteAsync(); | 			await ToggleMuteAsync(); | ||||||
| 		} | 		} | ||||||
| 		catch (Exception ex) | 		catch (Exception ex) | ||||||
| @ -101,7 +77,7 @@ public class DialAction : EncoderBase | |||||||
| 	{ | 	{ | ||||||
| 		try | 		try | ||||||
| 		{ | 		{ | ||||||
| 			Logger.Instance.LogMessage(TracingLevel.INFO, "Touch Press"); | 			//Logger.Instance.LogMessage(TracingLevel.INFO, "Touch Press"); | ||||||
| 			if (payload.IsLongPress) | 			if (payload.IsLongPress) | ||||||
| 			{ | 			{ | ||||||
| 				await ResetAllAsync(); | 				await ResetAllAsync(); | ||||||
| @ -121,12 +97,13 @@ public class DialAction : EncoderBase | |||||||
| 	{ | 	{ | ||||||
| 		try | 		try | ||||||
| 		{ | 		{ | ||||||
| 			Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Rotate"); | 			//Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Rotate"); | ||||||
| 			//dial rotated. ticks positive for right, negative for left | 			//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); | 				activeSession.IncrementVolumeLevel(settings.StepSize, payload.Ticks); | ||||||
| 				await UpdateStateIfNeeded(); | 				await UpdateStateIfNeeded(activeSession); | ||||||
| 			} | 			} | ||||||
| 			else | 			else | ||||||
| 			{ | 			{ | ||||||
| @ -159,10 +136,11 @@ public class DialAction : EncoderBase | |||||||
| 	{ | 	{ | ||||||
| 		try | 		try | ||||||
| 		{ | 		{ | ||||||
| 			if (_currentAudioSession != null) | 			var activeSession = _audioHelper.Current; | ||||||
|  | 			if (activeSession != null) | ||||||
| 			{ | 			{ | ||||||
| 				_currentAudioSession.ToggleMute(); | 				activeSession.ToggleMute(); | ||||||
| 				await UpdateStateIfNeeded(); | 				await UpdateStateIfNeeded(activeSession); | ||||||
| 			} | 			} | ||||||
| 			else | 			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 | 			//called once every 1000ms and can be used for updating the title/image of the key | ||||||
| 			var activeSession = _audioHelper.GetActiveSession(settings.FallbackBehavior); | 			var activeSession = _audioHelper.GetActiveSession(settings.FallbackBehavior); | ||||||
|  |  | ||||||
| 			if (activeSession != null) | 			await UpdateStateIfNeeded(activeSession); | ||||||
| 			{ |  | ||||||
| 				_currentAudioSession = activeSession; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			await UpdateStateIfNeeded(); |  | ||||||
| 		} | 		} | ||||||
| 		catch (Exception ex) | 		catch (Exception ex) | ||||||
| 		{ | 		{ | ||||||
| @ -198,14 +171,14 @@ public class DialAction : EncoderBase | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	private async Task UpdateStateIfNeeded() | 	private async Task UpdateStateIfNeeded(IAudioSession audioSession) | ||||||
| 	{ | 	{ | ||||||
| 		try | 		try | ||||||
| 		{ | 		{ | ||||||
| 			if (_currentAudioSession != null) | 			if (audioSession != null) | ||||||
| 			{ | 			{ | ||||||
|  |  | ||||||
| 				var uiState = new UIState(_currentAudioSession); | 				var uiState = new UIState(audioSession); | ||||||
|  |  | ||||||
| 				if (_previousState != null && uiState != null && | 				if (_previousState != null && uiState != null && | ||||||
| 					uiState.Title == _previousState.Title && | 					uiState.Title == _previousState.Title && | ||||||
| @ -241,7 +214,7 @@ public class DialAction : EncoderBase | |||||||
| 		try | 		try | ||||||
| 		{ | 		{ | ||||||
| 			Tools.AutoPopulateSettings(settings, payload.Settings); | 			Tools.AutoPopulateSettings(settings, payload.Settings); | ||||||
| 			SaveSettings(); | 			_ = SaveSettings(); | ||||||
| 		} | 		} | ||||||
| 		catch (Exception ex) | 		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 | 		try | ||||||
| 		{ | 		{ | ||||||
| @ -270,7 +243,7 @@ public class DialAction : EncoderBase | |||||||
| 		} | 		} | ||||||
| 		catch (Exception ex) | 		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> |   <ItemGroup> | ||||||
|     <Compile Include="AudioSessions\ActiveAudioSessionWrapper.cs" /> |     <Compile Include="AudioSessions\ActiveAudioSessionWrapper.cs" /> | ||||||
|     <Compile Include="AudioHelper.cs" /> |     <Compile Include="AudioHelper.cs" /> | ||||||
|  |     <Compile Include="AudioSessions\CoreAudio.cs" /> | ||||||
|     <Compile Include="AudioSessions\VolumeHelpers.cs" /> |     <Compile Include="AudioSessions\VolumeHelpers.cs" /> | ||||||
|     <Compile Include="AudioSessions\SystemSoundsAudioSession.cs" /> |     <Compile Include="AudioSessions\SystemSoundsAudioSession.cs" /> | ||||||
|     <Compile Include="AudioSessions\SystemVolumeAudioSession.cs" /> |     <Compile Include="AudioSessions\SystemVolumeAudioSession.cs" /> | ||||||
| @ -69,6 +70,7 @@ | |||||||
|     <Compile Include="Properties\AssemblyInfo.cs" /> |     <Compile Include="Properties\AssemblyInfo.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" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <None Include="App.config" /> |     <None Include="App.config" /> | ||||||
| @ -92,9 +94,6 @@ | |||||||
|     </Content> |     </Content> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <PackageReference Include="CoreAudio"> |  | ||||||
|       <Version>1.27.0</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> | ||||||
|  | |||||||
							
								
								
									
										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", |   "Name": "Focused Application Volume", | ||||||
|   "Description": "Control the volume of the focused application", |   "Description": "Control the volume of the focused application", | ||||||
|   "URL": "https://github.com/dlprows/FocusVolumeControl", |   "URL": "https://github.com/dlprows/FocusVolumeControl", | ||||||
|   "Version": "1.1.0", |   "Version": "1.1.2", | ||||||
|   "CodePath": "FocusVolumeControl", |   "CodePath": "FocusVolumeControl", | ||||||
|   "Category": "Volume Control [dlprows]", |   "Category": "Volume Control [dlprows]", | ||||||
|   "Icon": "Images/pluginIcon", |   "Icon": "Images/pluginIcon", | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user