Add in the ability to make custom overrides.

Fixed issues with the CSS in the property inspector
Make it so i can actually write unit tests if i want to
This commit is contained in:
dlprows 2024-04-20 21:19:44 -06:00
parent 7abbc92080
commit fdfa32909f
29 changed files with 810 additions and 216 deletions

View File

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.2.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FocusVolumeControl\FocusVolumeControl.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,168 @@
using FocusVolumeControl.Overrides;
namespace FocusVolumeControl.UnitTests
{
public class OverrideParserTests
{
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
[InlineData("\n")]
public void BlankReturnsEmpty(string str)
{
//arrange
//act
var overrides = OverrideParser.Parse(str);
//assert
Assert.Empty(overrides);
}
[Fact]
public void HelldiversParses()
{
//arrange
var str =
"""
eq: HELLDIVERS 2
helldivers2
""";
//act
var overrides = OverrideParser.Parse(str);
//assert
Assert.Single(overrides);
Assert.Equal(MatchType.Equal, overrides[0].MatchType);
Assert.Equal("HELLDIVERS™ 2", overrides[0].WindowQuery);
Assert.Equal("helldivers2", overrides[0].AudioProcessName);
}
[Fact]
public void MultipleOverridesParse()
{
//arrange
var str =
"""
eq: HELLDIVERS 2
helldivers2
start: Task
Steam
""";
//act
var overrides = OverrideParser.Parse(str);
//assert
Assert.Equal(MatchType.Equal, overrides[0].MatchType);
Assert.Equal("HELLDIVERS™ 2", overrides[0].WindowQuery);
Assert.Equal("helldivers2", overrides[0].AudioProcessName);
Assert.Equal(MatchType.StartsWith, overrides[1].MatchType);
Assert.Equal("Task", overrides[1].WindowQuery);
Assert.Equal("Steam", overrides[1].AudioProcessName);
}
[Fact]
public void IncompleteMatchesAreSkipped()
{
//arrange
var str =
"""
eq: HELLDIVERS 2
start: Task
Steam
""";
//act
var overrides = OverrideParser.Parse(str);
//assert
Assert.Single(overrides);
Assert.Equal(MatchType.StartsWith, overrides[0].MatchType);
Assert.Equal("Task", overrides[0].WindowQuery);
Assert.Equal("Steam", overrides[0].AudioProcessName);
}
[Fact]
public void InvalidMatchesAreSkipped()
{
//arrange
var str =
"""
equal: Chrome
chrome
end: Task
Steam
""";
//act
var overrides = OverrideParser.Parse(str);
//assert
Assert.Single(overrides);
Assert.Equal(MatchType.EndsWith, overrides[0].MatchType);
Assert.Equal("Task", overrides[0].WindowQuery);
Assert.Equal("Steam", overrides[0].AudioProcessName);
}
[Fact]
public void MatchesAreCaseInsensitive()
{
//arrange
var str =
"""
Eq: 0
0
eNd: 1
1
StArT: 2
2
Regex: 3
3
""";
//act
var overrides = OverrideParser.Parse(str);
//assert
Assert.Equal(MatchType.Equal, overrides[0].MatchType);
Assert.Equal(MatchType.EndsWith, overrides[1].MatchType);
Assert.Equal(MatchType.StartsWith, overrides[2].MatchType);
Assert.Equal(MatchType.Regex, overrides[3].MatchType);
}
[Fact]
public void CommentsAreSkipped()
{
//arrange
var str =
"""
//Eq: 0
//0
end: 1
1
""";
//act
var overrides = OverrideParser.Parse(str);
//assert
Assert.Single(overrides);
Assert.Equal(MatchType.EndsWith, overrides[0].MatchType);
Assert.Equal("1", overrides[0].WindowQuery);
Assert.Equal("1", overrides[0].AudioProcessName);
}
}
}

View File

@ -0,0 +1 @@
global using Xunit;

View File

