1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Plugins - Added RequiresAdmin boolean

Utilities - Simplified shutdown method signature
Utilities - Added restart method with option to elevate
Core - Moved actual shutdown/restart logic to UI
This commit is contained in:
Robert 2021-01-21 19:25:46 +01:00
parent 281c18200a
commit de5b8e4458
12 changed files with 151 additions and 39 deletions

View File

@ -0,0 +1,26 @@
using System;
namespace Artemis.Core
{
/// <summary>
/// Provides data about application restart events
/// </summary>
public class RestartEventArgs : EventArgs
{
internal RestartEventArgs(bool elevate, TimeSpan delay)
{
Elevate = elevate;
Delay = delay;
}
/// <summary>
/// Gets a boolean indicating whether the application should be restarted with elevated permissions
/// </summary>
public bool Elevate { get; }
/// <summary>
/// Gets the delay before killing process and restarting
/// </summary>
public TimeSpan Delay { get; }
}
}

View File

@ -17,6 +17,7 @@ namespace Artemis.Core
private string _main = null!; private string _main = null!;
private string _name = null!; private string _name = null!;
private Plugin _plugin = null!; private Plugin _plugin = null!;
private bool _requiresAdmin;
private Version _version = null!; private Version _version = null!;
internal PluginInfo() internal PluginInfo()
@ -86,7 +87,8 @@ namespace Artemis.Core
} }
/// <summary> /// <summary>
/// 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
/// </summary> /// </summary>
[DefaultValue(true)] [DefaultValue(true)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
@ -96,6 +98,16 @@ namespace Artemis.Core
set => SetAndNotify(ref _autoEnableFeatures, value); set => SetAndNotify(ref _autoEnableFeatures, value);
} }
/// <summary>
/// Gets a boolean indicating whether this plugin requires elevated admin privileges
/// </summary>
[JsonProperty]
public bool RequiresAdmin
{
get => _requiresAdmin;
internal set => SetAndNotify(ref _requiresAdmin, value);
}
/// <summary> /// <summary>
/// Gets the plugin this info is associated with /// Gets the plugin this info is associated with
/// </summary> /// </summary>

View File

