diff --git a/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnob.Plugin.RunProgram.csproj b/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnob.Plugin.RunProgram.csproj
new file mode 100644
index 0000000..a83be72
--- /dev/null
+++ b/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnob.Plugin.RunProgram.csproj
@@ -0,0 +1,91 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {BB1E8BA4-7965-4E46-B1BE-D2A7C491A204}
+ Library
+ Properties
+ MassiveKnob.Plugin.RunProgram
+ MassiveKnob.Plugin.RunProgram
+ v4.7.2
+ 512
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ RunProgramSettingsView.xaml
+
+
+
+
+
+ Strings.resx
+ True
+ True
+
+
+
+
+ Always
+
+
+
+
+ {A1298BE4-1D23-416C-8C56-FC9264487A95}
+ MassiveKnob.Plugin
+
+
+
+
+ PublicResXFileCodeGenerator
+ Strings.Designer.cs
+
+
+
+
+ 4.5.4
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
\ No newline at end of file
diff --git a/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnobPlugin.json b/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnobPlugin.json
new file mode 100644
index 0000000..d892ba4
--- /dev/null
+++ b/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnobPlugin.json
@@ -0,0 +1,3 @@
+{
+ "EntryAssembly": "MassiveKnob.Plugin.RunProgram.dll"
+}
diff --git a/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnobRunProgramPlugin.cs b/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnobRunProgramPlugin.cs
new file mode 100644
index 0000000..6e9f0ff
--- /dev/null
+++ b/Windows/MassiveKnob.Plugin.RunProgram/MassiveKnobRunProgramPlugin.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using MassiveKnob.Plugin.RunProgram.RunProgram;
+
+namespace MassiveKnob.Plugin.RunProgram
+{
+ [MassiveKnobPlugin]
+ public class MassiveKnobRunProgramPlugin : IMassiveKnobActionPlugin
+ {
+ public Guid PluginId { get; } = new Guid("10537f2a-6876-48b8-8ef9-8d05f185fa62");
+ 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 RunProgramAction()
+ };
+ }
+}
diff --git a/Windows/MassiveKnob.Plugin.RunProgram/Properties/AssemblyInfo.cs b/Windows/MassiveKnob.Plugin.RunProgram/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..754d30c
--- /dev/null
+++ b/Windows/MassiveKnob.Plugin.RunProgram/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.RunProgram")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MassiveKnob.Plugin.RunProgram")]
+[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("bb1e8ba4-7965-4e46-b1be-d2a7c491a204")]
+
+// 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.RunProgram/RunProgram/RunProgramAction.cs b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramAction.cs
new file mode 100644
index 0000000..8a1342a
--- /dev/null
+++ b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramAction.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using System.Windows.Controls;
+using Microsoft.Extensions.Logging;
+
+namespace MassiveKnob.Plugin.RunProgram.RunProgram
+{
+ public class RunProgramAction : IMassiveKnobAction
+ {
+ public Guid ActionId { get; } = new Guid("c3a79015-4b8f-414d-9682-02307de8639c");
+ public MassiveKnobActionType ActionType { get; } = MassiveKnobActionType.InputDigital;
+ public string Name { get; } = Strings.RunProgramName;
+ public string Description { get; } = Strings.RunProgramDescription;
+
+
+ public IMassiveKnobActionInstance Create(ILogger logger)
+ {
+ return new Instance(logger);
+ }
+
+
+ private class Instance : IMassiveKnobDigitalAction
+ {
+ private readonly ILogger logger;
+ private IMassiveKnobActionContext actionContext;
+ private RunProgramSettings settings;
+
+
+ public Instance(ILogger logger)
+ {
+ this.logger = logger;
+ }
+
+
+ public void Initialize(IMassiveKnobActionContext context)
+ {
+ actionContext = context;
+ settings = context.GetSettings();
+ }
+
+
+ public void Dispose()
+ {
+ }
+
+
+ public UserControl CreateSettingsControl()
+ {
+ var viewModel = new RunProgramSettingsViewModel(settings);
+ viewModel.PropertyChanged += (sender, args) =>
+ {
+ actionContext.SetSettings(settings);
+ };
+
+ return new RunProgramSettingsView(viewModel);
+ }
+
+
+ public ValueTask DigitalChanged(bool on)
+ {
+ if (!on)
+ return default;
+
+ if (string.IsNullOrEmpty(settings.Filename))
+ return default;
+
+ logger.LogInformation("Run program: filename = {filename}, arguments = {arguments}", settings.Filename, settings.Arguments);
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = settings.Filename,
+ Arguments = settings.Arguments,
+ UseShellExecute = true,
+ Verb = "open"
+ });
+
+ return default;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettings.cs b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettings.cs
new file mode 100644
index 0000000..f0b00d6
--- /dev/null
+++ b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettings.cs
@@ -0,0 +1,8 @@
+namespace MassiveKnob.Plugin.RunProgram.RunProgram
+{
+ public class RunProgramSettings
+ {
+ public string Filename { get; set; }
+ public string Arguments { get; set; }
+ }
+}
diff --git a/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsView.xaml b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsView.xaml
new file mode 100644
index 0000000..6ddd583
--- /dev/null
+++ b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsView.xaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsView.xaml.cs
new file mode 100644
index 0000000..bad83f5
--- /dev/null
+++ b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsView.xaml.cs
@@ -0,0 +1,27 @@
+using System.Windows;
+
+namespace MassiveKnob.Plugin.RunProgram.RunProgram
+{
+ ///
+ /// Interaction logic for RunProgramSettingsView.xaml
+ ///
+ public partial class RunProgramSettingsView
+ {
+ public RunProgramSettingsView(RunProgramSettingsViewModel viewModel)
+ {
+ DataContext = viewModel;
+ InitializeComponent();
+ }
+
+ private void ButtonBrowseClick(object sender, RoutedEventArgs e)
+ {
+ var dialog = new Microsoft.Win32.OpenFileDialog
+ {
+ Filter = Strings.FilenameDialogFilter
+ };
+
+ if (dialog.ShowDialog().GetValueOrDefault())
+ ((RunProgramSettingsViewModel) DataContext).Filename = dialog.FileName;
+ }
+ }
+}
diff --git a/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsViewModel.cs
new file mode 100644
index 0000000..8d4a63e
--- /dev/null
+++ b/Windows/MassiveKnob.Plugin.RunProgram/RunProgram/RunProgramSettingsViewModel.cs
@@ -0,0 +1,53 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace MassiveKnob.Plugin.RunProgram.RunProgram
+{
+ public class RunProgramSettingsViewModel : INotifyPropertyChanged
+ {
+ private readonly RunProgramSettings settings;
+
+
+ // ReSharper disable UnusedMember.Global - used by WPF Binding
+ public string Filename
+ {
+ get => settings.Filename;
+ set
+ {
+ if (value == settings.Filename)
+ return;
+
+ settings.Filename = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public string Arguments
+ {
+ get => settings.Arguments;
+ set
+ {
+ if (value == settings.Arguments)
+ return;
+
+ settings.Arguments = value;
+ OnPropertyChanged();
+ }
+ }
+ // ReSharper restore UnusedMember.Global
+
+
+ public RunProgramSettingsViewModel(RunProgramSettings settings)
+ {
+ this.settings = settings;
+ }
+
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
diff --git a/Windows/MassiveKnob.Plugin.RunProgram/Strings.Designer.cs b/Windows/MassiveKnob.Plugin.RunProgram/Strings.Designer.cs
new file mode 100644
index 0000000..f2c7400
--- /dev/null
+++ b/Windows/MassiveKnob.Plugin.RunProgram/Strings.Designer.cs
@@ -0,0 +1,126 @@
+//------------------------------------------------------------------------------
+//
+// 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.RunProgram {
+ 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.RunProgram.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 Executable files (*.exe, *.bat, *.cmd)|*.exe,*.bat,*.cmd|All files (*.*)|*.*.
+ ///
+ public static string FilenameDialogFilter {
+ get {
+ return ResourceManager.GetString("FilenameDialogFilter", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Provides an action to run an application when a button is pressed..
+ ///
+ public static string PluginDescription {
+ get {
+ return ResourceManager.GetString("PluginDescription", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Run Program.
+ ///
+ public static string PluginName {
+ get {
+ return ResourceManager.GetString("PluginName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Runs an application when a button is pressed..
+ ///
+ public static string RunProgramDescription {
+ get {
+ return ResourceManager.GetString("RunProgramDescription", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Run program.
+ ///
+ public static string RunProgramName {
+ get {
+ return ResourceManager.GetString("RunProgramName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Optional arguments passed to the executable.
+ ///
+ public static string SettingArguments {
+ get {
+ return ResourceManager.GetString("SettingArguments", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Executable, file or URL to open.
+ ///
+ public static string SettingFilename {
+ get {
+ return ResourceManager.GetString("SettingFilename", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/Windows/MassiveKnob.Plugin.RunProgram/Strings.resx b/Windows/MassiveKnob.Plugin.RunProgram/Strings.resx
new file mode 100644
index 0000000..82a4572
--- /dev/null
+++ b/Windows/MassiveKnob.Plugin.RunProgram/Strings.resx
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+ Executable files (*.exe, *.bat, *.cmd)|*.exe,*.bat,*.cmd|All files (*.*)|*.*
+
+
+ Provides an action to run an application when a button is pressed.
+
+
+ Run Program
+
+
+ Runs an application when a button is pressed.
+
+
+ Run program
+
+
+ Optional arguments passed to the executable
+
+
+ Executable, file or URL to open
+
+
\ No newline at end of file
diff --git a/Windows/MassiveKnob.Plugin/IMassiveKnobPlugin.cs b/Windows/MassiveKnob.Plugin/IMassiveKnobPlugin.cs
index e4a138d..d7b15a0 100644
--- a/Windows/MassiveKnob.Plugin/IMassiveKnobPlugin.cs
+++ b/Windows/MassiveKnob.Plugin/IMassiveKnobPlugin.cs
@@ -1,7 +1,5 @@
using System;
-// ReSharper disable UnusedMember.Global - public API
-
namespace MassiveKnob.Plugin
{
///
diff --git a/Windows/MassiveKnob.sln b/Windows/MassiveKnob.sln
index c19297a..6268963 100644
--- a/Windows/MassiveKnob.sln
+++ b/Windows/MassiveKnob.sln
@@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin.VoiceMee
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Voicemeeter", "VoicemeeterRemote\Voicemeeter\Voicemeeter.csproj", "{F35DD8E5-91FA-403E-B6F6-8D2B4AE84198}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassiveKnob.Plugin.RunProgram", "MassiveKnob.Plugin.RunProgram\MassiveKnob.Plugin.RunProgram.csproj", "{BB1E8BA4-7965-4E46-B1BE-D2A7C491A204}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -63,6 +65,10 @@ Global
{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
+ {BB1E8BA4-7965-4E46-B1BE-D2A7C491A204}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BB1E8BA4-7965-4E46-B1BE-D2A7C491A204}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BB1E8BA4-7965-4E46-B1BE-D2A7C491A204}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BB1E8BA4-7965-4E46-B1BE-D2A7C491A204}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Windows/MassiveKnob/App.xaml.cs b/Windows/MassiveKnob/App.xaml.cs
index b0b699b..22f38de 100644
--- a/Windows/MassiveKnob/App.xaml.cs
+++ b/Windows/MassiveKnob/App.xaml.cs
@@ -1,8 +1,11 @@
-using System.Diagnostics;
+using System;
+using System.Diagnostics;
using System.Windows;
using Hardcodet.Wpf.TaskbarNotification;
using MassiveKnob.View;
+using Serilog;
using SimpleInjector;
+using WpfBindingErrors;
namespace MassiveKnob
{
@@ -28,6 +31,9 @@ namespace MassiveKnob
{
base.OnStartup(e);
+ // Do not let WPF swallow exceptions in bindings
+ BindingExceptionThrower.Attach();
+
notifyIcon = (TaskbarIcon)FindResource("NotifyIcon");
Debug.Assert(notifyIcon != null, nameof(notifyIcon) + " != null");
}
diff --git a/Windows/MassiveKnob/ContainerBuilder.cs b/Windows/MassiveKnob/ContainerBuilder.cs
new file mode 100644
index 0000000..65818fc
--- /dev/null
+++ b/Windows/MassiveKnob/ContainerBuilder.cs
@@ -0,0 +1,48 @@
+using MassiveKnob.View;
+using MassiveKnob.View.Settings;
+using MassiveKnob.ViewModel;
+using MassiveKnob.ViewModel.Settings;
+using SimpleInjector;
+
+namespace MassiveKnob
+{
+ public static class ContainerBuilder
+ {
+ public static Container Create()
+ {
+ var container = new Container();
+ container.Options.EnableAutoVerification = false;
+
+ container.Register();
+
+ container.Register();
+ container.Register();
+
+ container.Register();
+ container.Register();
+
+ container.Register();
+ container.Register();
+
+ container.Register();
+ container.Register();
+
+ container.Register();
+ container.Register();
+
+ container.Register();
+ container.Register();
+
+ container.Register();
+ container.Register();
+
+ container.Register();
+ container.Register();
+
+ container.Register();
+ container.Register();
+
+ return container;
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/Core/IPluginManager.cs b/Windows/MassiveKnob/Core/IPluginManager.cs
index bbc8710..35b27ce 100644
--- a/Windows/MassiveKnob/Core/IPluginManager.cs
+++ b/Windows/MassiveKnob/Core/IPluginManager.cs
@@ -5,7 +5,16 @@ namespace MassiveKnob.Core
{
public interface IPluginManager
{
+ IEnumerable GetPlugins();
+
IEnumerable GetDevicePlugins();
IEnumerable GetActionPlugins();
}
+
+
+ public interface IMassiveKnobPluginInfo
+ {
+ string Filename { get; }
+ IMassiveKnobPlugin Plugin { get; }
+ }
}
diff --git a/Windows/MassiveKnob/Core/PluginManager.cs b/Windows/MassiveKnob/Core/PluginManager.cs
index e5434b3..e45c8bb 100644
--- a/Windows/MassiveKnob/Core/PluginManager.cs
+++ b/Windows/MassiveKnob/Core/PluginManager.cs
@@ -34,23 +34,28 @@ namespace MassiveKnob.Core
public class PluginManager : IPluginManager
{
private readonly ILogger logger;
- private readonly List plugins = new List();
+ private readonly List plugins = new List();
public PluginManager(ILogger logger)
{
this.logger = logger;
}
-
+
+
+ public IEnumerable GetPlugins()
+ {
+ return plugins;
+ }
public IEnumerable GetDevicePlugins()
{
- return plugins.Where(p => p is IMassiveKnobDevicePlugin).Cast();
+ return plugins.Where(p => p.Plugin is IMassiveKnobDevicePlugin).Select(p => (IMassiveKnobDevicePlugin)p.Plugin);
}
public IEnumerable GetActionPlugins()
{
- return plugins.Where(p => p is IMassiveKnobActionPlugin).Cast();
+ return plugins.Where(p => p.Plugin is IMassiveKnobActionPlugin).Select(p => (IMassiveKnobActionPlugin)p.Plugin);
}
@@ -190,7 +195,7 @@ namespace MassiveKnob.Core
logger.Information("Found plugin with Id {pluginId}: {name}", plugin.PluginId, plugin.Name);
ValidateRegistration(filename, plugin, registeredIds);
- plugins.Add((IMassiveKnobPlugin)pluginInstance);
+ plugins.Add(new PluginInfo(filename, (IMassiveKnobPlugin)pluginInstance));
}
}
@@ -280,5 +285,19 @@ namespace MassiveKnob.Core
// ReSharper disable once UnusedAutoPropertyAccessor.Local - for JSON deserialization
public string EntryAssembly { get; set; }
}
+
+
+ private class PluginInfo : IMassiveKnobPluginInfo
+ {
+ public string Filename { get; }
+ public IMassiveKnobPlugin Plugin { get; }
+
+
+ public PluginInfo(string filename, IMassiveKnobPlugin plugin)
+ {
+ Filename = filename;
+ Plugin = plugin;
+ }
+ }
}
}
diff --git a/Windows/MassiveKnob/MassiveKnob.csproj b/Windows/MassiveKnob/MassiveKnob.csproj
index 5756b27..0e94192 100644
--- a/Windows/MassiveKnob/MassiveKnob.csproj
+++ b/Windows/MassiveKnob/MassiveKnob.csproj
@@ -58,6 +58,7 @@
+
@@ -72,10 +73,17 @@
+
-
- InputOutputView.xaml
-
+
+
+
+
+
+
+
+
+
SettingsWindow.xaml
@@ -88,26 +96,24 @@
True
Strings.resx
-
- AnalogInputsView.xaml
+
+ BaseSettingsInputOutputView.xaml
-
- AnalogOutputsView.xaml
+
+
+
+
+
+ SettingsPluginsView.xaml
-
- StartupView.xaml
+
+ SettingsStartupView.xaml
-
- LoggingView.xaml
+
+ SettingsLoggingView.xaml
-
- DigitalInputsView.xaml
-
-
- DigitalOutputsView.xaml
-
-
- DeviceView.xaml
+
+ SettingsDeviceView.xaml
PublicResXFileCodeGenerator
@@ -158,6 +164,9 @@
5.0.0
+
+ 1.1.0
+
@@ -192,35 +201,31 @@
MSBuild:Compile
Designer
+
+ MSBuild:Compile
+ Designer
+
MSBuild:Compile
Designer
-
+
Designer
MSBuild:Compile
-
- Designer
- MSBuild:Compile
-
-
+
MSBuild:Compile
Designer
-
+
MSBuild:Compile
Designer
-
- Designer
+
MSBuild:Compile
-
-
Designer
- MSBuild:Compile
-
+
Designer
MSBuild:Compile
@@ -230,10 +235,6 @@
Designer
MSBuild:Compile
-
- Designer
- MSBuild:Compile
-
Designer
MSBuild:Compile
diff --git a/Windows/MassiveKnob/Program.cs b/Windows/MassiveKnob/Program.cs
index 0e64f6f..ad4f8a9 100644
--- a/Windows/MassiveKnob/Program.cs
+++ b/Windows/MassiveKnob/Program.cs
@@ -4,10 +4,7 @@ using System.Text;
using System.Windows;
using MassiveKnob.Core;
using MassiveKnob.Settings;
-using MassiveKnob.View;
-using MassiveKnob.ViewModel;
using Serilog;
-using SimpleInjector;
namespace MassiveKnob
{
@@ -19,23 +16,40 @@ namespace MassiveKnob
{
var settings = MassiveKnobSettingsJsonSerializer.Deserialize();
+
var loggingSwitch = new LoggingSwitch();
loggingSwitch.SetLogging(settings.Log.Enabled, settings.Log.Level);
+ var logFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob", @"Logs");
+
var logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Filter.ByIncludingOnly(loggingSwitch.IsIncluded)
.Enrich.FromLogContext()
.WriteTo.File(
- Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob", @"Logs", @".log"),
+ Path.Combine(logFilePath, @".log"),
rollingInterval: RollingInterval.Day,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{@Context}{NewLine}{Exception}")
.CreateLogger();
logger.Information("MassiveKnob starting");
- var pluginManager = new PluginManager(logger);
+
+ AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
+ {
+ var e = (Exception)args.ExceptionObject;
+ logger.Error(e, "Unhandled exception: {message}", e.Message);
+
+ MessageBox.Show(
+ "Oops, something went very wrong. Please notify the developer and include this message, you can copy it using Ctrl-C. " +
+ "Preferably also include the log file which can be found at:" + Environment.NewLine + logFilePath +
+ Environment.NewLine + Environment.NewLine +
+ e.Message, "Massive Knob - Fatal error", MessageBoxButton.OK, MessageBoxImage.Error);
+ };
+
+
+ var pluginManager = new PluginManager(logger);
var messages = new StringBuilder();
pluginManager.Load((exception, filename) =>
{
@@ -48,23 +62,17 @@ namespace MassiveKnob
return 1;
}
+
var orchestrator = new MassiveKnobOrchestrator(pluginManager, logger, settings);
orchestrator.Load();
-
- var container = new Container();
- container.Options.EnableAutoVerification = false;
-
- container.RegisterInstance(logger);
+
+ var container = ContainerBuilder.Create();
+ container.RegisterInstance(logger);
container.RegisterInstance(loggingSwitch);
container.RegisterInstance(pluginManager);
container.RegisterInstance(orchestrator);
- container.Register();
- container.Register();
- container.Register();
-
-
var app = container.GetInstance();
app.Run();
diff --git a/Windows/MassiveKnob/Resources/Plugins.xaml b/Windows/MassiveKnob/Resources/Plugins.xaml
new file mode 100644
index 0000000..a258dc4
--- /dev/null
+++ b/Windows/MassiveKnob/Resources/Plugins.xaml
@@ -0,0 +1,17 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Windows/MassiveKnob/Settings/MassiveKnobSettings.cs b/Windows/MassiveKnob/Settings/MassiveKnobSettings.cs
index b56c4f1..3f0d88f 100644
--- a/Windows/MassiveKnob/Settings/MassiveKnobSettings.cs
+++ b/Windows/MassiveKnob/Settings/MassiveKnobSettings.cs
@@ -15,7 +15,8 @@ namespace MassiveKnob.Settings
AnalogOutputs,
DigitalOutputs,
Logging,
- Startup
+ Startup,
+ Plugins
}
diff --git a/Windows/MassiveKnob/Strings.Designer.cs b/Windows/MassiveKnob/Strings.Designer.cs
index b34191f..10def61 100644
--- a/Windows/MassiveKnob/Strings.Designer.cs
+++ b/Windows/MassiveKnob/Strings.Designer.cs
@@ -303,6 +303,15 @@ namespace MassiveKnob {
}
}
+ ///
+ /// Looks up a localized string similar to Plugins.
+ ///
+ public static string MenuItemPlugins {
+ get {
+ return ResourceManager.GetString("MenuItemPlugins", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Startup.
///
diff --git a/Windows/MassiveKnob/Strings.resx b/Windows/MassiveKnob/Strings.resx
index 58fdfc1..021591c 100644
--- a/Windows/MassiveKnob/Strings.resx
+++ b/Windows/MassiveKnob/Strings.resx
@@ -198,6 +198,9 @@
Logging
+
+ Plugins
+
Startup
diff --git a/Windows/MassiveKnob/Style.xaml b/Windows/MassiveKnob/Style.xaml
index 37a8685..e0a5785 100644
--- a/Windows/MassiveKnob/Style.xaml
+++ b/Windows/MassiveKnob/Style.xaml
@@ -22,8 +22,8 @@
-
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Windows/MassiveKnob/View/InputOutputView.xaml b/Windows/MassiveKnob/View/InputOutputView.xaml
deleted file mode 100644
index 99fc97f..0000000
--- a/Windows/MassiveKnob/View/InputOutputView.xaml
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Windows/MassiveKnob/View/InputOutputView.xaml.cs b/Windows/MassiveKnob/View/InputOutputView.xaml.cs
deleted file mode 100644
index 5d4d6a5..0000000
--- a/Windows/MassiveKnob/View/InputOutputView.xaml.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace MassiveKnob.View
-{
- ///
- /// Interaction logic for InputOutputView.xaml
- ///
- public partial class InputOutputView
- {
- public InputOutputView()
- {
- InitializeComponent();
- }
- }
-}
diff --git a/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml b/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml
deleted file mode 100644
index c2a890e..0000000
--- a/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml.cs b/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml.cs
deleted file mode 100644
index 4b0a7e4..0000000
--- a/Windows/MassiveKnob/View/Settings/AnalogInputsView.xaml.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-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
deleted file mode 100644
index 19e8585..0000000
--- a/Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml.cs b/Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml.cs
deleted file mode 100644
index b0b0d57..0000000
--- a/Windows/MassiveKnob/View/Settings/AnalogOutputsView.xaml.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace MassiveKnob.View.Settings
-{
- ///
- /// Interaction logic for AnalogOutputsView.xaml
- ///
- public partial class AnalogOutputsView
- {
- public AnalogOutputsView()
- {
- InitializeComponent();
- }
- }
-}
diff --git a/Windows/MassiveKnob/View/Settings/BaseSettingsInputOutputView.xaml b/Windows/MassiveKnob/View/Settings/BaseSettingsInputOutputView.xaml
new file mode 100644
index 0000000..869b80f
--- /dev/null
+++ b/Windows/MassiveKnob/View/Settings/BaseSettingsInputOutputView.xaml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Windows/MassiveKnob/View/Settings/BaseSettingsInputOutputView.xaml.cs b/Windows/MassiveKnob/View/Settings/BaseSettingsInputOutputView.xaml.cs
new file mode 100644
index 0000000..1933399
--- /dev/null
+++ b/Windows/MassiveKnob/View/Settings/BaseSettingsInputOutputView.xaml.cs
@@ -0,0 +1,16 @@
+using MassiveKnob.ViewModel.Settings;
+
+namespace MassiveKnob.View.Settings
+{
+ ///
+ /// Interaction logic for BaseSettingsInputOutputView.xaml
+ ///
+ public partial class BaseSettingsInputOutputView
+ {
+ public BaseSettingsInputOutputView(BaseSettingsInputOutputViewModel viewModel)
+ {
+ DataContext = viewModel;
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/View/Settings/DeviceView.xaml.cs b/Windows/MassiveKnob/View/Settings/DeviceView.xaml.cs
deleted file mode 100644
index 5cfec2b..0000000
--- a/Windows/MassiveKnob/View/Settings/DeviceView.xaml.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-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
deleted file mode 100644
index 652fc93..0000000
--- a/Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml.cs b/Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml.cs
deleted file mode 100644
index 189a997..0000000
--- a/Windows/MassiveKnob/View/Settings/DigitalInputsView.xaml.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-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
deleted file mode 100644
index 87d67d9..0000000
--- a/Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml.cs b/Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml.cs
deleted file mode 100644
index 61f2a1c..0000000
--- a/Windows/MassiveKnob/View/Settings/DigitalOutputsView.xaml.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-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.cs b/Windows/MassiveKnob/View/Settings/LoggingView.xaml.cs
deleted file mode 100644
index 7fe2838..0000000
--- a/Windows/MassiveKnob/View/Settings/LoggingView.xaml.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace MassiveKnob.View.Settings
-{
- ///
- /// Interaction logic for LoggingView.xaml
- ///
- public partial class LoggingView
- {
- public LoggingView()
- {
- InitializeComponent();
- }
- }
-}
diff --git a/Windows/MassiveKnob/View/Settings/SettingsAnalogInputsView.cs b/Windows/MassiveKnob/View/Settings/SettingsAnalogInputsView.cs
new file mode 100644
index 0000000..7438f59
--- /dev/null
+++ b/Windows/MassiveKnob/View/Settings/SettingsAnalogInputsView.cs
@@ -0,0 +1,12 @@
+using MassiveKnob.ViewModel.Settings;
+
+namespace MassiveKnob.View.Settings
+{
+ public class SettingsAnalogInputsView : BaseSettingsInputOutputView
+ {
+ // ReSharper disable once SuggestBaseTypeForParameter - required for injection
+ public SettingsAnalogInputsView(SettingsAnalogInputsViewModel viewModel) : base(viewModel)
+ {
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/View/Settings/SettingsAnalogOutputsView.cs b/Windows/MassiveKnob/View/Settings/SettingsAnalogOutputsView.cs
new file mode 100644
index 0000000..238b49f
--- /dev/null
+++ b/Windows/MassiveKnob/View/Settings/SettingsAnalogOutputsView.cs
@@ -0,0 +1,12 @@
+using MassiveKnob.ViewModel.Settings;
+
+namespace MassiveKnob.View.Settings
+{
+ public class SettingsAnalogOutputsView : BaseSettingsInputOutputView
+ {
+ // ReSharper disable once SuggestBaseTypeForParameter - required for injection
+ public SettingsAnalogOutputsView(SettingsAnalogOutputsViewModel viewModel) : base(viewModel)
+ {
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/View/Settings/DeviceView.xaml b/Windows/MassiveKnob/View/Settings/SettingsDeviceView.xaml
similarity index 90%
rename from Windows/MassiveKnob/View/Settings/DeviceView.xaml
rename to Windows/MassiveKnob/View/Settings/SettingsDeviceView.xaml
index 334a619..f5fb62a 100644
--- a/Windows/MassiveKnob/View/Settings/DeviceView.xaml
+++ b/Windows/MassiveKnob/View/Settings/SettingsDeviceView.xaml
@@ -1,13 +1,14 @@
-
+ d:DataContext="{d:DesignInstance Type=settings:SettingsDeviceViewModelDesignTime, IsDesignTimeCreatable=True}">
diff --git a/Windows/MassiveKnob/View/Settings/SettingsDeviceView.xaml.cs b/Windows/MassiveKnob/View/Settings/SettingsDeviceView.xaml.cs
new file mode 100644
index 0000000..e655ca8
--- /dev/null
+++ b/Windows/MassiveKnob/View/Settings/SettingsDeviceView.xaml.cs
@@ -0,0 +1,16 @@
+using MassiveKnob.ViewModel.Settings;
+
+namespace MassiveKnob.View.Settings
+{
+ ///
+ /// Interaction logic for SettingsDeviceView.xaml
+ ///
+ public partial class SettingsDeviceView
+ {
+ public SettingsDeviceView(SettingsDeviceViewModel viewModel)
+ {
+ DataContext = viewModel;
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/View/Settings/SettingsDigitalInputsView.cs b/Windows/MassiveKnob/View/Settings/SettingsDigitalInputsView.cs
new file mode 100644
index 0000000..dfe6d3a
--- /dev/null
+++ b/Windows/MassiveKnob/View/Settings/SettingsDigitalInputsView.cs
@@ -0,0 +1,12 @@
+using MassiveKnob.ViewModel.Settings;
+
+namespace MassiveKnob.View.Settings
+{
+ public class SettingsDigitalInputsView : BaseSettingsInputOutputView
+ {
+ // ReSharper disable once SuggestBaseTypeForParameter - required for injection
+ public SettingsDigitalInputsView(SettingsDigitalInputsViewModel viewModel) : base(viewModel)
+ {
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/View/Settings/SettingsDigitalOutputsView.cs b/Windows/MassiveKnob/View/Settings/SettingsDigitalOutputsView.cs
new file mode 100644
index 0000000..fc1239e
--- /dev/null
+++ b/Windows/MassiveKnob/View/Settings/SettingsDigitalOutputsView.cs
@@ -0,0 +1,12 @@
+using MassiveKnob.ViewModel.Settings;
+
+namespace MassiveKnob.View.Settings
+{
+ public class SettingsDigitalOutputsView : BaseSettingsInputOutputView
+ {
+ // ReSharper disable once SuggestBaseTypeForParameter - required for injection
+ public SettingsDigitalOutputsView(SettingsDigitalOutputsViewModel viewModel) : base(viewModel)
+ {
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/View/Settings/LoggingView.xaml b/Windows/MassiveKnob/View/Settings/SettingsLoggingView.xaml
similarity index 97%
rename from Windows/MassiveKnob/View/Settings/LoggingView.xaml
rename to Windows/MassiveKnob/View/Settings/SettingsLoggingView.xaml
index 5a53d07..0aee137 100644
--- a/Windows/MassiveKnob/View/Settings/LoggingView.xaml
+++ b/Windows/MassiveKnob/View/Settings/SettingsLoggingView.xaml
@@ -1,4 +1,4 @@
-
+ /// Interaction logic for SettingsLoggingView.xaml
+ ///
+ public partial class SettingsLoggingView
+ {
+ public SettingsLoggingView(SettingsLoggingViewModel viewModel)
+ {
+ DataContext = viewModel;
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml b/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml
new file mode 100644
index 0000000..3b5c24a
--- /dev/null
+++ b/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml.cs b/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml.cs
new file mode 100644
index 0000000..8c0d43b
--- /dev/null
+++ b/Windows/MassiveKnob/View/Settings/SettingsPluginsView.xaml.cs
@@ -0,0 +1,31 @@
+using System.Diagnostics;
+using System.Windows;
+using System.Windows.Input;
+using MassiveKnob.ViewModel;
+using MassiveKnob.ViewModel.Settings;
+
+namespace MassiveKnob.View.Settings
+{
+ ///
+ /// Interaction logic for SettingsPluginsView.xaml
+ ///
+ public partial class SettingsPluginsView
+ {
+ public SettingsPluginsView(SettingsPluginsViewModel viewModel)
+ {
+ DataContext = viewModel;
+ InitializeComponent();
+ }
+
+ private void UrlMouseDown(object sender, MouseButtonEventArgs e)
+ {
+ var dataContext = ((FrameworkElement) e.Source).DataContext;
+
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = ((PluginViewModel) dataContext).Url,
+ Verb = "open"
+ });
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/View/Settings/StartupView.xaml b/Windows/MassiveKnob/View/Settings/SettingsStartupView.xaml
similarity index 95%
rename from Windows/MassiveKnob/View/Settings/StartupView.xaml
rename to Windows/MassiveKnob/View/Settings/SettingsStartupView.xaml
index f9ce4c2..f3caa84 100644
--- a/Windows/MassiveKnob/View/Settings/StartupView.xaml
+++ b/Windows/MassiveKnob/View/Settings/SettingsStartupView.xaml
@@ -1,4 +1,4 @@
-
+ /// Interaction logic for SettingsStartupView.xaml
+ ///
+ public partial class SettingsStartupView
+ {
+ public SettingsStartupView(SettingsStartupViewModel viewModel)
+ {
+ DataContext = viewModel;
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/View/Settings/StartupView.xaml.cs b/Windows/MassiveKnob/View/Settings/StartupView.xaml.cs
deleted file mode 100644
index b8ee75e..0000000
--- a/Windows/MassiveKnob/View/Settings/StartupView.xaml.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-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 ddc16e1..84c9b00 100644
--- a/Windows/MassiveKnob/View/SettingsWindow.xaml
+++ b/Windows/MassiveKnob/View/SettingsWindow.xaml
@@ -23,6 +23,7 @@
+
@@ -81,6 +82,7 @@
+
diff --git a/Windows/MassiveKnob/ViewModel/InputOutputViewModel.cs b/Windows/MassiveKnob/ViewModel/InputOutputViewModel.cs
index 77c3d11..8e4a544 100644
--- a/Windows/MassiveKnob/ViewModel/InputOutputViewModel.cs
+++ b/Windows/MassiveKnob/ViewModel/InputOutputViewModel.cs
@@ -125,7 +125,7 @@ namespace MassiveKnob.ViewModel
// ReSharper restore UnusedMember.Global
- public InputOutputViewModel(SettingsViewModel settingsViewModel, IMassiveKnobOrchestrator orchestrator,
+ public InputOutputViewModel(IEnumerable allActions, IMassiveKnobOrchestrator orchestrator,
MassiveKnobActionType actionType, int index)
{
this.orchestrator = orchestrator;
@@ -155,7 +155,7 @@ namespace MassiveKnob.ViewModel
}
- Actions = settingsViewModel.Actions.Where(AllowAction).ToList();
+ Actions = allActions.Where(AllowAction).ToList();
var actionInfo = orchestrator.GetAction(actionType, index);
diff --git a/Windows/MassiveKnob/ViewModel/PluginViewModel.cs b/Windows/MassiveKnob/ViewModel/PluginViewModel.cs
new file mode 100644
index 0000000..86cc751
--- /dev/null
+++ b/Windows/MassiveKnob/ViewModel/PluginViewModel.cs
@@ -0,0 +1,29 @@
+using System.Windows;
+
+namespace MassiveKnob.ViewModel
+{
+ public class PluginViewModel
+ {
+ // ReSharper disable UnusedMember.Global - used by WPF Binding
+ public string Name { get; }
+ public string Description { get; }
+ public string Filename { get; }
+ public string Author { get; }
+ public string Url { get; }
+
+ public Visibility DescriptionVisibility => string.IsNullOrEmpty(Description) ? Visibility.Collapsed : Visibility.Visible;
+ public Visibility AuthorVisibility => string.IsNullOrEmpty(Author) ? Visibility.Collapsed : Visibility.Visible;
+ public Visibility UrlVisibility => string.IsNullOrEmpty(Url) ? Visibility.Collapsed : Visibility.Visible;
+ // ReSharper restore UnusedMember.Global
+
+
+ public PluginViewModel(string name, string description, string filename, string author, string url)
+ {
+ Name = name;
+ Description = description;
+ Filename = filename;
+ Author = author;
+ Url = url;
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/ViewModel/Settings/BaseSettingsInputOutputViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/BaseSettingsInputOutputViewModel.cs
new file mode 100644
index 0000000..b3eb84d
--- /dev/null
+++ b/Windows/MassiveKnob/ViewModel/Settings/BaseSettingsInputOutputViewModel.cs
@@ -0,0 +1,148 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Windows;
+using MassiveKnob.Core;
+using MassiveKnob.Plugin;
+
+namespace MassiveKnob.ViewModel.Settings
+{
+ public class BaseSettingsInputOutputViewModel : IDisposable, INotifyPropertyChanged
+ {
+ private readonly IMassiveKnobOrchestrator orchestrator;
+
+ private readonly MassiveKnobActionType inputOutputType;
+ private DeviceSpecs? specs;
+ private readonly IDisposable activeDeviceSubscription;
+ private IEnumerable inputOutputs;
+
+
+ // ReSharper disable UnusedMember.Global - used by WPF Binding
+ public IList Actions { get; }
+
+ public DeviceSpecs? Specs
+ {
+ get => specs;
+ set
+ {
+ specs = value;
+ OnPropertyChanged();
+
+ DisposeInputOutputViewModels();
+ int inputOutputCount;
+
+ switch (inputOutputType)
+ {
+ case MassiveKnobActionType.InputAnalog:
+ inputOutputCount = specs?.AnalogInputCount ?? 0;
+ break;
+
+ case MassiveKnobActionType.InputDigital:
+ inputOutputCount = specs?.DigitalInputCount ?? 0;
+ break;
+
+ case MassiveKnobActionType.OutputAnalog:
+ inputOutputCount = specs?.AnalogOutputCount ?? 0;
+ break;
+
+ case MassiveKnobActionType.OutputDigital:
+ inputOutputCount = specs?.DigitalOutputCount ?? 0;
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ InputOutputs = Enumerable
+ .Range(0, inputOutputCount)
+ .Select(i => new InputOutputViewModel(Actions, orchestrator, inputOutputType, i))
+ .ToList();
+ }
+ }
+
+
+ public IEnumerable InputOutputs
+ {
+ get => inputOutputs;
+ set
+ {
+ inputOutputs = value;
+ OnPropertyChanged();
+ }
+ }
+ // ReSharper restore UnusedMember.Global
+
+
+ public BaseSettingsInputOutputViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator, MassiveKnobActionType inputOutputType)
+ {
+ this.orchestrator = orchestrator;
+ this.inputOutputType = inputOutputType;
+
+ // For design-time support
+ if (orchestrator == null)
+ return;
+
+
+ var allActions = new List
+ {
+ new ActionViewModel(null, null)
+ };
+
+ allActions.AddRange(
+ pluginManager.GetActionPlugins()
+ .SelectMany(ap => ap.Actions.Select(a => new ActionViewModel(ap, a)))
+ .OrderBy(a => a.Name.ToLower()));
+
+ Actions = allActions;
+
+
+ activeDeviceSubscription = orchestrator.ActiveDeviceSubject.Subscribe(info =>
+ {
+ Application.Current?.Dispatcher.Invoke(() =>
+ {
+ Specs = info.Specs;
+ });
+ });
+
+ if (orchestrator.ActiveDevice != null)
+ Specs = orchestrator.ActiveDevice.Specs;
+ }
+
+
+ public void Dispose()
+ {
+ DisposeInputOutputViewModels();
+ activeDeviceSubscription?.Dispose();
+ }
+
+
+ private void DisposeInputOutputViewModels()
+ {
+ if (inputOutputs == null)
+ return;
+
+ foreach (var viewModel in inputOutputs)
+ viewModel.Dispose();
+ }
+
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+
+
+ public class BaseSettingsInputOutputViewModelDesignTime : BaseSettingsInputOutputViewModel
+ {
+ public BaseSettingsInputOutputViewModelDesignTime()
+ : base(null, null, MassiveKnobActionType.InputAnalog)
+ {
+ Specs = new DeviceSpecs(2, 2, 2, 2);
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/ViewModel/Settings/SettingsAnalogInputsViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/SettingsAnalogInputsViewModel.cs
new file mode 100644
index 0000000..8060fea
--- /dev/null
+++ b/Windows/MassiveKnob/ViewModel/Settings/SettingsAnalogInputsViewModel.cs
@@ -0,0 +1,13 @@
+using MassiveKnob.Core;
+using MassiveKnob.Plugin;
+
+namespace MassiveKnob.ViewModel.Settings
+{
+ public class SettingsAnalogInputsViewModel : BaseSettingsInputOutputViewModel
+ {
+ public SettingsAnalogInputsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator)
+ : base(pluginManager, orchestrator, MassiveKnobActionType.InputAnalog)
+ {
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/ViewModel/Settings/SettingsAnalogOutputsViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/SettingsAnalogOutputsViewModel.cs
new file mode 100644
index 0000000..4440fe0
--- /dev/null
+++ b/Windows/MassiveKnob/ViewModel/Settings/SettingsAnalogOutputsViewModel.cs
@@ -0,0 +1,13 @@
+using MassiveKnob.Core;
+using MassiveKnob.Plugin;
+
+namespace MassiveKnob.ViewModel.Settings
+{
+ public class SettingsAnalogOutputsViewModel : BaseSettingsInputOutputViewModel
+ {
+ public SettingsAnalogOutputsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator)
+ : base(pluginManager, orchestrator, MassiveKnobActionType.OutputAnalog)
+ {
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/ViewModel/Settings/SettingsDeviceViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/SettingsDeviceViewModel.cs
new file mode 100644
index 0000000..a7e1a00
--- /dev/null
+++ b/Windows/MassiveKnob/ViewModel/Settings/SettingsDeviceViewModel.cs
@@ -0,0 +1,165 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Windows.Controls;
+using System.Windows.Media;
+using MassiveKnob.Core;
+
+namespace MassiveKnob.ViewModel.Settings
+{
+ public class SettingsDeviceViewModel : IDisposable, INotifyPropertyChanged
+ {
+ private readonly IMassiveKnobOrchestrator orchestrator;
+ private DeviceViewModel selectedDevice;
+ private UserControl settingsControl;
+
+ private readonly IDisposable deviceStatusSubscription;
+
+ // ReSharper disable UnusedMember.Global - used by WPF Binding
+ public IList Devices { get; }
+
+
+ public DeviceViewModel SelectedDevice
+ {
+ get => selectedDevice;
+ set
+ {
+ if (value == selectedDevice)
+ return;
+
+ selectedDevice = value;
+ var deviceInfo = orchestrator?.SetActiveDevice(value?.Device);
+
+ OnPropertyChanged();
+
+ SettingsControl = deviceInfo?.Instance.CreateSettingsControl();
+ }
+ }
+
+ public UserControl SettingsControl
+ {
+ get => settingsControl;
+ set
+ {
+ if (value == settingsControl)
+ return;
+
+ if (settingsControl is IDisposable disposable)
+ disposable.Dispose();
+
+ settingsControl = value;
+ OnPropertyChanged();
+ }
+ }
+
+
+ public string ConnectionStatusText
+ {
+ get
+ {
+ if (orchestrator == null)
+ return "Design-time";
+
+ switch (orchestrator.DeviceStatus)
+ {
+ case MassiveKnobDeviceStatus.Disconnected:
+ return Strings.DeviceStatusDisconnected;
+
+ case MassiveKnobDeviceStatus.Connecting:
+ return Strings.DeviceStatusConnecting;
+
+ case MassiveKnobDeviceStatus.Connected:
+ return Strings.DeviceStatusConnected;
+
+ default:
+ return null;
+ }
+ }
+ }
+
+ public Brush ConnectionStatusColor
+ {
+ get
+ {
+ if (orchestrator == null)
+ return Brushes.Fuchsia;
+
+ switch (orchestrator.DeviceStatus)
+ {
+ case MassiveKnobDeviceStatus.Disconnected:
+ return Brushes.DarkRed;
+
+ case MassiveKnobDeviceStatus.Connecting:
+ return Brushes.Orange;
+
+ case MassiveKnobDeviceStatus.Connected:
+ return Brushes.ForestGreen;
+
+ default:
+ return null;
+ }
+ }
+ }
+ // ReSharper restore UnusedMember.Global
+
+
+ public SettingsDeviceViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator)
+ {
+ this.orchestrator = orchestrator;
+
+ // For design-time support
+ if (orchestrator == null)
+ return;
+
+ deviceStatusSubscription = orchestrator.DeviceStatusSubject.Subscribe(status =>
+ {
+ OnDependantPropertyChanged(nameof(ConnectionStatusColor));
+ OnDependantPropertyChanged(nameof(ConnectionStatusText));
+ });
+
+
+ Devices = pluginManager.GetDevicePlugins()
+ .SelectMany(dp => dp.Devices.Select(d => new DeviceViewModel(dp, d)))
+ .OrderBy(d => d.Name.ToLower())
+ .ToList();
+
+ if (orchestrator.ActiveDevice == null)
+ return;
+
+ selectedDevice = Devices.Single(d => d.Device.DeviceId == orchestrator.ActiveDevice.Info.DeviceId);
+ SettingsControl = orchestrator.ActiveDevice.Instance.CreateSettingsControl();
+ }
+
+
+ public void Dispose()
+ {
+ if (SettingsControl is IDisposable disposable)
+ disposable.Dispose();
+
+ deviceStatusSubscription?.Dispose();
+ }
+
+
+ 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));
+ }
+ }
+
+
+ public class SettingsDeviceViewModelDesignTime : SettingsDeviceViewModel
+ {
+ public SettingsDeviceViewModelDesignTime() : base(null, null)
+ {
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/ViewModel/Settings/SettingsDigitalInputsViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/SettingsDigitalInputsViewModel.cs
new file mode 100644
index 0000000..4458ba6
--- /dev/null
+++ b/Windows/MassiveKnob/ViewModel/Settings/SettingsDigitalInputsViewModel.cs
@@ -0,0 +1,13 @@
+using MassiveKnob.Core;
+using MassiveKnob.Plugin;
+
+namespace MassiveKnob.ViewModel.Settings
+{
+ public class SettingsDigitalInputsViewModel : BaseSettingsInputOutputViewModel
+ {
+ public SettingsDigitalInputsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator)
+ : base(pluginManager, orchestrator, MassiveKnobActionType.InputDigital)
+ {
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/ViewModel/Settings/SettingsDigitalOutputsViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/SettingsDigitalOutputsViewModel.cs
new file mode 100644
index 0000000..dec55c6
--- /dev/null
+++ b/Windows/MassiveKnob/ViewModel/Settings/SettingsDigitalOutputsViewModel.cs
@@ -0,0 +1,13 @@
+using MassiveKnob.Core;
+using MassiveKnob.Plugin;
+
+namespace MassiveKnob.ViewModel.Settings
+{
+ public class SettingsDigitalOutputsViewModel : BaseSettingsInputOutputViewModel
+ {
+ public SettingsDigitalOutputsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator)
+ : base(pluginManager, orchestrator, MassiveKnobActionType.OutputDigital)
+ {
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/ViewModel/Settings/SettingsLoggingViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/SettingsLoggingViewModel.cs
new file mode 100644
index 0000000..bfb1bbb
--- /dev/null
+++ b/Windows/MassiveKnob/ViewModel/Settings/SettingsLoggingViewModel.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using MassiveKnob.Core;
+using MassiveKnob.Settings;
+using Serilog.Events;
+
+namespace MassiveKnob.ViewModel.Settings
+{
+ public class SettingsLoggingViewModel : INotifyPropertyChanged
+ {
+ private readonly IMassiveKnobOrchestrator orchestrator;
+ private readonly ILoggingSwitch loggingSwitch;
+
+ // ReSharper disable UnusedMember.Global - used by WPF Binding
+ 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"));
+ // ReSharper restore UnusedMember.Global
+
+
+ public SettingsLoggingViewModel(IMassiveKnobOrchestrator orchestrator, ILoggingSwitch loggingSwitch)
+ {
+ this.orchestrator = orchestrator;
+ this.loggingSwitch = loggingSwitch;
+
+ // For design-time support
+ if (orchestrator == null)
+ return;
+
+
+ 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;
+ }
+
+
+ private void ApplyLoggingSettings()
+ {
+ orchestrator?.UpdateSettings(settings =>
+ {
+ settings.Log.Enabled = LoggingEnabled;
+ settings.Log.Level = SelectedLoggingLevel.Level;
+ });
+
+ loggingSwitch?.SetLogging(LoggingEnabled, selectedLoggingLevel.Level);
+ }
+
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/ViewModel/Settings/SettingsPluginsViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/SettingsPluginsViewModel.cs
new file mode 100644
index 0000000..dfe6c80
--- /dev/null
+++ b/Windows/MassiveKnob/ViewModel/Settings/SettingsPluginsViewModel.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using MassiveKnob.Core;
+
+namespace MassiveKnob.ViewModel.Settings
+{
+ public class SettingsPluginsViewModel
+ {
+ // ReSharper disable UnusedMember.Global - used by WPF Binding
+ public IEnumerable Plugins { get; protected set; }
+ // ReSharper restore UnusedMember.Global
+
+
+ public SettingsPluginsViewModel(IPluginManager pluginManager)
+ {
+ // Design-time support
+ if (pluginManager == null)
+ return;
+
+ Plugins = pluginManager.GetPlugins()
+ .Select(p => new PluginViewModel(p.Plugin.Name, p.Plugin.Description, p.Filename, p.Plugin.Author, p.Plugin.Url))
+ .OrderBy(p => p.Name, StringComparer.CurrentCultureIgnoreCase)
+ .ToList();
+ }
+ }
+
+
+ public class SettingsPluginsViewModelDesignTime : SettingsPluginsViewModel
+ {
+ public SettingsPluginsViewModelDesignTime()
+ : base(null)
+ {
+ Plugins = new[]
+ {
+ new PluginViewModel("Plugin without description", null, "D:\\Does\\Not\\Exist.dll", "Some Massive Knob ", "https://lmgtfy.app/?q=Massive+Knob"),
+ new PluginViewModel("Design-time plugin", "Fake plugin only visible at design-time.", "C:\\Does\\Not\\Exist.dll", null, null)
+ };
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/ViewModel/Settings/SettingsStartupViewModel.cs b/Windows/MassiveKnob/ViewModel/Settings/SettingsStartupViewModel.cs
new file mode 100644
index 0000000..fee4592
--- /dev/null
+++ b/Windows/MassiveKnob/ViewModel/Settings/SettingsStartupViewModel.cs
@@ -0,0 +1,67 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using Microsoft.Win32;
+
+namespace MassiveKnob.ViewModel.Settings
+{
+ public class SettingsStartupViewModel : INotifyPropertyChanged
+ {
+ public const string RunKey = @"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
+ public const string RunValue = @"MassiveKnob";
+
+ // ReSharper disable UnusedMember.Global - used by WPF Binding
+ private bool runAtStartup;
+ public bool RunAtStartup
+ {
+ get => runAtStartup;
+ set
+ {
+ if (value == runAtStartup)
+ return;
+
+ runAtStartup = value;
+ OnPropertyChanged();
+
+ ApplyRunAtStartup();
+ }
+ }
+ // ReSharper restore UnusedMember.Global
+
+
+ public SettingsStartupViewModel()
+ {
+ var runKey = Registry.CurrentUser.OpenSubKey(RunKey, false);
+ runAtStartup = runKey?.GetValue(RunValue) != null;
+ }
+
+
+ private void ApplyRunAtStartup()
+ {
+ var runKey = Registry.CurrentUser.OpenSubKey(RunKey, true);
+ Debug.Assert(runKey != null, nameof(runKey) + " != null");
+
+ if (RunAtStartup)
+ {
+ var entryAssembly = Assembly.GetEntryAssembly();
+ Debug.Assert(entryAssembly != null, nameof(entryAssembly) + " != null");
+
+ runKey.SetValue(RunValue, new Uri(entryAssembly.CodeBase).LocalPath);
+ }
+ else
+ {
+ runKey.DeleteValue(RunValue, false);
+ }
+ }
+
+
+ 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 11e1928..cd8a771 100644
--- a/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs
+++ b/Windows/MassiveKnob/ViewModel/SettingsViewModel.cs
@@ -1,55 +1,39 @@
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 System.Windows.Media;
using MassiveKnob.Core;
using MassiveKnob.Plugin;
using MassiveKnob.Settings;
using MassiveKnob.View.Settings;
-using Microsoft.Win32;
-using Serilog.Events;
namespace MassiveKnob.ViewModel
{
- // TODO (code quality) split ViewModel for individual views, create viewmodel using container
- // TODO (nice to have) installed plugins list
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) }
+ { SettingsMenuItem.Device, typeof(SettingsDeviceView) },
+ { SettingsMenuItem.AnalogInputs, typeof(SettingsAnalogInputsView) },
+ { SettingsMenuItem.DigitalInputs, typeof(SettingsDigitalInputsView) },
+ { SettingsMenuItem.AnalogOutputs, typeof(SettingsAnalogOutputsView) },
+ { SettingsMenuItem.DigitalOutputs, typeof(SettingsDigitalOutputsView) },
+ { SettingsMenuItem.Logging, typeof(SettingsLoggingView) },
+ { SettingsMenuItem.Startup, typeof(SettingsStartupView) },
+ { SettingsMenuItem.Plugins, typeof(SettingsPluginsView) }
};
-
-
+
+ private readonly SimpleInjector.Container container;
private readonly IMassiveKnobOrchestrator orchestrator;
- private readonly ILoggingSwitch loggingSwitch;
- private DeviceViewModel selectedDevice;
private UserControl selectedView;
private SettingsMenuItem selectedMenuItem;
- private UserControl settingsControl;
private DeviceSpecs? specs;
- private IEnumerable analogInputs;
- private IEnumerable digitalInputs;
- private IEnumerable analogOutputs;
- private IEnumerable digitalOutputs;
-
private readonly IDisposable activeDeviceSubscription;
- private readonly IDisposable deviceStatusSubscription;
+
// ReSharper disable UnusedMember.Global - used by WPF Binding
public SettingsMenuItem SelectedMenuItem
@@ -64,8 +48,8 @@ namespace MassiveKnob.ViewModel
OnPropertyChanged();
if (menuItemControls.TryGetValue(selectedMenuItem, out var viewType))
- SelectedView = (UserControl) Activator.CreateInstance(viewType);
-
+ SelectedView = (UserControl)container?.GetInstance(viewType);
+
orchestrator?.UpdateSettings(settings =>
{
settings.UI.ActiveMenuItem = selectedMenuItem;
@@ -88,42 +72,7 @@ namespace MassiveKnob.ViewModel
- public IList Devices { get; }
- public IList Actions { get; }
-
-
- public DeviceViewModel SelectedDevice
- {
- get => selectedDevice;
- set
- {
- if (value == selectedDevice)
- return;
-
- selectedDevice = value;
- var deviceInfo = orchestrator?.SetActiveDevice(value?.Device);
-
- OnPropertyChanged();
-
- SettingsControl = deviceInfo?.Instance.CreateSettingsControl();
- }
- }
-
- public UserControl SettingsControl
- {
- get => settingsControl;
- set
- {
- if (value == settingsControl)
- return;
-
- if (settingsControl is IDisposable disposable)
- disposable.Dispose();
-
- settingsControl = value;
- OnPropertyChanged();
- }
- }
+ //public IList Actions { get; }
public DeviceSpecs? Specs
{
@@ -136,7 +85,7 @@ namespace MassiveKnob.ViewModel
OnDependantPropertyChanged("DigitalInputVisibility");
OnDependantPropertyChanged("AnalogOutputVisibility");
OnDependantPropertyChanged("DigitalOutputVisibility");
-
+ /*
DisposeInputOutputViewModels(AnalogInputs);
DisposeInputOutputViewModels(DigitalInputs);
DisposeInputOutputViewModels(AnalogOutputs);
@@ -161,6 +110,7 @@ namespace MassiveKnob.ViewModel
.Range(0, specs?.DigitalOutputCount ?? 0)
.Select(i => new InputOutputViewModel(this, orchestrator, MassiveKnobActionType.OutputDigital, i))
.ToList();
+ */
}
}
@@ -169,6 +119,7 @@ namespace MassiveKnob.ViewModel
? Visibility.Visible
: Visibility.Collapsed;
+ /*
public IEnumerable AnalogInputs
{
get => analogInputs;
@@ -178,11 +129,13 @@ namespace MassiveKnob.ViewModel
OnPropertyChanged();
}
}
+ */
public Visibility DigitalInputVisibility => specs.HasValue && specs.Value.DigitalInputCount > 0
? Visibility.Visible
: Visibility.Collapsed;
+ /*
public IEnumerable DigitalInputs
{
get => digitalInputs;
@@ -192,11 +145,13 @@ namespace MassiveKnob.ViewModel
OnPropertyChanged();
}
}
+ */
public Visibility AnalogOutputVisibility => specs.HasValue && specs.Value.AnalogOutputCount > 0
? Visibility.Visible
: Visibility.Collapsed;
+ /*
public IEnumerable AnalogOutputs
{
get => analogOutputs;
@@ -206,11 +161,13 @@ namespace MassiveKnob.ViewModel
OnPropertyChanged();
}
}
+ */
public Visibility DigitalOutputVisibility => specs.HasValue && specs.Value.DigitalOutputCount > 0
? Visibility.Visible
: Visibility.Collapsed;
+ /*
public IEnumerable DigitalOutputs
{
get => digitalOutputs;
@@ -221,7 +178,6 @@ namespace MassiveKnob.ViewModel
}
}
-
public IList LoggingLevels { get; }
private LoggingLevelViewModel selectedLoggingLevel;
@@ -281,62 +237,15 @@ namespace MassiveKnob.ViewModel
ApplyRunAtStartup();
}
}
-
-
- public string ConnectionStatusText
- {
- get
- {
- if (orchestrator == null)
- return "Design-time";
-
- switch (orchestrator.DeviceStatus)
- {
- case MassiveKnobDeviceStatus.Disconnected:
- return Strings.DeviceStatusDisconnected;
-
- case MassiveKnobDeviceStatus.Connecting:
- return Strings.DeviceStatusConnecting;
-
- case MassiveKnobDeviceStatus.Connected:
- return Strings.DeviceStatusConnected;
-
- default:
- return null;
- }
- }
- }
-
- public Brush ConnectionStatusColor
- {
- get
- {
- if (orchestrator == null)
- return Brushes.Fuchsia;
-
- switch (orchestrator.DeviceStatus)
- {
- case MassiveKnobDeviceStatus.Disconnected:
- return Brushes.DarkRed;
-
- case MassiveKnobDeviceStatus.Connecting:
- return Brushes.Orange;
-
- case MassiveKnobDeviceStatus.Connected:
- return Brushes.ForestGreen;
-
- default:
- return null;
- }
- }
- }
+ */
// ReSharper restore UnusedMember.Global
- public SettingsViewModel(IPluginManager pluginManager, IMassiveKnobOrchestrator orchestrator, ILoggingSwitch loggingSwitch)
+ public SettingsViewModel(SimpleInjector.Container container, /*IPluginManager pluginManager, */IMassiveKnobOrchestrator orchestrator/*, ILoggingSwitch loggingSwitch*/)
{
+ this.container = container;
this.orchestrator = orchestrator;
- this.loggingSwitch = loggingSwitch;
+ //this.loggingSwitch = loggingSwitch;
// For design-time support
if (orchestrator == null)
@@ -348,6 +257,7 @@ namespace MassiveKnob.ViewModel
SelectedMenuItem = activeMenuItem;
+
activeDeviceSubscription = orchestrator.ActiveDeviceSubject.Subscribe(info =>
{
Application.Current?.Dispatcher.Invoke(() =>
@@ -355,18 +265,12 @@ namespace MassiveKnob.ViewModel
Specs = info.Specs;
});
});
- deviceStatusSubscription = orchestrator.DeviceStatusSubject.Subscribe(status =>
- {
- OnDependantPropertyChanged(nameof(ConnectionStatusColor));
- OnDependantPropertyChanged(nameof(ConnectionStatusText));
- });
+
+ if (orchestrator.ActiveDevice != null)
+ Specs = orchestrator.ActiveDevice.Specs;
- Devices = pluginManager.GetDevicePlugins()
- .SelectMany(dp => dp.Devices.Select(d => new DeviceViewModel(dp, d)))
- .OrderBy(d => d.Name.ToLower())
- .ToList();
-
+ /*
var allActions = new List
{
new ActionViewModel(null, null)
@@ -379,12 +283,6 @@ namespace MassiveKnob.ViewModel
Actions = allActions;
- if (orchestrator.ActiveDevice != null)
- {
- 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;
@@ -403,24 +301,23 @@ namespace MassiveKnob.ViewModel
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);
+ */
activeDeviceSubscription?.Dispose();
- deviceStatusSubscription?.Dispose();
}
-
+ /*
private void ApplyLoggingSettings()
{
orchestrator?.UpdateSettings(settings =>
@@ -460,6 +357,7 @@ namespace MassiveKnob.ViewModel
foreach (var viewModel in viewModels)
viewModel.Dispose();
}
+ */
public event PropertyChangedEventHandler PropertyChanged;
@@ -478,7 +376,7 @@ namespace MassiveKnob.ViewModel
public class SettingsViewModelDesignTime : SettingsViewModel
{
- public SettingsViewModelDesignTime() : base(null, null, null)
+ public SettingsViewModelDesignTime() : base(null, null)
{
Specs = new DeviceSpecs(2, 2, 2, 2);
}
diff --git a/Windows/Setup/MassiveKnobSetup.iss b/Windows/Setup/MassiveKnobSetup.iss
index ded0de0..c345a3f 100644
--- a/Windows/Setup/MassiveKnobSetup.iss
+++ b/Windows/Setup/MassiveKnobSetup.iss
@@ -36,6 +36,7 @@ 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\runprogram; Description: "Run program"; Types: full custom
Name: optionalplugins\emulatordevice; Description: "Emulator device"; Types: full custom
Name: optionalplugins\voicemeeter; Description: "VoiceMeeter actions"; Types: full custom
@@ -65,6 +66,10 @@ Source: {#BasePath}\MassiveKnob.Plugin.EmulatorDevice\bin\Release\*.dll; DestDir
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
+; Run Program plugin
+Source: {#BasePath}\MassiveKnob.Plugin.RunProgram\bin\Release\MassiveKnobPlugin.json; DestDir: "{app}\Plugins\RunProgram"; Flags: ignoreversion; Components: optionalplugins\runprogram
+Source: {#BasePath}\MassiveKnob.Plugin.RunProgram\bin\Release\*.dll; DestDir: "{app}\Plugins\RunProgram"; Flags: ignoreversion; Components: optionalplugins\runprogram
+
[Dirs]
Name: "{localappdata}\MassiveKnob"
Name: "{localappdata}\MassiveKnob\Logs"