mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 03:48:56 +00:00
Profile adaption - Added support for device hint to 'all' devices
Layouts - Round LED sizes to integers Auto-update - Use modern Windows 10 toasts UI - On shutdown wait 6 seconds longer before force-shutdown UI - Use black icons in notifications on white Windows theme Message service - Added optional toast callbacks
This commit is contained in:
1 parent
5c337e9c5e
commit
96c55e5c03
26 files changed
+886
-674
No files matched your search
@@ -48,7 +48,7 @@ namespace Artemis.Core
|
||||
public void Apply(Layer layer, List<ArtemisDevice> devices)
|
||||
{
|
||||
IEnumerable<ArtemisDevice> matches = devices
|
||||
.Where(d => d.RgbDevice.DeviceInfo.DeviceType == DeviceType)
|
||||
.Where(d => DeviceType == RGBDeviceType.All || d.RgbDevice.DeviceInfo.DeviceType == DeviceType)
|
||||
.OrderBy(d => d.Rectangle.Top)
|
||||
.ThenBy(d => d.Rectangle.Left)
|
||||
.Skip(Skip);
|
||||
|
||||
@@ -66,8 +66,8 @@ namespace Artemis.Core
|
||||
|
||||
_leds = new List<ArtemisLed>();
|
||||
|
||||
Load();
|
||||
Adapter = new LayerAdapter(this);
|
||||
Load();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
@@ -242,6 +242,7 @@ namespace Artemis.Core
|
||||
|
||||
ExpandedPropertyGroups.AddRange(LayerEntity.ExpandedPropertyGroups);
|
||||
LoadRenderElement();
|
||||
Adapter.Load();
|
||||
}
|
||||
|
||||
internal override void Save()
|
||||
@@ -276,6 +277,9 @@ namespace Artemis.Core
|
||||
LayerEntity.Leds.Add(ledEntity);
|
||||
}
|
||||
|
||||
// Adaption hints
|
||||
Adapter.Save();
|
||||
|
||||
SaveRenderElement();
|
||||
}
|
||||
|
||||
|
||||
@@ -70,33 +70,42 @@ namespace Artemis.Core
|
||||
public List<IAdaptionHint> DetermineHints(IEnumerable<ArtemisDevice> devices)
|
||||
{
|
||||
List<IAdaptionHint> newHints = new();
|
||||
// Any fully covered device will add a device adaption hint for that type
|
||||
foreach (IGrouping<ArtemisDevice, ArtemisLed> deviceLeds in Layer.Leds.GroupBy(l => l.Device))
|
||||
if (devices.All(DoesLayerCoverDevice))
|
||||
{
|
||||
ArtemisDevice device = deviceLeds.Key;
|
||||
// If there is already an adaption hint for this type, don't add another
|
||||
if (AdaptionHints.Any(h => h is DeviceAdaptionHint d && d.DeviceType == device.RgbDevice.DeviceInfo.DeviceType))
|
||||
continue;
|
||||
if (DoesLayerCoverDevice(device))
|
||||
{
|
||||
DeviceAdaptionHint hint = new() {DeviceType = device.RgbDevice.DeviceInfo.DeviceType};
|
||||
AdaptionHints.Add(hint);
|
||||
newHints.Add(hint);
|
||||
}
|
||||
DeviceAdaptionHint hint = new() {DeviceType = RGBDeviceType.All};
|
||||
AdaptionHints.Add(hint);
|
||||
newHints.Add(hint);
|
||||
}
|
||||
|
||||
// Any fully covered category will add a category adaption hint for its category
|
||||
foreach (DeviceCategory deviceCategory in Enum.GetValues<DeviceCategory>())
|
||||
else
|
||||
{
|
||||
if (AdaptionHints.Any(h => h is CategoryAdaptionHint c && c.Category == deviceCategory))
|
||||
continue;
|
||||
|
||||
List<ArtemisDevice> categoryDevices = devices.Where(d => d.Categories.Contains(deviceCategory)).ToList();
|
||||
if (categoryDevices.Any() && categoryDevices.All(DoesLayerCoverDevice))
|
||||
// Any fully covered device will add a device adaption hint for that type
|
||||
foreach (IGrouping<ArtemisDevice, ArtemisLed> deviceLeds in Layer.Leds.GroupBy(l => l.Device))
|
||||
{
|
||||
CategoryAdaptionHint hint = new() {Category = deviceCategory};
|
||||
AdaptionHints.Add(hint);
|
||||
newHints.Add(hint);
|
||||
ArtemisDevice device = deviceLeds.Key;
|
||||
// If there is already an adaption hint for this type, don't add another
|
||||
if (AdaptionHints.Any(h => h is DeviceAdaptionHint d && d.DeviceType == device.RgbDevice.DeviceInfo.DeviceType))
|
||||
continue;
|
||||
if (DoesLayerCoverDevice(device))
|
||||
{
|
||||
DeviceAdaptionHint hint = new() {DeviceType = device.RgbDevice.DeviceInfo.DeviceType};
|
||||
AdaptionHints.Add(hint);
|
||||
newHints.Add(hint);
|
||||
}
|
||||
}
|
||||
|
||||
// Any fully covered category will add a category adaption hint for its category
|
||||
foreach (DeviceCategory deviceCategory in Enum.GetValues<DeviceCategory>())
|
||||
{
|
||||
if (AdaptionHints.Any(h => h is CategoryAdaptionHint c && c.Category == deviceCategory))
|
||||
continue;
|
||||
|
||||
List<ArtemisDevice> categoryDevices = devices.Where(d => d.Categories.Contains(deviceCategory)).ToList();
|
||||
if (categoryDevices.Any() && categoryDevices.All(DoesLayerCoverDevice))
|
||||
{
|
||||
CategoryAdaptionHint hint = new() {Category = deviceCategory};
|
||||
AdaptionHints.Add(hint);
|
||||
newHints.Add(hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Artemis.Core
|
||||
internal ProfileDescriptor(ProfileModule profileModule, ProfileEntity profileEntity)
|
||||
{
|
||||
ProfileModule = profileModule;
|
||||
|
||||
|
||||
Id = profileEntity.Id;
|
||||
Name = profileEntity.Name;
|
||||
IsLastActiveProfile = profileEntity.IsActive;
|
||||
@@ -38,5 +38,9 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public bool IsLastActiveProfile { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the profile will be adapted the next time it is activated
|
||||
/// </summary>
|
||||
public bool NeedsAdaption { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -381,7 +381,8 @@ namespace Artemis.Core
|
||||
"set to true because the device provider does not support it");
|
||||
|
||||
if (layout.IsValid)
|
||||
layout.RgbLayout!.ApplyTo(RgbDevice, createMissingLeds, removeExcessiveLeds);
|
||||
layout.ApplyTo(RgbDevice, createMissingLeds, removeExcessiveLeds);
|
||||
|
||||
|
||||
UpdateLeds();
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using RGB.NET.Core;
|
||||
using RGB.NET.Layout;
|
||||
|
||||
namespace Artemis.Core
|
||||
@@ -107,6 +108,44 @@ namespace Artemis.Core
|
||||
else
|
||||
Image = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the layout to the provided device
|
||||
/// </summary>
|
||||
public void ApplyTo(IRGBDevice device, bool createMissingLeds = false, bool removeExcessiveLeds = false)
|
||||
{
|
||||
device.Size = new Size(MathF.Round(RgbLayout.Width), MathF.Round(RgbLayout.Height));
|
||||
device.DeviceInfo.LayoutMetadata = RgbLayout.CustomData;
|
||||
|
||||
HashSet<LedId> ledIds = new();
|
||||
foreach (ILedLayout layoutLed in RgbLayout.Leds)
|
||||
{
|
||||
if (Enum.TryParse(layoutLed.Id, true, out LedId ledId))
|
||||
{
|
||||
ledIds.Add(ledId);
|
||||
|
||||
Led? led = device[ledId];
|
||||
if ((led == null) && createMissingLeds)
|
||||
led = device.AddLed(ledId, new Point(), new Size());
|
||||
|
||||
if (led != null)
|
||||
{
|
||||
led.Location = new Point(MathF.Round(layoutLed.X), MathF.Round(layoutLed.Y));
|
||||
led.Size = new Size(MathF.Round(layoutLed.Width), MathF.Round(layoutLed.Height));
|
||||
led.Shape = layoutLed.Shape;
|
||||
led.ShapeData = layoutLed.ShapeData;
|
||||
led.LayoutMetadata = layoutLed.CustomData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (removeExcessiveLeds)
|
||||
{
|
||||
List<LedId> ledsToRemove = device.Select(led => led.Id).Where(id => !ledIds.Contains(id)).ToList();
|
||||
foreach (LedId led in ledsToRemove)
|
||||
device.RemoveLed(led);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core.DataModelExpansions;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Newtonsoft.Json;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.Modules
|
||||
@@ -91,11 +94,13 @@ namespace Artemis.Core.Modules
|
||||
/// </summary>
|
||||
public abstract class ProfileModule : Module
|
||||
{
|
||||
private readonly List<ProfileDescriptor> _defaultProfiles;
|
||||
private readonly object _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all properties ignored at runtime using <c>IgnoreProperty(x => x.y)</c>
|
||||
/// </summary>
|
||||
protected internal readonly List<PropertyInfo> HiddenPropertiesList = new();
|
||||
private readonly object _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ProfileModule" /> class
|
||||
@@ -103,6 +108,7 @@ namespace Artemis.Core.Modules
|
||||
protected ProfileModule()
|
||||
{
|
||||
OpacityOverride = 1;
|
||||
_defaultProfiles = new List<ProfileDescriptor>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -130,6 +136,11 @@ namespace Artemis.Core.Modules
|
||||
/// </summary>
|
||||
public bool AnimatingProfileChange { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of default profiles, to add a new default profile use <see cref="AddDefaultProfile" />
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<ProfileDescriptor> DefaultProfiles => _defaultProfiles.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Called after the profile has updated
|
||||
/// </summary>
|
||||
@@ -148,6 +159,40 @@ namespace Artemis.Core.Modules
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the <see cref="ActiveProfile" /> has changed
|
||||
/// </summary>
|
||||
public event EventHandler? ActiveProfileChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a default profile by reading it from the file found at the provided path
|
||||
/// </summary>
|
||||
/// <param name="file"></param>
|
||||
protected void AddDefaultProfile(string file)
|
||||
{
|
||||
// Ensure the file exists
|
||||
if (!File.Exists(file))
|
||||
throw new ArtemisPluginFeatureException(this, $"Could not find default profile at {file}.");
|
||||
// Deserialize and make sure that succeeded
|
||||
ProfileEntity? profileEntity = JsonConvert.DeserializeObject<ProfileEntity>(File.ReadAllText(file));
|
||||
if (profileEntity == null)
|
||||
throw new ArtemisPluginFeatureException(this, $"Failed to deserialize default profile at {file}.");
|
||||
// Ensure the profile ID is unique
|
||||
ProfileDescriptor descriptor = new(this, profileEntity) {NeedsAdaption = true};
|
||||
if (_defaultProfiles.Any(d => d.Id == descriptor.Id))
|
||||
throw new ArtemisPluginFeatureException(this, $"Cannot add default profile from {file}, profile ID {descriptor.Id} already in use.");
|
||||
|
||||
_defaultProfiles.Add(descriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="ActiveProfileChanged" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnActiveProfileChanged()
|
||||
{
|
||||
ActiveProfileChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
internal override void InternalUpdate(double deltaTime)
|
||||
{
|
||||
StartUpdateMeasure();
|
||||
@@ -245,22 +290,5 @@ namespace Artemis.Core.Modules
|
||||
base.Deactivate(isDeactivateOverride);
|
||||
Activate(isActivateOverride);
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the <see cref="ActiveProfile" /> has changed
|
||||
/// </summary>
|
||||
public event EventHandler? ActiveProfileChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the <see cref="ActiveProfileChanged" /> event
|
||||
/// </summary>
|
||||
protected virtual void OnActiveProfileChanged()
|
||||
{
|
||||
ActiveProfileChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ namespace Artemis.Core
|
||||
public abstract string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether installing or uninstalling this prerequisite requires admin privileges
|
||||
/// [NYI] Gets a boolean indicating whether installing or uninstalling this prerequisite requires admin privileges
|
||||
/// </summary>
|
||||
public abstract bool RequiresElevation { get; }
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -17,11 +18,13 @@ namespace Artemis.Core
|
||||
/// <param name="fileName">The target file to execute</param>
|
||||
/// <param name="arguments">A set of command-line arguments to use when starting the application</param>
|
||||
/// <param name="waitForExit">A boolean indicating whether the action should wait for the process to exit</param>
|
||||
public ExecuteFileAction(string name, string fileName, string? arguments = null, bool waitForExit = true) : base(name)
|
||||
/// <param name="elevate">A boolean indicating whether the file should run with administrator privileges (does not require <see cref="PluginPrerequisite.RequiresElevation"/>)</param>
|
||||
public ExecuteFileAction(string name, string fileName, string? arguments = null, bool waitForExit = true, bool elevate = false) : base(name)
|
||||
{
|
||||
FileName = fileName ?? throw new ArgumentNullException(nameof(fileName));
|
||||
Arguments = arguments;
|
||||
WaitForExit = waitForExit;
|
||||
Elevate = elevate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -30,7 +33,7 @@ namespace Artemis.Core
|
||||
public string FileName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a set of command-line arguments to use when starting the application
|
||||
/// Gets a set of command-line arguments to use when starting the application
|
||||
/// </summary>
|
||||
public string? Arguments { get; }
|
||||
|
||||
@@ -39,6 +42,11 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public bool WaitForExit { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the file should run with administrator privileges
|
||||
/// </summary>
|
||||
public bool Elevate { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Execute(CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -48,7 +56,7 @@ namespace Artemis.Core
|
||||
ShowProgressBar = true;
|
||||
ProgressIndeterminate = true;
|
||||
|
||||
int result = await RunProcessAsync(FileName, Arguments);
|
||||
int result = await RunProcessAsync(FileName, Arguments, Elevate);
|
||||
|
||||
Status = $"{FileName} exited with code {result}";
|
||||
}
|
||||
@@ -64,13 +72,19 @@ namespace Artemis.Core
|
||||
}
|
||||
}
|
||||
|
||||
private static Task<int> RunProcessAsync(string fileName, string? arguments)
|
||||
private static Task<int> RunProcessAsync(string fileName, string? arguments, bool elevate)
|
||||
{
|
||||
TaskCompletionSource<int> tcs = new();
|
||||
|
||||
Process process = new()
|
||||
{
|
||||
StartInfo = {FileName = fileName, Arguments = arguments!},
|
||||
StartInfo =
|
||||
{
|
||||
FileName = fileName,
|
||||
Arguments = arguments!,
|
||||
Verb = elevate ? "RunAs" : "",
|
||||
UseShellExecute = elevate
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
|
||||
@@ -80,7 +94,17 @@ namespace Artemis.Core
|
||||
process.Dispose();
|
||||
};
|
||||
|
||||
process.Start();
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
}
|
||||
catch (Win32Exception e)
|
||||
{
|
||||
if (!elevate || e.NativeErrorCode != 0x4c7)
|
||||
throw;
|
||||
tcs.SetResult(-1);
|
||||
}
|
||||
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
@@ -107,6 +107,11 @@ namespace Artemis.Core.Services
|
||||
|
||||
Profile profile = new(profileDescriptor.ProfileModule, profileEntity);
|
||||
InstantiateProfile(profile);
|
||||
if (profileDescriptor.NeedsAdaption)
|
||||
{
|
||||
AdaptProfile(profile);
|
||||
profileDescriptor.NeedsAdaption = false;
|
||||
}
|
||||
|
||||
profileDescriptor.ProfileModule.ChangeActiveProfile(profile, _rgbService.EnabledDevices);
|
||||
SaveActiveProfile(profileDescriptor.ProfileModule);
|
||||
@@ -290,7 +295,7 @@ namespace Artemis.Core.Services
|
||||
profileEntity.Name = $"{profileEntity.Name} - {nameAffix}";
|
||||
|
||||
_profileRepository.Add(profileEntity);
|
||||
return new ProfileDescriptor(profileModule, profileEntity);
|
||||
return new ProfileDescriptor(profileModule, profileEntity) {NeedsAdaption = true};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
Reference in new issue
Block a user