@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FocusVolumeControl", "Focus
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SoundBrowser", "SoundBrowser\SoundBrowser.csproj", "{0E8AB334-82F1-4DBC-9BDA-B6F9714A1847}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FocusVolumeControl.UnitTests", "FocusVolumeControl.UnitTests\FocusVolumeControl.UnitTests.csproj", "{322E16C9-C96E-45DF-912F-DB6366170645}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -21,6 +23,10 @@ Global
{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
{322E16C9-C96E-45DF-912F-DB6366170645}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{322E16C9-C96E-45DF-912F-DB6366170645}.Debug|Any CPU.Build.0 = Debug|Any CPU
{322E16C9-C96E-45DF-912F-DB6366170645}.Release|Any CPU.ActiveCfg = Release|Any CPU
{322E16C9-C96E-45DF-912F-DB6366170645}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -1,8 +1,10 @@
using FocusVolumeControl.AudioSessions;
using FocusVolumeControl.Overrides;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
namespace FocusVolumeControl.AudioHelpers;
@ -14,6 +16,8 @@ public class AudioHelper
int[] _currentProcesses;
int _retryFallbackCount = 0;
public List<Override> Overrides { get; set; }
public IAudioSession Current { get; private set; }
public void ResetCache()
@ -113,7 +117,12 @@ public class AudioHelper
{
lock (_lock)
{
var processes = GetPossibleProcesses();
var processes = TryGetProcessFromOverrides();
if(processes == null)
{
processes = GetPossibleProcesses();
}
var processIds = processes?.Select(x => x.Id).ToArray();
//_currentProcesses null - first time getting sessions
@ -175,27 +184,18 @@ public class AudioHelper
Native.GetWindowThreadProcessId(handle, out var pid);
if(ids.Count == 0 && pid == 0)
{
foreach(var p in Process.GetProcesses())
{
if(p.MainWindowHandle == handle)
{
if(p.MainWindowTitle == "HELLDIVERS™ 2")
{
ids = FindAudioSessionByProcessName("helldivers2");
break;
}
}
}
}
else
if(pid != 0)
{
ids.Insert(0, pid);
}
if(ids.Count == 0)
{
return new List<Process>();
}
var processes = ids.Distinct()
.Select(x => Process.GetProcessById(x))
.Select(Process.GetProcessById)
.ToList();
if(processes.FirstOrDefault()?.ProcessName == "explorer")
@ -328,6 +328,47 @@ public class AudioHelper
return null;
}
private List<Process> TryGetProcessFromOverrides(IntPtr? handleOverride = null)
{
var handle = handleOverride ?? Native.GetForegroundWindow();
if (Overrides?.Any() == true)
{
Process tmp = null;
foreach (var p in Process.GetProcesses())
{
if (p.MainWindowHandle == handle)
{
tmp = p;
break;
}
}
if (tmp != null)
{
foreach (var o in Overrides)
{
if (
(o.MatchType == MatchType.Equal && tmp.MainWindowTitle.Equals(o.WindowQuery, StringComparison.InvariantCultureIgnoreCase))
|| (o.MatchType == MatchType.StartsWith && tmp.MainWindowTitle.StartsWith(o.WindowQuery, StringComparison.OrdinalIgnoreCase))
|| (o.MatchType == MatchType.EndsWith && tmp.MainWindowTitle.EndsWith(o.WindowQuery, StringComparison.OrdinalIgnoreCase))
|| (o.MatchType == MatchType.Regex && Regex.IsMatch(tmp.MainWindowTitle, o.WindowQuery))
)
{
var ids = FindAudioSessionByProcessName(o.AudioProcessName);
if (ids?.Count > 0)
{
return ids.Distinct()
.Select(Process.GetProcessById)
.ToList();
}
}
}
}
}
return null;
}
private List<int> FindAudioSessionByProcessName(string processName)
{
@ -338,7 +379,6 @@ public class AudioHelper
}
var results = new List<int>();
Process bestProcessMatch = null;
var deviceEnumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
@ -356,8 +396,6 @@ public class AudioHelper
manager.GetSessionEnumerator(out var sessionEnumerator);
var currentIndex = int.MaxValue;
sessionEnumerator.GetCount(out var count);
for (int i = 0; i < count; i++)
{
@ -367,7 +405,13 @@ public class AudioHelper
var audioProcess = GetProcessById(sessionProcessId);
if(audioProcess != null && audioProcess.ProcessName == processName)
if(audioProcess == null)
{
continue;
}
var audioProcessName = _nameAndIconHelper.TryGetProcessNameWithoutIcon(audioProcess);
if(audioProcess.ProcessName == processName || displayName == processName || processName == audioProcessName)
{
results.Add(sessionProcessId);
}

View File

@ -83,6 +83,41 @@ public class NameAndIconHelper
}
}
public string TryGetProcessNameWithoutIcon(Process process)
{
try
{
//appx packages are installed from the windows store. eg, itunes
var appx = AppxPackage.FromProcess(process);
if (appx == null)
{
//if the display name is already set, then it came from the display name of the audio session
if(!string.IsNullOrEmpty(process.MainWindowTitle))
{
return process.MainWindowTitle;
}
//using process.MainModule.FileVersionInfo sometimes throws permission exceptions
//we get the file version info with a limited query flag to avoid that
var fileVersionInfo = GetFileVersionInfo(process);
if(!string.IsNullOrEmpty(fileVersionInfo?.FileDescription))
{
return fileVersionInfo.FileDescription;
}
return process.ProcessName;
}
else
{
return appx.DisplayName ?? process.ProcessName;
}
}
catch { }
return process.ProcessName;
}
FileVersionInfo GetFileVersionInfo(Process process)
{
var path = GetExecutablePathWithPInvoke(process);

View File

@ -10,9 +10,11 @@ using FocusVolumeControl.AudioSession;
namespace FocusVolumeControl.AudioSessions;
#nullable enable
public sealed class ActiveAudioSessionWrapper : IAudioSession
{
public string DisplayName { get; set; }
public string DisplayName { get; set; } = null!;
private List<IAudioSessionControl2> Sessions { get; } = new List<IAudioSessionControl2>();
private IEnumerable<ISimpleAudioVolume> Volume => Sessions.Cast<ISimpleAudioVolume>();
@ -102,3 +104,4 @@ public sealed class ActiveAudioSessionWrapper : IAudioSession
return VolumeHelpers.GetVolumePercentage(level);
}
}
#nullable restore

View File

@ -4,6 +4,8 @@ using FocusVolumeControl.UI;
using System;
using System.Drawing;
#nullable enable
namespace FocusVolumeControl.AudioSession
{
public abstract class IconWrapper
@ -101,3 +103,4 @@ namespace FocusVolumeControl.AudioSession
}
}
#nullable restore

View File

