diff --git a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs index 887321a72..7643c93ae 100644 --- a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs +++ b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs @@ -71,7 +71,7 @@ namespace Artemis.Core.Services Plugin ImportPlugin(string fileName); /// - /// Unloads and permanently removes the provided plugin + /// Unloads and permanently removes the provided plugin /// /// The plugin to remove void RemovePlugin(Plugin plugin); @@ -127,6 +127,13 @@ namespace Artemis.Core.Services /// DeviceProvider GetDeviceProviderByDevice(IRGBDevice device); + /// + /// Queues an action for the provided plugin for the next time Artemis starts, before plugins are loaded + /// + /// The plugin to queue the action for + /// The action to take + void QueuePluginAction(Plugin plugin, PluginManagementAction pluginAction); + #region Events /// diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 952c62c4f..cadb77caf 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -27,6 +27,7 @@ namespace Artemis.Core.Services private readonly ILogger _logger; private readonly IPluginRepository _pluginRepository; private readonly List _plugins; + private bool _isElevated; public PluginManagementService(IKernel kernel, ILogger logger, IPluginRepository pluginRepository) { @@ -34,6 +35,8 @@ namespace Artemis.Core.Services _logger = logger; _pluginRepository = pluginRepository; _plugins = new List(); + + ProcessQueuedActions(); } private void CopyBuiltInPlugin(FileInfo zipFileInfo, ZipArchive zipArchive) @@ -181,6 +184,7 @@ namespace Artemis.Core.Services if (LoadingPlugins) throw new ArtemisCoreException("Cannot load plugins while a previous load hasn't been completed yet."); + _isElevated = isElevated; LoadingPlugins = true; // Unload all currently loaded plugins first @@ -203,7 +207,7 @@ namespace Artemis.Core.Services // ReSharper disable InconsistentlySynchronizedField - It's read-only, idc _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) { _logger.Information("Restarting because one or more plugins requires elevation"); @@ -336,6 +340,19 @@ namespace Artemis.Core.Services if (plugin.Assembly == null) 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 plugin.Kernel = new ChildKernel(_kernel, new PluginModule(plugin)); OnPluginEnabling(new PluginEventArgs(plugin)); @@ -523,6 +540,21 @@ namespace Artemis.Core.Services _logger.Verbose("Enabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin); 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 { pluginFeature.SetEnabled(true, isAutoEnable); @@ -586,6 +618,34 @@ namespace Artemis.Core.Services #endregion + #region Queued actions + + public void QueuePluginAction(Plugin plugin, PluginManagementAction pluginAction) + { + List 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 private void SavePlugin(Plugin plugin) @@ -673,4 +733,20 @@ namespace Artemis.Core.Services #endregion } + + /// + /// Represents a type of plugin management action + /// + public enum PluginManagementAction + { + /// + /// A plugin management action that removes a plugin + /// + Delete, + + // /// + // /// A plugin management action that updates a plugin + // /// + // Update + } } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs b/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs index ae4827426..08a17f922 100644 --- a/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs +++ b/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs @@ -13,4 +13,21 @@ namespace Artemis.Storage.Entities.Plugins public string Name { get; set; } public string Value { get; set; } } + + /// + /// Represents a queued action for a plugin + /// + public abstract class PluginQueuedActionEntity + { + public Guid Id { get; set; } + public Guid PluginGuid { get; set; } + } + + /// + /// Represents a queued delete action for a plugin + /// + public class PluginQueuedDeleteEntity : PluginQueuedActionEntity + { + public string Directory { get; set; } + } } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs index 9a1258416..d93ba4628 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Artemis.Storage.Entities.Plugins; namespace Artemis.Storage.Repositories.Interfaces @@ -8,9 +9,15 @@ namespace Artemis.Storage.Repositories.Interfaces void AddPlugin(PluginEntity pluginEntity); PluginEntity GetPluginByGuid(Guid pluginGuid); void SavePlugin(PluginEntity pluginEntity); + void AddSetting(PluginSettingEntity pluginSettingEntity); PluginSettingEntity GetSettingByGuid(Guid pluginGuid); PluginSettingEntity GetSettingByNameAndGuid(string name, Guid pluginGuid); void SaveSetting(PluginSettingEntity pluginSettingEntity); + + void AddQueuedAction(PluginQueuedActionEntity pluginQueuedActionEntity); + List GetQueuedActions(); + List GetQueuedActions(Guid pluginGuid); + void RemoveQueuedAction(PluginQueuedActionEntity pluginQueuedActionEntity); } } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/PluginRepository.cs b/src/Artemis.Storage/Repositories/PluginRepository.cs index b97c8fc83..5a923a60a 100644 --- a/src/Artemis.Storage/Repositories/PluginRepository.cs +++ b/src/Artemis.Storage/Repositories/PluginRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Repositories.Interfaces; using LiteDB; @@ -14,6 +15,7 @@ namespace Artemis.Storage.Repositories _repository = repository; _repository.Database.GetCollection().EnsureIndex(s => new {s.Name, s.PluginGuid}, true); + _repository.Database.GetCollection().EnsureIndex(s => s.PluginGuid); } public void AddPlugin(PluginEntity pluginEntity) @@ -51,5 +53,24 @@ namespace Artemis.Storage.Repositories { _repository.Upsert(pluginSettingEntity); } + + public List GetQueuedActions() + { + return _repository.Query().ToList(); + } + + public List GetQueuedActions(Guid pluginGuid) + { + return _repository.Query().Where(q => q.PluginGuid == pluginGuid).ToList(); + } + + public void AddQueuedAction(PluginQueuedActionEntity pluginQueuedActionEntity) + { + _repository.Upsert(pluginQueuedActionEntity); + } + public void RemoveQueuedAction(PluginQueuedActionEntity pluginQueuedActionEntity) + { + _repository.Delete(pluginQueuedActionEntity.Id); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs index 2d0190136..02d543a4c 100644 --- a/src/Artemis.UI/Ninject/Factories/IVMFactory.cs +++ b/src/Artemis.UI/Ninject/Factories/IVMFactory.cs @@ -39,7 +39,7 @@ namespace Artemis.UI.Ninject.Factories public interface ISettingsVmFactory : IVmFactory { PluginSettingsViewModel CreatePluginSettingsViewModel(Plugin plugin); - PluginFeatureViewModel CreatePluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo); + PluginFeatureViewModel CreatePluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, bool showShield); DeviceSettingsViewModel CreateDeviceSettingsViewModel(ArtemisDevice device); } diff --git a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs index c02ee2db3..86e8fdb7b 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs @@ -3,17 +3,20 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection.Metadata; using System.Security.Principal; using System.Threading.Tasks; using System.Xml.Linq; using Artemis.Core; using Artemis.Core.LayerBrushes; using Artemis.Core.Services; +using Artemis.Core.Services.Core; using Artemis.UI.Properties; using Artemis.UI.Screens.StartupWizard; using Artemis.UI.Services; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; +using Artemis.UI.Utilities; using Ninject; using Serilog.Events; using Stylet; @@ -70,7 +73,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.General _defaultLayerBrushDescriptor = _settingsService.GetSetting("ProfileEditor.DefaultLayerBrushDescriptor", new LayerBrushReference { LayerBrushProviderId = "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba", - BrushType = "ColorBrush" + BrushType = "SolidBrush" }); WebServerPortSetting = _settingsService.GetSetting("WebServer.Port", 9696); @@ -306,7 +309,8 @@ namespace Artemis.UI.Screens.Settings.Tabs.General if (File.Exists(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 try @@ -314,26 +318,13 @@ namespace Artemis.UI.Screens.Settings.Tabs.General bool taskCreated = false; if (!recreate) { - 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(); - taskCreated = schtasks.ExitCode == 0; + taskCreated = SettingsUtilities.IsAutoRunTaskCreated(); } if (StartWithWindows && !taskCreated) - CreateAutoRunTask(); + SettingsUtilities.CreateAutoRunTask(TimeSpan.FromSeconds(AutoRunDelay)); else if (!StartWithWindows && taskCreated) - RemoveAutoRunTask(); + SettingsUtilities.RemoveAutoRunTask(); } catch (Exception e) { @@ -341,71 +332,10 @@ namespace Artemis.UI.Screens.Settings.Tabs.General 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 { Light, diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml index ad2358da7..b97f80323 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureView.xaml @@ -45,8 +45,17 @@ - + + Feature enabled diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs index f7a0899d1..fda530a9c 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs @@ -11,26 +11,33 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins { public class PluginFeatureViewModel : Screen { + private readonly ICoreService _coreService; private readonly IDialogService _dialogService; private readonly IPluginManagementService _pluginManagementService; private bool _enabling; private readonly IMessageService _messageService; - public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, + public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo, + bool showShield, + ICoreService coreService, IDialogService dialogService, IPluginManagementService pluginManagementService, IMessageService messageService) { + _coreService = coreService; _dialogService = dialogService; _pluginManagementService = pluginManagementService; _messageService = messageService; FeatureInfo = pluginFeatureInfo; + ShowShield = FeatureInfo.Plugin.Info.RequiresAdmin && showShield; } public PluginFeatureInfo FeatureInfo { get; } public Exception LoadException => FeatureInfo.Instance?.LoadException; + public bool ShowShield { get; } + public bool Enabling { get => _enabling; @@ -95,6 +102,16 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins 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)); } catch (Exception e) diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs index 77cdc5d6d..19414cb33 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs @@ -118,7 +118,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins protected override void OnInitialActivate() { foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features) - Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo)); + Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false)); base.OnInitialActivate(); } diff --git a/src/Artemis.UI/Screens/StartupWizard/StartupWizardView.xaml b/src/Artemis.UI/Screens/StartupWizard/StartupWizardView.xaml index 0d959c213..b81bbab63 100644 --- a/src/Artemis.UI/Screens/StartupWizard/StartupWizardView.xaml +++ b/src/Artemis.UI/Screens/StartupWizard/StartupWizardView.xaml @@ -5,6 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mde="https://spiegelp.github.io/MaterialDesignExtensions/winfx/xaml" xmlns:s="https://github.com/canton7/Stylet" + xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" mc:Ignorable="d" Width="800" Height="600" @@ -17,47 +18,58 @@ FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto" UseLayoutRounding="True" d:DesignHeight="450" d:DesignWidth="800"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/StartupWizardViewModel.cs b/src/Artemis.UI/Screens/StartupWizard/StartupWizardViewModel.cs index 73d244cf1..8f1eb12a0 100644 --- a/src/Artemis.UI/Screens/StartupWizard/StartupWizardViewModel.cs +++ b/src/Artemis.UI/Screens/StartupWizard/StartupWizardViewModel.cs @@ -2,18 +2,22 @@ using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Screens.StartupWizard.Steps; +using Artemis.UI.Shared.Services; using MaterialDesignExtensions.Controllers; using MaterialDesignExtensions.Controls; +using MaterialDesignThemes.Wpf; using Stylet; namespace Artemis.UI.Screens.StartupWizard { public class StartupWizardViewModel : Conductor.Collection.OneActive { + private readonly IMessageService _messageService; private readonly ISettingsService _settingsService; private StepperController _stepperController; public StartupWizardViewModel(ISettingsService settingsService, + IMessageService messageService, WelcomeStepViewModel welcome, DevicesStepViewModel devices, LayoutStepViewModel layout, @@ -21,6 +25,7 @@ namespace Artemis.UI.Screens.StartupWizard FinishStepViewModel finish) { _settingsService = settingsService; + _messageService = messageService; Items.Add(welcome); Items.Add(devices); Items.Add(layout); @@ -30,6 +35,8 @@ namespace Artemis.UI.Screens.StartupWizard ActiveItem = Items.First(); } + public ISnackbarMessageQueue MainMessageQueue { get; set; } + public void ActiveStepChanged(object sender, ActiveStepChangedEventArgs e) { Stepper stepper = (Stepper) sender; @@ -40,22 +47,23 @@ namespace Artemis.UI.Screens.StartupWizard } public void SkipOrFinishWizard() - { - RequestClose(); - } - - protected override void OnClose() { PluginSetting setting = _settingsService.GetSetting("UI.SetupWizardCompleted", false); setting.Value = true; setting.Save(); - base.OnClose(); + RequestClose(); } - + public void Continue() { _stepperController.Continue(); } + + protected override void OnInitialActivate() + { + MainMessageQueue = _messageService.MainMessageQueue; + base.OnInitialActivate(); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepView.xaml b/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepView.xaml index df4f7c13f..bd9c7ddc2 100644 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepView.xaml +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepView.xaml @@ -16,7 +16,7 @@ - EnabledDevices are supported through the use of device providers. + Devices are supported through the use of device providers. In the list below you can enable device providers for each brand you own by checking . diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepViewModel.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepViewModel.cs index 16e677d38..efc7b29db 100644 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepViewModel.cs +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/DevicesStepViewModel.cs @@ -31,7 +31,7 @@ namespace Artemis.UI.Screens.StartupWizard.Steps IEnumerable features = _pluginManagementService.GetAllPlugins() .SelectMany(p => p.Features.Where(f => typeof(DeviceProvider).IsAssignableFrom(f.FeatureType))) .OrderBy(d => d.GetType().Name); - Items.AddRange(features.Select(d => _settingsVmFactory.CreatePluginFeatureViewModel(d))); + Items.AddRange(features.Select(d => _settingsVmFactory.CreatePluginFeatureViewModel(d, true))); base.OnActivate(); } diff --git a/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStepViewModel.cs b/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStepViewModel.cs index b1076408c..b1ed82ae8 100644 --- a/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStepViewModel.cs +++ b/src/Artemis.UI/Screens/StartupWizard/Steps/SettingsStepViewModel.cs @@ -7,6 +7,7 @@ using Artemis.UI.Screens.Settings.Tabs.General; using Artemis.UI.Screens.Settings.Tabs.Plugins; using Artemis.UI.Shared; using Artemis.UI.Shared.Services; +using Artemis.UI.Utilities; using Stylet; 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).Save(); 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 { - string autoRunFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), "Artemis.lnk"); - string executableFile = Constants.ExecutablePath; + bool taskCreated = false; + if (!recreate) + taskCreated = SettingsUtilities.IsAutoRunTaskCreated(); - if (File.Exists(autoRunFile)) - File.Delete(autoRunFile); - if (StartWithWindows) - ShortcutUtilities.Create(autoRunFile, executableFile, "--autorun", new FileInfo(executableFile).DirectoryName, "Artemis", "", ""); - else - StartMinimized = false; + if (StartWithWindows && !taskCreated) + SettingsUtilities.CreateAutoRunTask(TimeSpan.FromSeconds(15)); + else if (!StartWithWindows && taskCreated) + SettingsUtilities.RemoveAutoRunTask(); } 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; } } diff --git a/src/Artemis.UI/Utilities/SettingsUtilities.cs b/src/Artemis.UI/Utilities/SettingsUtilities.cs new file mode 100644 index 000000000..44b43a395 --- /dev/null +++ b/src/Artemis.UI/Utilities/SettingsUtilities.cs @@ -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(); + } + } +} \ No newline at end of file