Add settings for fallback behavior

update action icon with padding
This commit is contained in:
dlprows 2023-08-20 20:52:48 -06:00
parent 1dc8ab8a2d
commit 90c014e932
24 changed files with 760 additions and 786 deletions

View File

@ -1,73 +0,0 @@
using CoreAudio;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FocusVolumeControl
{
public class ActiveAudioSessionWrapper
{
public string DisplayName { get; set; }
public string ExecutablePath { get; set; }
private List<SimpleAudioVolume> Volume { get; } = new List<SimpleAudioVolume>();
public string Icon { get; set; }
public bool Any()
{
return Volume.Any();
}
public int Count => Volume.Count;
public void AddVolume(SimpleAudioVolume volume)
{
Volume.Add(volume);
}
public void ToggleMute()
{
//when all volumes are muted, Volume.All will return true
//so we swap from muted to false (opposite of Volume.All)
//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);
Volume.ForEach(x => x.Mute = !muted);
}
public bool? GetMuted()
{
var muted = Volume.All(x => x.Mute);
var unmuted = Volume.All(x => !x.Mute);
if(muted == !unmuted)
{
return muted;
}
return null;
}
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;
level += (0.01f * step) * ticks;
level = Math.Max(level, 0);
Volume.ForEach(x => x.MasterVolume = level);
}
public int GetVolumeLevel()
{
var level = Volume.FirstOrDefault()?.MasterVolume ?? 0;
return (int)(level * 100);
}
}
}

View File

@ -1,20 +1,18 @@
using CoreAudio; using CoreAudio;
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;
using System.Text;
using System.Threading.Tasks;
namespace FocusVolumeControl namespace FocusVolumeControl;
public class AudioHelper
{ {
public class AudioHelper IAudioSession _current;
{
ActiveAudioSessionWrapper _current;
List<Process> _currentProcesses; List<Process> _currentProcesses;
public ActiveAudioSessionWrapper FindSession(List<Process> processes) public IAudioSession FindSession(List<Process> processes)
{ {
var deviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid()); var deviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid());
@ -34,7 +32,7 @@ namespace FocusVolumeControl
try try
{ {
var displayName = audioProcess.MainModule.FileVersionInfo.FileDescription; var displayName = audioProcess.MainModule.FileVersionInfo.FileDescription;
if(string.IsNullOrEmpty(displayName)) if (string.IsNullOrEmpty(displayName))
{ {
displayName = audioProcess.ProcessName; displayName = audioProcess.ProcessName;
} }
@ -54,7 +52,11 @@ namespace FocusVolumeControl
return matchingSession.Any() ? matchingSession : null; return matchingSession.Any() ? matchingSession : null;
} }
public ActiveAudioSessionWrapper GetActiveSession() static object _lock = new object();
public IAudioSession GetActiveSession(FallbackBehavior fallbackBehavior)
{
lock (_lock)
{ {
var processes = GetPossibleProcesses(); var processes = GetPossibleProcesses();
@ -63,9 +65,22 @@ namespace FocusVolumeControl
_current = FindSession(processes); _current = FindSession(processes);
} }
if(_current == null)
{
if(fallbackBehavior == FallbackBehavior.SystemSounds)
{
_current = GetSystemSounds();
}
else if(fallbackBehavior == FallbackBehavior.SystemVolume)
{
_current = GetSystemVolume();
}
}
_currentProcesses = processes; _currentProcesses = processes;
return _current; return _current;
} }
}
/// <summary> /// <summary>
/// Get the list of processes that might be currently selected /// Get the list of processes that might be currently selected
@ -117,5 +132,49 @@ namespace FocusVolumeControl
} }
public void ResetAll()
{
try
{
var deviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid());
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;
} }
}
catch { }
}
public IAudioSession GetSystemSounds()
{
var deviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid());
using var device = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
using var manager = device.AudioSessionManager2;
var sessions = manager.Sessions;
foreach (var session in sessions)
{
if (session.IsSystemSoundsSession)
{
return new SystemSoundsAudioSession(session.SimpleAudioVolume);
}
}
return null;
}
public IAudioSession GetSystemVolume()
{
var deviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid());
using var device = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
return new SystemVolumeAudioSession(device.AudioEndpointVolume);
}
} }

View File