@ -78,6 +78,7 @@ namespace Artemis.Core.Services
public TimeSpan FrameTime { get; private set; } public TimeSpan FrameTime { get; private set; }
public bool ModuleRenderingDisabled { get; set; } public bool ModuleRenderingDisabled { get; set; }
public List<string>? StartupArguments { get; set; } public List<string>? StartupArguments { get; set; }
public bool IsElevated { get; set; }
public void Dispose() public void Dispose()
{ {
@ -93,18 +94,27 @@ namespace Artemis.Core.Services
throw new ArtemisCoreException("Cannot initialize the core as it is already initialized."); throw new ArtemisCoreException("Cannot initialize the core as it is already initialized.");
AssemblyInformationalVersionAttribute? versionAttribute = typeof(CoreService).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>(); AssemblyInformationalVersionAttribute? versionAttribute = typeof(CoreService).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
_logger.Information("Initializing Artemis Core version {version}, build {buildNumber} branch {branch}.", versionAttribute?.InformationalVersion, Constants.BuildInfo.BuildNumber, _logger.Information(
Constants.BuildInfo.SourceBranch); "Initializing Artemis Core version {version}, build {buildNumber} branch {branch}.",
// This should prevent a certain someone from removing HidSharp as an unused dependency as well versionAttribute?.InformationalVersion,
_logger.Information("Forcing plugins to use HidSharp {hidSharpVersion}", Assembly.GetAssembly(typeof(HidDevice))!.GetName().Version); Constants.BuildInfo.BuildNumber,
Constants.BuildInfo.SourceBranch
);
_logger.Information("Startup arguments: {args}", StartupArguments);
_logger.Information("Elevated permissions: {perms}", IsElevated);
ApplyLoggingLevel(); 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); DeserializationLogger.Initialize(Kernel);
// Initialize the services // Initialize the services
_pluginManagementService.CopyBuiltInPlugins(); _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; ArtemisSurface surfaceConfig = _surfaceService.ActiveSurface;
_logger.Information("Initialized with active surface entity {surfaceConfig}-{guid}", surfaceConfig.Name, surfaceConfig.EntityId); _logger.Information("Initialized with active surface entity {surfaceConfig}-{guid}", surfaceConfig.Name, surfaceConfig.EntityId);

View File

@ -28,6 +28,11 @@ namespace Artemis.Core.Services
/// </summary> /// </summary>
List<string>? StartupArguments { get; set; } List<string>? StartupArguments { get; set; }
/// <summary>
/// Gets a boolean indicating whether Artemis is running in an elevated environment (admin permissions)
/// </summary>
bool IsElevated { get; set; }
/// <summary> /// <summary>
/// Initializes the core, only call once /// Initializes the core, only call once
/// </summary> /// </summary>

View File

@ -26,7 +26,7 @@ namespace Artemis.Core.Services
/// <summary> /// <summary>
/// Loads all installed plugins. If plugins already loaded this will reload them all /// Loads all installed plugins. If plugins already loaded this will reload them all
/// </summary> /// </summary>
void LoadPlugins(bool ignorePluginLock); void LoadPlugins(bool ignorePluginLock, bool isElevated);
/// <summary> /// <summary>
/// Unloads all installed plugins. /// Unloads all installed plugins.

View File

@ -172,7 +172,7 @@ namespace Artemis.Core.Services
#region Plugins #region Plugins
public void LoadPlugins(bool ignorePluginLock) public void LoadPlugins(bool ignorePluginLock, bool isElevated)
{ {
if (LoadingPlugins) if (LoadingPlugins)
throw new ArtemisCoreException("Cannot load plugins while a previous load hasn't been completed yet."); throw new ArtemisCoreException("Cannot load plugins while a previous load hasn't been completed yet.");
@ -188,9 +188,7 @@ namespace Artemis.Core.Services
{ {
try try
{ {
Plugin plugin = LoadPlugin(subDirectory); LoadPlugin(subDirectory);
if (plugin.Entity.IsEnabled)
EnablePlugin(plugin, false, ignorePluginLock);
} }
catch (Exception e) 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; LoadingPlugins = false;
} }
@ -217,7 +234,7 @@ namespace Artemis.Core.Services
public Plugin LoadPlugin(DirectoryInfo directory) public Plugin LoadPlugin(DirectoryInfo directory)
{ {
_logger.Debug("Loading plugin from {directory}", directory.FullName); _logger.Verbose("Loading plugin from {directory}", directory.FullName);
// Load the metadata // Load the metadata
string metadataFile = Path.Combine(directory.FullName, "plugin.json"); 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) 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)); OnPluginFeatureEnabling(new PluginFeatureEventArgs(pluginFeature));
try try
@ -443,7 +459,7 @@ namespace Artemis.Core.Services
if (pluginFeature.IsEnabled) 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)); OnPluginFeatureEnabled(new PluginFeatureEventArgs(pluginFeature));
} }
else else
@ -457,7 +473,7 @@ namespace Artemis.Core.Services
{ {
try try
{ {
_logger.Debug("Disabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin); _logger.Verbose("Disabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin);
pluginFeature.SetEnabled(false); pluginFeature.SetEnabled(false);
} }
finally finally
@ -470,7 +486,7 @@ namespace Artemis.Core.Services
if (!pluginFeature.IsEnabled) 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)); OnPluginFeatureDisabled(new PluginFeatureEventArgs(pluginFeature));
} }
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl; using System.Security.AccessControl;
using System.Security.Principal; using System.Security.Principal;
@ -27,31 +28,22 @@ namespace Artemis.Core
/// gracefully shut down /// gracefully shut down
/// </para> /// </para>
/// </summary> /// </summary>
/// <param name="delay">The delay in seconds after which to kill the application (ignored when a debugger is attached)</param> public static void Shutdown()
/// <param name="restart">Whether or not to restart the application after shutdown (ignored when a debugger is attached)</param>
public static void Shutdown(int delay, bool restart)
{ {
// 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 // Request a graceful shutdown, whatever UI we're running can pick this up
OnShutdownRequested(); OnShutdownRequested();
} }
/// <summary>
/// Restarts the application
/// </summary>
/// <param name="elevate">Whether the application should be restarted with elevated permissions</param>
/// <param name="delay">Delay in seconds before killing process and restarting </param>
public static void Restart(bool elevate, TimeSpan delay)
{
OnRestartRequested(new RestartEventArgs(elevate, delay));
}
/// <summary> /// <summary>
/// Opens the provided URL in the default web browser /// Opens the provided URL in the default web browser
/// </summary> /// </summary>
@ -105,11 +97,21 @@ namespace Artemis.Core
/// </summary> /// </summary>
public static event EventHandler? ShutdownRequested; public static event EventHandler? ShutdownRequested;
/// <summary>
/// Occurs when the core has requested an application restart
/// </summary>
public static event EventHandler<RestartEventArgs>? RestartRequested;
private static void OnShutdownRequested() private static void OnShutdownRequested()
{ {
ShutdownRequested?.Invoke(null, EventArgs.Empty); ShutdownRequested?.Invoke(null, EventArgs.Empty);
} }
#endregion #endregion
private static void OnRestartRequested(RestartEventArgs e)
{
RestartRequested?.Invoke(null, e);
}
} }
} }

