From dc0e761244b4be9d94a8c168ac75ef1398db567e Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Sun, 21 Feb 2021 14:04:24 +0100 Subject: [PATCH 01/10] Implemented basic plugin architecture Moving interface to WPF for easier integration --- .../Actions/DeviceVolumeAction.cs | 56 ++++++ .../MassiveKnob.Plugin.CoreAudio.csproj | 74 +++++++ .../MassiveKnobCoreAudioPlugin.cs | 21 ++ .../Properties/AssemblyInfo.cs | 36 ++++ .../Settings/DeviceVolumeActionSettings.xaml | 12 ++ .../DeviceVolumeActionSettings.xaml.cs | 28 +++ .../Devices/MockDevice.cs | 45 +++++ .../MassiveKnob.Plugin.MockDevice.csproj | 69 +++++++ .../MassiveKnobMockDevicePlugin.cs | 20 ++ .../Properties/AssemblyInfo.cs | 35 ++++ .../Settings/MockDeviceSettings.xaml | 24 +++ .../Settings/MockDeviceSettings.xaml.cs | 28 +++ .../MassiveKnob.Plugin/IMassiveKnobAction.cs | 59 ++++++ .../IMassiveKnobActionContext.cs | 12 ++ .../IMassiveKnobActionInstance.cs | 18 ++ .../IMassiveKnobActionPlugin.cs | 15 ++ .../IMassiveKnobAnalogAction.cs | 16 ++ .../MassiveKnob.Plugin/IMassiveKnobContext.cs | 22 +++ .../MassiveKnob.Plugin/IMassiveKnobDevice.cs | 31 +++ .../IMassiveKnobDeviceInstance.cs | 17 ++ .../IMassiveKnobDigitalAction.cs | 9 + .../IMassiveKnobHardwarePlugin.cs | 15 ++ .../MassiveKnob.Plugin/IMassiveKnobPlugin.cs | 36 ++++ .../MassiveKnob.Plugin.csproj | 68 +++++++ .../MassiveKnobPluginAttribute.cs | 17 ++ .../Properties/AssemblyInfo.cs | 36 ++++ .../ReSharper/JetBrains.Annotations.cs | 180 ++++++++++++++++++ Windows/MassiveKnob.sln | 20 +- Windows/{ => MassiveKnob}/App.config | 0 Windows/MassiveKnob/App.xaml | 26 +++ Windows/MassiveKnob/App.xaml.cs | 75 ++++++++ .../Forms/SettingsForm.Designer.cs | 0 .../{ => MassiveKnob}/Forms/SettingsForm.cs | 0 .../{ => MassiveKnob}/Forms/SettingsForm.resx | 0 .../Hardware/AbstractMassiveKnobHardware.cs | 0 .../Hardware/CoreAudioDeviceManager.cs | 0 .../Hardware/IAudioDeviceManager.cs | 0 .../Hardware/IMassiveKnobHardware.cs | 0 .../Hardware/MockMassiveKnobHardware.cs | 0 .../Hardware/SerialMassiveKnobHardware.cs | 0 .../Helpers/ComboBoxTemplateSelector.cs | 54 ++++++ .../MassiveKnob/Helpers/DelegateCommand.cs | 80 ++++++++ Windows/{ => MassiveKnob}/MainIcon.ico | Bin Windows/{ => MassiveKnob}/MassiveKnob.csproj | 79 ++++---- .../Model/IMassiveKnobOrchestrator.cs | 11 ++ Windows/MassiveKnob/Model/IPluginManager.cs | 12 ++ .../Model/MassiveKnobOrchestrator.cs | 52 +++++ Windows/MassiveKnob/Model/PluginManager.cs | 79 ++++++++ Windows/MassiveKnob/Program.cs | 46 +++++ .../Properties/AssemblyInfo.cs | 0 Windows/{ => MassiveKnob}/Resources/Icon.ai | 0 .../{ => MassiveKnob}/Resources/MainIcon.ico | Bin .../Resources/NotifyIcon.ico | Bin Windows/MassiveKnob/Settings/Settings.cs | 38 ++++ .../Settings/SettingsJsonSerializer.cs | 48 +---- Windows/{ => MassiveKnob}/Strings.Designer.cs | 0 Windows/{ => MassiveKnob}/Strings.resx | 0 Windows/MassiveKnob/Style.xaml | 22 +++ .../KnobDeviceControl.Designer.cs | 0 .../UserControls/KnobDeviceControl.cs | 0 Windows/MassiveKnob/View/SettingsWindow.xaml | 45 +++++ .../MassiveKnob/View/SettingsWindow.xaml.cs | 16 ++ .../ViewModel/SettingsViewModel.cs | 109 +++++++++++ Windows/Program.cs | 44 ----- Windows/Properties/Resources.Designer.cs | 70 ------- Windows/Properties/Resources.resx | 117 ------------ Windows/Properties/Settings.Designer.cs | 29 --- Windows/Properties/Settings.settings | 7 - Windows/Settings/Settings.cs | 26 --- 69 files changed, 1730 insertions(+), 374 deletions(-) create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Actions/DeviceVolumeAction.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobCoreAudioPlugin.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Properties/AssemblyInfo.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.xaml create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.MockDevice/Devices/MockDevice.cs create mode 100644 Windows/MassiveKnob.Plugin.MockDevice/MassiveKnob.Plugin.MockDevice.csproj create mode 100644 Windows/MassiveKnob.Plugin.MockDevice/MassiveKnobMockDevicePlugin.cs create mode 100644 Windows/MassiveKnob.Plugin.MockDevice/Properties/AssemblyInfo.cs create mode 100644 Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.xaml create mode 100644 Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin/IMassiveKnobAction.cs create mode 100644 Windows/MassiveKnob.Plugin/IMassiveKnobActionContext.cs create mode 100644 Windows/MassiveKnob.Plugin/IMassiveKnobActionInstance.cs create mode 100644 Windows/MassiveKnob.Plugin/IMassiveKnobActionPlugin.cs create mode 100644 Windows/MassiveKnob.Plugin/IMassiveKnobAnalogAction.cs create mode 100644 Windows/MassiveKnob.Plugin/IMassiveKnobContext.cs create mode 100644 Windows/MassiveKnob.Plugin/IMassiveKnobDevice.cs create mode 100644 Windows/MassiveKnob.Plugin/IMassiveKnobDeviceInstance.cs create mode 100644 Windows/MassiveKnob.Plugin/IMassiveKnobDigitalAction.cs create mode 100644 Windows/MassiveKnob.Plugin/IMassiveKnobHardwarePlugin.cs create mode 100644 Windows/MassiveKnob.Plugin/IMassiveKnobPlugin.cs create mode 100644 Windows/MassiveKnob.Plugin/MassiveKnob.Plugin.csproj create mode 100644 Windows/MassiveKnob.Plugin/MassiveKnobPluginAttribute.cs create mode 100644 Windows/MassiveKnob.Plugin/Properties/AssemblyInfo.cs create mode 100644 Windows/MassiveKnob.Plugin/ReSharper/JetBrains.Annotations.cs rename Windows/{ => MassiveKnob}/App.config (100%) create mode 100644 Windows/MassiveKnob/App.xaml create mode 100644 Windows/MassiveKnob/App.xaml.cs rename Windows/{ => MassiveKnob}/Forms/SettingsForm.Designer.cs (100%) rename Windows/{ => MassiveKnob}/Forms/SettingsForm.cs (100%) rename Windows/{ => MassiveKnob}/Forms/SettingsForm.resx (100%) rename Windows/{ => MassiveKnob}/Hardware/AbstractMassiveKnobHardware.cs (100%) rename Windows/{ => MassiveKnob}/Hardware/CoreAudioDeviceManager.cs (100%) rename Windows/{ => MassiveKnob}/Hardware/IAudioDeviceManager.cs (100%) rename Windows/{ => MassiveKnob}/Hardware/IMassiveKnobHardware.cs (100%) rename Windows/{ => MassiveKnob}/Hardware/MockMassiveKnobHardware.cs (100%) rename Windows/{ => MassiveKnob}/Hardware/SerialMassiveKnobHardware.cs (100%) create mode 100644 Windows/MassiveKnob/Helpers/ComboBoxTemplateSelector.cs create mode 100644 Windows/MassiveKnob/Helpers/DelegateCommand.cs rename Windows/{ => MassiveKnob}/MainIcon.ico (100%) rename Windows/{ => MassiveKnob}/MassiveKnob.csproj (72%) create mode 100644 Windows/MassiveKnob/Model/IMassiveKnobOrchestrator.cs create mode 100644 Windows/MassiveKnob/Model/IPluginManager.cs create mode 100644 Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs create mode 100644 Windows/MassiveKnob/Model/PluginManager.cs create mode 100644 Windows/MassiveKnob/Program.cs rename Windows/{ => MassiveKnob}/Properties/AssemblyInfo.cs (100%) rename Windows/{ => MassiveKnob}/Resources/Icon.ai (100%) rename Windows/{ => MassiveKnob}/Resources/MainIcon.ico (100%) rename Windows/{ => MassiveKnob}/Resources/NotifyIcon.ico (100%) create mode 100644 Windows/MassiveKnob/Settings/Settings.cs rename Windows/{ => MassiveKnob}/Settings/SettingsJsonSerializer.cs (53%) rename Windows/{ => MassiveKnob}/Strings.Designer.cs (100%) rename Windows/{ => MassiveKnob}/Strings.resx (100%) create mode 100644 Windows/MassiveKnob/Style.xaml rename Windows/{ => MassiveKnob}/UserControls/KnobDeviceControl.Designer.cs (100%) rename Windows/{ => MassiveKnob}/UserControls/KnobDeviceControl.cs (100%) create mode 100644 Windows/MassiveKnob/View/SettingsWindow.xaml create mode 100644 Windows/MassiveKnob/View/SettingsWindow.xaml.cs create mode 100644 Windows/MassiveKnob/ViewModel/SettingsViewModel.cs delete mode 100644 Windows/Program.cs delete mode 100644 Windows/Properties/Resources.Designer.cs delete mode 100644 Windows/Properties/Resources.resx delete mode 100644 Windows/Properties/Settings.Designer.cs delete mode 100644 Windows/Properties/Settings.settings delete mode 100644 Windows/Settings/Settings.cs diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Actions/DeviceVolumeAction.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Actions/DeviceVolumeAction.cs new file mode 100644 index 0000000..93829f5 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Actions/DeviceVolumeAction.cs @@ -0,0 +1,56 @@ +using System; +using System.Threading.Tasks; +using System.Windows.Controls; + +namespace MassiveKnob.Plugin.CoreAudio.Actions +{ + public class DeviceVolumeAction : IMassiveKnobAction + { + public Guid ActionId { get; } = new Guid("aabd329c-8be5-4d1e-90ab-5114143b21dd"); + public MassiveKnobActionType ActionType { get; } = MassiveKnobActionType.InputAnalog; + public string Name { get; } = "Set volume"; + public string Description { get; } = "Sets the volume for the selected device, regardless of the current default device."; + + + public IMassiveKnobActionInstance Create(IMassiveKnobActionContext context) + { + return new Instance(context); + } + + + private class Instance : IMassiveKnobAnalogAction + { + private readonly Settings settings; + + + public Instance(IMassiveKnobContext context) + { + settings = context.GetSettings(); + } + + + public void Dispose() + { + } + + + public UserControl CreateSettingsControl() + { + return null; + } + + + public ValueTask AnalogChanged(byte value) + { + // TODO set volume + return default; + } + } + + + private class Settings + { + + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj new file mode 100644 index 0000000..cb62ecd --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj @@ -0,0 +1,74 @@ + + + + + Debug + AnyCPU + {5BD5E2F2-9923-4F74-AC69-ACDA0B847937} + Library + Properties + MassiveKnob.Plugin.CoreAudio + MassiveKnob.Plugin.CoreAudio + v4.7.2 + 512 + true + + + true + full + false + $(localappdata)\MassiveKnob\Plugins\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + DeviceVolumeActionSettings.xaml + + + + + {a1298be4-1d23-416c-8c56-fc9264487a95} + MassiveKnob.Plugin + + + + + 4.5.4 + + + + + + Designer + MSBuild:Compile + + + + \ No newline at end of file diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobCoreAudioPlugin.cs b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobCoreAudioPlugin.cs new file mode 100644 index 0000000..1e8cc5e --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobCoreAudioPlugin.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using MassiveKnob.Plugin.CoreAudio.Actions; + +namespace MassiveKnob.Plugin.CoreAudio +{ + [MassiveKnobPlugin] + public class MassiveKnobCoreAudioPlugin : IMassiveKnobActionPlugin + { + public Guid PluginId { get; } = new Guid("eaa5d3f8-8f9b-4a4b-8e29-827228d23e95"); + public string Name { get; } = "Windows Core Audio"; + public string Description { get; } = "Included with Massive Knob by default. Provides volume control per device and default device switching."; + public string Author { get; } = "Mark van Renswoude "; + public string Url { get; } = "https://www.github.com/MvRens/MassiveKnob/"; + + public IEnumerable Actions { get; } = new IMassiveKnobAction[] + { + new DeviceVolumeAction() + }; + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Properties/AssemblyInfo.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b37ec34 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Properties/AssemblyInfo.cs @@ -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("MassiveKnob.Plugin.CoreAudio")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MassiveKnob.Plugin.CoreAudio")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[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("5bd5e2f2-9923-4f74-ac69-acda0b847937")] + +// 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")] diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.xaml b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.xaml new file mode 100644 index 0000000..86aa8c4 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.xaml.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.xaml.cs new file mode 100644 index 0000000..6c1ed5b --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +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 MassiveKnob.Plugin.CoreAudio.Settings +{ + /// + /// Interaction logic for DeviceVolumeActionSettings.xaml + /// + public partial class DeviceVolumeActionSettings : UserControl + { + public DeviceVolumeActionSettings() + { + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.MockDevice/Devices/MockDevice.cs b/Windows/MassiveKnob.Plugin.MockDevice/Devices/MockDevice.cs new file mode 100644 index 0000000..d402c4d --- /dev/null +++ b/Windows/MassiveKnob.Plugin.MockDevice/Devices/MockDevice.cs @@ -0,0 +1,45 @@ +using System; +using System.Windows.Controls; +using MassiveKnob.Plugin.MockDevice.Settings; + +namespace MassiveKnob.Plugin.MockDevice.Devices +{ + public class MockDevice : IMassiveKnobDevice + { + public Guid DeviceId { get; } = new Guid("e1a4977a-abf4-4c75-a17d-fd8d3a8451ff"); + public string Name { get; } = "Mock device"; + public string Description { get; } = "Emulates the actual device but does not communicate with anything."; + + public IMassiveKnobDeviceInstance Create(IMassiveKnobContext context) + { + return new Instance(context); + } + + + private class Instance : IMassiveKnobDeviceInstance + { + public Instance(IMassiveKnobContext context) + { + // TODO read settings + } + + + public void Dispose() + { + } + + + public UserControl CreateSettingsControl() + { + // TODO pass context + return new MockDeviceSettings(); + } + } + + + private class Settings + { + // TODO interval, etc. + } + } +} diff --git a/Windows/MassiveKnob.Plugin.MockDevice/MassiveKnob.Plugin.MockDevice.csproj b/Windows/MassiveKnob.Plugin.MockDevice/MassiveKnob.Plugin.MockDevice.csproj new file mode 100644 index 0000000..d9a3c45 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.MockDevice/MassiveKnob.Plugin.MockDevice.csproj @@ -0,0 +1,69 @@ + + + + + Debug + AnyCPU + {674DE974-B134-4DB5-BFAF-7BC3D05E16DE} + Library + Properties + MassiveKnob.Plugin.MockDevice + MassiveKnob.Plugin.MockDevice + v4.7.2 + 512 + true + + + true + full + false + $(localappdata)\MassiveKnob\Plugins\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + MockDeviceSettings.xaml + + + + + {A1298BE4-1D23-416C-8C56-FC9264487A95} + MassiveKnob.Plugin + + + + + + Designer + MSBuild:Compile + + + + \ No newline at end of file diff --git a/Windows/MassiveKnob.Plugin.MockDevice/MassiveKnobMockDevicePlugin.cs b/Windows/MassiveKnob.Plugin.MockDevice/MassiveKnobMockDevicePlugin.cs new file mode 100644 index 0000000..feb9eb5 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.MockDevice/MassiveKnobMockDevicePlugin.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace MassiveKnob.Plugin.MockDevice +{ + [MassiveKnobPlugin] + public class MassiveKnobMockDevicePlugin : IMassiveKnobDevicePlugin + { + public Guid PluginId { get; } = new Guid("85f04232-d70f-494c-94a2-41452591ffb3"); + public string Name { get; } = "Mock Device"; + public string Description { get; } = "Emulates the actual device but does not communicate with anything."; + public string Author { get; } = "Mark van Renswoude "; + public string Url { get; } = "https://www.github.com/MvRens/MassiveKnob/"; + + public IEnumerable Devices { get; } = new IMassiveKnobDevice[] + { + new Devices.MockDevice() + }; + } +} diff --git a/Windows/MassiveKnob.Plugin.MockDevice/Properties/AssemblyInfo.cs b/Windows/MassiveKnob.Plugin.MockDevice/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..63e0908 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.MockDevice/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +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("MassiveKnob.Plugin.MockDevice")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MassiveKnob.Plugin.MockDevice")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[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("674de974-b134-4db5-bfaf-7bc3d05e16de")] + +// 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")] diff --git a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.xaml b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.xaml new file mode 100644 index 0000000..15b1798 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + Number of knobs + + + Randomly change the volume + + diff --git a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.xaml.cs b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.xaml.cs new file mode 100644 index 0000000..843c196 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +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 MassiveKnob.Plugin.MockDevice.Settings +{ + /// + /// Interaction logic for MockDeviceSettings.xaml + /// + public partial class MockDeviceSettings : UserControl + { + public MockDeviceSettings() + { + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobAction.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobAction.cs new file mode 100644 index 0000000..ba3f3fc --- /dev/null +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobAction.cs @@ -0,0 +1,59 @@ +using System; + +namespace MassiveKnob.Plugin +{ + /// + /// Determines the type of control this action can be assigned to. + /// + [Flags] + public enum MassiveKnobActionType + { + /// + /// Can be assigned to a potentiometer. The action instance must implement IMassiveKnobAnalogAction. + /// + InputAnalog = 1 << 0, + + /// + /// Can be assigned to a button or switch. The action instance must implement IMassiveKnobDigitalAction. + /// + InputDigital = 1 << 1, + + /// + /// Can be assigned to an output, like an LED or relay. + /// + OutputSignal = 1 << 2 + } + + + /// + /// Provides information about an action which can be assigned to a knob or button. + /// + public interface IMassiveKnobAction + { + /// + /// Unique identifier for the action. + /// + Guid ActionId { get; } + + /// + /// Determines the type of control this action can be assigned to. + /// + MassiveKnobActionType ActionType { get; } + + /// + /// The name of the action as shown in the action list when assigning to a knob or button. + /// + string Name { get; } + + /// + /// A short description of the functionality provided by the action. + /// + string Description { get; } + + /// + /// Called when an action is bound to a knob or button to create an instance of the action. + /// + /// Provides an interface to the Massive Knob settings and device. Can be stored until the action instance is disposed. + IMassiveKnobActionInstance Create(IMassiveKnobActionContext context); + } +} diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobActionContext.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobActionContext.cs new file mode 100644 index 0000000..9f08a67 --- /dev/null +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobActionContext.cs @@ -0,0 +1,12 @@ +namespace MassiveKnob.Plugin +{ + /// + public interface IMassiveKnobActionContext : IMassiveKnobContext + { + /// + /// Sets the state of the signal. Only valid for OutputSignal action types, will raise an exception otherwise. + /// + /// Whether the signal is on or off. + void SetSignal(bool on); + } +} diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobActionInstance.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobActionInstance.cs new file mode 100644 index 0000000..3973f60 --- /dev/null +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobActionInstance.cs @@ -0,0 +1,18 @@ +using System; +using System.Windows.Controls; + +namespace MassiveKnob.Plugin +{ + /// + /// Base interface for actions assigned to a knob or button. Implement one of the descendant + /// interfaces to provide input or output. + /// + public interface IMassiveKnobActionInstance : IDisposable + { + /// + /// Called when an action should display it's settings. Assume the width is variable, height is + /// determined by the UserControl. Return null to indicate there are no settings for this action. + /// + UserControl CreateSettingsControl(); + } +} diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobActionPlugin.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobActionPlugin.cs new file mode 100644 index 0000000..255defc --- /dev/null +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobActionPlugin.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace MassiveKnob.Plugin +{ + /// + /// Implemented by plugins supporting actions which can be assigned to knobs or buttons. + /// + public interface IMassiveKnobActionPlugin : IMassiveKnobPlugin + { + /// + /// A list of actions supported by the plugin which can be assigned to knobs or buttons. + /// + IEnumerable Actions { get; } + } +} diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobAnalogAction.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobAnalogAction.cs new file mode 100644 index 0000000..15cfae1 --- /dev/null +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobAnalogAction.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; + +namespace MassiveKnob.Plugin +{ + /// + /// Required to be implemented for Action type InputAnalog. Receives an update when a knob's position changes. + /// + public interface IMassiveKnobAnalogAction : IMassiveKnobActionInstance + { + /// + /// Called when a knob's position changes. + /// + /// The new value. Range is 0 to 100. + ValueTask AnalogChanged(byte value); + } +} diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobContext.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobContext.cs new file mode 100644 index 0000000..fdecb2a --- /dev/null +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobContext.cs @@ -0,0 +1,22 @@ +namespace MassiveKnob.Plugin +{ + /// + /// Provides an interface to the Massive Knob settings and device. + /// + public interface IMassiveKnobContext + { + /// + /// Reads the settings for the current action instance. + /// + /// The class type to be deserialized using Newtonsoft.Json. + /// The deserialized class if settings were previously stored, or a new instance of the class otherwise. + T GetSettings() where T : class, new(); + + /// + /// Stores the settings for the current action instance. + /// + /// The class type to be serialized using Newtonsoft.Json. + /// The object to be serialized using Newtonsoft.Json. + void SetSettings(T settings) where T : class, new(); + } +} diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobDevice.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobDevice.cs new file mode 100644 index 0000000..f949076 --- /dev/null +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobDevice.cs @@ -0,0 +1,31 @@ +using System; + +namespace MassiveKnob.Plugin +{ + /// + /// Contains information about the device supported by this plugin. + /// + public interface IMassiveKnobDevice + { + /// + /// Unique identifier for the device. + /// + Guid DeviceId { get; } + + /// + /// The name of the action as shown in the action list when assigning to a knob or button. + /// + string Name { get; } + + /// + /// A short description of the functionality provided by the action. + /// + string Description { get; } + + /// + /// Called when the device is selected. + /// + /// Provides an interface to the Massive Knob settings and device. Can be stored until the device instance is disposed. + IMassiveKnobDeviceInstance Create(IMassiveKnobContext context); + } +} diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobDeviceInstance.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobDeviceInstance.cs new file mode 100644 index 0000000..d8307df --- /dev/null +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobDeviceInstance.cs @@ -0,0 +1,17 @@ +using System; +using System.Windows.Controls; + +namespace MassiveKnob.Plugin +{ + /// + /// Represents a connection to a Massive Knob device. + /// + public interface IMassiveKnobDeviceInstance : IDisposable + { + /// + /// Called when a device should display it's settings. Assume the width is variable, height is + /// determined by the UserControl. Return null to indicate there are no settings for this device. + /// + UserControl CreateSettingsControl(); + } +} diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobDigitalAction.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobDigitalAction.cs new file mode 100644 index 0000000..fb3992f --- /dev/null +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobDigitalAction.cs @@ -0,0 +1,9 @@ +namespace MassiveKnob.Plugin +{ + /// + /// Required to be implemented for Action type InputDigital. Receives an update when a knob's position changes. + /// + public interface IMassiveKnobDigitalAction : IMassiveKnobActionInstance + { + } +} diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobHardwarePlugin.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobHardwarePlugin.cs new file mode 100644 index 0000000..b20c651 --- /dev/null +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobHardwarePlugin.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace MassiveKnob.Plugin +{ + /// + /// Implemented by plugins which interface with Massive Knob device. + /// + public interface IMassiveKnobDevicePlugin : IMassiveKnobPlugin + { + /// + /// A list of devices supported by the plugin. + /// + IEnumerable Devices { get; } + } +} diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobPlugin.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobPlugin.cs new file mode 100644 index 0000000..d7b15a0 --- /dev/null +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobPlugin.cs @@ -0,0 +1,36 @@ +using System; + +namespace MassiveKnob.Plugin +{ + /// + /// Minimum required interface to implement for writing a Massive Knob plugin. + /// Implement one of the various descendant interfaces to provide more functionality. + /// + public interface IMassiveKnobPlugin + { + /// + /// Unique identifier for the plugin. + /// + Guid PluginId { get; } + + /// + /// The name of the plugin as shown in the plugin list. + /// + string Name { get; } + + /// + /// A short description of the functionality provided by the plugin. + /// + string Description { get; } + + /// + /// The name of the author(s) of the plugin. + /// + string Author { get; } + + /// + /// URL to the plugin's website. + /// + string Url { get; } + } +} diff --git a/Windows/MassiveKnob.Plugin/MassiveKnob.Plugin.csproj b/Windows/MassiveKnob.Plugin/MassiveKnob.Plugin.csproj new file mode 100644 index 0000000..8cdc5b2 --- /dev/null +++ b/Windows/MassiveKnob.Plugin/MassiveKnob.Plugin.csproj @@ -0,0 +1,68 @@ + + + + + Debug + AnyCPU + {A1298BE4-1D23-416C-8C56-FC9264487A95} + Library + Properties + MassiveKnob.Plugin + MassiveKnob.Plugin + v4.7.2 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + bin\Debug\MassiveKnob.Plugin.xml + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + bin\Release\MassiveKnob.Plugin.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.5.4 + + + + \ No newline at end of file diff --git a/Windows/MassiveKnob.Plugin/MassiveKnobPluginAttribute.cs b/Windows/MassiveKnob.Plugin/MassiveKnobPluginAttribute.cs new file mode 100644 index 0000000..17c16b3 --- /dev/null +++ b/Windows/MassiveKnob.Plugin/MassiveKnobPluginAttribute.cs @@ -0,0 +1,17 @@ +using System; +using JetBrains.Annotations; + +// ReSharper disable once UnusedMember.Global - public API + +namespace MassiveKnob.Plugin +{ + /// + /// Attach this attribute to a class to register it as a Massive Knob plugin. + /// The class must implement IMassiveKnobPlugin. + /// + [AttributeUsage(AttributeTargets.Class)] + [MeansImplicitUse] + public class MassiveKnobPluginAttribute : Attribute + { + } +} diff --git a/Windows/MassiveKnob.Plugin/Properties/AssemblyInfo.cs b/Windows/MassiveKnob.Plugin/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..dddcccd --- /dev/null +++ b/Windows/MassiveKnob.Plugin/Properties/AssemblyInfo.cs @@ -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("MassiveKnob.PluginHost")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MassiveKnob.PluginHost")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[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("a1298be4-1d23-416c-8c56-fc9264487a95")] + +// 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")] diff --git a/Windows/MassiveKnob.Plugin/ReSharper/JetBrains.Annotations.cs b/Windows/MassiveKnob.Plugin/ReSharper/JetBrains.Annotations.cs new file mode 100644 index 0000000..2790b2e --- /dev/null +++ b/Windows/MassiveKnob.Plugin/ReSharper/JetBrains.Annotations.cs @@ -0,0 +1,180 @@ +/* + * Stripped version of the ReSharper Annotations source. Enables hinting without referencing the + * ReSharper.Annotations NuGet package. + * + * If you need more annotations, this code was generated using + * ReSharper - Options - Code Annotations - Copy C# implementation to clipboard + */ + + +/* MIT License + +Copyright (c) 2016 JetBrains http://www.jetbrains.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ + +using System; +// ReSharper disable InheritdocConsiderUsage + +#pragma warning disable 1591 +// ReSharper disable UnusedMember.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable IntroduceOptionalParameters.Global +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable InconsistentNaming + +// ReSharper disable once CheckNamespace +namespace JetBrains.Annotations +{ + /// + /// Indicates that the value of the marked element could be null sometimes, + /// so the check for null is necessary before its usage. + /// + /// + /// [CanBeNull] object Test() => null; + /// + /// void UseTest() { + /// var p = Test(); + /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] + internal sealed class CanBeNullAttribute : Attribute { } + + /// + /// Indicates that the value of the marked element could never be null. + /// + /// + /// [NotNull] object Foo() { + /// return null; // Warning: Possible 'null' assignment + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)] + internal sealed class NotNullAttribute : Attribute { } + + /// + /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library), + /// so this symbol will not be marked as unused (as well as by other usage inspections). + /// + [AttributeUsage(AttributeTargets.All)] + internal sealed class UsedImplicitlyAttribute : Attribute + { + public UsedImplicitlyAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + + public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) { } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + public ImplicitUseKindFlags UseKindFlags { get; } + + public ImplicitUseTargetFlags TargetFlags { get; } + } + + /// + /// Should be used on attributes and causes ReSharper to not mark symbols marked with such attributes + /// as unused (as well as by other usage inspections) + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)] + internal sealed class MeansImplicitUseAttribute : Attribute + { + public MeansImplicitUseAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + + public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) { } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; private set; } + + [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + [Flags] + internal enum ImplicitUseKindFlags + { + Default = Access | Assign | InstantiatedWithFixedConstructorSignature, + /// Only entity marked with attribute considered used. + Access = 1, + /// Indicates implicit assignment to a member. + Assign = 2, + /// + /// Indicates implicit instantiation of a type with fixed constructor signature. + /// That means any unused constructor parameters won't be reported as such. + /// + InstantiatedWithFixedConstructorSignature = 4, + /// Indicates implicit instantiation of a type. + InstantiatedNoFixedConstructorSignature = 8 + } + + /// + /// Specify what is considered used implicitly when marked + /// with or . + /// + [Flags] + internal enum ImplicitUseTargetFlags + { + Default = Itself, + Itself = 1, + /// Members of entity marked with attribute are considered used. + Members = 2, + /// Entity marked with attribute and all its members considered used. + WithMembers = Itself | Members + } + + /// + /// This attribute is intended to mark publicly available API + /// which should not be removed and so is treated as used. + /// + [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)] + internal sealed class PublicAPIAttribute : Attribute + { + public PublicAPIAttribute() { } + + public PublicAPIAttribute([NotNull] string comment) + { + Comment = comment; + } + + [CanBeNull] public string Comment { get; } + } +} \ No newline at end of file diff --git a/Windows/MassiveKnob.sln b/Windows/MassiveKnob.sln index 64ec8c6..85a3fb7 100644 --- a/Windows/MassiveKnob.sln +++ b/Windows/MassiveKnob.sln @@ -3,7 +3,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.31005.135 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob", "MassiveKnob.csproj", "{73130EC7-49B3-40AD-8367-1095C0F41905}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob", "MassiveKnob\MassiveKnob.csproj", "{73130EC7-49B3-40AD-8367-1095C0F41905}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin", "MassiveKnob.Plugin\MassiveKnob.Plugin.csproj", "{A1298BE4-1D23-416C-8C56-FC9264487A95}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin.CoreAudio", "MassiveKnob.Plugin.CoreAudio\MassiveKnob.Plugin.CoreAudio.csproj", "{5BD5E2F2-9923-4F74-AC69-ACDA0B847937}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin.MockDevice", "MassiveKnob.Plugin.MockDevice\MassiveKnob.Plugin.MockDevice.csproj", "{674DE974-B134-4DB5-BFAF-7BC3D05E16DE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +21,18 @@ Global {73130EC7-49B3-40AD-8367-1095C0F41905}.Debug|Any CPU.Build.0 = Debug|Any CPU {73130EC7-49B3-40AD-8367-1095C0F41905}.Release|Any CPU.ActiveCfg = Release|Any CPU {73130EC7-49B3-40AD-8367-1095C0F41905}.Release|Any CPU.Build.0 = Release|Any CPU + {A1298BE4-1D23-416C-8C56-FC9264487A95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1298BE4-1D23-416C-8C56-FC9264487A95}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1298BE4-1D23-416C-8C56-FC9264487A95}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1298BE4-1D23-416C-8C56-FC9264487A95}.Release|Any CPU.Build.0 = Release|Any CPU + {5BD5E2F2-9923-4F74-AC69-ACDA0B847937}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5BD5E2F2-9923-4F74-AC69-ACDA0B847937}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5BD5E2F2-9923-4F74-AC69-ACDA0B847937}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5BD5E2F2-9923-4F74-AC69-ACDA0B847937}.Release|Any CPU.Build.0 = Release|Any CPU + {674DE974-B134-4DB5-BFAF-7BC3D05E16DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {674DE974-B134-4DB5-BFAF-7BC3D05E16DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {674DE974-B134-4DB5-BFAF-7BC3D05E16DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {674DE974-B134-4DB5-BFAF-7BC3D05E16DE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Windows/App.config b/Windows/MassiveKnob/App.config similarity index 100% rename from Windows/App.config rename to Windows/MassiveKnob/App.config diff --git a/Windows/MassiveKnob/App.xaml b/Windows/MassiveKnob/App.xaml new file mode 100644 index 0000000..5a65864 --- /dev/null +++ b/Windows/MassiveKnob/App.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Windows/MassiveKnob/App.xaml.cs b/Windows/MassiveKnob/App.xaml.cs new file mode 100644 index 0000000..5192f72 --- /dev/null +++ b/Windows/MassiveKnob/App.xaml.cs @@ -0,0 +1,75 @@ +using System.Diagnostics; +using System.Windows; +using Hardcodet.Wpf.TaskbarNotification; +using MassiveKnob.View; +using SimpleInjector; + +namespace MassiveKnob +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App + { + private readonly Container container; + private TaskbarIcon notifyIcon; + + private SettingsWindow settingsWindow; + + + public App(Container container) + { + this.container = container; + InitializeComponent(); + } + + + protected override void OnStartup(StartupEventArgs e) + { + base.OnStartup(e); + + notifyIcon = (TaskbarIcon)FindResource("NotifyIcon"); + Debug.Assert(notifyIcon != null, nameof(notifyIcon) + " != null"); + } + + + protected override void OnExit(ExitEventArgs e) + { + notifyIcon?.Dispose(); + + base.OnExit(e); + } + + + + private void ShowSettings() + { + if (settingsWindow == null) + { + settingsWindow = container.GetInstance(); + settingsWindow.Closed += (sender, args) => { settingsWindow = null; }; + settingsWindow.Show(); + } + else + settingsWindow.Activate(); + } + + + private void NotifyIconMenuSettingsClick(object sender, RoutedEventArgs e) + { + ShowSettings(); + } + + + private void NotifyIconTrayMouseDoubleClick(object sender, RoutedEventArgs e) + { + ShowSettings(); + } + + + private void NotifyIconMenuQuitClick(object sender, RoutedEventArgs e) + { + Shutdown(); + } + } +} \ No newline at end of file diff --git a/Windows/Forms/SettingsForm.Designer.cs b/Windows/MassiveKnob/Forms/SettingsForm.Designer.cs similarity index 100% rename from Windows/Forms/SettingsForm.Designer.cs rename to Windows/MassiveKnob/Forms/SettingsForm.Designer.cs diff --git a/Windows/Forms/SettingsForm.cs b/Windows/MassiveKnob/Forms/SettingsForm.cs similarity index 100% rename from Windows/Forms/SettingsForm.cs rename to Windows/MassiveKnob/Forms/SettingsForm.cs diff --git a/Windows/Forms/SettingsForm.resx b/Windows/MassiveKnob/Forms/SettingsForm.resx similarity index 100% rename from Windows/Forms/SettingsForm.resx rename to Windows/MassiveKnob/Forms/SettingsForm.resx diff --git a/Windows/Hardware/AbstractMassiveKnobHardware.cs b/Windows/MassiveKnob/Hardware/AbstractMassiveKnobHardware.cs similarity index 100% rename from Windows/Hardware/AbstractMassiveKnobHardware.cs rename to Windows/MassiveKnob/Hardware/AbstractMassiveKnobHardware.cs diff --git a/Windows/Hardware/CoreAudioDeviceManager.cs b/Windows/MassiveKnob/Hardware/CoreAudioDeviceManager.cs similarity index 100% rename from Windows/Hardware/CoreAudioDeviceManager.cs rename to Windows/MassiveKnob/Hardware/CoreAudioDeviceManager.cs diff --git a/Windows/Hardware/IAudioDeviceManager.cs b/Windows/MassiveKnob/Hardware/IAudioDeviceManager.cs similarity index 100% rename from Windows/Hardware/IAudioDeviceManager.cs rename to Windows/MassiveKnob/Hardware/IAudioDeviceManager.cs diff --git a/Windows/Hardware/IMassiveKnobHardware.cs b/Windows/MassiveKnob/Hardware/IMassiveKnobHardware.cs similarity index 100% rename from Windows/Hardware/IMassiveKnobHardware.cs rename to Windows/MassiveKnob/Hardware/IMassiveKnobHardware.cs diff --git a/Windows/Hardware/MockMassiveKnobHardware.cs b/Windows/MassiveKnob/Hardware/MockMassiveKnobHardware.cs similarity index 100% rename from Windows/Hardware/MockMassiveKnobHardware.cs rename to Windows/MassiveKnob/Hardware/MockMassiveKnobHardware.cs diff --git a/Windows/Hardware/SerialMassiveKnobHardware.cs b/Windows/MassiveKnob/Hardware/SerialMassiveKnobHardware.cs similarity index 100% rename from Windows/Hardware/SerialMassiveKnobHardware.cs rename to Windows/MassiveKnob/Hardware/SerialMassiveKnobHardware.cs diff --git a/Windows/MassiveKnob/Helpers/ComboBoxTemplateSelector.cs b/Windows/MassiveKnob/Helpers/ComboBoxTemplateSelector.cs new file mode 100644 index 0000000..adda8da --- /dev/null +++ b/Windows/MassiveKnob/Helpers/ComboBoxTemplateSelector.cs @@ -0,0 +1,54 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Markup; +using System.Windows.Media; + +namespace MassiveKnob.Helpers +{ + // Source: https://stackoverflow.com/questions/4672867/can-i-use-a-different-template-for-the-selected-item-in-a-wpf-combobox-than-for + public class ComboBoxTemplateSelector : DataTemplateSelector + { + public DataTemplate SelectedItemTemplate { get; set; } + public DataTemplateSelector SelectedItemTemplateSelector { get; set; } + public DataTemplate DropdownItemsTemplate { get; set; } + public DataTemplateSelector DropdownItemsTemplateSelector { get; set; } + + public override DataTemplate SelectTemplate(object item, DependencyObject container) + { + var itemToCheck = container; + + // Search up the visual tree, stopping at either a ComboBox or + // a ComboBoxItem (or null). This will determine which template to use + while (itemToCheck != null && !(itemToCheck is ComboBoxItem) && !(itemToCheck is ComboBox)) + itemToCheck = VisualTreeHelper.GetParent(itemToCheck); + + // If you stopped at a ComboBoxItem, you're in the dropdown + var inDropDown = (itemToCheck is ComboBoxItem); + + return inDropDown + ? DropdownItemsTemplate ?? DropdownItemsTemplateSelector?.SelectTemplate(item, container) + : SelectedItemTemplate ?? SelectedItemTemplateSelector?.SelectTemplate(item, container); + } + } + + + public class ComboBoxTemplateSelectorExtension : MarkupExtension + { + public DataTemplate SelectedItemTemplate { get; set; } + public DataTemplateSelector SelectedItemTemplateSelector { get; set; } + public DataTemplate DropdownItemsTemplate { get; set; } + public DataTemplateSelector DropdownItemsTemplateSelector { get; set; } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return new ComboBoxTemplateSelector() + { + SelectedItemTemplate = SelectedItemTemplate, + SelectedItemTemplateSelector = SelectedItemTemplateSelector, + DropdownItemsTemplate = DropdownItemsTemplate, + DropdownItemsTemplateSelector = DropdownItemsTemplateSelector + }; + } + } +} diff --git a/Windows/MassiveKnob/Helpers/DelegateCommand.cs b/Windows/MassiveKnob/Helpers/DelegateCommand.cs new file mode 100644 index 0000000..e895714 --- /dev/null +++ b/Windows/MassiveKnob/Helpers/DelegateCommand.cs @@ -0,0 +1,80 @@ +using System; +using System.Windows.Input; + +namespace MassiveKnob.Helpers +{ + public class DelegateCommand : ICommand + { + private readonly Action execute; + private readonly Func canExecute; + + + public DelegateCommand(Action execute) : this(execute, null) + { + } + + + public DelegateCommand(Action execute, Func canExecute) + { + this.execute = execute ?? throw new ArgumentNullException(nameof(execute)); + this.canExecute = canExecute; + } + + + public bool CanExecute(object parameter) + { + return canExecute?.Invoke() ?? true; + } + + + public void Execute(object parameter) + { + execute(); + } + + + public event EventHandler CanExecuteChanged + { + add => CommandManager.RequerySuggested += value; + remove => CommandManager.RequerySuggested -= value; + } + } + + + public class DelegateCommand : ICommand + { + private readonly Action execute; + private readonly Predicate canExecute; + + + public DelegateCommand(Action execute) : this(execute, null) + { + } + + + public DelegateCommand(Action execute, Predicate canExecute) + { + this.execute = execute ?? throw new ArgumentNullException(nameof(execute)); + this.canExecute = canExecute; + } + + + public bool CanExecute(object parameter) + { + return canExecute?.Invoke((T)parameter) ?? true; + } + + + public void Execute(object parameter) + { + execute((T)parameter); + } + + + public event EventHandler CanExecuteChanged + { + add => CommandManager.RequerySuggested += value; + remove => CommandManager.RequerySuggested -= value; + } + } +} diff --git a/Windows/MainIcon.ico b/Windows/MassiveKnob/MainIcon.ico similarity index 100% rename from Windows/MainIcon.ico rename to Windows/MassiveKnob/MainIcon.ico diff --git a/Windows/MassiveKnob.csproj b/Windows/MassiveKnob/MassiveKnob.csproj similarity index 72% rename from Windows/MassiveKnob.csproj rename to Windows/MassiveKnob/MassiveKnob.csproj index 7655aa1..fb0171e 100644 --- a/Windows/MassiveKnob.csproj +++ b/Windows/MassiveKnob/MassiveKnob.csproj @@ -10,6 +10,7 @@ MassiveKnob v4.7.2 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} true true @@ -41,8 +42,11 @@ MassiveKnob.Program + + + @@ -50,22 +54,26 @@ - + - - Form - - - SettingsForm.cs - + + + + + + + + + SettingsWindow.xaml + @@ -75,37 +83,10 @@ True Strings.resx - - UserControl - - - KnobDeviceControl.cs - - - SettingsForm.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - ResXFileCodeGenerator Strings.Designer.cs - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - @@ -123,6 +104,9 @@ 0.11.24 + + 1.0.8 + 12.0.3 @@ -135,6 +119,33 @@ + + + + + Designer + MSBuild:Compile + + + App.xaml + Code + + + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + + {A1298BE4-1D23-416C-8C56-FC9264487A95} + MassiveKnob.Plugin + diff --git a/Windows/MassiveKnob/Model/IMassiveKnobOrchestrator.cs b/Windows/MassiveKnob/Model/IMassiveKnobOrchestrator.cs new file mode 100644 index 0000000..a818b26 --- /dev/null +++ b/Windows/MassiveKnob/Model/IMassiveKnobOrchestrator.cs @@ -0,0 +1,11 @@ +using MassiveKnob.Plugin; + +namespace MassiveKnob.Model +{ + public interface IMassiveKnobOrchestrator + { + IMassiveKnobDeviceInstance ActiveDeviceInstance { get; } + + IMassiveKnobDeviceInstance SetActiveDevice(IMassiveKnobDevice device); + } +} diff --git a/Windows/MassiveKnob/Model/IPluginManager.cs b/Windows/MassiveKnob/Model/IPluginManager.cs new file mode 100644 index 0000000..87e185d --- /dev/null +++ b/Windows/MassiveKnob/Model/IPluginManager.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using MassiveKnob.Plugin; + +namespace MassiveKnob.Model +{ + public interface IPluginManager + { + IEnumerable Plugins { get; } + + IEnumerable GetDevicePlugins(); + } +} diff --git a/Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs b/Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs new file mode 100644 index 0000000..87e2de8 --- /dev/null +++ b/Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs @@ -0,0 +1,52 @@ +using MassiveKnob.Plugin; + +namespace MassiveKnob.Model +{ + public class MassiveKnobOrchestrator : IMassiveKnobOrchestrator + { + private readonly Settings.Settings settings; + + + public IMassiveKnobDeviceInstance ActiveDeviceInstance { get; private set; } + + + public MassiveKnobOrchestrator(Settings.Settings settings) + { + this.settings = settings; + } + + + public IMassiveKnobDeviceInstance SetActiveDevice(IMassiveKnobDevice device) + { + ActiveDeviceInstance?.Dispose(); + ActiveDeviceInstance = device?.Create(new Context(settings)); + + return ActiveDeviceInstance; + } + + + + public class Context : IMassiveKnobContext + { + private readonly Settings.Settings settings; + + + public Context(Settings.Settings settings) + { + this.settings = settings; + } + + public T GetSettings() where T : class, new() + { + // TODO + return default; + } + + + public void SetSettings(T settings) where T : class, new() + { + // TODO + } + } + } +} diff --git a/Windows/MassiveKnob/Model/PluginManager.cs b/Windows/MassiveKnob/Model/PluginManager.cs new file mode 100644 index 0000000..48fec68 --- /dev/null +++ b/Windows/MassiveKnob/Model/PluginManager.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using MassiveKnob.Plugin; + +namespace MassiveKnob.Model +{ + public class PluginManager : IPluginManager + { + private readonly List plugins = new List(); + + + public IEnumerable Plugins => plugins; + + public IEnumerable GetDevicePlugins() + { + return plugins.Where(p => p is IMassiveKnobDevicePlugin).Cast(); + } + + + public void Load() + { + var codeBase = Assembly.GetEntryAssembly()?.CodeBase; + if (!string.IsNullOrEmpty(codeBase)) + { + var localPath = Path.GetDirectoryName(new Uri(codeBase).LocalPath); + if (!string.IsNullOrEmpty(localPath)) + { + var applicationPluginPath = Path.Combine(localPath, @"Plugins"); + LoadPlugins(applicationPluginPath); + } + } + + + var localPluginPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob", @"Plugins"); + LoadPlugins(localPluginPath); + } + + + private void LoadPlugins(string path) + { + if (!Directory.Exists(path)) + return; + + var filenames = Directory.GetFiles(path, "*.dll"); + + foreach (var filename in filenames) + { + try + { + var pluginAssembly = Assembly.LoadFrom(filename); + RegisterPlugins(pluginAssembly); + } + catch (Exception e) + { + // TODO report error +// Console.WriteLine(e); + throw; + } + } + } + + + private void RegisterPlugins(Assembly assembly) + { + var pluginTypes = assembly.GetTypes().Where(t => t.GetCustomAttribute() != null); + foreach (var pluginType in pluginTypes) + { + var pluginInstance = Activator.CreateInstance(pluginType); + if (!(pluginInstance is IMassiveKnobPlugin)) + throw new InvalidCastException($"Type {pluginType.FullName} claims to be a MassiveKnobPlugin but does not implement IMassiveKnobPlugin"); + + plugins.Add((IMassiveKnobPlugin)pluginInstance); + } + } + } +} diff --git a/Windows/MassiveKnob/Program.cs b/Windows/MassiveKnob/Program.cs new file mode 100644 index 0000000..f28ec47 --- /dev/null +++ b/Windows/MassiveKnob/Program.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading.Tasks; +using MassiveKnob.Model; +using MassiveKnob.Settings; +using MassiveKnob.View; +using MassiveKnob.ViewModel; +using SimpleInjector; + +namespace MassiveKnob +{ + public static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + public static void Main() + { + MainAsync().GetAwaiter().GetResult(); + } + + + private static async Task MainAsync() + { + var container = new Container(); + container.Options.EnableAutoVerification = false; + + container.RegisterSingleton(); + + container.Register(); + container.Register(); + container.Register(); + + var settings = await SettingsJsonSerializer.Deserialize(); + container.RegisterInstance(settings); + + var pluginManager = new PluginManager(); + pluginManager.Load(); + container.RegisterInstance(pluginManager); + + + var app = container.GetInstance(); + app.Run(); + } + } +} diff --git a/Windows/Properties/AssemblyInfo.cs b/Windows/MassiveKnob/Properties/AssemblyInfo.cs similarity index 100% rename from Windows/Properties/AssemblyInfo.cs rename to Windows/MassiveKnob/Properties/AssemblyInfo.cs diff --git a/Windows/Resources/Icon.ai b/Windows/MassiveKnob/Resources/Icon.ai similarity index 100% rename from Windows/Resources/Icon.ai rename to Windows/MassiveKnob/Resources/Icon.ai diff --git a/Windows/Resources/MainIcon.ico b/Windows/MassiveKnob/Resources/MainIcon.ico similarity index 100% rename from Windows/Resources/MainIcon.ico rename to Windows/MassiveKnob/Resources/MainIcon.ico diff --git a/Windows/Resources/NotifyIcon.ico b/Windows/MassiveKnob/Resources/NotifyIcon.ico similarity index 100% rename from Windows/Resources/NotifyIcon.ico rename to Windows/MassiveKnob/Resources/NotifyIcon.ico diff --git a/Windows/MassiveKnob/Settings/Settings.cs b/Windows/MassiveKnob/Settings/Settings.cs new file mode 100644 index 0000000..2ac71c3 --- /dev/null +++ b/Windows/MassiveKnob/Settings/Settings.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; + +namespace MassiveKnob.Settings +{ + public class Settings + { + public DeviceSettings Device { get; set; } + public List Actions { get; set; } + + + public static Settings Default() + { + return new Settings + { + Device = null, + Actions = new List() + }; + } + + + public class DeviceSettings + { + public Guid? PluginId { get; set; } + public Guid? DeviceId { get; set; } + public JObject Settings { get; set; } + } + + + public class ActionSettings + { + public Guid PluginId { get; set; } + public Guid ActionId { get; set; } + public JObject Settings { get; set; } + } + } +} diff --git a/Windows/Settings/SettingsJsonSerializer.cs b/Windows/MassiveKnob/Settings/SettingsJsonSerializer.cs similarity index 53% rename from Windows/Settings/SettingsJsonSerializer.cs rename to Windows/MassiveKnob/Settings/SettingsJsonSerializer.cs index d696165..dddc346 100644 --- a/Windows/Settings/SettingsJsonSerializer.cs +++ b/Windows/MassiveKnob/Settings/SettingsJsonSerializer.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; @@ -25,8 +24,7 @@ namespace MassiveKnob.Settings public static async Task Serialize(Settings settings, string filename) { - var serializedSettings = SerializedSettings.FromSettings(settings); - var json = JsonConvert.SerializeObject(serializedSettings); + var json = JsonConvert.SerializeObject(settings); using (var stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Read, 4096, true)) using (var streamWriter = new StreamWriter(stream, Encoding.UTF8)) @@ -58,49 +56,7 @@ namespace MassiveKnob.Settings if (string.IsNullOrEmpty(json)) return Settings.Default(); - var serializedSettings = JsonConvert.DeserializeObject(json); - return serializedSettings.ToSettings(); - } - - - private class SerializedSettings - { - // ReSharper disable MemberCanBePrivate.Local - used for JSON serialization - public string SerialPort; - public SerializedKnobSettings[] Knobs; - // ReSharper restore MemberCanBePrivate.Local - - - public static SerializedSettings FromSettings(Settings settings) - { - return new SerializedSettings - { - SerialPort = settings.SerialPort, - Knobs = settings.Knobs.Select(knob => new SerializedKnobSettings - { - DeviceId = knob.DeviceId - }).ToArray() - }; - } - - - public Settings ToSettings() - { - return new Settings - { - SerialPort = SerialPort, - Knobs = Knobs.Select(knob => new Settings.KnobSettings - { - DeviceId = knob.DeviceId - }).ToList() - }; - } - - - public class SerializedKnobSettings - { - public Guid? DeviceId; - } + return JsonConvert.DeserializeObject(json); } } } diff --git a/Windows/Strings.Designer.cs b/Windows/MassiveKnob/Strings.Designer.cs similarity index 100% rename from Windows/Strings.Designer.cs rename to Windows/MassiveKnob/Strings.Designer.cs diff --git a/Windows/Strings.resx b/Windows/MassiveKnob/Strings.resx similarity index 100% rename from Windows/Strings.resx rename to Windows/MassiveKnob/Strings.resx diff --git a/Windows/MassiveKnob/Style.xaml b/Windows/MassiveKnob/Style.xaml new file mode 100644 index 0000000..443fe67 --- /dev/null +++ b/Windows/MassiveKnob/Style.xaml @@ -0,0 +1,22 @@ + + + + + + + + + \ No newline at end of file diff --git a/Windows/UserControls/KnobDeviceControl.Designer.cs b/Windows/MassiveKnob/UserControls/KnobDeviceControl.Designer.cs similarity index 100% rename from Windows/UserControls/KnobDeviceControl.Designer.cs rename to Windows/MassiveKnob/UserControls/KnobDeviceControl.Designer.cs diff --git a/Windows/UserControls/KnobDeviceControl.cs b/Windows/MassiveKnob/UserControls/KnobDeviceControl.cs similarity index 100% rename from Windows/UserControls/KnobDeviceControl.cs rename to Windows/MassiveKnob/UserControls/KnobDeviceControl.cs diff --git a/Windows/MassiveKnob/View/SettingsWindow.xaml b/Windows/MassiveKnob/View/SettingsWindow.xaml new file mode 100644 index 0000000..601f5e4 --- /dev/null +++ b/Windows/MassiveKnob/View/SettingsWindow.xaml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + Device + + + + + + + + + Controls + + + diff --git a/Windows/MassiveKnob/View/SettingsWindow.xaml.cs b/Windows/MassiveKnob/View/SettingsWindow.xaml.cs new file mode 100644 index 0000000..8d9ec31 --- /dev/null +++ b/Windows/MassiveKnob/View/SettingsWindow.xaml.cs @@ -0,0 +1,16 @@ +using MassiveKnob.ViewModel; + +namespace MassiveKnob.View +{ + /// + /// Interaction logic for SettingsWindow.xaml + /// + public partial class SettingsWindow + { + public SettingsWindow(SettingsViewModel settingsViewModel) + { + DataContext = settingsViewModel; + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs b/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs new file mode 100644 index 0000000..4764a9b --- /dev/null +++ b/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Windows.Controls; +using MassiveKnob.Model; +using MassiveKnob.Plugin; + +namespace MassiveKnob.ViewModel +{ + public class SettingsViewModel : INotifyPropertyChanged + { + private readonly Settings.Settings settings; + private readonly IMassiveKnobOrchestrator orchestrator; + private DeviceViewModel selectedDevice; + private UserControl settingsControl; + + + public IEnumerable Devices { get; } + public DeviceViewModel SelectedDevice + { + get => selectedDevice; + + set + { + if (value == selectedDevice) + return; + + selectedDevice = value; + var deviceInstance = orchestrator.SetActiveDevice(value?.Device); + + if (value == null) + settings.Device = null; + else + { + settings.Device = new Settings.Settings.DeviceSettings + { + PluginId = value.Plugin.PluginId, + DeviceId = value.Device.DeviceId, + Settings = null + }; + } + + OnPropertyChanged(); + + SettingsControl = deviceInstance?.CreateSettingsControl(); + } + } + + public UserControl SettingsControl + { + get => settingsControl; + + set + { + if (value == settingsControl) + return; + + settingsControl = value; + OnPropertyChanged(); + } + } + + + + + public SettingsViewModel(IPluginManager pluginManager, Settings.Settings settings, IMassiveKnobOrchestrator orchestrator) + { + this.settings = settings; + this.orchestrator = orchestrator; + + Devices = pluginManager.GetDevicePlugins().SelectMany(dp => dp.Devices.Select(d => new DeviceViewModel(dp, d))); + + if (settings.Device != null) + SelectedDevice = Devices.FirstOrDefault(d => + d.Plugin.PluginId == settings.Device.PluginId && + d.Device.DeviceId == settings.Device.DeviceId); + } + + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + + + public class DeviceViewModel + { + // ReSharper disable UnusedMember.Global - used by WPF Binding + public string Name => Device.Name; + public string Description => Device.Description; + // ReSharper restore UnusedMember.Global + + public IMassiveKnobDevicePlugin Plugin { get; } + public IMassiveKnobDevice Device { get; } + + + public DeviceViewModel(IMassiveKnobDevicePlugin plugin, IMassiveKnobDevice device) + { + Plugin = plugin; + Device = device; + } + } + } +} diff --git a/Windows/Program.cs b/Windows/Program.cs deleted file mode 100644 index 8f2f1f3..0000000 --- a/Windows/Program.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Windows.Forms; -using MassiveKnob.Forms; -using MassiveKnob.Hardware; -using SimpleInjector; -using SimpleInjector.Diagnostics; - -namespace MassiveKnob -{ - public static class Program - { - /// - /// The main entry point for the application. - /// - [STAThread] - public static void Main() - { - var container = BuildContainer(); - - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(container.GetInstance()); - } - - - private static Container BuildContainer() - { - var container = new Container(); - container.Options.EnableAutoVerification = false; - - container.Register(); - container.GetRegistration(typeof(SettingsForm))?.Registration - .SuppressDiagnosticWarning(DiagnosticType.DisposableTransientComponent, "Windows Form implements IDisposable"); - - container.Register(); - - // For testing without the hardware: - container.Register(); - //container.Register(() => new MockMassiveKnobHardwareFactory(3, TimeSpan.FromSeconds(1), 25)); - - return container; - } - } -} diff --git a/Windows/Properties/Resources.Designer.cs b/Windows/Properties/Resources.Designer.cs deleted file mode 100644 index a1a7364..0000000 --- a/Windows/Properties/Resources.Designer.cs +++ /dev/null @@ -1,70 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - - -namespace MassiveKnob.Properties -{ - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if ((resourceMan == null)) - { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MassiveKnob.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { - return resourceCulture; - } - set - { - resourceCulture = value; - } - } - } -} diff --git a/Windows/Properties/Resources.resx b/Windows/Properties/Resources.resx deleted file mode 100644 index af7dbeb..0000000 --- a/Windows/Properties/Resources.resx +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/Windows/Properties/Settings.Designer.cs b/Windows/Properties/Settings.Designer.cs deleted file mode 100644 index 28fa051..0000000 --- a/Windows/Properties/Settings.Designer.cs +++ /dev/null @@ -1,29 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - - -namespace MassiveKnob.Properties -{ - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { - return defaultInstance; - } - } - } -} diff --git a/Windows/Properties/Settings.settings b/Windows/Properties/Settings.settings deleted file mode 100644 index 3964565..0000000 --- a/Windows/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/Windows/Settings/Settings.cs b/Windows/Settings/Settings.cs deleted file mode 100644 index ddfc593..0000000 --- a/Windows/Settings/Settings.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace MassiveKnob.Settings -{ - public class Settings - { - public string SerialPort { get; set; } - public List Knobs { get; set; } - - - public static Settings Default() - { - return new Settings - { - Knobs = new List() - }; - } - - - public class KnobSettings - { - public Guid? DeviceId { get; set; } - } - } -} From ff1e1ca74c16cb9f429b345d5885118c5198045a Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Wed, 24 Feb 2021 09:05:11 +0100 Subject: [PATCH 02/10] Working proof-of-concept for refactoring Implemented orchestrator Implemented mock device Implemented serial device Implemented volume action Removed old implementation --- Arduino/MassiveKnob/MassiveKnob.ino | 44 +- .../Actions/DeviceVolumeAction.cs | 57 +- .../CoreAudioControllerInstance.cs | 16 + .../MassiveKnob.Plugin.CoreAudio.csproj | 26 +- .../MassiveKnobCoreAudioPlugin.cs | 13 + .../Properties/AssemblyInfo.cs | 1 - .../Settings/BaseDeviceSettings.cs | 9 + .../Settings/BaseDeviceSettingsViewModel.cs | 110 ++++ .../Settings/DeviceVolumeActionSettings.cs | 7 + .../Settings/DeviceVolumeActionSettings.xaml | 12 - .../DeviceVolumeActionSettings.xaml.cs | 28 - .../DeviceVolumeActionSettingsView.xaml | 14 + .../DeviceVolumeActionSettingsView.xaml.cs | 14 + .../DeviceVolumeActionSettingsViewModel.cs | 10 + .../Strings.Designer.cs | 108 ++++ .../MassiveKnob.Plugin.CoreAudio/Strings.resx | 135 +++++ .../Devices/MockDevice.cs | 76 ++- .../MassiveKnob.Plugin.MockDevice.csproj | 8 +- .../Settings/MockDeviceSettings.cs | 9 + .../Settings/MockDeviceSettings.xaml | 24 - .../Settings/MockDeviceSettings.xaml.cs | 28 - .../Settings/MockDeviceSettingsView.xaml | 30 + .../Settings/MockDeviceSettingsView.xaml.cs | 15 + .../Settings/MockDeviceSettingsViewModel.cs | 67 +++ .../Devices/SerialDevice.cs | 64 ++ .../MassiveKnob.Plugin.SerialDevice.csproj | 71 +++ .../MassiveKnobSerialDevicePlugin.cs | 20 + .../Properties/AssemblyInfo.cs | 35 ++ .../Settings/SerialDeviceSettings.cs | 8 + .../Settings/SerialDeviceSettingsView.xaml | 27 + .../Settings/SerialDeviceSettingsView.xaml.cs | 14 + .../Settings/SerialDeviceSettingsViewModel.cs | 77 +++ .../Worker/SerialWorker.cs | 233 ++++++++ .../MassiveKnob.Plugin/IMassiveKnobAction.cs | 16 +- .../IMassiveKnobActionContext.cs | 10 +- .../IMassiveKnobActionInstance.cs | 6 + .../IMassiveKnobAnalogAction.cs | 2 +- .../MassiveKnob.Plugin/IMassiveKnobDevice.cs | 3 +- .../IMassiveKnobDeviceContext.cs | 80 +++ .../IMassiveKnobDeviceInstance.cs | 6 + ...ePlugin.cs => IMassiveKnobDevicePlugin.cs} | 0 .../IMassiveKnobDigitalAction.cs | 9 +- .../MassiveKnob.Plugin.csproj | 3 +- .../Properties/AssemblyInfo.cs | 1 - Windows/MassiveKnob.sln | 6 + .../Hardware/AbstractMassiveKnobHardware.cs | 69 --- .../Hardware/CoreAudioDeviceManager.cs | 82 --- .../Hardware/IAudioDeviceManager.cs | 27 - .../Hardware/IMassiveKnobHardware.cs | 31 - .../Hardware/MockMassiveKnobHardware.cs | 71 --- .../Hardware/SerialMassiveKnobHardware.cs | 180 ------ .../Helpers/ComboBoxTemplateSelector.cs | 5 +- .../MassiveKnob/Helpers/DelegateCommand.cs | 4 +- Windows/MassiveKnob/Helpers/SerialQueue.cs | 65 ++ Windows/MassiveKnob/MassiveKnob.csproj | 20 +- .../Model/IMassiveKnobOrchestrator.cs | 41 +- Windows/MassiveKnob/Model/IPluginManager.cs | 3 +- .../Model/MassiveKnobOrchestrator.cs | 559 +++++++++++++++++- Windows/MassiveKnob/Model/PluginManager.cs | 97 ++- Windows/MassiveKnob/Program.cs | 39 +- Windows/MassiveKnob/Settings/Settings.cs | 48 +- .../Settings/SettingsJsonSerializer.cs | 46 +- Windows/MassiveKnob/Strings.Designer.cs | 78 +-- Windows/MassiveKnob/Strings.resx | 28 +- Windows/MassiveKnob/Style.xaml | 15 + Windows/MassiveKnob/View/InputOutputView.xaml | 44 ++ .../MassiveKnob/View/InputOutputView.xaml.cs | 13 + Windows/MassiveKnob/View/SettingsWindow.xaml | 95 ++- .../MassiveKnob/ViewModel/ActionViewModel.cs | 29 + .../MassiveKnob/ViewModel/DeviceViewModel.cs | 25 + .../ViewModel/InputOutputViewModel.cs | 86 +++ .../ViewModel/SettingsViewModel.cs | 164 +++-- 72 files changed, 2657 insertions(+), 859 deletions(-) create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/CoreAudioControllerInstance.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Settings/BaseDeviceSettings.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Settings/BaseDeviceSettingsViewModel.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.cs delete mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.xaml delete mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsView.xaml create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsView.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsViewModel.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Strings.Designer.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Strings.resx create mode 100644 Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.cs delete mode 100644 Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.xaml delete mode 100644 Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsView.xaml create mode 100644 Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsView.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsViewModel.cs create mode 100644 Windows/MassiveKnob.Plugin.SerialDevice/Devices/SerialDevice.cs create mode 100644 Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnob.Plugin.SerialDevice.csproj create mode 100644 Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnobSerialDevicePlugin.cs create mode 100644 Windows/MassiveKnob.Plugin.SerialDevice/Properties/AssemblyInfo.cs create mode 100644 Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettings.cs create mode 100644 Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsView.xaml create mode 100644 Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsView.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsViewModel.cs create mode 100644 Windows/MassiveKnob.Plugin.SerialDevice/Worker/SerialWorker.cs create mode 100644 Windows/MassiveKnob.Plugin/IMassiveKnobDeviceContext.cs rename Windows/MassiveKnob.Plugin/{IMassiveKnobHardwarePlugin.cs => IMassiveKnobDevicePlugin.cs} (100%) delete mode 100644 Windows/MassiveKnob/Hardware/AbstractMassiveKnobHardware.cs delete mode 100644 Windows/MassiveKnob/Hardware/CoreAudioDeviceManager.cs delete mode 100644 Windows/MassiveKnob/Hardware/IAudioDeviceManager.cs delete mode 100644 Windows/MassiveKnob/Hardware/IMassiveKnobHardware.cs delete mode 100644 Windows/MassiveKnob/Hardware/MockMassiveKnobHardware.cs delete mode 100644 Windows/MassiveKnob/Hardware/SerialMassiveKnobHardware.cs create mode 100644 Windows/MassiveKnob/Helpers/SerialQueue.cs create mode 100644 Windows/MassiveKnob/View/InputOutputView.xaml create mode 100644 Windows/MassiveKnob/View/InputOutputView.xaml.cs create mode 100644 Windows/MassiveKnob/ViewModel/ActionViewModel.cs create mode 100644 Windows/MassiveKnob/ViewModel/DeviceViewModel.cs create mode 100644 Windows/MassiveKnob/ViewModel/InputOutputViewModel.cs diff --git a/Arduino/MassiveKnob/MassiveKnob.ino b/Arduino/MassiveKnob/MassiveKnob.ino index 5169183..ec70437 100644 --- a/Arduino/MassiveKnob/MassiveKnob.ino +++ b/Arduino/MassiveKnob/MassiveKnob.ino @@ -9,8 +9,7 @@ const byte KnobCount = 1; // For each potentiometer, specify the port const byte KnobPin[KnobCount] = { -// A0, - A1 + A2 }; // Minimum time between reporting changing values, reduces serial traffic @@ -46,6 +45,7 @@ float emaValue[KnobCount]; unsigned long currentTime; unsigned long lastPlot; + void setup() { Serial.begin(115200); @@ -56,7 +56,10 @@ void setup() // Seed the moving average for (byte knobIndex = 0; knobIndex < KnobCount; knobIndex++) + { + pinMode(KnobPin[knobIndex], INPUT); emaValue[knobIndex] = analogRead(KnobPin[knobIndex]); + } for (byte seed = 1; seed < EMASeedCount - 1; seed++) for (byte knobIndex = 0; knobIndex < KnobCount; knobIndex++) @@ -119,18 +122,28 @@ void processMessage(byte message) case 'Q': // Quit processQuitMessage(); break; + + default: + outputError("Unknown message: " + (char)message); + break; } } void processHandshakeMessage() { - byte buffer[2]; + byte buffer[3]; if (Serial.readBytes(buffer, 3) < 3) + { + outputError("Invalid handshake length"); return; + } if (buffer[0] != 'M' || buffer[1] != 'K') + { + outputError("Invalid handshake: " + String((char)buffer[0]) + String((char)buffer[1]) + String((char)buffer[2])); return; + } switch (buffer[2]) { @@ -147,6 +160,8 @@ void processHandshakeMessage() break; default: + outputMode = PlainText; + outputError("Unknown output mode: " + String((char)buffer[2])); return; } @@ -185,6 +200,11 @@ void processQuitMessage() byte getVolume(byte knobIndex) { + analogRead(KnobPin[knobIndex]); + + // Give the ADC some time to stabilize + delay(10); + analogReadValue[knobIndex] = analogRead(KnobPin[knobIndex]); emaValue[knobIndex] = (EMAAlpha * analogReadValue[knobIndex]) + ((1 - EMAAlpha) * emaValue[knobIndex]); @@ -228,3 +248,21 @@ void outputPlotter() Serial.println(); } + + +void outputError(String message) +{ + switch (outputMode) + { + case Binary: + Serial.write('E'); + Serial.write((byte)message.length()); + Serial.print(message); + break; + + case PlainText: + Serial.print("Error: "); + Serial.println(message); + break; + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Actions/DeviceVolumeAction.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Actions/DeviceVolumeAction.cs index 93829f5..db31607 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/Actions/DeviceVolumeAction.cs +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Actions/DeviceVolumeAction.cs @@ -1,6 +1,8 @@ using System; using System.Threading.Tasks; using System.Windows.Controls; +using AudioSwitcher.AudioApi; +using MassiveKnob.Plugin.CoreAudio.Settings; namespace MassiveKnob.Plugin.CoreAudio.Actions { @@ -12,45 +14,62 @@ namespace MassiveKnob.Plugin.CoreAudio.Actions public string Description { get; } = "Sets the volume for the selected device, regardless of the current default device."; - public IMassiveKnobActionInstance Create(IMassiveKnobActionContext context) + public IMassiveKnobActionInstance Create() { - return new Instance(context); + return new Instance(); } private class Instance : IMassiveKnobAnalogAction { - private readonly Settings settings; + private IMassiveKnobActionContext actionContext; + private DeviceVolumeActionSettings settings; + private IDevice playbackDevice; - - public Instance(IMassiveKnobContext context) + + public void Initialize(IMassiveKnobActionContext context) { - settings = context.GetSettings(); + actionContext = context; + settings = context.GetSettings(); + ApplySettings(); } - + public void Dispose() { } - + + private void ApplySettings() + { + var coreAudioController = CoreAudioControllerInstance.Acquire(); + playbackDevice = settings.DeviceId.HasValue ? coreAudioController.GetDevice(settings.DeviceId.Value) : null; + } + + public UserControl CreateSettingsControl() { - return null; + var viewModel = new DeviceVolumeActionSettingsViewModel(settings); + viewModel.PropertyChanged += (sender, args) => + { + if (!viewModel.IsSettingsProperty(args.PropertyName)) + return; + + actionContext.SetSettings(settings); + ApplySettings(); + }; + + return new DeviceVolumeActionSettingsView(viewModel); } - - public ValueTask AnalogChanged(byte value) + + public async ValueTask AnalogChanged(byte value) { - // TODO set volume - return default; + if (playbackDevice == null) + return; + + await playbackDevice.SetVolumeAsync(value); } } - - - private class Settings - { - - } } } diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/CoreAudioControllerInstance.cs b/Windows/MassiveKnob.Plugin.CoreAudio/CoreAudioControllerInstance.cs new file mode 100644 index 0000000..1026c25 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/CoreAudioControllerInstance.cs @@ -0,0 +1,16 @@ +using System; +using AudioSwitcher.AudioApi.CoreAudio; + +namespace MassiveKnob.Plugin.CoreAudio +{ + public static class CoreAudioControllerInstance + { + private static readonly Lazy Instance = new Lazy(); + + + public static CoreAudioController Acquire() + { + return Instance.Value; + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj index cb62ecd..123b6c9 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj +++ b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj @@ -46,10 +46,20 @@ + - - DeviceVolumeActionSettings.xaml + + + DeviceVolumeActionSettingsView.xaml + + + + + + Strings.resx + True + True @@ -59,13 +69,21 @@ + + 4.0.0-alpha5 + 4.5.4 - - + + ResXFileCodeGenerator + Strings.Designer.cs + + + + Designer MSBuild:Compile diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobCoreAudioPlugin.cs b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobCoreAudioPlugin.cs index 1e8cc5e..13c738d 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobCoreAudioPlugin.cs +++ b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobCoreAudioPlugin.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using MassiveKnob.Plugin.CoreAudio.Actions; namespace MassiveKnob.Plugin.CoreAudio @@ -17,5 +18,17 @@ namespace MassiveKnob.Plugin.CoreAudio { new DeviceVolumeAction() }; + + + public MassiveKnobCoreAudioPlugin() + { + // My system suffers from this issue: https://github.com/xenolightning/AudioSwitcher/issues/40 + // ...which causes the first call to the CoreAudioController to take up to 10 seconds, + // so initialise it as soon as possible. Bit of a workaround, but eh. + Task.Run(() => + { + CoreAudioControllerInstance.Acquire(); + }); + } } } diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Properties/AssemblyInfo.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Properties/AssemblyInfo.cs index b37ec34..7ba6cb2 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/Properties/AssemblyInfo.cs +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/BaseDeviceSettings.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/BaseDeviceSettings.cs new file mode 100644 index 0000000..dd8cb66 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/BaseDeviceSettings.cs @@ -0,0 +1,9 @@ +using System; + +namespace MassiveKnob.Plugin.CoreAudio.Settings +{ + public class BaseDeviceSettings + { + public Guid? DeviceId { get; set; } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/BaseDeviceSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/BaseDeviceSettingsViewModel.cs new file mode 100644 index 0000000..454bd84 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/BaseDeviceSettingsViewModel.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using System.Windows; +using AudioSwitcher.AudioApi; + +namespace MassiveKnob.Plugin.CoreAudio.Settings +{ + public class BaseDeviceSettingsViewModel : INotifyPropertyChanged + { + private readonly BaseDeviceSettings settings; + public event PropertyChangedEventHandler PropertyChanged; + + private IList playbackDevices; + private PlaybackDeviceViewModel selectedDevice; + + // ReSharper disable UnusedMember.Global - used by WPF Binding + public IList PlaybackDevices + { + get => playbackDevices; + set + { + playbackDevices = value; + OnPropertyChanged(); + } + } + + public PlaybackDeviceViewModel SelectedDevice + { + get => selectedDevice; + set + { + if (value == selectedDevice) + return; + + selectedDevice = value; + settings.DeviceId = selectedDevice?.Id; + OnPropertyChanged(); + } + } + // ReSharper restore UnusedMember.Global + + + public BaseDeviceSettingsViewModel(BaseDeviceSettings settings) + { + this.settings = settings; + + Task.Run(async () => + { + var coreAudioController = CoreAudioControllerInstance.Acquire(); + var devices = await coreAudioController.GetPlaybackDevicesAsync(); + var deviceViewModels = devices + .OrderBy(d => d.FullName) + .Select(PlaybackDeviceViewModel.FromDevice) + .ToList(); + + Application.Current.Dispatcher.Invoke(() => + { + PlaybackDevices = deviceViewModels; + SelectedDevice = deviceViewModels.SingleOrDefault(d => d.Id == settings.DeviceId); + }); + }); + } + + + public virtual bool IsSettingsProperty(string propertyName) + { + return propertyName != nameof(PlaybackDevices); + } + + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } + + + public class PlaybackDeviceViewModel + { + public Guid Id { get; private set; } + public string DisplayName { get; private set; } + + + public static PlaybackDeviceViewModel FromDevice(IDevice device) + { + string displayFormat; + + if ((device.State & DeviceState.Disabled) != 0) + displayFormat = Strings.DeviceDisplayNameDisabled; + else if ((device.State & DeviceState.Unplugged) != 0) + displayFormat = Strings.DeviceDisplayNameUnplugged; + else if ((device.State & DeviceState.NotPresent) != 0) + displayFormat = Strings.DeviceDisplayNameNotPresent; + else if ((device.State & DeviceState.Active) == 0) + displayFormat = Strings.DeviceDisplayNameInactive; + else + displayFormat = Strings.DeviceDisplayNameActive; + + return new PlaybackDeviceViewModel + { + Id = device.Id, + DisplayName = string.Format(displayFormat, device.FullName) + }; + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.cs new file mode 100644 index 0000000..6fa5eee --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.cs @@ -0,0 +1,7 @@ +namespace MassiveKnob.Plugin.CoreAudio.Settings +{ + public class DeviceVolumeActionSettings : BaseDeviceSettings + { + // TODO OSD + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.xaml b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.xaml deleted file mode 100644 index 86aa8c4..0000000 --- a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.xaml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.xaml.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.xaml.cs deleted file mode 100644 index 6c1ed5b..0000000 --- a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.xaml.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -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 MassiveKnob.Plugin.CoreAudio.Settings -{ - /// - /// Interaction logic for DeviceVolumeActionSettings.xaml - /// - public partial class DeviceVolumeActionSettings : UserControl - { - public DeviceVolumeActionSettings() - { - InitializeComponent(); - } - } -} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsView.xaml b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsView.xaml new file mode 100644 index 0000000..b1485b0 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsView.xaml @@ -0,0 +1,14 @@ + + + Playback device + + + diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsView.xaml.cs new file mode 100644 index 0000000..7ecd71f --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsView.xaml.cs @@ -0,0 +1,14 @@ +namespace MassiveKnob.Plugin.CoreAudio.Settings +{ + /// + /// Interaction logic for DeviceVolumeActionSettingsView.xaml + /// + public partial class DeviceVolumeActionSettingsView + { + public DeviceVolumeActionSettingsView(DeviceVolumeActionSettingsViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsViewModel.cs new file mode 100644 index 0000000..7e46566 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsViewModel.cs @@ -0,0 +1,10 @@ +namespace MassiveKnob.Plugin.CoreAudio.Settings +{ + public class DeviceVolumeActionSettingsViewModel : BaseDeviceSettingsViewModel + { + // ReSharper disable once SuggestBaseTypeForParameter - by design + public DeviceVolumeActionSettingsViewModel(DeviceVolumeActionSettings settings) : base(settings) + { + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Strings.Designer.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Strings.Designer.cs new file mode 100644 index 0000000..746832c --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Strings.Designer.cs @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace MassiveKnob.Plugin.CoreAudio { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MassiveKnob.Plugin.CoreAudio.Strings", typeof(Strings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to {0}. + /// + internal static string DeviceDisplayNameActive { + get { + return ResourceManager.GetString("DeviceDisplayNameActive", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} (Disabled). + /// + internal static string DeviceDisplayNameDisabled { + get { + return ResourceManager.GetString("DeviceDisplayNameDisabled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} (Inactive). + /// + internal static string DeviceDisplayNameInactive { + get { + return ResourceManager.GetString("DeviceDisplayNameInactive", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} (Not present). + /// + internal static string DeviceDisplayNameNotPresent { + get { + return ResourceManager.GetString("DeviceDisplayNameNotPresent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} (Unplugged). + /// + internal static string DeviceDisplayNameUnplugged { + get { + return ResourceManager.GetString("DeviceDisplayNameUnplugged", resourceCulture); + } + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Strings.resx b/Windows/MassiveKnob.Plugin.CoreAudio/Strings.resx new file mode 100644 index 0000000..5a89d1c --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Strings.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + {0} + + + {0} (Disabled) + + + {0} (Inactive) + + + {0} (Not present) + + + {0} (Unplugged) + + \ No newline at end of file diff --git a/Windows/MassiveKnob.Plugin.MockDevice/Devices/MockDevice.cs b/Windows/MassiveKnob.Plugin.MockDevice/Devices/MockDevice.cs index d402c4d..4112c92 100644 --- a/Windows/MassiveKnob.Plugin.MockDevice/Devices/MockDevice.cs +++ b/Windows/MassiveKnob.Plugin.MockDevice/Devices/MockDevice.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Windows.Controls; using MassiveKnob.Plugin.MockDevice.Settings; @@ -9,37 +10,86 @@ namespace MassiveKnob.Plugin.MockDevice.Devices public Guid DeviceId { get; } = new Guid("e1a4977a-abf4-4c75-a17d-fd8d3a8451ff"); public string Name { get; } = "Mock device"; public string Description { get; } = "Emulates the actual device but does not communicate with anything."; - - public IMassiveKnobDeviceInstance Create(IMassiveKnobContext context) + + public IMassiveKnobDeviceInstance Create() { - return new Instance(context); + return new Instance(); } private class Instance : IMassiveKnobDeviceInstance { - public Instance(IMassiveKnobContext context) + private IMassiveKnobDeviceContext deviceContext; + private MockDeviceSettings settings; + private Timer inputChangeTimer; + + private int reportedAnalogInputCount; + private int reportedDigitalInputCount; + private readonly Random random = new Random(); + + + public void Initialize(IMassiveKnobDeviceContext context) { - // TODO read settings + deviceContext = context; + settings = deviceContext.GetSettings(); + + ApplySettings(); } public void Dispose() { + inputChangeTimer?.Dispose(); + } + + + private void ApplySettings() + { + if (settings.AnalogCount != reportedAnalogInputCount || + settings.DigitalCount != reportedDigitalInputCount) + { + deviceContext.Connected(new DeviceSpecs(settings.AnalogCount, settings.DigitalCount, 0, 0)); + + reportedAnalogInputCount = settings.AnalogCount; + reportedDigitalInputCount = settings.DigitalCount; + } + + + var interval = TimeSpan.FromSeconds(Math.Max(settings.Interval, 1)); + + if (inputChangeTimer == null) + inputChangeTimer = new Timer(Tick, null, interval, interval); + else + inputChangeTimer.Change(interval, interval); } public UserControl CreateSettingsControl() { - // TODO pass context - return new MockDeviceSettings(); + var viewModel = new MockDeviceSettingsViewModel(settings); + viewModel.PropertyChanged += (sender, args) => + { + deviceContext.SetSettings(settings); + ApplySettings(); + }; + + return new MockDeviceSettingsView(viewModel); + } + + + private void Tick(object state) + { + var totalInputCount = reportedAnalogInputCount + reportedDigitalInputCount; + if (totalInputCount == 0) + return; + + var changeInput = random.Next(0, totalInputCount); + + if (changeInput < reportedAnalogInputCount) + deviceContext.AnalogChanged(changeInput, (byte)random.Next(0, 101)); + else + deviceContext.DigitalChanged(changeInput - reportedAnalogInputCount, random.Next(0, 2) == 1); } - } - - - private class Settings - { - // TODO interval, etc. } } } diff --git a/Windows/MassiveKnob.Plugin.MockDevice/MassiveKnob.Plugin.MockDevice.csproj b/Windows/MassiveKnob.Plugin.MockDevice/MassiveKnob.Plugin.MockDevice.csproj index d9a3c45..8a0f341 100644 --- a/Windows/MassiveKnob.Plugin.MockDevice/MassiveKnob.Plugin.MockDevice.csproj +++ b/Windows/MassiveKnob.Plugin.MockDevice/MassiveKnob.Plugin.MockDevice.csproj @@ -48,9 +48,11 @@ - - MockDeviceSettings.xaml + + MockDeviceSettingsView.xaml + + @@ -60,7 +62,7 @@ - + Designer MSBuild:Compile diff --git a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.cs b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.cs new file mode 100644 index 0000000..5990d4b --- /dev/null +++ b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.cs @@ -0,0 +1,9 @@ +namespace MassiveKnob.Plugin.MockDevice.Settings +{ + public class MockDeviceSettings + { + public int AnalogCount { get; set; } = 3; + public int DigitalCount { get; set; } = 1; + public int Interval { get; set; } = 5; + } +} diff --git a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.xaml b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.xaml deleted file mode 100644 index 15b1798..0000000 --- a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.xaml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - Number of knobs - - - Randomly change the volume - - diff --git a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.xaml.cs b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.xaml.cs deleted file mode 100644 index 843c196..0000000 --- a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.xaml.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -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 MassiveKnob.Plugin.MockDevice.Settings -{ - /// - /// Interaction logic for MockDeviceSettings.xaml - /// - public partial class MockDeviceSettings : UserControl - { - public MockDeviceSettings() - { - InitializeComponent(); - } - } -} diff --git a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsView.xaml b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsView.xaml new file mode 100644 index 0000000..6fcf56b --- /dev/null +++ b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsView.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + Number of analog inputs + + + Number of digital inputs + + + Random change interval (seconds) + + + diff --git a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsView.xaml.cs new file mode 100644 index 0000000..503729c --- /dev/null +++ b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsView.xaml.cs @@ -0,0 +1,15 @@ +namespace MassiveKnob.Plugin.MockDevice.Settings +{ + /// + /// Interaction logic for MockDeviceSettingsView.xaml + /// + public partial class MockDeviceSettingsView + { + public MockDeviceSettingsView(MockDeviceSettingsViewModel settingsViewModel) + { + DataContext = settingsViewModel; + + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsViewModel.cs new file mode 100644 index 0000000..54040ad --- /dev/null +++ b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsViewModel.cs @@ -0,0 +1,67 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace MassiveKnob.Plugin.MockDevice.Settings +{ + public class MockDeviceSettingsViewModel : INotifyPropertyChanged + { + private readonly MockDeviceSettings settings; + public event PropertyChangedEventHandler PropertyChanged; + + + // ReSharper disable UnusedMember.Global - used by WPF Binding + public int AnalogCount + { + get => settings.AnalogCount; + set + { + if (value == settings.AnalogCount) + return; + + settings.AnalogCount = value; + OnPropertyChanged(); + } + } + + + public int DigitalCount + { + get => settings.DigitalCount; + set + { + if (value == settings.DigitalCount) + return; + + settings.DigitalCount = value; + OnPropertyChanged(); + } + } + + + public int Interval + { + get => settings.Interval; + set + { + if (value == settings.Interval) + return; + + settings.Interval = value; + OnPropertyChanged(); + } + } + // ReSharper restore UnusedMember.Global + + + public MockDeviceSettingsViewModel(MockDeviceSettings settings) + { + this.settings = settings; + } + + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/Devices/SerialDevice.cs b/Windows/MassiveKnob.Plugin.SerialDevice/Devices/SerialDevice.cs new file mode 100644 index 0000000..b155974 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.SerialDevice/Devices/SerialDevice.cs @@ -0,0 +1,64 @@ +using System; +using System.Windows.Controls; +using MassiveKnob.Plugin.SerialDevice.Settings; +using MassiveKnob.Plugin.SerialDevice.Worker; + +namespace MassiveKnob.Plugin.SerialDevice.Devices +{ + public class SerialDevice : IMassiveKnobDevice + { + public Guid DeviceId { get; } = new Guid("65255f25-d8f6-426b-8f12-cf03c44a1bf5"); + public string Name { get; } = "Serial device"; + public string Description { get; } = "A Serial (USB) device which implements the Massive Knob Protocol."; + + public IMassiveKnobDeviceInstance Create() + { + return new Instance(); + } + + + private class Instance : IMassiveKnobDeviceInstance + { + private IMassiveKnobDeviceContext deviceContext; + private SerialDeviceSettings settings; + private SerialWorker worker; + + public void Initialize(IMassiveKnobDeviceContext context) + { + deviceContext = context; + settings = deviceContext.GetSettings(); + + worker = new SerialWorker(context); + ApplySettings(); + } + + + public void Dispose() + { + worker.Dispose(); + } + + + private void ApplySettings() + { + worker.Connect(settings.PortName, settings.BaudRate); + } + + + public UserControl CreateSettingsControl() + { + var viewModel = new SerialDeviceSettingsViewModel(settings); + viewModel.PropertyChanged += (sender, args) => + { + if (!viewModel.IsSettingsProperty(args.PropertyName)) + return; + + deviceContext.SetSettings(settings); + ApplySettings(); + }; + + return new SerialDeviceSettingsView(viewModel); + } + } + } +} diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnob.Plugin.SerialDevice.csproj b/Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnob.Plugin.SerialDevice.csproj new file mode 100644 index 0000000..f85d09a --- /dev/null +++ b/Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnob.Plugin.SerialDevice.csproj @@ -0,0 +1,71 @@ + + + + + Debug + AnyCPU + {FC0D22D8-5F1B-4D85-8269-FA4837CDE3A2} + Library + Properties + MassiveKnob.Plugin.SerialDevice + MassiveKnob.Plugin.SerialDevice + v4.7.2 + 512 + true + + + true + full + false + $(localappdata)\MassiveKnob\Plugins\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + SerialDeviceSettingsView.xaml + + + + + + + + {A1298BE4-1D23-416C-8C56-FC9264487A95} + MassiveKnob.Plugin + + + + + Designer + MSBuild:Compile + + + + \ No newline at end of file diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnobSerialDevicePlugin.cs b/Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnobSerialDevicePlugin.cs new file mode 100644 index 0000000..9e0ccaf --- /dev/null +++ b/Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnobSerialDevicePlugin.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace MassiveKnob.Plugin.SerialDevice +{ + [MassiveKnobPlugin] + public class MassiveKnobSerialDevicePlugin : IMassiveKnobDevicePlugin + { + public Guid PluginId { get; } = new Guid("276475e6-5ff0-420f-82dc-8aff5e8631d5"); + public string Name { get; } = "Serial Device"; + public string Description { get; } = "A Serial (USB) device which implements the Massive Knob Protocol."; + public string Author { get; } = "Mark van Renswoude "; + public string Url { get; } = "https://www.github.com/MvRens/MassiveKnob/"; + + public IEnumerable Devices { get; } = new IMassiveKnobDevice[] + { + new Devices.SerialDevice() + }; + } +} diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/Properties/AssemblyInfo.cs b/Windows/MassiveKnob.Plugin.SerialDevice/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..751b137 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.SerialDevice/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +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("MassiveKnob.Plugin.SerialDevice")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MassiveKnob.Plugin.SerialDevice")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[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("fc0d22d8-5f1b-4d85-8269-fa4837cde3a2")] + +// 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")] diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettings.cs b/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettings.cs new file mode 100644 index 0000000..3abc590 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettings.cs @@ -0,0 +1,8 @@ +namespace MassiveKnob.Plugin.SerialDevice.Settings +{ + public class SerialDeviceSettings + { + public string PortName { get; set; } = null; + public int BaudRate { get; set; } = 115200; + } +} diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsView.xaml b/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsView.xaml new file mode 100644 index 0000000..12150f1 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsView.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + Serial port + + + Baud rate + + + diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsView.xaml.cs new file mode 100644 index 0000000..3e894f0 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsView.xaml.cs @@ -0,0 +1,14 @@ +namespace MassiveKnob.Plugin.SerialDevice.Settings +{ + /// + /// Interaction logic for SerialDeviceSettingsView.xaml + /// + public partial class SerialDeviceSettingsView + { + public SerialDeviceSettingsView(SerialDeviceSettingsViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsViewModel.cs new file mode 100644 index 0000000..56485a1 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsViewModel.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.IO.Ports; +using System.Runtime.CompilerServices; + +namespace MassiveKnob.Plugin.SerialDevice.Settings +{ + public class SerialDeviceSettingsViewModel : INotifyPropertyChanged + { + private readonly SerialDeviceSettings settings; + private IEnumerable serialPorts; + public event PropertyChangedEventHandler PropertyChanged; + + + // ReSharper disable UnusedMember.Global - used by WPF Binding + public IEnumerable SerialPorts + { + get => serialPorts; + set + { + serialPorts = value; + OnPropertyChanged(); + } + } + + + public string PortName + { + get => settings.PortName; + set + { + if (value == settings.PortName) + return; + + settings.PortName = value; + OnPropertyChanged(); + } + } + + + public int BaudRate + { + get => settings.BaudRate; + set + { + if (value == settings.BaudRate) + return; + + settings.BaudRate = value; + OnPropertyChanged(); + } + } + // ReSharper restore UnusedMember.Global + + + public SerialDeviceSettingsViewModel(SerialDeviceSettings settings) + { + this.settings = settings; + + serialPorts = SerialPort.GetPortNames(); + + // TODO subscribe to device notification to refresh list + } + + + public bool IsSettingsProperty(string propertyName) + { + return propertyName != nameof(SerialPorts); + } + + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/Worker/SerialWorker.cs b/Windows/MassiveKnob.Plugin.SerialDevice/Worker/SerialWorker.cs new file mode 100644 index 0000000..2e02f25 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.SerialDevice/Worker/SerialWorker.cs @@ -0,0 +1,233 @@ +using System; +using System.Diagnostics; +using System.IO.Ports; +using System.Text; +using System.Threading; + +namespace MassiveKnob.Plugin.SerialDevice.Worker +{ + public class SerialWorker : IDisposable + { + private readonly IMassiveKnobDeviceContext context; + + private readonly object workerLock = new object(); + private string lastPortName; + private int lastBaudRate; + private Thread workerThread; + private CancellationTokenSource workerThreadCancellation = new CancellationTokenSource(); + + public SerialWorker(IMassiveKnobDeviceContext context) + { + this.context = context; + } + + + public void Dispose() + { + Disconnect(); + } + + + public void Connect(string portName, int baudRate) + { + lock (workerLock) + { + if (portName == lastPortName && baudRate == lastBaudRate) + return; + + lastPortName = portName; + lastBaudRate = baudRate; + + Disconnect(); + + if (string.IsNullOrEmpty(portName) || baudRate == 0) + return; + + + workerThreadCancellation = new CancellationTokenSource(); + workerThread = new Thread(() => RunWorker(workerThreadCancellation.Token, portName, baudRate)) + { + Name = "MassiveKnobSerialDevice Worker" + }; + workerThread.Start(); + } + } + + + private void Disconnect() + { + lock (workerLock) + { + workerThreadCancellation?.Cancel(); + + workerThreadCancellation = null; + workerThread = null; + } + } + + + + private void RunWorker(CancellationToken cancellationToken, string portName, int baudRate) + { + context.Connecting(); + while (!cancellationToken.IsCancellationRequested) + { + SerialPort serialPort = null; + DeviceSpecs specs = default; + + void SafeCloseSerialPort() + { + try + { + serialPort?.Dispose(); + } + catch + { + // ignored + } + + serialPort = null; + context.Connecting(); + } + + + while (serialPort == null && !cancellationToken.IsCancellationRequested) + { + if (!TryConnect(ref serialPort, portName, baudRate, out specs)) + { + SafeCloseSerialPort(); + Thread.Sleep(500); + } + else + break; + } + + if (cancellationToken.IsCancellationRequested) + { + SafeCloseSerialPort(); + break; + } + + var processingMessage = false; + + Debug.Assert(serialPort != null, nameof(serialPort) + " != null"); + serialPort.DataReceived += (sender, args) => + { + if (args.EventType != SerialData.Chars || processingMessage) + return; + + var senderPort = (SerialPort)sender; + processingMessage = true; + try + { + var message = (char)senderPort.ReadByte(); + ProcessMessage(senderPort, message); + } + finally + { + processingMessage = false; + } + }; + + + context.Connected(specs); + try + { + // This is where sending data to the hardware would be implemented + while (serialPort.IsOpen && !cancellationToken.IsCancellationRequested) + { + Thread.Sleep(10); + } + } + catch + { + // ignored + } + + context.Disconnected(); + SafeCloseSerialPort(); + + if (!cancellationToken.IsCancellationRequested) + Thread.Sleep(500); + } + } + + + private static bool TryConnect(ref SerialPort serialPort, string portName, int baudRate, out DeviceSpecs specs) + { + try + { + serialPort = new SerialPort(portName, baudRate) + { + Encoding = Encoding.ASCII, + ReadTimeout = 1000, + WriteTimeout = 1000, + DtrEnable = true // TODO make setting + }; + serialPort.Open(); + + // Send handshake + serialPort.Write(new[] { 'H', 'M', 'K', 'B' }, 0, 4); + + // Wait for reply + var response = serialPort.ReadByte(); + + if ((char) response == 'H') + { + var knobCount = serialPort.ReadByte(); + if (knobCount > -1) + { + specs = new DeviceSpecs(knobCount, 0, 0, 0); + return true; + } + } + else + CheckForError(serialPort, (char)response); + + specs = default; + return false; + } + catch + { + specs = default; + return false; + } + } + + + private void ProcessMessage(SerialPort serialPort, char message) + { + switch (message) + { + case 'V': + var knobIndex = (byte)serialPort.ReadByte(); + var volume = (byte)serialPort.ReadByte(); + + if (knobIndex < 255 && volume <= 100) + context.AnalogChanged(knobIndex, volume); + + break; + } + } + + + private static void CheckForError(SerialPort serialPort, char message) + { + if (message != 'E') + return; + + var length = serialPort.ReadByte(); + if (length <= 0) + return; + + var buffer = new byte[length]; + var bytesRead = 0; + + while (bytesRead < length) + bytesRead += serialPort.Read(buffer, bytesRead, length - bytesRead); + + var errorMessage = Encoding.ASCII.GetString(buffer); + Debug.Print(errorMessage); + } + } +} diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobAction.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobAction.cs index ba3f3fc..bba4713 100644 --- a/Windows/MassiveKnob.Plugin/IMassiveKnobAction.cs +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobAction.cs @@ -19,12 +19,17 @@ namespace MassiveKnob.Plugin InputDigital = 1 << 1, /// - /// Can be assigned to an output, like an LED or relay. + /// Can be assigned to an analog output. /// - OutputSignal = 1 << 2 + OutputAnalog = 1 << 2, + + /// + /// Can be assigned to a digital output, like an LED or relay. + /// + OutputDigital = 1 << 3 } - - + + /// /// Provides information about an action which can be assigned to a knob or button. /// @@ -53,7 +58,6 @@ namespace MassiveKnob.Plugin /// /// Called when an action is bound to a knob or button to create an instance of the action. /// - /// Provides an interface to the Massive Knob settings and device. Can be stored until the action instance is disposed. - IMassiveKnobActionInstance Create(IMassiveKnobActionContext context); + IMassiveKnobActionInstance Create(); } } diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobActionContext.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobActionContext.cs index 9f08a67..b8154b1 100644 --- a/Windows/MassiveKnob.Plugin/IMassiveKnobActionContext.cs +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobActionContext.cs @@ -4,9 +4,15 @@ public interface IMassiveKnobActionContext : IMassiveKnobContext { /// - /// Sets the state of the signal. Only valid for OutputSignal action types, will raise an exception otherwise. + /// Sets the state of the analog output. Only valid for OutputAnalog action types, will raise an exception otherwise. + /// + /// The analog value in the range of 0 to 100. + void SetAnalogOutput(byte value); + + /// + /// Sets the state of the digital output. Only valid for OutputDigital action types, will raise an exception otherwise. /// /// Whether the signal is on or off. - void SetSignal(bool on); + void SetDigitalOutput(bool on); } } diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobActionInstance.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobActionInstance.cs index 3973f60..110a27f 100644 --- a/Windows/MassiveKnob.Plugin/IMassiveKnobActionInstance.cs +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobActionInstance.cs @@ -9,6 +9,12 @@ namespace MassiveKnob.Plugin /// public interface IMassiveKnobActionInstance : IDisposable { + /// + /// Called right after this instance is created. + /// + /// Provides an interface to the Massive Knob settings and device. Can be stored until the action instance is disposed. + void Initialize(IMassiveKnobActionContext context); + /// /// Called when an action should display it's settings. Assume the width is variable, height is /// determined by the UserControl. Return null to indicate there are no settings for this action. diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobAnalogAction.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobAnalogAction.cs index 15cfae1..4fe88ff 100644 --- a/Windows/MassiveKnob.Plugin/IMassiveKnobAnalogAction.cs +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobAnalogAction.cs @@ -8,7 +8,7 @@ namespace MassiveKnob.Plugin public interface IMassiveKnobAnalogAction : IMassiveKnobActionInstance { /// - /// Called when a knob's position changes. + /// Called when an analog input's value changes. /// /// The new value. Range is 0 to 100. ValueTask AnalogChanged(byte value); diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobDevice.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobDevice.cs index f949076..3a9f6c3 100644 --- a/Windows/MassiveKnob.Plugin/IMassiveKnobDevice.cs +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobDevice.cs @@ -25,7 +25,6 @@ namespace MassiveKnob.Plugin /// /// Called when the device is selected. /// - /// Provides an interface to the Massive Knob settings and device. Can be stored until the device instance is disposed. - IMassiveKnobDeviceInstance Create(IMassiveKnobContext context); + IMassiveKnobDeviceInstance Create(); } } diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobDeviceContext.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobDeviceContext.cs new file mode 100644 index 0000000..c170ec6 --- /dev/null +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobDeviceContext.cs @@ -0,0 +1,80 @@ +namespace MassiveKnob.Plugin +{ + /// + public interface IMassiveKnobDeviceContext : IMassiveKnobContext + { + /// + /// Call when an attempt is being made to connect to the device. If the connection is always ready + /// this call can be skipped. + /// + void Connecting(); + + /// + /// Call when the device is connected. This method must be called before AnalogChanged or DigitalChanged. + /// + /// The specs as reported by the device. + void Connected(DeviceSpecs specs); + + /// + /// Call when the connection to the device has been lost. + /// + void Disconnected(); + + /// + /// Call when an analog input's value has changed. + /// + /// The index of the analog input. Must be within bounds of the value reported in Connected. + /// The new value in the range from 0 to 100. + void AnalogChanged(int analogInputIndex, byte value); + + /// + /// Call when a digital input's state has changed. + /// + /// The index of the digital input. Must be within bounds of the value reported in Connected. + /// Whether the input is considered on or off. + void DigitalChanged(int digitalInputIndex, bool on); + } + + + /// + /// Defines the specs as reported by the device. + /// + public readonly struct DeviceSpecs + { + /// + /// The number of analog input controls supported by the device. + /// + public readonly int AnalogInputCount; + + /// + /// The number of digital input controls supported by the device. + /// + public readonly int DigitalInputCount; + + /// + /// The number of analog output controls supported by the device. + /// + public readonly int AnalogOutputCount; + + /// + /// The number of digital output controls supported by the device. + /// + public readonly int DigitalOutputCount; + + + /// + /// Defines the specs as reported by the device. + /// + /// The number of analog input controls supported by the device. + /// The number of digital input controls supported by the device. + /// The number of analog output controls supported by the device. + /// The number of digital output controls supported by the device. + public DeviceSpecs(int analogInputCount, int digitalInputCount, int analogOutputCount, int digitalOutputCount) + { + AnalogInputCount = analogInputCount; + DigitalInputCount = digitalInputCount; + AnalogOutputCount = analogOutputCount; + DigitalOutputCount = digitalOutputCount; + } + } +} diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobDeviceInstance.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobDeviceInstance.cs index d8307df..1924a3e 100644 --- a/Windows/MassiveKnob.Plugin/IMassiveKnobDeviceInstance.cs +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobDeviceInstance.cs @@ -8,6 +8,12 @@ namespace MassiveKnob.Plugin /// public interface IMassiveKnobDeviceInstance : IDisposable { + /// + /// Called right after this instance is created. + /// + /// Provides an interface to the Massive Knob settings and device. Can be stored until the device instance is disposed. + void Initialize(IMassiveKnobDeviceContext context); + /// /// Called when a device should display it's settings. Assume the width is variable, height is /// determined by the UserControl. Return null to indicate there are no settings for this device. diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobHardwarePlugin.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobDevicePlugin.cs similarity index 100% rename from Windows/MassiveKnob.Plugin/IMassiveKnobHardwarePlugin.cs rename to Windows/MassiveKnob.Plugin/IMassiveKnobDevicePlugin.cs diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobDigitalAction.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobDigitalAction.cs index fb3992f..2d2998a 100644 --- a/Windows/MassiveKnob.Plugin/IMassiveKnobDigitalAction.cs +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobDigitalAction.cs @@ -1,9 +1,16 @@ -namespace MassiveKnob.Plugin +using System.Threading.Tasks; + +namespace MassiveKnob.Plugin { /// /// Required to be implemented for Action type InputDigital. Receives an update when a knob's position changes. /// public interface IMassiveKnobDigitalAction : IMassiveKnobActionInstance { + /// + /// Called when a digital input's value changes. + /// + /// The new value. + ValueTask DigitalChanged(bool on); } } diff --git a/Windows/MassiveKnob.Plugin/MassiveKnob.Plugin.csproj b/Windows/MassiveKnob.Plugin/MassiveKnob.Plugin.csproj index 8cdc5b2..5659c17 100644 --- a/Windows/MassiveKnob.Plugin/MassiveKnob.Plugin.csproj +++ b/Windows/MassiveKnob.Plugin/MassiveKnob.Plugin.csproj @@ -44,6 +44,7 @@ + @@ -52,7 +53,7 @@ - + diff --git a/Windows/MassiveKnob.Plugin/Properties/AssemblyInfo.cs b/Windows/MassiveKnob.Plugin/Properties/AssemblyInfo.cs index dddcccd..a776320 100644 --- a/Windows/MassiveKnob.Plugin/Properties/AssemblyInfo.cs +++ b/Windows/MassiveKnob.Plugin/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/Windows/MassiveKnob.sln b/Windows/MassiveKnob.sln index 85a3fb7..8f3c00f 100644 --- a/Windows/MassiveKnob.sln +++ b/Windows/MassiveKnob.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin.CoreAudi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin.MockDevice", "MassiveKnob.Plugin.MockDevice\MassiveKnob.Plugin.MockDevice.csproj", "{674DE974-B134-4DB5-BFAF-7BC3D05E16DE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin.SerialDevice", "MassiveKnob.Plugin.SerialDevice\MassiveKnob.Plugin.SerialDevice.csproj", "{FC0D22D8-5F1B-4D85-8269-FA4837CDE3A2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +35,10 @@ Global {674DE974-B134-4DB5-BFAF-7BC3D05E16DE}.Debug|Any CPU.Build.0 = Debug|Any CPU {674DE974-B134-4DB5-BFAF-7BC3D05E16DE}.Release|Any CPU.ActiveCfg = Release|Any CPU {674DE974-B134-4DB5-BFAF-7BC3D05E16DE}.Release|Any CPU.Build.0 = Release|Any CPU + {FC0D22D8-5F1B-4D85-8269-FA4837CDE3A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC0D22D8-5F1B-4D85-8269-FA4837CDE3A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC0D22D8-5F1B-4D85-8269-FA4837CDE3A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC0D22D8-5F1B-4D85-8269-FA4837CDE3A2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Windows/MassiveKnob/Hardware/AbstractMassiveKnobHardware.cs b/Windows/MassiveKnob/Hardware/AbstractMassiveKnobHardware.cs deleted file mode 100644 index 275bf14..0000000 --- a/Windows/MassiveKnob/Hardware/AbstractMassiveKnobHardware.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace MassiveKnob.Hardware -{ - public abstract class AbstractMassiveKnobHardware : IMassiveKnobHardware - { - protected ObserverProxy Observers = new ObserverProxy(); - - public void AttachObserver(IMassiveKnobHardwareObserver observer) - { - Observers.AttachObserver(observer); - } - - public void DetachObserver(IMassiveKnobHardwareObserver observer) - { - Observers.DetachObserver(observer); - } - - - public abstract Task TryConnect(); - public abstract Task Disconnect(); - - - public class ObserverProxy : IMassiveKnobHardwareObserver - { - private readonly List observers = new List(); - - - public void AttachObserver(IMassiveKnobHardwareObserver observer) - { - observers.Add(observer); - } - - public void DetachObserver(IMassiveKnobHardwareObserver observer) - { - observers.Remove(observer); - } - - - public void Connecting() - { - foreach (var observer in observers) - observer.Connecting(); - } - - - public void Connected(int knobCount) - { - foreach (var observer in observers) - observer.Connected(knobCount); - } - - - public void Disconnected() - { - foreach (var observer in observers) - observer.Disconnected(); - } - - - public void VolumeChanged(int knob, int volume) - { - foreach (var observer in observers) - observer.VolumeChanged(knob, volume); - } - } - } -} diff --git a/Windows/MassiveKnob/Hardware/CoreAudioDeviceManager.cs b/Windows/MassiveKnob/Hardware/CoreAudioDeviceManager.cs deleted file mode 100644 index 05421cb..0000000 --- a/Windows/MassiveKnob/Hardware/CoreAudioDeviceManager.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using AudioSwitcher.AudioApi; -using AudioSwitcher.AudioApi.CoreAudio; - -namespace MassiveKnob.Hardware -{ - public class CoreAudioDeviceManager : IAudioDeviceManager - { - private readonly Lazy audioController = new Lazy(); - private List devices; - - - public void Dispose() - { - if (audioController.IsValueCreated) - audioController.Value.Dispose(); - } - - - public async Task> GetDevices() - { - return devices ?? (devices = (await audioController.Value.GetPlaybackDevicesAsync()) - .Select(device => new AudioDevice(device) as IAudioDevice) - .ToList()); - } - - - public Task GetDeviceById(Guid deviceId) - { - return Task.FromResult(devices?.FirstOrDefault(device => device.Id == deviceId)); - } - - - private class AudioDevice : IAudioDevice - { - private readonly IDevice device; - - public Guid Id { get; } - public string DisplayName { get; } - - - public AudioDevice(IDevice device) - { - this.device = device; - Id = device.Id; - - string displayFormat; - - if ((device.State & DeviceState.Disabled) != 0) - displayFormat = Strings.DeviceDisplayNameDisabled; - else if ((device.State & DeviceState.Unplugged) != 0) - displayFormat = Strings.DeviceDisplayNameUnplugged; - else if ((device.State & DeviceState.NotPresent) != 0) - displayFormat = Strings.DeviceDisplayNameNotPresent; - else if ((device.State & DeviceState.Active) == 0) - displayFormat = Strings.DeviceDisplayNameInactive; - else - displayFormat = Strings.DeviceDisplayNameActive; - - DisplayName = string.Format(displayFormat, device.FullName); - } - - - public Task SetVolume(int volume) - { - return device.SetVolumeAsync(volume); - } - } - } - - - public class CoreAudioDeviceManagerFactory : IAudioDeviceManagerFactory - { - public IAudioDeviceManager Create() - { - return new CoreAudioDeviceManager(); - } - } -} diff --git a/Windows/MassiveKnob/Hardware/IAudioDeviceManager.cs b/Windows/MassiveKnob/Hardware/IAudioDeviceManager.cs deleted file mode 100644 index 1993e77..0000000 --- a/Windows/MassiveKnob/Hardware/IAudioDeviceManager.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace MassiveKnob.Hardware -{ - public interface IAudioDevice - { - Guid Id { get; } - string DisplayName { get; } - - Task SetVolume(int volume); - } - - - public interface IAudioDeviceManager : IDisposable - { - Task> GetDevices(); - Task GetDeviceById(Guid deviceId); - } - - - public interface IAudioDeviceManagerFactory - { - IAudioDeviceManager Create(); - } -} diff --git a/Windows/MassiveKnob/Hardware/IMassiveKnobHardware.cs b/Windows/MassiveKnob/Hardware/IMassiveKnobHardware.cs deleted file mode 100644 index 5421915..0000000 --- a/Windows/MassiveKnob/Hardware/IMassiveKnobHardware.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Threading.Tasks; - -namespace MassiveKnob.Hardware -{ - public interface IMassiveKnobHardwareObserver - { - void Connecting(); - void Connected(int knobCount); - void Disconnected(); - - void VolumeChanged(int knob, int volume); - // void ButtonPress(int index); -- for switching the active device, TBD - } - - - public interface IMassiveKnobHardware - { - void AttachObserver(IMassiveKnobHardwareObserver observer); - void DetachObserver(IMassiveKnobHardwareObserver observer); - - Task TryConnect(); - Task Disconnect(); - // Task SetActiveKnob(int knob); -- for providing LED feedback when switching the active device, TBD - } - - - public interface IMassiveKnobHardwareFactory - { - IMassiveKnobHardware Create(string portName); - } -} diff --git a/Windows/MassiveKnob/Hardware/MockMassiveKnobHardware.cs b/Windows/MassiveKnob/Hardware/MockMassiveKnobHardware.cs deleted file mode 100644 index 798bec3..0000000 --- a/Windows/MassiveKnob/Hardware/MockMassiveKnobHardware.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace MassiveKnob.Hardware -{ - public class MockMassiveKnobHardware : AbstractMassiveKnobHardware - { - private readonly int knobCount; - private readonly TimeSpan volumeChangeInterval; - private readonly int maxVolume; - private Timer changeVolumeTimer; - private readonly Random random = new Random(); - - - public MockMassiveKnobHardware(int knobCount, TimeSpan volumeChangeInterval, int maxVolume) - { - this.knobCount = knobCount; - this.volumeChangeInterval = volumeChangeInterval; - this.maxVolume = maxVolume; - } - - - public override async Task TryConnect() - { - if (changeVolumeTimer != null) - return; - - await Task.Delay(2000); - - Observers.Connected(knobCount); - changeVolumeTimer = new Timer( - state => - { - Observers.VolumeChanged(random.Next(0, knobCount), random.Next(0, maxVolume)); - }, - null, - volumeChangeInterval, - volumeChangeInterval); - } - - - public override Task Disconnect() - { - changeVolumeTimer?.Dispose(); - return Task.CompletedTask; - } - } - - - // ReSharper disable once UnusedMember.Global - for testing purposes only - public class MockMassiveKnobHardwareFactory : IMassiveKnobHardwareFactory - { - private readonly int knobCount; - private readonly TimeSpan volumeChangeInterval; - private readonly int maxVolume; - - public MockMassiveKnobHardwareFactory(int knobCount, TimeSpan volumeChangeInterval, int maxVolume) - { - this.knobCount = knobCount; - this.volumeChangeInterval = volumeChangeInterval; - this.maxVolume = maxVolume; - } - - - public IMassiveKnobHardware Create(string portName) - { - return new MockMassiveKnobHardware(knobCount, volumeChangeInterval, maxVolume); - } - } -} diff --git a/Windows/MassiveKnob/Hardware/SerialMassiveKnobHardware.cs b/Windows/MassiveKnob/Hardware/SerialMassiveKnobHardware.cs deleted file mode 100644 index ebd6a41..0000000 --- a/Windows/MassiveKnob/Hardware/SerialMassiveKnobHardware.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System.Diagnostics; -using System.IO.Ports; -using System.Threading; -using System.Threading.Tasks; - -namespace MassiveKnob.Hardware -{ - public class SerialMassiveKnobHardware : AbstractMassiveKnobHardware - { - private readonly string portName; - private Thread workerThread; - - private readonly CancellationTokenSource workerThreadCancellation = new CancellationTokenSource(); - private readonly TaskCompletionSource workerThreadCompleted = new TaskCompletionSource(); - - - public SerialMassiveKnobHardware(string portName) - { - this.portName = portName; - } - - - public override async Task TryConnect() - { - if (workerThread != null) - await Disconnect(); - - workerThread = new Thread(RunWorker) - { - Name = "SerialMassiveKnobHardware Worker" - }; - workerThread.Start(); - } - - - public override async Task Disconnect() - { - workerThreadCancellation.Cancel(); - await workerThreadCompleted.Task; - - workerThread = null; - } - - - private void RunWorker() - { - Observers.Connecting(); - - while (!workerThreadCancellation.IsCancellationRequested) - { - SerialPort serialPort = null; - - void SafeCloseSerialPort() - { - try - { - serialPort?.Dispose(); - } - catch - { - // ignroed - } - - serialPort = null; - Observers.Disconnected(); - Observers.Connecting(); - } - - - var knobCount = 0; - - while (serialPort == null && !workerThreadCancellation.IsCancellationRequested) - { - try - { - serialPort = new SerialPort(portName, 115200); - serialPort.Open(); - - // Send handshake - serialPort.Write(new[] { 'H', 'M', 'K', 'B' }, 0, 4); - - // Wait for reply - serialPort.ReadTimeout = 1000; - var response = serialPort.ReadByte(); - - if ((char) response == 'H') - { - knobCount = serialPort.ReadByte(); - if (knobCount > -1) - break; - } - - SafeCloseSerialPort(); - Thread.Sleep(500); - } - catch - { - SafeCloseSerialPort(); - Thread.Sleep(500); - } - } - - if (workerThreadCancellation.IsCancellationRequested) - { - SafeCloseSerialPort(); - break; - } - - var processingMessage = false; - - Debug.Assert(serialPort != null, nameof(serialPort) + " != null"); - serialPort.DataReceived += (sender, args) => - { - if (args.EventType != SerialData.Chars || processingMessage) - return; - - var senderPort = (SerialPort) sender; - processingMessage = true; - try - { - var message = (char) senderPort.ReadByte(); - ProcessMessage(senderPort, message); - } - finally - { - processingMessage = false; - } - }; - - - Observers.Connected(knobCount); - try - { - // This is where sending data to the hardware would be implemented - while (serialPort.IsOpen && !workerThreadCancellation.IsCancellationRequested) - { - Thread.Sleep(10); - } - } - catch - { - // ignored - } - - Observers.Disconnected(); - SafeCloseSerialPort(); - - if (!workerThreadCancellation.IsCancellationRequested) - Thread.Sleep(500); - } - - workerThreadCompleted.TrySetResult(true); - } - - - private void ProcessMessage(SerialPort serialPort, char message) - { - switch (message) - { - case 'V': - var knobIndex = (byte)serialPort.ReadByte(); - var volume = (byte)serialPort.ReadByte(); - - if (knobIndex < 255 && volume <= 100) - Observers.VolumeChanged(knobIndex, volume); - - break; - } - } - } - - - public class SerialMassiveKnobHardwareFactory : IMassiveKnobHardwareFactory - { - public IMassiveKnobHardware Create(string portName) - { - return new SerialMassiveKnobHardware(portName); - } - } -} diff --git a/Windows/MassiveKnob/Helpers/ComboBoxTemplateSelector.cs b/Windows/MassiveKnob/Helpers/ComboBoxTemplateSelector.cs index adda8da..303ce8f 100644 --- a/Windows/MassiveKnob/Helpers/ComboBoxTemplateSelector.cs +++ b/Windows/MassiveKnob/Helpers/ComboBoxTemplateSelector.cs @@ -24,7 +24,7 @@ namespace MassiveKnob.Helpers itemToCheck = VisualTreeHelper.GetParent(itemToCheck); // If you stopped at a ComboBoxItem, you're in the dropdown - var inDropDown = (itemToCheck is ComboBoxItem); + var inDropDown = itemToCheck is ComboBoxItem; return inDropDown ? DropdownItemsTemplate ?? DropdownItemsTemplateSelector?.SelectTemplate(item, container) @@ -33,6 +33,7 @@ namespace MassiveKnob.Helpers } + // ReSharper disable once UnusedMember.Global - used in XAML public class ComboBoxTemplateSelectorExtension : MarkupExtension { public DataTemplate SelectedItemTemplate { get; set; } @@ -42,7 +43,7 @@ namespace MassiveKnob.Helpers public override object ProvideValue(IServiceProvider serviceProvider) { - return new ComboBoxTemplateSelector() + return new ComboBoxTemplateSelector { SelectedItemTemplate = SelectedItemTemplate, SelectedItemTemplateSelector = SelectedItemTemplateSelector, diff --git a/Windows/MassiveKnob/Helpers/DelegateCommand.cs b/Windows/MassiveKnob/Helpers/DelegateCommand.cs index e895714..8266c22 100644 --- a/Windows/MassiveKnob/Helpers/DelegateCommand.cs +++ b/Windows/MassiveKnob/Helpers/DelegateCommand.cs @@ -1,4 +1,5 @@ -using System; +/* +using System; using System.Windows.Input; namespace MassiveKnob.Helpers @@ -78,3 +79,4 @@ namespace MassiveKnob.Helpers } } } +*/ \ No newline at end of file diff --git a/Windows/MassiveKnob/Helpers/SerialQueue.cs b/Windows/MassiveKnob/Helpers/SerialQueue.cs new file mode 100644 index 0000000..25194bf --- /dev/null +++ b/Windows/MassiveKnob/Helpers/SerialQueue.cs @@ -0,0 +1,65 @@ +using System; +using System.Threading.Tasks; + +// Original source: https://github.com/Gentlee/SerialQueue +// ReSharper disable UnusedMember.Global - public API + +namespace MassiveKnob.Helpers +{ + public class SerialQueue + { + private readonly object locker = new object(); + private readonly WeakReference lastTaskWeakRef = new WeakReference(null); + + public Task Enqueue(Action action) + { + return Enqueue(() => + { + action(); + return true; + }); + } + + public Task Enqueue(Func function) + { + lock (locker) + { + var resultTask = lastTaskWeakRef.TryGetTarget(out var lastTask) + ? lastTask.ContinueWith(_ => function(), TaskContinuationOptions.ExecuteSynchronously) + : Task.Run(function); + + lastTaskWeakRef.SetTarget(resultTask); + + return resultTask; + } + } + + public Task Enqueue(Func asyncAction) + { + lock (locker) + { + var resultTask = lastTaskWeakRef.TryGetTarget(out var lastTask) + ? lastTask.ContinueWith(_ => asyncAction(), TaskContinuationOptions.ExecuteSynchronously).Unwrap() + : Task.Run(asyncAction); + + lastTaskWeakRef.SetTarget(resultTask); + + return resultTask; + } + } + + public Task Enqueue(Func> asyncFunction) + { + lock (locker) + { + var resultTask = lastTaskWeakRef.TryGetTarget(out var lastTask) + ? lastTask.ContinueWith(_ => asyncFunction(), TaskContinuationOptions.ExecuteSynchronously).Unwrap() + : Task.Run(asyncFunction); + + lastTaskWeakRef.SetTarget(resultTask); + + return resultTask; + } + } + } +} diff --git a/Windows/MassiveKnob/MassiveKnob.csproj b/Windows/MassiveKnob/MassiveKnob.csproj index fb0171e..6d627cd 100644 --- a/Windows/MassiveKnob/MassiveKnob.csproj +++ b/Windows/MassiveKnob/MassiveKnob.csproj @@ -58,19 +58,20 @@ - - - - - - + + + + + + InputOutputView.xaml + SettingsWindow.xaml @@ -116,6 +117,9 @@ 5.2.1 + + 5.0.0 + @@ -136,6 +140,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/Windows/MassiveKnob/Model/IMassiveKnobOrchestrator.cs b/Windows/MassiveKnob/Model/IMassiveKnobOrchestrator.cs index a818b26..e0737a3 100644 --- a/Windows/MassiveKnob/Model/IMassiveKnobOrchestrator.cs +++ b/Windows/MassiveKnob/Model/IMassiveKnobOrchestrator.cs @@ -1,11 +1,44 @@ -using MassiveKnob.Plugin; +using System; +using MassiveKnob.Plugin; namespace MassiveKnob.Model { - public interface IMassiveKnobOrchestrator + public interface IMassiveKnobOrchestrator : IDisposable { - IMassiveKnobDeviceInstance ActiveDeviceInstance { get; } + MassiveKnobDeviceInfo ActiveDevice { get; } + IObservable ActiveDeviceSubject { get; } - IMassiveKnobDeviceInstance SetActiveDevice(IMassiveKnobDevice device); + MassiveKnobDeviceInfo SetActiveDevice(IMassiveKnobDevice device); + + MassiveKnobActionInfo GetAction(MassiveKnobActionType actionType, int index); + MassiveKnobActionInfo SetAction(MassiveKnobActionType actionType, int index, IMassiveKnobAction action); + } + + + public class MassiveKnobDeviceInfo + { + public IMassiveKnobDevice Info { get; } + public IMassiveKnobDeviceInstance Instance { get; } + public DeviceSpecs? Specs { get; } + + public MassiveKnobDeviceInfo(IMassiveKnobDevice info, IMassiveKnobDeviceInstance instance, DeviceSpecs? specs) + { + Info = info; + Instance = instance; + Specs = specs; + } + } + + + public class MassiveKnobActionInfo + { + public IMassiveKnobAction Info { get; } + public IMassiveKnobActionInstance Instance { get; } + + public MassiveKnobActionInfo(IMassiveKnobAction info, IMassiveKnobActionInstance instance) + { + Info = info; + Instance = instance; + } } } diff --git a/Windows/MassiveKnob/Model/IPluginManager.cs b/Windows/MassiveKnob/Model/IPluginManager.cs index 87e185d..9f6a2c4 100644 --- a/Windows/MassiveKnob/Model/IPluginManager.cs +++ b/Windows/MassiveKnob/Model/IPluginManager.cs @@ -5,8 +5,7 @@ namespace MassiveKnob.Model { public interface IPluginManager { - IEnumerable Plugins { get; } - IEnumerable GetDevicePlugins(); + IEnumerable GetActionPlugins(); } } diff --git a/Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs b/Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs index 87e2de8..2654904 100644 --- a/Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs +++ b/Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs @@ -1,51 +1,548 @@ -using MassiveKnob.Plugin; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Subjects; +using MassiveKnob.Helpers; +using MassiveKnob.Plugin; +using MassiveKnob.Settings; +using Newtonsoft.Json.Linq; namespace MassiveKnob.Model { public class MassiveKnobOrchestrator : IMassiveKnobOrchestrator { - private readonly Settings.Settings settings; + private readonly IPluginManager pluginManager; + + private readonly object settingsLock = new object(); + private Settings.Settings settings; + private readonly SerialQueue flushSettingsQueue = new SerialQueue(); + + private MassiveKnobDeviceInfo activeDevice; + private readonly Subject activeDeviceInfoSubject = new Subject(); + private IMassiveKnobDeviceContext activeDeviceContext; + + private readonly List analogInputs = new List(); + private readonly List digitalInputs = new List(); + private readonly List analogOutputs = new List(); + private readonly List digitalOutputs = new List(); - public IMassiveKnobDeviceInstance ActiveDeviceInstance { get; private set; } - - - public MassiveKnobOrchestrator(Settings.Settings settings) + public MassiveKnobDeviceInfo ActiveDevice { - this.settings = settings; - } - - - public IMassiveKnobDeviceInstance SetActiveDevice(IMassiveKnobDevice device) - { - ActiveDeviceInstance?.Dispose(); - ActiveDeviceInstance = device?.Create(new Context(settings)); - - return ActiveDeviceInstance; - } - - - - public class Context : IMassiveKnobContext - { - private readonly Settings.Settings settings; - - - public Context(Settings.Settings settings) + get => activeDevice; + private set { - this.settings = settings; + if (value == activeDevice) + return; + + activeDevice = value; + activeDeviceInfoSubject.OnNext(activeDevice); + } + } + + public IObservable ActiveDeviceSubject => activeDeviceInfoSubject; + + + public MassiveKnobOrchestrator(IPluginManager pluginManager) + { + this.pluginManager = pluginManager; + } + + + public void Dispose() + { + activeDevice?.Instance?.Dispose(); + + void DisposeMappings(IEnumerable mappings) + { + foreach (var mapping in mappings) + mapping?.ActionInfo.Instance?.Dispose(); + } + + + DisposeMappings(analogInputs); + DisposeMappings(digitalInputs); + DisposeMappings(analogOutputs); + DisposeMappings(digitalOutputs); + + activeDeviceInfoSubject?.Dispose(); + } + + + public void Load() + { + lock (settingsLock) + { + settings = SettingsJsonSerializer.Deserialize(); + + if (settings.Device == null) + return; + + var allDevices = pluginManager.GetDevicePlugins().SelectMany(dp => dp.Devices); + var device = allDevices.FirstOrDefault(d => d.DeviceId == settings.Device.DeviceId); + + InternalSetActiveDevice(device, false); + } + } + + + MassiveKnobDeviceInfo IMassiveKnobOrchestrator.ActiveDevice => activeDevice; + + public MassiveKnobDeviceInfo SetActiveDevice(IMassiveKnobDevice device) + { + return InternalSetActiveDevice(device, true); + } + + + public MassiveKnobActionInfo GetAction(MassiveKnobActionType actionType, int index) + { + var list = GetActionMappingList(actionType); + return index >= list.Count ? null : list[index].ActionInfo; + } + + + public MassiveKnobActionInfo SetAction(MassiveKnobActionType actionType, int index, IMassiveKnobAction action) + { + var list = GetActionMappingList(actionType); + if (index >= list.Count) + return null; + + if (list[index]?.ActionInfo.Info == action) + return list[index].ActionInfo; + + list[index]?.ActionInfo.Instance?.Dispose(); + + lock (settingsLock) + { + var settingsList = GetActionSettingsList(actionType); + while (index >= settingsList.Count) + settingsList.Add(null); + + settingsList[index] = action == null ? null : new Settings.Settings.ActionSettings + { + ActionId = action.ActionId, + Settings = null + }; + } + + FlushSettings(); + + + Action initializeAfterRegistration = null; + var mapping = CreateActionMapping(action, index, (actionInstance, actionContext) => + { + initializeAfterRegistration = () => actionInstance.Initialize(actionContext); + }); + + list[index] = mapping; + initializeAfterRegistration?.Invoke(); + + return mapping?.ActionInfo; + } + + + private MassiveKnobDeviceInfo InternalSetActiveDevice(IMassiveKnobDevice device, bool resetSettings) + { + if (device == ActiveDevice?.Info) + return ActiveDevice; + + + if (resetSettings) + { + lock (settingsLock) + { + if (device == null) + settings.Device = null; + else + { + settings.Device = new Settings.Settings.DeviceSettings + { + DeviceId = device.DeviceId, + Settings = null + }; + } + } + + FlushSettings(); + } + + ActiveDevice?.Instance.Dispose(); + + if (device != null) + { + var instance = device.Create(); + ActiveDevice = new MassiveKnobDeviceInfo(device, instance, null); + + activeDeviceContext = new DeviceContext(this, device); + instance.Initialize(activeDeviceContext); + } + else + { + ActiveDevice = null; + activeDeviceContext = null; + } + + return ActiveDevice; + } + + + protected T GetDeviceSettings(IMassiveKnobDeviceContext context) where T : class, new() + { + if (context != activeDeviceContext) + throw new InvalidOperationException("Caller must be the active device to retrieve the settings"); + + lock (settingsLock) + { + return settings.Device.Settings?.ToObject() ?? new T(); + } + } + + + protected void SetDeviceSettings(IMassiveKnobDeviceContext context, IMassiveKnobDevice device, T deviceSettings) where T : class, new() + { + if (context != activeDeviceContext) + throw new InvalidOperationException("Caller must be the active device to update the settings"); + + lock (settingsLock) + { + if (settings.Device == null) + settings.Device = new Settings.Settings.DeviceSettings + { + DeviceId = device.DeviceId + }; + + settings.Device.Settings = JObject.FromObject(deviceSettings); + } + + FlushSettings(); + } + + + protected T GetActionSettings(IMassiveKnobActionContext context, IMassiveKnobAction action, int index) where T : class, new() + { + var list = GetActionMappingList(action.ActionType); + if (index >= list.Count) + return new T(); + + if (list[index]?.Context != context) + throw new InvalidOperationException("Caller must be the active action to retrieve the settings"); + + lock (settingsLock) + { + var settingsList = GetActionSettingsList(action.ActionType); + if (index >= settingsList.Count) + return new T(); + + return settingsList[index].Settings?.ToObject() ?? new T(); + } + } + + + protected void SetActionSettings(IMassiveKnobActionContext context, IMassiveKnobAction action, int index, T actionSettings) where T : class, new() + { + var list = GetActionMappingList(action.ActionType); + if (index >= list.Count) + return; + + if (list[index].Context != context) + throw new InvalidOperationException("Caller must be the active action to retrieve the settings"); + + lock (settingsLock) + { + var settingsList = GetActionSettingsList(action.ActionType); + + while (index >= settingsList.Count) + settingsList.Add(null); + + if (settingsList[index] == null) + settingsList[index] = new Settings.Settings.ActionSettings + { + ActionId = action.ActionId + }; + + settingsList[index].Settings = JObject.FromObject(actionSettings); + } + + FlushSettings(); + } + + + protected void AnalogChanged(IMassiveKnobDeviceContext context, int analogInputIndex, byte value) + { + if (context != activeDeviceContext) + return; + + var mapping = GetActionMappingList(MassiveKnobActionType.InputAnalog); + if (mapping == null || analogInputIndex >= mapping.Count) + return; + + if (mapping[analogInputIndex].ActionInfo.Instance is IMassiveKnobAnalogAction analogAction) + analogAction.AnalogChanged(value); + } + + + protected void DigitalChanged(IMassiveKnobDeviceContext context, int digitalInputIndex, bool on) + { + if (context != activeDeviceContext) + return; + + var mapping = GetActionMappingList(MassiveKnobActionType.InputAnalog); + if (mapping == null || digitalInputIndex >= mapping.Count) + return; + + if (mapping[digitalInputIndex].ActionInfo.Instance is IMassiveKnobDigitalAction digitalAction) + digitalAction.DigitalChanged(on); + } + + + private List GetActionMappingList(MassiveKnobActionType actionType) + { + switch (actionType) + { + case MassiveKnobActionType.InputAnalog: + return analogInputs; + + case MassiveKnobActionType.InputDigital: + return digitalInputs; + + case MassiveKnobActionType.OutputAnalog: + return analogOutputs; + + case MassiveKnobActionType.OutputDigital: + return digitalOutputs; + + default: + throw new ArgumentOutOfRangeException(nameof(actionType), actionType, null); + } + } + + + private List GetActionSettingsList(MassiveKnobActionType actionType) + { + switch (actionType) + { + case MassiveKnobActionType.InputAnalog: + return settings.AnalogInput; + + case MassiveKnobActionType.InputDigital: + return settings.DigitalInput; + + case MassiveKnobActionType.OutputAnalog: + return settings.AnalogOutput; + + case MassiveKnobActionType.OutputDigital: + return settings.DigitalOutput; + + default: + throw new ArgumentOutOfRangeException(nameof(actionType), actionType, null); + } + } + + private void FlushSettings() + { + Settings.Settings settingsSnapshot; + + lock (settingsLock) + { + settingsSnapshot = settings.Clone(); + } + + flushSettingsQueue.Enqueue(async () => + { + await SettingsJsonSerializer.Serialize(settingsSnapshot); + }); + } + + + protected void UpdateActiveDeviceSpecs(IMassiveKnobDeviceContext context, DeviceSpecs specs) + { + if (context != activeDeviceContext) + return; + + + var delayedInitializeActions = new List(); + void DelayedInitialize(IMassiveKnobActionInstance instance, IMassiveKnobActionContext instanceContext) + { + delayedInitializeActions.Add(() => + { + instance.Initialize(instanceContext); + }); + } + + lock (settingsLock) + { + UpdateMapping(analogInputs, specs.AnalogInputCount, settings.AnalogInput, DelayedInitialize); + UpdateMapping(digitalInputs, specs.DigitalInputCount, settings.DigitalInput, DelayedInitialize); + UpdateMapping(analogOutputs, specs.AnalogOutputCount, settings.AnalogOutput, DelayedInitialize); + UpdateMapping(digitalOutputs, specs.DigitalOutputCount, settings.DigitalOutput, DelayedInitialize); + } + + foreach (var delayedInitializeAction in delayedInitializeActions) + delayedInitializeAction(); + + + ActiveDevice = new MassiveKnobDeviceInfo( + ActiveDevice.Info, + ActiveDevice.Instance, + specs); + } + + + private void UpdateMapping(List mapping, int newCount, List actionSettings, Action initializeOutsideLock) + { + if (mapping.Count > newCount) + { + for (var actionIndex = newCount; actionIndex < mapping.Count; actionIndex++) + mapping[actionIndex]?.ActionInfo.Instance?.Dispose(); + + mapping.RemoveRange(newCount, mapping.Count - newCount); + } + + if (actionSettings.Count > newCount) + actionSettings.RemoveRange(newCount, actionSettings.Count - newCount); + + + if (mapping.Count >= newCount) return; + { + var allActions = pluginManager.GetActionPlugins().SelectMany(ap => ap.Actions).ToArray(); + + for (var actionIndex = mapping.Count; actionIndex < newCount; actionIndex++) + { + if (actionIndex < actionSettings.Count && actionSettings[actionIndex] != null) + { + var action = allActions.FirstOrDefault(d => d.ActionId == actionSettings[actionIndex].ActionId); + mapping.Add(CreateActionMapping(action, actionIndex, initializeOutsideLock)); + } + else + mapping.Add(null); + } + } + } + + + private ActionMapping CreateActionMapping(IMassiveKnobAction action, int index, Action initialize) + { + if (action == null) + return null; + + var instance = action.Create(); + var context = new ActionContext(this, action, index); + + var mapping = new ActionMapping(new MassiveKnobActionInfo(action, instance), context); + initialize(instance, context); + + return mapping; + } + + + private class ActionMapping + { + public MassiveKnobActionInfo ActionInfo { get; } + public IMassiveKnobActionContext Context { get; } + + + public ActionMapping(MassiveKnobActionInfo actionInfo, IMassiveKnobActionContext context) + { + ActionInfo = actionInfo; + Context = context; + } + } + + + private class DeviceContext : IMassiveKnobDeviceContext + { + private readonly MassiveKnobOrchestrator owner; + private readonly IMassiveKnobDevice device; + + + public DeviceContext(MassiveKnobOrchestrator owner, IMassiveKnobDevice device) + { + this.owner = owner; + this.device = device; } + public T GetSettings() where T : class, new() { - // TODO - return default; + return owner.GetDeviceSettings(this); + } + + + public void SetSettings(T settings) where T : class, new() + { + owner.SetDeviceSettings(this, device, settings); + } + + + public void Connecting() + { + // TODO update status ? + } + + + public void Connected(DeviceSpecs specs) + { + // TODO update status ? + + owner.UpdateActiveDeviceSpecs(this, specs); + } + + + public void Disconnected() + { + // TODO update status ? + } + + + public void AnalogChanged(int analogInputIndex, byte value) + { + owner.AnalogChanged(this, analogInputIndex, value); + } + + + public void DigitalChanged(int digitalInputIndex, bool on) + { + owner.DigitalChanged(this, digitalInputIndex, on); + } + } + + + private class ActionContext : IMassiveKnobActionContext + { + private readonly MassiveKnobOrchestrator owner; + private readonly IMassiveKnobAction action; + private readonly int index; + + + public ActionContext(MassiveKnobOrchestrator owner, IMassiveKnobAction action, int index) + { + this.owner = owner; + this.action = action; + this.index = index; + } + + + public T GetSettings() where T : class, new() + { + return owner.GetActionSettings(this, action, index); + } + + + public void SetSettings(T settings) where T : class, new() + { + owner.SetActionSettings(this, action, index, settings); + } + + + public void SetDigitalOutput(bool on) + { + throw new NotImplementedException(); } - public void SetSettings(T settings) where T : class, new() + public void SetAnalogOutput(byte value) { - // TODO + throw new NotImplementedException(); } } } diff --git a/Windows/MassiveKnob/Model/PluginManager.cs b/Windows/MassiveKnob/Model/PluginManager.cs index 48fec68..1a2b27e 100644 --- a/Windows/MassiveKnob/Model/PluginManager.cs +++ b/Windows/MassiveKnob/Model/PluginManager.cs @@ -7,21 +7,46 @@ using MassiveKnob.Plugin; namespace MassiveKnob.Model { + public class MassiveKnobPluginIdConflictException : Exception + { + public Guid ConflictingId { get; } + public string FirstAssemblyFilename { get; } + public string ConflictingAssemblyFilename { get; } + + + public MassiveKnobPluginIdConflictException( + Guid conflictingId, + string firstAssemblyFilename, + string conflictingAssemblyFilename) + : base($"Conflicting ID {conflictingId} was already registered by {firstAssemblyFilename}.") + { + ConflictingId = conflictingId; + FirstAssemblyFilename = firstAssemblyFilename; + ConflictingAssemblyFilename = conflictingAssemblyFilename; + } + } + + public class PluginManager : IPluginManager { private readonly List plugins = new List(); - public IEnumerable Plugins => plugins; - public IEnumerable GetDevicePlugins() { return plugins.Where(p => p is IMassiveKnobDevicePlugin).Cast(); } - - public void Load() + public IEnumerable GetActionPlugins() { + return plugins.Where(p => p is IMassiveKnobActionPlugin).Cast(); + } + + + public void Load(Action onException) + { + var registeredIds = new RegisteredIds(); + var codeBase = Assembly.GetEntryAssembly()?.CodeBase; if (!string.IsNullOrEmpty(codeBase)) { @@ -29,17 +54,17 @@ namespace MassiveKnob.Model if (!string.IsNullOrEmpty(localPath)) { var applicationPluginPath = Path.Combine(localPath, @"Plugins"); - LoadPlugins(applicationPluginPath); + LoadPlugins(applicationPluginPath, registeredIds, onException); } } var localPluginPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob", @"Plugins"); - LoadPlugins(localPluginPath); + LoadPlugins(localPluginPath, registeredIds, onException); } - private void LoadPlugins(string path) + private void LoadPlugins(string path, RegisteredIds registeredIds, Action onException) { if (!Directory.Exists(path)) return; @@ -51,19 +76,17 @@ namespace MassiveKnob.Model try { var pluginAssembly = Assembly.LoadFrom(filename); - RegisterPlugins(pluginAssembly); + RegisterPlugins(filename, pluginAssembly, registeredIds); } catch (Exception e) { - // TODO report error -// Console.WriteLine(e); - throw; + onException(e, filename); } } } - private void RegisterPlugins(Assembly assembly) + private void RegisterPlugins(string filename, Assembly assembly, RegisteredIds registeredIds) { var pluginTypes = assembly.GetTypes().Where(t => t.GetCustomAttribute() != null); foreach (var pluginType in pluginTypes) @@ -71,9 +94,57 @@ namespace MassiveKnob.Model var pluginInstance = Activator.CreateInstance(pluginType); if (!(pluginInstance is IMassiveKnobPlugin)) throw new InvalidCastException($"Type {pluginType.FullName} claims to be a MassiveKnobPlugin but does not implement IMassiveKnobPlugin"); - + + ValidateRegistration(filename, (IMassiveKnobPlugin)pluginInstance, registeredIds); plugins.Add((IMassiveKnobPlugin)pluginInstance); } } + + + private static void ValidateRegistration(string filename, IMassiveKnobPlugin plugin, RegisteredIds registeredIds) + { + // Make sure all GUIDs are actually unique and someone has not copy/pasted a plugin without + // modifying the values. This way we can safely make that assumption in other code. + if (registeredIds.PluginById.TryGetValue(plugin.PluginId, out var conflictingPluginFilename)) + throw new MassiveKnobPluginIdConflictException(plugin.PluginId, conflictingPluginFilename, filename); + + registeredIds.PluginById.Add(plugin.PluginId, filename); + + + // ReSharper disable once ConvertIfStatementToSwitchStatement - no, a plugin can implement both interfaces + if (plugin is IMassiveKnobDevicePlugin devicePlugin) + { + foreach (var device in devicePlugin.Devices) + { + if (registeredIds.DeviceById.TryGetValue(device.DeviceId, out var conflictingDeviceFilename)) + throw new MassiveKnobPluginIdConflictException(device.DeviceId, conflictingDeviceFilename, filename); + + registeredIds.DeviceById.Add(device.DeviceId, filename); + } + } + + + // ReSharper disable once InvertIf + if (plugin is IMassiveKnobActionPlugin actionPlugin) + { + foreach (var action in actionPlugin.Actions) + { + if (registeredIds.ActionById.TryGetValue(action.ActionId, out var conflictingActionFilename)) + throw new MassiveKnobPluginIdConflictException(action.ActionId, conflictingActionFilename, filename); + + registeredIds.ActionById.Add(action.ActionId, filename); + + // TODO check ActionType vs. implemented interfaces + } + } + } + + + private class RegisteredIds + { + public readonly Dictionary PluginById = new Dictionary(); + public readonly Dictionary DeviceById = new Dictionary(); + public readonly Dictionary ActionById = new Dictionary(); + } } } diff --git a/Windows/MassiveKnob/Program.cs b/Windows/MassiveKnob/Program.cs index f28ec47..dadea3e 100644 --- a/Windows/MassiveKnob/Program.cs +++ b/Windows/MassiveKnob/Program.cs @@ -1,7 +1,7 @@ using System; -using System.Threading.Tasks; +using System.Text; +using System.Windows; using MassiveKnob.Model; -using MassiveKnob.Settings; using MassiveKnob.View; using MassiveKnob.ViewModel; using SimpleInjector; @@ -14,33 +14,42 @@ namespace MassiveKnob /// The main entry point for the application. /// [STAThread] - public static void Main() + public static int Main() { - MainAsync().GetAwaiter().GetResult(); - } + var pluginManager = new PluginManager(); + + var messages = new StringBuilder(); + pluginManager.Load((exception, filename) => + { + messages.AppendLine($"{filename}: {exception.Message}"); + }); + + if (messages.Length > 0) + { + MessageBox.Show($"Error while loading plugins:\r\n\r\n{messages}", "Massive Knob", MessageBoxButton.OK, MessageBoxImage.Error); + return 1; + } + + var orchestrator = new MassiveKnobOrchestrator(pluginManager); + orchestrator.Load(); - private static async Task MainAsync() - { var container = new Container(); container.Options.EnableAutoVerification = false; - container.RegisterSingleton(); + container.RegisterInstance(pluginManager); + container.RegisterInstance(orchestrator); container.Register(); container.Register(); container.Register(); - var settings = await SettingsJsonSerializer.Deserialize(); - container.RegisterInstance(settings); - - var pluginManager = new PluginManager(); - pluginManager.Load(); - container.RegisterInstance(pluginManager); - var app = container.GetInstance(); app.Run(); + + orchestrator.Dispose(); + return 0; } } } diff --git a/Windows/MassiveKnob/Settings/Settings.cs b/Windows/MassiveKnob/Settings/Settings.cs index 2ac71c3..37c8b24 100644 --- a/Windows/MassiveKnob/Settings/Settings.cs +++ b/Windows/MassiveKnob/Settings/Settings.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json.Linq; namespace MassiveKnob.Settings @@ -7,32 +8,67 @@ namespace MassiveKnob.Settings public class Settings { public DeviceSettings Device { get; set; } - public List Actions { get; set; } + public List AnalogInput { get; set; } + public List DigitalInput { get; set; } + public List AnalogOutput { get; set; } + public List DigitalOutput { get; set; } + + + public void Verify() + { + if (AnalogInput == null) AnalogInput = new List(); + if (DigitalInput == null) DigitalInput = new List(); + if (AnalogOutput == null) AnalogOutput = new List(); + if (DigitalOutput == null) DigitalOutput = new List(); + } - public static Settings Default() + public Settings Clone() { return new Settings { - Device = null, - Actions = new List() + Device = Device?.Clone(), + AnalogInput = AnalogInput.Select(a => a?.Clone()).ToList(), + DigitalInput = DigitalInput.Select(a => a?.Clone()).ToList(), + AnalogOutput = AnalogOutput.Select(a => a?.Clone()).ToList(), + DigitalOutput = DigitalOutput.Select(a => a?.Clone()).ToList() }; } public class DeviceSettings { - public Guid? PluginId { get; set; } public Guid? DeviceId { get; set; } public JObject Settings { get; set; } + + public DeviceSettings Clone() + { + return new DeviceSettings + { + DeviceId = DeviceId, + + // This is safe, as the JObject itself is never manipulated, only replaced + Settings = Settings + }; + } } public class ActionSettings { - public Guid PluginId { get; set; } public Guid ActionId { get; set; } public JObject Settings { get; set; } + + public ActionSettings Clone() + { + return new ActionSettings + { + ActionId = ActionId, + + // This is safe, as the JObject itself is never manipulated, only replaced + Settings = Settings + }; + } } } } diff --git a/Windows/MassiveKnob/Settings/SettingsJsonSerializer.cs b/Windows/MassiveKnob/Settings/SettingsJsonSerializer.cs index dddc346..57bfd7d 100644 --- a/Windows/MassiveKnob/Settings/SettingsJsonSerializer.cs +++ b/Windows/MassiveKnob/Settings/SettingsJsonSerializer.cs @@ -1,13 +1,25 @@ using System; +using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace MassiveKnob.Settings { public static class SettingsJsonSerializer { + private static readonly JsonSerializerSettings DefaultSettings = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + Converters = new List + { + new StringEnumConverter() + } + }; + + public static string GetDefaultFilename() { var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob"); @@ -24,7 +36,7 @@ namespace MassiveKnob.Settings public static async Task Serialize(Settings settings, string filename) { - var json = JsonConvert.SerializeObject(settings); + var json = JsonConvert.SerializeObject(settings, DefaultSettings); using (var stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Read, 4096, true)) using (var streamWriter = new StreamWriter(stream, Encoding.UTF8)) @@ -35,28 +47,34 @@ namespace MassiveKnob.Settings } - public static Task Deserialize() + public static Settings Deserialize() { return Deserialize(GetDefaultFilename()); } - public static async Task Deserialize(string filename) + public static Settings Deserialize(string filename) { - if (!File.Exists(filename)) - return Settings.Default(); + Settings settings = null; - string json; - - using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, true)) - using (var streamReader = new StreamReader(stream, Encoding.UTF8)) + if (File.Exists(filename)) { - json = await streamReader.ReadToEndAsync(); + string json; + + using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, true)) + using (var streamReader = new StreamReader(stream, Encoding.UTF8)) + { + json = streamReader.ReadToEnd(); + } + + if (!string.IsNullOrEmpty(json)) + settings = JsonConvert.DeserializeObject(json, DefaultSettings); } + + if (settings == null) + settings = new Settings(); - if (string.IsNullOrEmpty(json)) - return Settings.Default(); - - return JsonConvert.DeserializeObject(json); + settings.Verify(); + return settings; } } } diff --git a/Windows/MassiveKnob/Strings.Designer.cs b/Windows/MassiveKnob/Strings.Designer.cs index c5bf674..3611e3b 100644 --- a/Windows/MassiveKnob/Strings.Designer.cs +++ b/Windows/MassiveKnob/Strings.Designer.cs @@ -61,83 +61,11 @@ namespace MassiveKnob { } /// - /// Looks up a localized string similar to {0}. + /// Looks up a localized string similar to Not configured. /// - internal static string DeviceDisplayNameActive { + internal static string ActionNotConfigured { get { - return ResourceManager.GetString("DeviceDisplayNameActive", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} (Disabled). - /// - internal static string DeviceDisplayNameDisabled { - get { - return ResourceManager.GetString("DeviceDisplayNameDisabled", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} (Inactive). - /// - internal static string DeviceDisplayNameInactive { - get { - return ResourceManager.GetString("DeviceDisplayNameInactive", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} (Not present). - /// - internal static string DeviceDisplayNameNotPresent { - get { - return ResourceManager.GetString("DeviceDisplayNameNotPresent", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} (Unplugged). - /// - internal static string DeviceDisplayNameUnplugged { - get { - return ResourceManager.GetString("DeviceDisplayNameUnplugged", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Knob {0}. - /// - internal static string KnobIndex { - get { - return ResourceManager.GetString("KnobIndex", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connected. - /// - internal static string StatusConnected { - get { - return ResourceManager.GetString("StatusConnected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Connecting.... - /// - internal static string StatusConnecting { - get { - return ResourceManager.GetString("StatusConnecting", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Not connected. - /// - internal static string StatusNotConnected { - get { - return ResourceManager.GetString("StatusNotConnected", resourceCulture); + return ResourceManager.GetString("ActionNotConfigured", resourceCulture); } } } diff --git a/Windows/MassiveKnob/Strings.resx b/Windows/MassiveKnob/Strings.resx index 0576451..72ba0d9 100644 --- a/Windows/MassiveKnob/Strings.resx +++ b/Windows/MassiveKnob/Strings.resx @@ -117,31 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Connected - - - Connecting... - - - Not connected - - - Knob {0} - - - {0} - - - {0} (Disabled) - - - {0} (Inactive) - - - {0} (Not present) - - - {0} (Unplugged) + + Not configured \ No newline at end of file diff --git a/Windows/MassiveKnob/Style.xaml b/Windows/MassiveKnob/Style.xaml index 443fe67..b7a4d7e 100644 --- a/Windows/MassiveKnob/Style.xaml +++ b/Windows/MassiveKnob/Style.xaml @@ -1,5 +1,9 @@  + + @@ -12,10 +16,21 @@ + + + + diff --git a/Windows/MassiveKnob/View/InputOutputView.xaml b/Windows/MassiveKnob/View/InputOutputView.xaml new file mode 100644 index 0000000..4e8d2f0 --- /dev/null +++ b/Windows/MassiveKnob/View/InputOutputView.xaml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Windows/MassiveKnob/View/InputOutputView.xaml.cs b/Windows/MassiveKnob/View/InputOutputView.xaml.cs new file mode 100644 index 0000000..5d4d6a5 --- /dev/null +++ b/Windows/MassiveKnob/View/InputOutputView.xaml.cs @@ -0,0 +1,13 @@ +namespace MassiveKnob.View +{ + /// + /// Interaction logic for InputOutputView.xaml + /// + public partial class InputOutputView + { + public InputOutputView() + { + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob/View/SettingsWindow.xaml b/Windows/MassiveKnob/View/SettingsWindow.xaml index 601f5e4..2ac0269 100644 --- a/Windows/MassiveKnob/View/SettingsWindow.xaml +++ b/Windows/MassiveKnob/View/SettingsWindow.xaml @@ -4,9 +4,13 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:helpers="clr-namespace:MassiveKnob.Helpers" + xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel" + xmlns:view="clr-namespace:MassiveKnob.View" mc:Ignorable="d" Title="Massive Knob - Settings" Height="555" Width="704.231" - WindowStartupLocation="CenterScreen"> + WindowStartupLocation="CenterScreen" + Style="{StaticResource DefaultWindow}" + d:DataContext="{d:DesignInstance Type=viewModel:SettingsViewModel}"> @@ -14,32 +18,103 @@ - + - + - + + Device - + - + + - - Controls + + + Analog inputs + + + + + + + + + + + + Digital inputs + + + + + + + + + + + + Analog outputs + + + + + + + + + + + + Digital outputs + + + + + + + + diff --git a/Windows/MassiveKnob/ViewModel/ActionViewModel.cs b/Windows/MassiveKnob/ViewModel/ActionViewModel.cs new file mode 100644 index 0000000..e443ecb --- /dev/null +++ b/Windows/MassiveKnob/ViewModel/ActionViewModel.cs @@ -0,0 +1,29 @@ +using System.Windows; +using MassiveKnob.Plugin; + +namespace MassiveKnob.ViewModel +{ + public class ActionViewModel + { + // ReSharper disable UnusedMember.Global - used by WPF Binding + public string Name => RepresentsNull ? Strings.ActionNotConfigured : Action.Name; + public string Description => RepresentsNull ? null : Action.Description; + + public Visibility DescriptionVisibility => string.IsNullOrEmpty(Description) ? Visibility.Collapsed : Visibility.Visible; + // ReSharper restore UnusedMember.Global + + public IMassiveKnobActionPlugin Plugin { get; } + public IMassiveKnobAction Action { get; } + + public bool RepresentsNull => Action == null; + + + + public ActionViewModel(IMassiveKnobActionPlugin plugin, IMassiveKnobAction action) + { + Plugin = plugin; + Action = action; + } + } + +} diff --git a/Windows/MassiveKnob/ViewModel/DeviceViewModel.cs b/Windows/MassiveKnob/ViewModel/DeviceViewModel.cs new file mode 100644 index 0000000..d6769ea --- /dev/null +++ b/Windows/MassiveKnob/ViewModel/DeviceViewModel.cs @@ -0,0 +1,25 @@ +using System.Windows; +using MassiveKnob.Plugin; + +namespace MassiveKnob.ViewModel +{ + public class DeviceViewModel + { + // ReSharper disable UnusedMember.Global - used by WPF Binding + public string Name => Device.Name; + public string Description => Device.Description; + + public Visibility DescriptionVisibility => string.IsNullOrEmpty(Description) ? Visibility.Collapsed : Visibility.Visible; + // ReSharper restore UnusedMember.Global + + public IMassiveKnobDevicePlugin Plugin { get; } + public IMassiveKnobDevice Device { get; } + + + public DeviceViewModel(IMassiveKnobDevicePlugin plugin, IMassiveKnobDevice device) + { + Plugin = plugin; + Device = device; + } + } +} diff --git a/Windows/MassiveKnob/ViewModel/InputOutputViewModel.cs b/Windows/MassiveKnob/ViewModel/InputOutputViewModel.cs new file mode 100644 index 0000000..a31f5fa --- /dev/null +++ b/Windows/MassiveKnob/ViewModel/InputOutputViewModel.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Windows.Controls; +using MassiveKnob.Model; +using MassiveKnob.Plugin; + +namespace MassiveKnob.ViewModel +{ + public class InputOutputViewModel : INotifyPropertyChanged + { + private readonly SettingsViewModel settingsViewModel; + private readonly IMassiveKnobOrchestrator orchestrator; + private readonly MassiveKnobActionType actionType; + private readonly int index; + + private ActionViewModel selectedAction; + private UserControl actionSettingsControl; + + + // ReSharper disable UnusedMember.Global - used by WPF Binding + public string DisplayName => actionType == MassiveKnobActionType.OutputAnalog || actionType == MassiveKnobActionType.OutputDigital + ? $"Output #{index + 1}" + : $"Input #{index + 1}"; + + public IList Actions => settingsViewModel.Actions; + + + public ActionViewModel SelectedAction + { + get => selectedAction; + set + { + if (value == selectedAction) + return; + + selectedAction = value == null || value.RepresentsNull ? null : value; + var actionInfo = orchestrator.SetAction(actionType, index, selectedAction?.Action); + + OnPropertyChanged(); + + ActionSettingsControl = actionInfo?.Instance.CreateSettingsControl(); + } + } + + public UserControl ActionSettingsControl + { + get => actionSettingsControl; + set + { + if (value == actionSettingsControl) + return; + + actionSettingsControl = value; + OnPropertyChanged(); + } + } + // ReSharper restore UnusedMember.Global + + + public InputOutputViewModel(SettingsViewModel settingsViewModel, IMassiveKnobOrchestrator orchestrator, MassiveKnobActionType actionType, int index) + { + this.settingsViewModel = settingsViewModel; + this.orchestrator = orchestrator; + this.actionType = actionType; + this.index = index; + + var actionInfo = orchestrator.GetAction(actionType, index); + + selectedAction = actionInfo != null + ? Actions.SingleOrDefault(a => !a.RepresentsNull && a.Action.ActionId == actionInfo.Info.ActionId) + : Actions.Single(a => a.RepresentsNull); + + actionSettingsControl = actionInfo?.Instance.CreateSettingsControl(); + } + + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs b/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs index 4764a9b..0c4dcfb 100644 --- a/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs +++ b/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; +using System.Windows; using System.Windows.Controls; using MassiveKnob.Model; using MassiveKnob.Plugin; @@ -11,47 +12,42 @@ namespace MassiveKnob.ViewModel { public class SettingsViewModel : INotifyPropertyChanged { - private readonly Settings.Settings settings; private readonly IMassiveKnobOrchestrator orchestrator; private DeviceViewModel selectedDevice; private UserControl settingsControl; + private DeviceSpecs? specs; + private IEnumerable analogInputs; + private IEnumerable digitalInputs; + private IEnumerable analogOutputs; + private IEnumerable digitalOutputs; + + + // ReSharper disable UnusedMember.Global - used by WPF Binding + public IList Devices { get; } + public IList Actions { get; } + - public IEnumerable Devices { get; } public DeviceViewModel SelectedDevice { get => selectedDevice; - set { if (value == selectedDevice) return; selectedDevice = value; - var deviceInstance = orchestrator.SetActiveDevice(value?.Device); - - if (value == null) - settings.Device = null; - else - { - settings.Device = new Settings.Settings.DeviceSettings - { - PluginId = value.Plugin.PluginId, - DeviceId = value.Device.DeviceId, - Settings = null - }; - } + var deviceInfo = orchestrator.SetActiveDevice(value?.Device); OnPropertyChanged(); - SettingsControl = deviceInstance?.CreateSettingsControl(); + SettingsControl = deviceInfo?.Instance.CreateSettingsControl(); } } public UserControl SettingsControl { get => settingsControl; - set { if (value == settingsControl) @@ -62,20 +58,113 @@ namespace MassiveKnob.ViewModel } } - - - - public SettingsViewModel(IPluginManager pluginManager, Settings.Settings settings, IMassiveKnobOrchestrator orchestrator) + public DeviceSpecs? Specs + { + get => specs; + set + { + specs = value; + OnPropertyChanged(); + OnOtherPropertyChanged("AnalogInputVisibility"); + OnOtherPropertyChanged("DigitalInputVisibility"); + + AnalogInputs = Enumerable + .Range(0, specs?.AnalogInputCount ?? 0) + .Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.InputAnalog, i)); + + DigitalInputs = Enumerable + .Range(0, specs?.DigitalInputCount ?? 0) + .Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.InputDigital, i)); + } + } + + public Visibility AnalogInputVisibility => specs.HasValue && specs.Value.AnalogInputCount > 0 + ? Visibility.Visible + : Visibility.Collapsed; + + public IEnumerable AnalogInputs + { + get => analogInputs; + set + { + analogInputs = value; + OnPropertyChanged(); + } + } + + public Visibility DigitalInputVisibility => specs.HasValue && specs.Value.DigitalInputCount > 0 + ? Visibility.Visible + : Visibility.Collapsed; + + public IEnumerable DigitalInputs + { + get => digitalInputs; + set + { + digitalInputs = value; + OnPropertyChanged(); + } + } + + public Visibility AnalogOutputVisibility => specs.HasValue && specs.Value.AnalogOutputCount > 0 + ? Visibility.Visible + : Visibility.Collapsed; + + public IEnumerable AnalogOutputs + { + get => analogOutputs; + set + { + analogOutputs = value; + OnPropertyChanged(); + } + } + + public Visibility DigitalOutputVisibility => specs.HasValue && specs.Value.DigitalOutputCount > 0 + ? Visibility.Visible + : Visibility.Collapsed; + + public IEnumerable DigitalOutputs + { + get => digitalOutputs; + set + { + digitalOutputs = value; + OnPropertyChanged(); + } + } + // ReSharper restore UnusedMember.Global + + + + public SettingsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator) { - this.settings = settings; this.orchestrator = orchestrator; - Devices = pluginManager.GetDevicePlugins().SelectMany(dp => dp.Devices.Select(d => new DeviceViewModel(dp, d))); + orchestrator.ActiveDeviceSubject.Subscribe(info => { Specs = info.Specs; }); - if (settings.Device != null) - SelectedDevice = Devices.FirstOrDefault(d => - d.Plugin.PluginId == settings.Device.PluginId && - d.Device.DeviceId == settings.Device.DeviceId); + + Devices = pluginManager.GetDevicePlugins() + .SelectMany(dp => dp.Devices.Select(d => new DeviceViewModel(dp, d))) + .ToList(); + + var allActions = new List + { + new ActionViewModel(null, null) + }; + + allActions.AddRange( + pluginManager.GetActionPlugins() + .SelectMany(ap => ap.Actions.Select(a => new ActionViewModel(ap, a)))); + + Actions = allActions; + + if (orchestrator.ActiveDevice == null) + return; + + selectedDevice = Devices.Single(d => d.Device.DeviceId == orchestrator.ActiveDevice.Info.DeviceId); + SettingsControl = orchestrator.ActiveDevice.Instance.CreateSettingsControl(); + Specs = orchestrator.ActiveDevice.Specs; } @@ -86,24 +175,9 @@ namespace MassiveKnob.ViewModel PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } - - - public class DeviceViewModel + protected virtual void OnOtherPropertyChanged(string propertyName) { - // ReSharper disable UnusedMember.Global - used by WPF Binding - public string Name => Device.Name; - public string Description => Device.Description; - // ReSharper restore UnusedMember.Global - - public IMassiveKnobDevicePlugin Plugin { get; } - public IMassiveKnobDevice Device { get; } - - - public DeviceViewModel(IMassiveKnobDevicePlugin plugin, IMassiveKnobDevice device) - { - Plugin = plugin; - Device = device; - } + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } } From 28c25c8b43e03170d798d97697db8442350d210c Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Wed, 24 Feb 2021 19:35:01 +0100 Subject: [PATCH 03/10] Implemented OSD Implemented mute/unmute input and output actions Changed MockDevice to EmulatorDevice with UI for easier testing --- README.md | 4 +- .../Base/BaseDeviceSettings.cs | 12 + .../Base/BaseDeviceSettingsView.xaml | 19 + .../Base/BaseDeviceSettingsView.xaml.cs | 28 ++ .../BaseDeviceSettingsViewModel.cs | 33 +- .../GetMuted/DeviceGetMutedAction.cs | 82 ++++ .../GetMuted/DeviceGetMutedActionSettings.cs | 9 + .../DeviceGetMutedActionSettingsView.xaml} | 10 +- .../DeviceGetMutedActionSettingsView.xaml.cs | 14 + .../DeviceGetMutedActionSettingsViewModel.cs | 28 ++ .../GetVolume/DeviceGetVolumeAction.cs | 82 ++++ .../DeviceGetVolumeActionSettings.cs | 8 + .../DeviceGetVolumeActionSettingsView.xaml | 14 + .../DeviceGetVolumeActionSettingsView.xaml.cs | 16 + .../DeviceGetVolumeActionSettingsViewModel.cs | 12 + .../MassiveKnob.Plugin.CoreAudio.csproj | 73 ++- .../MassiveKnobCoreAudioPlugin.cs | 14 +- .../OSD/OSDManager.cs | 50 ++ .../OSD/OSDWindow.xaml | 55 +++ .../OSD/OSDWindow.xaml.cs | 49 ++ .../OSD/OSDWindowViewModel.cs | 92 ++++ .../OSD/SpeakerIcon.xaml | 52 +++ .../Resources/Muted.svg | 48 ++ .../Resources/OSDIcon.ai | 441 ++++++++++++++++++ .../SetMuted/DeviceSetMutedAction.cs | 87 ++++ .../SetMuted/DeviceSetMutedActionSettings.cs | 10 + .../DeviceSetMutedActionSettingsView.xaml | 14 + .../DeviceSetMutedActionSettingsView.xaml.cs | 14 + .../DeviceSetMutedActionSettingsViewModel.cs | 42 ++ .../DeviceSetVolumeAction.cs} | 21 +- .../DeviceSetVolumeActionSettings.cs | 9 + .../DeviceSetVolumeActionSettingsView.xaml | 14 + .../DeviceSetVolumeActionSettingsView.xaml.cs | 14 + .../DeviceSetVolumeActionSettingsViewModel.cs | 12 + .../Settings/BaseDeviceSettings.cs | 9 - .../Settings/DeviceVolumeActionSettings.cs | 7 - .../DeviceVolumeActionSettingsView.xaml.cs | 14 - .../DeviceVolumeActionSettingsViewModel.cs | 10 - .../Strings.Designer.cs | 160 ++++++- .../MassiveKnob.Plugin.CoreAudio/Strings.resx | 48 ++ .../Devices/EmulatorDevice.cs | 97 ++++ .../Devices/EmulatorDeviceWindow.xaml | 75 +++ .../Devices/EmulatorDeviceWindow.xaml.cs | 14 + .../Devices/EmulatorDeviceWindowViewModel.cs | 264 +++++++++++ .../MassiveKnob.Plugin.EmulatorDevice.csproj} | 28 +- .../MassiveKnobEmulatorDevicePlugin.cs} | 6 +- .../Properties/AssemblyInfo.cs | 0 .../Settings/EmulatorDeviceSettings.cs | 10 + .../Settings/EmulatorDeviceSettingsView.xaml | 34 ++ .../EmulatorDeviceSettingsView.xaml.cs | 15 + .../EmulatorDeviceSettingsViewModel.cs | 81 ++++ .../Devices/MockDevice.cs | 95 ---- .../Settings/MockDeviceSettings.cs | 9 - .../Settings/MockDeviceSettingsView.xaml | 30 -- .../Settings/MockDeviceSettingsView.xaml.cs | 15 - .../Settings/MockDeviceSettingsViewModel.cs | 67 --- .../Devices/SerialDevice.cs | 11 + .../Worker/SerialWorker.cs | 1 + .../IMassiveKnobDeviceInstance.cs | 14 + Windows/MassiveKnob.sln | 2 +- Windows/MassiveKnob.sln.DotSettings | 4 +- .../Model/MassiveKnobOrchestrator.cs | 56 ++- Windows/MassiveKnob/Style.xaml | 5 +- Windows/MassiveKnob/View/InputOutputView.xaml | 4 +- Windows/MassiveKnob/View/SettingsWindow.xaml | 2 +- .../ViewModel/InputOutputViewModel.cs | 5 +- .../ViewModel/SettingsViewModel.cs | 17 +- 67 files changed, 2357 insertions(+), 334 deletions(-) create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Base/BaseDeviceSettings.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Base/BaseDeviceSettingsView.xaml create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Base/BaseDeviceSettingsView.xaml.cs rename Windows/MassiveKnob.Plugin.CoreAudio/{Settings => Base}/BaseDeviceSettingsViewModel.cs (82%) create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedAction.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettings.cs rename Windows/MassiveKnob.Plugin.CoreAudio/{Settings/DeviceVolumeActionSettingsView.xaml => GetMuted/DeviceGetMutedActionSettingsView.xaml} (50%) create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettingsView.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettingsViewModel.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeAction.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeActionSettings.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeActionSettingsView.xaml create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeActionSettingsView.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeActionSettingsViewModel.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/OSD/OSDManager.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/OSD/OSDWindow.xaml create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/OSD/OSDWindow.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/OSD/OSDWindowViewModel.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/OSD/SpeakerIcon.xaml create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Resources/Muted.svg create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Resources/OSDIcon.ai create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedAction.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettings.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettingsView.xaml create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettingsView.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettingsViewModel.cs rename Windows/MassiveKnob.Plugin.CoreAudio/{Actions/DeviceVolumeAction.cs => SetVolume/DeviceSetVolumeAction.cs} (73%) create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeActionSettings.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeActionSettingsView.xaml create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeActionSettingsView.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeActionSettingsViewModel.cs delete mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Settings/BaseDeviceSettings.cs delete mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.cs delete mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsView.xaml.cs delete mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsViewModel.cs create mode 100644 Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDevice.cs create mode 100644 Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDeviceWindow.xaml create mode 100644 Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDeviceWindow.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDeviceWindowViewModel.cs rename Windows/{MassiveKnob.Plugin.MockDevice/MassiveKnob.Plugin.MockDevice.csproj => MassiveKnob.Plugin.EmulatorDevice/MassiveKnob.Plugin.EmulatorDevice.csproj} (73%) rename Windows/{MassiveKnob.Plugin.MockDevice/MassiveKnobMockDevicePlugin.cs => MassiveKnob.Plugin.EmulatorDevice/MassiveKnobEmulatorDevicePlugin.cs} (78%) rename Windows/{MassiveKnob.Plugin.MockDevice => MassiveKnob.Plugin.EmulatorDevice}/Properties/AssemblyInfo.cs (100%) create mode 100644 Windows/MassiveKnob.Plugin.EmulatorDevice/Settings/EmulatorDeviceSettings.cs create mode 100644 Windows/MassiveKnob.Plugin.EmulatorDevice/Settings/EmulatorDeviceSettingsView.xaml create mode 100644 Windows/MassiveKnob.Plugin.EmulatorDevice/Settings/EmulatorDeviceSettingsView.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.EmulatorDevice/Settings/EmulatorDeviceSettingsViewModel.cs delete mode 100644 Windows/MassiveKnob.Plugin.MockDevice/Devices/MockDevice.cs delete mode 100644 Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.cs delete mode 100644 Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsView.xaml delete mode 100644 Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsView.xaml.cs delete mode 100644 Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsViewModel.cs diff --git a/README.md b/README.md index 0f0d5cb..4bc27bc 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ Because of the second requirement, a simple media keys HID device does not suffi - by changing the Windows default output device - by running a VoiceMeeter macro 2. Corresponding LEDs to indicate the currently active device -3. OSD -4. API / plugins to use extra knobs and buttons for other purposes +3. ✔ OSD +4. ✔ API / plugins to use extra knobs and buttons for other purposes ## Developing The hardware side uses an Arduino sketch to communicate the hardware state over the serial port. diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Base/BaseDeviceSettings.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Base/BaseDeviceSettings.cs new file mode 100644 index 0000000..17622db --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Base/BaseDeviceSettings.cs @@ -0,0 +1,12 @@ +using System; + +namespace MassiveKnob.Plugin.CoreAudio.Base +{ + public class BaseDeviceSettings + { + public Guid? DeviceId { get; set; } + + // TODO more options, like positioning and style + public bool OSD { get; set; } = true; + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Base/BaseDeviceSettingsView.xaml b/Windows/MassiveKnob.Plugin.CoreAudio/Base/BaseDeviceSettingsView.xaml new file mode 100644 index 0000000..154cf72 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Base/BaseDeviceSettingsView.xaml @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Base/BaseDeviceSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Base/BaseDeviceSettingsView.xaml.cs new file mode 100644 index 0000000..2abe8e2 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Base/BaseDeviceSettingsView.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +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 MassiveKnob.Plugin.CoreAudio.Base +{ + /// + /// Interaction logic for BaseDeviceSettingsView.xaml + /// + public partial class BaseDeviceSettingsView : UserControl + { + public BaseDeviceSettingsView() + { + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/BaseDeviceSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Base/BaseDeviceSettingsViewModel.cs similarity index 82% rename from Windows/MassiveKnob.Plugin.CoreAudio/Settings/BaseDeviceSettingsViewModel.cs rename to Windows/MassiveKnob.Plugin.CoreAudio/Base/BaseDeviceSettingsViewModel.cs index 454bd84..9f89426 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/BaseDeviceSettingsViewModel.cs +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Base/BaseDeviceSettingsViewModel.cs @@ -7,11 +7,22 @@ using System.Threading.Tasks; using System.Windows; using AudioSwitcher.AudioApi; -namespace MassiveKnob.Plugin.CoreAudio.Settings +namespace MassiveKnob.Plugin.CoreAudio.Base { + public class BaseDeviceSettingsViewModel : BaseDeviceSettingsViewModel where T : BaseDeviceSettings + { + protected new T Settings => (T)base.Settings; + + public BaseDeviceSettingsViewModel(T settings) : base(settings) + { + } + } + + + public class BaseDeviceSettingsViewModel : INotifyPropertyChanged { - private readonly BaseDeviceSettings settings; + protected readonly BaseDeviceSettings Settings; public event PropertyChangedEventHandler PropertyChanged; private IList playbackDevices; @@ -37,7 +48,21 @@ namespace MassiveKnob.Plugin.CoreAudio.Settings return; selectedDevice = value; - settings.DeviceId = selectedDevice?.Id; + Settings.DeviceId = selectedDevice?.Id; + OnPropertyChanged(); + } + } + + + public bool OSD + { + get => Settings.OSD; + set + { + if (value == Settings.OSD) + return; + + Settings.OSD = value; OnPropertyChanged(); } } @@ -46,7 +71,7 @@ namespace MassiveKnob.Plugin.CoreAudio.Settings public BaseDeviceSettingsViewModel(BaseDeviceSettings settings) { - this.settings = settings; + Settings = settings; Task.Run(async () => { diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedAction.cs b/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedAction.cs new file mode 100644 index 0000000..dde57d3 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedAction.cs @@ -0,0 +1,82 @@ +using System; +using System.Windows.Controls; +using AudioSwitcher.AudioApi; +using MassiveKnob.Plugin.CoreAudio.OSD; + +namespace MassiveKnob.Plugin.CoreAudio.GetMuted +{ + public class DeviceGetMutedAction : IMassiveKnobAction + { + public Guid ActionId { get; } = new Guid("86646ca7-f472-4c5a-8d0f-7e5d2d162ab9"); + public MassiveKnobActionType ActionType { get; } = MassiveKnobActionType.OutputDigital; + public string Name { get; } = Strings.GetMutedName; + public string Description { get; } = Strings.GetMutedDescription; + + + public IMassiveKnobActionInstance Create() + { + return new Instance(); + } + + + private class Instance : IMassiveKnobActionInstance + { + private IMassiveKnobActionContext actionContext; + private DeviceGetMutedActionSettings settings; + private IDevice playbackDevice; + private IDisposable deviceChanged; + + + public void Initialize(IMassiveKnobActionContext context) + { + actionContext = context; + settings = context.GetSettings(); + ApplySettings(); + } + + + public void Dispose() + { + deviceChanged?.Dispose(); + } + + + private void ApplySettings() + { + if (playbackDevice != null && playbackDevice.Id == settings.DeviceId) + return; + + var coreAudioController = CoreAudioControllerInstance.Acquire(); + playbackDevice = settings.DeviceId.HasValue ? coreAudioController.GetDevice(settings.DeviceId.Value) : null; + + deviceChanged?.Dispose(); + deviceChanged = playbackDevice?.MuteChanged.Subscribe(MuteChanged); + } + + + public UserControl CreateSettingsControl() + { + var viewModel = new DeviceGetMutedActionSettingsViewModel(settings); + viewModel.PropertyChanged += (sender, args) => + { + if (!viewModel.IsSettingsProperty(args.PropertyName)) + return; + + actionContext.SetSettings(settings); + ApplySettings(); + }; + + return new DeviceGetMutedActionSettingsView(viewModel); + } + + + private void MuteChanged(DeviceMuteChangedArgs args) + { + actionContext.SetDigitalOutput(settings.Inverted ? !args.IsMuted : args.IsMuted); + + if (settings.OSD) + OSDManager.Show(args.Device); + } + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettings.cs b/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettings.cs new file mode 100644 index 0000000..51a0bd5 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettings.cs @@ -0,0 +1,9 @@ +using MassiveKnob.Plugin.CoreAudio.Base; + +namespace MassiveKnob.Plugin.CoreAudio.GetMuted +{ + public class DeviceGetMutedActionSettings : BaseDeviceSettings + { + public bool Inverted { get; set; } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsView.xaml b/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettingsView.xaml similarity index 50% rename from Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsView.xaml rename to Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettingsView.xaml index b1485b0..aef700b 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsView.xaml +++ b/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettingsView.xaml @@ -1,14 +1,14 @@ - + d:DataContext="{d:DesignInstance getMuted:DeviceGetMutedActionSettingsViewModel}"> - Playback device - + diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettingsView.xaml.cs new file mode 100644 index 0000000..067abf0 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettingsView.xaml.cs @@ -0,0 +1,14 @@ +namespace MassiveKnob.Plugin.CoreAudio.GetMuted +{ + /// + /// Interaction logic for DeviceGetMutedActionSettingsView.xaml + /// + public partial class DeviceGetMutedActionSettingsView + { + public DeviceGetMutedActionSettingsView(DeviceGetMutedActionSettingsViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettingsViewModel.cs new file mode 100644 index 0000000..1f52f80 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettingsViewModel.cs @@ -0,0 +1,28 @@ +using MassiveKnob.Plugin.CoreAudio.Base; + +namespace MassiveKnob.Plugin.CoreAudio.GetMuted +{ + public class DeviceGetMutedActionSettingsViewModel : BaseDeviceSettingsViewModel + { + // ReSharper disable UnusedMember.Global - used by WPF Binding + public bool Inverted + { + get => Settings.Inverted; + set + { + if (value == Settings.Inverted) + return; + + Settings.Inverted = value; + OnPropertyChanged(); + } + } + // ReSharper restore UnusedMember.Global + + + // ReSharper disable once SuggestBaseTypeForParameter - by design + public DeviceGetMutedActionSettingsViewModel(DeviceGetMutedActionSettings settings) : base(settings) + { + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeAction.cs b/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeAction.cs new file mode 100644 index 0000000..a8c2cb5 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeAction.cs @@ -0,0 +1,82 @@ +using System; +using System.Windows.Controls; +using AudioSwitcher.AudioApi; +using MassiveKnob.Plugin.CoreAudio.OSD; + +namespace MassiveKnob.Plugin.CoreAudio.GetVolume +{ + public class DeviceGetVolumeAction : IMassiveKnobAction + { + public Guid ActionId { get; } = new Guid("6ebf91af-8240-4a75-9729-c6a1eb60dcba"); + public MassiveKnobActionType ActionType { get; } = MassiveKnobActionType.OutputAnalog; + public string Name { get; } = Strings.GetVolumeName; + public string Description { get; } = Strings.GetVolumeDescription; + + + public IMassiveKnobActionInstance Create() + { + return new Instance(); + } + + + private class Instance : IMassiveKnobActionInstance + { + private IMassiveKnobActionContext actionContext; + private DeviceGetVolumeActionSettings settings; + private IDevice playbackDevice; + private IDisposable deviceChanged; + + + public void Initialize(IMassiveKnobActionContext context) + { + actionContext = context; + settings = context.GetSettings(); + ApplySettings(); + } + + + public void Dispose() + { + deviceChanged?.Dispose(); + } + + + private void ApplySettings() + { + if (playbackDevice != null && playbackDevice.Id == settings.DeviceId) + return; + + var coreAudioController = CoreAudioControllerInstance.Acquire(); + playbackDevice = settings.DeviceId.HasValue ? coreAudioController.GetDevice(settings.DeviceId.Value) : null; + + deviceChanged?.Dispose(); + deviceChanged = playbackDevice?.VolumeChanged.Subscribe(VolumeChanged); + } + + + public UserControl CreateSettingsControl() + { + var viewModel = new DeviceGetVolumeActionSettingsViewModel(settings); + viewModel.PropertyChanged += (sender, args) => + { + if (!viewModel.IsSettingsProperty(args.PropertyName)) + return; + + actionContext.SetSettings(settings); + ApplySettings(); + }; + + return new DeviceGetVolumeActionSettingsView(viewModel); + } + + + private void VolumeChanged(DeviceVolumeChangedArgs args) + { + actionContext.SetAnalogOutput((byte)args.Volume); + + if (settings.OSD) + OSDManager.Show(args.Device); + } + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeActionSettings.cs b/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeActionSettings.cs new file mode 100644 index 0000000..0c5c672 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeActionSettings.cs @@ -0,0 +1,8 @@ +using MassiveKnob.Plugin.CoreAudio.Base; + +namespace MassiveKnob.Plugin.CoreAudio.GetVolume +{ + public class DeviceGetVolumeActionSettings : BaseDeviceSettings + { + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeActionSettingsView.xaml b/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeActionSettingsView.xaml new file mode 100644 index 0000000..eb60b35 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeActionSettingsView.xaml @@ -0,0 +1,14 @@ + + + + + diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeActionSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeActionSettingsView.xaml.cs new file mode 100644 index 0000000..d45f6f1 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeActionSettingsView.xaml.cs @@ -0,0 +1,16 @@ +using MassiveKnob.Plugin.CoreAudio.SetMuted; + +namespace MassiveKnob.Plugin.CoreAudio.GetVolume +{ + /// + /// Interaction logic for DeviceGetVolumeActionSettingsView.xaml + /// + public partial class DeviceGetVolumeActionSettingsView + { + public DeviceGetVolumeActionSettingsView(DeviceGetVolumeActionSettingsViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeActionSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeActionSettingsViewModel.cs new file mode 100644 index 0000000..49d3b87 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeActionSettingsViewModel.cs @@ -0,0 +1,12 @@ +using MassiveKnob.Plugin.CoreAudio.Base; + +namespace MassiveKnob.Plugin.CoreAudio.GetVolume +{ + public class DeviceGetVolumeActionSettingsViewModel : BaseDeviceSettingsViewModel + { + // ReSharper disable once SuggestBaseTypeForParameter - by design + public DeviceGetVolumeActionSettingsViewModel(DeviceGetVolumeActionSettings settings) : base(settings) + { + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj index 123b6c9..879f89a 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj +++ b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj @@ -35,6 +35,7 @@ + @@ -45,17 +46,43 @@ - + + BaseDeviceSettingsView.xaml + + + + + DeviceGetMutedActionSettingsView.xaml + + + + + OSDWindow.xaml + + + + + + + DeviceSetMutedActionSettingsView.xaml + + + + + + DeviceGetVolumeActionSettingsView.xaml + - - - DeviceVolumeActionSettingsView.xaml + + + + DeviceSetVolumeActionSettingsView.xaml - - - + + + Strings.resx True @@ -72,21 +99,49 @@ 4.0.0-alpha5 + + 5.0.0 + 4.5.4 - ResXFileCodeGenerator + PublicResXFileCodeGenerator Strings.Designer.cs - + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + Designer MSBuild:Compile + \ No newline at end of file diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobCoreAudioPlugin.cs b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobCoreAudioPlugin.cs index 13c738d..35267ed 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobCoreAudioPlugin.cs +++ b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobCoreAudioPlugin.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using MassiveKnob.Plugin.CoreAudio.Actions; +using MassiveKnob.Plugin.CoreAudio.GetMuted; +using MassiveKnob.Plugin.CoreAudio.GetVolume; +using MassiveKnob.Plugin.CoreAudio.SetMuted; +using MassiveKnob.Plugin.CoreAudio.SetVolume; namespace MassiveKnob.Plugin.CoreAudio { @@ -9,14 +12,17 @@ namespace MassiveKnob.Plugin.CoreAudio public class MassiveKnobCoreAudioPlugin : IMassiveKnobActionPlugin { public Guid PluginId { get; } = new Guid("eaa5d3f8-8f9b-4a4b-8e29-827228d23e95"); - public string Name { get; } = "Windows Core Audio"; - public string Description { get; } = "Included with Massive Knob by default. Provides volume control per device and default device switching."; + public string Name { get; } = Strings.PluginName; + public string Description { get; } = Strings.PluginDescription; public string Author { get; } = "Mark van Renswoude "; public string Url { get; } = "https://www.github.com/MvRens/MassiveKnob/"; public IEnumerable Actions { get; } = new IMassiveKnobAction[] { - new DeviceVolumeAction() + new DeviceSetVolumeAction(), + new DeviceGetVolumeAction(), + new DeviceSetMutedAction(), + new DeviceGetMutedAction() }; diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/OSD/OSDManager.cs b/Windows/MassiveKnob.Plugin.CoreAudio/OSD/OSDManager.cs new file mode 100644 index 0000000..eca825f --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/OSD/OSDManager.cs @@ -0,0 +1,50 @@ +using System.Threading; +using System.Windows; +using AudioSwitcher.AudioApi; + +namespace MassiveKnob.Plugin.CoreAudio.OSD +{ + public static class OSDManager + { + private const int OSDTimeout = 2500; + + private static OSDWindowViewModel windowViewModel; + private static Window window; + private static Timer hideTimer; + + public static void Show(IDevice device) + { + Application.Current.Dispatcher.Invoke(() => + { + if (window == null) + { + windowViewModel = new OSDWindowViewModel(); + window = new OSDWindow(windowViewModel); + + hideTimer = new Timer(state => + { + Hide(); + }, null, OSDTimeout, Timeout.Infinite); + } + else + hideTimer.Change(OSDTimeout, Timeout.Infinite); + + windowViewModel.SetDevice(device); + window.Show(); + }); + } + + private static void Hide() + { + Application.Current.Dispatcher.Invoke(() => + { + window?.Close(); + window = null; + windowViewModel = null; + + hideTimer?.Dispose(); + hideTimer = null; + }); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/OSD/OSDWindow.xaml b/Windows/MassiveKnob.Plugin.CoreAudio/OSD/OSDWindow.xaml new file mode 100644 index 0000000..70a0036 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/OSD/OSDWindow.xaml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/OSD/OSDWindow.xaml.cs b/Windows/MassiveKnob.Plugin.CoreAudio/OSD/OSDWindow.xaml.cs new file mode 100644 index 0000000..2471103 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/OSD/OSDWindow.xaml.cs @@ -0,0 +1,49 @@ +using System; +using System.ComponentModel; +using System.Windows; +using System.Windows.Forms; +using System.Windows.Media.Animation; + +namespace MassiveKnob.Plugin.CoreAudio.OSD +{ + /// + /// Interaction logic for OSDWindow.xaml + /// + public partial class OSDWindow + { + private bool closeStoryBoardCompleted; + + + public OSDWindow(OSDWindowViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + + + private void OSDWindow_OnLoaded(object sender, RoutedEventArgs e) + { + var desktopArea = Screen.PrimaryScreen.WorkingArea; + + Left = (desktopArea.Width - Width) / 2; + Top = desktopArea.Bottom - Height - 25; + } + + + private void OSDWindow_OnClosing(object sender, CancelEventArgs e) + { + if (closeStoryBoardCompleted) + return; + + ((Storyboard)FindResource("CloseStoryboard")).Begin(this); + e.Cancel = true; + } + + + private void CloseStoryboard_Completed(object sender, EventArgs e) + { + closeStoryBoardCompleted = true; + Close(); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/OSD/OSDWindowViewModel.cs b/Windows/MassiveKnob.Plugin.CoreAudio/OSD/OSDWindowViewModel.cs new file mode 100644 index 0000000..a335dda --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/OSD/OSDWindowViewModel.cs @@ -0,0 +1,92 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Security.RightsManagement; +using System.Windows; +using AudioSwitcher.AudioApi; + +namespace MassiveKnob.Plugin.CoreAudio.OSD +{ + public class OSDWindowViewModel : INotifyPropertyChanged + { + // ReSharper disable UnusedMember.Global - used by WPF Binding + private string deviceName; + public string DeviceName + { + get => deviceName; + set + { + if (value == deviceName) + return; + + deviceName = value; + OnPropertyChanged(); + } + } + + private int volume; + public int Volume + { + get => volume; + set + { + if (value == volume) + return; + + volume = value; + OnPropertyChanged(); + OnDependantPropertyChanged(nameof(VolumeLowVisibility)); + OnDependantPropertyChanged(nameof(VolumeMediumVisibility)); + OnDependantPropertyChanged(nameof(VolumeHighVisibility)); + OnDependantPropertyChanged(nameof(VolumeIndicatorLeft)); + } + } + + + private bool isMuted; + public bool IsMuted + { + get => isMuted; + set + { + if (value == isMuted) + return; + + isMuted = value; + OnPropertyChanged(); + OnDependantPropertyChanged(nameof(IsMutedVisibility)); + OnDependantPropertyChanged(nameof(IsNotMutedVisibility)); + } + } + + + public Visibility IsMutedVisibility => IsMuted ? Visibility.Visible : Visibility.Collapsed; + public Visibility IsNotMutedVisibility => IsMuted ? Visibility.Collapsed : Visibility.Visible; + public Visibility VolumeLowVisibility => Volume > 0 ? Visibility.Visible : Visibility.Collapsed; + public Visibility VolumeMediumVisibility => Volume > 33 ? Visibility.Visible : Visibility.Collapsed; + public Visibility VolumeHighVisibility => Volume > 66 ? Visibility.Visible : Visibility.Collapsed; + + public int VolumeIndicatorLeft => Volume * 3; + // ReSharper enable UnusedMember.Global + + + public void SetDevice(IDevice device) + { + DeviceName = device.FullName; + Volume = (int)device.Volume; + IsMuted = device.IsMuted; + } + + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected virtual void OnDependantPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/OSD/SpeakerIcon.xaml b/Windows/MassiveKnob.Plugin.CoreAudio/OSD/SpeakerIcon.xaml new file mode 100644 index 0000000..8fc9879 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/OSD/SpeakerIcon.xaml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Resources/Muted.svg b/Windows/MassiveKnob.Plugin.CoreAudio/Resources/Muted.svg new file mode 100644 index 0000000..5f80244 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Resources/Muted.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Resources/OSDIcon.ai b/Windows/MassiveKnob.Plugin.CoreAudio/Resources/OSDIcon.ai new file mode 100644 index 0000000..9769ffa --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Resources/OSDIcon.ai @@ -0,0 +1,441 @@ +%PDF-1.5 % +1 0 obj <>/OCGs[5 0 R 6 0 R 7 0 R 27 0 R 28 0 R 29 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream + + + + + Adobe Illustrator CS6 (Windows) + 2021-02-24T14:53:14+01:00 + 2021-02-24T15:05:50+01:00 + 2021-02-24T15:05:50+01:00 + + + + 228 + 256 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAADkAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q+CP+cgvzj8x+cPOO p6XDeS2/ljTbiS0tNPjcrHL6LcGmlC09RnZary+yNh3qqwnyD+Y/mzyLrUOqaBevDwcNcWZYm3uE qOUc0daMGApXqOoIO+Kv0E/LX8xdB8/+VbfX9Iegf93eWjEGS3uFALxPTwrUHupBxVlOKuxV2Kux V2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVLvMPmPQvLulTatrl9Dp+nwD95cTtxWtKhVHVmNNlUEnti r5H/ADf/AOcttb1szaR5F9TSNJNUk1VvhvZh0Pp/74U9iPj916Yq+fYtT1GK/GoRXU0eoK/qreLI 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 + + + + + + 1 + False + False + + 256.000000 + 256.000000 + Pixels + + + + Cyan + Magenta + Yellow + Black + + + + + + Default Swatch Group + 0 + + + + + + Document + + + application/pdf + + + proof:pdf + uuid:1c626357-f5f2-453f-8659-dc4871555e80 + uuid:b8491561-178e-488f-8720-d6526fe71576 + + + + + + + + + + + + + + + + + + + + + + + + + +endstream endobj 3 0 obj <> endobj 9 0 obj <>/Resources<>/Properties<>>>/Thumb 34 0 R/TrimBox[0.0 0.0 256.0 256.0]/Type/Page>> endobj 31 0 obj <>stream +HVێ5|n^YP$-y" +m$OU3g,s|vWp>oۧ--w~|y)Uf  ͻ>}$XǰMbz$R^}kP-~y W)VgFB5&>c3T=y"݉$VőФ^EMu Q\:"]R")Eoȉ{Y|5 B2# nQ[HoQnxB`!}Rh*Z'+qWze 7Օ 'li0R[mcrCLC_%ި7V(AP9#10𡤢Z1dQat hB)@mB'.agKb42CH3*̡ " +$cIH:$+{EEmE(>:+U^[2[@М$̏3$U]^E\reŃ3^f} 1Dpk.+(ݻeW?:层O5KXӗ\=_&u,F?1>'eH2ǭ#@Zm^681AaD .yU.({yGYe +ZL)Ev5q`9&zE &Zu;|rO+i\̳8h;cBX#:pWUٻs[|%&z +endstream endobj 34 0 obj <>stream +8;Y:45n\]%#Qqm?s+C--LM@8VUn3g7c:/"kr4.@SL;fk4cCt1.)$%=AJRQjeQSA"3 +d)Np#IeR[#)>][O>NZCgqroHLWI$\8pF6+iqA#[(p-(@]2cq\t'"^ElA%q#rf.d7snWiD~> +endstream endobj 35 0 obj [/Indexed/DeviceRGB 255 36 0 R] endobj 36 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> +endstream endobj 27 0 obj <> endobj 28 0 obj <> endobj 29 0 obj <> endobj 41 0 obj [/View/Design] endobj 42 0 obj <>>> endobj 39 0 obj [/View/Design] endobj 40 0 obj <>>> endobj 37 0 obj [/View/Design] endobj 38 0 obj <>>> endobj 33 0 obj <> endobj 32 0 obj <> endobj 43 0 obj <> endobj 44 0 obj <>stream +%!PS-Adobe-3.0 +%%Creator: Adobe Illustrator(R) 16.0 +%%AI8_CreatorVersion: 16.0.0 +%%For: (PsychoMark) () +%%Title: (OSDIcon.ai) +%%CreationDate: 2/24/2021 3:05 PM +%%Canvassize: 16383 +%%BoundingBox: 2 35 166 222 +%%HiResBoundingBox: 2.25 35.3994 166 221.6006 +%%DocumentProcessColors: Cyan Magenta Yellow Black +%AI5_FileFormat 12.0 +%AI12_BuildNumber: 682 +%AI3_ColorUsage: Color +%AI7_ImageSettings: 0 +%%RGBProcessColor: 0 0 0 ([Registration]) +%AI3_Cropmarks: -0.5 0.5 255.5 256.5 +%AI3_TemplateBox: 127.5 128.5 127.5 128.5 +%AI3_TileBox: -178.5 -267.5 433.5 524.5 +%AI3_DocumentPreview: None +%AI5_ArtSize: 14400 14400 +%AI5_RulerUnits: 6 +%AI9_ColorModel: 1 +%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 +%AI5_TargetResolution: 800 +%AI5_NumLayers: 3 +%AI9_OpenToView: -156 409 2 1389 914 18 0 0 82 117 0 0 0 1 1 0 1 1 0 1 +%AI5_OpenViewLayers: 766 +%%PageOrigin:0 0 +%AI7_GridSettings: 72 8 72 8 1 0 0.8 0.8 0.8 0.9 0.9 0.9 +%AI9_Flatten: 1 +%AI12_CMSettings: 00.MS +%%EndComments + +endstream endobj 45 0 obj <>stream +%%BoundingBox: 2 35 166 222 +%%HiResBoundingBox: 2.25 35.3994 166 221.6006 +%AI7_Thumbnail: 116 128 8 +%%BeginData: 7608 Hex Bytes +%0000330000660000990000CC0033000033330033660033990033CC0033FF +%0066000066330066660066990066CC0066FF009900009933009966009999 +%0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 +%00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 +%3333663333993333CC3333FF3366003366333366663366993366CC3366FF +%3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 +%33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 +%6600666600996600CC6600FF6633006633336633666633996633CC6633FF +%6666006666336666666666996666CC6666FF669900669933669966669999 +%6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 +%66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF +%9933009933339933669933999933CC9933FF996600996633996666996699 +%9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 +%99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF +%CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 +%CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 +%CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF +%CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC +%FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 +%FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 +%FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 +%000011111111220000002200000022222222440000004400000044444444 +%550000005500000055555555770000007700000077777777880000008800 +%000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB +%DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF +%00FF0000FFFFFF0000FF00FFFFFF00FFFFFF +%524C45FD06FF7DF8F8F852FD4EFF52F8F82752FD1BFF52FD05F827FD4BFF +%7DFD06F827FD19FF7DFD07F852FD48FFA852FD08F87DFD18FF27FD08F852 +%FD46FF52FD0AF852FD18FF52FD09F87DFD44FF27FD0BF827FD18FF52FD0A +%F8A8FD41FF7DFD0DF852FD19FFFD0BF8A8FD3EFFA827FD0EF827FD19FFA8 +%FD0BF8FD3DFF52FD10F827FD1AFFA8FD0AF827FD3AFFA827FD11F827FD1B +%FF7DFD0AF827FD37FFA852FD13F827FD1CFF52FD0AF852FD35FF7D27FD14 +%F827FD1DFF27FD0AF87DFD33FF27FD16F852FD1EFF27FD0AF87DFD30FF7D +%FD0FF8A827FD07F827FD1EFFA8FD0BF8A8FD2DFFA827FD0EF852FFFF52FD +%07F852FD1FFFA8FD0AF827A8FD2BFF52FD0EF8277DFFFFFF27FD07F827FD +%20FF7DFD0AF827FD29FFA827FD0EF852A8FD04FF27FD07F827FD21FF52FD +%0AF852FD27FF7DFD0EF827A8FD06FF27FD07F827FD22FF52FD0AF87DFD24 +%FF7D27FD0EF852FD08FF27FD07F827FD23FF27FD0AF8A8FD22FF52FD0EF8 +%27A8FD09FF27FD07F827FD24FFFD0BF8A8FD1FFFA8FD0FF87DFD0BFF52FD +%07F852FD25FFFD0BF8FD1DFFA852FD0EF827FD0DFF27FD07F827FD25FFA8 +%FD0AF827FD1BFF7DFD0EF8277DFD0EFF52FD07F852FD26FF7DFD0AF827FD +%19FF27FD0EF852A8FD0FFF27FD07F827FD27FF52FD0AF852FD16FF7DFD0F +%F87DFD11FF27FD07F827FD28FF52FD0AF852FD13FFA852FD0EF852FD13FF +%27FD07F827FD29FF27FD0AF87DFD11FF52FD0EF8277DFD14FF27FD07F827 +%FD29FFA827FD0AF8A8FD0EFFA827FD0EF87DFD16FF27FD07F827FD2AFFA8 +%FD0BF8A8FD0BFFA852FD0EF827A8FD17FF52FD07F852FD2BFF7DFD0AF827 +%FD0AFF7D27FD0EF852FD19FF27FD07F827FD2CFF52FD0AF852FD08FF52FD +%0EF852A8FD1AFF52FD07F852FD2DFF52FD0AF87DFD05FF7DFD0FF87DFD1C +%FF27FD07F827FD2EFF27FD0AF87DFD04FF52FD0DF827FD1EFF27FD07F827 +%FD16FF7DFD1352FD05FF27FD0AF8A8FD04FF52FD0AF8277DFD1FFF27FD07 +%F827FD15FF52FD14F827FD04FFA8FD0BF8A8FD04FF27FD08F852A8FD20FF +%27FD07F827FD14FF52FD16F852FD04FFA8FD0AF827FD05FF27FD05F827A8 +%FD22FF27FD07F827FD14FFFD18F87DFD04FF7DFD0AF827FD05FFFD04F852 +%FD24FF52FD07F852FD14FFFD19F87DFD04FF7DFD0AF852FD05FFF827A8FD +%25FF27FD07F827FD14FFFD1AF8A8FD04FF52FD0AF852FD04FFA8A8FD26FF +%52FD07F852FD14FFFD1AF827A8FD04FF27FD0AF87DFD2BFF27FD07F827FD +%14FFFD1BF827A8FFFFFFA827FD0AF87DFD2AFF27FD07F827FD14FFFD08F8 +%2727522752275227522752275227522752275227A8FD04FFA8FD0AF827A8 +%FD29FF27FD07F827FD14FFFD08F87DFD19FF7DFD0AF827FD29FF27FD07F8 +%27FD14FFFD08F87DFD1AFF7DFD0AF852FD28FF27FD07F827FD14FFFD08F8 +%7DFD1BFF52FD0AF852FD27FF52FD07F852FD14FFFD08F87DFD1CFF27FD0A +%F87DFD26FF27FD07F827FD14FFFD08F87DFD1DFF27FD0AF87DFD25FF52FD +%07F852FD14FFFD08F87DFD1EFFFD0BF8A8FD24FF27FD07F827FD14FFFD08 +%F87DFD1EFFA8FD0BF8FD24FF27FD07F827FD14FFFD08F8A8FD1FFFA8FD0A +%F827FD23FF27FD07F827FD14FFFD08F87DFD20FF7DFD0AF827FD22FF27FD +%07F827FD14FFFD08F87DFD21FF52FD0AF852FD21FF27FD07F827FD14FFFD +%08F87DFD22FF27FD0AF852FD20FF52FD07F852FD14FFFD08F87DFD23FF27 +%FD0AF8A8FD1FFF27FD07F827FD14FFFD08F87DFD23FFA8FD0BF8A8FD1EFF +%52FD07F852FD14FFFD08F87DFD24FFA8FD0AF827FD1EFF27FD07F827FD14 +%FFFD08F87DFD25FF7DFD0AF827FD1DFF27FD07F827FD14FFFD08F8A8FD26 +%FF52FD0AF852FD1CFF27FD07F827FD14FFFD08F87DFD27FF27FD0AF87DFD +%1BFF27FD07F827FD14FFFD08F87DFD28FF27FD0AF87DFD1AFF27FD07F827 +%FD14FFFD08F87DFD29FFFD0BF8A8FD19FF52FD07F852FD14FFFD08F87DFD +%2AFFFD0BF8FD19FF27FD07F827FD14FFFD08F87DFD2AFFA8FD0BF8FD18FF +%52FD07F852FD14FFFD08F87DFD2BFF7DFD0AF827FD17FF27FD07F827FD14 +%FFFD08F87DFD2CFF52FD0AF852FD16FF27FD07F827FD14FFFD08F8A8FD2D +%FF52FD0AF87DFD15FF27FD07F827FD14FFFD08F87DFD2EFF27FD0AF87DFD +%14FF27FD07F827FD14FFFD08F87DFD2EFFA827FD0AF8A8FD13FF27FD07F8 +%27FD14FFFD08F87DFD2FFFA8FD0AF827A8FD12FF52FD07F852FD14FFFD08 +%F87DFD30FF7DFD0AF827FD12FF27FD07F827FD14FFFD08F87DFD31FF52FD +%0AF852FD11FF52FD07F852FD14FFFD08F87DFD32FF52FD0AF87DFD10FF27 +%FD07F827FD14FFFD08F87DFD33FF27FD0AF87DFD0FFF27FD07F827FD14FF +%FD08F8A8FD34FF27FD0AF8A8FD0EFF27FD07F827FD14FFFD08F87DFD35FF +%FD0BF8A8FD0DFF27FD07F827FD14FFFD08F87DFD35FFA8FD0BF8FD0DFF27 +%FD07F827FD14FFFD08F87DFD36FF7DFD0AF827FD0CFF52FD07F852FD14FF +%FD08F87DFD37FF7DFD0AF852FD0BFF27FD07F827FD14FFFD08F87DFD38FF +%52FD0AF852FD0AFF52FD07F852FD14FFFD08F87DFD39FF27FD0AF87DFD09 +%FF27FD07F827FD14FFFD08F87DFD39FFA827FD0AF87DFD08FF27FD07F827 +%FD14FFFD08F8A8FD3AFFA827FD09F827A8FD07FF27FD07F827FD14FFFD08 +%F87DFD3BFF7DFD0AF827A8FD06FF27FD07F827FD14FFFD08F87DFD3CFF7D +%FD0AF852FD06FF27FD07F827FD14FFFD08F87DFD3DFF52FD0AF852FD05FF +%27FD07F852FD14FFFD08F87DFD3EFF27FD0AF87DFD04FF7DFD07F827FD14 +%FFFD08F87DFD3FFF27FD0AF8A8FD04FF52FD06F852FD14FFFD08F8A8FD40 +%FFFD0BF8A8FD04FF52FD05F827FD14FFFD09F85227272752272727522727 +%2752272727522727275227272752272727522727275252A8FD1DFFA8FD0B +%F8FD05FF27FD04F827FD14FFFD2CF87DFD1DFFA8FD0AF827FD05FF27F8F8 +%F827FD14FFFD2DF827A8FD1CFF7DFD0AF827FD04FFA8F8F8F827FD14FFFD +%2FF852FD1CFF52FD0AF852FD04FFA8F8F827FD14FFFD31F8A8FD1BFF27FD +%0AF87DFD04FF7DF827FD14FFFD32F87DFD1BFF27FD0AF87DFD04FF7D27FD +%14FF52FD32F8277DFD19FFA8FD0BF8A8FD04FF7DFD15FF27FD33F852FD19 +%FF7DFD0AF827FD1BFF7D5227525252275252522752525227525252275252 +%5227525252275252522752525227525252FD0FF87DFD18FF7DFD0AF827FD +%41FF27FD0EF852A8FD17FF52FD0AF852FD41FF7D27FD0EF87DFD17FF27FD +%0AF87DFD42FF52FD0EF827A8FD16FF27FD0AF8A8FD42FFA8FD0FF87DFD16 +%FFFD0BF8A8FD43FF52FD0EF827A8FD14FFA8FD0BF8FD44FFA827FD0EF852 +%FD14FFA8FD0AF827FD45FF7DFD0EF827A8FD13FF7DFD0AF827FD45FFA827 +%FD0EF852A8FD12FF52FD0AF852FD46FF7DFD0EF8277DFD12FF52FD0AF87D +%FD46FFA827FD0EF827FD12FF27FD0AF87DFD47FF7DFD0FF87DFD10FFA827 +%FD0AF8A8FD48FF27FD0EF827A8FD0FFF7DFD0BF8A8FD48FF7D27FD0EF87D +%FD0FFF7DFD0AF827FD4AFF52FD0EF827A8FD08FFA8FD05FF52FD0AF852FD +%4AFFA8FD0FF87DFD08FF27A8FD04FF52FD0AF87DFD4BFF52FD0EF8277DFD +%06FF27F8FD05FF27FD0AF87DFD4BFFA827FD0EF852FD05FF27F827FD05FF +%27FD0AF8A8FD4CFF7DFD0FF8A8FFFFFF27F8F827FD04FFA8FD0BF8A8FD4D +%FF27FD0EF852A8FF27F8F8F852FD04FFA8FD0BF8FD4EFF52FD0FF87D52FD +%04F87DFD04FF7DFD0AF827FD4EFFA852FD14F8A8FD04FF7DFD0AF852FD4F +%FF7DFD14F8A8FD04FF52FD0AF852FD50FF52FD12F827A8FD04FF27FD0AF8 +%7DFD50FF7D27FD11F852FD04FFA827FD0AF87DFD51FF7DFD10F827FD05FF +%A8FD0AF827A8FD51FFA827FD0EF827FD06FF7DFD0AF827FD53FF52FD0DF8 +%27FD07FF7DFD0AF87DFD53FFA827FD0BF852FD08FF52FD09F852FD55FF7D +%FD0AF852FD09FF27FD08F87DFD57FF27FD08F87DFD0AFFFD08F87DFD58FF +%7D27FD05F852FD0CFFFD06F852FD5AFFA852F8F8F852FD0EFF27F8F8F852 +%FFFFFF +%%EndData + +endstream endobj 46 0 obj <>stream +%AI12_CompressedDataxܽz20MN`1`Lhn`fw_It"YZ]%JT%k WjR.0.CSφryin3A7[J>۲EѻfEϘyg4<`ߛOliYs{4DP{FM(ӛz<_Oӛ x4x^8S;q 7}mIͳaE5z3vfwQC<'CZ.7hI|IѮ{EOHo?}//Րw_A}p2~K+SAWRDz!~ԩؗLo+@ip _o UZmȈ70暂f.2[Ao(kN5lKo0S}T?,)m=߃o<ϛ1o  +-zJdo=b0LAeg"nusB]uODp!c0 HI/ P8 *=Q~ y a;e{ ƾSk2Ě[3ZEր{)T-;CZ!0a[ZO672RES;6T? Xሉ Mz00w X[@0N20O+WYjV5Zn`zlx[=kz@vex2$  f ^Bm$(T`tg@nQ|tQ?f9߭x/gSfIv3CsY +`I;$|c9xf\Χ`;<)FrŏxdopLρSX֪m/Cz@)w2_AGkBИ g2@A +T T:z}3DA>H/2XQ'hӘ*#(Y_;F "} q#Ak|=O?෿ $ax6|| x"cFtnchXؠS,:aԆ-5)'j#oZ I92L !#!4gVp]~ y? 0 kV=PN8EӃ%h< +Z|O Ђ =6FzZ;C0QvoN6bt6++X#:5Xx=_`yjg1S˫A׀F$:|3t9Cfƺ5*~9z xxR/h4:7&S\NR(H1 T(D/`qf/E4?I֡~Nn@mߵwI[G{ĸrG|dSʰ3]ζWHo2q~f|:%bzC/?ϿeTp+JkH'%C'٨g#BM'llBE?V~0zP>E.ytD^k)"䥋/L)kzmE["0er !E=6DŽ$)|fPHThjGQ&= ~*]8f.胕5_͆n zH(Pa+#PA{nbSjw,Ӕ_iwsf`ύ;LF`B?yRh D0id CP$Yʆ"(  +zLA6/N bTz5&vѼP="OvHveL&L [nAo-ܨi1Zη@88ͿB@C#]ٯPGL燕AHp:H;d(ǐ#7A4(nk4W0t+>BJ6U 6[YՈPee`@cL3JKopny yft/ӼHjP wW:H7-쎵l>d?VCj|_b/%Gw6Q< +@ӝ"-ey+0lv06FTj=]R͡8jV +@M`eyy _ ZPrT"H?4!DPcW4P+ޠT0@8&UJX[΢@Lv3< ͏ϘO*lٯ^af]ģ^77AL\LC9QӛԱB/+Y/fw,K +M$f!}HDm m15P ^ތڔ"lFr]9ǥu^XL|ANT +NfN5sb3GڶNhC4RCƴaa3["KJM6mb\q`k-r10uҋiM0q;1tbF `kdX=B=(Q;z\j4-!B l;QFf .}-2,lW+@?m0,LQ8x~3 QyiPǾYZcniJ`#V PDh#Pj5u%0,o=d~~2h40h`"1{j$%r+m16yNhQ3XՐ#cj7qU +CvcI&jA= |l2}Ih2ڬa$^m@)eԎc@lsz*ϓ;H[Nmtf[v#C1[F\fjJ疱I1h"FQ Qh?_6#F l9 Pmj0IU0f;Q{FcEK`a*l-(Z,;=jZh@/ڔ +2R[ Zqi5uQP9DGkL'j7{"&{ԐL-25#{rRAjsVh5:h*6zimIP,]3ν NQ 0! O,$ u>B/hF7 +55UhȪL,-7BC&d@u0R3; +-7ۑA-vjӏZpk'P% ӈ|?K?Q3M/c--뛻6SCCf|ӱx>m +؂(gFa56Fnf?t#Unhi +#C +OÎnFiUOOqo: [ae'h n ej٬Sݞcު oĸȘt`ihP* 6춃x e{T`ʛ*ߔ)Z*^Yrj=8څ֦YIͬdMDa~ *:hj4ǔ,W>ͳIhn{&(>m*_zs.VjdJp KA%6h;|UW:IVtm|VKf>pno\MhP>}ߑ~ vٟI&c==wnw]hZ=k%S!1ʺݖʸh[g˙Zm*T9A!߾w&;!nCIjf-exW2%;ʑux zCvJ/vt3PQ>;"+: t-Kƴ+sjkD~Qnxff~,;/oG2cyg=z2b`?IeƠ& Do8{h!Uiؒ< %k}%SexxyBχy(M|)zT"(Ƀev-@ +qt $VJ/C3N.)9g$9lzxk_nb}pZ>5$U"],2?g=}M>!( @C M%?H{̴8s 4o[&D>NX@祇f1WM84U—P(?-`LgYjv?T3N<``Mo]-UͤkIc“[Nws}J۩j>du7g&v0 +a*{9 u8Z3K~~u<̐ kMp<8 q1JpѦXA uwSw1K?>x> xt7t' }g NJ!"w:)SDn{q'bomY9*`t6H4rcȨIV`pf[f:B[=zߥP+.["BF%&|;IDJg!_kW&`伄>f}-o-clu$ +߰_hetUm!&-'š|ox^[Ts־06ܼxG>Yk_頣2cE-;#svC'm(￳C=v7ډݧ͏eg#&Fhy"u; ݺ}Lt> G2uRoG=2aj|?eاw2^̸A4=SC/ixqIVϝhhwCFPh\.if's=0#);-xGjv=26ad#ɖn\MCJT}&=zerE8k}!;>lQmms#gӓ:.̵$i犂Ʌ CD k}w[pdL@^sm<4-^\usAQ]ppc-{ /tk6H%_x 󸊟r:hlkÞ~#Sx*!R +DΖ~Brc .ZJ3U}+˔Q'J>CD0hA! N?oqWj:{|r [0q/b&naM!X&/9?V7cugv +kx sfi |P +&~~DK/FDtZpDt0>S~k0c}d>[Z?ҁV۝zצzӑJ/10ydK;@ \Fod'de֙}ڬ2/w Slrۘ`Zwpt/ٺy0Scp_C0h]g#Q(YEq$:>[ϙg;ED&Tkղ^FI(}9K1 S@-/lxɼyƘ7v!i=JNbf# p +ã1G (+j7qhoo{.!"Gͨw`.Yhwk07ca38,yrM? | t93#5NwyWH+by"-`; ^SSͷPS _dEaNHw.E_X +m7gwvTYp4hv bMңT\WԧWGl.'sE6\={Ցi|~¦Tc(h myh]zu ]~ssuӴX`sNS-mW#Fٯynj@`^3ؕ--33~r: un0"A^F̡ &EИ(Q t#([kny3\P1 + R`oʦR؞9!OoVJoE/ e +~Tv9 skMܵ['#}y$V=A.CCߖ?B)ClȞSZYsՒ9EtO]\}d2q:#d0\: +cTXS`J's28}3}#dXt??G3fN~/ּ^rv/ѻءOvFT7)th{h} +tjP%yOI(A)DuzW%yިtӯh4#%O55<x,Vp&N3JeMz)ZNE|uc2C]2Sa5\K DێBlr¢;>?4RT +WuqxKOpS1f56E"&-*%VfK%Q)2QPVEXԸu0uSBY6UF&|PoGJ4?GEcml`n|a 2'26A>XG7 vg;m+ %x دT2D̀kxiW׀xQ9:h`X2sw v΢#:0 5f" {mCVElp99YxjHoV@pV)"{VՇxߩ!F%<+0flw z] _: U%()XdBӉaʹ}yJ#Dw7]+Gvl7iR#Ҟ +Y4vvhotz%+ +#w+ qh/F>=;S3o!08˒Z۳ b|-,}' خE.eo0tH޼kV+D3'1_{Dx/Lk%=?kH -!֔ˡ+5nؙ[)-;K"xꈍqA} H"4%`U)]b59D~2**b;9fy ݑk͛i7GjYEY5aeаS[ +8sOm V1C{KĈ"fV:{a-%g7VF@3oy%Gc:E)?4Ԗ_ +X@8𨀵'fSzJyąۇ +5{Zx z2y}.c;KҹG-Wܒ mX+/ke5pVipiz c-JgZ]REEMu ߃<ֲWXyI +Fl~+a-Zq=& +Va8ܖo>Ǵ֜=3+@#n)b15+iAk +96_D^D}E:UL|*Yqtվ$_\fg,GĞ1X}AL1J|"va>X{v`}'8Y]na 7!KbYQ'r/u,_\~ Gk)`ڳ ,c+|LY,)Cbc ޙ:"d`5zŠ& g[0X*zdm#ߵ Ի_PTPý7^m_I(4Sū9>ϋ#Q?ʸDOVz=GTG>,e$-Q7L>[7[iHus0RHތSOBFR<ю\F5_]#g8^Oi}KB|{dWz ˯'e6;Wx}l#;?{޺ﻊDjF ZVXսKdy*??>{J ت=͸u)-fHsrT D~$%o>:vsq7gQ&Ɩ}n cb> c?m9?}@8ؚ= ,ES8CњHt&✡9.ncwXMG51o_:(aVZpEĶ,`wQǎa Y%a-,E6Yq-@# ߤ[+9~ŧ9sNKR %b..C£?.;šQ!=>YkFg\&Ia-~ϝCdsH/e9%`|U/XV/bB::Nzd kq#H͍6RȽ9BV?ќ0#98H +o*.ώs#'@ԣ?,PV|ϖtyr\~y2rE'ˬ>8Us'V̅ + +I=3.yjxWI8 >yv/%le=נM_YqP7AGzʄ`>;y$ޯCmS¬?l13mE}božCaW9LahiM"L+=u)$Rk`\흒&Ŧ0eXSqZ:* h!"F-z= }B;7ݍ+nvOQI-glfEӢXٺ:ww2 e 0Z˳`ʧ6ٜ`6V\D mϐP'P߭;ޠ2E qTe'-y{/&@=aZ?Fu>SQr#(Glgn qpУ~ زڭHS"P[>zE$h핮# +ES/ +GwXFxNS9ZփsAњݓO +#ͼթ/@lR?T>׉]D/JwsjMWOŮiƢ|QIW0E5zRqx&1LDA+`EJ:JZ.|ьn#%V +<4CjY$!6=%3!U-yؖO FZozPj%+2ҋT값e^:|Zhr>Ztr> L-|Q9z- ^xI9z-؀O/]OF\x~9z-./SC }r>Z>v4U +e)ׁ蓞1VFrg]o?b{If9)]e#+HA*8M䔩@RQŊ">#< +t]@z*',?M[Z.>m%6{'<#~u_::RLrl (4[`pfy4N/ #CʺuQZewKE'LY^ +bhbzCFbVF!>54)^I{2d^$t^@ [zĢBMkFSQ)@$)#):vJPuJ-zsß8[Eon8nFo`Y%VOGo|Q뭜'6:ӣ7q08U%eD=͍9P ,aAstDouY+U[6@'&^x$ WQː.+\ܜR!}՘HE8Q̪سAkp6\iZ֙ U!ı3K%.{jhU]L!JNO F V$LtCxٕyo+λvez*49:yLt8t@y'$^RXq޵+B'U橥ZqF*n d*{iq޵+ڕyޚҾPF?TyS/(Rթ#@^C +7tlzɚx5l (bHbkvG6f]|.F\{b<'X+p,y+X t=>34dsuEΉEЖ_ +!S- \UL\ֺQY8z\\(ZWY-Elt!$e(a vۍAgKɾ_HJEW(%M@)./PR$ *r%q:O_c Vwq\QHSw͍LB_O3:}r< +畩SX9Oaڅ}*+,{Fׅ}X?_'zQarb"PW-}>>IF5 +_7+m_OOb¾vZaѮT'Ww*tU(Tz]P'gH//쓓=Vaf {^O ^=On7V.gۙQ7g)9#oc(['7$%{vBʇYMCPO u~ܐ׸O-~ +dM}ҼcU&|Yf>>,@ik7}3e+v]}:NҳuB +RO6.6]'t$),̊y6:uq>_D9Yw#/T_*pߩA3/BJ;^򿛣;# {C1)Wxz5u$Gs}!$\je8 '[ʊG 0)g.C7i$KR chD'6; EO䭝aRAWSRGpT+t6h'JO!vR)+: ɕ84C\^/Gվ?nJ2vL9f%0eHQSΦS[q|3 cޮz.w|P+Z{7 +Ew!@QO=1`w÷ؿTNK Eb`GL 93L;s=bx׷ֽ:MKLuūN,QT{dԚKu9>)4{$bj߳}|{28yC/iu-y>ڗ4v|0ҜQYɕ t42˪}F{^^z]iSvt"(lTY;M]ɆlU㉅TmWkԪ>t;LGNԻWXBQHF.|> +J*judLjZj-թٸ?_9X(7NQwq+ BFrJe%jjawVn^&У7:/l_eO讻q"ENBeG=b "Xj؄FK 3Dn黗$w-`E -F+{p5̭|ZW +7BKC=)a+4K W\#3(ʼp1{*̳a7qX|xQ4WޞVWє-Ea.iI[ kŢ޽%rX1jp'v2lx]ZBӠkW5Af,|$(da]E`ZYgOGv&(}drB:cyɦV6ʭ|"sXZ7қyW&R`g5 4NY`KuRUgWtN+SANS#L[lruP#]uKSML9q9ܓFN]ێsI,LLgK=F&x:D +52Q:L_I (RU,I8T_=5-&/&NE!7_Q6=Ϥg2tTLϘ:N9.@/` S/oHc +F slf0t6lCr\P7N(h $JQ BxuvG10EQԺDl% -ռhaZTH7GGEKhoNv2M$]|Mq,TU17,Ik67g$r='$;\x#OĒD"P`vѠ % +bQa?$Q[(e?T@ңzDujI.P*IKʞtM~\g]VK?xY!F6^BÕ.+BLC!s/{Y!"NB%{(hjvȧ]z!Ǫmx9ZqqpE9P/Rk^xyE9CB_CY&o;L5~}ϋ.c'uX:Ò$' M'gJג0eQXUt4kx7¨+Ό#8zќ/6D.!r?(/"oqK&SfD!yx&D/V@ZV|_CS_o_B|?[_iy&Fu ug C;զ|\_"d;5ڵpl5 +OŝX]ٸ+J4[7TSf󎸥FVsJ^w;XZ1?vA&{b4-"$Z59񞍦_\aGcڎϒ$rOFODڙ{!,59CBȵB}t!RN&^F +!J NF-m屴`px {N[2 {2:^M/>x4R.f0oem~Q +[Ҟ!cMf>phdbs0ؒ4>~Ox&7zwY PBZO?Q1'_nد? ȃ}n@H6]>3  lpye\o=(y&Uv$<; ? G](͝v>:ߌk-iXwVv`Si$X\ Z T[z`Dډt1ڙ >|mf0WSqD|v]Y ~-oxOFگw˹6D8F,p E]=_#B>i`hmcבub-f:??dê R ZomVJ647=Þ7)y7} v~u҅4:.>SO!Ш<cg}G{ǎf +"K}ճM2`^a,H3Cble0B.Gxm7uIJ̾G2i$i]#%݁ݖZ.s.﾿ OGWБkr0khtШD%iP?@҆2瀄|om/]++DQt+sǦ6_&'C|`hl/xeu=ǠE LNxXtv6n$_hID4^{7;dH{[/c}$jRRtى70hG"*fHV"0, F"YHMً~gXV7x#NOziZɢ+OB:zvI/mx +0̤{_H \ux:b~AD7hL9K4UON=xm#@"3_Ob0O:8j|ๅf+份MFsQ_&O53\6')O*[ZwL&?;̻ {fXoYXͻcޝw:FEEq0Bz%7Xܹ4C w@#:^}Ǩ߽Y;diQW019{QD +{ZQx4G+u@pj^XjFc,q&X6 sճ"~O&כ%T&-n1mj(:Y}yc8{lyN,Hw}gќN@mv򺫣E3~Yn3*q\$%8@>!ǛUFС; 9irChc05E*k + +p˩bHImsb2 9źul?AIzvvlKq +Egɤg$@й/shcX1֘ m{ +$j&xf"$kWx)YsH](u9R)Cif?5^ߔѣ.2edin}kot Qðӎ8lm_Z(qN^xr̼Ϳ0_m5X`mT>zG}Z;ɀRЬQW1*Q݈ӅuvΛU nI< gj9X+횈cF23[B%KךDf0:6j}Ol8?1)}s(1կ7 s z'hۍ9U1֘U M6"&Qu]_C@Q_nFque%^dNYggr: y~Ljdٜ\dl5LĤ1`3G М:iz֙EZBArٯLMxsKʹ%K><%RKrrװ{$[y|e9_Bhㅗ =ځz9CzWh9#Z4Yo <,兑۳ m3igx0>jx4ri4Ϊk$_FnsJZI B_`3~`RF"$Sbiϧ}n9;#bS+A,$ =7SɈ~srߠlK=(4Bxїب 3MtO`fOHY?@Wc({nyawĖo/8Cukhi[Sr~b|w陋Z"dw &ӡ-ڙ 9"DIfۅhM:o4UO/S{6T咱q_~x{\n!;(뗇Y7?Su~9j6mRU3ͯ|viOKnJCB+oL肉Q`s ++xUа_Tc$ _xz B pZVʓ-* ix| ~˸m[mi3Gı@*a݄'bJ71Z\f +utVP]l}o8k#%S &Ӏ+Q@~ +ጄX ]Ȼ+ WBP,A(PtX^J`y ~XRtOk?xR%|[7y谢{CI52R8F1_sşe6 ?E0D +0NNB;h``=،|Sݰ:ղFE%r/aQnpՍлŽarn_..Mџ]"wR( \[]gU{2wG}2BNI HZOF.Y:LHnk.Gt _76-#;BjI$! +x + _gޯC/)TXc _"džM|,;Hf1rP%8sK޼?7 ix[,^+,ZXayhV'b'1fufn6p(tgotnigH⅃:u<_q67[oL>T3~gNH{(  |<}<8C,3u7s~E3kc> L-L AMć ;i9on&̅-܁ ?d1bU@F8bY7lIzLrXz%3Ã|r0>7q$ vWevr&m?s8;Ux6bO98/D+۪? C)#1\H2FLA Lᜁ?bv R?U +1GGJe?x~@Qn}v +id !.A>n1[ݛƨYz$D.^;fj/1 xslSl4Vw80cfN!Vu~Cdp{q I.ِDǦsD;y/AϠcC'fʼn#&8%rt;͇l11C+&bP PܗWuL:VPB~@y@q7!~k?WJYC KD4lh;ڇS7! GoZZI8| ‹)>WK"!Ss<>R\BkG@:Ic"1L%jJ:Kzx|(L("%|ج McT"V>~ƼVc }ƩGR**Ң'1eO1a9XjȤ"e 4nfΑ!4xWX&a‡ͪZ#OgDǭ}=%ԋٳZŻ/חˮ/ۧ/Ӈj7Y~t,kYjОu2r"CQ`1X? 9vCfR|}k'R-y{(uP:+8QbĮ\Qpe@6rxek:j^~ؼk0u[ۗn{}~ nnB:dieFFݮyX0|?٢h)1 Y%JPf[ =Px!r1;G-T.NSҩ0v;ܣ|0tv¿[5LlF-I +(fU?0 Myjǘ +i!t9c"Ek!SV"*y\`2M:nI%-Q*işDm +6:9(H2jdK6E@ ,A*XF(Kj)-O+jMtUlu@L~8| X#ت:BOJ6,yK(3LXG: MeRKmS"5z$ p LV~# 7!!>Iq̣g,eh=+;%"f5,؀MXۄ hT6Uk…UaPVМ8ٙ1f 8.O[68+Q4XiǣqG'`"q+F)2Ln}2 ]Qr:c1:&X֫+z6~CHuxԹ ҈Ac^ h!c7[Ềs@ +M<#FbaKkQі̡]S>M*-ƳMwϴmYEsaEyq]a=O"wi\~k33F$pCҾ!>n@ YI\^atL'zP_u zVr+ߵ'?5gݻӛ)?z\}4mZ6> endobj 6 0 obj <> endobj 7 0 obj <> endobj 20 0 obj [/View/Design] endobj 21 0 obj <>>> endobj 18 0 obj [/View/Design] endobj 19 0 obj <>>> endobj 16 0 obj [/View/Design] endobj 17 0 obj <>>> endobj 30 0 obj [29 0 R 28 0 R 27 0 R] endobj 47 0 obj <> endobj xref +0 48 +0000000004 65535 f +0000000016 00000 n +0000000216 00000 n +0000019974 00000 n +0000000000 00000 f +0000058324 00000 n +0000058394 00000 n +0000058472 00000 n +0000000000 00000 f +0000020025 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000058772 00000 n +0000058803 00000 n +0000058656 00000 n +0000058687 00000 n +0000058540 00000 n +0000058571 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000022609 00000 n +0000022680 00000 n +0000022759 00000 n +0000058888 00000 n +0000020381 00000 n +0000023289 00000 n +0000023176 00000 n +0000021768 00000 n +0000022047 00000 n +0000022095 00000 n +0000023060 00000 n +0000023091 00000 n +0000022944 00000 n +0000022975 00000 n +0000022828 00000 n +0000022859 00000 n +0000023363 00000 n +0000023537 00000 n +0000024569 00000 n +0000032372 00000 n +0000058927 00000 n +trailer +<]>> +startxref +59062 +%%EOF diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedAction.cs b/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedAction.cs new file mode 100644 index 0000000..b1e587a --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedAction.cs @@ -0,0 +1,87 @@ +using System; +using System.Threading.Tasks; +using System.Windows.Controls; +using AudioSwitcher.AudioApi; +using MassiveKnob.Plugin.CoreAudio.OSD; + +namespace MassiveKnob.Plugin.CoreAudio.SetMuted +{ + public class DeviceSetMutedAction : IMassiveKnobAction + { + public Guid ActionId { get; } = new Guid("032eb405-a1df-4178-b2d5-6cf556305a8c"); + public MassiveKnobActionType ActionType { get; } = MassiveKnobActionType.InputDigital; + public string Name { get; } = Strings.SetMutedName; + public string Description { get; } = Strings.SetMutedDescription; + + + public IMassiveKnobActionInstance Create() + { + return new Instance(); + } + + + private class Instance : IMassiveKnobDigitalAction + { + private IMassiveKnobActionContext actionContext; + private DeviceSetMutedActionSettings settings; + private IDevice playbackDevice; + + + public void Initialize(IMassiveKnobActionContext context) + { + actionContext = context; + settings = context.GetSettings(); + ApplySettings(); + } + + + public void Dispose() + { + } + + + private void ApplySettings() + { + var coreAudioController = CoreAudioControllerInstance.Acquire(); + playbackDevice = settings.DeviceId.HasValue ? coreAudioController.GetDevice(settings.DeviceId.Value) : null; + } + + + public UserControl CreateSettingsControl() + { + var viewModel = new DeviceSetMutedActionSettingsViewModel(settings); + viewModel.PropertyChanged += (sender, args) => + { + if (!viewModel.IsSettingsProperty(args.PropertyName)) + return; + + actionContext.SetSettings(settings); + ApplySettings(); + }; + + return new DeviceSetMutedActionSettingsView(viewModel); + } + + + public async ValueTask DigitalChanged(bool on) + { + if (playbackDevice == null) + return; + + if (settings.Toggle) + { + if (!on) + return; + + await playbackDevice.SetMuteAsync(!playbackDevice.IsMuted); + } + else + await playbackDevice.SetMuteAsync(settings.SetInverted ? !on : on); + + + if (settings.OSD) + OSDManager.Show(playbackDevice); + } + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettings.cs b/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettings.cs new file mode 100644 index 0000000..3d63acb --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettings.cs @@ -0,0 +1,10 @@ +using MassiveKnob.Plugin.CoreAudio.Base; + +namespace MassiveKnob.Plugin.CoreAudio.SetMuted +{ + public class DeviceSetMutedActionSettings : BaseDeviceSettings + { + public bool Toggle { get; set; } + public bool SetInverted { get; set;} + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettingsView.xaml b/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettingsView.xaml new file mode 100644 index 0000000..4cae53d --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettingsView.xaml @@ -0,0 +1,14 @@ + + + + + diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettingsView.xaml.cs new file mode 100644 index 0000000..3d65885 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettingsView.xaml.cs @@ -0,0 +1,14 @@ +namespace MassiveKnob.Plugin.CoreAudio.SetMuted +{ + /// + /// Interaction logic for DeviceSetMutedActionSettingsView.xaml + /// + public partial class DeviceSetMutedActionSettingsView + { + public DeviceSetMutedActionSettingsView(DeviceSetMutedActionSettingsViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettingsViewModel.cs new file mode 100644 index 0000000..7045a8c --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettingsViewModel.cs @@ -0,0 +1,42 @@ +using MassiveKnob.Plugin.CoreAudio.Base; + +namespace MassiveKnob.Plugin.CoreAudio.SetMuted +{ + public class DeviceSetMutedActionSettingsViewModel : BaseDeviceSettingsViewModel + { + // ReSharper disable UnusedMember.Global - used by WPF Binding + public bool Toggle + { + get => Settings.Toggle; + set + { + if (value == Settings.Toggle) + return; + + Settings.Toggle = value; + OnPropertyChanged(); + } + } + + + public bool SetInverted + { + get => Settings.SetInverted; + set + { + if (value == Settings.SetInverted) + return; + + Settings.SetInverted = value; + OnPropertyChanged(); + } + } + // ReSharper restore UnusedMember.Global + + + // ReSharper disable once SuggestBaseTypeForParameter - by design + public DeviceSetMutedActionSettingsViewModel(DeviceSetMutedActionSettings settings) : base(settings) + { + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Actions/DeviceVolumeAction.cs b/Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeAction.cs similarity index 73% rename from Windows/MassiveKnob.Plugin.CoreAudio/Actions/DeviceVolumeAction.cs rename to Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeAction.cs index db31607..4c8006e 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/Actions/DeviceVolumeAction.cs +++ b/Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeAction.cs @@ -2,16 +2,16 @@ using System.Threading.Tasks; using System.Windows.Controls; using AudioSwitcher.AudioApi; -using MassiveKnob.Plugin.CoreAudio.Settings; +using MassiveKnob.Plugin.CoreAudio.OSD; -namespace MassiveKnob.Plugin.CoreAudio.Actions +namespace MassiveKnob.Plugin.CoreAudio.SetVolume { - public class DeviceVolumeAction : IMassiveKnobAction + public class DeviceSetVolumeAction : IMassiveKnobAction { public Guid ActionId { get; } = new Guid("aabd329c-8be5-4d1e-90ab-5114143b21dd"); public MassiveKnobActionType ActionType { get; } = MassiveKnobActionType.InputAnalog; - public string Name { get; } = "Set volume"; - public string Description { get; } = "Sets the volume for the selected device, regardless of the current default device."; + public string Name { get; } = Strings.SetVolumeName; + public string Description { get; } = Strings.SetVolumeDescription; public IMassiveKnobActionInstance Create() @@ -23,14 +23,14 @@ namespace MassiveKnob.Plugin.CoreAudio.Actions private class Instance : IMassiveKnobAnalogAction { private IMassiveKnobActionContext actionContext; - private DeviceVolumeActionSettings settings; + private DeviceSetVolumeActionSettings settings; private IDevice playbackDevice; public void Initialize(IMassiveKnobActionContext context) { actionContext = context; - settings = context.GetSettings(); + settings = context.GetSettings(); ApplySettings(); } @@ -49,7 +49,7 @@ namespace MassiveKnob.Plugin.CoreAudio.Actions public UserControl CreateSettingsControl() { - var viewModel = new DeviceVolumeActionSettingsViewModel(settings); + var viewModel = new DeviceSetVolumeActionSettingsViewModel(settings); viewModel.PropertyChanged += (sender, args) => { if (!viewModel.IsSettingsProperty(args.PropertyName)) @@ -59,7 +59,7 @@ namespace MassiveKnob.Plugin.CoreAudio.Actions ApplySettings(); }; - return new DeviceVolumeActionSettingsView(viewModel); + return new DeviceSetVolumeActionSettingsView(viewModel); } @@ -69,6 +69,9 @@ namespace MassiveKnob.Plugin.CoreAudio.Actions return; await playbackDevice.SetVolumeAsync(value); + + if (settings.OSD) + OSDManager.Show(playbackDevice); } } } diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeActionSettings.cs b/Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeActionSettings.cs new file mode 100644 index 0000000..61d58c7 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeActionSettings.cs @@ -0,0 +1,9 @@ +using MassiveKnob.Plugin.CoreAudio.Base; + +namespace MassiveKnob.Plugin.CoreAudio.SetVolume +{ + public class DeviceSetVolumeActionSettings : BaseDeviceSettings + { + // TODO OSD + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeActionSettingsView.xaml b/Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeActionSettingsView.xaml new file mode 100644 index 0000000..fb068f2 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeActionSettingsView.xaml @@ -0,0 +1,14 @@ + + + + + diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeActionSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeActionSettingsView.xaml.cs new file mode 100644 index 0000000..9103cf2 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeActionSettingsView.xaml.cs @@ -0,0 +1,14 @@ +namespace MassiveKnob.Plugin.CoreAudio.SetVolume +{ + /// + /// Interaction logic for DeviceSetVolumeActionSettingsView.xaml + /// + public partial class DeviceSetVolumeActionSettingsView + { + public DeviceSetVolumeActionSettingsView(DeviceSetVolumeActionSettingsViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeActionSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeActionSettingsViewModel.cs new file mode 100644 index 0000000..baa5756 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/SetVolume/DeviceSetVolumeActionSettingsViewModel.cs @@ -0,0 +1,12 @@ +using MassiveKnob.Plugin.CoreAudio.Base; + +namespace MassiveKnob.Plugin.CoreAudio.SetVolume +{ + public class DeviceSetVolumeActionSettingsViewModel : BaseDeviceSettingsViewModel + { + // ReSharper disable once SuggestBaseTypeForParameter - by design + public DeviceSetVolumeActionSettingsViewModel(DeviceSetVolumeActionSettings settings) : base(settings) + { + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/BaseDeviceSettings.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/BaseDeviceSettings.cs deleted file mode 100644 index dd8cb66..0000000 --- a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/BaseDeviceSettings.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace MassiveKnob.Plugin.CoreAudio.Settings -{ - public class BaseDeviceSettings - { - public Guid? DeviceId { get; set; } - } -} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.cs deleted file mode 100644 index 6fa5eee..0000000 --- a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettings.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MassiveKnob.Plugin.CoreAudio.Settings -{ - public class DeviceVolumeActionSettings : BaseDeviceSettings - { - // TODO OSD - } -} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsView.xaml.cs deleted file mode 100644 index 7ecd71f..0000000 --- a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsView.xaml.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MassiveKnob.Plugin.CoreAudio.Settings -{ - /// - /// Interaction logic for DeviceVolumeActionSettingsView.xaml - /// - public partial class DeviceVolumeActionSettingsView - { - public DeviceVolumeActionSettingsView(DeviceVolumeActionSettingsViewModel viewModel) - { - DataContext = viewModel; - InitializeComponent(); - } - } -} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsViewModel.cs deleted file mode 100644 index 7e46566..0000000 --- a/Windows/MassiveKnob.Plugin.CoreAudio/Settings/DeviceVolumeActionSettingsViewModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MassiveKnob.Plugin.CoreAudio.Settings -{ - public class DeviceVolumeActionSettingsViewModel : BaseDeviceSettingsViewModel - { - // ReSharper disable once SuggestBaseTypeForParameter - by design - public DeviceVolumeActionSettingsViewModel(DeviceVolumeActionSettings settings) : base(settings) - { - } - } -} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Strings.Designer.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Strings.Designer.cs index 746832c..fd60a71 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/Strings.Designer.cs +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Strings.Designer.cs @@ -22,7 +22,7 @@ namespace MassiveKnob.Plugin.CoreAudio { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Strings { + public class Strings { private static global::System.Resources.ResourceManager resourceMan; @@ -36,7 +36,7 @@ namespace MassiveKnob.Plugin.CoreAudio { /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MassiveKnob.Plugin.CoreAudio.Strings", typeof(Strings).Assembly); @@ -51,7 +51,7 @@ namespace MassiveKnob.Plugin.CoreAudio { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,7 +63,7 @@ namespace MassiveKnob.Plugin.CoreAudio { /// /// Looks up a localized string similar to {0}. /// - internal static string DeviceDisplayNameActive { + public static string DeviceDisplayNameActive { get { return ResourceManager.GetString("DeviceDisplayNameActive", resourceCulture); } @@ -72,7 +72,7 @@ namespace MassiveKnob.Plugin.CoreAudio { /// /// Looks up a localized string similar to {0} (Disabled). /// - internal static string DeviceDisplayNameDisabled { + public static string DeviceDisplayNameDisabled { get { return ResourceManager.GetString("DeviceDisplayNameDisabled", resourceCulture); } @@ -81,7 +81,7 @@ namespace MassiveKnob.Plugin.CoreAudio { /// /// Looks up a localized string similar to {0} (Inactive). /// - internal static string DeviceDisplayNameInactive { + public static string DeviceDisplayNameInactive { get { return ResourceManager.GetString("DeviceDisplayNameInactive", resourceCulture); } @@ -90,7 +90,7 @@ namespace MassiveKnob.Plugin.CoreAudio { /// /// Looks up a localized string similar to {0} (Not present). /// - internal static string DeviceDisplayNameNotPresent { + public static string DeviceDisplayNameNotPresent { get { return ResourceManager.GetString("DeviceDisplayNameNotPresent", resourceCulture); } @@ -99,10 +99,154 @@ namespace MassiveKnob.Plugin.CoreAudio { /// /// Looks up a localized string similar to {0} (Unplugged). /// - internal static string DeviceDisplayNameUnplugged { + public static string DeviceDisplayNameUnplugged { get { return ResourceManager.GetString("DeviceDisplayNameUnplugged", resourceCulture); } } + + /// + /// Looks up a localized string similar to Sets the digital output to the muted state for the selected device, regardless of the current default device.. + /// + public static string GetMutedDescription { + get { + return ResourceManager.GetString("GetMutedDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Muted / unmuted. + /// + public static string GetMutedName { + get { + return ResourceManager.GetString("GetMutedName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets the analog output to the volume for the selected device, regardless of the current default device.. + /// + public static string GetVolumeDescription { + get { + return ResourceManager.GetString("GetVolumeDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Volume. + /// + public static string GetVolumeName { + get { + return ResourceManager.GetString("GetVolumeName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Included with Massive Knob by default. Provides volume control per device and default device switching.. + /// + public static string PluginDescription { + get { + return ResourceManager.GetString("PluginDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Windows Core Audio. + /// + public static string PluginName { + get { + return ResourceManager.GetString("PluginName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles the muted state for the selected device, regardless of the current default device.. + /// + public static string SetMutedDescription { + get { + return ResourceManager.GetString("SetMutedDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mute / unmute. + /// + public static string SetMutedName { + get { + return ResourceManager.GetString("SetMutedName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Inverted (muted is off). + /// + public static string SettingGetMutedInverted { + get { + return ResourceManager.GetString("SettingGetMutedInverted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show On-Screen Display. + /// + public static string SettingOSD { + get { + return ResourceManager.GetString("SettingOSD", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Playback device. + /// + public static string SettingPlaybackDevice { + get { + return ResourceManager.GetString("SettingPlaybackDevice", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Inverted (off is muted). + /// + public static string SettingSetMutedSetInverted { + get { + return ResourceManager.GetString("SettingSetMutedSetInverted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Set mute depending on value (eg. switch). + /// + public static string SettingSetMutedToggleFalse { + get { + return ResourceManager.GetString("SettingSetMutedToggleFalse", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggle mute when input turns on (eg. push button). + /// + public static string SettingSetMutedToggleTrue { + get { + return ResourceManager.GetString("SettingSetMutedToggleTrue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets the volume for the selected device to the value of the analog input, regardless of the current default device.. + /// + public static string SetVolumeDescription { + get { + return ResourceManager.GetString("SetVolumeDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Volume. + /// + public static string SetVolumeName { + get { + return ResourceManager.GetString("SetVolumeName", resourceCulture); + } + } } } diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Strings.resx b/Windows/MassiveKnob.Plugin.CoreAudio/Strings.resx index 5a89d1c..f49a4c9 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/Strings.resx +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Strings.resx @@ -132,4 +132,52 @@ {0} (Unplugged) + + Sets the digital output to the muted state for the selected device, regardless of the current default device. + + + Muted / unmuted + + + Sets the analog output to the volume for the selected device, regardless of the current default device. + + + Volume + + + Included with Massive Knob by default. Provides volume control per device and default device switching. + + + Windows Core Audio + + + Toggles the muted state for the selected device, regardless of the current default device. + + + Mute / unmute + + + Inverted (muted is off) + + + Show On-Screen Display + + + Playback device + + + Inverted (off is muted) + + + Set mute depending on value (eg. switch) + + + Toggle mute when input turns on (eg. push button) + + + Sets the volume for the selected device to the value of the analog input, regardless of the current default device. + + + Volume + \ No newline at end of file diff --git a/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDevice.cs b/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDevice.cs new file mode 100644 index 0000000..542ad36 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDevice.cs @@ -0,0 +1,97 @@ +using System; +using System.Threading; +using System.Windows.Controls; +using MassiveKnob.Plugin.EmulatorDevice.Settings; + +namespace MassiveKnob.Plugin.EmulatorDevice.Devices +{ + public class EmulatorDevice : IMassiveKnobDevice + { + public Guid DeviceId { get; } = new Guid("e1a4977a-abf4-4c75-a17d-fd8d3a8451ff"); + public string Name { get; } = "Mock device"; + public string Description { get; } = "Emulates the actual device but does not communicate with anything."; + + public IMassiveKnobDeviceInstance Create() + { + return new Instance(); + } + + + private class Instance : IMassiveKnobDeviceInstance + { + private IMassiveKnobDeviceContext deviceContext; + private EmulatorDeviceSettings settings; + + private DeviceSpecs reportedSpecs; + private EmulatorDeviceWindow window; + private EmulatorDeviceWindowViewModel windowViewModel; + + + public void Initialize(IMassiveKnobDeviceContext context) + { + deviceContext = context; + settings = deviceContext.GetSettings(); + + windowViewModel = new EmulatorDeviceWindowViewModel(settings, context); + window = new EmulatorDeviceWindow(windowViewModel); + ApplySettings(); + } + + + public void Dispose() + { + window.Close(); + } + + + private void ApplySettings() + { + if (settings.AnalogInputCount != reportedSpecs.AnalogInputCount || + settings.DigitalInputCount != reportedSpecs.DigitalInputCount || + settings.AnalogOutputCount != reportedSpecs.AnalogOutputCount || + settings.DigitalOutputCount != reportedSpecs.DigitalOutputCount) + { + reportedSpecs = new DeviceSpecs( + settings.AnalogInputCount, settings.DigitalInputCount, + settings.AnalogOutputCount, settings.DigitalOutputCount); + + deviceContext.Connected(reportedSpecs); + } + + windowViewModel.ApplySettings(); + window.Show(); + } + + + public UserControl CreateSettingsControl() + { + var viewModel = new EmulatorDeviceSettingsViewModel(settings); + viewModel.PropertyChanged += (sender, args) => + { + deviceContext.SetSettings(settings); + ApplySettings(); + }; + + return new EmulatorDeviceSettingsView(viewModel); + } + + + public void SetAnalogOutput(int analogOutputIndex, byte value) + { + if (analogOutputIndex >= windowViewModel.AnalogOutputCount) + return; + + windowViewModel.AnalogOutputs[analogOutputIndex].AnalogValue = value; + } + + + public void SetDigitalOutput(int digitalOutputIndex, bool @on) + { + if (digitalOutputIndex >= windowViewModel.DigitalOutputCount) + return; + + windowViewModel.DigitalOutputs[digitalOutputIndex].DigitalValue = on; + } + } + } +} diff --git a/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDeviceWindow.xaml b/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDeviceWindow.xaml new file mode 100644 index 0000000..2c2585d --- /dev/null +++ b/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDeviceWindow.xaml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDeviceWindow.xaml.cs b/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDeviceWindow.xaml.cs new file mode 100644 index 0000000..1bfefbd --- /dev/null +++ b/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDeviceWindow.xaml.cs @@ -0,0 +1,14 @@ +namespace MassiveKnob.Plugin.EmulatorDevice.Devices +{ + /// + /// Interaction logic for EmulatorDeviceWindow.xaml + /// + public partial class EmulatorDeviceWindow + { + public EmulatorDeviceWindow(EmulatorDeviceWindowViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDeviceWindowViewModel.cs b/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDeviceWindowViewModel.cs new file mode 100644 index 0000000..54688ec --- /dev/null +++ b/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDeviceWindowViewModel.cs @@ -0,0 +1,264 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using MassiveKnob.Plugin.EmulatorDevice.Settings; + +namespace MassiveKnob.Plugin.EmulatorDevice.Devices +{ + public class EmulatorDeviceWindowViewModel : INotifyPropertyChanged + { + private readonly EmulatorDeviceSettings settings; + private readonly IMassiveKnobDeviceContext context; + public event PropertyChangedEventHandler PropertyChanged; + + + // ReSharper disable UnusedMember.Global - used by WPF Binding + private int analogInputCount; + public int AnalogInputCount + { + get => analogInputCount; + set + { + if (value == analogInputCount) + return; + + analogInputCount = value; + OnPropertyChanged(); + + AnalogInputs = Enumerable.Range(0, AnalogInputCount) + .Select(i => new InputOutputViewModel(context, MassiveKnobActionType.InputAnalog, i)) + .ToList(); + } + } + + private IList analogInputs; + public IList AnalogInputs + { + get => analogInputs; + set + { + analogInputs = value; + OnPropertyChanged(); + } + } + + + private int digitalInputCount; + public int DigitalInputCount + { + get => digitalInputCount; + set + { + if (value == digitalInputCount) + return; + + digitalInputCount = value; + OnPropertyChanged(); + + DigitalInputs = Enumerable.Range(0, DigitalInputCount) + .Select(i => new InputOutputViewModel(context, MassiveKnobActionType.InputDigital, i)) + .ToList(); + } + } + + private IList digitalInputs; + public IList DigitalInputs + { + get => digitalInputs; + set + { + digitalInputs = value; + OnPropertyChanged(); + } + } + + + private int analogOutputCount; + public int AnalogOutputCount + { + get => analogOutputCount; + set + { + if (value == analogOutputCount) + return; + + analogOutputCount = value; + OnPropertyChanged(); + + AnalogOutputs = Enumerable.Range(0, AnalogOutputCount) + .Select(i => new InputOutputViewModel(context, MassiveKnobActionType.OutputAnalog, i)) + .ToList(); + } + } + + + private IList analogOutputs; + public IList AnalogOutputs + { + get => analogOutputs; + set + { + analogOutputs = value; + OnPropertyChanged(); + } + } + + + private int digitalOutputCount; + public int DigitalOutputCount + { + get => digitalOutputCount; + set + { + if (value == digitalOutputCount) + return; + + digitalOutputCount = value; + OnPropertyChanged(); + + DigitalOutputs = Enumerable.Range(0, DigitalOutputCount) + .Select(i => new InputOutputViewModel(context, MassiveKnobActionType.OutputDigital, i)) + .ToList(); + } + } + + private IList digitalOutputs; + public IList DigitalOutputs + { + get => digitalOutputs; + set + { + digitalOutputs = value; + OnPropertyChanged(); + } + } + // ReSharper restore UnusedMember.Global + + + + public EmulatorDeviceWindowViewModel(EmulatorDeviceSettings settings, IMassiveKnobDeviceContext context) + { + this.settings = settings; + this.context = context; + + ApplySettings(); + } + + + public void ApplySettings() + { + AnalogInputCount = settings.AnalogInputCount; + DigitalInputCount = settings.DigitalInputCount; + AnalogOutputCount = settings.AnalogOutputCount; + DigitalOutputCount = settings.DigitalOutputCount; + } + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + + public class InputOutputViewModel : INotifyPropertyChanged + { + private readonly IMassiveKnobDeviceContext context; + public MassiveKnobActionType ActionType { get; } + public int Index { get; } + + public string DisplayName + { + get + { + switch (ActionType) + { + case MassiveKnobActionType.InputAnalog: + return $"Analog input #{Index + 1}"; + + case MassiveKnobActionType.InputDigital: + return $"Digital input #{Index + 1}"; + + case MassiveKnobActionType.OutputAnalog: + return $"Analog output #{Index + 1}"; + + case MassiveKnobActionType.OutputDigital: + return $"Digital output #{Index + 1}"; + + default: + return (Index + 1).ToString(); + } + } + } + + + private byte analogValue; + public byte AnalogValue + { + get => analogValue; + set + { + analogValue = value; + OnPropertyChanged(); + + if (ActionType == MassiveKnobActionType.InputAnalog) + // Context can be null in DesignTime + context?.AnalogChanged(Index, analogValue); + } + } + + private bool digitalValue; + public bool DigitalValue + { + get => digitalValue; + set + { + digitalValue = value; + OnPropertyChanged(); + OnDependantPropertyChanged("DigitalValueDisplayText"); + + if (ActionType == MassiveKnobActionType.InputDigital) + context?.DigitalChanged(Index, digitalValue); + } + } + + public string DigitalValueDisplayText => DigitalValue ? "On" : "Off"; + + + public InputOutputViewModel(IMassiveKnobDeviceContext context, MassiveKnobActionType actionType, int index) + { + this.context = context; + ActionType = actionType; + Index = index; + } + + + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected void OnDependantPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } + } + + + public class EmulatorDeviceWindowViewModelDesignTime : EmulatorDeviceWindowViewModel + { + public EmulatorDeviceWindowViewModelDesignTime() : base( + new EmulatorDeviceSettings + { + AnalogInputCount = 2, + DigitalInputCount = 2, + AnalogOutputCount = 2, + DigitalOutputCount = 2 + }, null) + { + } + } +} diff --git a/Windows/MassiveKnob.Plugin.MockDevice/MassiveKnob.Plugin.MockDevice.csproj b/Windows/MassiveKnob.Plugin.EmulatorDevice/MassiveKnob.Plugin.EmulatorDevice.csproj similarity index 73% rename from Windows/MassiveKnob.Plugin.MockDevice/MassiveKnob.Plugin.MockDevice.csproj rename to Windows/MassiveKnob.Plugin.EmulatorDevice/MassiveKnob.Plugin.EmulatorDevice.csproj index 8a0f341..454d0d2 100644 --- a/Windows/MassiveKnob.Plugin.MockDevice/MassiveKnob.Plugin.MockDevice.csproj +++ b/Windows/MassiveKnob.Plugin.EmulatorDevice/MassiveKnob.Plugin.EmulatorDevice.csproj @@ -7,8 +7,8 @@ {674DE974-B134-4DB5-BFAF-7BC3D05E16DE} Library Properties - MassiveKnob.Plugin.MockDevice - MassiveKnob.Plugin.MockDevice + MassiveKnob.Plugin.EmulatorDevice + MassiveKnob.Plugin.EmulatorDevice v4.7.2 512 true @@ -45,14 +45,18 @@ - - - - - MockDeviceSettingsView.xaml + + + EmulatorDeviceWindow.xaml - - + + + + + EmulatorDeviceSettingsView.xaml + + + @@ -62,7 +66,11 @@ - + + MSBuild:Compile + Designer + + Designer MSBuild:Compile diff --git a/Windows/MassiveKnob.Plugin.MockDevice/MassiveKnobMockDevicePlugin.cs b/Windows/MassiveKnob.Plugin.EmulatorDevice/MassiveKnobEmulatorDevicePlugin.cs similarity index 78% rename from Windows/MassiveKnob.Plugin.MockDevice/MassiveKnobMockDevicePlugin.cs rename to Windows/MassiveKnob.Plugin.EmulatorDevice/MassiveKnobEmulatorDevicePlugin.cs index feb9eb5..f19c347 100644 --- a/Windows/MassiveKnob.Plugin.MockDevice/MassiveKnobMockDevicePlugin.cs +++ b/Windows/MassiveKnob.Plugin.EmulatorDevice/MassiveKnobEmulatorDevicePlugin.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; -namespace MassiveKnob.Plugin.MockDevice +namespace MassiveKnob.Plugin.EmulatorDevice { [MassiveKnobPlugin] - public class MassiveKnobMockDevicePlugin : IMassiveKnobDevicePlugin + public class MassiveKnobEmulatorDevicePlugin : IMassiveKnobDevicePlugin { public Guid PluginId { get; } = new Guid("85f04232-d70f-494c-94a2-41452591ffb3"); public string Name { get; } = "Mock Device"; @@ -14,7 +14,7 @@ namespace MassiveKnob.Plugin.MockDevice public IEnumerable Devices { get; } = new IMassiveKnobDevice[] { - new Devices.MockDevice() + new EmulatorDevice.Devices.EmulatorDevice() }; } } diff --git a/Windows/MassiveKnob.Plugin.MockDevice/Properties/AssemblyInfo.cs b/Windows/MassiveKnob.Plugin.EmulatorDevice/Properties/AssemblyInfo.cs similarity index 100% rename from Windows/MassiveKnob.Plugin.MockDevice/Properties/AssemblyInfo.cs rename to Windows/MassiveKnob.Plugin.EmulatorDevice/Properties/AssemblyInfo.cs diff --git a/Windows/MassiveKnob.Plugin.EmulatorDevice/Settings/EmulatorDeviceSettings.cs b/Windows/MassiveKnob.Plugin.EmulatorDevice/Settings/EmulatorDeviceSettings.cs new file mode 100644 index 0000000..1091b7c --- /dev/null +++ b/Windows/MassiveKnob.Plugin.EmulatorDevice/Settings/EmulatorDeviceSettings.cs @@ -0,0 +1,10 @@ +namespace MassiveKnob.Plugin.EmulatorDevice.Settings +{ + public class EmulatorDeviceSettings + { + public int AnalogInputCount { get; set; } = 2; + public int DigitalInputCount { get; set; } = 2; + public int AnalogOutputCount { get; set; } = 2; + public int DigitalOutputCount { get; set; } = 2; + } +} diff --git a/Windows/MassiveKnob.Plugin.EmulatorDevice/Settings/EmulatorDeviceSettingsView.xaml b/Windows/MassiveKnob.Plugin.EmulatorDevice/Settings/EmulatorDeviceSettingsView.xaml new file mode 100644 index 0000000..2f05a7d --- /dev/null +++ b/Windows/MassiveKnob.Plugin.EmulatorDevice/Settings/EmulatorDeviceSettingsView.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + Analog inputs + + + Digital inputs + + + Analog outputs + + + Digital outputs + + + diff --git a/Windows/MassiveKnob.Plugin.EmulatorDevice/Settings/EmulatorDeviceSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.EmulatorDevice/Settings/EmulatorDeviceSettingsView.xaml.cs new file mode 100644 index 0000000..2ab7f89 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.EmulatorDevice/Settings/EmulatorDeviceSettingsView.xaml.cs @@ -0,0 +1,15 @@ +namespace MassiveKnob.Plugin.EmulatorDevice.Settings +{ + /// + /// Interaction logic for EmulatorDeviceSettingsView.xaml + /// + public partial class EmulatorDeviceSettingsView + { + public EmulatorDeviceSettingsView(EmulatorDeviceSettingsViewModel settingsViewModel) + { + DataContext = settingsViewModel; + + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.EmulatorDevice/Settings/EmulatorDeviceSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.EmulatorDevice/Settings/EmulatorDeviceSettingsViewModel.cs new file mode 100644 index 0000000..1afa309 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.EmulatorDevice/Settings/EmulatorDeviceSettingsViewModel.cs @@ -0,0 +1,81 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace MassiveKnob.Plugin.EmulatorDevice.Settings +{ + public class EmulatorDeviceSettingsViewModel : INotifyPropertyChanged + { + private readonly EmulatorDeviceSettings settings; + public event PropertyChangedEventHandler PropertyChanged; + + + // ReSharper disable UnusedMember.Global - used by WPF Binding + public int AnalogInputCount + { + get => settings.AnalogInputCount; + set + { + if (value == settings.AnalogInputCount) + return; + + settings.AnalogInputCount = value; + OnPropertyChanged(); + } + } + + + public int DigitalInputCount + { + get => settings.DigitalInputCount; + set + { + if (value == settings.DigitalInputCount) + return; + + settings.DigitalInputCount = value; + OnPropertyChanged(); + } + } + + + public int AnalogOutputCount + { + get => settings.AnalogOutputCount; + set + { + if (value == settings.AnalogOutputCount) + return; + + settings.AnalogOutputCount = value; + OnPropertyChanged(); + } + } + + + public int DigitalOutputCount + { + get => settings.DigitalOutputCount; + set + { + if (value == settings.DigitalOutputCount) + return; + + settings.DigitalOutputCount = value; + OnPropertyChanged(); + } + } + // ReSharper restore UnusedMember.Global + + + public EmulatorDeviceSettingsViewModel(EmulatorDeviceSettings settings) + { + this.settings = settings; + } + + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.MockDevice/Devices/MockDevice.cs b/Windows/MassiveKnob.Plugin.MockDevice/Devices/MockDevice.cs deleted file mode 100644 index 4112c92..0000000 --- a/Windows/MassiveKnob.Plugin.MockDevice/Devices/MockDevice.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Threading; -using System.Windows.Controls; -using MassiveKnob.Plugin.MockDevice.Settings; - -namespace MassiveKnob.Plugin.MockDevice.Devices -{ - public class MockDevice : IMassiveKnobDevice - { - public Guid DeviceId { get; } = new Guid("e1a4977a-abf4-4c75-a17d-fd8d3a8451ff"); - public string Name { get; } = "Mock device"; - public string Description { get; } = "Emulates the actual device but does not communicate with anything."; - - public IMassiveKnobDeviceInstance Create() - { - return new Instance(); - } - - - private class Instance : IMassiveKnobDeviceInstance - { - private IMassiveKnobDeviceContext deviceContext; - private MockDeviceSettings settings; - private Timer inputChangeTimer; - - private int reportedAnalogInputCount; - private int reportedDigitalInputCount; - private readonly Random random = new Random(); - - - public void Initialize(IMassiveKnobDeviceContext context) - { - deviceContext = context; - settings = deviceContext.GetSettings(); - - ApplySettings(); - } - - - public void Dispose() - { - inputChangeTimer?.Dispose(); - } - - - private void ApplySettings() - { - if (settings.AnalogCount != reportedAnalogInputCount || - settings.DigitalCount != reportedDigitalInputCount) - { - deviceContext.Connected(new DeviceSpecs(settings.AnalogCount, settings.DigitalCount, 0, 0)); - - reportedAnalogInputCount = settings.AnalogCount; - reportedDigitalInputCount = settings.DigitalCount; - } - - - var interval = TimeSpan.FromSeconds(Math.Max(settings.Interval, 1)); - - if (inputChangeTimer == null) - inputChangeTimer = new Timer(Tick, null, interval, interval); - else - inputChangeTimer.Change(interval, interval); - } - - - public UserControl CreateSettingsControl() - { - var viewModel = new MockDeviceSettingsViewModel(settings); - viewModel.PropertyChanged += (sender, args) => - { - deviceContext.SetSettings(settings); - ApplySettings(); - }; - - return new MockDeviceSettingsView(viewModel); - } - - - private void Tick(object state) - { - var totalInputCount = reportedAnalogInputCount + reportedDigitalInputCount; - if (totalInputCount == 0) - return; - - var changeInput = random.Next(0, totalInputCount); - - if (changeInput < reportedAnalogInputCount) - deviceContext.AnalogChanged(changeInput, (byte)random.Next(0, 101)); - else - deviceContext.DigitalChanged(changeInput - reportedAnalogInputCount, random.Next(0, 2) == 1); - } - } - } -} diff --git a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.cs b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.cs deleted file mode 100644 index 5990d4b..0000000 --- a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettings.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MassiveKnob.Plugin.MockDevice.Settings -{ - public class MockDeviceSettings - { - public int AnalogCount { get; set; } = 3; - public int DigitalCount { get; set; } = 1; - public int Interval { get; set; } = 5; - } -} diff --git a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsView.xaml b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsView.xaml deleted file mode 100644 index 6fcf56b..0000000 --- a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsView.xaml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - Number of analog inputs - - - Number of digital inputs - - - Random change interval (seconds) - - - diff --git a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsView.xaml.cs deleted file mode 100644 index 503729c..0000000 --- a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsView.xaml.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace MassiveKnob.Plugin.MockDevice.Settings -{ - /// - /// Interaction logic for MockDeviceSettingsView.xaml - /// - public partial class MockDeviceSettingsView - { - public MockDeviceSettingsView(MockDeviceSettingsViewModel settingsViewModel) - { - DataContext = settingsViewModel; - - InitializeComponent(); - } - } -} diff --git a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsViewModel.cs deleted file mode 100644 index 54040ad..0000000 --- a/Windows/MassiveKnob.Plugin.MockDevice/Settings/MockDeviceSettingsViewModel.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System.ComponentModel; -using System.Runtime.CompilerServices; - -namespace MassiveKnob.Plugin.MockDevice.Settings -{ - public class MockDeviceSettingsViewModel : INotifyPropertyChanged - { - private readonly MockDeviceSettings settings; - public event PropertyChangedEventHandler PropertyChanged; - - - // ReSharper disable UnusedMember.Global - used by WPF Binding - public int AnalogCount - { - get => settings.AnalogCount; - set - { - if (value == settings.AnalogCount) - return; - - settings.AnalogCount = value; - OnPropertyChanged(); - } - } - - - public int DigitalCount - { - get => settings.DigitalCount; - set - { - if (value == settings.DigitalCount) - return; - - settings.DigitalCount = value; - OnPropertyChanged(); - } - } - - - public int Interval - { - get => settings.Interval; - set - { - if (value == settings.Interval) - return; - - settings.Interval = value; - OnPropertyChanged(); - } - } - // ReSharper restore UnusedMember.Global - - - public MockDeviceSettingsViewModel(MockDeviceSettings settings) - { - this.settings = settings; - } - - - protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - } -} diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/Devices/SerialDevice.cs b/Windows/MassiveKnob.Plugin.SerialDevice/Devices/SerialDevice.cs index b155974..a3c1244 100644 --- a/Windows/MassiveKnob.Plugin.SerialDevice/Devices/SerialDevice.cs +++ b/Windows/MassiveKnob.Plugin.SerialDevice/Devices/SerialDevice.cs @@ -59,6 +59,17 @@ namespace MassiveKnob.Plugin.SerialDevice.Devices return new SerialDeviceSettingsView(viewModel); } + + + public void SetAnalogOutput(int analogOutputIndex, byte value) + { + // TODO Support SetAnalogOutput + } + + public void SetDigitalOutput(int digitalOutputIndex, bool @on) + { + // TODO Support SetDigitalOutput + } } } } diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/Worker/SerialWorker.cs b/Windows/MassiveKnob.Plugin.SerialDevice/Worker/SerialWorker.cs index 2e02f25..56c7b9b 100644 --- a/Windows/MassiveKnob.Plugin.SerialDevice/Worker/SerialWorker.cs +++ b/Windows/MassiveKnob.Plugin.SerialDevice/Worker/SerialWorker.cs @@ -174,6 +174,7 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker if ((char) response == 'H') { + // TODO support multiple I/O's var knobCount = serialPort.ReadByte(); if (knobCount > -1) { diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobDeviceInstance.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobDeviceInstance.cs index 1924a3e..b736b5c 100644 --- a/Windows/MassiveKnob.Plugin/IMassiveKnobDeviceInstance.cs +++ b/Windows/MassiveKnob.Plugin/IMassiveKnobDeviceInstance.cs @@ -19,5 +19,19 @@ namespace MassiveKnob.Plugin /// determined by the UserControl. Return null to indicate there are no settings for this device. /// UserControl CreateSettingsControl(); + + /// + /// Called when the state of an analog output should be changed. + /// + /// The index of the analog output to set. + /// The analog value in the range of 0 to 100. + void SetAnalogOutput(int analogOutputIndex, byte value); + + /// + /// Called when the state of a digital output should be changed. + /// + /// The index of the digital output to set. + /// Whether the signal is on or off. + void SetDigitalOutput(int digitalOutputIndex, bool on); } } diff --git a/Windows/MassiveKnob.sln b/Windows/MassiveKnob.sln index 8f3c00f..f992761 100644 --- a/Windows/MassiveKnob.sln +++ b/Windows/MassiveKnob.sln @@ -9,7 +9,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin", "Massi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin.CoreAudio", "MassiveKnob.Plugin.CoreAudio\MassiveKnob.Plugin.CoreAudio.csproj", "{5BD5E2F2-9923-4F74-AC69-ACDA0B847937}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin.MockDevice", "MassiveKnob.Plugin.MockDevice\MassiveKnob.Plugin.MockDevice.csproj", "{674DE974-B134-4DB5-BFAF-7BC3D05E16DE}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin.EmulatorDevice", "MassiveKnob.Plugin.EmulatorDevice\MassiveKnob.Plugin.EmulatorDevice.csproj", "{674DE974-B134-4DB5-BFAF-7BC3D05E16DE}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin.SerialDevice", "MassiveKnob.Plugin.SerialDevice\MassiveKnob.Plugin.SerialDevice.csproj", "{FC0D22D8-5F1B-4D85-8269-FA4837CDE3A2}" EndProject diff --git a/Windows/MassiveKnob.sln.DotSettings b/Windows/MassiveKnob.sln.DotSettings index 584d2d0..edc850c 100644 --- a/Windows/MassiveKnob.sln.DotSettings +++ b/Windows/MassiveKnob.sln.DotSettings @@ -1,2 +1,4 @@  - UI \ No newline at end of file + OSD + UI + OSD \ No newline at end of file diff --git a/Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs b/Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs index 2654904..aa7186d 100644 --- a/Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs +++ b/Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs @@ -97,7 +97,7 @@ namespace MassiveKnob.Model public MassiveKnobActionInfo GetAction(MassiveKnobActionType actionType, int index) { var list = GetActionMappingList(actionType); - return index >= list.Count ? null : list[index].ActionInfo; + return index >= list.Count ? null : list[index]?.ActionInfo; } @@ -244,7 +244,7 @@ namespace MassiveKnob.Model if (index >= list.Count) return; - if (list[index].Context != context) + if (list[index]?.Context != context) throw new InvalidOperationException("Caller must be the active action to retrieve the settings"); lock (settingsLock) @@ -273,7 +273,7 @@ namespace MassiveKnob.Model return; var mapping = GetActionMappingList(MassiveKnobActionType.InputAnalog); - if (mapping == null || analogInputIndex >= mapping.Count) + if (mapping == null || analogInputIndex >= mapping.Count || mapping[analogInputIndex] == null) return; if (mapping[analogInputIndex].ActionInfo.Instance is IMassiveKnobAnalogAction analogAction) @@ -286,8 +286,8 @@ namespace MassiveKnob.Model if (context != activeDeviceContext) return; - var mapping = GetActionMappingList(MassiveKnobActionType.InputAnalog); - if (mapping == null || digitalInputIndex >= mapping.Count) + var mapping = GetActionMappingList(MassiveKnobActionType.InputDigital); + if (mapping == null || digitalInputIndex >= mapping.Count || mapping[digitalInputIndex] == null) return; if (mapping[digitalInputIndex].ActionInfo.Instance is IMassiveKnobDigitalAction digitalAction) @@ -295,6 +295,38 @@ namespace MassiveKnob.Model } + public void SetAnalogOutput(IMassiveKnobActionContext context, IMassiveKnobAction action, int index, byte value) + { + if (activeDevice == null) + return; + + var list = GetActionMappingList(action.ActionType); + if (index >= list.Count) + return; + + if (list[index]?.Context != context) + return; + + activeDevice.Instance.SetAnalogOutput(index, value); + } + + + public void SetDigitalOutput(IMassiveKnobActionContext context, IMassiveKnobAction action, int index, bool on) + { + if (activeDevice == null) + return; + + var list = GetActionMappingList(action.ActionType); + if (index >= list.Count) + return; + + if (list[index]?.Context != context) + return; + + activeDevice.Instance.SetDigitalOutput(index, on); + } + + private List GetActionMappingList(MassiveKnobActionType actionType) { switch (actionType) @@ -534,15 +566,15 @@ namespace MassiveKnob.Model } - public void SetDigitalOutput(bool on) - { - throw new NotImplementedException(); - } - - public void SetAnalogOutput(byte value) { - throw new NotImplementedException(); + owner.SetAnalogOutput(this, action, index, value); + } + + + public void SetDigitalOutput(bool on) + { + owner.SetDigitalOutput(this, action, index, on); } } } diff --git a/Windows/MassiveKnob/Style.xaml b/Windows/MassiveKnob/Style.xaml index b7a4d7e..1553388 100644 --- a/Windows/MassiveKnob/Style.xaml +++ b/Windows/MassiveKnob/Style.xaml @@ -17,10 +17,9 @@ + \ No newline at end of file diff --git a/Windows/MassiveKnob/Resources/Logging.xaml b/Windows/MassiveKnob/Resources/Logging.xaml new file mode 100644 index 0000000..f6db3ce --- /dev/null +++ b/Windows/MassiveKnob/Resources/Logging.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + diff --git a/Windows/MassiveKnob/Resources/MainIcon.ico b/Windows/MassiveKnob/Resources/MainIcon.ico index b4abcb46d7a07f7ae68e4a0a5ac37a491e8d712c..208d78d255cf041377a820e744278a4820bca927 100644 GIT binary patch literal 167971 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 E1GkaVQ2+n{ literal 173247 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�Q#-1xVGck7JW$F_b${MaOC(sxK3dn@+Wmv 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 diff --git a/Windows/MassiveKnob/Resources/NotifyIcon.ico b/Windows/MassiveKnob/Resources/NotifyIcon.ico index fdc8219472f61730bd7a79fd97312bc860ad7576..9acaca5ed9d4f66516e54eafc58d975c49565fb4 100644 GIT binary patch literal 168930 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�KO0O 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

Ho&2Q^=hL2esm6-WX# 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 Xv_H_w6NGx4ouV;=8X%JZS=RkO110tt diff --git a/Windows/MassiveKnob/Resources/Startup.xaml b/Windows/MassiveKnob/Resources/Startup.xaml new file mode 100644 index 0000000..fa1de3f --- /dev/null +++ b/Windows/MassiveKnob/Resources/Startup.xaml @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/Windows/MassiveKnob/Settings/ILoggingSwitch.cs b/Windows/MassiveKnob/Settings/ILoggingSwitch.cs new file mode 100644 index 0000000..0e8b482 --- /dev/null +++ b/Windows/MassiveKnob/Settings/ILoggingSwitch.cs @@ -0,0 +1,9 @@ +using Serilog.Events; + +namespace MassiveKnob.Settings +{ + public interface ILoggingSwitch + { + void SetLogging(bool enabled, LogEventLevel minimumLevel); + } +} diff --git a/Windows/MassiveKnob/Settings/LoggingSwitch.cs b/Windows/MassiveKnob/Settings/LoggingSwitch.cs new file mode 100644 index 0000000..736f63d --- /dev/null +++ b/Windows/MassiveKnob/Settings/LoggingSwitch.cs @@ -0,0 +1,26 @@ +using System; +using Serilog.Core; +using Serilog.Events; + +namespace MassiveKnob.Settings +{ + public class LoggingSwitch : LoggingLevelSwitch, ILoggingSwitch + { + private bool enabled; + private LogEventLevel minimumLevel; + + + public bool IsIncluded(LogEvent logEvent) + { + return enabled && logEvent.Level >= minimumLevel; + } + + + // ReSharper disable ParameterHidesMember + public void SetLogging(bool enabled, LogEventLevel minimumLevel) + { + this.enabled = enabled; + this.minimumLevel = minimumLevel; + } + } +} diff --git a/Windows/MassiveKnob/Settings/Settings.cs b/Windows/MassiveKnob/Settings/MassiveKnobSettings.cs similarity index 58% rename from Windows/MassiveKnob/Settings/Settings.cs rename to Windows/MassiveKnob/Settings/MassiveKnobSettings.cs index 37c8b24..33a194a 100644 --- a/Windows/MassiveKnob/Settings/Settings.cs +++ b/Windows/MassiveKnob/Settings/MassiveKnobSettings.cs @@ -2,10 +2,25 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; +using Serilog.Events; namespace MassiveKnob.Settings { - public class Settings + public enum SettingsMenuItem + { + None, + Device, + AnalogInputs, + DigitalInputs, + AnalogOutputs, + DigitalOutputs, + Logging, + Startup + } + + + + public class MassiveKnobSettings { public DeviceSettings Device { get; set; } public List AnalogInput { get; set; } @@ -13,6 +28,20 @@ namespace MassiveKnob.Settings public List AnalogOutput { get; set; } public List DigitalOutput { get; set; } + private UISettings ui; + public UISettings UI + { + get => ui ?? (ui = new UISettings()); + set => ui = value ?? new UISettings(); + } + + private LogSettings log; + public LogSettings Log + { + get => log ?? (log = new LogSettings()); + set => log = value ?? new LogSettings(); + } + public void Verify() { @@ -23,15 +52,17 @@ namespace MassiveKnob.Settings } - public Settings Clone() + public MassiveKnobSettings Clone() { - return new Settings + return new MassiveKnobSettings { Device = Device?.Clone(), AnalogInput = AnalogInput.Select(a => a?.Clone()).ToList(), DigitalInput = DigitalInput.Select(a => a?.Clone()).ToList(), AnalogOutput = AnalogOutput.Select(a => a?.Clone()).ToList(), - DigitalOutput = DigitalOutput.Select(a => a?.Clone()).ToList() + DigitalOutput = DigitalOutput.Select(a => a?.Clone()).ToList(), + UI = UI.Clone(), + Log = Log.Clone() }; } @@ -70,5 +101,35 @@ namespace MassiveKnob.Settings }; } } + + + public class UISettings + { + public SettingsMenuItem ActiveMenuItem { get; set; } = SettingsMenuItem.None; + + public UISettings Clone() + { + return new UISettings + { + ActiveMenuItem = ActiveMenuItem + }; + } + } + + + public class LogSettings + { + public bool Enabled { get; set; } = true; + public LogEventLevel Level { get; set; } = LogEventLevel.Information; + + public LogSettings Clone() + { + return new LogSettings + { + Enabled = Enabled, + Level = Level + }; + } + } } } diff --git a/Windows/MassiveKnob/Settings/SettingsJsonSerializer.cs b/Windows/MassiveKnob/Settings/MassiveKnobSettingsJsonSerializer.cs similarity index 78% rename from Windows/MassiveKnob/Settings/SettingsJsonSerializer.cs rename to Windows/MassiveKnob/Settings/MassiveKnobSettingsJsonSerializer.cs index 57bfd7d..88ec34c 100644 --- a/Windows/MassiveKnob/Settings/SettingsJsonSerializer.cs +++ b/Windows/MassiveKnob/Settings/MassiveKnobSettingsJsonSerializer.cs @@ -8,7 +8,7 @@ using Newtonsoft.Json.Converters; namespace MassiveKnob.Settings { - public static class SettingsJsonSerializer + public static class MassiveKnobSettingsJsonSerializer { private static readonly JsonSerializerSettings DefaultSettings = new JsonSerializerSettings { @@ -29,12 +29,12 @@ namespace MassiveKnob.Settings } - public static Task Serialize(Settings settings) + public static Task Serialize(MassiveKnobSettings settings) { return Serialize(settings, GetDefaultFilename()); } - public static async Task Serialize(Settings settings, string filename) + public static async Task Serialize(MassiveKnobSettings settings, string filename) { var json = JsonConvert.SerializeObject(settings, DefaultSettings); @@ -47,14 +47,14 @@ namespace MassiveKnob.Settings } - public static Settings Deserialize() + public static MassiveKnobSettings Deserialize() { return Deserialize(GetDefaultFilename()); } - public static Settings Deserialize(string filename) + public static MassiveKnobSettings Deserialize(string filename) { - Settings settings = null; + MassiveKnobSettings settings = null; if (File.Exists(filename)) { @@ -67,11 +67,11 @@ namespace MassiveKnob.Settings } if (!string.IsNullOrEmpty(json)) - settings = JsonConvert.DeserializeObject(json, DefaultSettings); + settings = JsonConvert.DeserializeObject(json, DefaultSettings); } if (settings == null) - settings = new Settings(); + settings = new MassiveKnobSettings(); settings.Verify(); return settings; diff --git a/Windows/MassiveKnob/Strings.Designer.cs b/Windows/MassiveKnob/Strings.Designer.cs index 3611e3b..34f5acc 100644 --- a/Windows/MassiveKnob/Strings.Designer.cs +++ b/Windows/MassiveKnob/Strings.Designer.cs @@ -22,7 +22,7 @@ namespace MassiveKnob { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Strings { + public class Strings { private static global::System.Resources.ResourceManager resourceMan; @@ -36,7 +36,7 @@ namespace MassiveKnob { /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MassiveKnob.Strings", typeof(Strings).Assembly); @@ -51,7 +51,7 @@ namespace MassiveKnob { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,10 +63,226 @@ namespace MassiveKnob { /// /// Looks up a localized string similar to Not configured. /// - internal static string ActionNotConfigured { + public static string ActionNotConfigured { get { return ResourceManager.GetString("ActionNotConfigured", resourceCulture); } } + + /// + /// Looks up a localized string similar to Input #{0}. + /// + public static string InputHeader { + get { + return ResourceManager.GetString("InputHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enabled. + /// + public static string LoggingEnabled { + get { + return ResourceManager.GetString("LoggingEnabled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Logging level. + /// + public static string LoggingLevel { + get { + return ResourceManager.GetString("LoggingLevel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error. + /// + public static string LoggingLevelError { + get { + return ResourceManager.GetString("LoggingLevelError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Only serious errors are logged.. + /// + public static string LoggingLevelErrorDescription { + get { + return ResourceManager.GetString("LoggingLevelErrorDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Information. + /// + public static string LoggingLevelInformation { + get { + return ResourceManager.GetString("LoggingLevelInformation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Information, warnings and errors are logged. This is the recommended level.. + /// + public static string LoggingLevelInformationDescription { + get { + return ResourceManager.GetString("LoggingLevelInformationDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Verbose. + /// + public static string LoggingLevelVerbose { + get { + return ResourceManager.GetString("LoggingLevelVerbose", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to I like big logs and I can not lie!. + /// + public static string LoggingLevelVerboseDescription { + get { + return ResourceManager.GetString("LoggingLevelVerboseDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Warning. + /// + public static string LoggingLevelWarning { + get { + return ResourceManager.GetString("LoggingLevelWarning", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Warnings and errors are logged. This includes issues with loading plugins.. + /// + public static string LoggingLevelWarningDescription { + get { + return ResourceManager.GetString("LoggingLevelWarningDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Logs are saved to {0}. + /// + public static string LoggingOutputPath { + get { + return ResourceManager.GetString("LoggingOutputPath", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Device. + /// + public static string MenuGroupDevice { + get { + return ResourceManager.GetString("MenuGroupDevice", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Settings. + /// + public static string MenuGroupSettings { + get { + return ResourceManager.GetString("MenuGroupSettings", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Analog inputs. + /// + public static string MenuItemAnalogInputs { + get { + return ResourceManager.GetString("MenuItemAnalogInputs", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Analog outputs. + /// + public static string MenuItemAnalogOutputs { + get { + return ResourceManager.GetString("MenuItemAnalogOutputs", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Device. + /// + public static string MenuItemDevice { + get { + return ResourceManager.GetString("MenuItemDevice", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Digital inputs. + /// + public static string MenuItemDigitalInputs { + get { + return ResourceManager.GetString("MenuItemDigitalInputs", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Digital outputs. + /// + public static string MenuItemDigitalOutputs { + get { + return ResourceManager.GetString("MenuItemDigitalOutputs", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Logging. + /// + public static string MenuItemLogging { + get { + return ResourceManager.GetString("MenuItemLogging", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Startup. + /// + public static string MenuItemStartup { + get { + return ResourceManager.GetString("MenuItemStartup", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Output #{0}. + /// + public static string OutputHeader { + get { + return ResourceManager.GetString("OutputHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Run MassiveKnob at startup. + /// + public static string RunAtStartup { + get { + return ResourceManager.GetString("RunAtStartup", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Massive Knob - Settings. + /// + public static string SettingsWindowTitle { + get { + return ResourceManager.GetString("SettingsWindowTitle", resourceCulture); + } + } } } diff --git a/Windows/MassiveKnob/Strings.resx b/Windows/MassiveKnob/Strings.resx index 72ba0d9..bb908fd 100644 --- a/Windows/MassiveKnob/Strings.resx +++ b/Windows/MassiveKnob/Strings.resx @@ -120,4 +120,76 @@ Not configured + + Input #{0} + + + Enabled + + + Logging level + + + Error + + + Only serious errors are logged. + + + Information + + + Information, warnings and errors are logged. This is the recommended level. + + + Verbose + + + I like big logs and I can not lie! + + + Warning + + + Warnings and errors are logged. This includes issues with loading plugins. + + + Logs are saved to {0} + + + Device + + + Settings + + + Analog inputs + + + Analog outputs + + + Device + + + Digital inputs + + + Digital outputs + + + Logging + + + Startup + + + Output #{0} + + + Run MassiveKnob at startup + + + Massive Knob - Settings + \ No newline at end of file diff --git a/Windows/MassiveKnob/Style.xaml b/Windows/MassiveKnob/Style.xaml index 1553388..37a8685 100644 --- a/Windows/MassiveKnob/Style.xaml +++ b/Windows/MassiveKnob/Style.xaml @@ -1,7 +1,7 @@  + + + + \ No newline at end of file diff --git a/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml b/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml new file mode 100644 index 0000000..c2a890e --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml.cs b/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml.cs new file mode 100644 index 0000000..4b0a7e4 --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml.cs @@ -0,0 +1,13 @@ +namespace MassiveKnob.View.Settings +{ + /// + /// Interaction logic for AnalogInputsView.xaml + /// + public partial class AnalogInputsView + { + public AnalogInputsView() + { + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml b/Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml new file mode 100644 index 0000000..19e8585 --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml.cs b/Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml.cs new file mode 100644 index 0000000..b0b0d57 --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml.cs @@ -0,0 +1,13 @@ +namespace MassiveKnob.View.Settings +{ + /// + /// Interaction logic for AnalogOutputsView.xaml + /// + public partial class AnalogOutputsView + { + public AnalogOutputsView() + { + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob/View/Settings/DeviceView.xaml b/Windows/MassiveKnob/View/Settings/DeviceView.xaml new file mode 100644 index 0000000..c540ea7 --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/DeviceView.xaml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Windows/MassiveKnob/View/Settings/DeviceView.xaml.cs b/Windows/MassiveKnob/View/Settings/DeviceView.xaml.cs new file mode 100644 index 0000000..5cfec2b --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/DeviceView.xaml.cs @@ -0,0 +1,13 @@ +namespace MassiveKnob.View.Settings +{ + /// + /// Interaction logic for DeviceView.xaml + /// + public partial class DeviceView + { + public DeviceView() + { + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml b/Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml new file mode 100644 index 0000000..652fc93 --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml.cs b/Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml.cs new file mode 100644 index 0000000..189a997 --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml.cs @@ -0,0 +1,13 @@ +namespace MassiveKnob.View.Settings +{ + /// + /// Interaction logic for DigitalInputsView.xaml + /// + public partial class DigitalInputsView + { + public DigitalInputsView() + { + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml b/Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml new file mode 100644 index 0000000..87d67d9 --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml.cs b/Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml.cs new file mode 100644 index 0000000..61f2a1c --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml.cs @@ -0,0 +1,13 @@ +namespace MassiveKnob.View.Settings +{ + /// + /// Interaction logic for DigitalOutputsView.xaml + /// + public partial class DigitalOutputsView + { + public DigitalOutputsView() + { + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob/View/Settings/LoggingView.xaml b/Windows/MassiveKnob/View/Settings/LoggingView.xaml new file mode 100644 index 0000000..5a53d07 --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/LoggingView.xaml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Windows/MassiveKnob/View/Settings/LoggingView.xaml.cs b/Windows/MassiveKnob/View/Settings/LoggingView.xaml.cs new file mode 100644 index 0000000..7fe2838 --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/LoggingView.xaml.cs @@ -0,0 +1,13 @@ +namespace MassiveKnob.View.Settings +{ + /// + /// Interaction logic for LoggingView.xaml + /// + public partial class LoggingView + { + public LoggingView() + { + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob/View/Settings/StartupView.xaml b/Windows/MassiveKnob/View/Settings/StartupView.xaml new file mode 100644 index 0000000..f9ce4c2 --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/StartupView.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + diff --git a/Windows/MassiveKnob/View/Settings/StartupView.xaml.cs b/Windows/MassiveKnob/View/Settings/StartupView.xaml.cs new file mode 100644 index 0000000..b8ee75e --- /dev/null +++ b/Windows/MassiveKnob/View/Settings/StartupView.xaml.cs @@ -0,0 +1,13 @@ +namespace MassiveKnob.View.Settings +{ + /// + /// Interaction logic for StartupView.xaml + /// + public partial class StartupView + { + public StartupView() + { + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob/View/SettingsWindow.xaml b/Windows/MassiveKnob/View/SettingsWindow.xaml index f88f45d..ddc16e1 100644 --- a/Windows/MassiveKnob/View/SettingsWindow.xaml +++ b/Windows/MassiveKnob/View/SettingsWindow.xaml @@ -3,118 +3,89 @@ 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:helpers="clr-namespace:MassiveKnob.Helpers" xmlns:viewModel="clr-namespace:MassiveKnob.ViewModel" - xmlns:view="clr-namespace:MassiveKnob.View" + xmlns:massiveKnob="clr-namespace:MassiveKnob" + xmlns:helpers="clr-namespace:MassiveKnob.Helpers" + xmlns:settings="clr-namespace:MassiveKnob.Settings" mc:Ignorable="d" - Title="Massive Knob - Settings" Height="555" Width="704.231" + Title="{x:Static massiveKnob:Strings.SettingsWindowTitle}" Height="555" Width="800" WindowStartupLocation="CenterScreen" + ResizeMode="CanMinimize" Style="{StaticResource DefaultWindow}" - d:DataContext="{d:DesignInstance Type=viewModel:SettingsViewModel}"> + d:DataContext="{d:DesignInstance Type=viewModel:SettingsViewModelDesignTime, IsDesignTimeCreatable=True}"> + + + + + + - - - - - - + - - - + + + + + - - - - Device - - - + + + + + + + + + + + + + - + + + + - - - Analog inputs - - - - - - - - - - - - Digital inputs - - - - - - - - - - - - Analog outputs - - - - - - - - - - - - Digital outputs - - - - - - - - - - + + + + diff --git a/Windows/MassiveKnob/View/SettingsWindow.xaml.cs b/Windows/MassiveKnob/View/SettingsWindow.xaml.cs index 8d9ec31..66257fd 100644 --- a/Windows/MassiveKnob/View/SettingsWindow.xaml.cs +++ b/Windows/MassiveKnob/View/SettingsWindow.xaml.cs @@ -7,10 +7,16 @@ namespace MassiveKnob.View /// public partial class SettingsWindow { + // ReSharper disable once SuggestBaseTypeForParameter - for clarity public SettingsWindow(SettingsViewModel settingsViewModel) { DataContext = settingsViewModel; InitializeComponent(); + + Closed += (sender, args) => + { + settingsViewModel.Dispose(); + }; } } } diff --git a/Windows/MassiveKnob/ViewModel/InputOutputViewModel.cs b/Windows/MassiveKnob/ViewModel/InputOutputViewModel.cs index ceb71cb..bd81402 100644 --- a/Windows/MassiveKnob/ViewModel/InputOutputViewModel.cs +++ b/Windows/MassiveKnob/ViewModel/InputOutputViewModel.cs @@ -1,14 +1,15 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Windows.Controls; -using MassiveKnob.Model; +using MassiveKnob.Core; using MassiveKnob.Plugin; namespace MassiveKnob.ViewModel { - public class InputOutputViewModel : INotifyPropertyChanged + public class InputOutputViewModel : IDisposable, INotifyPropertyChanged { private readonly IMassiveKnobOrchestrator orchestrator; private readonly MassiveKnobActionType actionType; @@ -19,9 +20,11 @@ namespace MassiveKnob.ViewModel // ReSharper disable UnusedMember.Global - used by WPF Binding - public string DisplayName => actionType == MassiveKnobActionType.OutputAnalog || actionType == MassiveKnobActionType.OutputDigital - ? $"Output #{index + 1}" - : $"Input #{index + 1}"; + public string DisplayName => string.Format( + actionType == MassiveKnobActionType.OutputAnalog || actionType == MassiveKnobActionType.OutputDigital + ? Strings.OutputHeader + : Strings.InputHeader, + index + 1); public IList Actions { get; } @@ -51,6 +54,9 @@ namespace MassiveKnob.ViewModel if (value == actionSettingsControl) return; + if (actionSettingsControl is IDisposable disposable) + disposable.Dispose(); + actionSettingsControl = value; OnPropertyChanged(); } @@ -63,6 +69,11 @@ namespace MassiveKnob.ViewModel this.orchestrator = orchestrator; this.actionType = actionType; this.index = index; + + + // For design-time support + if (orchestrator == null) + return; Actions = settingsViewModel.Actions.Where(a => a.RepresentsNull || a.Action.ActionType == actionType).ToList(); @@ -76,6 +87,13 @@ namespace MassiveKnob.ViewModel actionSettingsControl = actionInfo?.Instance.CreateSettingsControl(); } + + public void Dispose() + { + if (ActionSettingsControl is IDisposable disposable) + disposable.Dispose(); + } + public event PropertyChangedEventHandler PropertyChanged; diff --git a/Windows/MassiveKnob/ViewModel/LoggingLevelViewModel.cs b/Windows/MassiveKnob/ViewModel/LoggingLevelViewModel.cs new file mode 100644 index 0000000..f0f8959 --- /dev/null +++ b/Windows/MassiveKnob/ViewModel/LoggingLevelViewModel.cs @@ -0,0 +1,20 @@ +using Serilog.Events; + +namespace MassiveKnob.ViewModel +{ + public class LoggingLevelViewModel + { + public LogEventLevel Level { get; } + public string Name { get; } + public string Description { get; } + + + public LoggingLevelViewModel(LogEventLevel level, string name, string description) + { + Level = level; + Name = name; + Description = description; + } + } + +} diff --git a/Windows/MassiveKnob/ViewModel/MenuItemProperties.cs b/Windows/MassiveKnob/ViewModel/MenuItemProperties.cs new file mode 100644 index 0000000..e53e18b --- /dev/null +++ b/Windows/MassiveKnob/ViewModel/MenuItemProperties.cs @@ -0,0 +1,21 @@ +using System.Windows; +using System.Windows.Controls; + +namespace MassiveKnob.ViewModel +{ + public static class MenuItemProperties + { + public static string GetText(DependencyObject obj) { return (string) obj.GetValue(TextProperty); } + public static void SetText(DependencyObject obj, string value) { obj.SetValue(TextProperty, value); } + + public static readonly DependencyProperty TextProperty = + DependencyProperty.RegisterAttached("Text", typeof(string), typeof(MenuItemProperties), new FrameworkPropertyMetadata("Menu item")); + + + public static Viewbox GetIcon(DependencyObject obj) { return (Viewbox)obj.GetValue(IconProperty); } + public static void SetIcon(DependencyObject obj, Viewbox value) { obj.SetValue(IconProperty, value); } + + public static readonly DependencyProperty IconProperty = + DependencyProperty.RegisterAttached("Icon", typeof(Viewbox), typeof(MenuItemProperties), new FrameworkPropertyMetadata(null)); + } +} diff --git a/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs b/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs index bc7ed43..ff58946 100644 --- a/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs +++ b/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs @@ -1,20 +1,42 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; +using System.IO; using System.Linq; +using System.Reflection; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Controls; -using MassiveKnob.Model; +using MassiveKnob.Core; using MassiveKnob.Plugin; +using MassiveKnob.Settings; +using MassiveKnob.View.Settings; +using Microsoft.Win32; +using Serilog.Events; namespace MassiveKnob.ViewModel { - // TODO (nice to have) better design-time version - public class SettingsViewModel : INotifyPropertyChanged + public class SettingsViewModel : IDisposable, INotifyPropertyChanged { + private readonly Dictionary menuItemControls = new Dictionary + { + { SettingsMenuItem.Device, typeof(DeviceView) }, + { SettingsMenuItem.AnalogInputs, typeof(AnalogInputsView) }, + { SettingsMenuItem.DigitalInputs, typeof(DigitalInputsView) }, + { SettingsMenuItem.AnalogOutputs, typeof(AnalogOutputsView) }, + { SettingsMenuItem.DigitalOutputs, typeof(DigitalOutputsView) }, + { SettingsMenuItem.Logging, typeof(LoggingView) }, + { SettingsMenuItem.Startup, typeof(StartupView) } + }; + + + private readonly IMassiveKnobOrchestrator orchestrator; + private readonly ILoggingSwitch loggingSwitch; private DeviceViewModel selectedDevice; + private UserControl selectedView; + private SettingsMenuItem selectedMenuItem; private UserControl settingsControl; private DeviceSpecs? specs; @@ -25,10 +47,46 @@ namespace MassiveKnob.ViewModel // ReSharper disable UnusedMember.Global - used by WPF Binding + public SettingsMenuItem SelectedMenuItem + { + get => selectedMenuItem; + set + { + if (value == selectedMenuItem) + return; + + selectedMenuItem = value; + OnPropertyChanged(); + + if (menuItemControls.TryGetValue(selectedMenuItem, out var viewType)) + SelectedView = (UserControl) Activator.CreateInstance(viewType); + + orchestrator?.UpdateSettings(settings => + { + settings.UI.ActiveMenuItem = selectedMenuItem; + }); + } + } + + public UserControl SelectedView + { + get => selectedView; + set + { + if (value == selectedView) + return; + + selectedView = value; + OnPropertyChanged(); + } + } + + + public IList Devices { get; } public IList Actions { get; } - + public DeviceViewModel SelectedDevice { get => selectedDevice; @@ -38,7 +96,7 @@ namespace MassiveKnob.ViewModel return; selectedDevice = value; - var deviceInfo = orchestrator.SetActiveDevice(value?.Device); + var deviceInfo = orchestrator?.SetActiveDevice(value?.Device); OnPropertyChanged(); @@ -54,6 +112,9 @@ namespace MassiveKnob.ViewModel if (value == settingsControl) return; + if (settingsControl is IDisposable disposable) + disposable.Dispose(); + settingsControl = value; OnPropertyChanged(); } @@ -71,6 +132,11 @@ namespace MassiveKnob.ViewModel OnDependantPropertyChanged("AnalogOutputVisibility"); OnDependantPropertyChanged("DigitalOutputVisibility"); + DisposeInputOutputViewModels(AnalogInputs); + DisposeInputOutputViewModels(DigitalInputs); + DisposeInputOutputViewModels(AnalogOutputs); + DisposeInputOutputViewModels(DigitalOutputs); + AnalogInputs = Enumerable .Range(0, specs?.AnalogInputCount ?? 0) .Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.InputAnalog, i)); @@ -89,6 +155,7 @@ namespace MassiveKnob.ViewModel } } + public Visibility AnalogInputVisibility => specs.HasValue && specs.Value.AnalogInputCount > 0 ? Visibility.Visible : Visibility.Collapsed; @@ -144,13 +211,80 @@ namespace MassiveKnob.ViewModel OnPropertyChanged(); } } + + + public IList LoggingLevels { get; } + + private LoggingLevelViewModel selectedLoggingLevel; + public LoggingLevelViewModel SelectedLoggingLevel + { + get => selectedLoggingLevel; + set + { + if (value == selectedLoggingLevel) + return; + + selectedLoggingLevel = value; + OnPropertyChanged(); + + ApplyLoggingSettings(); + } + } + + + private bool loggingEnabled; + public bool LoggingEnabled + { + get => loggingEnabled; + set + { + if (value == loggingEnabled) + return; + + loggingEnabled = value; + OnPropertyChanged(); + + ApplyLoggingSettings(); + } + } + + + // TODO (code quality) do not hardcode path here + public string LoggingOutputPath { get; } = string.Format(Strings.LoggingOutputPath, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob", @"Logs")); + + + private bool runAtStartup; + public bool RunAtStartup + { + get => runAtStartup; + set + { + if (value == runAtStartup) + return; + + runAtStartup = value; + OnPropertyChanged(); + + ApplyRunAtStartup(); + } + } // ReSharper restore UnusedMember.Global - - public SettingsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator) + public SettingsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator, ILoggingSwitch loggingSwitch) { this.orchestrator = orchestrator; + this.loggingSwitch = loggingSwitch; + + // For design-time support + if (orchestrator == null) + return; + + var activeMenuItem = orchestrator.GetSettings().UI.ActiveMenuItem; + if (activeMenuItem == SettingsMenuItem.None) + activeMenuItem = SettingsMenuItem.Device; + + SelectedMenuItem = activeMenuItem; orchestrator.ActiveDeviceSubject.Subscribe(info => { Specs = info.Specs; }); @@ -170,12 +304,83 @@ namespace MassiveKnob.ViewModel Actions = allActions; - if (orchestrator.ActiveDevice == null) - return; + if (orchestrator.ActiveDevice != null) + { + selectedDevice = Devices.Single(d => d.Device.DeviceId == orchestrator.ActiveDevice.Info.DeviceId); + SettingsControl = orchestrator.ActiveDevice.Instance.CreateSettingsControl(); + Specs = orchestrator.ActiveDevice.Specs; + } - selectedDevice = Devices.Single(d => d.Device.DeviceId == orchestrator.ActiveDevice.Info.DeviceId); - SettingsControl = orchestrator.ActiveDevice.Instance.CreateSettingsControl(); - Specs = orchestrator.ActiveDevice.Specs; + + var logSettings = orchestrator.GetSettings().Log; + LoggingLevels = new List + { + new LoggingLevelViewModel(LogEventLevel.Error, Strings.LoggingLevelError, Strings.LoggingLevelErrorDescription), + new LoggingLevelViewModel(LogEventLevel.Warning, Strings.LoggingLevelWarning, Strings.LoggingLevelWarningDescription), + new LoggingLevelViewModel(LogEventLevel.Information, Strings.LoggingLevelInformation, Strings.LoggingLevelInformationDescription), + new LoggingLevelViewModel(LogEventLevel.Verbose, Strings.LoggingLevelVerbose, Strings.LoggingLevelVerboseDescription), + }; + + selectedLoggingLevel = LoggingLevels.SingleOrDefault(l => l.Level == logSettings.Level) + ?? LoggingLevels.Single(l => l.Level == LogEventLevel.Information); + loggingEnabled = logSettings.Enabled; + + + var runKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", false); + runAtStartup = runKey?.GetValue("MassiveKnob") != null; + } + + + public void Dispose() + { + if (SettingsControl is IDisposable disposable) + disposable.Dispose(); + + DisposeInputOutputViewModels(AnalogInputs); + DisposeInputOutputViewModels(DigitalInputs); + DisposeInputOutputViewModels(AnalogOutputs); + DisposeInputOutputViewModels(DigitalOutputs); + } + + + private void ApplyLoggingSettings() + { + orchestrator?.UpdateSettings(settings => + { + settings.Log.Enabled = LoggingEnabled; + settings.Log.Level = SelectedLoggingLevel.Level; + }); + + loggingSwitch?.SetLogging(LoggingEnabled, selectedLoggingLevel.Level); + } + + + private void ApplyRunAtStartup() + { + var runKey = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true); + Debug.Assert(runKey != null, nameof(runKey) + " != null"); + + if (RunAtStartup) + { + var entryAssembly = Assembly.GetEntryAssembly(); + Debug.Assert(entryAssembly != null, nameof(entryAssembly) + " != null"); + + runKey.SetValue("MassiveKnob", new Uri(entryAssembly.CodeBase).LocalPath); + } + else + { + runKey.DeleteValue("MassiveKnob", false); + } + } + + + private static void DisposeInputOutputViewModels(IEnumerable viewModels) + { + if (viewModels == null) + return; + + foreach (var viewModel in viewModels) + viewModel.Dispose(); } @@ -191,4 +396,13 @@ namespace MassiveKnob.ViewModel PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } + + + public class SettingsViewModelDesignTime : SettingsViewModel + { + public SettingsViewModelDesignTime() : base(null, null, null) + { + Specs = new DeviceSpecs(2, 2, 2, 2); + } + } } diff --git a/Windows/min.NET b/Windows/min.NET index 6db7da6..65c76b3 160000 --- a/Windows/min.NET +++ b/Windows/min.NET @@ -1 +1 @@ -Subproject commit 6db7da6234713a50a2c278c00bcd710249738e5e +Subproject commit 65c76b3f214522dd5f1da3704b83375bf238daba From cae557e7e12588a7796ca0c42ae3b08ba71ff4c5 Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Sat, 6 Mar 2021 10:53:38 +0100 Subject: [PATCH 10/10] Implemented default device switching Added VoiceMeeter plugin (run macro and get status) Added Inno Setup script Added Fusion 360 files for housing designs --- .gitignore | 2 + .gitmodules | 4 + ...buttons, under desk, Arduino Pro Micro.f3d | Bin 0 -> 179148 bytes ... 3 pots, under desk, Arduino Pro Micro.f3d | Bin 0 -> 179332 bytes README.md | 45 ++-- Windows/BuildRelease.ps1 | 41 ++++ .../GetDefault/DeviceGetDefaultAction.cs | 101 +++++++++ .../DeviceGetDefaultActionSettings.cs | 12 ++ .../DeviceGetDefaultActionSettingsView.xaml | 28 +++ ...DeviceGetDefaultActionSettingsView.xaml.cs | 14 ++ ...DeviceGetDefaultActionSettingsViewModel.cs | 54 +++++ .../MassiveKnob.Plugin.CoreAudio.csproj | 23 +- .../MassiveKnobCoreAudioPlugin.cs | 8 +- .../SetDefault/DeviceSetDefaultAction.cs | 84 ++++++++ .../DeviceSetDefaultActionSettings.cs | 10 + .../DeviceSetDefaultActionSettingsView.xaml | 23 ++ ...DeviceSetDefaultActionSettingsView.xaml.cs | 14 ++ ...DeviceSetDefaultActionSettingsViewModel.cs | 41 ++++ .../Strings.Designer.cs | 90 ++++++++ .../MassiveKnob.Plugin.CoreAudio/Strings.resx | 30 +++ .../MassiveKnob.Plugin.EmulatorDevice.csproj | 2 +- .../MassiveKnob.Plugin.SerialDevice.csproj | 2 +- .../Base/BaseVoiceMeeterSettings.cs | 13 ++ .../Base/BaseVoiceMeeterSettingsView.xaml | 15 ++ .../Base/BaseVoiceMeeterSettingsView.xaml.cs | 13 ++ .../Base/BaseVoiceMeeterSettingsViewModel.cs | 84 ++++++++ .../VoiceMeeterGetParameterAction.cs | 139 ++++++++++++ .../VoiceMeeterGetParameterActionSettings.cs | 11 + ...eMeeterGetParameterActionSettingsView.xaml | 25 +++ ...eterGetParameterActionSettingsView.xaml.cs | 14 ++ ...eterGetParameterActionSettingsViewModel.cs | 54 +++++ .../IVoiceMeeterAction.cs | 7 + .../InstanceRegister.cs | 69 ++++++ .../MassiveKnob.Plugin.VoiceMeeter.csproj | 110 ++++++++++ .../MassiveKnobPlugin.json | 3 + .../MassiveKnobVoiceMeeterPlugin.cs | 23 ++ .../Properties/AssemblyInfo.cs | 36 ++++ .../RunMacro/VoiceMeeterRunMacroAction.cs | 80 +++++++ .../VoiceMeeterRunMacroActionSettings.cs | 9 + ...VoiceMeeterRunMacroActionSettingsView.xaml | 20 ++ ...ceMeeterRunMacroActionSettingsView.xaml.cs | 14 ++ ...ceMeeterRunMacroActionSettingsViewModel.cs | 29 +++ .../Strings.Designer.cs | 172 +++++++++++++++ .../Strings.resx | 157 ++++++++++++++ Windows/MassiveKnob.sln | 12 ++ Windows/MassiveKnob/Core/PluginManager.cs | 55 ++++- Windows/MassiveKnob/Resources/Analog.xaml | 2 +- Windows/MassiveKnob/Resources/Device.xaml | 2 +- Windows/MassiveKnob/Resources/Digital.xaml | 2 +- Windows/MassiveKnob/Resources/Logging.xaml | 2 +- Windows/MassiveKnob/Resources/Startup.xaml | 2 +- .../ViewModel/SettingsViewModel.cs | 2 + Windows/Setup/MassiveKnobSetup.iss | 197 ++++++++++++++++++ Windows/VoicemeeterRemote | 1 + 54 files changed, 1971 insertions(+), 31 deletions(-) create mode 100644 Hardware/Massive Knob housing - 2 pots, 2 buttons, under desk, Arduino Pro Micro.f3d create mode 100644 Hardware/Massive Knob housing - 3 pots, under desk, Arduino Pro Micro.f3d create mode 100644 Windows/BuildRelease.ps1 create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/GetDefault/DeviceGetDefaultAction.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/GetDefault/DeviceGetDefaultActionSettings.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/GetDefault/DeviceGetDefaultActionSettingsView.xaml create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/GetDefault/DeviceGetDefaultActionSettingsView.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/GetDefault/DeviceGetDefaultActionSettingsViewModel.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultAction.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultActionSettings.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultActionSettingsView.xaml create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultActionSettingsView.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultActionSettingsViewModel.cs create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/Base/BaseVoiceMeeterSettings.cs create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/Base/BaseVoiceMeeterSettingsView.xaml create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/Base/BaseVoiceMeeterSettingsView.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/Base/BaseVoiceMeeterSettingsViewModel.cs create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterAction.cs create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterActionSettings.cs create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterActionSettingsView.xaml create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterActionSettingsView.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterActionSettingsViewModel.cs create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/IVoiceMeeterAction.cs create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/InstanceRegister.cs create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/MassiveKnob.Plugin.VoiceMeeter.csproj create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/MassiveKnobPlugin.json create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/MassiveKnobVoiceMeeterPlugin.cs create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/Properties/AssemblyInfo.cs create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroAction.cs create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroActionSettings.cs create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroActionSettingsView.xaml create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroActionSettingsView.xaml.cs create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroActionSettingsViewModel.cs create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/Strings.Designer.cs create mode 100644 Windows/MassiveKnob.Plugin.VoiceMeeter/Strings.resx create mode 100644 Windows/Setup/MassiveKnobSetup.iss create mode 160000 Windows/VoicemeeterRemote diff --git a/.gitignore b/.gitignore index 74d1994..c6fe696 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ .vs/ bin/ obj/ +Windows/packages/ +Windows/Release/ *.user \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 7ccb927..ef386cb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ [submodule "Windows/min.NET"] path = Windows/min.NET url = https://github.com/MvRens/min.NET + +[submodule "Windows/VoicemeeterRemote"] + path = Windows/VoicemeeterRemote + url = https://github.com/MvRens/VoicemeeterRemote diff --git a/Hardware/Massive Knob housing - 2 pots, 2 buttons, under desk, Arduino Pro Micro.f3d b/Hardware/Massive Knob housing - 2 pots, 2 buttons, under desk, Arduino Pro Micro.f3d new file mode 100644 index 0000000000000000000000000000000000000000..fafd68c608083264ba40de41838de7991b0f3670 GIT binary patch literal 179148 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

j5kRH@WK|xM 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 + ApplySettings(); + } + + + public void Dispose() + { + deviceChanged?.Dispose(); + } + + + private void ApplySettings() + { + if (playbackDevice != null && playbackDevice.Id == settings.DeviceId) + return; + + var coreAudioController = CoreAudioControllerInstance.Acquire(); + playbackDevice = settings.DeviceId.HasValue ? coreAudioController.GetDevice(settings.DeviceId.Value) : null; + + deviceChanged?.Dispose(); + deviceChanged = playbackDevice?.PropertyChanged.Subscribe(PropertyChanged); + + CheckActive(); + } + + + public UserControl CreateSettingsControl() + { + var viewModel = new DeviceGetDefaultActionSettingsViewModel(settings); + viewModel.PropertyChanged += (sender, args) => + { + if (!viewModel.IsSettingsProperty(args.PropertyName)) + return; + + actionContext.SetSettings(settings); + ApplySettings(); + }; + + return new DeviceGetDefaultActionSettingsView(viewModel); + } + + + private void PropertyChanged(DevicePropertyChangedArgs args) + { + if (args.ChangedType != DeviceChangedType.DefaultChanged) + return; + + CheckActive(); + + // TODO default OSD + //if (settings.OSD) + //OSDManager.Show(args.Device); + } + + + private void CheckActive() + { + if (playbackDevice == null) + return; + + var isDefault = (settings.Playback && playbackDevice.IsDefaultDevice) || + (settings.Communications && playbackDevice.IsDefaultCommunicationsDevice); + + actionContext.SetDigitalOutput(settings.Inverted ? !isDefault : isDefault); + } + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/GetDefault/DeviceGetDefaultActionSettings.cs b/Windows/MassiveKnob.Plugin.CoreAudio/GetDefault/DeviceGetDefaultActionSettings.cs new file mode 100644 index 0000000..7ff2ea4 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/GetDefault/DeviceGetDefaultActionSettings.cs @@ -0,0 +1,12 @@ +using MassiveKnob.Plugin.CoreAudio.Base; + +namespace MassiveKnob.Plugin.CoreAudio.GetDefault +{ + public class DeviceGetDefaultActionSettings : BaseDeviceSettings + { + public bool Playback { get; set; } = true; + public bool Communications { get; set; } = true; + + public bool Inverted { get; set; } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/GetDefault/DeviceGetDefaultActionSettingsView.xaml b/Windows/MassiveKnob.Plugin.CoreAudio/GetDefault/DeviceGetDefaultActionSettingsView.xaml new file mode 100644 index 0000000..257cd89 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/GetDefault/DeviceGetDefaultActionSettingsView.xaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/GetDefault/DeviceGetDefaultActionSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.CoreAudio/GetDefault/DeviceGetDefaultActionSettingsView.xaml.cs new file mode 100644 index 0000000..47aa19b --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/GetDefault/DeviceGetDefaultActionSettingsView.xaml.cs @@ -0,0 +1,14 @@ +namespace MassiveKnob.Plugin.CoreAudio.GetDefault +{ + ///

+ /// Interaction logic for DeviceGetDefaultActionSettingsView.xaml + /// + public partial class DeviceGetDefaultActionSettingsView + { + public DeviceGetDefaultActionSettingsView(DeviceGetDefaultActionSettingsViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/GetDefault/DeviceGetDefaultActionSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.CoreAudio/GetDefault/DeviceGetDefaultActionSettingsViewModel.cs new file mode 100644 index 0000000..98018eb --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/GetDefault/DeviceGetDefaultActionSettingsViewModel.cs @@ -0,0 +1,54 @@ +using MassiveKnob.Plugin.CoreAudio.Base; + +namespace MassiveKnob.Plugin.CoreAudio.GetDefault +{ + public class DeviceGetDefaultActionSettingsViewModel : BaseDeviceSettingsViewModel + { + // ReSharper disable UnusedMember.Global - used by WPF Binding + public bool Playback + { + get => Settings.Playback; + set + { + if (value == Settings.Playback) + return; + + Settings.Playback = value; + OnPropertyChanged(); + } + } + + public bool Communications + { + get => Settings.Communications; + set + { + if (value == Settings.Communications) + return; + + Settings.Communications = value; + OnPropertyChanged(); + } + } + + public bool Inverted + { + get => Settings.Inverted; + set + { + if (value == Settings.Inverted) + return; + + Settings.Inverted = value; + OnPropertyChanged(); + } + } + // ReSharper restore UnusedMember.Global + + + // ReSharper disable once SuggestBaseTypeForParameter - by design + public DeviceGetDefaultActionSettingsViewModel(DeviceGetDefaultActionSettings settings) : base(settings) + { + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj index 1cab7bc..802bfcb 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj +++ b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj @@ -17,7 +17,7 @@ true full false - $(localappdata)\MassiveKnob\Plugins\CoreAudio\ + bin\Debug\ DEBUG;TRACE prompt 4 @@ -49,6 +49,12 @@ BaseDeviceSettingsView.xaml + + + + DeviceGetDefaultActionSettingsView.xaml + + @@ -61,6 +67,12 @@ + + + + DeviceSetDefaultActionSettingsView.xaml + + @@ -120,6 +132,10 @@ Designer MSBuild:Compile

*bxTx4S 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�p=y^$5+nnpzdQy3EB+H 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É&LnmvX-Nr%6lL}Mw&IQopI&)Lje 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

+ + MSBuild:Compile + Designer + MSBuild:Compile Designer @@ -136,6 +152,10 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + MSBuild:Compile Designer @@ -150,5 +170,6 @@ Always
+ \ No newline at end of file diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobCoreAudioPlugin.cs b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobCoreAudioPlugin.cs index 35267ed..5896fd1 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobCoreAudioPlugin.cs +++ b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobCoreAudioPlugin.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using MassiveKnob.Plugin.CoreAudio.GetDefault; using MassiveKnob.Plugin.CoreAudio.GetMuted; using MassiveKnob.Plugin.CoreAudio.GetVolume; +using MassiveKnob.Plugin.CoreAudio.SetDefault; using MassiveKnob.Plugin.CoreAudio.SetMuted; using MassiveKnob.Plugin.CoreAudio.SetVolume; @@ -21,8 +23,12 @@ namespace MassiveKnob.Plugin.CoreAudio { new DeviceSetVolumeAction(), new DeviceGetVolumeAction(), + new DeviceSetMutedAction(), - new DeviceGetMutedAction() + new DeviceGetMutedAction(), + + new DeviceSetDefaultAction(), + new DeviceGetDefaultAction() }; diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultAction.cs b/Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultAction.cs new file mode 100644 index 0000000..4026741 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultAction.cs @@ -0,0 +1,84 @@ +using System; +using System.Threading.Tasks; +using System.Windows.Controls; +using AudioSwitcher.AudioApi; +using Microsoft.Extensions.Logging; + +namespace MassiveKnob.Plugin.CoreAudio.SetDefault +{ + public class DeviceSetDefaultAction : IMassiveKnobAction + { + public Guid ActionId { get; } = new Guid("b76f1eb7-2419-42b4-9de4-9bfe6f65a841"); + public MassiveKnobActionType ActionType { get; } = MassiveKnobActionType.InputDigital; + public string Name { get; } = Strings.SetDefaultName; + public string Description { get; } = Strings.SetDefaultDescription; + + + public IMassiveKnobActionInstance Create(ILogger logger) + { + return new Instance(); + } + + + private class Instance : IMassiveKnobDigitalAction + { + private IMassiveKnobActionContext actionContext; + private DeviceSetDefaultActionSettings settings; + private IDevice playbackDevice; + + + public void Initialize(IMassiveKnobActionContext context) + { + actionContext = context; + settings = context.GetSettings(); + ApplySettings(); + } + + + public void Dispose() + { + } + + + private void ApplySettings() + { + var coreAudioController = CoreAudioControllerInstance.Acquire(); + playbackDevice = settings.DeviceId.HasValue ? coreAudioController.GetDevice(settings.DeviceId.Value) : null; + } + + + public UserControl CreateSettingsControl() + { + var viewModel = new DeviceSetDefaultActionSettingsViewModel(settings); + viewModel.PropertyChanged += (sender, args) => + { + if (!viewModel.IsSettingsProperty(args.PropertyName)) + return; + + actionContext.SetSettings(settings); + ApplySettings(); + }; + + return new DeviceSetDefaultActionSettingsView(viewModel); + } + + + public async ValueTask DigitalChanged(bool on) + { + if (playbackDevice == null || !on) + return; + + if (settings.Playback) + await playbackDevice.SetAsDefaultAsync(); + + if (settings.Communications) + await playbackDevice.SetAsDefaultCommunicationsAsync(); + + + // TODO OSD for default device + //if (settings.OSD) + //OSDManager.Show(playbackDevice); + } + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultActionSettings.cs b/Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultActionSettings.cs new file mode 100644 index 0000000..1c92c00 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultActionSettings.cs @@ -0,0 +1,10 @@ +using MassiveKnob.Plugin.CoreAudio.Base; + +namespace MassiveKnob.Plugin.CoreAudio.SetDefault +{ + public class DeviceSetDefaultActionSettings : BaseDeviceSettings + { + public bool Playback { get; set; } = true; + public bool Communications { get; set; } = true; + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultActionSettingsView.xaml b/Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultActionSettingsView.xaml new file mode 100644 index 0000000..bbf2c9a --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultActionSettingsView.xaml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultActionSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultActionSettingsView.xaml.cs new file mode 100644 index 0000000..4a12a89 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultActionSettingsView.xaml.cs @@ -0,0 +1,14 @@ +namespace MassiveKnob.Plugin.CoreAudio.SetDefault +{ + /// + /// Interaction logic for DeviceSetDefaultActionSettingsView.xaml + /// + public partial class DeviceSetDefaultActionSettingsView + { + public DeviceSetDefaultActionSettingsView(DeviceSetDefaultActionSettingsViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultActionSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultActionSettingsViewModel.cs new file mode 100644 index 0000000..be7cc94 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.CoreAudio/SetDefault/DeviceSetDefaultActionSettingsViewModel.cs @@ -0,0 +1,41 @@ +using MassiveKnob.Plugin.CoreAudio.Base; + +namespace MassiveKnob.Plugin.CoreAudio.SetDefault +{ + public class DeviceSetDefaultActionSettingsViewModel : BaseDeviceSettingsViewModel + { + // ReSharper disable UnusedMember.Global - used by WPF Binding + public bool Playback + { + get => Settings.Playback; + set + { + if (value == Settings.Playback) + return; + + Settings.Playback = value; + OnPropertyChanged(); + } + } + + public bool Communications + { + get => Settings.Communications; + set + { + if (value == Settings.Communications) + return; + + Settings.Communications = value; + OnPropertyChanged(); + } + } + // ReSharper restore UnusedMember.Global + + + // ReSharper disable once SuggestBaseTypeForParameter - by design + public DeviceSetDefaultActionSettingsViewModel(DeviceSetDefaultActionSettings settings) : base(settings) + { + } + } +} diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Strings.Designer.cs b/Windows/MassiveKnob.Plugin.CoreAudio/Strings.Designer.cs index e855eab..22fb5fc 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/Strings.Designer.cs +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Strings.Designer.cs @@ -105,6 +105,24 @@ namespace MassiveKnob.Plugin.CoreAudio { } } + /// + /// Looks up a localized string similar to Sets the digital output depending on whether the selected device is the active playback or communications device.. + /// + public static string GetDefaultDescription { + get { + return ResourceManager.GetString("GetDefaultDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Is default device. + /// + public static string GetDefaultName { + get { + return ResourceManager.GetString("GetDefaultName", resourceCulture); + } + } + /// /// Looks up a localized string similar to Sets the digital output to the muted state for the selected device, regardless of the current default device.. /// @@ -159,6 +177,24 @@ namespace MassiveKnob.Plugin.CoreAudio { } } + /// + /// Looks up a localized string similar to Changes the default playback and/or communications device when the input turns on.. + /// + public static string SetDefaultDescription { + get { + return ResourceManager.GetString("SetDefaultDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Set default device. + /// + public static string SetDefaultName { + get { + return ResourceManager.GetString("SetDefaultName", resourceCulture); + } + } + /// /// Looks up a localized string similar to Toggles the muted state for the selected device, regardless of the current default device.. /// @@ -177,6 +213,42 @@ namespace MassiveKnob.Plugin.CoreAudio { } } + /// + /// Looks up a localized string similar to is the default communications device. + /// + public static string SettingGetDefaultCommunications { + get { + return ResourceManager.GetString("SettingGetDefaultCommunications", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Inverted (off when the device is the default). + /// + public static string SettingGetDefaultInverted { + get { + return ResourceManager.GetString("SettingGetDefaultInverted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to is the default playback device. + /// + public static string SettingGetDefaultPlayback { + get { + return ResourceManager.GetString("SettingGetDefaultPlayback", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Turn on when the selected device. + /// + public static string SettingGetDefaultWhen { + get { + return ResourceManager.GetString("SettingGetDefaultWhen", resourceCulture); + } + } + /// /// Looks up a localized string similar to Inverted (off when muted). /// @@ -204,6 +276,24 @@ namespace MassiveKnob.Plugin.CoreAudio { } } + /// + /// Looks up a localized string similar to Set as the default communications device. + /// + public static string SettingSetDefaultCommunications { + get { + return ResourceManager.GetString("SettingSetDefaultCommunications", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Set as the default playback device. + /// + public static string SettingSetDefaultPlayback { + get { + return ResourceManager.GetString("SettingSetDefaultPlayback", resourceCulture); + } + } + /// /// Looks up a localized string similar to Inverted (muted when off). /// diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/Strings.resx b/Windows/MassiveKnob.Plugin.CoreAudio/Strings.resx index 1339126..9ba8dfb 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/Strings.resx +++ b/Windows/MassiveKnob.Plugin.CoreAudio/Strings.resx @@ -132,6 +132,12 @@ {0} (Unplugged) + + Sets the digital output depending on whether the selected device is the active playback or communications device. + + + Is default device + Sets the digital output to the muted state for the selected device, regardless of the current default device. @@ -150,12 +156,30 @@ Windows Core Audio + + Changes the default playback and/or communications device when the input turns on. + + + Set default device + Toggles the muted state for the selected device, regardless of the current default device. Mute / unmute + + is the default communications device + + + Inverted (off when the device is the default) + + + is the default playback device + + + Turn on when the selected device + Inverted (off when muted) @@ -165,6 +189,12 @@ Playback device + + Set as the default communications device + + + Set as the default playback device + Inverted (muted when off) diff --git a/Windows/MassiveKnob.Plugin.EmulatorDevice/MassiveKnob.Plugin.EmulatorDevice.csproj b/Windows/MassiveKnob.Plugin.EmulatorDevice/MassiveKnob.Plugin.EmulatorDevice.csproj index 92e4d38..a83d3e4 100644 --- a/Windows/MassiveKnob.Plugin.EmulatorDevice/MassiveKnob.Plugin.EmulatorDevice.csproj +++ b/Windows/MassiveKnob.Plugin.EmulatorDevice/MassiveKnob.Plugin.EmulatorDevice.csproj @@ -17,7 +17,7 @@ true full false - $(localappdata)\MassiveKnob\Plugins\EmulatorDevice\ + bin\Debug\ DEBUG;TRACE prompt 4 diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnob.Plugin.SerialDevice.csproj b/Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnob.Plugin.SerialDevice.csproj index 17e0d9f..8de965f 100644 --- a/Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnob.Plugin.SerialDevice.csproj +++ b/Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnob.Plugin.SerialDevice.csproj @@ -17,7 +17,7 @@ true full false - $(localappdata)\MassiveKnob\Plugins\SerialDevice\ + bin\Debug\ DEBUG;TRACE prompt 4 diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/Base/BaseVoiceMeeterSettings.cs b/Windows/MassiveKnob.Plugin.VoiceMeeter/Base/BaseVoiceMeeterSettings.cs new file mode 100644 index 0000000..f5c9273 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/Base/BaseVoiceMeeterSettings.cs @@ -0,0 +1,13 @@ +using Voicemeeter; + +namespace MassiveKnob.Plugin.VoiceMeeter.Base +{ + public class BaseVoiceMeeterSettings + { + public RunVoicemeeterParam Version + { + get => InstanceRegister.Version; + set => InstanceRegister.Version = value; + } + } +} diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/Base/BaseVoiceMeeterSettingsView.xaml b/Windows/MassiveKnob.Plugin.VoiceMeeter/Base/BaseVoiceMeeterSettingsView.xaml new file mode 100644 index 0000000..279f21e --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/Base/BaseVoiceMeeterSettingsView.xaml @@ -0,0 +1,15 @@ + + + + + + diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/Base/BaseVoiceMeeterSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.VoiceMeeter/Base/BaseVoiceMeeterSettingsView.xaml.cs new file mode 100644 index 0000000..31f0dc4 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/Base/BaseVoiceMeeterSettingsView.xaml.cs @@ -0,0 +1,13 @@ +namespace MassiveKnob.Plugin.VoiceMeeter.Base +{ + /// + /// Interaction logic for BaseVoiceMeeterSettingsView.xaml + /// + public partial class BaseVoiceMeeterSettingsView + { + public BaseVoiceMeeterSettingsView() + { + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/Base/BaseVoiceMeeterSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.VoiceMeeter/Base/BaseVoiceMeeterSettingsViewModel.cs new file mode 100644 index 0000000..d3bf795 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/Base/BaseVoiceMeeterSettingsViewModel.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using Voicemeeter; + +namespace MassiveKnob.Plugin.VoiceMeeter.Base +{ + public class BaseVoiceMeeterSettingsViewModel : BaseVoiceMeeterSettingsViewModel where T : BaseVoiceMeeterSettings + { + protected new T Settings => (T)base.Settings; + + public BaseVoiceMeeterSettingsViewModel(T settings) : base(settings) + { + } + } + + + + public class BaseVoiceMeeterSettingsViewModel : INotifyPropertyChanged + { + protected readonly BaseVoiceMeeterSettings Settings; + public event PropertyChangedEventHandler PropertyChanged; + + // ReSharper disable UnusedMember.Global - used by WPF Binding + public IList Versions { get; } + + private VoiceMeeterVersionViewModel selectedVersion; + public VoiceMeeterVersionViewModel SelectedVersion + { + get => selectedVersion; + set + { + if (value == selectedVersion) + return; + + selectedVersion = value; + OnPropertyChanged(); + + Settings.Version = value?.Version ?? RunVoicemeeterParam.None; + } + } + // ReSharper restore UnusedMember.Global + + + public BaseVoiceMeeterSettingsViewModel(BaseVoiceMeeterSettings settings) + { + Settings = settings; + + Versions = new List + { + new VoiceMeeterVersionViewModel(RunVoicemeeterParam.Voicemeeter, "VoiceMeeter Standard"), + new VoiceMeeterVersionViewModel(RunVoicemeeterParam.VoicemeeterBanana, "VoiceMeeter Banana"), + new VoiceMeeterVersionViewModel(RunVoicemeeterParam.VoicemeeterPotato, "VoiceMeeter Potato") + }; + } + + + public virtual bool IsSettingsProperty(string propertyName) + { + // SelectedVersion already trigger a VoiceMeeterVersionChanged for all instances, + // which causes the settings to be stored + return propertyName != nameof(Versions) && propertyName != nameof(SelectedVersion); + } + + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } + + public class VoiceMeeterVersionViewModel + { + public RunVoicemeeterParam Version { get; } + public string DisplayName { get; } + + + public VoiceMeeterVersionViewModel(RunVoicemeeterParam version, string displayName) + { + Version = version; + DisplayName = displayName; + } + } +} diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterAction.cs b/Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterAction.cs new file mode 100644 index 0000000..afbec34 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterAction.cs @@ -0,0 +1,139 @@ +using System; +using System.Threading.Tasks; +using System.Windows.Controls; +using Microsoft.Extensions.Logging; +using Voicemeeter; + +namespace MassiveKnob.Plugin.VoiceMeeter.GetParameter +{ + public class VoiceMeeterGetParameterAction : IMassiveKnobAction + { + public Guid ActionId { get; } = new Guid("4904fffb-aaec-4f19-88bb-49f6ed38c3ec"); + public MassiveKnobActionType ActionType { get; } = MassiveKnobActionType.OutputDigital; + public string Name { get; } = Strings.GetParameterName; + public string Description { get; } = Strings.GetParameterDescription; + + + public IMassiveKnobActionInstance Create(ILogger logger) + { + return new Instance(); + } + + + private class Instance : IMassiveKnobActionInstance, IVoiceMeeterAction + { + private IMassiveKnobActionContext actionContext; + private VoiceMeeterGetParameterActionSettings settings; + private Parameters parameters; + private IDisposable parameterChanged; + + + public void Initialize(IMassiveKnobActionContext context) + { + actionContext = context; + settings = context.GetSettings(); + ApplySettings(); + + InstanceRegister.Register(this); + } + + + public void Dispose() + { + InstanceRegister.Unregister(this); + parameterChanged?.Dispose(); + parameters?.Dispose(); + } + + + private void ApplySettings() + { + if (InstanceRegister.Version == RunVoicemeeterParam.None) + return; + + if (parameters == null) + parameters = new Parameters(); + + if (string.IsNullOrEmpty(settings.Parameter)) + { + parameterChanged?.Dispose(); + parameterChanged = null; + } + + if (parameterChanged == null) + parameterChanged = parameters.Subscribe(x => ParametersChanged()); + + // TODO directly update output depending on value + /* + if (playbackDevice != null) + actionContext.SetDigitalOutput(settings.Inverted ? !playbackDevice.IsMuted : playbackDevice.IsMuted); + */ + } + + + public UserControl CreateSettingsControl() + { + var viewModel = new VoiceMeeterGetParameterActionSettingsViewModel(settings); + viewModel.PropertyChanged += (sender, args) => + { + if (!viewModel.IsSettingsProperty(args.PropertyName)) + return; + + actionContext.SetSettings(settings); + ApplySettings(); + }; + + return new VoiceMeeterGetParameterActionSettingsView(viewModel); + } + + + public void VoiceMeeterVersionChanged() + { + // TODO update viewModel + // TODO reset parameterChanged subscription + + actionContext.SetSettings(settings); + } + + + private void ParametersChanged() + { + if (InstanceRegister.Version == RunVoicemeeterParam.None || string.IsNullOrEmpty(settings.Parameter)) + return; + + // TODO if another task is already running, wait / chain + // TODO only start task if not yet initialized + Task.Run(async () => + { + await InstanceRegister.InitializeVoicemeeter(); + bool on; + + if (float.TryParse(settings.Value, out var settingsFloatValue)) + { + try + { + // Even on/off values are returned as floating point "1.000" in text form, + // so try to compare in native format first + var floatValue = global::VoiceMeeter.Remote.GetParameter(settings.Parameter); + on = Math.Abs(settingsFloatValue - floatValue) < 0.001; + } + catch + { + // Fall back to text comparison + var value = global::VoiceMeeter.Remote.GetTextParameter(settings.Parameter); + on = string.Equals(value, settings.Value, StringComparison.InvariantCultureIgnoreCase); + } + } + else + { + var value = global::VoiceMeeter.Remote.GetTextParameter(settings.Parameter); + on = string.Equals(value, settings.Value, StringComparison.InvariantCultureIgnoreCase); + } + + // TODO check specific parameter for changes, not just any parameter + actionContext.SetDigitalOutput(settings.Inverted ? !on : on); + }); + } + } + } +} diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterActionSettings.cs b/Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterActionSettings.cs new file mode 100644 index 0000000..54919df --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterActionSettings.cs @@ -0,0 +1,11 @@ +using MassiveKnob.Plugin.VoiceMeeter.Base; + +namespace MassiveKnob.Plugin.VoiceMeeter.GetParameter +{ + public class VoiceMeeterGetParameterActionSettings : BaseVoiceMeeterSettings + { + public string Parameter { get; set; } + public string Value { get; set; } + public bool Inverted { get; set; } + } +} diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterActionSettingsView.xaml b/Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterActionSettingsView.xaml new file mode 100644 index 0000000..fa6b5e0 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterActionSettingsView.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterActionSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterActionSettingsView.xaml.cs new file mode 100644 index 0000000..c62f2d9 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterActionSettingsView.xaml.cs @@ -0,0 +1,14 @@ +namespace MassiveKnob.Plugin.VoiceMeeter.GetParameter +{ + /// + /// Interaction logic for VoiceMeeterGetParameterActionSettingsView.xaml + /// + public partial class VoiceMeeterGetParameterActionSettingsView + { + public VoiceMeeterGetParameterActionSettingsView(VoiceMeeterGetParameterActionSettingsViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterActionSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterActionSettingsViewModel.cs new file mode 100644 index 0000000..2959ff0 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/GetParameter/VoiceMeeterGetParameterActionSettingsViewModel.cs @@ -0,0 +1,54 @@ +using MassiveKnob.Plugin.VoiceMeeter.Base; + +namespace MassiveKnob.Plugin.VoiceMeeter.GetParameter +{ + public class VoiceMeeterGetParameterActionSettingsViewModel : BaseVoiceMeeterSettingsViewModel + { + // ReSharper disable UnusedMember.Global - used by WPF Binding + public string Parameter + { + get => Settings.Parameter; + set + { + if (value == Settings.Parameter) + return; + + Settings.Parameter = value; + OnPropertyChanged(); + } + } + + public string Value + { + get => Settings.Value; + set + { + if (value == Settings.Value) + return; + + Settings.Value = value; + OnPropertyChanged(); + } + } + + public bool Inverted + { + get => Settings.Inverted; + set + { + if (value == Settings.Inverted) + return; + + Settings.Inverted = value; + OnPropertyChanged(); + } + } + // ReSharper restore UnusedMember.Global + + + // ReSharper disable once SuggestBaseTypeForParameter - by design + public VoiceMeeterGetParameterActionSettingsViewModel(VoiceMeeterGetParameterActionSettings settings) : base(settings) + { + } + } +} diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/IVoiceMeeterAction.cs b/Windows/MassiveKnob.Plugin.VoiceMeeter/IVoiceMeeterAction.cs new file mode 100644 index 0000000..48f7419 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/IVoiceMeeterAction.cs @@ -0,0 +1,7 @@ +namespace MassiveKnob.Plugin.VoiceMeeter +{ + public interface IVoiceMeeterAction + { + void VoiceMeeterVersionChanged(); + } +} diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/InstanceRegister.cs b/Windows/MassiveKnob.Plugin.VoiceMeeter/InstanceRegister.cs new file mode 100644 index 0000000..e04e945 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/InstanceRegister.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Voicemeeter; + +namespace MassiveKnob.Plugin.VoiceMeeter +{ + public static class InstanceRegister + { + private static readonly object InstancesLock = new object(); + private static readonly HashSet Instances = new HashSet(); + private static Task initializeTask; + + + // The VoiceMeeter Remote only connects to one instance, so all actions need to be in sync + private static RunVoicemeeterParam version; + public static RunVoicemeeterParam Version + { + get => version; + set + { + if (value == version) + return; + + version = value; + Notify(action => action.VoiceMeeterVersionChanged()); + + initializeTask = Task.Run(async () => + { + await global::VoiceMeeter.Remote.Initialize(version); + }); + } + } + + + public static Task InitializeVoicemeeter() + { + return initializeTask ?? Task.CompletedTask; + } + + + public static void Register(IVoiceMeeterAction instance) + { + lock (InstancesLock) + { + Instances.Add(instance); + } + } + + + public static void Unregister(IVoiceMeeterAction instance) + { + lock (InstancesLock) + { + Instances.Remove(instance); + } + } + + + public static void Notify(Action action) + { + lock (InstancesLock) + { + foreach (var instance in Instances) + action(instance); + } + } + } +} diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/MassiveKnob.Plugin.VoiceMeeter.csproj b/Windows/MassiveKnob.Plugin.VoiceMeeter/MassiveKnob.Plugin.VoiceMeeter.csproj new file mode 100644 index 0000000..260bd28 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/MassiveKnob.Plugin.VoiceMeeter.csproj @@ -0,0 +1,110 @@ + + + + + Debug + AnyCPU + {19533600-D4F6-4BD4-82A3-C0234FDF044C} + Library + Properties + MassiveKnob.Plugin.VoiceMeeter + MassiveKnob.Plugin.VoiceMeeter + v4.7.2 + 512 + true + + + true + full + false + $(localappdata)\MassiveKnob\Plugins\VoiceMeeter\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + BaseVoiceMeeterSettingsView.xaml + + + + + + VoiceMeeterGetParameterActionSettingsView.xaml + + + + + + + + + + VoiceMeeterRunMacroActionSettingsView.xaml + + + + Strings.resx + True + True + + + + + PublicResXFileCodeGenerator + Strings.Designer.cs + + + + + {A1298BE4-1D23-416C-8C56-FC9264487A95} + MassiveKnob.Plugin + + + {f35dd8e5-91fa-403e-b6f6-8d2b4ae84198} + Voicemeeter + + + + + Always + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + + \ No newline at end of file diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/MassiveKnobPlugin.json b/Windows/MassiveKnob.Plugin.VoiceMeeter/MassiveKnobPlugin.json new file mode 100644 index 0000000..bac5965 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/MassiveKnobPlugin.json @@ -0,0 +1,3 @@ +{ + "EntryAssembly": "MassiveKnob.Plugin.VoiceMeeter.dll" +} diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/MassiveKnobVoiceMeeterPlugin.cs b/Windows/MassiveKnob.Plugin.VoiceMeeter/MassiveKnobVoiceMeeterPlugin.cs new file mode 100644 index 0000000..0158123 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/MassiveKnobVoiceMeeterPlugin.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using MassiveKnob.Plugin.VoiceMeeter.GetParameter; +using MassiveKnob.Plugin.VoiceMeeter.RunMacro; + +namespace MassiveKnob.Plugin.VoiceMeeter +{ + [MassiveKnobPlugin] + public class MassiveKnobVoiceMeeterPlugin : IMassiveKnobActionPlugin + { + public Guid PluginId { get; } = new Guid("cf6634f1-97e3-4a18-a4aa-289b558c0e82"); + public string Name { get; } = Strings.PluginName; + public string Description { get; } = Strings.PluginDescription; + public string Author { get; } = "Mark van Renswoude "; + public string Url { get; } = "https://www.github.com/MvRens/MassiveKnob/"; + + public IEnumerable Actions { get; } = new IMassiveKnobAction[] + { + new VoiceMeeterRunMacroAction(), + new VoiceMeeterGetParameterAction() + }; + } +} diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/Properties/AssemblyInfo.cs b/Windows/MassiveKnob.Plugin.VoiceMeeter/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1da267e --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/Properties/AssemblyInfo.cs @@ -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("MassiveKnob.Plugin.VoiceMeeter")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MassiveKnob.Plugin.VoiceMeeter")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[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("19533600-d4f6-4bd4-82a3-c0234fdf044c")] + +// 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")] diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroAction.cs b/Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroAction.cs new file mode 100644 index 0000000..e1d6bf1 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroAction.cs @@ -0,0 +1,80 @@ +using System; +using System.Threading.Tasks; +using System.Windows.Controls; +using Microsoft.Extensions.Logging; +using Voicemeeter; + +namespace MassiveKnob.Plugin.VoiceMeeter.RunMacro +{ + public class VoiceMeeterRunMacroAction : IMassiveKnobAction + { + public Guid ActionId { get; } = new Guid("3bf41e96-9418-4a0e-ba5f-580e0b94dcce"); + public MassiveKnobActionType ActionType { get; } = MassiveKnobActionType.InputDigital; + public string Name { get; } = Strings.RunMacroName; + public string Description { get; } = Strings.RunMacroDescription; + + + public IMassiveKnobActionInstance Create(ILogger logger) + { + return new Instance(); + } + + + private class Instance : IMassiveKnobDigitalAction, IVoiceMeeterAction + { + private IMassiveKnobActionContext actionContext; + private VoiceMeeterRunMacroActionSettings settings; + + + public void Initialize(IMassiveKnobActionContext context) + { + actionContext = context; + settings = context.GetSettings(); + + InstanceRegister.Register(this); + } + + + public void Dispose() + { + InstanceRegister.Unregister(this); + } + + + public UserControl CreateSettingsControl() + { + var viewModel = new VoiceMeeterRunMacroActionSettingsViewModel(settings); + viewModel.PropertyChanged += (sender, args) => + { + if (!viewModel.IsSettingsProperty(args.PropertyName)) + return; + + actionContext.SetSettings(settings); + }; + + return new VoiceMeeterRunMacroActionSettingsView(viewModel); + } + + + public async ValueTask DigitalChanged(bool on) + { + if (!on) + return; + + if (settings.Version == RunVoicemeeterParam.None || string.IsNullOrEmpty(settings.Script)) + return; + + await InstanceRegister.InitializeVoicemeeter(); + global::VoiceMeeter.Remote.SetParameters(settings.Script); + } + + + public void VoiceMeeterVersionChanged() + { + // TODO update viewModel + + actionContext.SetSettings(settings); + } + } + } +} diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroActionSettings.cs b/Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroActionSettings.cs new file mode 100644 index 0000000..81b084e --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroActionSettings.cs @@ -0,0 +1,9 @@ +using MassiveKnob.Plugin.VoiceMeeter.Base; + +namespace MassiveKnob.Plugin.VoiceMeeter.RunMacro +{ + public class VoiceMeeterRunMacroActionSettings : BaseVoiceMeeterSettings + { + public string Script { get; set; } + } +} diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroActionSettingsView.xaml b/Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroActionSettingsView.xaml new file mode 100644 index 0000000..ffc0f23 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroActionSettingsView.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroActionSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroActionSettingsView.xaml.cs new file mode 100644 index 0000000..692c590 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroActionSettingsView.xaml.cs @@ -0,0 +1,14 @@ +namespace MassiveKnob.Plugin.VoiceMeeter.RunMacro +{ + /// + /// Interaction logic for VoiceMeeterRunMacroActionSettingsView.xaml + /// + public partial class VoiceMeeterRunMacroActionSettingsView + { + public VoiceMeeterRunMacroActionSettingsView(VoiceMeeterRunMacroActionSettingsViewModel viewModel) + { + DataContext = viewModel; + InitializeComponent(); + } + } +} diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroActionSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroActionSettingsViewModel.cs new file mode 100644 index 0000000..8c67693 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/RunMacro/VoiceMeeterRunMacroActionSettingsViewModel.cs @@ -0,0 +1,29 @@ +using MassiveKnob.Plugin.VoiceMeeter.Base; + +namespace MassiveKnob.Plugin.VoiceMeeter.RunMacro +{ + public class VoiceMeeterRunMacroActionSettingsViewModel : BaseVoiceMeeterSettingsViewModel + { + // ReSharper disable UnusedMember.Global - used by WPF Bindingpriv + public string Script + { + get => Settings.Script; + set + { + // TODO timer for change notification + if (value == Settings.Script) + return; + + Settings.Script = value; + OnPropertyChanged(); + } + } + // ReSharper restore UnusedMember.Global + + + // ReSharper disable once SuggestBaseTypeForParameter - by design + public VoiceMeeterRunMacroActionSettingsViewModel(VoiceMeeterRunMacroActionSettings settings) : base(settings) + { + } + } +} diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/Strings.Designer.cs b/Windows/MassiveKnob.Plugin.VoiceMeeter/Strings.Designer.cs new file mode 100644 index 0000000..2cbb168 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/Strings.Designer.cs @@ -0,0 +1,172 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace MassiveKnob.Plugin.VoiceMeeter { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Strings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MassiveKnob.Plugin.VoiceMeeter.Strings", typeof(Strings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Turns the output on if the specified parameter equals the specified value.. + /// + public static string GetParameterDescription { + get { + return ResourceManager.GetString("GetParameterDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Get parameter. + /// + public static string GetParameterName { + get { + return ResourceManager.GetString("GetParameterName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Provides actions to run VoiceMeeter macros or check the current state.. + /// + public static string PluginDescription { + get { + return ResourceManager.GetString("PluginDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to VoiceMeeter Remote. + /// + public static string PluginName { + get { + return ResourceManager.GetString("PluginName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Runs a VoiceMeeter macro when the input turns on.. + /// + public static string RunMacroDescription { + get { + return ResourceManager.GetString("RunMacroDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Run macro. + /// + public static string RunMacroName { + get { + return ResourceManager.GetString("RunMacroName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Inverted (on if the parameter does not equal the value). + /// + public static string SettingGetParameterInverted { + get { + return ResourceManager.GetString("SettingGetParameterInverted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parameter name. + /// + public static string SettingGetParameterParameter { + get { + return ResourceManager.GetString("SettingGetParameterParameter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Expected value. + /// + public static string SettingGetParameterValue { + get { + return ResourceManager.GetString("SettingGetParameterValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Script. + /// + public static string SettingRunMacroScript { + get { + return ResourceManager.GetString("SettingRunMacroScript", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Example: Strip[0].A1 = 1; Strip[0].A2 = 0; + ///Use comma or semicolons to separate commands, or put each command on a separate line. For more information see the VoiceMeeter documentation on Macro Buttons.. + /// + public static string SettingRunMacroScriptExample { + get { + return ResourceManager.GetString("SettingRunMacroScriptExample", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to VoiceMeeter version. + /// + public static string SettingVoiceMeeterVersion { + get { + return ResourceManager.GetString("SettingVoiceMeeterVersion", resourceCulture); + } + } + } +} diff --git a/Windows/MassiveKnob.Plugin.VoiceMeeter/Strings.resx b/Windows/MassiveKnob.Plugin.VoiceMeeter/Strings.resx new file mode 100644 index 0000000..f688f92 --- /dev/null +++ b/Windows/MassiveKnob.Plugin.VoiceMeeter/Strings.resx @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Turns the output on if the specified parameter equals the specified value. + + + Get parameter + + + Provides actions to run VoiceMeeter macros or check the current state. + + + VoiceMeeter Remote + + + Runs a VoiceMeeter macro when the input turns on. + + + Run macro + + + Inverted (on if the parameter does not equal the value) + + + Parameter name + + + Expected value + + + Script + + + Example: Strip[0].A1 = 1; Strip[0].A2 = 0; +Use comma or semicolons to separate commands, or put each command on a separate line. For more information see the VoiceMeeter documentation on Macro Buttons. + + + VoiceMeeter version + + \ No newline at end of file diff --git a/Windows/MassiveKnob.sln b/Windows/MassiveKnob.sln index 6a480a2..c19297a 100644 --- a/Windows/MassiveKnob.sln +++ b/Windows/MassiveKnob.sln @@ -17,6 +17,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MIN", "min.NET\MIN\MIN.cspr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MIN.SerialPort", "min.NET\MIN.SerialPort\MIN.SerialPort.csproj", "{DB8819EB-D2B7-4AAE-A699-BD200F2C113A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin.VoiceMeeter", "MassiveKnob.Plugin.VoiceMeeter\MassiveKnob.Plugin.VoiceMeeter.csproj", "{19533600-D4F6-4BD4-82A3-C0234FDF044C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Voicemeeter", "VoicemeeterRemote\Voicemeeter\Voicemeeter.csproj", "{F35DD8E5-91FA-403E-B6F6-8D2B4AE84198}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -51,6 +55,14 @@ Global {DB8819EB-D2B7-4AAE-A699-BD200F2C113A}.Debug|Any CPU.Build.0 = Debug|Any CPU {DB8819EB-D2B7-4AAE-A699-BD200F2C113A}.Release|Any CPU.ActiveCfg = Release|Any CPU {DB8819EB-D2B7-4AAE-A699-BD200F2C113A}.Release|Any CPU.Build.0 = Release|Any CPU + {19533600-D4F6-4BD4-82A3-C0234FDF044C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19533600-D4F6-4BD4-82A3-C0234FDF044C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19533600-D4F6-4BD4-82A3-C0234FDF044C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19533600-D4F6-4BD4-82A3-C0234FDF044C}.Release|Any CPU.Build.0 = Release|Any CPU + {F35DD8E5-91FA-403E-B6F6-8D2B4AE84198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F35DD8E5-91FA-403E-B6F6-8D2B4AE84198}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F35DD8E5-91FA-403E-B6F6-8D2B4AE84198}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F35DD8E5-91FA-403E-B6F6-8D2B4AE84198}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Windows/MassiveKnob/Core/PluginManager.cs b/Windows/MassiveKnob/Core/PluginManager.cs index 198a3eb..45bb752 100644 --- a/Windows/MassiveKnob/Core/PluginManager.cs +++ b/Windows/MassiveKnob/Core/PluginManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -59,23 +60,56 @@ namespace MassiveKnob.Core var registeredIds = new RegisteredIds(); var codeBase = Assembly.GetEntryAssembly()?.CodeBase; - if (!string.IsNullOrEmpty(codeBase)) + if (string.IsNullOrEmpty(codeBase)) { - var localPath = Path.GetDirectoryName(new Uri(codeBase).LocalPath); - if (!string.IsNullOrEmpty(localPath)) - { - var applicationPluginPath = Path.Combine(localPath, @"Plugins"); - LoadPlugins(applicationPluginPath, registeredIds, onException); - } + logger.Error("No known EntryAssembly, unable to load plugins"); + return; } + var localPath = Path.GetDirectoryName(new Uri(codeBase).LocalPath); + if (string.IsNullOrEmpty(localPath)) + { + logger.Error("EntryAssembly CodeBase does not resolve to a local path, unable to load plugins: {codeBase}", codeBase); + return; + } - var localPluginPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob", @"Plugins"); + var applicationPluginPath = Path.Combine(localPath, @"Plugins"); + LoadPlugins(applicationPluginPath, registeredIds, onException); + + #if DEBUG + // For debugging, load directly from the various bin folders + // ReSharper disable once InvertIf + if (IsInPath(localPath, "MassiveKnob", "bin", "Debug")) + { + // Go up three folders, filter out lingering bin/Release builds + var solutionPath = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(localPath))); + LoadPlugins(solutionPath, registeredIds, onException, pluginPath => IsInPath(pluginPath, "bin", "Debug")); + } + #endif + + var localPluginPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "MassiveKnob", "Plugins"); LoadPlugins(localPluginPath, registeredIds, onException); } + + + private static bool IsInPath(string actualPath, params string[] expectedPathComponents) + { + if (string.IsNullOrEmpty(actualPath) || expectedPathComponents.Length == 0) + return false; + + var expectedPath = Path.Combine(expectedPathComponents); + + if (!actualPath.EndsWith(Path.DirectorySeparatorChar.ToString())) + actualPath += Path.DirectorySeparatorChar; + + if (!expectedPath.EndsWith(Path.DirectorySeparatorChar.ToString())) + expectedPath += Path.DirectorySeparatorChar; + + return actualPath.EndsWith(expectedPath, StringComparison.CurrentCultureIgnoreCase); + } - private void LoadPlugins(string path, RegisteredIds registeredIds, Action onException) + private void LoadPlugins(string path, RegisteredIds registeredIds, Action onException, Func predicate = null) { logger.Information("Checking {path} for plugins...", path); if (!Directory.Exists(path)) @@ -89,6 +123,9 @@ namespace MassiveKnob.Core var pluginPath = Path.GetDirectoryName(metadataFilename); if (string.IsNullOrEmpty(pluginPath)) continue; + + if (predicate != null && !predicate(pluginPath)) + continue; PluginMetadata pluginMetadata; try diff --git a/Windows/MassiveKnob/Resources/Analog.xaml b/Windows/MassiveKnob/Resources/Analog.xaml index d678219..21d6ab4 100644 --- a/Windows/MassiveKnob/Resources/Analog.xaml +++ b/Windows/MassiveKnob/Resources/Analog.xaml @@ -4,7 +4,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> - + diff --git a/Windows/MassiveKnob/Resources/Device.xaml b/Windows/MassiveKnob/Resources/Device.xaml index 1b4f5ed..8ae9373 100644 --- a/Windows/MassiveKnob/Resources/Device.xaml +++ b/Windows/MassiveKnob/Resources/Device.xaml @@ -4,7 +4,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> - + diff --git a/Windows/MassiveKnob/Resources/Digital.xaml b/Windows/MassiveKnob/Resources/Digital.xaml index 09faf4b..52fa31d 100644 --- a/Windows/MassiveKnob/Resources/Digital.xaml +++ b/Windows/MassiveKnob/Resources/Digital.xaml @@ -4,7 +4,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> - + diff --git a/Windows/MassiveKnob/Resources/Logging.xaml b/Windows/MassiveKnob/Resources/Logging.xaml index f6db3ce..ead1df1 100644 --- a/Windows/MassiveKnob/Resources/Logging.xaml +++ b/Windows/MassiveKnob/Resources/Logging.xaml @@ -4,7 +4,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> - + diff --git a/Windows/MassiveKnob/Resources/Startup.xaml b/Windows/MassiveKnob/Resources/Startup.xaml index fa1de3f..927367b 100644 --- a/Windows/MassiveKnob/Resources/Startup.xaml +++ b/Windows/MassiveKnob/Resources/Startup.xaml @@ -4,7 +4,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> - + diff --git a/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs b/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs index ff58946..0c4ff78 100644 --- a/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs +++ b/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs @@ -17,6 +17,8 @@ using Serilog.Events; namespace MassiveKnob.ViewModel { + // TODO (code quality) split ViewModel for individual views, create viewmodel using container + // TODO (must have) show device status public class SettingsViewModel : IDisposable, INotifyPropertyChanged { private readonly Dictionary menuItemControls = new Dictionary diff --git a/Windows/Setup/MassiveKnobSetup.iss b/Windows/Setup/MassiveKnobSetup.iss new file mode 100644 index 0000000..32f6fca --- /dev/null +++ b/Windows/Setup/MassiveKnobSetup.iss @@ -0,0 +1,197 @@ +#define AppName "Massive Knob" +#define AppVersion GetEnv("BUILD_VERSION") +#define AppPublisher "Mark van Renswoude" +#define AppURL "https://github.com/MvRens/MassiveKnob" +#define AppExeName "MassiveKnob.exe" +#define BasePath ".." + +#if AppVersion == "" + #define AppVersion "IDE build" +#endif + + +[Setup] +AppId={{6D668D73-54E5-4FE1-8028-24FE993627B8} +AppName={#AppName} +AppVersion={#AppVersion} +AppPublisher={#AppPublisher} +AppPublisherURL={#AppURL} +AppSupportURL={#AppURL} +AppUpdatesURL={#AppURL} +DefaultDirName={pf}\{#AppName} +DisableProgramGroupPage=yes +OutputDir={#BasePath}\Release +OutputBaseFilename=MassiveKnobSetup-{#AppVersion} +Compression=lzma +SolidCompression=yes +ArchitecturesInstallIn64BitMode=x64 + +[Types] +Name: "full"; Description: "Full installation" +Name: "custom"; Description: "Custom installation"; Flags: iscustom + +[Components] +Name: main; Description: "Massive Knob application"; Types: full custom; Flags: fixed +Name: essentialplugins; Description: "Essential plugins"; Types: full custom +Name: essentialplugins\serialdevice; Description: "Serial device"; Types: full custom +Name: essentialplugins\coreaudio; Description: "Windows Core Audio actions"; Types: full custom +Name: optionalplugins; Description: "Optional plugins"; Types: full custom +Name: optionalplugins\emulatordevice; Description: "Emulator device"; Types: full custom +Name: optionalplugins\voicemeeter; Description: "VoiceMeeter actions"; Types: full custom + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked + +[Files] +; Main application +Source: {#BasePath}\MassiveKnob\bin\Release\{#AppExeName}; DestDir: "{app}"; Flags: ignoreversion; Components: main +Source: {#BasePath}\MassiveKnob\bin\Release\{#AppExeName}.config; DestDir: "{app}"; Flags: ignoreversion; Components: main +Source: {#BasePath}\MassiveKnob\bin\Release\*.dll; DestDir: "{app}"; Flags: ignoreversion; Components: main + +; Serial device plugin +Source: {#BasePath}\MassiveKnob.Plugin.SerialDevice\bin\Release\MassiveKnobPlugin.json; DestDir: "{app}\Plugins\SerialDevice"; Flags: ignoreversion; Components: essentialplugins\serialdevice +Source: {#BasePath}\MassiveKnob.Plugin.SerialDevice\bin\Release\*.dll; DestDir: "{app}\Plugins\SerialDevice"; Flags: ignoreversion; Components: essentialplugins\serialdevice + +; Core Audio plugin +Source: {#BasePath}\MassiveKnob.Plugin.CoreAudio\bin\Release\MassiveKnobPlugin.json; DestDir: "{app}\Plugins\CoreAudio"; Flags: ignoreversion; Components: essentialplugins\coreaudio +Source: {#BasePath}\MassiveKnob.Plugin.CoreAudio\bin\Release\*.dll; DestDir: "{app}\Plugins\CoreAudio"; Flags: ignoreversion; Components: essentialplugins\coreaudio + +; Emulator device plugin +Source: {#BasePath}\MassiveKnob.Plugin.EmulatorDevice\bin\Release\MassiveKnobPlugin.json; DestDir: "{app}\Plugins\EmulatorDevice"; Flags: ignoreversion; Components: optionalplugins\emulatordevice +Source: {#BasePath}\MassiveKnob.Plugin.EmulatorDevice\bin\Release\*.dll; DestDir: "{app}\Plugins\EmulatorDevice"; Flags: ignoreversion; Components: optionalplugins\emulatordevice + +; VoiceMeeter plugin +Source: {#BasePath}\MassiveKnob.Plugin.VoiceMeeter\bin\Release\MassiveKnobPlugin.json; DestDir: "{app}\Plugins\VoiceMeeter"; Flags: ignoreversion; Components: optionalplugins\voicemeeter +Source: {#BasePath}\MassiveKnob.Plugin.VoiceMeeter\bin\Release\*.dll; DestDir: "{app}\Plugins\VoiceMeeter"; Flags: ignoreversion; Components: optionalplugins\voicemeeter + +[Dirs] +Name: "{localappdata}\MassiveKnob" +Name: "{localappdata}\MassiveKnob\Logs" +Name: "{localappdata}\MassiveKnob\Plugins" + +[Icons] +Name: "{commonprograms}\{#AppName}"; Filename: "{app}\{#AppExeName}" +Name: "{commondesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Tasks: desktopicon + +[Run] +;Filename: "{app}\{#AppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(AppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent + +[Code] +// .NET version detection credit goes to: +// https://www.kynosarges.org/DotNetVersion.html + +function IsDotNetDetected(version: string; service: cardinal): boolean; +// Indicates whether the specified version and service pack of the .NET Framework is installed. +// +// version -- Specify one of these strings for the required .NET Framework version: +// 'v1.1' .NET Framework 1.1 +// 'v2.0' .NET Framework 2.0 +// 'v3.0' .NET Framework 3.0 +// 'v3.5' .NET Framework 3.5 +// 'v4\Client' .NET Framework 4.0 Client Profile +// 'v4\Full' .NET Framework 4.0 Full Installation +// 'v4.5' .NET Framework 4.5 +// 'v4.5.1' .NET Framework 4.5.1 +// 'v4.5.2' .NET Framework 4.5.2 +// 'v4.6' .NET Framework 4.6 +// 'v4.6.1' .NET Framework 4.6.1 +// 'v4.6.2' .NET Framework 4.6.2 +// 'v4.7' .NET Framework 4.7 +// 'v4.7.1' .NET Framework 4.7.1 +// 'v4.7.2' .NET Framework 4.7.2 +// 'v4.8' .NET Framework 4.8 +// +// service -- Specify any non-negative integer for the required service pack level: +// 0 No service packs required +// 1, 2, etc. Service pack 1, 2, etc. required +var + key, versionKey: string; + install, release, serviceCount, versionRelease: cardinal; + success: boolean; +begin + versionKey := version; + versionRelease := 0; + + // .NET 1.1 and 2.0 embed release number in version key + if version = 'v1.1' then begin + versionKey := 'v1.1.4322'; + end else if version = 'v2.0' then begin + versionKey := 'v2.0.50727'; + end + + // .NET 4.5 and newer install as update to .NET 4.0 Full + else if Pos('v4.', version) = 1 then begin + versionKey := 'v4\Full'; + case version of + 'v4.5': versionRelease := 378389; + 'v4.5.1': versionRelease := 378675; // 378758 on Windows 8 and older + 'v4.5.2': versionRelease := 379893; + 'v4.6': versionRelease := 393295; // 393297 on Windows 8.1 and older + 'v4.6.1': versionRelease := 394254; // 394271 before Win10 November Update + 'v4.6.2': versionRelease := 394802; // 394806 before Win10 Anniversary Update + 'v4.7': versionRelease := 460798; // 460805 before Win10 Creators Update + 'v4.7.1': versionRelease := 461308; // 461310 before Win10 Fall Creators Update + 'v4.7.2': versionRelease := 461808; // 461814 before Win10 April 2018 Update + 'v4.8': versionRelease := 528040; // 528049 before Win10 May 2019 Update + end; + end; + + // installation key group for all .NET versions + key := 'SOFTWARE\Microsoft\NET Framework Setup\NDP\' + versionKey; + + // .NET 3.0 uses value InstallSuccess in subkey Setup + if Pos('v3.0', version) = 1 then begin + success := RegQueryDWordValue(HKLM, key + '\Setup', 'InstallSuccess', install); + end else begin + success := RegQueryDWordValue(HKLM, key, 'Install', install); + end; + + // .NET 4.0 and newer use value Servicing instead of SP + if Pos('v4', version) = 1 then begin + success := success and RegQueryDWordValue(HKLM, key, 'Servicing', serviceCount); + end else begin + success := success and RegQueryDWordValue(HKLM, key, 'SP', serviceCount); + end; + + // .NET 4.5 and newer use additional value Release + if versionRelease > 0 then begin + success := success and RegQueryDWordValue(HKLM, key, 'Release', release); + success := success and (release >= versionRelease); + end; + + result := success and (install = 1) and (serviceCount >= service); +end; + + +function InitializeSetup(): Boolean; +var + response: Integer; + errorCode: Integer; + +begin + if not IsDotNetDetected('v4.7.2', 0) then + begin + response := SuppressibleMsgBox('Massive Knob requires Microsoft .NET Framework 4.7.2, which does not appear to be installed.' + + 'Do you want to open the .NET download page?'#13#10#13#10 + + 'Click No to continue, although Massive Knob may not run properly afterwards, or Cancel to abort the setup.', + mbInformation, + MB_YESNOCANCEL, + IDNO); + + case response of + IDYES: + begin + ShellExecAsOriginalUser('open', 'https://dotnet.microsoft.com/download/dotnet-framework/net472', '', '', + SW_SHOWNORMAL, ewNoWait, errorCode); + Result := False; + end; + + IDNO: + Result := True; + + IDCANCEL: + Result := False; + end; + end else + Result := True; +end; \ No newline at end of file diff --git a/Windows/VoicemeeterRemote b/Windows/VoicemeeterRemote new file mode 160000 index 0000000..5d259cd --- /dev/null +++ b/Windows/VoicemeeterRemote @@ -0,0 +1 @@ +Subproject commit 5d259cdaee942029487e37a02e9a32ed9833d80c