1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-31 01:42:02 +00:00

Plugin settings - Restart Artemis for plugins that require admin rights

Setup wizard - Restart Artemis device providers that require admin rights
Setup wizard - Gracefully handle errors during device provider enable
Profile editor - Select a valid default brush on clean installs
This commit is contained in:
Robert 2021-03-11 20:27:54 +01:00
parent b917f36978
commit 985a94dbaf
16 changed files with 363 additions and 151 deletions

View File

@ -71,7 +71,7 @@ namespace Artemis.Core.Services
Plugin ImportPlugin(string fileName); Plugin ImportPlugin(string fileName);
/// <summary> /// <summary>
/// Unloads and permanently removes the provided plugin /// Unloads and permanently removes the provided plugin
/// </summary> /// </summary>
/// <param name="plugin">The plugin to remove</param> /// <param name="plugin">The plugin to remove</param>
void RemovePlugin(Plugin plugin); void RemovePlugin(Plugin plugin);
@ -127,6 +127,13 @@ namespace Artemis.Core.Services
/// <returns></returns> /// <returns></returns>
DeviceProvider GetDeviceProviderByDevice(IRGBDevice device); DeviceProvider GetDeviceProviderByDevice(IRGBDevice device);
/// <summary>
/// Queues an action for the provided plugin for the next time Artemis starts, before plugins are loaded
/// </summary>
/// <param name="plugin">The plugin to queue the action for</param>
/// <param name="pluginAction">The action to take</param>
void QueuePluginAction(Plugin plugin, PluginManagementAction pluginAction);
#region Events #region Events
/// <summary> /// <summary>

View File