View File

@ -1,11 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Markup; using System.Windows.Markup;
using System.Windows.Threading; using System.Windows.Threading;
using Artemis.Core;
using Artemis.Core.Ninject; using Artemis.Core.Ninject;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Ninject; using Artemis.UI.Ninject;
@ -37,6 +40,7 @@ namespace Artemis.UI
protected override void Launch() protected override void Launch()
{ {
Core.Utilities.ShutdownRequested += UtilitiesOnShutdownRequested; Core.Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
Core.Utilities.RestartRequested += UtilitiesOnRestartRequested;
Core.Utilities.PrepareFirstLaunch(); Core.Utilities.PrepareFirstLaunch();
ILogger logger = Kernel.Get<ILogger>(); ILogger logger = Kernel.Get<ILogger>();
@ -77,6 +81,7 @@ namespace Artemis.UI
} }
_core.StartupArguments = StartupArguments; _core.StartupArguments = StartupArguments;
_core.IsElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
_core.Initialize(); _core.Initialize();
} }
catch (Exception e) catch (Exception e)
@ -124,6 +129,30 @@ namespace Artemis.UI
private void UtilitiesOnShutdownRequested(object sender, EventArgs e) 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()); Execute.OnUIThread(() => Application.Current.Shutdown());
} }

View File

@ -40,6 +40,8 @@
</StackPanel> </StackPanel>
</Button> </Button>
<Button Content="Force garbage collection" Command="{s:Action ForceGarbageCollection}" /> <Button Content="Force garbage collection" Command="{s:Action ForceGarbageCollection}" />
<Button Content="Elevate permissions" Command="{s:Action Elevate}" />
<Button Content="Restart" Command="{s:Action Restart}" />
</StackPanel> </StackPanel>
</materialDesign:PopupBox> </materialDesign:PopupBox>
</mde:AppBar> </mde:AppBar>

View File

@ -43,5 +43,15 @@ namespace Artemis.UI.Screens.Settings.Debug
GC.WaitForPendingFinalizers(); GC.WaitForPendingFinalizers();
GC.Collect(); GC.Collect();
} }
public void Elevate()
{
Core.Utilities.Restart(true, TimeSpan.FromMilliseconds(500));
}
public void Restart()
{
Core.Utilities.Restart(false, TimeSpan.FromMilliseconds(500));
}
} }
} }

View File

@ -95,7 +95,7 @@ namespace Artemis.UI.Screens
public void TrayExit() public void TrayExit()
{ {
Core.Utilities.Shutdown(2, false); Core.Utilities.Shutdown();
} }
public void TrayOpenDebugger() public void TrayOpenDebugger()