Compare commits

..

2 Commits

Author SHA1 Message Date
ab769bf7d2 Switching from my base64 code to the code supplied by sdtools 2023-08-06 13:54:00 -06:00
65f0c9faf6 Inital commit 2023-08-06 13:53:28 -06:00
34 changed files with 1272 additions and 0 deletions

263
.gitignore vendored Normal file
View File

@ -0,0 +1,263 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
###################
# compiled source #
###################
*.com
*.class
*.dll
*.exe
*.pdb
*.dll.config
*.cache
*.suo
# Include dlls if theyre in the NuGet packages directory
!/packages/*/lib/*.dll
# Include dlls if they're in the CommonReferences directory
!*CommonReferences/*.dll
####################
# VS Upgrade stuff #
####################
_UpgradeReport_Files/
###############
# Directories #
###############
bin/
obj/
TestResults/
###################
# Web publish log #
###################
*.Publish.xml
#############
# Resharper #
#############
/_ReSharper.*
*.ReSharper.*
############
# Packages #
############
# its better to unpack these files and commit the raw source
# git has its own built in compression methods
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
######################
# Logs and databases #
######################
*.log
*.sqlite
# OS generated files #
######################
.DS_Store?
ehthumbs.db
Icon?
Thumbs.db
# User-specific files
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
# Visual Studo 2015 cache/options directory
.vs/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
bower_components/
orleans.codegen.cs
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt

View File

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.6.33829.357
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FocusVolumeControl", "FocusVolumeControl\FocusVolumeControl.csproj", "{4635D874-69C0-4010-BE46-77EF92EB1553}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SoundBrowser", "SoundBrowser\SoundBrowser.csproj", "{0E8AB334-82F1-4DBC-9BDA-B6F9714A1847}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4635D874-69C0-4010-BE46-77EF92EB1553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4635D874-69C0-4010-BE46-77EF92EB1553}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4635D874-69C0-4010-BE46-77EF92EB1553}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4635D874-69C0-4010-BE46-77EF92EB1553}.Release|Any CPU.Build.0 = Release|Any CPU
{0E8AB334-82F1-4DBC-9BDA-B6F9714A1847}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E8AB334-82F1-4DBC-9BDA-B6F9714A1847}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E8AB334-82F1-4DBC-9BDA-B6F9714A1847}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E8AB334-82F1-4DBC-9BDA-B6F9714A1847}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {22FD348F-67D1-423B-B3E3-C8C0022DCD96}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,17 @@
using CoreAudio;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FocusVolumeControl
{
internal class ActiveAudioSessionWrapper
{
public string DisplayName { get; set; }
public string ExecutablePath { get; set; }
public SimpleAudioVolume Volume { get; set; }
}
}

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="CommandLine" publicKeyToken="5a870481e358d379" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-2.9.1.0" newVersion="2.9.1.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Drawing.Common" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0"/>
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="NLog" publicKeyToken="5120e14c03d0593c" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -0,0 +1,63 @@
using CoreAudio;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace FocusVolumeControl
{
internal class AudioHelper
{
ActiveAudioSessionWrapper GetSessionForProcess(Process process)
{
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)
{
var audioProcess = Process.GetProcessById((int)session.ProcessID);
if (session.ProcessID == process.Id || audioProcess?.ProcessName == process.ProcessName)
{
var displayName = audioProcess.MainModule.FileVersionInfo.FileDescription;
var path = audioProcess.MainModule.FileName;
return new ActiveAudioSessionWrapper()
{
DisplayName = displayName,
ExecutablePath = path,
Volume = session.SimpleAudioVolume
};
}
}
return null;
}
internal ActiveAudioSessionWrapper GetActiveSession()
{
const int nChars = 256;
IntPtr handle = IntPtr.Zero;
StringBuilder Buff = new StringBuilder(nChars);
handle = Native.GetForegroundWindow();
if (handle == IntPtr.Zero)
{
//todo: return system or something like that?
return null;
}
var tid = Native.GetWindowThreadProcessId(handle, out var pid);
var process = Process.GetProcessById(pid);
return GetSessionForProcess(process);
}
}
}

View File

@ -0,0 +1,203 @@
using BarRaider.SdTools;
using BarRaider.SdTools.Payloads;
using CoreAudio;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
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.Text;
using System.Threading;
using System.Threading.Tasks;
namespace FocusVolumeControl
{
/*
todo:
link both discord processes
steam not detecting
long press reset
option for what to do when on app without sound
gitea
*/
[PluginActionId("com.dlprows.focusvolumecontrol.dialaction")]
public class DialAction : EncoderBase
{
private class PluginSettings
{
public static PluginSettings CreateDefaultSettings()
{
PluginSettings instance = new PluginSettings();
return instance;
}
}
private PluginSettings settings;
//IntPtr _foregroundWindowChangedEvent;
//WinEventDelegate _delegate;
ActiveAudioSessionWrapper _currentAudioSession;
AudioHelper _audioHelper = new AudioHelper();
public DialAction(ISDConnection connection, InitialPayload payload) : base(connection, payload)
{
if (payload.Settings == null || payload.Settings.Count == 0)
{
settings = PluginSettings.CreateDefaultSettings();
SaveSettings();
}
else
{
settings = payload.Settings.ToObject<PluginSettings>();
}
//_delegate = new WinEventDelegate(WinEventProc);
//_foregroundWindowChangedEvent = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, _delegate, 0, 0, WINEVENT_OUTOFCONTEXT);
}
public override async void DialDown(DialPayload payload)
{
//dial pressed down
Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Down");
if(_currentAudioSession != null)
{
_currentAudioSession.Volume.Mute = !_currentAudioSession.Volume.Mute;
var uiState = UIState.Build(_currentAudioSession);
await Connection.SetFeedbackAsync(uiState);
}
else
{
await Connection.ShowAlert();
}
}
public override async void TouchPress(TouchpadPressPayload payload)
{
Logger.Instance.LogMessage(TracingLevel.INFO, "Touch Press");
if (payload.IsLongPress)
{
//todo: iterate through all sessions setting them back to 100 except the master volume
}
else
{
if (_currentAudioSession != null)
{
_currentAudioSession.Volume.Mute = !_currentAudioSession.Volume.Mute;
var uiState = UIState.Build(_currentAudioSession);
await Connection.SetFeedbackAsync(uiState);
}
else
{
await Connection.ShowAlert();
}
}
}
public override async void DialRotate(DialRotatePayload payload)
{
Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Rotate");
//dial rotated. ticks positive for right, negative for left
if(_currentAudioSession != null)
{
_currentAudioSession.Volume.MasterVolume += (0.01f) * payload.Ticks;
var uiState = UIState.Build(_currentAudioSession);
await Connection.SetFeedbackAsync(uiState);
}
else
{
await Connection.ShowAlert();
}
}
public override void DialUp(DialPayload payload)
{
//dial unpressed
Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Up");
}
public override void Dispose()
{
/*
if(_foregroundWindowChangedEvent != IntPtr.Zero)
{
Native.UnhookWinEvent(_foregroundWindowChangedEvent);
}
*/
}
public override async void OnTick()
{
//called once every 1000ms and can be used for updating the title/image fo the key
var activeSession = _audioHelper.GetActiveSession();
if (activeSession == null)
{
//todo: something?
}
else
{
_currentAudioSession = activeSession;
}
if(_currentAudioSession != null)
{
var uiState = UIState.BuildWithImage(_currentAudioSession);
await Connection.SetFeedbackAsync(uiState);
}
}
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload)
{
}
public override void ReceivedSettings(ReceivedSettingsPayload payload)
{
Tools.AutoPopulateSettings(settings, payload.Settings);
SaveSettings();
}
private Task SaveSettings()
{
return Connection.SetSettingsAsync(JObject.FromObject(settings));
}
/*
public async void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
var activeSession = _audioHelper.GetActiveSession();
if(activeSession == null)
{
//todo: something?
}
else
{
_currentAudioSession = activeSession;
//populate the UI
await Connection.SetTitleAsync(_currentAudioSession.DisplayName);
}
}
*/
}
}

