diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs
index 408ce0d50..effc348c2 100644
--- a/src/Artemis.Core/Constants.cs
+++ b/src/Artemis.Core/Constants.cs
@@ -108,6 +108,6 @@ namespace Artemis.Core
typeof(float),
typeof(double),
typeof(decimal)
- };
+ };
}
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Events/RestartEventArgs.cs b/src/Artemis.Core/Events/RestartEventArgs.cs
new file mode 100644
index 000000000..6df873143
--- /dev/null
+++ b/src/Artemis.Core/Events/RestartEventArgs.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace Artemis.Core
+{
+ ///
+ /// Provides data about application restart events
+ ///
+ public class RestartEventArgs : EventArgs
+ {
+ internal RestartEventArgs(bool elevate, TimeSpan delay)
+ {
+ Elevate = elevate;
+ Delay = delay;
+ }
+
+ ///
+ /// Gets a boolean indicating whether the application should be restarted with elevated permissions
+ ///
+ public bool Elevate { get; }
+
+ ///
+ /// Gets the delay before killing process and restarting
+ ///
+ public TimeSpan Delay { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/PluginInfo.cs b/src/Artemis.Core/Plugins/PluginInfo.cs
index 831fd4015..fcbeacd07 100644
--- a/src/Artemis.Core/Plugins/PluginInfo.cs
+++ b/src/Artemis.Core/Plugins/PluginInfo.cs
@@ -17,6 +17,7 @@ namespace Artemis.Core
private string _main = null!;
private string _name = null!;
private Plugin _plugin = null!;
+ private bool _requiresAdmin;
private Version _version = null!;
internal PluginInfo()
@@ -86,7 +87,8 @@ namespace Artemis.Core
}
///
- /// Gets or sets a boolean indicating whether this plugin should automatically enable all its features when it is first loaded
+ /// Gets or sets a boolean indicating whether this plugin should automatically enable all its features when it is first
+ /// loaded
///
[DefaultValue(true)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
@@ -96,6 +98,16 @@ namespace Artemis.Core
set => SetAndNotify(ref _autoEnableFeatures, value);
}
+ ///
+ /// Gets a boolean indicating whether this plugin requires elevated admin privileges
+ ///
+ [JsonProperty]
+ public bool RequiresAdmin
+ {
+ get => _requiresAdmin;
+ internal set => SetAndNotify(ref _requiresAdmin, value);
+ }
+
///
/// Gets the plugin this info is associated with
///
diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs
index 55674dd4c..4177b475a 100644
--- a/src/Artemis.Core/Services/CoreService.cs
+++ b/src/Artemis.Core/Services/CoreService.cs
@@ -78,6 +78,7 @@ namespace Artemis.Core.Services
public TimeSpan FrameTime { get; private set; }
public bool ModuleRenderingDisabled { get; set; }
public List? StartupArguments { get; set; }
+ public bool IsElevated { get; set; }
public void Dispose()
{
@@ -93,18 +94,27 @@ namespace Artemis.Core.Services
throw new ArtemisCoreException("Cannot initialize the core as it is already initialized.");
AssemblyInformationalVersionAttribute? versionAttribute = typeof(CoreService).Assembly.GetCustomAttribute();
- _logger.Information("Initializing Artemis Core version {version}, build {buildNumber} branch {branch}.", versionAttribute?.InformationalVersion, Constants.BuildInfo.BuildNumber,
- Constants.BuildInfo.SourceBranch);
- // This should prevent a certain someone from removing HidSharp as an unused dependency as well
- _logger.Information("Forcing plugins to use HidSharp {hidSharpVersion}", Assembly.GetAssembly(typeof(HidDevice))!.GetName().Version);
+ _logger.Information(
+ "Initializing Artemis Core version {version}, build {buildNumber} branch {branch}.",
+ versionAttribute?.InformationalVersion,
+ Constants.BuildInfo.BuildNumber,
+ Constants.BuildInfo.SourceBranch
+ );
+ _logger.Information("Startup arguments: {args}", StartupArguments);
+ _logger.Information("Elevated permissions: {perms}", IsElevated);
ApplyLoggingLevel();
+ // Don't remove even if it looks useless
+ // Just this line should prevent a certain someone from removing HidSharp as an unused dependency as well
+ Version? hidSharpVersion = Assembly.GetAssembly(typeof(HidDevice))!.GetName().Version;
+ _logger.Debug("Forcing plugins to use HidSharp {hidSharpVersion}", hidSharpVersion);
+
DeserializationLogger.Initialize(Kernel);
// Initialize the services
_pluginManagementService.CopyBuiltInPlugins();
- _pluginManagementService.LoadPlugins(StartupArguments != null && StartupArguments.Contains("--ignore-plugin-lock"));
+ _pluginManagementService.LoadPlugins(StartupArguments != null && StartupArguments.Contains("--ignore-plugin-lock"), IsElevated);
ArtemisSurface surfaceConfig = _surfaceService.ActiveSurface;
_logger.Information("Initialized with active surface entity {surfaceConfig}-{guid}", surfaceConfig.Name, surfaceConfig.EntityId);
diff --git a/src/Artemis.Core/Services/Interfaces/ICoreService.cs b/src/Artemis.Core/Services/Interfaces/ICoreService.cs
index cb658c2c3..6e13fba3b 100644
--- a/src/Artemis.Core/Services/Interfaces/ICoreService.cs
+++ b/src/Artemis.Core/Services/Interfaces/ICoreService.cs
@@ -28,6 +28,11 @@ namespace Artemis.Core.Services
///
List? StartupArguments { get; set; }
+ ///
+ /// Gets a boolean indicating whether Artemis is running in an elevated environment (admin permissions)
+ ///
+ bool IsElevated { get; set; }
+
///
/// Initializes the core, only call once
///
diff --git a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs
index 8dec2854b..170f87771 100644
--- a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs
+++ b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs
@@ -26,7 +26,7 @@ namespace Artemis.Core.Services
///
/// Loads all installed plugins. If plugins already loaded this will reload them all
///
- void LoadPlugins(bool ignorePluginLock);
+ void LoadPlugins(bool ignorePluginLock, bool isElevated);
///
/// Unloads all installed plugins.
diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs
index cdf0f41df..bf18f1dde 100644
--- a/src/Artemis.Core/Services/PluginManagementService.cs
+++ b/src/Artemis.Core/Services/PluginManagementService.cs
@@ -172,7 +172,7 @@ namespace Artemis.Core.Services
#region Plugins
- public void LoadPlugins(bool ignorePluginLock)
+ public void LoadPlugins(bool ignorePluginLock, bool isElevated)
{
if (LoadingPlugins)
throw new ArtemisCoreException("Cannot load plugins while a previous load hasn't been completed yet.");
@@ -188,9 +188,7 @@ namespace Artemis.Core.Services
{
try
{
- Plugin plugin = LoadPlugin(subDirectory);
- if (plugin.Entity.IsEnabled)
- EnablePlugin(plugin, false, ignorePluginLock);
+ LoadPlugin(subDirectory);
}
catch (Exception e)
{
@@ -198,6 +196,25 @@ namespace Artemis.Core.Services
}
}
+ lock (_plugins)
+ {
+ _logger.Debug("Loaded {count} plugin(s)", _plugins.Count);
+
+ bool mustElevate = !isElevated && _plugins.Any(p => p.Entity.IsEnabled && p.Info.RequiresAdmin);
+ if (mustElevate)
+ {
+ _logger.Information("Restarting because one or more plugins requires elevation");
+ // No need for a delay this early on, nothing that needs graceful shutdown is happening yet
+ Utilities.Restart(true, TimeSpan.Zero);
+ return;
+ }
+
+ foreach (Plugin plugin in _plugins.Where(p => p.Entity.IsEnabled))
+ EnablePlugin(plugin, false, ignorePluginLock);
+
+ _logger.Debug("Enabled {count} plugin(s)", _plugins.Where(p => p.IsEnabled).Sum(p => p.Features.Count(f => f.IsEnabled)));
+ }
+
LoadingPlugins = false;
}
@@ -217,7 +234,7 @@ namespace Artemis.Core.Services
public Plugin LoadPlugin(DirectoryInfo directory)
{
- _logger.Debug("Loading plugin from {directory}", directory.FullName);
+ _logger.Verbose("Loading plugin from {directory}", directory.FullName);
// Load the metadata
string metadataFile = Path.Combine(directory.FullName, "plugin.json");
@@ -411,8 +428,7 @@ namespace Artemis.Core.Services
public void EnablePluginFeature(PluginFeature pluginFeature, bool saveState, bool isAutoEnable)
{
- _logger.Debug("Enabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin);
-
+ _logger.Verbose("Enabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin);
OnPluginFeatureEnabling(new PluginFeatureEventArgs(pluginFeature));
try
@@ -443,7 +459,7 @@ namespace Artemis.Core.Services
if (pluginFeature.IsEnabled)
{
- _logger.Debug("Successfully enabled plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin);
+ _logger.Verbose("Successfully enabled plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin);
OnPluginFeatureEnabled(new PluginFeatureEventArgs(pluginFeature));
}
else
@@ -457,7 +473,7 @@ namespace Artemis.Core.Services
{
try
{
- _logger.Debug("Disabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin);
+ _logger.Verbose("Disabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin);
pluginFeature.SetEnabled(false);
}
finally
@@ -470,7 +486,7 @@ namespace Artemis.Core.Services
if (!pluginFeature.IsEnabled)
{
- _logger.Debug("Successfully disabled plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin);
+ _logger.Verbose("Successfully disabled plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin);
OnPluginFeatureDisabled(new PluginFeatureEventArgs(pluginFeature));
}
}
diff --git a/src/Artemis.Core/Utilities/Utilities.cs b/src/Artemis.Core/Utilities/Utilities.cs
index 39cfc9339..47861c970 100644
--- a/src/Artemis.Core/Utilities/Utilities.cs
+++ b/src/Artemis.Core/Utilities/Utilities.cs
@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.IO;
+using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
@@ -27,31 +28,22 @@ namespace Artemis.Core
/// gracefully shut down
///
///
- /// The delay in seconds after which to kill the application (ignored when a debugger is attached)
- /// Whether or not to restart the application after shutdown (ignored when a debugger is attached)
- public static void Shutdown(int delay, bool restart)
+ public static void Shutdown()
{
- // Always kill the process after the delay has passed, with all the plugins a graceful shutdown cannot be guaranteed
- string arguments = "-Command \"& {Start-Sleep -s " + delay + "; (Get-Process 'Artemis.UI').kill()}";
- // If restart is required, start the executable again after the process was killed
- if (restart)
- arguments = "-Command \"& {Start-Sleep -s " + delay + "; (Get-Process 'Artemis.UI').kill(); Start-Process -FilePath '" + Process.GetCurrentProcess().MainModule!.FileName + "'}\"";
-
- ProcessStartInfo info = new()
- {
- Arguments = arguments,
- WindowStyle = ProcessWindowStyle.Hidden,
- CreateNoWindow = true,
- FileName = "PowerShell.exe"
- };
-
- if (!Debugger.IsAttached)
- Process.Start(info);
-
// Request a graceful shutdown, whatever UI we're running can pick this up
OnShutdownRequested();
}
+ ///
+ /// Restarts the application
+ ///
+ /// Whether the application should be restarted with elevated permissions
+ /// Delay in seconds before killing process and restarting
+ public static void Restart(bool elevate, TimeSpan delay)
+ {
+ OnRestartRequested(new RestartEventArgs(elevate, delay));
+ }
+
///
/// Opens the provided URL in the default web browser
///
@@ -105,11 +97,21 @@ namespace Artemis.Core
///
public static event EventHandler? ShutdownRequested;
+ ///
+ /// Occurs when the core has requested an application restart
+ ///
+ public static event EventHandler? RestartRequested;
+
private static void OnShutdownRequested()
{
ShutdownRequested?.Invoke(null, EventArgs.Empty);
}
#endregion
+
+ private static void OnRestartRequested(RestartEventArgs e)
+ {
+ RestartRequested?.Invoke(null, e);
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Bootstrapper.cs b/src/Artemis.UI/Bootstrapper.cs
index 51af5da78..400ded1a6 100644
--- a/src/Artemis.UI/Bootstrapper.cs
+++ b/src/Artemis.UI/Bootstrapper.cs
@@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Globalization;
using System.Linq;
+using System.Security.Principal;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Threading;
+using Artemis.Core;
using Artemis.Core.Ninject;
using Artemis.Core.Services;
using Artemis.UI.Ninject;
@@ -37,6 +40,7 @@ namespace Artemis.UI
protected override void Launch()
{
Core.Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
+ Core.Utilities.RestartRequested += UtilitiesOnRestartRequested;
Core.Utilities.PrepareFirstLaunch();
ILogger logger = Kernel.Get();
@@ -77,6 +81,7 @@ namespace Artemis.UI
}
_core.StartupArguments = StartupArguments;
+ _core.IsElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
_core.Initialize();
}
catch (Exception e)
@@ -124,6 +129,30 @@ namespace Artemis.UI
private void UtilitiesOnShutdownRequested(object sender, EventArgs e)
{
+ // Use PowerShell to kill the process after 2 sec just in case
+ ProcessStartInfo info = new()
+ {
+ Arguments = "-Command \"& {Start-Sleep -s 2; (Get-Process 'Artemis.UI').kill()}",
+ WindowStyle = ProcessWindowStyle.Hidden,
+ CreateNoWindow = true,
+ FileName = "PowerShell.exe"
+ };
+ Process.Start(info);
+
+ Execute.OnUIThread(() => Application.Current.Shutdown());
+ }
+
+ private void UtilitiesOnRestartRequested(object sender, RestartEventArgs e)
+ {
+ ProcessStartInfo info = new()
+ {
+ Arguments =
+ $"-Command \"& {{Start-Sleep -Milliseconds {(int) e.Delay.TotalMilliseconds}; (Get-Process 'Artemis.UI').kill(); Start-Process -FilePath '{Constants.ExecutablePath}' {(e.Elevate ? "-Verb RunAs" : "")}}}\"",
+ WindowStyle = ProcessWindowStyle.Hidden,
+ CreateNoWindow = true,
+ FileName = "PowerShell.exe"
+ };
+ Process.Start(info);
Execute.OnUIThread(() => Application.Current.Shutdown());
}
diff --git a/src/Artemis.UI/Screens/Settings/Debug/DebugView.xaml b/src/Artemis.UI/Screens/Settings/Debug/DebugView.xaml
index fbe220eb2..46eb707f5 100644
--- a/src/Artemis.UI/Screens/Settings/Debug/DebugView.xaml
+++ b/src/Artemis.UI/Screens/Settings/Debug/DebugView.xaml
@@ -40,6 +40,8 @@
+
+
diff --git a/src/Artemis.UI/Screens/Settings/Debug/DebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/DebugViewModel.cs
index ed89a25b3..86109e851 100644
--- a/src/Artemis.UI/Screens/Settings/Debug/DebugViewModel.cs
+++ b/src/Artemis.UI/Screens/Settings/Debug/DebugViewModel.cs
@@ -43,5 +43,15 @@ namespace Artemis.UI.Screens.Settings.Debug
GC.WaitForPendingFinalizers();
GC.Collect();
}
+
+ public void Elevate()
+ {
+ Core.Utilities.Restart(true, TimeSpan.FromMilliseconds(500));
+ }
+
+ public void Restart()
+ {
+ Core.Utilities.Restart(false, TimeSpan.FromMilliseconds(500));
+ }
}
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/TrayViewModel.cs b/src/Artemis.UI/Screens/TrayViewModel.cs
index 65a409439..302bae074 100644
--- a/src/Artemis.UI/Screens/TrayViewModel.cs
+++ b/src/Artemis.UI/Screens/TrayViewModel.cs
@@ -95,7 +95,7 @@ namespace Artemis.UI.Screens
public void TrayExit()
{
- Core.Utilities.Shutdown(2, false);
+ Core.Utilities.Shutdown();
}
public void TrayOpenDebugger()