@ -27,6 +27,7 @@ namespace Artemis.Core.Services
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IPluginRepository _pluginRepository; private readonly IPluginRepository _pluginRepository;
private readonly List<Plugin> _plugins; private readonly List<Plugin> _plugins;
private bool _isElevated;
public PluginManagementService(IKernel kernel, ILogger logger, IPluginRepository pluginRepository) public PluginManagementService(IKernel kernel, ILogger logger, IPluginRepository pluginRepository)
{ {
@ -34,6 +35,8 @@ namespace Artemis.Core.Services
_logger = logger; _logger = logger;
_pluginRepository = pluginRepository; _pluginRepository = pluginRepository;
_plugins = new List<Plugin>(); _plugins = new List<Plugin>();
ProcessQueuedActions();
} }
private void CopyBuiltInPlugin(FileInfo zipFileInfo, ZipArchive zipArchive) private void CopyBuiltInPlugin(FileInfo zipFileInfo, ZipArchive zipArchive)
@ -181,6 +184,7 @@ namespace Artemis.Core.Services
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.");
_isElevated = isElevated;
LoadingPlugins = true; LoadingPlugins = true;
// Unload all currently loaded plugins first // Unload all currently loaded plugins first
@ -203,7 +207,7 @@ namespace Artemis.Core.Services
// ReSharper disable InconsistentlySynchronizedField - It's read-only, idc // ReSharper disable InconsistentlySynchronizedField - It's read-only, idc
_logger.Debug("Loaded {count} plugin(s)", _plugins.Count); _logger.Debug("Loaded {count} plugin(s)", _plugins.Count);
bool adminRequired = _plugins.Any(p => p.Entity.IsEnabled && p.Info.RequiresAdmin); bool adminRequired = _plugins.Any(p => p.Info.RequiresAdmin && p.Entity.IsEnabled && p.Entity.Features.Any(f => f.IsEnabled));
if (!isElevated && adminRequired) if (!isElevated && adminRequired)
{ {
_logger.Information("Restarting because one or more plugins requires elevation"); _logger.Information("Restarting because one or more plugins requires elevation");
@ -336,6 +340,19 @@ namespace Artemis.Core.Services
if (plugin.Assembly == null) if (plugin.Assembly == null)
throw new ArtemisPluginException(plugin, "Cannot enable a plugin that hasn't successfully been loaded"); throw new ArtemisPluginException(plugin, "Cannot enable a plugin that hasn't successfully been loaded");
if (plugin.Info.RequiresAdmin && plugin.Entity.Features.Any(f => f.IsEnabled) && !_isElevated)
{
if (!saveState)
throw new ArtemisCoreException("Cannot enable a plugin that requires elevation without saving it's state.");
plugin.Entity.IsEnabled = true;
SavePlugin(plugin);
_logger.Information("Restarting because a newly enabled plugin requires elevation");
Utilities.Restart(true, TimeSpan.FromMilliseconds(500));
return;
}
// Create the Ninject child kernel and load the module // Create the Ninject child kernel and load the module
plugin.Kernel = new ChildKernel(_kernel, new PluginModule(plugin)); plugin.Kernel = new ChildKernel(_kernel, new PluginModule(plugin));
OnPluginEnabling(new PluginEventArgs(plugin)); OnPluginEnabling(new PluginEventArgs(plugin));
@ -523,6 +540,21 @@ namespace Artemis.Core.Services
_logger.Verbose("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));
if (pluginFeature.Plugin.Info.RequiresAdmin && !_isElevated)
{
if (!saveState)
throw new ArtemisCoreException("Cannot enable a feature that requires elevation without saving it's state.");
pluginFeature.Entity.IsEnabled = true;
pluginFeature.Plugin.Entity.IsEnabled = true;
SavePlugin(pluginFeature.Plugin);
_logger.Information("Restarting because a newly enabled feature requires elevation");
Utilities.Restart(true, TimeSpan.FromMilliseconds(500));
return;
}
try try
{ {
pluginFeature.SetEnabled(true, isAutoEnable); pluginFeature.SetEnabled(true, isAutoEnable);
@ -586,6 +618,34 @@ namespace Artemis.Core.Services
#endregion #endregion
#region Queued actions
public void QueuePluginAction(Plugin plugin, PluginManagementAction pluginAction)
{
List<PluginQueuedActionEntity> existing = _pluginRepository.GetQueuedActions(plugin.Guid);
if (existing.Any(e => pluginAction == PluginManagementAction.Delete && e is PluginQueuedDeleteEntity))
return;
if (pluginAction == PluginManagementAction.Delete)
_pluginRepository.AddQueuedAction(new PluginQueuedDeleteEntity {PluginGuid = plugin.Guid, Directory = plugin.Directory.FullName});
}
private void ProcessQueuedActions()
{
foreach (PluginQueuedActionEntity pluginQueuedActionEntity in _pluginRepository.GetQueuedActions())
{
if (pluginQueuedActionEntity is PluginQueuedDeleteEntity deleteAction)
{
if (Directory.Exists(deleteAction.Directory))
Directory.Delete(deleteAction.Directory, true);
}
_pluginRepository.RemoveQueuedAction(pluginQueuedActionEntity);
}
}
#endregion
#region Storage #region Storage
private void SavePlugin(Plugin plugin) private void SavePlugin(Plugin plugin)
@ -673,4 +733,20 @@ namespace Artemis.Core.Services
#endregion #endregion
} }
/// <summary>
/// Represents a type of plugin management action
/// </summary>
public enum PluginManagementAction
{
/// <summary>
/// A plugin management action that removes a plugin
/// </summary>
Delete,
// /// <summary>
// /// A plugin management action that updates a plugin
// /// </summary>
// Update
}
} }

View File

@ -13,4 +13,21 @@ namespace Artemis.Storage.Entities.Plugins
public string Name { get; set; } public string Name { get; set; }
public string Value { get; set; } public string Value { get; set; }
} }
/// <summary>
/// Represents a queued action for a plugin
/// </summary>
public abstract class PluginQueuedActionEntity
{
public Guid Id { get; set; }
public Guid PluginGuid { get; set; }
}
/// <summary>
/// Represents a queued delete action for a plugin
/// </summary>
public class PluginQueuedDeleteEntity : PluginQueuedActionEntity
{
public string Directory { get; set; }
}
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Entities.Plugins;
namespace Artemis.Storage.Repositories.Interfaces namespace Artemis.Storage.Repositories.Interfaces
@ -8,9 +9,15 @@ namespace Artemis.Storage.Repositories.Interfaces
void AddPlugin(PluginEntity pluginEntity); void AddPlugin(PluginEntity pluginEntity);
PluginEntity GetPluginByGuid(Guid pluginGuid); PluginEntity GetPluginByGuid(Guid pluginGuid);
void SavePlugin(PluginEntity pluginEntity); void SavePlugin(PluginEntity pluginEntity);
void AddSetting(PluginSettingEntity pluginSettingEntity); void AddSetting(PluginSettingEntity pluginSettingEntity);
PluginSettingEntity GetSettingByGuid(Guid pluginGuid); PluginSettingEntity GetSettingByGuid(Guid pluginGuid);
PluginSettingEntity GetSettingByNameAndGuid(string name, Guid pluginGuid); PluginSettingEntity GetSettingByNameAndGuid(string name, Guid pluginGuid);
void SaveSetting(PluginSettingEntity pluginSettingEntity); void SaveSetting(PluginSettingEntity pluginSettingEntity);
void AddQueuedAction(PluginQueuedActionEntity pluginQueuedActionEntity);
List<PluginQueuedActionEntity> GetQueuedActions();
List<PluginQueuedActionEntity> GetQueuedActions(Guid pluginGuid);
void RemoveQueuedAction(PluginQueuedActionEntity pluginQueuedActionEntity);
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Entities.Plugins;
using Artemis.Storage.Repositories.Interfaces; using Artemis.Storage.Repositories.Interfaces;
using LiteDB; using LiteDB;
@ -14,6 +15,7 @@ namespace Artemis.Storage.Repositories
_repository = repository; _repository = repository;
_repository.Database.GetCollection<PluginSettingEntity>().EnsureIndex(s => new {s.Name, s.PluginGuid}, true); _repository.Database.GetCollection<PluginSettingEntity>().EnsureIndex(s => new {s.Name, s.PluginGuid}, true);
_repository.Database.GetCollection<PluginQueuedActionEntity>().EnsureIndex(s => s.PluginGuid);
} }
public void AddPlugin(PluginEntity pluginEntity) public void AddPlugin(PluginEntity pluginEntity)
@ -51,5 +53,24 @@ namespace Artemis.Storage.Repositories
{ {
_repository.Upsert(pluginSettingEntity); _repository.Upsert(pluginSettingEntity);
} }
public List<PluginQueuedActionEntity> GetQueuedActions()
{
return _repository.Query<PluginQueuedActionEntity>().ToList();
}
public List<PluginQueuedActionEntity> GetQueuedActions(Guid pluginGuid)
{
return _repository.Query<PluginQueuedActionEntity>().Where(q => q.PluginGuid == pluginGuid).ToList();
}
public void AddQueuedAction(PluginQueuedActionEntity pluginQueuedActionEntity)
{
_repository.Upsert(pluginQueuedActionEntity);
}
public void RemoveQueuedAction(PluginQueuedActionEntity pluginQueuedActionEntity)
{
_repository.Delete<PluginQueuedActionEntity>(pluginQueuedActionEntity.Id);
}
} }
} }

