diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 0ae50f9..10158c1 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -7,9 +7,11 @@ version: 2
updates:
- package-ecosystem: "nuget"
directory: "/" # Location of package manifests
+ target-branch: "develop"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
+ target-branch: "develop"
schedule:
interval: "weekly"
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index b6aa56f..ea4944b 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -13,10 +13,10 @@ name: "CodeQL"
on:
push:
- branches: [ "main", release/* ]
+ branches: [ "main", release/*, "develop" ]
pull_request:
# The branches below must be a subset of the branches above
- branches: [ "main" ]
+ branches: [ "main", "develop" ]
schedule:
- cron: '23 3 * * 4'
@@ -59,8 +59,8 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- - name: Autobuild
- uses: github/codeql-action/autobuild@v3
+ # - name: Autobuild
+ # uses: github/codeql-action/autobuild@v3
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -68,9 +68,10 @@ jobs:
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
- # - run: |
- # echo "Run, Build Application using script"
- # ./location_of_script_within_repo/buildscript.sh
+ - name: Build
+ run: |
+ dotnet build StreamDeckSimHub.Plugin/StreamDeckSimHub.Plugin.csproj
+ dotnet build StreamDeckSimHub.PluginTests/StreamDeckSimHub.PluginTests.csproj
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 9597200..b66e2d0 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -1,13 +1,13 @@
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
-name: .NET
+name: .NET Build and Test
on:
push:
- branches: [ "main" ]
+ branches: [ "main", "develop" ]
pull_request:
- branches: [ "main" ]
+ branches: [ "main", "develop" ]
jobs:
build:
@@ -23,8 +23,13 @@ jobs:
with:
dotnet-version: 8.0.x
- name: Restore dependencies
- run: dotnet restore
+ run: |
+ dotnet restore StreamDeckSimHub.Plugin/StreamDeckSimHub.Plugin.csproj
+ dotnet restore StreamDeckSimHub.PluginTests/StreamDeckSimHub.PluginTests.csproj
- name: Build
- run: dotnet build --no-restore
+ run: |
+ dotnet build --no-restore StreamDeckSimHub.Plugin/StreamDeckSimHub.Plugin.csproj
+ dotnet build --no-restore StreamDeckSimHub.PluginTests/StreamDeckSimHub.PluginTests.csproj
- name: Test
- run: dotnet test --no-build --verbosity normal
+ run: |
+ dotnet test --no-build --verbosity normal StreamDeckSimHub.PluginTests/StreamDeckSimHub.PluginTests.csproj
diff --git a/Directory.Build.props b/Directory.Build.props
index 26f6094..3634511 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -6,4 +6,7 @@
3.6.143
+
+ $(MSBuildThisFileDirectory)
+
\ No newline at end of file
diff --git a/README.adoc b/README.adoc
index cd376f3..21e1cc1 100644
--- a/README.adoc
+++ b/README.adoc
@@ -47,13 +47,13 @@ image::images/teaser/Teaser-2.png[Teaser 2,800]
== Installation
-WARNING: To download, do not use the green button! Instead, click on "Releases" on the right side and download the file with extension `streamDeckPlugin`.
+WARNING: To download, do not use the green button! Instead, click on "Releases" on the right side and download the installer file.
Be sure to have the SimHub Property Server plugin installed into SimHub (see above). When updating this plugin, be sure to also check the SimHub Property Server plugin for updates.
TIP: For the usage of this plugin, the https://dotnet.microsoft.com/en-us/download/dotnet/8.0[.NET Runtime 8.0] has to be installed. Without this, the plugin won't even start. Download ".NET Desktop Runtime 8.0.x (x64)" from Microsoft.
-Download the file `net.planetrenner.simhub.streamDeckPlugin` from the GitHub "Releases" page and double-click it to install it into Stream Deck.
+Download the installer from the GitHub "Releases" page and double-click it to install it into Stream Deck.
== Usage
diff --git a/StreamDeckSimHub.Installer/Actions/AbstractInstallerAction.cs b/StreamDeckSimHub.Installer/Actions/AbstractInstallerAction.cs
new file mode 100644
index 0000000..17557ea
--- /dev/null
+++ b/StreamDeckSimHub.Installer/Actions/AbstractInstallerAction.cs
@@ -0,0 +1,71 @@
+// Copyright (C) 2024 Martin Renner
+// LGPL-3.0-or-later (see file COPYING and COPYING.LESSER)
+
+using System.Windows.Media;
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace StreamDeckSimHub.Installer.Actions;
+
+public abstract partial class AbstractInstallerAction : ObservableObject, IInstallerAction
+{
+ private readonly NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
+
+ public abstract string Name { get; }
+
+ [ObservableProperty]
+ private string _message = string.Empty;
+
+ [ObservableProperty]
+ private Brush _actionResultColor = ActionColors.InactiveBrush;
+
+ public async Task Execute()
+ {
+ _logger.Info($"Starting action {GetType().Name}");
+
+ ActionResultColor = ActionColors.RunningBrush;
+ try
+ {
+ var result = await ExecuteInternal();
+ ActionResultColor = result switch
+ {
+ ActionResult.Success => ActionColors.SuccessBrush,
+ ActionResult.Error => ActionColors.ErrorBrush,
+ ActionResult.NotRequired => ActionColors.InactiveBrush,
+ ActionResult.Warning => ActionColors.WarningBrush,
+ _ => throw new ArgumentOutOfRangeException()
+ };
+
+ _logger.Info($"Finished action {GetType().Name} with result {result}");
+
+ // Give the user a tiny moment to realize that there is something going on.
+ await Task.Delay(1000);
+
+ return result;
+ }
+ catch (Exception e)
+ {
+ SetAndLogError(e, $"Action {GetType().Name} failed with exception");
+ return ActionResult.Error;
+ }
+ }
+
+ protected abstract Task ExecuteInternal();
+
+ protected void SetAndLogInfo(string message)
+ {
+ Message = message;
+ _logger.Info(message);
+ }
+
+ protected void SetAndLogError(string message)
+ {
+ Message = message;
+ _logger.Info(message);
+ }
+
+ protected void SetAndLogError(Exception e, string message)
+ {
+ Message = message;
+ _logger.Error(e, message);
+ }
+}
\ No newline at end of file
diff --git a/StreamDeckSimHub.Installer/Actions/IInstallerAction.cs b/StreamDeckSimHub.Installer/Actions/IInstallerAction.cs
new file mode 100644
index 0000000..ba0f28a
--- /dev/null
+++ b/StreamDeckSimHub.Installer/Actions/IInstallerAction.cs
@@ -0,0 +1,51 @@
+// Copyright (C) 2024 Martin Renner
+// LGPL-3.0-or-later (see file COPYING and COPYING.LESSER)
+
+using System.Windows.Media;
+
+namespace StreamDeckSimHub.Installer.Actions;
+
+public enum ActionResult
+{
+ NotRequired,
+ Success,
+ Warning,
+ Error,
+}
+
+public abstract class ActionColors
+{
+ public static readonly Brush InactiveBrush = Brushes.Gray;
+ public static readonly Brush RunningBrush = Brushes.Orange;
+ public static readonly Brush SuccessBrush = Brushes.Green;
+ public static readonly Brush ErrorBrush = Brushes.Red;
+ public static readonly Brush WarningBrush = Brushes.Yellow;
+}
+
+///
+/// An action that can be executed by the installer.
+///
+/// Holds the action logic, but is also misused as a ViewModel.
+public interface IInstallerAction
+{
+ ///
+ /// Displayed as header in the UI.
+ ///
+ string Name { get; }
+
+ ///
+ /// Detailed message below the header.
+ ///
+ string Message { get; }
+
+ ///
+ /// Result of the action in the form of a colored brush.
+ ///
+ Brush ActionResultColor { get; }
+
+ ///
+ /// Executes the action.
+ ///
+ /// matic result of the action. Is used in the installer logic.
+ Task Execute();
+}
diff --git a/StreamDeckSimHub.Installer/Actions/InstallStreamDeckPlugin.cs b/StreamDeckSimHub.Installer/Actions/InstallStreamDeckPlugin.cs
new file mode 100644
index 0000000..f0a9559
--- /dev/null
+++ b/StreamDeckSimHub.Installer/Actions/InstallStreamDeckPlugin.cs
@@ -0,0 +1,88 @@
+// Copyright (C) 2024 Martin Renner
+// LGPL-3.0-or-later (see file COPYING and COPYING.LESSER)
+
+using System.IO;
+using System.IO.Compression;
+using StreamDeckSimHub.Installer.Tools;
+
+namespace StreamDeckSimHub.Installer.Actions;
+
+public class InstallStreamDeckPlugin : AbstractInstallerAction
+{
+ public override string Name => "Installing Stream Deck SimHub Plugin";
+
+ protected override Task ExecuteInternal()
+ {
+ if (Directory.Exists(Path.Combine(Configuration.StreamDeckPluginDir, Configuration.PluginDirName)))
+ {
+ SetAndLogInfo("Deleting existing Stream Deck SimHub Plugin");
+ var result = DeleteExistingInstallation(Path.Combine(Configuration.StreamDeckPluginDir, Configuration.PluginDirName));
+ if (!result)
+ {
+ return Task.FromResult(ActionResult.Error);
+ }
+ }
+
+ SetAndLogInfo("Installing Stream Deck SimHub Plugin");
+ if (!ExtractPlugin(Configuration.StreamDeckPluginDir))
+ {
+ return Task.FromResult(ActionResult.Error);
+ }
+
+ SetAndLogInfo("Successfully installed Stream Deck SimHub Plugin");
+ return Task.FromResult(ActionResult.Success);
+ }
+
+ private bool DeleteExistingInstallation(string pluginDir)
+ {
+ try
+ {
+ // Delete all files in the base directory
+ var baseDirInfo = new DirectoryInfo(pluginDir);
+ foreach (var fileInfo in baseDirInfo.EnumerateFiles())
+ {
+ fileInfo.Delete();
+ }
+
+ // Delete all directories recursive in the base directory - except "images"
+ foreach (var dirInfo in baseDirInfo.EnumerateDirectories().Where(dirInfo => dirInfo.Name != "images"))
+ {
+ dirInfo.Delete(true);
+ }
+
+ // Delete all directories recursive in the "images" directory - except "custom"
+ var imagesDirInfo = new DirectoryInfo(Path.Combine(pluginDir, "images"));
+ foreach (var dirInfo in imagesDirInfo.EnumerateDirectories().Where(dirInfo => dirInfo.Name != "custom"))
+ {
+ dirInfo.Delete(true);
+ }
+
+ return true;
+ }
+ catch (Exception e)
+ {
+ SetAndLogError(e, $"Could not delete existing installation: {e.Message}");
+ return false;
+ }
+ }
+
+ private bool ExtractPlugin(string streamDeckPluginDir)
+ {
+ var myAssembly = typeof(App).Assembly;
+ var resourcePaths = myAssembly.GetManifestResourceNames().Where(res => res.EndsWith(Configuration.PluginZipName)).ToList();
+ if (resourcePaths.Count == 1)
+ {
+ using var pluginStream = myAssembly.GetManifestResourceStream(resourcePaths[0]);
+ if (pluginStream == null)
+ {
+ SetAndLogError("Could not find embedded Stream Deck SimHub Plugin (stream is null)");
+ return false;
+ }
+ ZipFile.ExtractToDirectory(pluginStream, streamDeckPluginDir, true);
+ return true;
+ }
+
+ SetAndLogError($"Could not find embedded Stream Deck SimHub Plugin ({resourcePaths.Count} streams)");
+ return false;
+ }
+}
diff --git a/StreamDeckSimHub.Installer/Actions/StartStreamDeckSoftware.cs b/StreamDeckSimHub.Installer/Actions/StartStreamDeckSoftware.cs
new file mode 100644
index 0000000..cb00c27
--- /dev/null
+++ b/StreamDeckSimHub.Installer/Actions/StartStreamDeckSoftware.cs
@@ -0,0 +1,38 @@
+// Copyright (C) 2024 Martin Renner
+// LGPL-3.0-or-later (see file COPYING and COPYING.LESSER)
+
+using System.IO;
+using Microsoft.Win32;
+using StreamDeckSimHub.Installer.Tools;
+
+namespace StreamDeckSimHub.Installer.Actions;
+
+///
+/// Starts the Stream Deck software.
+///
+public class StartStreamDeckSoftware : AbstractInstallerAction
+{
+ public override string Name => "Starting Stream Deck software";
+
+ protected override Task ExecuteInternal()
+ {
+ var installFolder = GetStreamDeckInstallFolder();
+ ProcessTools.StartProcess(Path.Combine(installFolder, "StreamDeck.exe"), installFolder);
+
+ SetAndLogInfo("Stream Deck software started");
+ return Task.FromResult(ActionResult.Success);
+ }
+
+ private string GetStreamDeckInstallFolder()
+ {
+ var installPath = (string?) Registry.GetValue(Configuration.StreamDeckRegistryFolder, Configuration.StreamDeckRegistryInstallFolder, null);
+ if (!string.IsNullOrEmpty(installPath))
+ {
+ SetAndLogInfo($"Found Stream Deck directory in registry: {installPath}");
+ return installPath;
+ }
+
+ SetAndLogInfo($"Could not find Stream Deck directory in registry. Using default.");
+ return Configuration.StreamDeckDefaultInstallFolder;
+ }
+}
diff --git a/StreamDeckSimHub.Installer/Actions/StopStreamDeckSoftware.cs b/StreamDeckSimHub.Installer/Actions/StopStreamDeckSoftware.cs
new file mode 100644
index 0000000..64f46c1
--- /dev/null
+++ b/StreamDeckSimHub.Installer/Actions/StopStreamDeckSoftware.cs
@@ -0,0 +1,78 @@
+// Copyright (C) 2024 Martin Renner
+// LGPL-3.0-or-later (see file COPYING and COPYING.LESSER)
+
+using StreamDeckSimHub.Installer.Tools;
+
+namespace StreamDeckSimHub.Installer.Actions;
+
+///
+/// Stops the Stream Deck software, if it is running.
+///
+public partial class StopStreamDeckSoftware : AbstractInstallerAction
+{
+ public override string Name => "Stopping Stream Deck software";
+
+ protected override async Task ExecuteInternal()
+ {
+ if (!IsStreamDeckRunning())
+ {
+ SetAndLogInfo("Stream Deck software is not running. Stopping not required.");
+ return ActionResult.NotRequired;
+ }
+
+ if (!IsPluginRunning())
+ {
+ SetAndLogInfo("Plugin is not running.");
+ }
+
+ var process = ProcessTools.GetProcess(Configuration.StreamDeckProcessName);
+ process?.Kill(true);
+
+ if (!await WaitForStreamDeckKilled())
+ {
+ SetAndLogError("The Stream Deck software could not be stopped. Please stop it manually and try again.");
+ return ActionResult.Error;
+ }
+
+ if (!await WaitForPluginKilled())
+ {
+ SetAndLogError("The Stream Deck SimHub Plugin could not be stopped. Please kill it manually and try again.");
+ return ActionResult.Error;
+ }
+
+ SetAndLogInfo("The Stream Deck software stopped.");
+ return ActionResult.Success;
+ }
+
+ private bool IsStreamDeckRunning()
+ {
+ return ProcessTools.IsProcessRunning(Configuration.StreamDeckProcessName);
+ }
+
+ private bool IsPluginRunning()
+ {
+ return ProcessTools.IsProcessRunning(Configuration.PluginProcessName);
+ }
+
+ private async Task WaitForStreamDeckKilled()
+ {
+ for (var i = 0; i < 10; i++)
+ {
+ if (!IsStreamDeckRunning()) return true;
+ await Task.Delay(1000);
+ }
+
+ return false;
+ }
+
+ private async Task WaitForPluginKilled()
+ {
+ for (var i = 0; i < 10; i++)
+ {
+ if (!IsPluginRunning()) return true;
+ await Task.Delay(1000);
+ }
+
+ return false;
+ }
+}
diff --git a/StreamDeckSimHub.Installer/Actions/VerifySimHubPlugin.cs b/StreamDeckSimHub.Installer/Actions/VerifySimHubPlugin.cs
new file mode 100644
index 0000000..af97ff9
--- /dev/null
+++ b/StreamDeckSimHub.Installer/Actions/VerifySimHubPlugin.cs
@@ -0,0 +1,52 @@
+using System.Diagnostics;
+using System.IO;
+using Microsoft.Win32;
+using StreamDeckSimHub.Installer.Tools;
+
+namespace StreamDeckSimHub.Installer.Actions;
+
+public class VerifySimHubPlugin : AbstractInstallerAction
+{
+ public override string Name => "Verify SimHub installation and SimHub Property Server plugin";
+
+ protected override Task ExecuteInternal()
+ {
+ var installFolder = GetSimHubInstallFolder();
+ if (!File.Exists(Path.Combine(installFolder, "SimHubWPF.exe")))
+ {
+ SetAndLogInfo("Could not find SimHub installation. SimHub is required for this plugin.");
+ return Task.FromResult(ActionResult.Warning);
+ }
+
+ if (!File.Exists(Path.Combine(installFolder, "PropertyServer.dll")))
+ {
+ SetAndLogInfo($"Could not find SimHub Property Server plugin. Is is required for this plugin. Please install it from {Configuration.SimHubPluginUrl}");
+ return Task.FromResult(ActionResult.Warning);
+ }
+
+ var pluginVersionInfo = FileVersionInfo.GetVersionInfo(Path.Combine(installFolder, "PropertyServer.dll"));
+ Version pluginVersion = new(pluginVersionInfo.ProductMajorPart, pluginVersionInfo.ProductMinorPart, pluginVersionInfo.ProductBuildPart);
+ if (pluginVersion < Configuration.RequiredSimHubPluginVersion)
+ {
+ SetAndLogInfo($"SimHub Property Server plugin is too old. Found version {pluginVersion}, required version is {Configuration.RequiredSimHubPluginVersion}. Please update the SimHub Property Server plugin by visiting {Configuration.SimHubPluginUrl}");
+ return Task.FromResult(ActionResult.Warning);
+ }
+
+ SetAndLogInfo("Found SimHub and the SimHub Property Server plugin.");
+ return Task.FromResult(ActionResult.Success);
+ }
+
+ private string GetSimHubInstallFolder()
+ {
+ var installPath = (string?) Registry.GetValue(Configuration.SimHubRegistryFolder, Configuration.SimHubRegistryInstallFolder, null);
+ if (!string.IsNullOrEmpty(installPath))
+ {
+ SetAndLogInfo($"Found SimHub directory in registry: {installPath}");
+ return installPath;
+ }
+
+ SetAndLogInfo($"Could not find SimHub directory in registry. Using default.");
+ return Configuration.StreamDeckDefaultInstallFolder;
+ }
+
+}
diff --git a/StreamDeckSimHub.Installer/App.xaml b/StreamDeckSimHub.Installer/App.xaml
new file mode 100644
index 0000000..97f9e00
--- /dev/null
+++ b/StreamDeckSimHub.Installer/App.xaml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/StreamDeckSimHub.Installer/App.xaml.cs b/StreamDeckSimHub.Installer/App.xaml.cs
new file mode 100644
index 0000000..9fe0d95
--- /dev/null
+++ b/StreamDeckSimHub.Installer/App.xaml.cs
@@ -0,0 +1,19 @@
+// Copyright (C) 2024 Martin Renner
+// LGPL-3.0-or-later (see file COPYING and COPYING.LESSER)
+
+using System.Reflection;
+using System.Windows;
+using NLog;
+
+namespace StreamDeckSimHub.Installer;
+
+///
+/// Interaction logic for App.xaml
+///
+public partial class App : Application
+{
+ private void App_OnStartup(object sender, StartupEventArgs e)
+ {
+ LogManager.Setup().LoadConfigurationFromAssemblyResource(typeof(App).GetTypeInfo().Assembly);
+ }
+}
\ No newline at end of file
diff --git a/StreamDeckSimHub.Installer/AssemblyInfo.cs b/StreamDeckSimHub.Installer/AssemblyInfo.cs
new file mode 100644
index 0000000..4a05c7d
--- /dev/null
+++ b/StreamDeckSimHub.Installer/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
\ No newline at end of file
diff --git a/StreamDeckSimHub.Installer/MainWindow.xaml b/StreamDeckSimHub.Installer/MainWindow.xaml
new file mode 100644
index 0000000..9f3516a
--- /dev/null
+++ b/StreamDeckSimHub.Installer/MainWindow.xaml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This program will install the Stream Deck SimHub Plugin into the Stream Deck software.
+
+ Version:
+
+
+ Your custom images will be preserved.
+
+
+ Run this program with your current user (i.e. do not run as administrator if you are currently not logged in as administrator).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/StreamDeckSimHub.Installer/MainWindow.xaml.cs b/StreamDeckSimHub.Installer/MainWindow.xaml.cs
new file mode 100644
index 0000000..1245191
--- /dev/null
+++ b/StreamDeckSimHub.Installer/MainWindow.xaml.cs
@@ -0,0 +1,17 @@
+// Copyright (C) 2024 Martin Renner
+// LGPL-3.0-or-later (see file COPYING and COPYING.LESSER)
+
+using System.Windows;
+
+namespace StreamDeckSimHub.Installer;
+
+///
+/// Interaction logic for MainWindow.xaml
+///
+public partial class MainWindow : Window
+{
+ public MainWindow()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/StreamDeckSimHub.Installer/MainWindowViewModel.cs b/StreamDeckSimHub.Installer/MainWindowViewModel.cs
new file mode 100644
index 0000000..7292aeb
--- /dev/null
+++ b/StreamDeckSimHub.Installer/MainWindowViewModel.cs
@@ -0,0 +1,107 @@
+// Copyright (C) 2024 Martin Renner
+// LGPL-3.0-or-later (see file COPYING and COPYING.LESSER)
+
+using System.Collections.ObjectModel;
+using System.Windows.Media;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using StreamDeckSimHub.Installer.Actions;
+
+namespace StreamDeckSimHub.Installer;
+
+public partial class MainWindowViewModel : ObservableObject
+{
+ private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
+ private static readonly Brush SuccessBrush = Brushes.Green;
+ private static readonly Brush WarningBrush = Brushes.Orange;
+ private static readonly Brush ErrorBrush = Brushes.Red;
+
+ public string Version => ThisAssembly.AssemblyFileVersion;
+
+ [ObservableProperty]
+ private ObservableCollection _installerSteps = [];
+
+ [ObservableProperty]
+ private string _result = string.Empty;
+
+ [ObservableProperty]
+ private Brush _resultBrush = SuccessBrush;
+
+ [RelayCommand]
+ private async Task Install()
+ {
+ ClearResultText();
+ InstallerSteps.Clear();
+ Logger.Info("========== Starting installation ==========");
+
+ var stopStreamDeck = new StopStreamDeckSoftware();
+ InstallerSteps.Add(stopStreamDeck);
+ if (await stopStreamDeck.Execute() == ActionResult.Error)
+ {
+ SetErrorResultText();
+ return;
+ }
+
+ var result = ActionResult.Success;
+ var installStreamDeckPlugin = new InstallStreamDeckPlugin();
+ InstallerSteps.Add(installStreamDeckPlugin);
+ result = SetHigherResultLevel(await installStreamDeckPlugin.Execute(), result);
+
+ var startStreamDeck = new StartStreamDeckSoftware();
+ InstallerSteps.Add(startStreamDeck);
+ result = SetHigherResultLevel(await startStreamDeck.Execute(), result);
+
+ var verifySimHubPlugin = new VerifySimHubPlugin();
+ InstallerSteps.Add(verifySimHubPlugin);
+ result = SetHigherResultLevel(await verifySimHubPlugin.Execute(), result);
+
+ switch (result)
+ {
+ case ActionResult.NotRequired:
+ case ActionResult.Success:
+ SetSuccessResultText();
+ break;
+ case ActionResult.Warning:
+ SetWarningResultText();
+ break;
+ case ActionResult.Error:
+ SetErrorResultText();
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ private ActionResult SetHigherResultLevel(ActionResult result, ActionResult existingResult)
+ {
+ var v1 = (int)result;
+ var v2 = (int)existingResult;
+ return v1 > v2 ? result : existingResult;
+ }
+
+ private void ClearResultText()
+ {
+ Result = string.Empty;
+ ResultBrush = SuccessBrush;
+ }
+
+ private void SetSuccessResultText()
+ {
+ Result = "The plugin was installed successfully. You can exit the program now.";
+ ResultBrush = SuccessBrush;
+ }
+
+ private void SetWarningResultText()
+ {
+ Result = "There have been warnings during the installation. Please check the steps above.";
+ ResultBrush = WarningBrush;
+ }
+
+ private void SetErrorResultText()
+ {
+ Result = """
+ The installation was NOT successful. Please stop the Stream Deck software manually and try again.
+ """;
+ ResultBrush = ErrorBrush;
+ }
+}
\ No newline at end of file
diff --git a/StreamDeckSimHub.Installer/NLog.config b/StreamDeckSimHub.Installer/NLog.config
new file mode 100644
index 0000000..4c5d6b9
--- /dev/null
+++ b/StreamDeckSimHub.Installer/NLog.config
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StreamDeckSimHub.Installer/README.adoc b/StreamDeckSimHub.Installer/README.adoc
new file mode 100644
index 0000000..fb72534
--- /dev/null
+++ b/StreamDeckSimHub.Installer/README.adoc
@@ -0,0 +1,6 @@
+= Installer
+
+. Build the plugin: `release.bat` or `release.bat debug`
+
+. Build the installer: `dotnet publish StreamDeckSimHub.Installer\StreamDeckSimHub.Installer.csproj`
+
diff --git a/StreamDeckSimHub.Installer/StreamDeckSimHub.Installer.csproj b/StreamDeckSimHub.Installer/StreamDeckSimHub.Installer.csproj
new file mode 100644
index 0000000..3007e2b
--- /dev/null
+++ b/StreamDeckSimHub.Installer/StreamDeckSimHub.Installer.csproj
@@ -0,0 +1,33 @@
+
+
+
+ WinExe
+ net8.0-windows
+ true
+ false
+ enable
+ enable
+ true
+
+
+
+ embedded
+
+
+ embedded
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/StreamDeckSimHub.Installer/Tools/Configuration.cs b/StreamDeckSimHub.Installer/Tools/Configuration.cs
new file mode 100644
index 0000000..4afce59
--- /dev/null
+++ b/StreamDeckSimHub.Installer/Tools/Configuration.cs
@@ -0,0 +1,29 @@
+// Copyright (C) 2024 Martin Renner
+// LGPL-3.0-or-later (see file COPYING and COPYING.LESSER)
+
+using System.IO;
+
+namespace StreamDeckSimHub.Installer.Tools;
+
+public static class Configuration
+{
+ public const string StreamDeckProcessName = "StreamDeck";
+ public const string PluginProcessName = "StreamDeckSimHub";
+
+ public const string StreamDeckRegistryFolder = @"HKEY_CURRENT_USER\SOFTWARE\Elgato Systems GmbH\StreamDeck";
+ public const string StreamDeckRegistryInstallFolder = "InstallDir";
+ public static readonly string StreamDeckDefaultInstallFolder = Path.Combine("C:", "Program Files", "Elgato", "StreamDeck");
+
+ public static readonly string AppDataRoaming = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
+ public static readonly string StreamDeckPluginDir = Path.Combine(AppDataRoaming, "Elgato", "StreamDeck", "Plugins");
+
+ public const string PluginDirName = "net.planetrenner.simhub.sdPlugin";
+ public const string PluginZipName = "net.planetrenner.simhub.streamDeckPlugin";
+
+ public const string SimHubRegistryFolder = @"HKEY_CURRENT_USER\Software\SimHub";
+ public const string SimHubRegistryInstallFolder = "InstallDirectory";
+ public static readonly string SimHubDefaultInstallFolder = Path.Combine("C:", "Program Files (x86)", "SimHub");
+
+ public const string SimHubPluginUrl = "https://github.com/pre-martin/SimHubPropertyServer";
+ public static readonly Version RequiredSimHubPluginVersion = new(1, 9, 6);
+}
diff --git a/StreamDeckSimHub.Installer/Tools/ProcessTools.cs b/StreamDeckSimHub.Installer/Tools/ProcessTools.cs
new file mode 100644
index 0000000..b8d7302
--- /dev/null
+++ b/StreamDeckSimHub.Installer/Tools/ProcessTools.cs
@@ -0,0 +1,38 @@
+// Copyright (C) 2024 Martin Renner
+// LGPL-3.0-or-later (see file COPYING and COPYING.LESSER)
+
+using System.Diagnostics;
+using System.IO;
+
+namespace StreamDeckSimHub.Installer.Tools;
+
+public static class ProcessTools
+{
+ ///
+ /// Is a given process running?
+ ///
+ public static bool IsProcessRunning(string processName)
+ {
+ return GetProcess(processName) != null;
+ }
+
+ ///
+ /// Simple wrapper for Process.GetProcessesByName().
+ ///
+ public static Process? GetProcess(string processName)
+ {
+ return Process.GetProcessesByName(processName).FirstOrDefault();
+ }
+
+ ///
+ /// Starts a new process.
+ ///
+ public static void StartProcess(string fileName, string? workingDirectory = null)
+ {
+ var process = new Process();
+ process.StartInfo.FileName = fileName;
+ process.StartInfo.WorkingDirectory = workingDirectory ?? Directory.GetCurrentDirectory();
+ process.StartInfo.UseShellExecute = true;
+ process.Start();
+ }
+}
\ No newline at end of file
diff --git a/StreamDeckSimHub.Plugin/Bundle.ps1 b/StreamDeckSimHub.Plugin/Bundle.ps1
new file mode 100644
index 0000000..2fbf48c
--- /dev/null
+++ b/StreamDeckSimHub.Plugin/Bundle.ps1
@@ -0,0 +1,19 @@
+# Copyright (C) 2024 Martin Renner
+# LGPL-3.0-or-later (see file COPYING and COPYING.LESSER)
+
+
+if ($Args.Count -lt 1) {
+ throw 'Arguments are missing'
+}
+
+$PublishDir = $Args[0]
+
+Remove-Item "..\build\*" -Recurse
+
+Copy-Item "$PublishDir" -Destination "..\build" -Recurse
+Pushd ..\build
+Rename-Item -Path "publish" -NewName "net.planetrenner.simhub.sdPlugin"
+
+..\..\DistributionTool.exe -b -i net.planetrenner.simhub.sdPlugin -o .
+
+Popd
diff --git a/StreamDeckSimHub.Plugin/ReplaceVersion.ps1 b/StreamDeckSimHub.Plugin/ReplaceVersion.ps1
index 0bc6740..259a637 100644
--- a/StreamDeckSimHub.Plugin/ReplaceVersion.ps1
+++ b/StreamDeckSimHub.Plugin/ReplaceVersion.ps1
@@ -15,6 +15,18 @@ if ($Args.Count -lt 2) {
$ManifestFile = $Args[0]
$Version = $Args[1]
-$manifest = (Get-Content($ManifestFile) | ConvertFrom-Json)
-$manifest.Version = $Version
-$manifest | ConvertTo-Json -depth 100 | Out-File -Encoding utf8 $ManifestFile
+Write-Host "Setting version $Version in file $ManifestFile"
+
+try {
+ $manifest = (Get-Content($ManifestFile) | ConvertFrom-Json)
+ $manifest.Version = $Version
+ $manifest | ConvertTo-Json -depth 100 | Out-File -Encoding utf8 ($ManifestFile + ".new")
+
+ Move-Item ($ManifestFile + ".new") -Destination $ManifestFile -Force
+ Write-Host "Done"
+}
+catch {
+ Write-Host "An error occured:"
+ Write-Host $_
+ Exit 1
+}
diff --git a/StreamDeckSimHub.Plugin/StreamDeckSimHub.Plugin.csproj b/StreamDeckSimHub.Plugin/StreamDeckSimHub.Plugin.csproj
index aa9499c..49ced41 100644
--- a/StreamDeckSimHub.Plugin/StreamDeckSimHub.Plugin.csproj
+++ b/StreamDeckSimHub.Plugin/StreamDeckSimHub.Plugin.csproj
@@ -5,7 +5,6 @@
net8.0
true
false
- win-x64
enable
enable
StreamDeckSimHub
@@ -35,13 +34,15 @@
-
+
powershell.exe
pwsh
+
+
diff --git a/StreamDeckSimHub.sln b/StreamDeckSimHub.sln
index ed63efd..2b423dd 100644
--- a/StreamDeckSimHub.sln
+++ b/StreamDeckSimHub.sln
@@ -8,12 +8,13 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionFiles", "SolutionFiles", "{E3F720A4-BDC4-4B7D-B604-BF323E7AB170}"
ProjectSection(SolutionItems) = preProject
README.adoc = README.adoc
- release.bat = release.bat
deploy.bat = deploy.bat
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StreamDeckSimHub.PluginTests", "StreamDeckSimHub.PluginTests\StreamDeckSimHub.PluginTests.csproj", "{D36BDE59-5F66-4311-9105-ABE072AFED82}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StreamDeckSimHub.Installer", "StreamDeckSimHub.Installer\StreamDeckSimHub.Installer.csproj", "{1F5DC759-9721-48BE-AD84-CE582C405C59}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -28,6 +29,10 @@ Global
{D36BDE59-5F66-4311-9105-ABE072AFED82}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D36BDE59-5F66-4311-9105-ABE072AFED82}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D36BDE59-5F66-4311-9105-ABE072AFED82}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1F5DC759-9721-48BE-AD84-CE582C405C59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1F5DC759-9721-48BE-AD84-CE582C405C59}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1F5DC759-9721-48BE-AD84-CE582C405C59}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1F5DC759-9721-48BE-AD84-CE582C405C59}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/doc/Release.adoc b/doc/Release.adoc
index a987ec1..ec67491 100644
--- a/doc/Release.adoc
+++ b/doc/Release.adoc
@@ -1,14 +1,17 @@
= Release Process
-. If there is no release branch yet for the current version: +
+. If there is no release branch yet for the current version: +
`nbgv prepare-release`
-. Switch to the release branch: +
+. Switch to the release branch: +
`git switch release/v1.2`
. Push the release branch
-. Create a tag and push it afterwards:
+. Create a tag and push it afterward:
- `nbgv tag`
- `git push origin v1.2`
. Build the plugin:
- - `release.bat`
-. Create a release in GitHub from the tag and attach the file `net.planetrenner.simhub.streamDeckPlugin`
+ - `dotnet build StreamDeckSimHub.Plugin\StreamDeckSimHub.Plugin.csproj -c Release`
+ - `dotnet publish StreamDeckSimHub.Plugin\StreamDeckSimHub.Plugin.csproj -c Release`
+. Build the installer:
+ - `dotnet publish StreamDeckSimHub.Installer\StreamDeckSimHub.Installer.csproj -c Release`
+. Create a release in GitHub from the tag and attach the file `StreamDeckSimHub.Installer-vX.Y.z.exe`
. Push the main branch.
diff --git a/release.bat b/release.bat
deleted file mode 100644
index 2ff5183..0000000
--- a/release.bat
+++ /dev/null
@@ -1,28 +0,0 @@
-@echo off
-setlocal
-
-set CONFIG=Release
-if "%1%" == "debug" set CONFIG=Debug
-
-echo.
-echo Building for configuration: %CONFIG%
-echo.
-
-dotnet publish StreamDeckSimHub.Plugin\StreamDeckSimHub.Plugin.csproj -c %CONFIG% -r win-x64
-if %errorlevel% neq 0 goto :endFailure
-
-if exist build rmdir /s /q build
-if exist build goto :endFailure
-mkdir build
-xcopy StreamDeckSimHub.Plugin\bin\%CONFIG%\net8.0\win-x64\publish build\net.planetrenner.simhub.sdPlugin /e /i /q
-
-cd build
-..\..\DistributionTool.exe -b -i net.planetrenner.simhub.sdPlugin -o .
-rem 7z.exe a -bd net.planetrenner.simhub.streamDeckPlugin.zip
-rem ren net.planetrenner.simhub.streamDeckPlugin.zip net.planetrenner.simhub.streamDeckPlugin
-goto end
-
-:endFailure
-exit /b 1
-
-:end