View File

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{4635D874-69C0-4010-BE46-77EF92EB1553}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>FocusVolumeControl</RootNamespace>
<AssemblyName>FocusVolumeControl</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<LangVersion>latest</LangVersion>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\com.dlprows.focusvolumecontrol.sdPlugin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\com.dlprows.focusvolumecontrol.sdPlugin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ActiveAudioSessionWrapper.cs" />
<Compile Include="AudioHelper.cs" />
<Compile Include="DialAction.cs" />
<Compile Include="ISDConnectionExtensions.cs" />
<Compile Include="Native.cs" />
<Compile Include="PluginAction.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UIState.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="install.bat" />
<None Include="manifest.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Content Include="Images\categoryIcon%402x.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Images\categoryIcon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Images\actionIcon%402x.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Images\actionIcon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Images\icon%402x.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Images\icon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Images\pluginAction%402x.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Images\pluginAction.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Images\pluginIcon%402x.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Images\pluginIcon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="PropertyInspector\PluginActionPI.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="PropertyInspector\PluginActionPI.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="CoreAudio">
<Version>1.27.0</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>13.0.3</Version>
</PackageReference>
<PackageReference Include="NLog">
<Version>5.2.3</Version>
</PackageReference>
<PackageReference Include="streamdeck-client-csharp">
<Version>4.3.0</Version>
</PackageReference>
<PackageReference Include="StreamDeck-Tools">
<Version>6.1.1</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,18 @@
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.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace FocusVolumeControl
{
internal class Native
{
internal delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
[DllImport("user32.dll")]
static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
[DllImport("user32.dll")]
internal static extern bool UnhookWinEvent(IntPtr hWinEventHook);
private const uint WINEVENT_OUTOFCONTEXT = 0;
private const uint EVENT_SYSTEM_FOREGROUND = 3;
internal static IntPtr RegisterForForegroundWindowChangedEvent(WinEventDelegate dele) => SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, dele, 0, 0, WINEVENT_OUTOFCONTEXT);
[DllImport("user32.dll")]
internal static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int processId);
}
}