@ -0,0 +1,82 @@
using CoreAudio;
using System;
using System.Collections.Generic;
using System.Linq;
using BarRaider.SdTools;
using System.Drawing;
namespace FocusVolumeControl.AudioSessions;
public class ActiveAudioSessionWrapper : IAudioSession
{
public string DisplayName { get; set; }
public string ExecutablePath { get; set; }
private List<SimpleAudioVolume> Volume { get; } = new List<SimpleAudioVolume>();
string _icon;
public string GetIcon()
{
if (string.IsNullOrEmpty(_icon))
{
try
{
var tmp = Icon.ExtractAssociatedIcon(ExecutablePath);
_icon = Tools.ImageToBase64(tmp.ToBitmap(), true);
}
catch
{
_icon = "Image/pluginIcon.png";
}
}
return _icon;
}
public bool Any()
{
return Volume.Any();
}
public int Count => Volume.Count;
public void AddVolume(SimpleAudioVolume volume)
{
Volume.Add(volume);
}
public void ToggleMute()
{
//when all volumes are muted, Volume.All will return true
//so we swap from muted to false (opposite of Volume.All)
//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);
Volume.ForEach(x => x.Mute = !muted);
}
public bool IsMuted()
{
return Volume.All(x => x.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;
level += (0.01f * step) * ticks;
level = Math.Max(level, 0);
level = Math.Min(level, 1);
Volume.ForEach(x => x.MasterVolume = level);
}
public int GetVolumeLevel()
{
var level = Volume.FirstOrDefault()?.MasterVolume ?? 0;
return (int)(level * 100);
}
}

View File

@ -0,0 +1,16 @@
namespace FocusVolumeControl.AudioSessions;
public interface IAudioSession
{
public string DisplayName { get; }
public string GetIcon();
public void ToggleMute();
public bool IsMuted();
public void IncrementVolumeLevel(int step, int ticks);
public int GetVolumeLevel();
}

View File

@ -0,0 +1,38 @@
using CoreAudio;
using System;
namespace FocusVolumeControl.AudioSessions;
internal class SystemSoundsAudioSession : IAudioSession
{
public SystemSoundsAudioSession(SimpleAudioVolume volumeControl)
{
_volumeControl = volumeControl;
}
SimpleAudioVolume _volumeControl;
public string DisplayName => "System sounds";
public string GetIcon() => "Images/systemSounds";
public void ToggleMute()
{
_volumeControl.Mute = !_volumeControl.Mute;
}
public bool IsMuted() => _volumeControl.Mute;
public void IncrementVolumeLevel(int step, int ticks)
{
var level = _volumeControl.MasterVolume;
level += (0.01f * step) * ticks;
level = Math.Max(level, 0);
level = Math.Min(level, 1);
_volumeControl.MasterVolume = level;
}
public int GetVolumeLevel() => (int)(_volumeControl.MasterVolume * 100);
}

View File

@ -0,0 +1,38 @@
using CoreAudio;
using System;
namespace FocusVolumeControl.AudioSessions;
internal class SystemVolumeAudioSession : IAudioSession
{
public SystemVolumeAudioSession(AudioEndpointVolume volumeControl)
{
_volumeControl = volumeControl;
}
AudioEndpointVolume _volumeControl;
public string DisplayName => "System Volume";
public string GetIcon() => "Images/actionIcon";
public void ToggleMute()
{
_volumeControl.Mute = !_volumeControl.Mute;
}
public bool IsMuted() => _volumeControl.Mute;
public void IncrementVolumeLevel(int step, int ticks)
{
var level = _volumeControl.MasterVolumeLevelScalar;
level += (0.01f * step) * ticks;
level = Math.Max(level, 0);
level = Math.Min(level, 1);
_volumeControl.MasterVolumeLevelScalar = level;
}
public int GetVolumeLevel() => (int)(_volumeControl.MasterVolumeLevelScalar * 100);
}

View File

@ -1,46 +1,29 @@
using BarRaider.SdTools; using BarRaider.SdTools;
using BarRaider.SdTools.Payloads; using BarRaider.SdTools.Payloads;
using CoreAudio; using FocusVolumeControl.AudioSessions;
using FocusVolumeControl.UI; using FocusVolumeControl.UI;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.ServiceModel.Description;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Threading; using System.Windows.Threading;
namespace FocusVolumeControl namespace FocusVolumeControl;
[PluginActionId("com.dlprows.focusvolumecontrol.dialaction")]
public class DialAction : EncoderBase
{ {
/*
todo:
link both discord processes
steam not detecting
long press reset
option for what to do when on app without sound
*/
[PluginActionId("com.dlprows.focusvolumecontrol.dialaction")]
public class DialAction : EncoderBase
{
private class PluginSettings private class PluginSettings
{ {
[JsonProperty("onVolumeNotFound")]
public FallbackBehavior FallbackBehavior { get; set; }
public static PluginSettings CreateDefaultSettings() public static PluginSettings CreateDefaultSettings()
{ {
PluginSettings instance = new PluginSettings(); PluginSettings instance = new PluginSettings();
instance.FallbackBehavior = FallbackBehavior.PreviousApp;
return instance; return instance;
} }
} }
@ -50,7 +33,7 @@ namespace FocusVolumeControl
IntPtr _foregroundWindowChangedEvent; IntPtr _foregroundWindowChangedEvent;
Native.WinEventDelegate _delegate; Native.WinEventDelegate _delegate;
ActiveAudioSessionWrapper _currentAudioSession; IAudioSession _currentAudioSession;
AudioHelper _audioHelper = new AudioHelper(); AudioHelper _audioHelper = new AudioHelper();
Thread _thread; Thread _thread;
@ -84,6 +67,8 @@ namespace FocusVolumeControl
_thread.SetApartmentState(ApartmentState.STA); _thread.SetApartmentState(ApartmentState.STA);
_thread.Start(); _thread.Start();
_currentAudioSession = settings.FallbackBehavior == FallbackBehavior.SystemSounds ? _audioHelper.GetSystemSounds() : _audioHelper.GetSystemVolume();
} }
public override async void DialDown(DialPayload payload) public override async void DialDown(DialPayload payload)
@ -98,7 +83,7 @@ namespace FocusVolumeControl
Logger.Instance.LogMessage(TracingLevel.INFO, "Touch Press"); Logger.Instance.LogMessage(TracingLevel.INFO, "Touch Press");
if (payload.IsLongPress) if (payload.IsLongPress)
{ {
//todo: iterate through all sessions setting them back to 100 except the master volume _audioHelper.ResetAll();
} }
else else
{ {
@ -108,6 +93,9 @@ namespace FocusVolumeControl
async Task ToggleMuteAsync() async Task ToggleMuteAsync()
{ {
try
{
if (_currentAudioSession != null) if (_currentAudioSession != null)
{ {
_currentAudioSession.ToggleMute(); _currentAudioSession.ToggleMute();
@ -118,11 +106,19 @@ namespace FocusVolumeControl
await Connection.ShowAlert(); await Connection.ShowAlert();
} }
} }
catch (Exception ex)
{
await Connection.ShowAlert();
Logger.Instance.LogMessage(TracingLevel.ERROR, $"Unable to toggle mute: {ex.Message}");
}
}
public override async void DialRotate(DialRotatePayload payload) public override async void DialRotate(DialRotatePayload payload)
{ {
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
try
{
if (_currentAudioSession != null) if (_currentAudioSession != null)
{ {
_currentAudioSession.IncrementVolumeLevel(1, payload.Ticks); _currentAudioSession.IncrementVolumeLevel(1, payload.Ticks);
@ -133,6 +129,12 @@ namespace FocusVolumeControl
await Connection.ShowAlert(); await Connection.ShowAlert();
} }
} }
catch (Exception ex)
{
await Connection.ShowAlert();
Logger.Instance.LogMessage(TracingLevel.ERROR, $"Unable to toggle mute: {ex.Message}");
}
}
public override void DialUp(DialPayload payload) public override void DialUp(DialPayload payload)
{ {
@ -152,14 +154,10 @@ namespace FocusVolumeControl
public override async void OnTick() public override async void OnTick()
{ {
//called once every 1000ms and can be used for updating the title/image fo the key //called once every 1000ms and can be used for updating the title/image of the key
var activeSession = _audioHelper.GetActiveSession(); var activeSession = _audioHelper.GetActiveSession(settings.FallbackBehavior);
if (activeSession == null) if(activeSession != null)
{
//todo: something?
}
else
{ {
_currentAudioSession = activeSession; _currentAudioSession = activeSession;
} }
@ -172,7 +170,7 @@ namespace FocusVolumeControl
if (_currentAudioSession != null) if (_currentAudioSession != null)
{ {
var uiState = UIState.Build(_currentAudioSession); var uiState = new UIState(_currentAudioSession);
if ( _previousState != null && uiState != null && if ( _previousState != null && uiState != null &&
uiState.Title == _previousState.Title && uiState.Title == _previousState.Title &&
@ -216,6 +214,4 @@ namespace FocusVolumeControl
} }
}
} }

View File

@ -0,0 +1,8 @@
namespace FocusVolumeControl;
public enum FallbackBehavior
{
PreviousApp,
SystemSounds,
SystemVolume
}

View File

@ -54,13 +54,16 @@
<Reference Include="WindowsBase" /> <Reference Include="WindowsBase" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="ActiveAudioSessionWrapper.cs" /> <Compile Include="AudioSessions\ActiveAudioSessionWrapper.cs" />
<Compile Include="AudioHelper.cs" /> <Compile Include="AudioHelper.cs" />
<Compile Include="AudioSessions\SystemSoundsAudioSession.cs" />
<Compile Include="AudioSessions\SystemVolumeAudioSession.cs" />
<Compile Include="DialAction.cs" /> <Compile Include="DialAction.cs" />
<Compile Include="ISDConnectionExtensions.cs" /> <Compile Include="AudioSessions\IAudioSession.cs" />
<Compile Include="FallbackBehavior.cs" />
<Compile Include="UI\ISDConnectionExtensions.cs" />
<Compile Include="Native.cs" /> <Compile Include="Native.cs" />
<Compile Include="ParentProcessUtilities.cs" /> <Compile Include="ParentProcessUtilities.cs" />
<Compile Include="PluginAction.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UI\UIState.cs" /> <Compile Include="UI\UIState.cs" />
@ -104,6 +107,12 @@
<Content Include="Images\pluginIcon.png"> <Content Include="Images\pluginIcon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Images\systemSounds%402x.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Images\systemSounds.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="PropertyInspector\PluginActionPI.html"> <Content Include="PropertyInspector\PluginActionPI.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>

View File

@ -1,18 +0,0 @@
using BarRaider.SdTools;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FocusVolumeControl
{
internal static class ISDConnectionExtensions
{
public static async Task SetFeedbackAsync(this ISDConnection _this, object feedbackPayload)
{
await _this.SetFeedbackAsync(JObject.FromObject(feedbackPayload));
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,14 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace FocusVolumeControl namespace FocusVolumeControl;
public class Native
{ {
public class Native
{
public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime); public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
[DllImport("user32.dll")] [DllImport("user32.dll")]
@ -59,6 +56,7 @@ namespace FocusVolumeControl
return ids; return ids;
} }
[DllImport("ntdll.dll")]
public static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ParentProcessUtilities processInformation, int processInformationLength, out int returnLength);
}
} }

View File

@ -1,19 +1,15 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace FocusVolumeControl namespace FocusVolumeControl;
/// <summary>
/// A utility class to determine a process parent.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ParentProcessUtilities
{ {
/// <summary>
/// A utility class to determine a process parent.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ParentProcessUtilities
{
// These members must match PROCESS_BASIC_INFORMATION // These members must match PROCESS_BASIC_INFORMATION
internal IntPtr Reserved1; internal IntPtr Reserved1;
internal IntPtr PebBaseAddress; internal IntPtr PebBaseAddress;
@ -22,8 +18,6 @@ namespace FocusVolumeControl
internal IntPtr UniqueProcessId; internal IntPtr UniqueProcessId;
internal IntPtr InheritedFromUniqueProcessId; internal IntPtr InheritedFromUniqueProcessId;
[DllImport("ntdll.dll")]
private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ParentProcessUtilities processInformation, int processInformationLength, out int returnLength);
/// <summary> /// <summary>
/// Gets the parent process of specified process. /// Gets the parent process of specified process.
@ -44,7 +38,7 @@ namespace FocusVolumeControl
public static Process GetParentProcess(Process process) public static Process GetParentProcess(Process process)
{ {
var data = new ParentProcessUtilities(); var data = new ParentProcessUtilities();
int status = NtQueryInformationProcess(process.Handle, 0, ref data, Marshal.SizeOf(data), out var returnLength); int status = Native.NtQueryInformationProcess(process.Handle, 0, ref data, Marshal.SizeOf(data), out var returnLength);
if (status != 0) if (status != 0)
{ {
return null; return null;
@ -60,6 +54,4 @@ namespace FocusVolumeControl
} }
} }
}
} }

View File

@ -1,83 +0,0 @@
using BarRaider.SdTools;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FocusVolumeControl
{
[PluginActionId("FocusVolumeControl.pluginaction")]
public class PluginAction : KeypadBase
{
private class PluginSettings
{
public static PluginSettings CreateDefaultSettings()
{
PluginSettings instance = new PluginSettings();
instance.OutputFileName = String.Empty;
instance.InputString = String.Empty;
return instance;
}
[FilenameProperty]
[JsonProperty(PropertyName = "outputFileName")]
public string OutputFileName { get; set; }
[JsonProperty(PropertyName = "inputString")]
public string InputString { get; set; }
}
#region Private Members
private PluginSettings settings;
#endregion
public PluginAction(SDConnection connection, InitialPayload payload) : base(connection, payload)
{
if (payload.Settings == null || payload.Settings.Count == 0)
{
this.settings = PluginSettings.CreateDefaultSettings();
SaveSettings();
}
else
{
this.settings = payload.Settings.ToObject<PluginSettings>();
}
}
public override void Dispose()
{
Logger.Instance.LogMessage(TracingLevel.INFO, $"Destructor called");
}
public override void KeyPressed(KeyPayload payload)
{
Logger.Instance.LogMessage(TracingLevel.INFO, "Key Pressed");
}
public override void KeyReleased(KeyPayload payload) { }
public override void OnTick() { }
public override void ReceivedSettings(ReceivedSettingsPayload payload)
{
Tools.AutoPopulateSettings(settings, payload.Settings);
SaveSettings();
}
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload) { }
#region Private Methods
private Task SaveSettings()
{
return Connection.SetSettingsAsync(JObject.FromObject(settings));
}
#endregion
}
}

View File

@ -1,14 +1,9 @@
using BarRaider.SdTools; using BarRaider.SdTools;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FocusVolumeControl namespace FocusVolumeControl;
internal class Program
{ {
internal class Program
{
static void Main(string[] args) static void Main(string[] args)
{ {
// Uncomment this line of code to allow for debugging // Uncomment this line of code to allow for debugging
@ -16,5 +11,4 @@ namespace FocusVolumeControl
SDWrapper.Run(args); SDWrapper.Run(args);
} }
}
} }

View File

@ -12,6 +12,24 @@
</head> </head>
<body> <body>
<div class="sdpi-wrapper"> <div class="sdpi-wrapper">
<div class="sdpi-item">
<div class="sdpi-item-label">Fallback Behavior</div>
<select class="sdpi-item-value sdProperty" id="fallbackBehavior" oninput="setSettings()">
<option value="0">Previous App</option>
<option value="1">System Sounds</option>
<option value="2">Main System Volume</option>
</select>
</div>
<div class="sdpi-item">
<details class="message">
<p>If the focused app does not have a volume control you can pick your desired behavior</p>
<ul>
<li>Previous App - the focused app will be the last app that had a volume control</li>
<li>System Sounds - switch to control system sounds. For example, if you're using an app that makes a windows "error" sound, its volume is controlled by system sounds. So you can adjust the error sound without impacting other apps</li>
<li>Main System Volume - switch to the main volume control for the system. This will change the volume of all apps</li>
</ul>
</details>
</div>
</div> </div>
</body> </body>
</html> </html>

View File

@ -0,0 +1,13 @@
using BarRaider.SdTools;
using Newtonsoft.Json.Linq;
using System.Threading.Tasks;
namespace FocusVolumeControl.UI;
internal static class ISDConnectionExtensions
{
public static async Task SetFeedbackAsync(this ISDConnection _this, object feedbackPayload)
{
await _this.SetFeedbackAsync(JObject.FromObject(feedbackPayload));
}
}

View File

@ -8,11 +8,12 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using BarRaider.SdTools; using BarRaider.SdTools;
using Newtonsoft.Json; using Newtonsoft.Json;
using FocusVolumeControl.AudioSessions;
namespace FocusVolumeControl.UI namespace FocusVolumeControl.UI;
internal class UIState
{ {
internal class UIState
{
[JsonProperty("title")] [JsonProperty("title")]
public string Title { get; private init; } public string Title { get; private init; }
@ -25,41 +26,16 @@ namespace FocusVolumeControl.UI
[JsonProperty("icon")] [JsonProperty("icon")]
public ValueWithOpacity<string> icon { get; private init; } public ValueWithOpacity<string> icon { get; private init; }
public static UIState Build(ActiveAudioSessionWrapper session) public UIState(IAudioSession session)
{ {
var volume = session.GetVolumeLevel(); var volume = session.GetVolumeLevel();
var opacity = session.IsMuted() ? 0.5f : 1;
var iconData = session.GetIcon();
var opacity = session.GetMuted() != true ? 1 : 0.5f; Title = session.DisplayName;
Value = new() { Value = $"{volume}%", Opacity = opacity };
var iconData = ""; Indicator = new() { Value = volume, Opacity = opacity };
icon = new() { Value = iconData, Opacity = opacity };
if (session.Icon != null)
{
iconData = session.Icon;
}
else
{
try
{
var icon = Icon.ExtractAssociatedIcon(session.ExecutablePath);
iconData = Tools.ImageToBase64(icon.ToBitmap(), true);
}
catch
{
iconData = "Image/pluginIcon.png";
}
session.Icon = iconData;
} }
return new UIState()
{
Title = session.DisplayName,
Value = new() { Value = $"{volume}%", Opacity = opacity },
Indicator = new() { Value = volume, Opacity = opacity },
icon = new() { Value = iconData, Opacity = opacity },
};
}
}
} }

View File

@ -1,20 +1,13 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FocusVolumeControl.UI namespace FocusVolumeControl.UI;
internal class ValueWithOpacity<T>
{ {
internal class ValueWithOpacity<T>
{
[JsonProperty("value")] [JsonProperty("value")]
public required T Value { get; init; } public required T Value { get; init; }
[JsonProperty("opacity")] [JsonProperty("opacity")]
public required float Opacity { get; init; } public required float Opacity { get; init; }
}
} }

View File

@ -1,31 +1,17 @@
using CoreAudio; using CoreAudio;
using FocusVolumeControl; using FocusVolumeControl;
using System; using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace SoundBrowser namespace SoundBrowser;
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{ {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
AudioHelper _audioHelper; AudioHelper _audioHelper;
Native.WinEventDelegate _delegate; Native.WinEventDelegate _delegate;
@ -91,7 +77,6 @@ namespace SoundBrowser
sb.AppendLine("picked the following best match"); sb.AppendLine("picked the following best match");
sb.AppendLine($"\tsession: {session.DisplayName}"); sb.AppendLine($"\tsession: {session.DisplayName}");
sb.AppendLine($"\tvolume: {session.GetVolumeLevel()}"); sb.AppendLine($"\tvolume: {session.GetVolumeLevel()}");
sb.AppendLine($"\tcount: {session.Count}");
} }
else else
{ {
@ -130,6 +115,4 @@ namespace SoundBrowser
} }
}
} }

View File

@ -1,65 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace SoundBrowser
{
/// <summary>
/// A utility class to determine a process parent.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct ParentProcessUtilities
{
// These members must match PROCESS_BASIC_INFORMATION
internal IntPtr Reserved1;
internal IntPtr PebBaseAddress;
internal IntPtr Reserved2_0;
internal IntPtr Reserved2_1;
internal IntPtr UniqueProcessId;
internal IntPtr InheritedFromUniqueProcessId;
[DllImport("ntdll.dll")]
private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ParentProcessUtilities processInformation, int processInformationLength, out int returnLength);
/// <summary>
/// Gets the parent process of specified process.
/// </summary>
/// <param name="id">The process id.</param>
/// <returns>An instance of the Process class.</returns>
public static Process? GetParentProcess(int id)
{
Process process = Process.GetProcessById(id);
return GetParentProcess(process.Handle);
}
/// <summary>
/// Gets the parent process of a specified process.
/// </summary>
/// <param name="handle">The process handle.</param>
/// <returns>An instance of the Process class.</returns>
public static Process? GetParentProcess(IntPtr handle)
{
ParentProcessUtilities pbi = new ParentProcessUtilities();
int returnLength;
int status = NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf(pbi), out returnLength);
if (status != 0)
throw new Win32Exception(status);
try
{
return Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32());
}
catch (ArgumentException)
{
// not found
return null;
}
}
}
}