Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
f9b23a62a3 | |||
06266daa92 | |||
1fea2a2e11 | |||
2e44a27b2b | |||
84a9a89074 | |||
b57ea24b11 | |||
0d056215bc | |||
f0a5a48c73 | |||
cdf05d2913 |
@ -54,6 +54,14 @@ public class AudioHelper
|
||||
|
||||
static object _lock = new object();
|
||||
|
||||
public void ResetCache()
|
||||
{
|
||||
lock(_lock)
|
||||
{
|
||||
_current = null;
|
||||
}
|
||||
}
|
||||
|
||||
public IAudioSession GetActiveSession(FallbackBehavior fallbackBehavior)
|
||||
{
|
||||
lock (_lock)
|
||||
@ -67,11 +75,11 @@ public class AudioHelper
|
||||
|
||||
if(_current == null)
|
||||
{
|
||||
if(fallbackBehavior == FallbackBehavior.SystemSounds)
|
||||
if(fallbackBehavior == FallbackBehavior.SystemSounds && _current is not SystemSoundsAudioSession)
|
||||
{
|
||||
_current = GetSystemSounds();
|
||||
}
|
||||
else if(fallbackBehavior == FallbackBehavior.SystemVolume)
|
||||
else if(fallbackBehavior == FallbackBehavior.SystemVolume && _current is not SystemVolumeAudioSession)
|
||||
{
|
||||
_current = GetSystemVolume();
|
||||
}
|
||||
|
@ -65,18 +65,14 @@ public class ActiveAudioSessionWrapper : IAudioSession
|
||||
{
|
||||
//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);
|
||||
|
||||
level = VolumeHelpers.GetAdjustedVolume(level, step, ticks);
|
||||
Volume.ForEach(x => x.MasterVolume = level);
|
||||
}
|
||||
|
||||
public int GetVolumeLevel()
|
||||
{
|
||||
var level = Volume.FirstOrDefault()?.MasterVolume ?? 0;
|
||||
return (int)(level * 100);
|
||||
return VolumeHelpers.GetVolumePercentage(level);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,15 +24,10 @@ internal class SystemSoundsAudioSession : IAudioSession
|
||||
|
||||
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);
|
||||
|
||||
var level = VolumeHelpers.GetAdjustedVolume(_volumeControl.MasterVolume, step, ticks);
|
||||
_volumeControl.MasterVolume = level;
|
||||
}
|
||||
|
||||
public int GetVolumeLevel() => (int)(_volumeControl.MasterVolume * 100);
|
||||
public int GetVolumeLevel() => VolumeHelpers.GetVolumePercentage(_volumeControl.MasterVolume);
|
||||
|
||||
}
|
||||
|
@ -24,15 +24,10 @@ internal class SystemVolumeAudioSession : IAudioSession
|
||||
|
||||
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);
|
||||
|
||||
var level = VolumeHelpers.GetAdjustedVolume(_volumeControl.MasterVolumeLevelScalar, step, ticks);
|
||||
_volumeControl.MasterVolumeLevelScalar = level;
|
||||
}
|
||||
|
||||
public int GetVolumeLevel() => (int)(_volumeControl.MasterVolumeLevelScalar * 100);
|
||||
public int GetVolumeLevel() => VolumeHelpers.GetVolumePercentage(_volumeControl.MasterVolumeLevelScalar);
|
||||
|
||||
}
|
||||
|
25
src/FocusVolumeControl/AudioSessions/VolumeHelpers.cs
Normal file
25
src/FocusVolumeControl/AudioSessions/VolumeHelpers.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FocusVolumeControl.AudioSessions
|
||||
{
|
||||
internal class VolumeHelpers
|
||||
{
|
||||
public static float GetAdjustedVolume(float startingVolume, int step, int ticks)
|
||||
{
|
||||
var level = startingVolume;
|
||||
|
||||
level += 0.01f * step * ticks;
|
||||
level = Math.Max(level, 0);
|
||||
level = Math.Min(level, 1);
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
public static int GetVolumePercentage(float volume) => (int)Math.Round(volume * 100);
|
||||
|
||||
}
|
||||
}
|
@ -19,6 +19,9 @@ public class DialAction : EncoderBase
|
||||
[JsonProperty("fallbackBehavior")]
|
||||
public FallbackBehavior FallbackBehavior { get; set; }
|
||||
|
||||
[JsonProperty("stepSize")]
|
||||
public int StepSize { get; set; }
|
||||
|
||||
public static PluginSettings CreateDefaultSettings()
|
||||
{
|
||||
PluginSettings instance = new PluginSettings();
|
||||
@ -66,79 +69,8 @@ public class DialAction : EncoderBase
|
||||
_thread.SetApartmentState(ApartmentState.STA);
|
||||
_thread.Start();
|
||||
|
||||
|
||||
_currentAudioSession = settings.FallbackBehavior == FallbackBehavior.SystemSounds ? _audioHelper.GetSystemSounds() : _audioHelper.GetSystemVolume();
|
||||
}
|
||||
|
||||
public override async void DialDown(DialPayload payload)
|
||||
{
|
||||
//dial pressed down
|
||||
Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Down");
|
||||
await ToggleMuteAsync();
|
||||
}
|
||||
|
||||
public override async void TouchPress(TouchpadPressPayload payload)
|
||||
{
|
||||
Logger.Instance.LogMessage(TracingLevel.INFO, "Touch Press");
|
||||
if (payload.IsLongPress)
|
||||
{
|
||||
_audioHelper.ResetAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ToggleMuteAsync();
|
||||
}
|
||||
}
|
||||
|
||||
async Task ToggleMuteAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
if (_currentAudioSession != null)
|
||||
{
|
||||
_currentAudioSession.ToggleMute();
|
||||
await UpdateStateIfNeeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
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)
|
||||
{
|
||||
Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Rotate");
|
||||
//dial rotated. ticks positive for right, negative for left
|
||||
try
|
||||
{
|
||||
if (_currentAudioSession != null)
|
||||
{
|
||||
_currentAudioSession.IncrementVolumeLevel(1, payload.Ticks);
|
||||
await UpdateStateIfNeeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
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)
|
||||
{
|
||||
//dial unpressed
|
||||
Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Up");
|
||||
_ = UpdateStateIfNeeded();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
@ -151,7 +83,103 @@ public class DialAction : EncoderBase
|
||||
_dispatcher.InvokeShutdown();
|
||||
}
|
||||
|
||||
public override async void DialDown(DialPayload payload)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Down");
|
||||
await ToggleMuteAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.LogMessage(TracingLevel.ERROR, $"Unexpected Error in DialDown:\n {ex}");
|
||||
}
|
||||
}
|
||||
public override void DialUp(DialPayload payload) { }
|
||||
|
||||
public override async void TouchPress(TouchpadPressPayload payload)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Instance.LogMessage(TracingLevel.INFO, "Touch Press");
|
||||
if (payload.IsLongPress)
|
||||
{
|
||||
await ResetAllAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ToggleMuteAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.LogMessage(TracingLevel.ERROR, $"Unexpected Error in TouchPress:\n {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public override async void DialRotate(DialRotatePayload payload)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Instance.LogMessage(TracingLevel.INFO, "Dial Rotate");
|
||||
//dial rotated. ticks positive for right, negative for left
|
||||
if (_currentAudioSession != null)
|
||||
{
|
||||
_currentAudioSession.IncrementVolumeLevel(settings.StepSize, payload.Ticks);
|
||||
await UpdateStateIfNeeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Connection.ShowAlert();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_audioHelper.ResetCache();
|
||||
await Connection.ShowAlert();
|
||||
Logger.Instance.LogMessage(TracingLevel.ERROR, $"Unable to increment volume:\n {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
async Task ResetAllAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_audioHelper.ResetAll();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_audioHelper.ResetCache();
|
||||
await Connection.ShowAlert();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
async Task ToggleMuteAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_currentAudioSession != null)
|
||||
{
|
||||
_currentAudioSession.ToggleMute();
|
||||
await UpdateStateIfNeeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
await Connection.ShowAlert();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
_audioHelper.ResetCache();
|
||||
await Connection.ShowAlert();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override async void OnTick()
|
||||
{
|
||||
try
|
||||
{
|
||||
//called once every 1000ms and can be used for updating the title/image of the key
|
||||
var activeSession = _audioHelper.GetActiveSession(settings.FallbackBehavior);
|
||||
@ -163,8 +191,16 @@ public class DialAction : EncoderBase
|
||||
|
||||
await UpdateStateIfNeeded();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_audioHelper.ResetCache();
|
||||
Logger.Instance.LogMessage(TracingLevel.ERROR, $"Exception on Tick:\n {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateStateIfNeeded()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_currentAudioSession != null)
|
||||
{
|
||||
@ -188,6 +224,11 @@ public class DialAction : EncoderBase
|
||||
_previousState = uiState;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.LogMessage(TracingLevel.ERROR, $"Failed to update screen\n {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void ReceivedGlobalSettings(ReceivedGlobalSettingsPayload payload)
|
||||
@ -196,21 +237,40 @@ public class DialAction : EncoderBase
|
||||
|
||||
|
||||
public override void ReceivedSettings(ReceivedSettingsPayload payload)
|
||||
{
|
||||
try
|
||||
{
|
||||
Tools.AutoPopulateSettings(settings, payload.Settings);
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
private Task SaveSettings()
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Connection.SetSettingsAsync(JObject.FromObject(settings));
|
||||
Logger.Instance.LogMessage(TracingLevel.ERROR, $"Unexpected Error in SaveSettings:\n {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
await Connection.SetSettingsAsync(JObject.FromObject(settings));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.LogMessage(TracingLevel.ERROR, $"Unexpected Error in SaveSettings:\n {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnTick();
|
||||
}
|
||||
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.LogMessage(TracingLevel.ERROR, $"Unexpected Error in DialDown:\n {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,7 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="AudioSessions\ActiveAudioSessionWrapper.cs" />
|
||||
<Compile Include="AudioHelper.cs" />
|
||||
<Compile Include="AudioSessions\VolumeHelpers.cs" />
|
||||
<Compile Include="AudioSessions\SystemSoundsAudioSession.cs" />
|
||||
<Compile Include="AudioSessions\SystemVolumeAudioSession.cs" />
|
||||
<Compile Include="DialAction.cs" />
|
||||
@ -83,6 +84,9 @@
|
||||
<Content Include="Images\**\*.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="PropertyInspector\**\*.js;PropertyInspector\**\*.css">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="PropertyInspector\PluginActionPI.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
@ -6,11 +6,14 @@
|
||||
<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>
|
||||
<link rel="stylesheet" href="./lib/sdpi.css">
|
||||
<link rel="sytlesheet" href="./lib/rangeTooltip.css">
|
||||
<script src="lib/sdtools.common.js"></script>
|
||||
<script src="lib/rangeTooltip.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sdpi-wrapper">
|
||||
|
||||
<div class="sdpi-item">
|
||||
<div class="sdpi-item-label">Fallback</div>
|
||||
<select class="sdpi-item-value sdProperty" id="fallbackBehavior" oninput="setSettings()">
|
||||
@ -19,12 +22,25 @@
|
||||
<option value="2">Main System Volume</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<div type="range" class="sdpi-item sdShowTooltip">
|
||||
<div class="sdpi-item-label">Step Size</div>
|
||||
<div class="sdpi-item-value">
|
||||
<span class="clickable" value="1">1</span>
|
||||
<input type="range" min="1" max="10" value="1" class="sdProperty" data-suffix=" %" id="stepSize" oninput="setSettings()" />
|
||||
<span class="clickable" value="1">10</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sdpi-info-label hidden" style="top: -1000;" value="">Tooltip</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>* Main System Volume - Switch to the main volume control for the system. This will change the volume of all applications</p>
|
||||
</details>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -0,0 +1,41 @@
|
||||
.sdpi-info-label {
|
||||
display: inline-block;
|
||||
user-select: none;
|
||||
position: absolute;
|
||||
height: 15px;
|
||||
width: auto;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
min-width: 44px;
|
||||
max-width: 80px;
|
||||
background: white;
|
||||
font-size: 11px;
|
||||
color: black;
|
||||
z-index: 1000;
|
||||
box-shadow: 0px 0px 12px rgba(0,0,0,.8);
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.sdpi-info-label.hidden {
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s linear;
|
||||
}
|
||||
|
||||
.sdpi-info-label.shown {
|
||||
position: absolute;
|
||||
opacity: 1;
|
||||
transition: opacity 0.25s ease-out;
|
||||
}
|
||||
|
||||
.rangeLabel {
|
||||
position: relative;
|
||||
font-weight: normal;
|
||||
margin-top: 22px;
|
||||
left: -200px;
|
||||
min-width: 200px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.percent::after {
|
||||
content: "%";
|
||||
}
|
122
src/FocusVolumeControl/PropertyInspector/lib/rangeTooltip.js
Normal file
122
src/FocusVolumeControl/PropertyInspector/lib/rangeTooltip.js
Normal file
@ -0,0 +1,122 @@
|
||||
// ****************************************************************
|
||||
// * EasyPI v1.3
|
||||
// * Author: BarRaider
|
||||
// *
|
||||
// * rangeTooltip.js adds a tooltip showing the value of a range slider.
|
||||
// * Requires rangeTooltip.css to be referenced in the HTML file.
|
||||
// *
|
||||
// * Project page: https://github.com/BarRaider/streamdeck-easypi
|
||||
// * Support: http://discord.barraider.com
|
||||
// ****************************************************************
|
||||
|
||||
var tooltip = document.querySelector('.sdpi-info-label');
|
||||
var tw;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Handler when the DOM is fully loaded
|
||||
setRangeTooltips();
|
||||
});
|
||||
|
||||
function calcRangeLabel(elem) {
|
||||
const value = elem.value;
|
||||
const percent = (elem.value - elem.min) / (elem.max - elem.min);
|
||||
let tooltipValue = value;
|
||||
let outputType = elem.dataset.suffix;
|
||||
if (outputType && outputType == '%') {
|
||||
tooltipValue = Math.round(100 * percent);
|
||||
}
|
||||
|
||||
return tooltipValue + outputType;
|
||||
}
|
||||
|
||||
function setElementLabel(elem, str) {
|
||||
// Try to set this for the rangeLabel class, if it exists
|
||||
let label = elem.querySelector('.rangeLabel');
|
||||
if (label) {
|
||||
label.innerHTML = str;
|
||||
}
|
||||
else {
|
||||
console.log('setElementLabel ERROR! No .rangeLabel found', elem);
|
||||
}
|
||||
}
|
||||
|
||||
function setRangeTooltips() {
|
||||
console.log("Loading setRangeTooltips");
|
||||
|
||||
if (!tooltip) {
|
||||
tooltip = document.querySelector('.sdpi-info-label');
|
||||
}
|
||||
|
||||
if (!tw) {
|
||||
tw = tooltip.getBoundingClientRect().width;
|
||||
}
|
||||
|
||||
const rangeToolTips = document.querySelectorAll('div[type=range].sdShowTooltip');
|
||||
rangeToolTips.forEach(elem => {
|
||||
let rangeSelector = elem.querySelector('input[type=range]');
|
||||
let fn = () => {
|
||||
const rangeRect = rangeSelector.getBoundingClientRect();
|
||||
const w = rangeRect.width - tw / 2;
|
||||
const labelStr = calcRangeLabel(rangeSelector);
|
||||
// Set the tooltip
|
||||
if (tooltip.classList.contains('hidden')) {
|
||||
tooltip.style.top = '-1000px';
|
||||
} else {
|
||||
const percent = (rangeSelector.value - rangeSelector.min) / (rangeSelector.max - rangeSelector.min);
|
||||
tooltip.style.left = (rangeRect.left + Math.round(w * percent) - tw / 4) + 'px';
|
||||
tooltip.textContent = labelStr;
|
||||
tooltip.style.top = (rangeRect.top - 32) + 'px';
|
||||
}
|
||||
|
||||
setElementLabel(elem, labelStr)
|
||||
};
|
||||
|
||||
rangeSelector.addEventListener(
|
||||
'mouseenter',
|
||||
function () {
|
||||
tooltip.classList.remove('hidden');
|
||||
tooltip.classList.add('shown');
|
||||
fn();
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
rangeSelector.addEventListener(
|
||||
'mouseout',
|
||||
function () {
|
||||
tooltip.classList.remove('shown');
|
||||
tooltip.classList.add('hidden');
|
||||
fn();
|
||||
},
|
||||
false
|
||||
);
|
||||
rangeSelector.addEventListener('input', fn, false);
|
||||
|
||||
rangeSelector.addEventListener("change", fn, false);
|
||||
|
||||
document.addEventListener(
|
||||
'settingsUpdated',
|
||||
function () {
|
||||
console.log('rangeTooltip settingsUpdated called');
|
||||
window.setTimeout(function () {
|
||||
let str = calcRangeLabel(rangeSelector);
|
||||
setElementLabel(elem, str);
|
||||
}, 500);
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
document.addEventListener(
|
||||
'websocketCreate',
|
||||
function () {
|
||||
console.log('rangeTooltip websocketCreate called');
|
||||
window.setTimeout(function () {
|
||||
let str = calcRangeLabel(rangeSelector);
|
||||
setElementLabel(elem, str);
|
||||
}, 500);
|
||||
},
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
}
|
1650
src/FocusVolumeControl/PropertyInspector/lib/sdpi.css
Normal file
1650
src/FocusVolumeControl/PropertyInspector/lib/sdpi.css
Normal file
File diff suppressed because it is too large
Load Diff
321
src/FocusVolumeControl/PropertyInspector/lib/sdtools.common.js
Normal file
321
src/FocusVolumeControl/PropertyInspector/lib/sdtools.common.js
Normal file
@ -0,0 +1,321 @@
|
||||
// ****************************************************************
|
||||
// * EasyPI v1.4
|
||||
// * Author: BarRaider
|
||||
// *
|
||||
// * JS library to simplify the communication between the
|
||||
// * Stream Deck's Property Inspector and the plugin.
|
||||
// *
|
||||
// * Project page: https://github.com/BarRaider/streamdeck-easypi
|
||||
// * Support: http://discord.barraider.com
|
||||
// *
|
||||
// * Initially forked from Elgato's common.js file
|
||||
// ****************************************************************
|
||||
|
||||
var websocket = null,
|
||||
uuid = null,
|
||||
registerEventName = null,
|
||||
actionInfo = {},
|
||||
inInfo = {},
|
||||
runningApps = [],
|
||||
isQT = navigator.appVersion.includes('QtWebEngine');
|
||||
|
||||
function connectElgatoStreamDeckSocket(inPort, inUUID, inRegisterEvent, inInfo, inActionInfo) {
|
||||
uuid = inUUID;
|
||||
registerEventName = inRegisterEvent;
|
||||
console.log(inUUID, inActionInfo);
|
||||
actionInfo = JSON.parse(inActionInfo); // cache the info
|
||||
inInfo = JSON.parse(inInfo);
|
||||
websocket = new WebSocket('ws://127.0.0.1:' + inPort);
|
||||
|
||||
addDynamicStyles(inInfo.colors);
|
||||
|
||||
websocket.onopen = websocketOnOpen;
|
||||
websocket.onmessage = websocketOnMessage;
|
||||
|
||||
// Allow others to get notified that the websocket is created
|
||||
var event = new Event('websocketCreate');
|
||||
document.dispatchEvent(event);
|
||||
|
||||
loadConfiguration(actionInfo.payload.settings);
|
||||
initPropertyInspector();
|
||||
}
|
||||
|
||||
function websocketOnOpen() {
|
||||
var json = {
|
||||
event: registerEventName,
|
||||
uuid: uuid
|
||||
};
|
||||
websocket.send(JSON.stringify(json));
|
||||
|
||||
// Notify the plugin that we are connected
|
||||
sendValueToPlugin('propertyInspectorConnected', 'property_inspector');
|
||||
}
|
||||
|
||||
function websocketOnMessage(evt) {
|
||||
// Received message from Stream Deck
|
||||
var jsonObj = JSON.parse(evt.data);
|
||||
|
||||
if (jsonObj.event === 'didReceiveSettings') {
|
||||
var payload = jsonObj.payload;
|
||||
loadConfiguration(payload.settings);
|
||||
}
|
||||
else {
|
||||
console.log("Ignored websocketOnMessage: " + jsonObj.event);
|
||||
}
|
||||
}
|
||||
|
||||
function loadConfiguration(payload) {
|
||||
console.log('loadConfiguration');
|
||||
console.log(payload);
|
||||
for (var key in payload) {
|
||||
try {
|
||||
var elem = document.getElementById(key);
|
||||
if (elem.classList.contains("sdCheckbox")) { // Checkbox
|
||||
elem.checked = payload[key];
|
||||
}
|
||||
else if (elem.classList.contains("sdFile")) { // File
|
||||
var elemFile = document.getElementById(elem.id + "Filename");
|
||||
elemFile.innerText = payload[key];
|
||||
if (!elemFile.innerText) {
|
||||
elemFile.innerText = "No file...";
|
||||
}
|
||||
}
|
||||
else if (elem.classList.contains("sdList")) { // Dynamic dropdown
|
||||
var textProperty = elem.getAttribute("sdListTextProperty");
|
||||
var valueProperty = elem.getAttribute("sdListValueProperty");
|
||||
var valueField = elem.getAttribute("sdValueField");
|
||||
|
||||
var items = payload[key];
|
||||
elem.options.length = 0;
|
||||
|
||||
for (var idx = 0; idx < items.length; idx++) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = items[idx][valueProperty];
|
||||
opt.text = items[idx][textProperty];
|
||||
elem.appendChild(opt);
|
||||
}
|
||||
elem.value = payload[valueField];
|
||||
}
|
||||
else if (elem.classList.contains("sdHTML")) { // HTML element
|
||||
elem.innerHTML = payload[key];
|
||||
}
|
||||
else { // Normal value
|
||||
elem.value = payload[key];
|
||||
}
|
||||
console.log("Load: " + key + "=" + payload[key]);
|
||||
}
|
||||
catch (err) {
|
||||
console.log("loadConfiguration failed for key: " + key + " - " + err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setSettings() {
|
||||
var payload = {};
|
||||
var elements = document.getElementsByClassName("sdProperty");
|
||||
|
||||
Array.prototype.forEach.call(elements, function (elem) {
|
||||
var key = elem.id;
|
||||
if (elem.classList.contains("sdCheckbox")) { // Checkbox
|
||||
payload[key] = elem.checked;
|
||||
}
|
||||
else if (elem.classList.contains("sdFile")) { // File
|
||||
var elemFile = document.getElementById(elem.id + "Filename");
|
||||
payload[key] = elem.value;
|
||||
if (!elem.value) {
|
||||
// Fetch innerText if file is empty (happens when we lose and regain focus to this key)
|
||||
payload[key] = elemFile.innerText;
|
||||
}
|
||||
else {
|
||||
// Set value on initial file selection
|
||||
elemFile.innerText = elem.value;
|
||||
}
|
||||
}
|
||||
else if (elem.classList.contains("sdList")) { // Dynamic dropdown
|
||||
var valueField = elem.getAttribute("sdValueField");
|
||||
payload[valueField] = elem.value;
|
||||
}
|
||||
else if (elem.classList.contains("sdHTML")) { // HTML element
|
||||
var valueField = elem.getAttribute("sdValueField");
|
||||
payload[valueField] = elem.innerHTML;
|
||||
}
|
||||
else { // Normal value
|
||||
payload[key] = elem.value;
|
||||
}
|
||||
console.log("Save: " + key + "<=" + payload[key]);
|
||||
});
|
||||
setSettingsToPlugin(payload);
|
||||
}
|
||||
|
||||
function setSettingsToPlugin(payload) {
|
||||
if (websocket && (websocket.readyState === 1)) {
|
||||
const json = {
|
||||
'event': 'setSettings',
|
||||
'context': uuid,
|
||||
'payload': payload
|
||||
};
|
||||
websocket.send(JSON.stringify(json));
|
||||
var event = new Event('settingsUpdated');
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Sends an entire payload to the sendToPlugin method
|
||||
function sendPayloadToPlugin(payload) {
|
||||
if (websocket && (websocket.readyState === 1)) {
|
||||
const json = {
|
||||
'action': actionInfo['action'],
|
||||
'event': 'sendToPlugin',
|
||||
'context': uuid,
|
||||
'payload': payload
|
||||
};
|
||||
websocket.send(JSON.stringify(json));
|
||||
}
|
||||
}
|
||||
|
||||
// Sends one value to the sendToPlugin method
|
||||
function sendValueToPlugin(value, param) {
|
||||
if (websocket && (websocket.readyState === 1)) {
|
||||
const json = {
|
||||
'action': actionInfo['action'],
|
||||
'event': 'sendToPlugin',
|
||||
'context': uuid,
|
||||
'payload': {
|
||||
[param]: value
|
||||
}
|
||||
};
|
||||
websocket.send(JSON.stringify(json));
|
||||
}
|
||||
}
|
||||
|
||||
function openWebsite() {
|
||||
if (websocket && (websocket.readyState === 1)) {
|
||||
const json = {
|
||||
'event': 'openUrl',
|
||||
'payload': {
|
||||
'url': 'https://BarRaider.com'
|
||||
}
|
||||
};
|
||||
websocket.send(JSON.stringify(json));
|
||||
}
|
||||
}
|
||||
|
||||
if (!isQT) {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
initPropertyInspector();
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Notify the plugin we are about to leave
|
||||
sendValueToPlugin('propertyInspectorWillDisappear', 'property_inspector');
|
||||
|
||||
// Don't set a returnValue to the event, otherwise Chromium with throw an error.
|
||||
});
|
||||
|
||||
function prepareDOMElements(baseElement) {
|
||||
baseElement = baseElement || document;
|
||||
|
||||
/**
|
||||
* You could add a 'label' to a textares, e.g. to show the number of charactes already typed
|
||||
* or contained in the textarea. This helper updates this label for you.
|
||||
*/
|
||||
baseElement.querySelectorAll('textarea').forEach((e) => {
|
||||
const maxl = e.getAttribute('maxlength');
|
||||
e.targets = baseElement.querySelectorAll(`[for='${e.id}']`);
|
||||
if (e.targets.length) {
|
||||
let fn = () => {
|
||||
for (let x of e.targets) {
|
||||
x.textContent = maxl ? `${e.value.length}/${maxl}` : `${e.value.length}`;
|
||||
}
|
||||
};
|
||||
fn();
|
||||
e.onkeyup = fn;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initPropertyInspector() {
|
||||
// Place to add functions
|
||||
prepareDOMElements(document);
|
||||
}
|
||||
|
||||
|
||||
function addDynamicStyles(clrs) {
|
||||
const node = document.getElementById('#sdpi-dynamic-styles') || document.createElement('style');
|
||||
if (!clrs.mouseDownColor) clrs.mouseDownColor = fadeColor(clrs.highlightColor, -100);
|
||||
const clr = clrs.highlightColor.slice(0, 7);
|
||||
const clr1 = fadeColor(clr, 100);
|
||||
const clr2 = fadeColor(clr, 60);
|
||||
const metersActiveColor = fadeColor(clr, -60);
|
||||
|
||||
node.setAttribute('id', 'sdpi-dynamic-styles');
|
||||
node.innerHTML = `
|
||||
|
||||
input[type="radio"]:checked + label span,
|
||||
input[type="checkbox"]:checked + label span {
|
||||
background-color: ${clrs.highlightColor};
|
||||
}
|
||||
|
||||
input[type="radio"]:active:checked + label span,
|
||||
input[type="radio"]:active + label span,
|
||||
input[type="checkbox"]:active:checked + label span,
|
||||
input[type="checkbox"]:active + label span {
|
||||
background-color: ${clrs.mouseDownColor};
|
||||
}
|
||||
|
||||
input[type="radio"]:active + label span,
|
||||
input[type="checkbox"]:active + label span {
|
||||
background-color: ${clrs.buttonPressedBorderColor};
|
||||
}
|
||||
|
||||
td.selected,
|
||||
td.selected:hover,
|
||||
li.selected:hover,
|
||||
li.selected {
|
||||
color: white;
|
||||
background-color: ${clrs.highlightColor};
|
||||
}
|
||||
|
||||
.sdpi-file-label > label:active,
|
||||
.sdpi-file-label.file:active,
|
||||
label.sdpi-file-label:active,
|
||||
label.sdpi-file-info:active,
|
||||
input[type="file"]::-webkit-file-upload-button:active,
|
||||
button:active {
|
||||
background-color: ${clrs.buttonPressedBackgroundColor};
|
||||
color: ${clrs.buttonPressedTextColor};
|
||||
border-color: ${clrs.buttonPressedBorderColor};
|
||||
}
|
||||
|
||||
::-webkit-progress-value,
|
||||
meter::-webkit-meter-optimum-value {
|
||||
background: linear-gradient(${clr2}, ${clr1} 20%, ${clr} 45%, ${clr} 55%, ${clr2})
|
||||
}
|
||||
|
||||
::-webkit-progress-value:active,
|
||||
meter::-webkit-meter-optimum-value:active {
|
||||
background: linear-gradient(${clr}, ${clr2} 20%, ${metersActiveColor} 45%, ${metersActiveColor} 55%, ${clr})
|
||||
}
|
||||
`;
|
||||
document.body.appendChild(node);
|
||||
};
|
||||
|
||||
/** UTILITIES */
|
||||
|
||||
/*
|
||||
Quick utility to lighten or darken a color (doesn't take color-drifting, etc. into account)
|
||||
Usage:
|
||||
fadeColor('#061261', 100); // will lighten the color
|
||||
fadeColor('#200867'), -100); // will darken the color
|
||||
*/
|
||||
function fadeColor(col, amt) {
|
||||
const min = Math.min, max = Math.max;
|
||||
const num = parseInt(col.replace(/#/g, ''), 16);
|
||||
const r = min(255, max((num >> 16) + amt, 0));
|
||||
const g = min(255, max((num & 0x0000FF) + amt, 0));
|
||||
const b = min(255, max(((num >> 8) & 0x00FF) + amt, 0));
|
||||
return '#' + (g | (b << 8) | (r << 16)).toString(16).padStart(6, 0);
|
||||
}
|
@ -30,10 +30,10 @@
|
||||
}
|
||||
],
|
||||
"Author": "dlprows",
|
||||
"Name": "FocusVolumeControl",
|
||||
"Name": "Focused Application Volume",
|
||||
"Description": "Control the volume of the focused application",
|
||||
"URL": "https://github.com/dlprows/FocusVolumeControl",
|
||||
"Version": "1.0.0",
|
||||
"Version": "1.1.0",
|
||||
"CodePath": "FocusVolumeControl",
|
||||
"Category": "Volume Control [dlprows]",
|
||||
"Icon": "Images/pluginIcon",
|
||||
|
Reference in New Issue
Block a user