diff --git a/src/Artemis.Core/Artemis.Core.csproj.DotSettings b/src/Artemis.Core/Artemis.Core.csproj.DotSettings index 461cc1c15..261d8170b 100644 --- a/src/Artemis.Core/Artemis.Core.csproj.DotSettings +++ b/src/Artemis.Core/Artemis.Core.csproj.DotSettings @@ -57,6 +57,7 @@ True True True + True True True True diff --git a/src/Artemis.Core/Events/DataModelPathEventArgs.cs b/src/Artemis.Core/Events/DataModelPathEventArgs.cs new file mode 100644 index 000000000..6e3da98ed --- /dev/null +++ b/src/Artemis.Core/Events/DataModelPathEventArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace Artemis.Core +{ + /// + /// Provides data about data model path related events + /// + public class DataModelPathEventArgs : EventArgs + { + internal DataModelPathEventArgs(DataModelPath dataModelPath) + { + DataModelPath = dataModelPath; + } + + /// + /// Gets the data model path this event is related to + /// + public DataModelPath DataModelPath { get; } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Events/DynamicDataModelChildEventArgs.cs b/src/Artemis.Core/Events/DynamicDataModelChildEventArgs.cs index 9248a716a..030fd815e 100644 --- a/src/Artemis.Core/Events/DynamicDataModelChildEventArgs.cs +++ b/src/Artemis.Core/Events/DynamicDataModelChildEventArgs.cs @@ -1,5 +1,5 @@ using System; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; namespace Artemis.Core { diff --git a/src/Artemis.Core/Models/Profile/Conditions/Wrappers/EventPredicateWrapperDataModel.cs b/src/Artemis.Core/Models/Profile/Conditions/Wrappers/EventPredicateWrapperDataModel.cs index 76e19f3c4..7b30c20f9 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Wrappers/EventPredicateWrapperDataModel.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Wrappers/EventPredicateWrapperDataModel.cs @@ -1,5 +1,5 @@ using System; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; namespace Artemis.Core { diff --git a/src/Artemis.Core/Models/Profile/Conditions/Wrappers/ListPredicateWrapperDataModel.cs b/src/Artemis.Core/Models/Profile/Conditions/Wrappers/ListPredicateWrapperDataModel.cs index f6eafa524..05b3e598e 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/Wrappers/ListPredicateWrapperDataModel.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/Wrappers/ListPredicateWrapperDataModel.cs @@ -1,5 +1,5 @@ using System; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; namespace Artemis.Core { diff --git a/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs index c2913c85f..1f54ca146 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/IDataBinding.cs @@ -1,5 +1,5 @@ using System; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; namespace Artemis.Core { diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs index cd56a38b2..352c3c09b 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelEvent.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; namespace Artemis.Core { diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelEventArgs.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelEventArgs.cs index 4a0431155..edd41bb6a 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelEventArgs.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelEventArgs.cs @@ -1,5 +1,5 @@ using System; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; namespace Artemis.Core { diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs index c2eb9f1e2..baa7eac6c 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using Artemis.Storage.Entities.Profile; namespace Artemis.Core @@ -175,6 +175,8 @@ namespace Artemis.Core internal void Invalidate() { + Target?.RemoveDataModelPath(this); + foreach (DataModelPathSegment dataModelPathSegment in _segments) dataModelPathSegment.Dispose(); _segments.Clear(); @@ -187,11 +189,11 @@ namespace Artemis.Core internal void Initialize() { - Invalidate(); - if (Target == null) return; + Target.AddDataModelPath(this); + DataModelPathSegment startSegment = new(this, "target", "target"); startSegment.Node = _segments.AddFirst(startSegment); @@ -260,7 +262,7 @@ namespace Artemis.Core DataModelStore.DataModelAdded += DataModelStoreOnDataModelAdded; DataModelStore.DataModelRemoved += DataModelStoreOnDataModelRemoved; } - + #region IDisposable /// @@ -330,6 +332,7 @@ namespace Artemis.Core if (e.Registration.DataModel.Module.Id != Entity.DataModelId) return; + Invalidate(); Target = e.Registration.DataModel; Initialize(); } @@ -339,8 +342,8 @@ namespace Artemis.Core if (e.Registration.DataModel.Module.Id != Entity.DataModelId) return; - Target = null; Invalidate(); + Target = null; } #endregion diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs index 7531cb09f..9e5be4a9e 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using Humanizer; namespace Artemis.Core @@ -301,7 +301,7 @@ namespace Artemis.Core private void DynamicChildOnDynamicChildRemoved(object? sender, DynamicDataModelChildEventArgs e) { if (e.DynamicChild.BaseValue == _dynamicDataModel) - DataModelPath.Initialize(); + DataModelPath.Invalidate(); } #endregion diff --git a/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEventArgs.cs b/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEventArgs.cs index f82169770..b25976a61 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEventArgs.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/ValueChangedEvent/DataModelValueChangedEventArgs.cs @@ -1,4 +1,4 @@ -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; namespace Artemis.Core { diff --git a/src/Artemis.Core/Models/Profile/ProfileCategory.cs b/src/Artemis.Core/Models/Profile/ProfileCategory.cs index b5d0b85f2..a15a63e91 100644 --- a/src/Artemis.Core/Models/Profile/ProfileCategory.cs +++ b/src/Artemis.Core/Models/Profile/ProfileCategory.cs @@ -5,6 +5,9 @@ using Artemis.Storage.Entities.Profile; namespace Artemis.Core { + /// + /// Represents a category containing + /// public class ProfileCategory : CorePropertyChanged, IStorageModel { private readonly List _profileConfigurations = new(); diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index 559feecdc..c63bbedef 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -59,6 +59,8 @@ namespace Artemis.Core foreach (BaseLayerEffect baseLayerEffect in LayerEffects) baseLayerEffect.Dispose(); + DisplayCondition?.Dispose(); + base.Dispose(disposing); } diff --git a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs index 01d7ef2dd..f70ef6e9c 100644 --- a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs +++ b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs @@ -1,13 +1,18 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Artemis.Core.Modules; using Artemis.Storage.Entities.Profile; namespace Artemis.Core { - public class ProfileConfiguration : CorePropertyChanged, IStorageModel + /// + /// Represents the configuration of a profile, contained in a + /// + public class ProfileConfiguration : CorePropertyChanged, IStorageModel, IDisposable { private ProfileCategory _category; + private bool _disposed; private bool _isMissingModule; private bool _isSuspended; @@ -140,11 +145,17 @@ namespace Artemis.Core /// public void Update() { + if (_disposed) + throw new ObjectDisposedException("ProfileConfiguration"); + ActivationConditionMet = ActivationCondition == null || ActivationCondition.Evaluate(); } public bool ShouldBeActive(bool includeActivationCondition) { + if (_disposed) + throw new ObjectDisposedException("ProfileConfiguration"); + if (Category.IsSuspended || IsSuspended || IsMissingModule) return false; @@ -161,15 +172,32 @@ namespace Artemis.Core internal void LoadModules(List enabledModules) { + if (_disposed) + throw new ObjectDisposedException("ProfileConfiguration"); + Module = enabledModules.FirstOrDefault(m => m.Id == Entity.ModuleId); IsMissingModule = Module == null && Entity.ModuleId != null; } + #region IDisposable + + /// + public void Dispose() + { + _disposed = true; + ActivationCondition?.Dispose(); + } + + #endregion + #region Implementation of IStorageModel /// public void Load() { + if (_disposed) + throw new ObjectDisposedException("ProfileConfiguration"); + Name = Entity.Name; IsSuspended = Entity.IsSuspended; ActivationBehaviour = (ActivationBehaviour) Entity.ActivationBehaviour; @@ -185,6 +213,9 @@ namespace Artemis.Core /// public void Save() { + if (_disposed) + throw new ObjectDisposedException("ProfileConfiguration"); + Entity.Name = Name; Entity.IsSuspended = IsSuspended; Entity.ActivationBehaviour = (int) ActivationBehaviour; @@ -199,9 +230,7 @@ namespace Artemis.Core Entity.ActivationCondition = ActivationCondition.Entity; } else - { Entity.ActivationCondition = null; - } if (!IsMissingModule) Entity.ModuleId = Module?.Id; diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs index 9dec7f4bc..b8034ca26 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs @@ -549,11 +549,6 @@ namespace Artemis.Core else LogicalLayout = DeviceEntity.LogicalLayout; } - - public void ClearLayout() - { - // TODO - } } /// diff --git a/src/Artemis.Core/Plugins/Modules/ActivationRequirements/ProcessActivationRequirement.cs b/src/Artemis.Core/Plugins/Modules/ActivationRequirements/ProcessActivationRequirement.cs index cd43a0c9a..aa9a5c3cd 100644 --- a/src/Artemis.Core/Plugins/Modules/ActivationRequirements/ProcessActivationRequirement.cs +++ b/src/Artemis.Core/Plugins/Modules/ActivationRequirements/ProcessActivationRequirement.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; +using Artemis.Core.Services; +using Ninject; namespace Artemis.Core.Modules { @@ -11,6 +13,8 @@ namespace Artemis.Core.Modules /// public class ProcessActivationRequirement : IModuleActivationRequirement { + private readonly IProcessMonitorService _processMonitorService; + /// /// Creates a new instance of the class /// @@ -18,6 +22,14 @@ namespace Artemis.Core.Modules /// The location of where the process must be running from (optional) public ProcessActivationRequirement(string? processName, string? location = null) { + if (string.IsNullOrWhiteSpace(processName) && string.IsNullOrWhiteSpace(location)) + throw new ArgumentNullException($"Atleast one {nameof(processName)} and {nameof(location)} must not be null"); + + // Let's not make a habit out of this :P + if (CoreService.Kernel == null) + throw new ArtemisCoreException("Cannot create a ProcessActivationRequirement before initializing the Core"); + _processMonitorService = CoreService.Kernel.Get(); + ProcessName = processName; Location = location; } @@ -38,10 +50,13 @@ namespace Artemis.Core.Modules if (ProcessName == null && Location == null) return false; - IEnumerable processes = ProcessName != null ? Process.GetProcessesByName(ProcessName).Where(p => !p.HasExited) : Process.GetProcesses().Where(p => !p.HasExited); - return Location != null - ? processes.Any(p => string.Equals(Path.GetDirectoryName(p.GetProcessFilename()), Location, StringComparison.CurrentCultureIgnoreCase)) - : processes.Any(); + IEnumerable processes = _processMonitorService.GetRunningProcesses(); + if (ProcessName != null) + processes = processes.Where(p => string.Equals(p.ProcessName, ProcessName, StringComparison.InvariantCultureIgnoreCase)); + if (Location != null) + processes = processes.Where(p => string.Equals(Path.GetDirectoryName(p.GetProcessFilename()), Location, StringComparison.InvariantCultureIgnoreCase)); + + return processes.Any(); } /// diff --git a/src/Artemis.Core/Plugins/Modules/Attributes/DataModelIgnoreAttribute.cs b/src/Artemis.Core/Plugins/Modules/Attributes/DataModelIgnoreAttribute.cs index a9339b2ee..6f3d06387 100644 --- a/src/Artemis.Core/Plugins/Modules/Attributes/DataModelIgnoreAttribute.cs +++ b/src/Artemis.Core/Plugins/Modules/Attributes/DataModelIgnoreAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DataModelExpansions +namespace Artemis.Core.Modules { /// /// Represents an attribute that marks a data model property to be ignored by the UI diff --git a/src/Artemis.Core/Plugins/Modules/Attributes/DataModelProperty.cs b/src/Artemis.Core/Plugins/Modules/Attributes/DataModelProperty.cs index 3f6ee745a..11fe2993f 100644 --- a/src/Artemis.Core/Plugins/Modules/Attributes/DataModelProperty.cs +++ b/src/Artemis.Core/Plugins/Modules/Attributes/DataModelProperty.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DataModelExpansions +namespace Artemis.Core.Modules { /// /// Represents an attribute that describes a data model property diff --git a/src/Artemis.Core/Plugins/Modules/DataModel.cs b/src/Artemis.Core/Plugins/Modules/DataModel.cs index 1321b705a..b4c89b08b 100644 --- a/src/Artemis.Core/Plugins/Modules/DataModel.cs +++ b/src/Artemis.Core/Plugins/Modules/DataModel.cs @@ -1,21 +1,21 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; -using Artemis.Core.Modules; using Humanizer; using Newtonsoft.Json; -using Module = Artemis.Core.Modules.Module; -namespace Artemis.Core.DataModelExpansions +namespace Artemis.Core.Modules { /// /// Represents a data model that contains information on a game/application etc. /// public abstract class DataModel { + private readonly HashSet _activePathsHashSet = new(); + private readonly List _activePaths = new(); private readonly Dictionary _dynamicChildren = new(); /// @@ -54,42 +54,18 @@ namespace Artemis.Core.DataModelExpansions [DataModelIgnore] public ReadOnlyDictionary DynamicChildren => new(_dynamicChildren); + /// + /// Gets a read-only list of s targeting this data model + /// + public ReadOnlyCollection ActivePaths => _activePaths.AsReadOnly(); + /// /// Returns a read-only collection of all properties in this datamodel that are to be ignored /// /// public ReadOnlyCollection GetHiddenProperties() { - if (Module is Module module) - return module.HiddenProperties; - - return new List().AsReadOnly(); - } - - /// - /// Occurs when a dynamic child has been added to this data model - /// - public event EventHandler? DynamicChildAdded; - - /// - /// Occurs when a dynamic child has been removed from this data model - /// - public event EventHandler? DynamicChildRemoved; - - /// - /// Invokes the event - /// - protected virtual void OnDynamicDataModelAdded(DynamicDataModelChildEventArgs e) - { - DynamicChildAdded?.Invoke(this, e); - } - - /// - /// Invokes the event - /// - protected virtual void OnDynamicDataModelRemoved(DynamicDataModelChildEventArgs e) - { - DynamicChildRemoved?.Invoke(this, e); + return Module.HiddenProperties; } #region Dynamic children @@ -277,5 +253,108 @@ namespace Artemis.Core.DataModelExpansions } #endregion + + #region Paths + + /// + /// Determines whether the provided dot-separated path is in use + /// + /// The path to check per example: MyDataModelChild.MyDataModelProperty + /// + /// If any child of the given path will return true as well; if + /// only an exact path match returns . + /// + internal bool IsPropertyInUse(string path, bool includeChildren) + { + path = path.ToUpperInvariant(); + return includeChildren + ? _activePathsHashSet.Any(p => p.StartsWith(path, StringComparison.Ordinal)) + : _activePathsHashSet.Contains(path); + } + + internal void AddDataModelPath(DataModelPath path) + { + if (_activePaths.Contains(path)) + return; + + _activePaths.Add(path); + + // Add to the hashset if this is the first path pointing + string hashPath = path.Path.ToUpperInvariant(); + if (!_activePathsHashSet.Contains(hashPath)) + _activePathsHashSet.Add(hashPath); + + OnActivePathAdded(new DataModelPathEventArgs(path)); + } + + internal void RemoveDataModelPath(DataModelPath path) + { + if (!_activePaths.Remove(path)) + return; + + // Remove from the hashset if this was the last path pointing there + if (_activePaths.All(p => p.Path != path.Path)) + _activePathsHashSet.Remove(path.Path.ToUpperInvariant()); + + OnActivePathRemoved(new DataModelPathEventArgs(path)); + } + + #endregion + + #region Events + + /// + /// Occurs when a dynamic child has been added to this data model + /// + public event EventHandler? DynamicChildAdded; + + /// + /// Occurs when a dynamic child has been removed from this data model + /// + public event EventHandler? DynamicChildRemoved; + + /// + /// Occurs when a dynamic child has been added to this data model + /// + public event EventHandler? ActivePathAdded; + + /// + /// Occurs when a dynamic child has been removed from this data model + /// + public event EventHandler? ActivePathRemoved; + + /// + /// Invokes the event + /// + protected virtual void OnDynamicDataModelAdded(DynamicDataModelChildEventArgs e) + { + DynamicChildAdded?.Invoke(this, e); + } + + /// + /// Invokes the event + /// + protected virtual void OnDynamicDataModelRemoved(DynamicDataModelChildEventArgs e) + { + DynamicChildRemoved?.Invoke(this, e); + } + + /// + /// Invokes the event + /// + protected virtual void OnActivePathAdded(DataModelPathEventArgs e) + { + ActivePathAdded?.Invoke(this, e); + } + + /// + /// Invokes the event + /// + protected virtual void OnActivePathRemoved(DataModelPathEventArgs e) + { + ActivePathRemoved?.Invoke(this, e); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Modules/DynamicChild.cs b/src/Artemis.Core/Plugins/Modules/DynamicChild.cs index 5e5732b55..203562865 100644 --- a/src/Artemis.Core/Plugins/Modules/DynamicChild.cs +++ b/src/Artemis.Core/Plugins/Modules/DynamicChild.cs @@ -1,6 +1,6 @@ using System; -namespace Artemis.Core.DataModelExpansions +namespace Artemis.Core.Modules { /// /// Represents a dynamic child value with its property attribute diff --git a/src/Artemis.Core/Plugins/Modules/Module.cs b/src/Artemis.Core/Plugins/Modules/Module.cs index edf3cb340..aa2d84691 100644 --- a/src/Artemis.Core/Plugins/Modules/Module.cs +++ b/src/Artemis.Core/Plugins/Modules/Module.cs @@ -5,7 +5,7 @@ using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; -using Artemis.Core.DataModelExpansions; +using System.Text; namespace Artemis.Core.Modules { @@ -25,7 +25,8 @@ namespace Artemis.Core.Modules } /// - /// Hide the provided property using a lambda expression, e.g. HideProperty(dm => dm.TimeDataModel.CurrentTimeUTC) + /// Hide the provided property using a lambda expression, e.g. + /// HideProperty(dm => dm.TimeDataModel.CurrentTimeUTC) /// /// A lambda expression pointing to the property to ignore public void HideProperty(Expression> propertyLambda) @@ -36,8 +37,8 @@ namespace Artemis.Core.Modules } /// - /// Stop hiding the provided property using a lambda expression, e.g. ShowProperty(dm => - /// dm.TimeDataModel.CurrentTimeUTC) + /// Stop hiding the provided property using a lambda expression, e.g. + /// ShowProperty(dm => dm.TimeDataModel.CurrentTimeUTC) /// /// A lambda expression pointing to the property to stop ignoring public void ShowProperty(Expression> propertyLambda) @@ -46,6 +47,36 @@ namespace Artemis.Core.Modules HiddenPropertiesList.RemoveAll(p => p.Equals(propertyInfo)); } + /// + /// Determines whether the provided dot-separated path is actively being used by Artemis + /// Note: is slightly faster but string-based. + /// + /// + /// The path to check per example: IsPropertyInUse(dm => dm.TimeDataModel.CurrentTimeUTC) + /// + /// + /// If any child of the given path will return true as well; if + /// only an exact path match returns . + /// + public bool IsPropertyInUse(Expression> propertyLambda, bool includeChildren) + { + string path = GetMemberPath((MemberExpression) propertyLambda.Body); + return IsPropertyInUse(path, includeChildren); + } + + /// + /// Determines whether the provided dot-separated path is actively being used by Artemis + /// + /// The path to check per example: MyDataModelChild.MyDataModelProperty + /// + /// If any child of the given path will return true as well; if + /// only an exact path match returns . + /// + public bool IsPropertyInUse(string path, bool includeChildren) + { + return DataModel.IsPropertyInUse(path, includeChildren); + } + internal override void InternalEnable() { DataModel = Activator.CreateInstance(); @@ -59,6 +90,20 @@ namespace Artemis.Core.Modules Deactivate(true); base.InternalDisable(); } + + private static string GetMemberPath(MemberExpression? me) + { + StringBuilder builder = new(); + while (me != null) + { + builder.Insert(0, me.Member.Name); + me = me.Expression as MemberExpression; + if (me != null) + builder.Insert(0, "."); + } + + return builder.ToString(); + } } /// @@ -78,7 +123,7 @@ namespace Artemis.Core.Modules /// Gets a read only collection of default profile paths /// public IReadOnlyCollection<(DefaultCategoryName, string)> DefaultProfilePaths => _defaultProfilePaths.AsReadOnly(); - + /// /// A list of activation requirements /// diff --git a/src/Artemis.Core/Plugins/TimedUpdateRegistration.cs b/src/Artemis.Core/Plugins/TimedUpdateRegistration.cs index 39d5ad8cb..a2a189745 100644 --- a/src/Artemis.Core/Plugins/TimedUpdateRegistration.cs +++ b/src/Artemis.Core/Plugins/TimedUpdateRegistration.cs @@ -22,6 +22,8 @@ namespace Artemis.Core internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Action action, string? name) { + if (CoreService.Kernel == null) + throw new ArtemisCoreException("Cannot create a TimedUpdateRegistration before initializing the Core"); _logger = CoreService.Kernel.Get(); Feature = feature; @@ -37,6 +39,8 @@ namespace Artemis.Core internal TimedUpdateRegistration(PluginFeature feature, TimeSpan interval, Func asyncAction, string? name) { + if (CoreService.Kernel == null) + throw new ArtemisCoreException("Cannot create a TimedUpdateRegistration before initializing the Core"); _logger = CoreService.Kernel.Get(); Feature = feature; diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index b07ba5861..f2a279a6f 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.Linq; using System.Reflection; using System.Threading.Tasks; -using Artemis.Core.DataModelExpansions; using Artemis.Core.Ninject; using Artemis.Storage; using HidSharp; @@ -22,7 +21,7 @@ namespace Artemis.Core.Services /// internal class CoreService : ICoreService { - internal static IKernel Kernel = null!; + internal static IKernel? Kernel; private readonly Stopwatch _frameStopWatch; private readonly ILogger _logger; @@ -201,7 +200,7 @@ namespace Artemis.Core.Services 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 _pluginManagementService.CopyBuiltInPlugins(); diff --git a/src/Artemis.Core/Services/Registration/DataModelService.cs b/src/Artemis.Core/Services/Registration/DataModelService.cs index 90e1c4739..450d2f239 100644 --- a/src/Artemis.Core/Services/Registration/DataModelService.cs +++ b/src/Artemis.Core/Services/Registration/DataModelService.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules; namespace Artemis.Core.Services diff --git a/src/Artemis.Core/Services/Registration/Interfaces/IDataModelService.cs b/src/Artemis.Core/Services/Registration/Interfaces/IDataModelService.cs index 072c68601..9d5b390fc 100644 --- a/src/Artemis.Core/Services/Registration/Interfaces/IDataModelService.cs +++ b/src/Artemis.Core/Services/Registration/Interfaces/IDataModelService.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; namespace Artemis.Core.Services { diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index e65b74b4b..22d7b1c66 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; +using System.Threading; using Artemis.Core.DeviceProviders; using Artemis.Core.Services.Models; using Artemis.Core.SkiaSharp; @@ -345,6 +346,13 @@ namespace Artemis.Core.Services return layout; } + if (device.DisableDefaultLayout) + { + layout = null; + ApplyDeviceLayout(device, layout); + return null; + } + // Look for a layout provided by the plugin layout = device.DeviceProvider.LoadLayout(device); if (layout.IsValid) @@ -354,8 +362,7 @@ namespace Artemis.Core.Services } // Finally fall back to a default layout - if (!device.DisableDefaultLayout) - layout = ArtemisLayout.GetDefaultLayout(device); + layout = ArtemisLayout.GetDefaultLayout(device); ApplyDeviceLayout(device, layout); return layout; } @@ -365,7 +372,7 @@ namespace Artemis.Core.Services if (layout == null) { if (device.Layout != null) - device.ClearLayout(); + ReloadDevice(device); return; } @@ -377,6 +384,16 @@ namespace Artemis.Core.Services UpdateLedGroup(); } + private void ReloadDevice(ArtemisDevice device) + { + DeviceProvider deviceProvider = device.DeviceProvider; + + // Feels bad but need to in order to get the initial LEDs back + _pluginManagementService.DisablePluginFeature(deviceProvider, false); + Thread.Sleep(500); + _pluginManagementService.EnablePluginFeature(deviceProvider, false); + } + public ArtemisDevice? GetDevice(IRGBDevice rgbDevice) { return _devices.FirstOrDefault(d => d.RgbDevice == rgbDevice); diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 41efce23d..b3fb0c267 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -357,6 +357,8 @@ namespace Artemis.Core.Services ProfileEntity profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId); if (profileEntity != null) _profileRepository.Remove(profileEntity); + + profileConfiguration.Dispose(); } public void SaveProfileCategory(ProfileCategory profileCategory) diff --git a/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs b/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs index d0e5e206b..70a019331 100644 --- a/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs +++ b/src/Artemis.Core/Services/WebServer/EndPoints/DataModelJsonPluginEndPoint.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Threading.Tasks; -using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules; using EmbedIO; using Newtonsoft.Json; diff --git a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs index 22051d7fd..7c8e2124f 100644 --- a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; -using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules; using EmbedIO; using EmbedIO.WebApi; diff --git a/src/Artemis.Core/Services/WebServer/WebServerService.cs b/src/Artemis.Core/Services/WebServer/WebServerService.cs index 4c9272d91..0d7654812 100644 --- a/src/Artemis.Core/Services/WebServer/WebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/WebServerService.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; -using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules; using EmbedIO; using EmbedIO.WebApi; diff --git a/src/Artemis.Core/Stores/DataModelStore.cs b/src/Artemis.Core/Stores/DataModelStore.cs index 336dac6f5..160aed9c2 100644 --- a/src/Artemis.Core/Stores/DataModelStore.cs +++ b/src/Artemis.Core/Stores/DataModelStore.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; namespace Artemis.Core { diff --git a/src/Artemis.Core/Stores/Registrations/DataModelRegistration.cs b/src/Artemis.Core/Stores/Registrations/DataModelRegistration.cs index 09c162758..c1eb7f789 100644 --- a/src/Artemis.Core/Stores/Registrations/DataModelRegistration.cs +++ b/src/Artemis.Core/Stores/Registrations/DataModelRegistration.cs @@ -1,5 +1,5 @@ using System; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; namespace Artemis.Core { diff --git a/src/Artemis.Core/Utilities/ExpressionUtilities.cs b/src/Artemis.Core/Utilities/ExpressionUtilities.cs index 4db041961..e460feba1 100644 --- a/src/Artemis.Core/Utilities/ExpressionUtilities.cs +++ b/src/Artemis.Core/Utilities/ExpressionUtilities.cs @@ -1,5 +1,5 @@ using System.Linq.Expressions; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; namespace Artemis.Core { diff --git a/src/Artemis.UI.Shared/DataModelVisualization/DataModelDisplayViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/DataModelDisplayViewModel.cs index 16edde678..abbc4171d 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/DataModelDisplayViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/DataModelDisplayViewModel.cs @@ -1,5 +1,5 @@ using System.Diagnostics.CodeAnalysis; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using Stylet; namespace Artemis.UI.Shared diff --git a/src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs index 1c147ac43..1c9574dfb 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/DataModelInputViewModel.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Data; using System.Windows.Input; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using Stylet; namespace Artemis.UI.Shared diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs index fa4ad2f99..ad6697b92 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs @@ -272,6 +272,7 @@ namespace Artemis.UI.Shared.Input _updateTimer.Dispose(); _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; + DataModelViewModel?.Dispose(); DataModelPath?.Dispose(); } } diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticViewModel.cs index d909eb9ba..2ef06ec9a 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelStaticViewModel.cs @@ -4,7 +4,7 @@ using System.Windows; using System.Windows.Input; using System.Windows.Media; using Artemis.Core; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using Artemis.UI.Shared.Services; using MaterialDesignColors.ColorManipulation; using Stylet; diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelEventViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelEventViewModel.cs index 5fab8425c..55ccdf424 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelEventViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelEventViewModel.cs @@ -1,7 +1,7 @@ using System; using System.Linq; using Artemis.Core; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using Artemis.UI.Shared.Services; namespace Artemis.UI.Shared diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs index c36d7129e..09187f910 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelListViewModel.cs @@ -2,7 +2,7 @@ using System.Collections; using System.Windows.Documents; using Artemis.Core; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using Artemis.UI.Shared.Services; using Stylet; diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs index 99092a24a..423af79e9 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertiesViewModel.cs @@ -1,6 +1,6 @@ using System; using Artemis.Core; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using Artemis.UI.Shared.Services; namespace Artemis.UI.Shared diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs index 3d3b20102..5f4b35891 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelPropertyViewModel.cs @@ -1,6 +1,6 @@ using System; using Artemis.Core; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using Artemis.UI.Shared.Services; namespace Artemis.UI.Shared diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs index 78490468a..47f3341d2 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Shared/DataModelVisualizationViewModel.cs @@ -3,8 +3,9 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reflection; +using System.Text; using Artemis.Core; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using Artemis.UI.Shared.Services; using Stylet; @@ -13,7 +14,7 @@ namespace Artemis.UI.Shared /// /// Represents a base class for a view model that visualizes a part of the data model /// - public abstract class DataModelVisualizationViewModel : PropertyChangedBase + public abstract class DataModelVisualizationViewModel : PropertyChangedBase, IDisposable { private const int MaxDepth = 4; private BindableCollection _children; @@ -22,6 +23,7 @@ namespace Artemis.UI.Shared private bool _isVisualizationExpanded; private DataModelVisualizationViewModel? _parent; private DataModelPropertyAttribute? _propertyDescription; + private bool _populatedStaticChildren; internal DataModelVisualizationViewModel(DataModel? dataModel, DataModelVisualizationViewModel? parent, DataModelPath? dataModelPath) { @@ -206,21 +208,26 @@ namespace Artemis.UI.Shared if (modelType == null) throw new ArtemisSharedUIException("Failed to populate data model visualization properties, couldn't get a property type"); - // Add missing static children - foreach (PropertyInfo propertyInfo in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(t => t.MetadataToken)) + // Add missing static children only once, they're static after all + if (!_populatedStaticChildren) { - string childPath = AppendToPath(propertyInfo.Name); - if (Children.Any(c => c.Path != null && c.Path.Equals(childPath))) - continue; - if (propertyInfo.GetCustomAttribute() != null) - continue; - MethodInfo? getMethod = propertyInfo.GetGetMethod(); - if (getMethod == null || getMethod.GetParameters().Any()) - continue; + foreach (PropertyInfo propertyInfo in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(t => t.MetadataToken)) + { + string childPath = AppendToPath(propertyInfo.Name); + if (Children.Any(c => c.Path != null && c.Path.Equals(childPath))) + continue; + if (propertyInfo.GetCustomAttribute() != null) + continue; + MethodInfo? getMethod = propertyInfo.GetGetMethod(); + if (getMethod == null || getMethod.GetParameters().Any()) + continue; - DataModelVisualizationViewModel? child = CreateChild(dataModelUIService, childPath, GetChildDepth()); - if (child != null) - Children.Add(child); + DataModelVisualizationViewModel? child = CreateChild(dataModelUIService, childPath, GetChildDepth()); + if (child != null) + Children.Add(child); + } + + _populatedStaticChildren = true; } // Remove static children that should be hidden @@ -302,7 +309,14 @@ namespace Artemis.UI.Shared private string AppendToPath(string toAppend) { - return !string.IsNullOrEmpty(Path) ? $"{Path}.{toAppend}" : toAppend; + if (string.IsNullOrEmpty(Path)) + return toAppend; + + StringBuilder builder = new(); + builder.Append(Path); + builder.Append("."); + builder.Append(toAppend); + return builder.ToString(); } private void RequestUpdate() @@ -327,5 +341,33 @@ namespace Artemis.UI.Shared } #endregion + + #region IDisposable + + /// + /// Releases the unmanaged resources used by the object and optionally releases the managed resources. + /// + /// + /// to release both managed and unmanaged resources; + /// to release only unmanaged resources. + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + DataModelPath?.Dispose(); + foreach (DataModelVisualizationViewModel dataModelVisualizationViewModel in Children) + dataModelVisualizationViewModel.Dispose(true); + } + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Ninject/Factories/ISharedVMFactory.cs b/src/Artemis.UI.Shared/Ninject/Factories/ISharedVMFactory.cs index ee1c48316..21cce1e60 100644 --- a/src/Artemis.UI.Shared/Ninject/Factories/ISharedVMFactory.cs +++ b/src/Artemis.UI.Shared/Ninject/Factories/ISharedVMFactory.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules; using Artemis.UI.Shared.Input; diff --git a/src/Artemis.UI.Shared/Services/DataModelUIService.cs b/src/Artemis.UI.Shared/Services/DataModelUIService.cs index 6066de626..dc8e5ca7e 100644 --- a/src/Artemis.UI.Shared/Services/DataModelUIService.cs +++ b/src/Artemis.UI.Shared/Services/DataModelUIService.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using Artemis.Core; -using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules; using Artemis.Core.Services; using Artemis.UI.Shared.DefaultTypes.DataModel.Display; diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs index 5da6a381b..2c3f6efdb 100644 --- a/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs +++ b/src/Artemis.UI.Shared/Services/Interfaces/IDataModelUIService.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Artemis.Core; -using Artemis.Core.DataModelExpansions; using Artemis.Core.Modules; using Artemis.UI.Shared.Input; diff --git a/src/Artemis.UI/ApplicationStateManager.cs b/src/Artemis.UI/ApplicationStateManager.cs index 2a08b4a3a..45dc2098c 100644 --- a/src/Artemis.UI/ApplicationStateManager.cs +++ b/src/Artemis.UI/ApplicationStateManager.cs @@ -7,10 +7,8 @@ using System.Net.Http; using System.Reflection; using System.Security.Principal; using System.Threading; -using System.Threading.Tasks; using System.Windows; using Artemis.Core; -using Artemis.UI.Screens; using Artemis.UI.Utilities; using Ninject; using Ookii.Dialogs.Wpf; @@ -44,30 +42,82 @@ namespace Artemis.UI if (createdNew) return false; - try - { - // Blocking is required here otherwise Artemis shuts down before the remote call gets a chance to finish - RemoteFocus().GetAwaiter().GetResult(); - } - catch (Exception) - { - // Not much could go wrong here but this code runs so early it'll crash if something does go wrong - return true; - } - - return true; + return RemoteFocus(); } - private async Task RemoteFocus() + public void DisplayException(Exception e) + { + using TaskDialog dialog = new(); + AssemblyInformationalVersionAttribute versionAttribute = typeof(ApplicationStateManager).Assembly.GetCustomAttribute(); + dialog.WindowTitle = $"Artemis {versionAttribute?.InformationalVersion} build {Constants.BuildInfo.BuildNumberDisplay}"; + dialog.MainInstruction = "Unfortunately Artemis ran into an unhandled exception and cannot continue."; + dialog.Content = e.Message; + dialog.ExpandedInformation = e.StackTrace.Trim(); + + dialog.CollapsedControlText = "Show stack trace"; + dialog.ExpandedControlText = "Hide stack trace"; + + dialog.Footer = "If this keeps happening check out the wiki or hit us up on Discord."; + dialog.FooterIcon = TaskDialogIcon.Error; + dialog.EnableHyperlinks = true; + dialog.HyperlinkClicked += OpenHyperlink; + + TaskDialogButton copyButton = new("Copy stack trace"); + TaskDialogButton closeButton = new("Close") {Default = true}; + dialog.Buttons.Add(copyButton); + dialog.Buttons.Add(closeButton); + dialog.ButtonClicked += (_, args) => + { + if (args.Item == copyButton) + { + Clipboard.SetText(e.ToString()); + args.Cancel = true; + } + }; + + dialog.ShowDialog(Application.Current.MainWindow); + } + + private bool RemoteFocus() { // At this point we cannot read the database yet to retrieve the web server port. // Instead use the method external applications should use as well. if (!File.Exists(Path.Combine(Constants.DataFolder, "webserver.txt"))) - return; + { + KillOtherInstances(); + return false; + } - string url = await File.ReadAllTextAsync(Path.Combine(Constants.DataFolder, "webserver.txt")); + string url = File.ReadAllText(Path.Combine(Constants.DataFolder, "webserver.txt")); using HttpClient client = new(); - await client.PostAsync(url + "remote/bring-to-foreground", null!); + try + { + HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground")); + httpResponseMessage.EnsureSuccessStatusCode(); + return true; + } + catch (Exception) + { + KillOtherInstances(); + return false; + } + } + + private void KillOtherInstances() + { + // Kill everything else heh + List processes = Process.GetProcessesByName("Artemis.UI").Where(p => p.Id != Process.GetCurrentProcess().Id).ToList(); + foreach (Process process in processes) + { + try + { + process.Kill(true); + } + catch (Exception) + { + // ignored + } + } } private void UtilitiesOnRestartRequested(object sender, RestartEventArgs e) @@ -134,39 +184,6 @@ namespace Artemis.UI Execute.OnUIThread(() => Application.Current.Shutdown()); } - public void DisplayException(Exception e) - { - using TaskDialog dialog = new(); - AssemblyInformationalVersionAttribute versionAttribute = typeof(ApplicationStateManager).Assembly.GetCustomAttribute(); - dialog.WindowTitle = $"Artemis {versionAttribute?.InformationalVersion} build {Constants.BuildInfo.BuildNumberDisplay}"; - dialog.MainInstruction = "Unfortunately Artemis ran into an unhandled exception and cannot continue."; - dialog.Content = e.Message; - dialog.ExpandedInformation = e.StackTrace.Trim(); - - dialog.CollapsedControlText = "Show stack trace"; - dialog.ExpandedControlText = "Hide stack trace"; - - dialog.Footer = "If this keeps happening check out the wiki or hit us up on Discord."; - dialog.FooterIcon = TaskDialogIcon.Error; - dialog.EnableHyperlinks = true; - dialog.HyperlinkClicked += OpenHyperlink; - - TaskDialogButton copyButton = new("Copy stack trace"); - TaskDialogButton closeButton = new("Close") {Default = true}; - dialog.Buttons.Add(copyButton); - dialog.Buttons.Add(closeButton); - dialog.ButtonClicked += (_, args) => - { - if (args.Item == copyButton) - { - Clipboard.SetText(e.ToString()); - args.Cancel = true; - } - }; - - dialog.ShowDialog(Application.Current.MainWindow); - } - private void OpenHyperlink(object sender, HyperlinkClickedEventArgs e) { ProcessStartInfo processInfo = new() diff --git a/src/Artemis.UI/DefaultTypes/DataModel/Input/BoolDataModelInputViewModel.cs b/src/Artemis.UI/DefaultTypes/DataModel/Input/BoolDataModelInputViewModel.cs index d4f966351..8b65a2075 100644 --- a/src/Artemis.UI/DefaultTypes/DataModel/Input/BoolDataModelInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/DataModel/Input/BoolDataModelInputViewModel.cs @@ -1,4 +1,4 @@ -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using Artemis.UI.Shared; namespace Artemis.UI.DefaultTypes.DataModel.Input diff --git a/src/Artemis.UI/DefaultTypes/DataModel/Input/DoubleDataModelInputViewModel.cs b/src/Artemis.UI/DefaultTypes/DataModel/Input/DoubleDataModelInputViewModel.cs index 4fe8e02ef..a0e946d89 100644 --- a/src/Artemis.UI/DefaultTypes/DataModel/Input/DoubleDataModelInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/DataModel/Input/DoubleDataModelInputViewModel.cs @@ -1,7 +1,7 @@ using System.Globalization; using System.Text.RegularExpressions; using System.Windows.Input; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using Artemis.UI.Shared; namespace Artemis.UI.DefaultTypes.DataModel.Input diff --git a/src/Artemis.UI/DefaultTypes/DataModel/Input/EnumDataModelInputViewModel.cs b/src/Artemis.UI/DefaultTypes/DataModel/Input/EnumDataModelInputViewModel.cs index ec374e67a..e995a49c3 100644 --- a/src/Artemis.UI/DefaultTypes/DataModel/Input/EnumDataModelInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/DataModel/Input/EnumDataModelInputViewModel.cs @@ -1,5 +1,5 @@ using System; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using Artemis.UI.Shared; using Stylet; diff --git a/src/Artemis.UI/DefaultTypes/DataModel/Input/IntDataModelInputViewModel.cs b/src/Artemis.UI/DefaultTypes/DataModel/Input/IntDataModelInputViewModel.cs index 4a9ba6487..13faa7e2f 100644 --- a/src/Artemis.UI/DefaultTypes/DataModel/Input/IntDataModelInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/DataModel/Input/IntDataModelInputViewModel.cs @@ -1,6 +1,6 @@ using System.Text.RegularExpressions; using System.Windows.Input; -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using Artemis.UI.Shared; namespace Artemis.UI.DefaultTypes.DataModel.Input diff --git a/src/Artemis.UI/DefaultTypes/DataModel/Input/SKColorDataModelInputViewModel.cs b/src/Artemis.UI/DefaultTypes/DataModel/Input/SKColorDataModelInputViewModel.cs index 02c1beb5f..93aafd576 100644 --- a/src/Artemis.UI/DefaultTypes/DataModel/Input/SKColorDataModelInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/DataModel/Input/SKColorDataModelInputViewModel.cs @@ -1,4 +1,4 @@ -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using Artemis.UI.Shared; using SkiaSharp; diff --git a/src/Artemis.UI/DefaultTypes/DataModel/Input/StringDataModelInputViewModel.cs b/src/Artemis.UI/DefaultTypes/DataModel/Input/StringDataModelInputViewModel.cs index 284a3fd08..450664b01 100644 --- a/src/Artemis.UI/DefaultTypes/DataModel/Input/StringDataModelInputViewModel.cs +++ b/src/Artemis.UI/DefaultTypes/DataModel/Input/StringDataModelInputViewModel.cs @@ -1,4 +1,4 @@ -using Artemis.Core.DataModelExpansions; +using Artemis.Core.Modules; using Artemis.UI.Shared; namespace Artemis.UI.DefaultTypes.DataModel.Input diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs index f566bf8d2..5aa6a6515 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/LayerPropertiesViewModel.cs @@ -9,6 +9,7 @@ using System.Windows.Media; using Artemis.Core; using Artemis.Core.LayerEffects; using Artemis.Core.Services; +using Artemis.UI.Extensions; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings; using Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects; @@ -220,7 +221,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties private void SelectedProfileEditorServiceOnSelectedProfileElementChanged(object sender, RenderProfileElementEventArgs e) { - PopulateProperties(e.RenderProfileElement); + Execute.PostToUIThread(() => PopulateProperties(e.RenderProfileElement)); } private void ProfileEditorServiceOnCurrentTimeChanged(object sender, EventArgs e) @@ -347,7 +348,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties .ToList(); // Order the effects List effectProperties = Items - .Where(l => l.TreeGroupViewModel.GroupType == LayerEffectRoot) + .Where(l => l.TreeGroupViewModel.GroupType == LayerEffectRoot && l.LayerPropertyGroup.LayerEffect != null) .OrderBy(l => l.LayerPropertyGroup.LayerEffect.Order) .ToList(); @@ -355,14 +356,16 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties for (int index = 0; index < nonEffectProperties.Count; index++) { LayerPropertyGroupViewModel layerPropertyGroupViewModel = nonEffectProperties[index]; - ((BindableCollection) Items).Move(Items.IndexOf(layerPropertyGroupViewModel), index); + if (Items.IndexOf(layerPropertyGroupViewModel) != index) + ((BindableCollection) Items).Move(Items.IndexOf(layerPropertyGroupViewModel), index); } // Put the effect properties after, sorted by their order for (int index = 0; index < effectProperties.Count; index++) { LayerPropertyGroupViewModel layerPropertyGroupViewModel = effectProperties[index]; - ((BindableCollection) Items).Move(Items.IndexOf(layerPropertyGroupViewModel), index + nonEffectProperties.Count); + if (Items.IndexOf(layerPropertyGroupViewModel) != index + nonEffectProperties.Count) + ((BindableCollection) Items).Move(Items.IndexOf(layerPropertyGroupViewModel), index + nonEffectProperties.Count); } } @@ -527,7 +530,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties Repeating = false; } } - + private TimeSpan GetCurrentSegmentStart() { TimeSpan current = ProfileEditorService.CurrentTime; @@ -637,7 +640,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties public void TimelineJump(object sender, MouseButtonEventArgs e) { // Get the parent grid, need that for our position - IInputElement parent = (IInputElement)VisualTreeHelper.GetParent((DependencyObject)sender); + IInputElement parent = (IInputElement) VisualTreeHelper.GetParent((DependencyObject) sender); double x = Math.Max(0, e.GetPosition(parent).X); TimeSpan newTime = TimeSpan.FromSeconds(x / ProfileEditorService.PixelsPerSecond); diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeView.xaml b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeView.xaml index a949eee63..c3388512a 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeView.xaml @@ -15,6 +15,15 @@ d:DataContext="{d:DesignInstance {x:Type profileTree1:ProfileTreeViewModel}}"> + + + + + + + @@ -33,7 +42,10 @@ dd:DragDrop.IsDragSource="True" dd:DragDrop.IsDropTarget="True" dd:DragDrop.DropHandler="{Binding}" - ContextMenuOpening="{s:Action ContextMenuOpening}"> + dd:DragDrop.DragAdornerTemplate="{StaticResource ElementDragTemplate}" + ContextMenuOpening="{s:Action ContextMenuOpening}" + PreviewMouseDown="{s:Action TreeViewPreviewMouseDown}" + PreviewMouseUp="{s:Action TreeViewPreviewMouseUp}"> diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs index ea167ba29..9156b86f1 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/ProfileTreeViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Windows; +using System.Windows.Input; using Artemis.Core; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem; @@ -15,6 +16,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree { private readonly IProfileEditorService _profileEditorService; private readonly IProfileTreeVmFactory _profileTreeVmFactory; + private bool _draggingTreeView; private TreeItemViewModel _selectedTreeItem; private bool _updatingTree; @@ -31,18 +33,24 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree get => _selectedTreeItem; set { - if (_updatingTree) return; - if (!SetAndNotify(ref _selectedTreeItem, value)) return; - - if (value != null && value.ProfileElement is RenderProfileElement renderElement) - _profileEditorService.ChangeSelectedProfileElement(renderElement); - else - _profileEditorService.ChangeSelectedProfileElement(null); + if (!_updatingTree && SetAndNotify(ref _selectedTreeItem, value) && !_draggingTreeView) + ApplySelectedTreeItem(); } } public bool CanPasteElement => _profileEditorService.GetCanPasteProfileElement(); + public void TreeViewPreviewMouseDown(object sender, MouseButtonEventArgs e) + { + _draggingTreeView = true; + } + + public void TreeViewPreviewMouseUp(object sender, MouseButtonEventArgs e) + { + _draggingTreeView = false; + ApplySelectedTreeItem(); + } + protected override void OnInitialActivate() { Subscribe(); @@ -55,6 +63,14 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree base.OnClose(); } + private void ApplySelectedTreeItem() + { + if (_selectedTreeItem != null && _selectedTreeItem.ProfileElement is RenderProfileElement renderElement) + _profileEditorService.ChangeSelectedProfileElement(renderElement); + else + _profileEditorService.ChangeSelectedProfileElement(null); + } + private void CreateRootFolderViewModel() { _updatingTree = true; @@ -64,7 +80,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree ActivateItem(null); return; } - + ActiveItem = _profileTreeVmFactory.FolderViewModel(folder); _updatingTree = false; } diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs index 2ae8702b1..e4309201c 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileTree/TreeItem/TreeItemViewModel.cs @@ -184,6 +184,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem else newSelection = ProfileElement.Parent; } + _profileEditorService.ChangeSelectedProfileElement(newSelection as RenderProfileElement); // Farewell, cruel world TreeItemViewModel parent = (TreeItemViewModel) Parent; @@ -191,7 +192,6 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree.TreeItem parent.RemoveExistingElement(this); _profileEditorService.SaveSelectedProfileConfiguration(); - _profileEditorService.ChangeSelectedProfileElement(newSelection as RenderProfileElement); } public void DuplicateElement() diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs index 32dbb01b5..7825cc03b 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/DataModelDebugViewModel.cs @@ -87,7 +87,7 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs protected override void OnClose() { _updateTimer.Dispose(); - base.OnClose(); + base.OnClose(); } #endregion @@ -109,6 +109,9 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs { _updateTimer.Stop(); _updateTimer.Elapsed -= OnUpdateTimerOnElapsed; + MainDataModel?.Dispose(); + MainDataModel = null; + _pluginManagementService.PluginFeatureEnabled -= OnPluginFeatureToggled; _pluginManagementService.PluginFeatureDisabled -= OnPluginFeatureToggled; diff --git a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs index 0cd156be7..97f47d8c7 100644 --- a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs @@ -25,9 +25,11 @@ namespace Artemis.UI.Screens.Settings.Device private readonly ICoreService _coreService; private readonly IDeviceService _deviceService; private readonly IDialogService _dialogService; + private readonly IDeviceDebugVmFactory _factory; private readonly IRgbService _rgbService; private SnackbarMessageQueue _deviceMessageQueue; private BindableCollection _selectedLeds; + private ArtemisDevice _device; public DeviceDialogViewModel(ArtemisDevice device, ICoreService coreService, @@ -40,22 +42,46 @@ namespace Artemis.UI.Screens.Settings.Device _deviceService = deviceService; _rgbService = rgbService; _dialogService = dialogService; + _factory = factory; - Device = device; PanZoomViewModel = new PanZoomViewModel(); SelectedLeds = new BindableCollection(); - Items.Add(factory.DevicePropertiesTabViewModel(device)); - if (device.DeviceType == RGBDeviceType.Keyboard) - Items.Add(factory.InputMappingsTabViewModel(device)); - Items.Add(factory.DeviceInfoTabViewModel(device)); - Items.Add(factory.DeviceLedsTabViewModel(device)); - - ActiveItem = Items.First(); - DisplayName = $"{device.RgbDevice.DeviceInfo.Model} | Artemis"; + Initialize(device); + } + + private void Initialize(ArtemisDevice device) + { + if (SelectedLeds.Any()) + SelectedLeds.Clear(); + + if (Device != null) + Device.DeviceUpdated -= DeviceOnDeviceUpdated; + Device = device; + Device.DeviceUpdated += DeviceOnDeviceUpdated; + + int activeTabIndex = 0; + if (Items.Any()) + { + activeTabIndex = Items.IndexOf(ActiveItem); + Items.Clear(); + } + Items.Add(_factory.DevicePropertiesTabViewModel(Device)); + if (Device.DeviceType == RGBDeviceType.Keyboard) + Items.Add(_factory.InputMappingsTabViewModel(Device)); + Items.Add(_factory.DeviceInfoTabViewModel(Device)); + Items.Add(_factory.DeviceLedsTabViewModel(Device)); + + ActiveItem = Items[activeTabIndex]; + DisplayName = $"{Device.RgbDevice.DeviceInfo.Model} | Artemis"; + } + + public ArtemisDevice Device + { + get => _device; + set => SetAndNotify(ref _device, value); } - public ArtemisDevice Device { get; } public PanZoomViewModel PanZoomViewModel { get; } public SnackbarMessageQueue DeviceMessageQueue @@ -84,14 +110,21 @@ namespace Artemis.UI.Screens.Settings.Device protected override void OnInitialActivate() { _coreService.FrameRendering += CoreServiceOnFrameRendering; + _rgbService.DeviceAdded += RgbServiceOnDeviceAdded; DeviceMessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(5)); - Device.DeviceUpdated += DeviceOnDeviceUpdated; base.OnInitialActivate(); } + private void RgbServiceOnDeviceAdded(object sender, DeviceEventArgs e) + { + if (e.Device != Device && e.Device.Identifier == Device.Identifier) + Execute.OnUIThread(() => Initialize(e.Device)); + } + protected override void OnClose() { _coreService.FrameRendering -= CoreServiceOnFrameRendering; + _rgbService.DeviceAdded -= RgbServiceOnDeviceAdded; Device.DeviceUpdated -= DeviceOnDeviceUpdated; base.OnClose(); } diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabView.xaml b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabView.xaml index 344e87fee..f1ff314ac 100644 --- a/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabView.xaml +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabView.xaml @@ -202,12 +202,12 @@ The device layout is used to determine the position of LEDs and to create the visual representation of the device you see on the left side of this window. - + - Use default layout if needed + Don't load default layout + ToolTip="With this enabled Artemis will not load a layout for this device unless you specifically provide one." /> diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs index af4f44aa4..244b7e448 100644 --- a/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs @@ -101,7 +101,7 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs get => _displayOnDevices; set => SetAndNotify(ref _displayOnDevices, value); } - + // This solution won't scale well but I don't expect there to be many more categories. // If for some reason there will be, dynamically creating a view model per category may be more appropriate public bool HasDeskCategory @@ -134,16 +134,6 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs set => SetCategory(DeviceCategory.Peripherals, value); } - public bool UseDefaultLayout - { - get => !Device.DisableDefaultLayout; - set - { - Device.DisableDefaultLayout = !value; - NotifyOfPropertyChange(nameof(UseDefaultLayout)); - } - } - public void ApplyScaling() { Device.RedScale = RedScale / 100f; @@ -266,7 +256,9 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs private void DeviceOnPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(Device.CustomLayoutPath) || e.PropertyName == nameof(Device.DisableDefaultLayout)) - _rgbService.ApplyBestDeviceLayout(Device); + { + Task.Run(() => _rgbService.ApplyBestDeviceLayout(Device)); + } } private void OnFrameRendering(object sender, FrameRenderingEventArgs e) diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsTabViewModel.cs index ee9c2f793..e775065a3 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Devices/DeviceSettingsTabViewModel.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; using Artemis.UI.Shared.Services; @@ -10,9 +11,9 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices { public class DeviceSettingsTabViewModel : Conductor.Collection.AllActive { - private readonly ISettingsVmFactory _settingsVmFactory; - private readonly IRgbService _rgbService; private readonly IDialogService _dialogService; + private readonly IRgbService _rgbService; + private readonly ISettingsVmFactory _settingsVmFactory; private bool _confirmedDisable; public DeviceSettingsTabViewModel(IRgbService rgbService, IDialogService dialogService, ISettingsVmFactory settingsVmFactory) @@ -24,28 +25,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices _settingsVmFactory = settingsVmFactory; } - #region Overrides of AllActive - - /// - protected override void OnActivate() - { - // Take it off the UI thread to avoid freezing on tab change - Task.Run(async () => - { - if (Items.Any()) - Items.Clear(); - - await Task.Delay(200); - - List instances = _rgbService.Devices.Select(d => _settingsVmFactory.CreateDeviceSettingsViewModel(d)).ToList(); - foreach (DeviceSettingsViewModel deviceSettingsViewModel in instances) - Items.Add(deviceSettingsViewModel); - }); - base.OnActivate(); - } - - #endregion - public async Task ShowDeviceDisableDialog() { if (_confirmedDisable) @@ -61,5 +40,49 @@ namespace Artemis.UI.Screens.Settings.Tabs.Devices return confirmed; } + + private void RgbServiceOnDeviceRemoved(object sender, DeviceEventArgs e) + { + DeviceSettingsViewModel viewModel = Items.FirstOrDefault(i => i.Device == e.Device); + if (viewModel != null) + Items.Remove(viewModel); + } + + private void RgbServiceOnDeviceAdded(object sender, DeviceEventArgs e) + { + Items.Add(_settingsVmFactory.CreateDeviceSettingsViewModel(e.Device)); + } + + #region Overrides of AllActive + + /// + protected override void OnActivate() + { + _rgbService.DeviceAdded += RgbServiceOnDeviceAdded; + _rgbService.DeviceRemoved += RgbServiceOnDeviceRemoved; + // Take it off the UI thread to avoid freezing on tab change + Task.Run(async () => + { + if (Items.Any()) + Items.Clear(); + + await Task.Delay(200); + + List instances = _rgbService.Devices.Select(d => _settingsVmFactory.CreateDeviceSettingsViewModel(d)).ToList(); + foreach (DeviceSettingsViewModel deviceSettingsViewModel in instances) + Items.Add(deviceSettingsViewModel); + }); + base.OnActivate(); + } + + /// + protected override void OnClose() + { + _rgbService.DeviceAdded -= RgbServiceOnDeviceAdded; + _rgbService.DeviceRemoved -= RgbServiceOnDeviceRemoved; + base.OnClose(); + } + + #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml index 7153e747c..504f87002 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabView.xaml @@ -218,27 +218,6 @@ - - - - - - - - - - - Automatically install updates - - If enabled updates are installed automatically without asking first. - - - - - - - - diff --git a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs index 2f8978ef4..895d903a0 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/General/GeneralSettingsTabViewModel.cs @@ -158,20 +158,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.General _settingsService.GetSetting("UI.CheckForUpdates", true).Value = value; _settingsService.GetSetting("UI.CheckForUpdates", true).Save(); NotifyOfPropertyChange(nameof(CheckForUpdates)); - - if (!value) - AutoInstallUpdates = false; - } - } - - public bool AutoInstallUpdates - { - get => _settingsService.GetSetting("UI.AutoInstallUpdates", false).Value; - set - { - _settingsService.GetSetting("UI.AutoInstallUpdates", false).Value = value; - _settingsService.GetSetting("UI.AutoInstallUpdates", false).Save(); - NotifyOfPropertyChange(nameof(AutoInstallUpdates)); } } diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml b/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml index 85ede98d4..209f1b250 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml +++ b/src/Artemis.UI/Screens/Sidebar/SidebarView.xaml @@ -91,14 +91,10 @@ - - - - - + + diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs index 7ed65869e..a80f0f929 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/SidebarViewModel.cs @@ -69,7 +69,7 @@ namespace Artemis.UI.Screens.Sidebar private void UpdateHeaderDevice() { - HeaderDevice = _rgbService.Devices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Keyboard && d.Layout.IsValid); + HeaderDevice = _rgbService.Devices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Keyboard && d.Layout is {IsValid: true}); } public ArtemisDevice HeaderDevice diff --git a/src/Artemis.UI/Services/UpdateService.cs b/src/Artemis.UI/Services/UpdateService.cs index 35dada594..5e3e481ee 100644 --- a/src/Artemis.UI/Services/UpdateService.cs +++ b/src/Artemis.UI/Services/UpdateService.cs @@ -28,7 +28,6 @@ namespace Artemis.UI.Services private const double UpdateCheckInterval = 3600000; // once per hour private const string ApiUrl = "https://dev.azure.com/artemis-rgb/Artemis/_apis/"; - private readonly PluginSetting _autoInstallUpdates; private readonly PluginSetting _checkForUpdates; private readonly IDialogService _dialogService; private readonly ILogger _logger; @@ -42,7 +41,6 @@ namespace Artemis.UI.Services _windowService.MainWindowOpened += WindowServiceOnMainWindowOpened; _checkForUpdates = settingsService.GetSetting("UI.CheckForUpdates", true); - _autoInstallUpdates = settingsService.GetSetting("UI.AutoInstallUpdates", false); _checkForUpdates.SettingChanged += CheckForUpdatesOnSettingChanged; Timer timer = new(UpdateCheckInterval); @@ -136,18 +134,7 @@ namespace Artemis.UI.Services if (_windowService.IsMainWindowOpen) await OfferUpdate(buildInfo); - else if (_autoInstallUpdates.Value) - { - new ToastContentBuilder() - .AddText("Installing new version", AdaptiveTextStyle.Header) - .AddText($"Build {buildNumberDisplay} is available, currently on {Constants.BuildInfo.BuildNumberDisplay}.") - .AddProgressBar(null, null, true) - .Show(); - - await ApplyUpdate(); - } else - { // If auto-install is disabled and the window is closed, best we can do is notify the user and stop. new ToastContentBuilder() .AddText("New version available", AdaptiveTextStyle.Header) @@ -155,7 +142,6 @@ namespace Artemis.UI.Services .AddButton("Update", ToastActivationType.Background, "update") .AddButton("Later", ToastActivationType.Background, "later") .Show(t => t.Activated += TOnActivated); - } return true; }