View File

@ -0,0 +1,83 @@
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

@ -0,0 +1,20 @@
using BarRaider.SdTools;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FocusVolumeControl
{
internal class Program
{
static void Main(string[] args)
{
// Uncomment this line of code to allow for debugging
//while (!System.Diagnostics.Debugger.IsAttached) { System.Threading.Thread.Sleep(100); }
SDWrapper.Run(args);
}
}
}

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("FocusVolumeControl")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("FocusVolumeControl")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("1478b282-5356-4535-b478-b68cfa9cca59")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,minimal-ui,viewport-fit=cover">
<meta name=apple-mobile-web-app-capable content=yes>
<meta name=apple-mobile-web-app-status-bar-style content=black>
<title>FocusVolumeControl Settings</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/barraider/streamdeck-easypi@latest/src/sdpi.css">
<script src="https://cdn.jsdelivr.net/gh/barraider/streamdeck-easypi@latest/src/sdtools.common.js"></script>
<script src="PluginActionPI.js"></script>
</head>
<body>
<div class="sdpi-wrapper">
</div>
</body>
</html>

View File

@ -0,0 +1 @@


View File

@ -0,0 +1,68 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BarRaider.SdTools;
namespace FocusVolumeControl
{
internal class UIState
{
public static Dictionary<string, object> Build(ActiveAudioSessionWrapper session)
{
var volume = (int)(session.Volume.MasterVolume * 100);
var valueThing = new Dictionary<string, string>()
{
{ "value", $"{volume}%" },
{ "opacity", "0.5" }
};
var opacity = session.Volume.Mute ? 0.5f : 1;
var payload = new Dictionary<string, object>()
{
{ "indicator", ValueWithOpacity(volume, opacity) },
{ "value", ValueWithOpacity($"{volume}%", opacity ) },
{ "title", session.DisplayName },
};
return payload;
}
public static Dictionary<string, object> ValueWithOpacity(object value, float opacity)
{
return new Dictionary<string, object>()
{
{ "value", value },
{ "opacity", opacity }
};
}
public static Dictionary<string, object> BuildWithImage(ActiveAudioSessionWrapper session)
{
var payload = Build(session);
var opacity = session.Volume.Mute ? 0.5f : 1;
var iconData = "";
try
{
var icon = Icon.ExtractAssociatedIcon(session.ExecutablePath);
iconData = Tools.ImageToBase64(icon.ToBitmap(), true);
}
catch
{
iconData = "Image/pluginIcon.png";
}
payload["icon"] = ValueWithOpacity(iconData, opacity);
return payload;
}
}
}

View File