View File

@ -39,7 +39,7 @@ namespace Artemis.UI.Ninject.Factories
public interface ISettingsVmFactory : IVmFactory public interface ISettingsVmFactory : IVmFactory
{ {
PluginSettingsViewModel CreatePluginSettingsViewModel(Plugin plugin); PluginSettingsViewModel CreatePluginSettingsViewModel(Plugin plugin);
PluginFeatureViewModel CreatePluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo); PluginFeatureViewModel CreatePluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield);
DeviceSettingsViewModel CreateDeviceSettingsViewModel(ArtemisDevice device); DeviceSettingsViewModel CreateDeviceSettingsViewModel(ArtemisDevice device);
} }

View File

@ -3,17 +3,20 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection.Metadata;
using System.Security.Principal; using System.Security.Principal;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.LayerBrushes; using Artemis.Core.LayerBrushes;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.Core.Services.Core;
using Artemis.UI.Properties; using Artemis.UI.Properties;
using Artemis.UI.Screens.StartupWizard; using Artemis.UI.Screens.StartupWizard;
using Artemis.UI.Services; using Artemis.UI.Services;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Utilities;
using Ninject; using Ninject;
using Serilog.Events; using Serilog.Events;
using Stylet; using Stylet;
@ -70,7 +73,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
_defaultLayerBrushDescriptor = _settingsService.GetSetting("ProfileEditor.DefaultLayerBrushDescriptor", new LayerBrushReference _defaultLayerBrushDescriptor = _settingsService.GetSetting("ProfileEditor.DefaultLayerBrushDescriptor", new LayerBrushReference
{ {
LayerBrushProviderId = "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba", LayerBrushProviderId = "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba",
BrushType = "ColorBrush" BrushType = "SolidBrush"
}); });
WebServerPortSetting = _settingsService.GetSetting("WebServer.Port", 9696); WebServerPortSetting = _settingsService.GetSetting("WebServer.Port", 9696);
@ -306,7 +309,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
if (File.Exists(autoRunFile)) if (File.Exists(autoRunFile))
File.Delete(autoRunFile); File.Delete(autoRunFile);
// TODO: Don't do anything if running a development build, only auto-run release builds if (Constants.BuildInfo.IsLocalBuild)
return;
// Create or remove the task if necessary // Create or remove the task if necessary
try try
@ -314,26 +318,13 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
bool taskCreated = false; bool taskCreated = false;
if (!recreate) if (!recreate)
{ {
Process schtasks = new() taskCreated = SettingsUtilities.IsAutoRunTaskCreated();
{
StartInfo =
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = true,
FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"),
Arguments = "/TN \"Artemis 2 autorun\""
}
};
schtasks.Start();
schtasks.WaitForExit();
taskCreated = schtasks.ExitCode == 0;
} }
if (StartWithWindows && !taskCreated) if (StartWithWindows && !taskCreated)
CreateAutoRunTask(); SettingsUtilities.CreateAutoRunTask(TimeSpan.FromSeconds(AutoRunDelay));
else if (!StartWithWindows && taskCreated) else if (!StartWithWindows && taskCreated)
RemoveAutoRunTask(); SettingsUtilities.RemoveAutoRunTask();
} }
catch (Exception e) catch (Exception e)
{ {
@ -341,71 +332,10 @@ namespace Artemis.UI.Screens.Settings.Tabs.General
throw; throw;
} }
} }
private void CreateAutoRunTask()
{
XDocument document = XDocument.Parse(Resources.artemis_autorun);
XElement task = document.Descendants().First();
task.Descendants().First(d => d.Name.LocalName == "RegistrationInfo").Descendants().First(d => d.Name.LocalName == "Date")
.SetValue(DateTime.Now);
task.Descendants().First(d => d.Name.LocalName == "RegistrationInfo").Descendants().First(d => d.Name.LocalName == "Author")
.SetValue(WindowsIdentity.GetCurrent().Name);
task.Descendants().First(d => d.Name.LocalName == "Triggers").Descendants().First(d => d.Name.LocalName == "LogonTrigger").Descendants().First(d => d.Name.LocalName == "Delay")
.SetValue(TimeSpan.FromSeconds(AutoRunDelay));
task.Descendants().First(d => d.Name.LocalName == "Principals").Descendants().First(d => d.Name.LocalName == "Principal").Descendants().First(d => d.Name.LocalName == "UserId")
.SetValue(WindowsIdentity.GetCurrent().User.Value);
task.Descendants().First(d => d.Name.LocalName == "Actions").Descendants().First(d => d.Name.LocalName == "Exec").Descendants().First(d => d.Name.LocalName == "WorkingDirectory")
.SetValue(Constants.ApplicationFolder);
task.Descendants().First(d => d.Name.LocalName == "Actions").Descendants().First(d => d.Name.LocalName == "Exec").Descendants().First(d => d.Name.LocalName == "Command")
.SetValue("\"" + Constants.ExecutablePath + "\"");
string xmlPath = Path.GetTempFileName();
using (Stream fileStream = new FileStream(xmlPath, FileMode.Create))
{
document.Save(fileStream);
}
Process schtasks = new()
{
StartInfo =
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = true,
Verb = "runas",
FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"),
Arguments = $"/Create /XML \"{xmlPath}\" /tn \"Artemis 2 autorun\" /F"
}
};
schtasks.Start();
schtasks.WaitForExit();
File.Delete(xmlPath);
}
private void RemoveAutoRunTask()
{
Process schtasks = new()
{
StartInfo =
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = true,
Verb = "runas",
FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"),
Arguments = "/Delete /TN \"Artemis 2 autorun\" /f"
}
};
schtasks.Start();
schtasks.WaitForExit();
}
} }
public enum ApplicationColorScheme public enum ApplicationColorScheme
{ {
Light, Light,

View File

@ -45,8 +45,17 @@
<TextBlock Grid.Column="1" Text="{Binding FeatureInfo.Name}" Style="{StaticResource MaterialDesignTextBlock}" VerticalAlignment="Center" ToolTip="{Binding FeatureInfo.Description}" /> <TextBlock Grid.Column="1" Text="{Binding FeatureInfo.Name}" Style="{StaticResource MaterialDesignTextBlock}" VerticalAlignment="Center" ToolTip="{Binding FeatureInfo.Description}" />
<!-- Enable toggle column --> <!-- Enable toggle column -->
<StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="8" <StackPanel Grid.Column="2"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay, FallbackValue=Collapsed}"> HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="8"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay, FallbackValue=Collapsed}"
Orientation="Horizontal">
<materialDesign:PackIcon Kind="ShieldHalfFull"
ToolTip="Plugin requires admin rights"
VerticalAlignment="Center"
Margin="0 0 5 0"
Visibility="{Binding ShowShield, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
<CheckBox Style="{StaticResource MaterialDesignCheckBox}" IsChecked="{Binding IsEnabled}" IsEnabled="{Binding FeatureInfo.Plugin.IsEnabled}"> <CheckBox Style="{StaticResource MaterialDesignCheckBox}" IsChecked="{Binding IsEnabled}" IsEnabled="{Binding FeatureInfo.Plugin.IsEnabled}">
Feature enabled Feature enabled
</CheckBox> </CheckBox>

View File

@ -11,26 +11,33 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
{ {
public class PluginFeatureViewModel : Screen public class PluginFeatureViewModel : Screen
{ {
private readonly ICoreService _coreService;
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private bool _enabling; private bool _enabling;
private readonly IMessageService _messageService; private readonly IMessageService _messageService;
public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo,
bool showShield,
ICoreService coreService,
IDialogService dialogService, IDialogService dialogService,
IPluginManagementService pluginManagementService, IPluginManagementService pluginManagementService,
IMessageService messageService) IMessageService messageService)
{ {
_coreService = coreService;
_dialogService = dialogService; _dialogService = dialogService;
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
_messageService = messageService; _messageService = messageService;
FeatureInfo = pluginFeatureInfo; FeatureInfo = pluginFeatureInfo;
ShowShield = FeatureInfo.Plugin.Info.RequiresAdmin && showShield;
} }
public PluginFeatureInfo FeatureInfo { get; } public PluginFeatureInfo FeatureInfo { get; }
public Exception LoadException => FeatureInfo.Instance?.LoadException; public Exception LoadException => FeatureInfo.Instance?.LoadException;
public bool ShowShield { get; }
public bool Enabling public bool Enabling
{ {
get => _enabling; get => _enabling;
@ -95,6 +102,16 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
try try
{ {
if (FeatureInfo.Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
{
bool confirmed = await _dialogService.ShowConfirmDialog("Enable feature", "The plugin of this feature requires admin rights, are you sure you want to enable it?");
if (!confirmed)
{
NotifyOfPropertyChange(nameof(IsEnabled));
return;
}
}
await Task.Run(() => _pluginManagementService.EnablePluginFeature(FeatureInfo.Instance, true)); await Task.Run(() => _pluginManagementService.EnablePluginFeature(FeatureInfo.Instance, true));
} }
catch (Exception e) catch (Exception e)

View File

@ -118,7 +118,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
protected override void OnInitialActivate() protected override void OnInitialActivate()
{ {
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features) foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo)); Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
base.OnInitialActivate(); base.OnInitialActivate();
} }

View File

@ -5,6 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mde="https://spiegelp.github.io/MaterialDesignExtensions/winfx/xaml" xmlns:mde="https://spiegelp.github.io/MaterialDesignExtensions/winfx/xaml"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d" mc:Ignorable="d"
Width="800" Width="800"
Height="600" Height="600"
@ -17,47 +18,58 @@
FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto" FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
UseLayoutRounding="True" UseLayoutRounding="True"
d:DesignHeight="450" d:DesignWidth="800"> d:DesignHeight="450" d:DesignWidth="800">
<materialDesign:DialogHost IsTabStop="False"
<mde:Stepper IsLinear="True" Layout="Horizontal" ActiveStepChanged="{s:Action ActiveStepChanged}" CancelNavigation="{s:Action SkipOrFinishWizard}" Margin="15"> Focusable="False"
<mde:Step> Identifier="RootDialog"
<mde:Step.Header> DialogTheme="Inherit"
<mde:StepTitleHeader FirstLevelTitle="Welcome" /> SnackbarMessageQueue="{Binding MainMessageQueue}">
</mde:Step.Header> <Grid>
<mde:Step.Content> <mde:Stepper IsLinear="True" Layout="Horizontal" ActiveStepChanged="{s:Action ActiveStepChanged}" CancelNavigation="{s:Action SkipOrFinishWizard}" Margin="15">
<ContentControl s:View.Model="{Binding ActiveItem}" /> <mde:Step>
</mde:Step.Content> <mde:Step.Header>
</mde:Step> <mde:StepTitleHeader FirstLevelTitle="Welcome" />
<mde:Step> </mde:Step.Header>
<mde:Step.Header> <mde:Step.Content>
<mde:StepTitleHeader FirstLevelTitle="Devices" SecondLevelTitle="Pick your brands" /> <ContentControl s:View.Model="{Binding ActiveItem}" />
</mde:Step.Header> </mde:Step.Content>
<mde:Step.Content> </mde:Step>
<ContentControl s:View.Model="{Binding ActiveItem}" /> <mde:Step>
</mde:Step.Content> <mde:Step.Header>
</mde:Step> <mde:StepTitleHeader FirstLevelTitle="Devices" SecondLevelTitle="Pick your brands" />
<mde:Step> </mde:Step.Header>
<mde:Step.Header> <mde:Step.Content>
<mde:StepTitleHeader FirstLevelTitle="Desktop layout" SecondLevelTitle="Map your surface" /> <ContentControl s:View.Model="{Binding ActiveItem}" />
</mde:Step.Header> </mde:Step.Content>
<mde:Step.Content> </mde:Step>
<ContentControl s:View.Model="{Binding ActiveItem}" /> <mde:Step>
</mde:Step.Content> <mde:Step.Header>
</mde:Step> <mde:StepTitleHeader FirstLevelTitle="Desktop layout" SecondLevelTitle="Map your surface" />
<mde:Step> </mde:Step.Header>
<mde:Step.Header> <mde:Step.Content>
<mde:StepTitleHeader FirstLevelTitle="Settings" SecondLevelTitle="Set your preferences" /> <ContentControl s:View.Model="{Binding ActiveItem}" />
</mde:Step.Header> </mde:Step.Content>
<mde:Step.Content> </mde:Step>
<ContentControl s:View.Model="{Binding ActiveItem}" /> <mde:Step>
</mde:Step.Content> <mde:Step.Header>
</mde:Step> <mde:StepTitleHeader FirstLevelTitle="Settings" SecondLevelTitle="Set your preferences" />
<mde:Step> </mde:Step.Header>
<mde:Step.Header> <mde:Step.Content>
<mde:StepTitleHeader FirstLevelTitle="Finish" /> <ContentControl s:View.Model="{Binding ActiveItem}" />
</mde:Step.Header> </mde:Step.Content>
<mde:Step.Content> </mde:Step>
<ContentControl s:View.Model="{Binding ActiveItem}" /> <mde:Step>
</mde:Step.Content> <mde:Step.Header>
</mde:Step> <mde:StepTitleHeader FirstLevelTitle="Finish" />
</mde:Stepper> </mde:Step.Header>
<mde:Step.Content>
<ContentControl s:View.Model="{Binding ActiveItem}" />
</mde:Step.Content>
</mde:Step>
</mde:Stepper>
<materialDesign:Snackbar x:Name="MainSnackbar"
MessageQueue="{Binding MainMessageQueue}"
materialDesign:SnackbarMessage.InlineActionButtonMaxHeight="80"
materialDesign:SnackbarMessage.ContentMaxHeight="200" />
</Grid>
</materialDesign:DialogHost>
</mde:MaterialWindow> </mde:MaterialWindow>

View File

@ -2,18 +2,22 @@
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services; using Artemis.Core.Services;
using Artemis.UI.Screens.StartupWizard.Steps; using Artemis.UI.Screens.StartupWizard.Steps;
using Artemis.UI.Shared.Services;
using MaterialDesignExtensions.Controllers; using MaterialDesignExtensions.Controllers;
using MaterialDesignExtensions.Controls; using MaterialDesignExtensions.Controls;
using MaterialDesignThemes.Wpf;
using Stylet; using Stylet;
namespace Artemis.UI.Screens.StartupWizard namespace Artemis.UI.Screens.StartupWizard
{ {
public class StartupWizardViewModel : Conductor<Screen>.Collection.OneActive public class StartupWizardViewModel : Conductor<Screen>.Collection.OneActive
{ {
private readonly IMessageService _messageService;
private readonly ISettingsService _settingsService; private readonly ISettingsService _settingsService;
private StepperController _stepperController; private StepperController _stepperController;
public StartupWizardViewModel(ISettingsService settingsService, public StartupWizardViewModel(ISettingsService settingsService,
IMessageService messageService,
WelcomeStepViewModel welcome, WelcomeStepViewModel welcome,
DevicesStepViewModel devices, DevicesStepViewModel devices,
LayoutStepViewModel layout, LayoutStepViewModel layout,
@ -21,6 +25,7 @@ namespace Artemis.UI.Screens.StartupWizard
FinishStepViewModel finish) FinishStepViewModel finish)
{ {
_settingsService = settingsService; _settingsService = settingsService;
_messageService = messageService;
Items.Add(welcome); Items.Add(welcome);
Items.Add(devices); Items.Add(devices);
Items.Add(layout); Items.Add(layout);
@ -30,6 +35,8 @@ namespace Artemis.UI.Screens.StartupWizard
ActiveItem = Items.First(); ActiveItem = Items.First();
} }
public ISnackbarMessageQueue MainMessageQueue { get; set; }
public void ActiveStepChanged(object sender, ActiveStepChangedEventArgs e) public void ActiveStepChanged(object sender, ActiveStepChangedEventArgs e)
{ {
Stepper stepper = (Stepper) sender; Stepper stepper = (Stepper) sender;
@ -40,22 +47,23 @@ namespace Artemis.UI.Screens.StartupWizard
} }
public void SkipOrFinishWizard() public void SkipOrFinishWizard()
{
RequestClose();
}
protected override void OnClose()
{ {
PluginSetting<bool> setting = _settingsService.GetSetting("UI.SetupWizardCompleted", false); PluginSetting<bool> setting = _settingsService.GetSetting("UI.SetupWizardCompleted", false);
setting.Value = true; setting.Value = true;
setting.Save(); setting.Save();
base.OnClose(); RequestClose();
} }
public void Continue() public void Continue()
{ {
_stepperController.Continue(); _stepperController.Continue();
} }
protected override void OnInitialActivate()
{
MainMessageQueue = _messageService.MainMessageQueue;
base.OnInitialActivate();
}
} }
} }

View File

@ -16,7 +16,7 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Grid.Row="0" Style="{StaticResource MaterialDesignBody1TextBlock}" TextWrapping="Wrap"> <TextBlock Grid.Row="0" Style="{StaticResource MaterialDesignBody1TextBlock}" TextWrapping="Wrap">
EnabledDevices are supported through the use of device providers. <LineBreak /> Devices are supported through the use of device providers. <LineBreak />
In the list below you can enable device providers for each brand you own by checking <Run Text="Feature enabled" FontWeight="Bold" />. In the list below you can enable device providers for each brand you own by checking <Run Text="Feature enabled" FontWeight="Bold" />.
</TextBlock> </TextBlock>

View File

@ -31,7 +31,7 @@ namespace Artemis.UI.Screens.StartupWizard.Steps
IEnumerable<PluginFeatureInfo> features = _pluginManagementService.GetAllPlugins() IEnumerable<PluginFeatureInfo> features = _pluginManagementService.GetAllPlugins()
.SelectMany(p => p.Features.Where(f => typeof(DeviceProvider).IsAssignableFrom(f.FeatureType))) .SelectMany(p => p.Features.Where(f => typeof(DeviceProvider).IsAssignableFrom(f.FeatureType)))
.OrderBy(d => d.GetType().Name); .OrderBy(d => d.GetType().Name);
Items.AddRange(features.Select(d => _settingsVmFactory.CreatePluginFeatureViewModel(d))); Items.AddRange(features.Select(d => _settingsVmFactory.CreatePluginFeatureViewModel(d, true)));
base.OnActivate(); base.OnActivate();
} }

View File

@ -7,6 +7,7 @@ using Artemis.UI.Screens.Settings.Tabs.General;
using Artemis.UI.Screens.Settings.Tabs.Plugins; using Artemis.UI.Screens.Settings.Tabs.Plugins;
using Artemis.UI.Shared; using Artemis.UI.Shared;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Utilities;
using Stylet; using Stylet;
namespace Artemis.UI.Screens.StartupWizard.Steps namespace Artemis.UI.Screens.StartupWizard.Steps
@ -34,7 +35,7 @@ namespace Artemis.UI.Screens.StartupWizard.Steps
_settingsService.GetSetting("UI.AutoRun", false).Value = value; _settingsService.GetSetting("UI.AutoRun", false).Value = value;
_settingsService.GetSetting("UI.AutoRun", false).Save(); _settingsService.GetSetting("UI.AutoRun", false).Save();
NotifyOfPropertyChange(nameof(StartWithWindows)); NotifyOfPropertyChange(nameof(StartWithWindows));
Task.Run(ApplyAutorun); Task.Run(() => ApplyAutorun(false));
} }
} }
@ -59,23 +60,35 @@ namespace Artemis.UI.Screens.StartupWizard.Steps
} }
} }
private void ApplyAutorun() private void ApplyAutorun(bool recreate)
{ {
if (!StartWithWindows)
StartMinimized = false;
// Remove the old auto-run method of placing a shortcut in shell:startup
string autoRunFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), "Artemis.lnk");
if (File.Exists(autoRunFile))
File.Delete(autoRunFile);
// Local builds shouldn't auto-run, this is just annoying
// if (Constants.BuildInfo.IsLocalBuild)
// return;
// Create or remove the task if necessary
try try
{ {
string autoRunFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), "Artemis.lnk"); bool taskCreated = false;
string executableFile = Constants.ExecutablePath; if (!recreate)
taskCreated = SettingsUtilities.IsAutoRunTaskCreated();
if (File.Exists(autoRunFile)) if (StartWithWindows && !taskCreated)
File.Delete(autoRunFile); SettingsUtilities.CreateAutoRunTask(TimeSpan.FromSeconds(15));
if (StartWithWindows) else if (!StartWithWindows && taskCreated)
ShortcutUtilities.Create(autoRunFile, executableFile, "--autorun", new FileInfo(executableFile).DirectoryName, "Artemis", "", ""); SettingsUtilities.RemoveAutoRunTask();
else
StartMinimized = false;
} }
catch (Exception e) catch (Exception e)
{ {
_dialogService.ShowExceptionDialog("An exception occured while trying to apply the auto run setting", e); Execute.PostToUIThread(() => _dialogService.ShowExceptionDialog("An exception occured while trying to apply the auto run setting", e));
throw; throw;
} }
} }

