7 Commits

Author SHA1 Message Date
609a7bdb65 Add a special case to make steam launched games not control steam's sound, because after fixing the process matching to make it pick up the right name and icon consistently, it makes it weirder for steam to have its volume changed unexpectedly 2023-09-24 15:35:07 -06:00
13fdfde3e5 Improve process matching by using the display name and icon of the first match in the process list.
previously it was non-deterministic, so you would sometimes get steam icon with game name, or steam name and icon even though the game was being controlled.
This makes it more consistently the correct icon and name
2023-09-24 15:34:17 -06:00
bbad79b4f3 Update Version 2023-09-24 15:04:47 -06:00
708180dc8e Commenting out the debug/info logs so the logs will only have errors 2023-09-24 15:02:20 -06:00
5711ace990 Fixed an issue where disposing was not working correctly, so using auto-profiles by application was causing the plugin to stop responding
now there is just one event loop for window changing, and instances of the dial action can register to the event
2023-09-24 15:01:12 -06:00
d89c8b1ffa Fix a bug where the step size starts at 0 instead of 1 2023-09-24 14:59:52 -06:00
f94052e54b added a comment to remind myself about why steam games do funny things 2023-09-16 15:50:17 -06:00
6 changed files with 112 additions and 53 deletions

View File

@ -36,6 +36,7 @@ public class AudioHelper
manager.GetSessionEnumerator(out var sessionEnumerator);
var results = new ActiveAudioSessionWrapper();
var currentIndex = int.MaxValue;
sessionEnumerator.GetCount(out var count);
for (int i = 0; i < count; i++)
@ -45,23 +46,33 @@ public class AudioHelper
session.GetProcessId(out var sessionProcessId);
var audioProcess = Process.GetProcessById(sessionProcessId);
if (processes.Any(x => x.Id == sessionProcessId || x.ProcessName == audioProcess?.ProcessName))
{
try
{
var displayName = audioProcess.MainModule.FileVersionInfo.FileDescription;
if (string.IsNullOrEmpty(displayName))
{
displayName = audioProcess.ProcessName;
}
results.DisplayName = displayName;
}
catch
{
results.DisplayName ??= audioProcess.ProcessName;
}
var index = processes.FindIndex(x => x.Id == sessionProcessId || x.ProcessName == audioProcess?.ProcessName);
results.ExecutablePath ??= audioProcess.MainModule.FileName;
if (index > -1)
{
//processes will be ordered from best to worst (starts with the app, goes to parent)
//so we want the display name and executable path to come from the process that is closest to the front of the list
//but we want all matching sessions so things like discord work right
if (index < currentIndex)
{
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);
@ -137,10 +148,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

View File

@ -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;

View File

@ -26,20 +26,13 @@ public class DialAction : EncoderBase
{
PluginSettings instance = new PluginSettings();
instance.FallbackBehavior = FallbackBehavior.SystemSounds;
instance.StepSize = 1;
return instance;
}
}
private PluginSettings settings;
IntPtr _foregroundWindowChangedEvent;
Native.WinEventDelegate _delegate;
PluginSettings settings;
AudioHelper _audioHelper = new AudioHelper();
Thread _thread;
Dispatcher _dispatcher;
UIState _previousState;
public DialAction(ISDConnection connection, InitialPayload payload) : base(connection, payload)
@ -54,19 +47,7 @@ public class DialAction : EncoderBase
settings = payload.Settings.ToObject<PluginSettings>();
}
_thread = new Thread(() =>
{
Logger.Instance.LogMessage(TracingLevel.DEBUG, "Registering for events");
_delegate = new Native.WinEventDelegate(WinEventProc);
_foregroundWindowChangedEvent = Native.RegisterForForegroundWindowChangedEvent(_delegate);
Logger.Instance.LogMessage(TracingLevel.DEBUG, "Starting Dispatcher");
_dispatcher = Dispatcher.CurrentDispatcher;
Dispatcher.Run();
Logger.Instance.LogMessage(TracingLevel.DEBUG, "Dispatcher Stopped");
});
_thread.SetApartmentState(ApartmentState.STA);
_thread.Start();
WindowChangedEventLoop.Instance.WindowChanged += WindowChanged;
var session = _audioHelper.GetActiveSession(settings.FallbackBehavior);
_ = UpdateStateIfNeeded(session);
@ -74,19 +55,15 @@ public class DialAction : EncoderBase
public override void Dispose()
{
Logger.Instance.LogMessage(TracingLevel.DEBUG, "Disposing");
if (_foregroundWindowChangedEvent != IntPtr.Zero)
{
Native.UnhookWinEvent(_foregroundWindowChangedEvent);
}
_dispatcher.InvokeShutdown();
//Logger.Instance.LogMessage(TracingLevel.DEBUG, "Disposing");
WindowChangedEventLoop.Instance.WindowChanged -= WindowChanged;
}
public override async void DialDown(DialPayload payload)
{
try
{
Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Down");
//Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Down");
await ToggleMuteAsync();
}
catch (Exception ex)
@ -100,7 +77,7 @@ public class DialAction : EncoderBase
{
try
{
Logger.Instance.LogMessage(TracingLevel.INFO, "Touch Press");
//Logger.Instance.LogMessage(TracingLevel.INFO, "Touch Press");
if (payload.IsLongPress)
{
await ResetAllAsync();
@ -120,7 +97,7 @@ public class DialAction : EncoderBase
{
try
{
Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Rotate");
//Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Rotate");
//dial rotated. ticks positive for right, negative for left
var activeSession = _audioHelper.Current;
if (activeSession != null)
@ -258,7 +235,7 @@ public class DialAction : EncoderBase
}
public void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
public void WindowChanged()
{
try
{
@ -266,7 +243,7 @@ public class DialAction : EncoderBase
}
catch (Exception ex)
{
Logger.Instance.LogMessage(TracingLevel.ERROR, $"Unexpected Error in DialDown:\n {ex}");
Logger.Instance.LogMessage(TracingLevel.ERROR, $"Unexpected Error in Window Down:\n {ex}");
}
}
}

View File

@ -70,6 +70,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UI\UIState.cs" />
<Compile Include="UI\ValueWithOpacity.cs" />
<Compile Include="WindowChangedEventLoop.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />

View 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}");
}
}
}
}

View File

@ -33,7 +33,7 @@
"Name": "Focused Application Volume",
"Description": "Control the volume of the focused application",
"URL": "https://github.com/dlprows/FocusVolumeControl",
"Version": "1.1.1",
"Version": "1.1.2",
"CodePath": "FocusVolumeControl",
"Category": "Volume Control [dlprows]",
"Icon": "Images/pluginIcon",