@ -0,0 +1,27 @@
@echo off
REM USAGE: Install.bat <DEBUG/RELEASE>
setlocal
REM cd to directory of install.bat
cd /d %~dp0
REM cd to bin/<Debug|Release>
cd bin/%1
SET DISTRIBUTION_TOOL="%~dp0%DistributionTool.exe"
SET STREAM_DECK_FILE="c:\Program Files\Elgato\StreamDeck\StreamDeck.exe"
SET STREAM_DECK_LOAD_TIMEOUT=7
REM close processes
taskkill /f /im streamdeck.exe
taskkill /f /im FocusVolumeControl.exe
timeout /t 2
del com.dlprows.focusvolumecontrol.streamDeckPlugin
%DISTRIBUTION_TOOL% -b -i com.dlprows.focusvolumecontrol.sdPlugin -o ./
rmdir %APPDATA%\Elgato\StreamDeck\Plugins\com.dlprows.focusvolumecontrol.sdPlugin /s /q
START "" %STREAM_DECK_FILE%
timeout /t %STREAM_DECK_LOAD_TIMEOUT%
com.dlprows.focusvolumecontrol.streamDeckPlugin

View File

@ -0,0 +1,51 @@
{
"Actions": [
{
"Name": "Focused App Volume",
"Icon": "Images/icon",
"States": [
{
"Image": "Images/pluginAction",
"TitleAlignment": "middle",
"FontSize": "12"
}
],
"Controllers": [ "Encoder" ],
"Encoder": {
"background": "backgroundImage",
"Icon": "Images/actionIcon",
"layout": "$B1",
"StackColor": "#AABBCC",
"TriggerDescription": {
"Rotate": "Change the volume",
"Push": "Mute/UnMute",
"Touch": "Mute/UnMute",
"LongTouch": "Reset all apps"
}
},
"SupportedInMultiActions": false,
"Tooltip": "Control the volume of the focused application",
"UUID": "com.dlprows.focusvolumecontrol.dialaction",
"PropertyInspectorPath": "PropertyInspector/PluginActionPI.html"
}
],
"Author": "Daniel Prows",
"Name": "FocusVolumeControl",
"Description": "Control the volume of the focused application",
"URL": "https://encyclopediaofdaniel.com",
"Version": "1.0",
"CodePath": "FocusVolumeControl",
"Category": "Volume Control [dlprows]",
"Icon": "Images/pluginIcon",
"CategoryIcon": "Images/categoryIcon",
"OS": [
{
"Platform": "windows",
"MinimumVersion": "10"
}
],
"SDKVersion": 2,
"Software": {
"MinimumVersion": "6.0"
}
}

View File

@ -0,0 +1,9 @@
<Application x:Class="SoundBrowser.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SoundBrowser"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace SoundBrowser
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}

View File

@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

View File

@ -0,0 +1,15 @@
<Window x:Class="SoundBrowser.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SoundBrowser"
mc:Ignorable="d"
Title="MainWindow" Height="150" Width="800">
<Grid>
<StackPanel>
<TextBlock x:Name="_tf">blah</TextBlock>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,116 @@
using CoreAudio;
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;
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
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
dele = new WinEventDelegate(WinEventProc);
IntPtr m_hhook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, dele, 0, 0, WINEVENT_OUTOFCONTEXT);
}
WinEventDelegate dele = null;
delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
[DllImport("user32.dll")]
static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
private const uint WINEVENT_OUTOFCONTEXT = 0;
private const uint EVENT_SYSTEM_FOREGROUND = 3;
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int processId);
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
private static SimpleAudioVolume GetVolumeObject(Process process)
{
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.ProcessID == process.Id)
{
return session.SimpleAudioVolume;
}
var audioProcess = Process.GetProcessById((int)session.ProcessID);
if(audioProcess?.ProcessName == process.ProcessName)
{
Console.WriteLine(process.MainModule.FileVersionInfo.FileDescription);
return session.SimpleAudioVolume;
}
}
return null;
}
SimpleAudioVolume _current;
private string GetActiveWindowTitle()
{
const int nChars = 256;
IntPtr handle = IntPtr.Zero;
StringBuilder Buff = new StringBuilder(nChars);
handle = GetForegroundWindow();
if (handle <= 0)
{
return "";
}
var tid = GetWindowThreadProcessId(handle, out var pid);
var process = Process.GetProcessById(pid);
var vol = GetVolumeObject(process);
_current = vol;
if(vol != null)
{
vol.Mute = true;
}
return $"{pid} vol:{vol?.MasterVolume ?? -1} - {process.ProcessName}";
}
public void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
_tf.Text = GetActiveWindowTitle();
}
}
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CoreAudio" Version="1.27.0" />
</ItemGroup>
</Project>