View File

@ -0,0 +1,95 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.Xml.Linq;
using Artemis.Core;
using Artemis.UI.Properties;
namespace Artemis.UI.Utilities
{
public static class SettingsUtilities
{
public static bool IsAutoRunTaskCreated()
{
Process schtasks = new()
{
StartInfo =
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = true,
FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"),
Arguments = "/TN \"Artemis 2 autorun\""
}
};
schtasks.Start();
schtasks.WaitForExit();
return schtasks.ExitCode == 0;
}
public static void CreateAutoRunTask(TimeSpan autoRunDelay)
{
XDocument document = XDocument.Parse(Resources.artemis_autorun);
XElement task = document.Descendants().First();
task.Descendants().First(d => d.Name.LocalName == "RegistrationInfo").Descendants().First(d => d.Name.LocalName == "Date")
.SetValue(DateTime.Now);
task.Descendants().First(d => d.Name.LocalName == "RegistrationInfo").Descendants().First(d => d.Name.LocalName == "Author")
.SetValue(WindowsIdentity.GetCurrent().Name);
task.Descendants().First(d => d.Name.LocalName == "Triggers").Descendants().First(d => d.Name.LocalName == "LogonTrigger").Descendants().First(d => d.Name.LocalName == "Delay")
.SetValue(autoRunDelay);
task.Descendants().First(d => d.Name.LocalName == "Principals").Descendants().First(d => d.Name.LocalName == "Principal").Descendants().First(d => d.Name.LocalName == "UserId")
.SetValue(WindowsIdentity.GetCurrent().User.Value);
task.Descendants().First(d => d.Name.LocalName == "Actions").Descendants().First(d => d.Name.LocalName == "Exec").Descendants().First(d => d.Name.LocalName == "WorkingDirectory")
.SetValue(Constants.ApplicationFolder);
task.Descendants().First(d => d.Name.LocalName == "Actions").Descendants().First(d => d.Name.LocalName == "Exec").Descendants().First(d => d.Name.LocalName == "Command")
.SetValue("\"" + Constants.ExecutablePath + "\"");
string xmlPath = Path.GetTempFileName();
using (Stream fileStream = new FileStream(xmlPath, FileMode.Create))
{
document.Save(fileStream);
}
Process schtasks = new()
{
StartInfo =
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = true,
Verb = "runas",
FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"),
Arguments = $"/Create /XML \"{xmlPath}\" /tn \"Artemis 2 autorun\" /F"
}
};
schtasks.Start();
schtasks.WaitForExit();
File.Delete(xmlPath);
}
public static void RemoveAutoRunTask()
{
Process schtasks = new()
{
StartInfo =
{
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = true,
Verb = "runas",
FileName = Path.Combine(Environment.SystemDirectory, "schtasks.exe"),
Arguments = "/Delete /TN \"Artemis 2 autorun\" /f"
}
};
schtasks.Start();
schtasks.WaitForExit();
}
}
}