@ -2,6 +2,7 @@
using BarRaider.SdTools.Payloads;
using FocusVolumeControl.AudioHelpers;
using FocusVolumeControl.AudioSessions;
using FocusVolumeControl.Overrides;
using FocusVolumeControl.UI;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@ -15,6 +16,12 @@ namespace FocusVolumeControl;
[PluginActionId("com.dlprows.focusvolumecontrol.dialaction")]
public class DialAction : EncoderBase
{
private const string DefaultOverrides =
"""
//eq: HELLDIVERS™ 2
//helldivers2
""";
private class PluginSettings
{
[JsonProperty("fallbackBehavior")]
@ -23,11 +30,15 @@ public class DialAction : EncoderBase
[JsonProperty("stepSize")]
public int StepSize { get; set; }
[JsonProperty("overrides")]
public string Overrides { get; set; }
public static PluginSettings CreateDefaultSettings()
{
PluginSettings instance = new PluginSettings();
instance.FallbackBehavior = FallbackBehavior.SystemSounds;
instance.StepSize = 1;
instance.Overrides = DefaultOverrides;
return instance;
}
}
@ -46,12 +57,18 @@ public class DialAction : EncoderBase
else
{
settings = payload.Settings.ToObject<PluginSettings>();
if(string.IsNullOrEmpty(settings.Overrides))
{
settings.Overrides = DefaultOverrides;
_ = SaveSettings();
}
}
WindowChangedEventLoop.Instance.WindowChanged += WindowChanged;
try
{
_audioHelper.Overrides = OverrideParser.Parse(settings.Overrides);
//just in case we fail to get the active session, don't prevent the plugin from launching
var session = _audioHelper.GetActiveSession(settings.FallbackBehavior);
_ = UpdateStateIfNeeded(session);
@ -220,7 +237,8 @@ public class DialAction : EncoderBase
try
{
Tools.AutoPopulateSettings(settings, payload.Settings);
_ = SaveSettings();
_audioHelper.Overrides = OverrideParser.Parse(settings.Overrides);
//_ = SaveSettings();
}
catch (Exception ex)
{

View File

@ -66,6 +66,10 @@
<Compile Include="AudioSessions\IAudioSession.cs" />
<Compile Include="FallbackBehavior.cs" />
<Compile Include="AudioHelpers\NameAndIconHelper.cs" />
<Compile Include="InternalsVisibleTo.cs" />
<Compile Include="Overrides\Override.cs" />
<Compile Include="Overrides\MatchType.cs" />
<Compile Include="Overrides\OverrideParser.cs" />
<Compile Include="UI\IconExtraction.cs" />
<Compile Include="UI\ISDConnectionExtensions.cs" />
<Compile Include="Native.cs" />
@ -91,7 +95,7 @@
<Content Include="Images\**\*.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="PropertyInspector\**\*.js;PropertyInspector\**\*.css">
<Content Include="PropertyInspector\**\*.js;PropertyInspector\**\*.css;PropertyInspector\assets\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="PropertyInspector\PluginActionPI.html">

View File

@ -0,0 +1,4 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("FocusVolumeControl.UnitTests")]
[assembly: InternalsVisibleTo("SoundBrowser")]

View File

@ -0,0 +1,10 @@
namespace FocusVolumeControl.Overrides
{
public enum MatchType
{
Equal,
StartsWith,
EndsWith,
Regex,
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FocusVolumeControl.Overrides
{
public class Override
{
public MatchType MatchType { get; set; }
public string WindowQuery { get; set; }
public string AudioProcessName { get; set; }
}
}

View File

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FocusVolumeControl.Overrides
{
internal class OverrideParser
{
public static List<Override> Parse(string raw)
{
var overrides = new List<Override>();
if (raw == null)
{
return overrides;
}
var lines = raw.Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries);
Override tmp = null;
foreach (var line in lines)
{
if (string.IsNullOrEmpty(line) || line.StartsWith("//"))
{
continue;
}
var split = line.Split(':');
if (split.Length > 1)
{
if (!string.IsNullOrEmpty(tmp?.WindowQuery) && !string.IsNullOrEmpty(tmp?.AudioProcessName))
{
overrides.Add(tmp);
}
tmp = new Override();
if (string.Equals(split[0], "eq", StringComparison.OrdinalIgnoreCase))
{
tmp.MatchType = MatchType.Equal;
}
else if (string.Equals(split[0], "start", StringComparison.OrdinalIgnoreCase))
{
tmp.MatchType = MatchType.StartsWith;
}
else if (string.Equals(split[0], "end", StringComparison.OrdinalIgnoreCase))
{
tmp.MatchType = MatchType.EndsWith;
}
else if (string.Equals(split[0], "regex", StringComparison.OrdinalIgnoreCase))
{
tmp.MatchType = MatchType.Regex;
}
else
{
continue;
}
tmp.WindowQuery = split[1].Trim();
}
else if (tmp != null)
{
tmp.AudioProcessName = split[0].Trim();
}
}
if (!string.IsNullOrEmpty(tmp?.WindowQuery) && !string.IsNullOrEmpty(tmp?.AudioProcessName))
{
overrides.Add(tmp);
}
return overrides;
}
}
}

View File

@ -6,8 +6,8 @@
<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="./lib/sdpi.css">
<link rel="sytlesheet" href="./lib/rangeTooltip.css">
<link rel="stylesheet" href="./css/sdpi.css">
<link rel="sytlesheet" href="./css/rangeTooltip.css">
<script src="lib/sdtools.common.js"></script>
<script src="lib/rangeTooltip.js"></script>
</head>
@ -16,13 +16,20 @@
<div class="sdpi-item">
<div class="sdpi-item-label">Fallback</div>
<select class="sdpi-item-value sdProperty" id="fallbackBehavior" oninput="setSettings()">
<select class="sdpi-item-value select sdProperty" id="fallbackBehavior" oninput="setSettings()">
<option value="0">System Sounds</option>
<option value="1">Previous App</option>
<option value="2">Default Output Device Volume</option>
</select>
</div>
<details>
<summary>Fallback Details</summary>
<p>If you look at windows volume mixer, you will see that not all applications can have their volume controlled. The fallback behavior controls what happens when you are in an application that doesn't show up in the volume mixer</p>
<p>* System Sounds - Switch to system sounds. This will control windows sound effects such as when an error sound plays. If you're in an application that is making beeping sounds, this will often allow you to control those sounds while leaving things like your music/videos alone</p>
<p>* Previous App - Use the last app that had a volume control. This can result in the stream deck not changing after you have quit an application.</p>
<p>* Default Output Device Volume - Switch to the main volume control for the default output device. This will change the volume of the default output device. This is usually volume for all applications, unless you override the output device for specific applications.</p>
</details>
<div type="range" class="sdpi-item sdShowTooltip">
<div class="sdpi-item-label">Step Size</div>
@ -32,15 +39,30 @@
<span class="clickable" value="1">10</span>
</div>
</div>
<div class="sdpi-info-label hidden" style="top: -1000;" value="">Tooltip</div>
<div class="sdpi-info-label hidden" style="z-index: 999;" value="">Tooltip</div>
<div type="textarea" class="sdpi-item">
<div class="sdpi-item-label">Overrides</div>
<span class="sdpi-item-value" textarea>
<textarea type="textarea" class="sdProperty" id="overrides" oninput="setSettings()"></textarea>
<label for="overrides">window title => volume process</label>
</span>
</div>
<details>
<p>If you look at windows volume mixer, you will see that not all applications can have their volume controlled. The fallback behavior controls what happens when you are in an application that doesn't show up in the volume mixer</p>
<p>* System Sounds - Switch to system sounds. This will control windows sound effects such as when an error sound plays. If you're in an application that is making beeping sounds, this will often allow you to control those sounds while leaving things like your music/videos alone</p>
<p>* Previous App - Use the last app that had a volume control. This can result in the stream deck not changing after you have quit an application.</p>
<p>* Default Output Device Volume - Switch to the main volume control for the default output device. This will change the volume of the default output device. This is usually volume for all applications, unless you override the output device for specific applications.</p>
<summary>Override Details</summary>
<p>Some games use anti-cheat software that interferes with the ability to know what application is running, and pair it to the appropriate audio process.</p>
<p>There is nothing I can do about this.</p>
<p>In order to work around this, the overrides mechanism is there for you to manually map an application window to an audio process.</p>
<p>They synax for this is</p>
<p>[matching]: Window Title <br />Audio Process</p>
<p>Blank lines can be used for spacing</p>
<p>[matching] can be eq, start or end. Which will perform an exact match, starts with, or ends with respectively.</p>
<p>Example:<br />eq: Task Manager<br />Discord</p>
</details>
</div>
</body>
</html>

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="6" viewBox="0 0 12 6">
<polygon fill="#8E8E92" fill-rule="evenodd" points="5 4 9 0 10 1 5 6 0 1 1 0"/>
</svg>

After

Width:  |  Height:  |  Size: 173 B

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="7" height="12" viewBox="0 0 7 12">
<g fill="#8E8E92" transform="translate(4, 5) rotate(-90) translate(-4, -5) translate(-2, 2)">
<polygon id="Path" points="5 4 9 0 10 1 5 6 0 1 1 0"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="10" viewBox="0 0 12 10">
<polygon fill="#FFF" points="7.2 7.5 7.2 -1.3 8.7 -1.3 8.6 9.1 2.7 8.7 2.7 7.2" transform="rotate(37 5.718 3.896)"/>
</svg>

After

Width:  |  Height:  |  Size: 212 B

View File

@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="none" fill-rule="evenodd">
<path fill="#9C9C9C" fill-rule="nonzero"
d="M1,5 L1,14 L14,14 L14,5 L1,5 Z M0,1 L15,1 L15,15 L0,15 L0,1 Z M14,4 L14,2 L1,2 L1,4 L14,4 Z"/>
<rect width="1" height="1" x="2" fill="#9C9C9C" fill-rule="nonzero"/>
<rect width="1" height="1" x="12" fill="#9C9C9C" fill-rule="nonzero"/>
<g transform="translate(3 7)">
<rect width="1" height="1" x="2" fill="#9C9C9C"/>
<rect width="1" height="1" fill="#666"/>
<rect width="1" height="1" x="4" fill="#9C9C9C"/>
<rect width="1" height="1" x="6" fill="#9C9C9C"/>
<rect width="1" height="1" x="8" fill="#9C9C9C"/>
<rect width="1" height="1" y="2" fill="#9C9C9C"/>
<rect width="1" height="1" x="2" y="2" fill="#9C9C9C"/>
<rect width="1" height="1" x="4" y="2" fill="#9C9C9C"/>
<rect width="1" height="1" x="6" y="2" fill="#9C9C9C"/>
<rect width="1" height="1" x="8" y="2" fill="#9C9C9C"/>
<rect width="1" height="1" y="4" fill="#9C9C9C"/>
<rect width="1" height="1" x="2" y="4" fill="#9C9C9C"/>
<rect width="1" height="1" x="4" y="4" fill="#9C9C9C"/>
<rect width="1" height="1" x="6" y="4" fill="#9C9C9C"/>
<rect width="1" height="1" x="8" y="4" fill="#666"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<g fill="#9C9C9C">
<path d="M15,15 L1.77635684e-15,15 L1.77635684e-15,1 L15,1 L15,15 Z M5,7 L5,8 L6,8 L6,7 L5,7 Z M3,7 L3,8 L4,8 L4,7 L3,7 Z M7,7 L7,8 L8,8 L8,7 L7,7 Z M9,7 L9,8 L10,8 L10,7 L9,7 Z M11,7 L11,8 L12,8 L12,7 L11,7 Z M3,9 L3,10 L4,10 L4,9 L3,9 Z M5,9 L5,10 L6,10 L6,9 L5,9 Z M7,9 L7,10 L8,10 L8,9 L7,9 Z M9,9 L9,10 L10,10 L10,9 L9,9 Z M11,9 L11,10 L12,10 L12,9 L11,9 Z M3,11 L3,12 L4,12 L4,11 L3,11 Z M5,11 L5,12 L6,12 L6,11 L5,11 Z M7,11 L7,12 L8,12 L8,11 L7,11 Z M9,11 L9,12 L10,12 L10,11 L9,11 Z M11,11 L11,12 L12,12 L12,11 L11,11 Z M14,4 L14,2 L1,2 L1,4 L14,4 Z"/>
<rect width="1" height="1" x="2"/>
<rect width="1" height="1" x="12"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 780 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="6" height="6" viewBox="0 0 6 6">
<circle cx="3" cy="3" r="3" fill="#FFF"/>
</svg>

After

Width:  |  Height:  |  Size: 133 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1" height="8" viewBox="0 0 1 8">
<rect id="Rectangle" width="1" height="6" x="0" y="1" fill="#555"/>
</svg>

After

Width:  |  Height:  |  Size: 159 B

View File

@ -1,65 +1,3 @@
.linkspan {
cursor: pointer;
color: #7397d2;
text-decoration: underline;
}
.titleAlignedSmall {
font-size: 9pt;
padding-left: 33px !important;
}
.small {
font-size: 9pt !important;
}
.leftMargin10 {
margin-left: 10px;
}
.leftMargin0 {
margin-left: 0px !important;
}
.leftPadding3 {
padding-left: 3px !important;
}
.leftPadding0 {
padding-left: 0px !important;
}
.bright {
color: #d8d8d8;
}
.iconLeft {
background-position: 0px 4px !important;
}
.summaryIconPadding {
padding-left: 15px;
}
.subMenu {
border-left: 1px dotted gray;
padding-left: 15px;
max-width: 96%;
background-color: #323232;
}
:root {
--sdpi-bgcolor: #2D2D2D;
--sdpi-background: #3D3D3D;
--sdpi-color: #d8d8d8;
--sdpi-bordercolor: #3a3a3a;
--sdpi-buttonbordercolor: #969696;
--sdpi-borderradius: 0px;
--sdpi-width: 224px;
--sdpi-fontweight: 600;
--sdpi-letterspacing: -0.25pt;
}
html {
--sdpi-bgcolor: #2D2D2D;
--sdpi-background: #3D3D3D;
@ -70,6 +8,16 @@ html {
--sdpi-width: 224px;
--sdpi-fontweight: 600;
--sdpi-letterspacing: -0.25pt;
--sdpi-tab-color: #969696;
--sdpi-tab-left-margin: 1px;
--sdpi-tab-top-offset: 1px;
--sdpi-tab-selected-color: #333333;
--sdpi-tab-selected-top-offset: 0px;
--sdpi-tab-font-size: 9pt;
--sdpi-tab-container-left-offset: 5px;
--sdpi-tab-padding-horizontal: 12px;
--sdpi-tab-padding-vertical: 5px;
--sdpi-linecolor: #454545;
height: 100%;
width: 100%;
overflow: hidden;
@ -119,6 +67,14 @@ hr2,
margin: 8px 0px;
}
h1 {
font-size: 1.3em;
font-weight: 500;
text-align: center;
margin-bottom: 12px;
}
.sdpi-heading::before,
.sdpi-heading::after {
content: "";
@ -225,6 +181,9 @@ progress {
margin-bottom: 4px;
}
/* TABS */
.tabs {
/**
* Setting display to flex makes this container lay
@ -232,19 +191,91 @@ progress {
* as in the above "Stepper input" example.
*/
display: flex;
border-bottom: 1px solid #D7DBDD;
border-bottom: 1px solid rgba(255, 255, 255, 0.0);
flex-wrap: nowrap;
white-space: nowrap;
overflow-x: auto;
text-transform: capitalize;
background-color: transparent;
margin-left: var(--sdpi-tab-container-left-offset);
}
.tabs::-webkit-scrollbar {
height: 4px;
display: none;
}
.tabs::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
border-radius: 8px;
}
.tabs::-webkit-scrollbar-thumb {
background-color: #444;
outline: 1px solid #444;
border-radius: 8px;
}
.tab-separator {
margin-left: 100px;
max-width: 234px;
margin-bottom: 20px;
margin-top: -4px;
}
.tab {
cursor: pointer;
padding: 5px 30px;
color: #16a2d7;
font-size: 9pt;
border-bottom: 2px solid transparent;
padding: var(--sdpi-tab-padding-vertical) var(--sdpi-tab-padding-horizontal);
color: var(--sdpi-tab-color);
font-size: var(--sdpi-tab-font-size);
font-weight: var(--title-font-weight);
background-color: rgba(0, 0, 0, 0.1);
margin: 0px;
margin-top: var(--sdpi-tab-top-offset);
margin-left: var(--sdpi-tab-left-margin);
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border: 1px solid rgba(255, 255, 255, 0.1);
border-bottom: 1px solid var(--sdpi-linecolor);
-webkit-user-select: none;
user-select: none;
}
.tab.is-tab-selected {
border-bottom-color: #4ebbe4;
.tab:first-child {
margin-left: 0px;
}
.tab-container {
margin-top: -14px;
}
.tab-container > hr {
margin-left: 100px;
max-width: 234px;
}
.tabs + hr {
margin-left: 0px;
max-width: 234px;
margin-top: -6px;
}
.tab.selected {
color: white;
background-color: var(--sdpi-tab-selected-color);
border-bottom: 2px solid var(--sdpi-tab-selected-color);
margin-top: var(--sdpi-tab-selected-top-offset);
}
.sdpi-item.tabgroup {
margin-top: 0px;
}
.istab {
background-color: rgba(0, 0, 0, 0.2);
margin-bottom: 20px;
padding: 4px;
}
select {
@ -306,18 +337,30 @@ option {
.sdpi-wrapper {
overflow-x: hidden;
height: 100%;
margin-right: 1px; /* ensure scroller thumb is not clipped */
}
.sdpi-item {
display: flex;
flex-direction: row;
min-height: 32px;
align-items: center;
min-height: 30px;
align-items: first baseline;
margin-top: 2px;
max-width: 344px;
-webkit-user-drag: none;
}
.sdpi-item[type="textarea"],
.sdpi-item[type="color"],
.sdpi-item[type="canvas"],
.sdpi-item .aligncenter {
align-items: center;
}
.sdpi-item[type="color"] > .sdpi-item-label {
line-height: 22px;
}
.sdpi-item:first-child {
margin-top: -1px;
}
@ -412,7 +455,7 @@ table > caption {
padding-right: 5px;
font-weight: 600;
-webkit-user-select: none;
line-height: 24px;
line-height: normal;
margin-left: -1px;
}
@ -486,8 +529,8 @@ ol.sdpi-item-value,
margin-left: 5px;
margin-right: 12px;
padding: 4px !important;
display: flex;
flex-direction: column;
/* display: flex;
flex-direction: column; */
}
.two-items li {
@ -632,6 +675,16 @@ summary {
cursor: pointer;
}
.sdpi-item.details {
align-items: first baseline;
}
/* needs Chromium update 2023
.sdpi-item:has(>details) {
align-items: first baseline;
}
*/
details * {
font-size: 12px;
font-weight: normal;
@ -654,21 +707,21 @@ details.message {
}
details.message > summary:first-of-type {
/*line-height: 48px;*/
line-height: 20px;
}
details.message h1 {
text-align: left;
}
details:not(.pointer) > summary {
/* details:not(.pointer)>summary {
list-style: none;
}
details > summary::-webkit-details-marker
details > summary::-webkit-details-marker,
.message > summary::-webkit-details-marker {
display: none;
}
} */
.info20,
.question,
@ -702,7 +755,6 @@ details > summary::-webkit-details-marker
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,18 C5.581722,18 2,14.418278 2,10 C2,5.581722 5.581722,2 10,2 C14.418278,2 18,5.581722 18,10 C18,14.418278 14.418278,18 10,18 Z M6.77783203,7.65332031 C6.77783203,7.84798274 6.85929281,8.02888914 7.0222168,8.19604492 C7.18514079,8.36320071 7.38508996,8.44677734 7.62207031,8.44677734 C8.02409055,8.44677734 8.29703704,8.20768468 8.44091797,7.72949219 C8.59326248,7.27245865 8.77945854,6.92651485 8.99951172,6.69165039 C9.2195649,6.45678594 9.56233491,6.33935547 10.027832,6.33935547 C10.4256205,6.33935547 10.7006836,6.37695313 11.0021973,6.68847656 C11.652832,7.53271484 10.942627,8.472229 10.3750916,9.1321106 C9.80755615,9.79199219 8.29492188,11.9897461 10.027832,12.1347656 C10.4498423,12.1700818 10.7027991,11.9147157 10.7832031,11.4746094 C11.0021973,9.59857178 13.1254883,8.82415771 13.1254883,7.53271484 C13.1254883,7.07568131 12.9974785,6.65250846 12.7414551,6.26318359 C12.4854317,5.87385873 12.1225609,5.56600048 11.652832,5.33959961 C11.1831031,5.11319874 10.6414419,5 10.027832,5 C9.36767248,5 8.79004154,5.13541531 8.29492187,5.40625 C7.79980221,5.67708469 7.42317837,6.01879677 7.16503906,6.43139648 C6.90689975,6.8439962 6.77783203,7.25130007 6.77783203,7.65332031 Z M10.0099668,15 C10.2713191,15 10.5016601,14.9108147 10.7009967,14.7324415 C10.9003332,14.5540682 11,14.3088087 11,13.9966555 C11,13.7157177 10.9047629,13.4793767 10.7142857,13.2876254 C10.5238086,13.0958742 10.2890379,13 10.0099668,13 C9.72646591,13 9.48726565,13.0958742 9.2923588,13.2876254 C9.09745196,13.4793767 9,13.7157177 9,13.9966555 C9,14.313268 9.10077419,14.5596424 9.30232558,14.735786 C9.50387698,14.9119295 9.73975502,15 10.0099668,15 Z'/%3E%3C/svg%3E%0A");
}
.sdpi-more-info {
position: fixed;
left: 0px;
@ -731,7 +783,6 @@ details > summary::-webkit-details-marker
user-select: none;
}
.sdpi-bottom-bar {
display: flex;
align-self: right;
@ -759,7 +810,6 @@ details a {
padding-right: 28px;
}
input:not([type="range"]),
textarea {
-webkit-appearance: none;
@ -827,7 +877,6 @@ input[type="checkbox"] {
margin-top: -2px;
min-width: 8px;
text-align: right;
user-select: none;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
@ -840,6 +889,10 @@ input[type="checkbox"] {
span + input[type="range"] {
display: flex;
}
span + .range-container > input[type="range"],
span + input[type="range"] {
max-width: 168px;
}
@ -1028,7 +1081,6 @@ textarea {
padding: 0;
}
.card-carousel-wrapper {
display: flex;
align-items: center;
@ -1091,7 +1143,8 @@ textarea {
margin: 0 5px;
cursor: pointer;
/* box-shadow: 0 4px 15px 0 rgba(40, 44, 53, 0.06), 0 2px 2px 0 rgba(40, 44, 53, 0.08); */
background-color: #fff;
/* background-color: #fff; */
text-align: center;
border-radius: 4px;
z-index: 3;
}
@ -1114,6 +1167,7 @@ textarea {
.card-carousel-cards .card-carousel--card img:hover {
opacity: 0.5;
background-color: rgba(255, 255, 255, .1);
}
.card-carousel-cards .card-carousel--card--footer {
@ -1141,44 +1195,8 @@ textarea {
color: #666a73;
}
h1 {
font-size: 1.3em;
font-weight: 500;
text-align: center;
margin-bottom: 12px;
}
::-webkit-datetime-edit {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
background: url(../assets/elg_calendar_inv.svg) no-repeat left center;
padding-right: 1em;
padding-left: 25px;
background-position: 4px 0px;
}
::-webkit-datetime-edit-fields-wrapper {
}
::-webkit-datetime-edit-text {
padding: 0 0.3em;
}
::-webkit-datetime-edit-month-field {
}
::-webkit-datetime-edit-day-field {
}
::-webkit-datetime-edit-year-field {
}
::-webkit-inner-spin-button {
/* display: none; */
}
::-webkit-calendar-picker-indicator {
background: transparent;
background: url(../assets/elg_calendar_inv.svg) no-repeat center;
font-size: 17px;
}
@ -1186,53 +1204,30 @@ h1 {
background-color: rgba(0, 0, 0, 0.2);
}
input[type="text"]::-webkit-calendar-picker-indicator {
background: transparent;
font-size: 12px;
}
input[type="date"] {
-webkit-align-items: center;
align-items: center;
display: -webkit-inline-flex;
font-family: monospace;
overflow: hidden;
padding: 0;
-webkit-padding-start: 1px;
}
input::-webkit-datetime-edit {
-webkit-flex: 1;
flex: 1;
-webkit-user-modify: read-only !important;
user-modify: read-only !important;
display: inline-block;
min-width: 0;
overflow: hidden;
padding: 4px;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
/*
input::-webkit-datetime-edit-fields-wrapper {
-webkit-user-modify: read-only !important;
display: inline-block;
padding: 1px 0;
white-space: pre;
}
*/
/*
input[type="date"] {
background-color: red;
outline: none;
}
input[type="date"]::-webkit-clear-button {
font-size: 18px;
height: 30px;
position: relative;
}
input[type="date"]::-webkit-inner-spin-button {
height: 28px;
}
input[type="date"]::-webkit-calendar-picker-indicator {
font-size: 15px;
} */
input[type="file"] {
opacity: 0;
display: none;
@ -1308,7 +1303,6 @@ input:required:valid {
background-color: var(--sdpi-background);
}
::-webkit-scrollbar {
width: 8px;
}
@ -1330,23 +1324,72 @@ a {
color: #7397d2;
}
.testcontainer {
display: flex;
background-color: #0000ff20;
max-width: 400px;
height: 200px;
align-content: space-evenly;
input[type="week"] {
-webkit-appearance: auto !important;
appearance: auto !important;
}
input[type=range] {
-webkit-appearance: none;
/* background-color: green; */
input[type="month"] + datalist,
input[type="day"] + datalist,
input[type="week"] + datalist,
input[type=text] + datalist {
display: none !important;
}
input[type="range"] {
-webkit-appearance: auto;
appearance: auto;
height: 6px;
margin-top: 12px;
z-index: 0;
overflow: visible;
}
input[type="range"]::-webkit-slider-runnable-track {
border: 0px solid transparent;
}
.sdpi-item[type="range"] .sdpi-item-value.datalist {
flex-direction: column;
}
datalist {
--sdpi-datalist-margin: 7px;
display: flex;
justify-content: space-between;
margin-top: 0px;
padding-top: 0px;
font-size: 12px;
margin-left: var(--sdpi-datalist-margin);
width: calc(100% - calc(var(--sdpi-datalist-margin) * 2.5));
}
datalist > option {
display: flex;
justify-content: center;
align-items: end;
/* background-image: url(../assets/tick.svg); */
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1' height='8' viewBox='0 0 1 8'%3E%3Crect width='1' height='6' x='0' y='1' fill='%23555'/%3E%3C/svg%3E%0A");
padding: 0;
font-weight: 400;
font-size: 12px;
color: #9A9A99;
width: 1px;
height: 30px;
z-index: 1;
margin-top: -6px;
background-position: top 6px right 5px;
background-repeat: repeat no-repeat; /* fallback */
background-repeat-y: no-repeat;
user-select: none;
-webkit-user-select: none;
}
[role="spinbutton"] {
-webkit-appearance: auto;
appearance: auto;
}
/*
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
@ -1595,7 +1638,8 @@ input[type="range"].colortemperature::-webkit-slider-runnable-track {
background-image: linear-gradient(to right, #94d0ec, #ffb165);
}
input[type="range"].colorbrightness::-webkit-slider-runnable-track {
input[type="range"].colorbrightness.greyscale::-webkit-slider-runnable-track,
input[type="range"].colorbrightness.grayscale::-webkit-slider-runnable-track {
background-color: #efefef;
background-image: linear-gradient(to right, black, rgba(0, 0, 0, 0));
}
@ -1648,3 +1692,54 @@ select {
-webkit-appearance: media-slider;
text-overflow: ellipsis;
}
/*--------- context menu ----------*/
.context-menu {
display: none;
position: absolute;
z-index: 10;
padding: 12px 0;
width: 120px;
background-color: #3D3D3D;
border: solid 1px #dfdfdf;
box-shadow: 1px 1px 2px #cfcfcf;
}
.context-menu--active {
display: block;
}
.context-menu__items {
list-style: none;
margin: 0;
padding: 0;
overflow-y: auto;
}
.context-menu__item {
display: block;
margin-bottom: 4px;
background-color: #3D3D3D !important;
}
.context-menu__item:last-child {
margin-bottom: 0;
}
.context-menu__link {
display: block;
padding: 4px 12px;
color: #ffff;
text-decoration: none;
white-space: nowrap;
}
.context-menu__link:hover {
color: #fff;
background-color: #0066aa;
}
.context-menu_message {
cursor: default;
}

View File

@ -29,6 +29,7 @@ function calcRangeLabel(elem) {
return tooltipValue + outputType;
}
/*
function setElementLabel(elem, str) {
// Try to set this for the rangeLabel class, if it exists
let label = elem.querySelector('.rangeLabel');
@ -39,6 +40,7 @@ function setElementLabel(elem, str) {
console.log('setElementLabel ERROR! No .rangeLabel found', elem);
}
}
*/
function setRangeTooltips() {
console.log("Loading setRangeTooltips");
@ -68,7 +70,7 @@ function setRangeTooltips() {
tooltip.style.top = (rangeRect.top - 32) + 'px';
}
setElementLabel(elem, labelStr)
//setElementLabel(elem, labelStr)
};
rangeSelector.addEventListener(
@ -100,7 +102,7 @@ function setRangeTooltips() {
console.log('rangeTooltip settingsUpdated called');
window.setTimeout(function () {
let str = calcRangeLabel(rangeSelector);
setElementLabel(elem, str);
//setElementLabel(elem, str);
}, 500);
},
false
@ -112,7 +114,7 @@ function setRangeTooltips() {
console.log('rangeTooltip websocketCreate called');
window.setTimeout(function () {
let str = calcRangeLabel(rangeSelector);
setElementLabel(elem, str);
//setElementLabel(elem, str);
}, 500);
},
false

View File

@ -7,6 +7,8 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
#nullable enable
namespace FocusVolumeControl.UI
{
internal class JavaIconExtractor
@ -94,3 +96,5 @@ namespace FocusVolumeControl.UI
}
}
#nullable restore

View File

@ -8,6 +8,8 @@ using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
#nullable enable
namespace FocusVolumeControl
{
internal class WindowChangedEventLoop
@ -16,10 +18,10 @@ namespace FocusVolumeControl
public static WindowChangedEventLoop Instance => _lazy.Value;
readonly Thread _thread;
Dispatcher _dispatcher;
Dispatcher? _dispatcher;
IntPtr _foregroundWindowChangedEvent;
Native.WinEventDelegate _delegate;
Native.WinEventDelegate? _delegate;
private WindowChangedEventLoop()
{
@ -37,7 +39,7 @@ namespace FocusVolumeControl
_thread.Start();
}
public event Action WindowChanged;
public event Action? WindowChanged;
CancellationTokenSource? _cancellationTokenSource = null;
@ -64,3 +66,5 @@ namespace FocusVolumeControl
}
}
}
#nullable restore

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.2.0",
"Version": "1.3.0",
"CodePath": "FocusVolumeControl",
"Category": "Volume Control [dlprows]",
"Icon": "Images/pluginIcon",