Devices - Reworking layout loading

This commit is contained in:
RobertBeekman committed 2024-01-09 21:27:51 +01:00
1 parent 9393bf2b68
commit e8590abd61
57 files changed
+824 -349

No files matched your search

+6
View File
@@ -62,6 +62,11 @@ public static class Constants
/// The full path to the Artemis user layouts folder
/// </summary>
public static readonly string LayoutsFolder = Path.Combine(DataFolder, "User Layouts");
/// <summary>
/// The full path to the Artemis user layouts folder
/// </summary>
public static readonly string WorkshopFolder = Path.Combine(DataFolder, "workshop");
/// <summary>
/// The current API version for plugins
@@ -155,4 +160,5 @@ public static class Constants
/// Gets the graphics context to be used for rendering by SkiaSharp.
/// </summary>
public static IManagedGraphicsContext? ManagedGraphicsContext { get; internal set; }
}
@@ -2,6 +2,7 @@ using System;
using System.Linq;
using System.Reflection;
using Artemis.Core.DryIoc.Factories;
using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.Storage;
using Artemis.Storage.Migrations.Interfaces;
@@ -35,7 +36,8 @@ public static class ContainerExtensions
// Bind migrations
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IStorageMigration>(), Reuse.Singleton, nonPublicServiceTypes: true);
container.RegisterMany(coreAssembly, type => type.IsAssignableTo<ILayoutProvider>(), Reuse.Singleton);
container.Register<IPluginSettingsFactory, PluginSettingsFactory>(Reuse.Singleton);
container.Register(Made.Of(_ => ServiceInfo.Of<IPluginSettingsFactory>(), f => f.CreatePluginSettings(Arg.Index<Type>(0)), r => r.Parent.ImplementationType));
container.Register<ILoggerFactory, LoggerFactory>(Reuse.Singleton);
@@ -2,9 +2,9 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using Artemis.Core.DeviceProviders;
using Artemis.Core.Providers;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Surface;
using RGB.NET.Core;
@@ -46,6 +46,7 @@ public class ArtemisDevice : CorePropertyChanged
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
Categories = new HashSet<DeviceCategory>();
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LayoutType};
RgbDevice.ColorCorrections.Clear();
RgbDevice.ColorCorrections.Add(new ScaleColorCorrection(this));
@@ -74,6 +75,7 @@ public class ArtemisDevice : CorePropertyChanged
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
Categories = new HashSet<DeviceCategory>();
LayoutSelection = new LayoutSelection {Type = DefaultLayoutProvider.LayoutType};
foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers)
InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(identifierEntity.InputProvider, identifierEntity.Identifier));
@@ -153,6 +155,8 @@ public class ArtemisDevice : CorePropertyChanged
/// </summary>
public HashSet<DeviceCategory> Categories { get; }
public LayoutSelection LayoutSelection { get; }
/// <summary>
/// Gets or sets the X-position of the device
/// </summary>
@@ -293,19 +297,6 @@ public class ArtemisDevice : CorePropertyChanged
}
}
/// <summary>
/// Gets or sets a boolean indicating whether falling back to default layouts is enabled or not
/// </summary>
public bool DisableDefaultLayout
{
get => DeviceEntity.DisableDefaultLayout;
set
{
DeviceEntity.DisableDefaultLayout = value;
OnPropertyChanged(nameof(DisableDefaultLayout));
}
}
/// <summary>
/// Gets or sets the logical layout of the device e.g. DE, UK or US.
/// <para>Only applicable to keyboards</para>
@@ -320,20 +311,6 @@ public class ArtemisDevice : CorePropertyChanged
}
}
/// <summary>
/// Gets or sets the path of the custom layout to load when calling <see cref="GetBestDeviceLayout" />
/// for this device
/// </summary>
public string? CustomLayoutPath
{
get => DeviceEntity.CustomLayoutPath;
set
{
DeviceEntity.CustomLayoutPath = value;
OnPropertyChanged(nameof(CustomLayoutPath));
}
}
/// <summary>
/// Gets the layout of the device expanded with Artemis-specific data
/// </summary>
@@ -381,40 +358,6 @@ public class ArtemisDevice : CorePropertyChanged
return artemisLed;
}
/// <summary>
/// Returns the most preferred device layout for this device.
/// </summary>
/// <returns>The most preferred device layout for this device.</returns>
public ArtemisLayout? GetBestDeviceLayout()
{
ArtemisLayout? layout;
// Configured layout path takes precedence over all other options
if (CustomLayoutPath != null)
{
layout = new ArtemisLayout(CustomLayoutPath, LayoutSource.Configured);
if (layout.IsValid)
return layout;
}
// Look for a layout provided by the user
layout = DeviceProvider.LoadUserLayout(this);
if (layout.IsValid)
return layout;
if (DisableDefaultLayout)
return null;
// Look for a layout provided by the plugin
layout = DeviceProvider.LoadLayout(this);
if (layout.IsValid)
return layout;
// Finally fall back to a default layout
layout = ArtemisLayout.GetDefaultLayout(this);
return layout;
}
/// <summary>
/// Occurs when the underlying RGB.NET device was updated
/// </summary>
@@ -481,7 +424,7 @@ public class ArtemisDevice : CorePropertyChanged
throw new ArtemisCoreException($"Cannot apply layout with {nameof(createMissingLeds)} set to true because the device provider does not support it");
if (layout != null && layout.IsValid && removeExcessiveLeds && !DeviceProvider.RemoveExcessiveLedsSupported)
throw new ArtemisCoreException($"Cannot apply layout with {nameof(removeExcessiveLeds)} set to true because the device provider does not support it");
// Always clear the current layout
ClearLayout();
@@ -534,6 +477,9 @@ public class ArtemisDevice : CorePropertyChanged
DeviceEntity.Categories.Clear();
foreach (DeviceCategory deviceCategory in Categories)
DeviceEntity.Categories.Add((int) deviceCategory);
DeviceEntity.LayoutType = LayoutSelection.Type;
DeviceEntity.LayoutParameter = LayoutSelection.Parameter;
}
internal void Load()
@@ -548,6 +494,9 @@ public class ArtemisDevice : CorePropertyChanged
if (!Categories.Any())
ApplyDefaultCategories();
LayoutSelection.Type = DeviceEntity.LayoutType;
LayoutSelection.Parameter = DeviceEntity.LayoutParameter;
LoadInputMappings();
}
@@ -573,7 +522,7 @@ public class ArtemisDevice : CorePropertyChanged
{
Leds = RgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly();
LedIds = new ReadOnlyDictionary<LedId, ArtemisLed>(Leds.ToDictionary(l => l.RgbLed.Id, l => l));
if (loadInputMappings)
LoadInputMappings();
}
@@ -12,17 +12,18 @@ namespace Artemis.Core;
/// </summary>
public class ArtemisLayout
{
private static readonly string DefaultLayoutPath = Path.Combine(Constants.ApplicationFolder, "DefaultLayouts", "Artemis");
/// <summary>
/// Creates a new instance of the <see cref="ArtemisLayout" /> class
/// </summary>
/// <param name="filePath">The path of the layout XML file</param>
/// <param name="source">The source from where this layout is being loaded</param>
public ArtemisLayout(string filePath, LayoutSource source)
public ArtemisLayout(string filePath)
{
FilePath = filePath;
Source = source;
Leds = new List<ArtemisLedLayout>();
IsDefaultLayout = filePath.StartsWith(DefaultLayoutPath);
LoadLayout();
}
@@ -31,11 +32,6 @@ public class ArtemisLayout
/// </summary>
public string FilePath { get; }
/// <summary>
/// Gets the source from where this layout was loaded
/// </summary>
public LayoutSource Source { get; }
/// <summary>
/// Gets a boolean indicating whether a valid layout was loaded
/// </summary>
@@ -61,6 +57,8 @@ public class ArtemisLayout
/// </summary>
public LayoutCustomDeviceData LayoutCustomDeviceData { get; private set; } = null!;
public bool IsDefaultLayout { get; private set; }
/// <summary>
/// Applies the layout to the provided device
/// </summary>
@@ -121,29 +119,28 @@ public class ArtemisLayout
internal static ArtemisLayout? GetDefaultLayout(ArtemisDevice device)
{
string layoutFolder = Path.Combine(Constants.ApplicationFolder, "DefaultLayouts", "Artemis");
if (device.DeviceType == RGBDeviceType.Keyboard)
{
// XL layout is defined by its programmable macro keys
if (device.Leds.Any(l => l.RgbLed.Id >= LedId.Keyboard_Programmable1 && l.RgbLed.Id <= LedId.Keyboard_Programmable32))
{
if (device.PhysicalLayout == KeyboardLayoutType.ANSI)
return new ArtemisLayout(Path.Combine(layoutFolder, "Keyboard", "Artemis XL keyboard-ANSI.xml"), LayoutSource.Default);
return new ArtemisLayout(Path.Combine(layoutFolder, "Keyboard", "Artemis XL keyboard-ISO.xml"), LayoutSource.Default);
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis XL keyboard-ANSI.xml"));
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis XL keyboard-ISO.xml"));
}
// L layout is defined by its numpad
if (device.Leds.Any(l => l.RgbLed.Id >= LedId.Keyboard_NumLock && l.RgbLed.Id <= LedId.Keyboard_NumPeriodAndDelete))
{
if (device.PhysicalLayout == KeyboardLayoutType.ANSI)
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis L keyboard-ANSI.xml"), LayoutSource.Default);
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis L keyboard-ISO.xml"), LayoutSource.Default);
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis L keyboard-ANSI.xml"));
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis L keyboard-ISO.xml"));
}
// No numpad will result in TKL
if (device.PhysicalLayout == KeyboardLayoutType.ANSI)
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis TKL keyboard-ANSI.xml"), LayoutSource.Default);
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard", "Artemis TKL keyboard-ISO.xml"), LayoutSource.Default);
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis TKL keyboard-ANSI.xml"));
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Keyboard", "Artemis TKL keyboard-ISO.xml"));
}
// if (device.DeviceType == RGBDeviceType.Mouse)
@@ -151,21 +148,21 @@ public class ArtemisLayout
// if (device.Leds.Count == 1)
// {
// if (device.Leds.Any(l => l.RgbLed.Id == LedId.Logo))
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "1 LED mouse logo.xml"), LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "1 LED mouse.xml"), LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "1 LED mouse logo.xml"), LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "1 LED mouse.xml"), LayoutSource.Default);
// }
// if (device.Leds.Any(l => l.RgbLed.Id == LedId.Logo))
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "4 LED mouse logo.xml"), LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "4 LED mouse.xml"), LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "4 LED mouse logo.xml"), LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Mouse", "4 LED mouse.xml"), LayoutSource.Default);
// }
if (device.DeviceType == RGBDeviceType.Headset)
{
if (device.Leds.Count == 1)
return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 1 LED headset.xml"), LayoutSource.Default);
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Headset", "Artemis 1 LED headset.xml"));
if (device.Leds.Count == 2)
return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 2 LED headset.xml"), LayoutSource.Default);
return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 4 LED headset.xml"), LayoutSource.Default);
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Headset", "Artemis 2 LED headset.xml"));
return new ArtemisLayout(Path.Combine(DefaultLayoutPath, "Headset", "Artemis 4 LED headset.xml"));
}
return null;
@@ -206,30 +203,10 @@ public class ArtemisLayout
else
Image = null;
}
}
/// <summary>
/// Represents a source from where a layout came
/// </summary>
public enum LayoutSource
{
/// <summary>
/// A layout loaded from config
/// </summary>
Configured,
/// <summary>
/// A layout loaded from the user layout folder
/// </summary>
User,
/// <summary>
/// A layout loaded from the plugin folder
/// </summary>
Plugin,
/// <summary>
/// A default layout loaded as a fallback option
/// </summary>
Default
/// <inheritdoc />
public override string ToString()
{
return FilePath;
}
}
@@ -0,0 +1,28 @@
namespace Artemis.Core;
/// <summary>
/// Represents a reference to a layout for a device.
/// </summary>
public class LayoutSelection : CorePropertyChanged
{
private string? _type;
private string? _parameter;
/// <summary>
/// Gets or sets what kind of layout reference this is.
/// </summary>
public string? Type
{
get => _type;
set => SetAndNotify(ref _type, value);
}
/// <summary>
/// Gets or sets the parameter of the layout reference, such as a file path of workshop entry ID.
/// </summary>
public string? Parameter
{
get => _parameter;
set => SetAndNotify(ref _parameter, value);
}
}
@@ -62,7 +62,7 @@ public abstract class DeviceProvider : PluginFeature
device.DeviceType.ToString(),
GetDeviceLayoutName(device)
);
return new ArtemisLayout(filePath, LayoutSource.Plugin);
return new ArtemisLayout(filePath);
}
/// <summary>
@@ -79,7 +79,7 @@ public abstract class DeviceProvider : PluginFeature
device.DeviceType.ToString(),
GetDeviceLayoutName(device)
);
return new ArtemisLayout(filePath, LayoutSource.User);
return new ArtemisLayout(filePath);
}
/// <summary>
@@ -0,0 +1,28 @@
using RGB.NET.Layout;
namespace Artemis.Core.Providers;
public class CustomPathLayoutProvider : ILayoutProvider
{
public static string LayoutType = "CustomPath";
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
{
if (device.LayoutSelection.Parameter == null)
return null;
return new ArtemisLayout(device.LayoutSelection.Parameter);
}
/// <inheritdoc />
public void ApplyLayout(ArtemisDevice device, ArtemisLayout layout)
{
device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported);
}
/// <inheritdoc />
public bool IsMatch(ArtemisDevice device)
{
return device.LayoutSelection.Type == LayoutType;
}
}
@@ -0,0 +1,31 @@
namespace Artemis.Core.Providers;
public class DefaultLayoutProvider : ILayoutProvider
{
public static string LayoutType = "Default";
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
{
// Look for a layout provided by the plugin
ArtemisLayout layout = device.DeviceProvider.LoadLayout(device);
// Finally fall back to a default layout
return layout.IsValid ? layout : ArtemisLayout.GetDefaultLayout(device);
}
/// <inheritdoc />
public void ApplyLayout(ArtemisDevice device, ArtemisLayout layout)
{
if (layout.IsDefaultLayout)
device.ApplyLayout(layout, false, false);
else
device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported);
}
/// <inheritdoc />
public bool IsMatch(ArtemisDevice device)
{
return device.LayoutSelection.Type == LayoutType;
}
}
@@ -0,0 +1,17 @@
namespace Artemis.Core.Providers;
/// <summary>
/// Represents a class that can provide Artemis layouts for devices.
/// </summary>
public interface ILayoutProvider
{
/// <summary>
/// If available, loads an Artemis layout for the provided device.
/// </summary>
/// <param name="device">The device to load the layout for.</param>
/// <returns>The resulting layout if one was available; otherwise <see langword="null"/>.</returns>
ArtemisLayout? GetDeviceLayout(ArtemisDevice device);
void ApplyLayout(ArtemisDevice device, ArtemisLayout layout);
bool IsMatch(ArtemisDevice device);
}
@@ -0,0 +1,24 @@
namespace Artemis.Core.Providers;
public class NoneLayoutProvider : ILayoutProvider
{
public static string LayoutType = "None";
/// <inheritdoc />
public ArtemisLayout? GetDeviceLayout(ArtemisDevice device)
{
return null;
}
/// <inheritdoc />
public void ApplyLayout(ArtemisDevice device, ArtemisLayout layout)
{
device.ApplyLayout(null, false, false);
}
/// <inheritdoc />
public bool IsMatch(ArtemisDevice device)
{
return device.LayoutSelection.Type == LayoutType;
}
}
+24 -6
View File
@@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core.DeviceProviders;
using Artemis.Core.Providers;
using Artemis.Core.Services.Models;
using Artemis.Storage.Entities.Surface;
using Artemis.Storage.Repositories.Interfaces;
@@ -18,15 +19,21 @@ internal class DeviceService : IDeviceService
private readonly IPluginManagementService _pluginManagementService;
private readonly IDeviceRepository _deviceRepository;
private readonly Lazy<IRenderService> _renderService;
private readonly Func<IEnumerable<ILayoutProvider>> _getLayoutProviders;
private readonly List<ArtemisDevice> _enabledDevices = new();
private readonly List<ArtemisDevice> _devices = new();
public DeviceService(ILogger logger, IPluginManagementService pluginManagementService, IDeviceRepository deviceRepository, Lazy<IRenderService> renderService)
public DeviceService(ILogger logger,
IPluginManagementService pluginManagementService,
IDeviceRepository deviceRepository,
Lazy<IRenderService> renderService,
Func<IEnumerable<ILayoutProvider>> getLayoutProviders)
{
_logger = logger;
_pluginManagementService = pluginManagementService;
_deviceRepository = deviceRepository;
_renderService = renderService;
_getLayoutProviders = getLayoutProviders;
EnabledDevices = new ReadOnlyCollection<ArtemisDevice>(_enabledDevices);
Devices = new ReadOnlyCollection<ArtemisDevice>(_devices);
@@ -157,12 +164,23 @@ internal class DeviceService : IDeviceService
}
/// <inheritdoc />
public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout)
public void LoadDeviceLayout(ArtemisDevice device)
{
if (layout == null || layout.Source == LayoutSource.Default)
device.ApplyLayout(layout, false, false);
ILayoutProvider? provider = _getLayoutProviders().FirstOrDefault(p => p.IsMatch(device));
if (provider == null)
_logger.Warning("Could not find a layout provider for type {LayoutType} of device {Device}", device.LayoutSelection.Type, device);
ArtemisLayout? layout = provider?.GetDeviceLayout(device);
if (layout != null && !layout.IsValid)
{
_logger.Warning("Got an invalid layout {Layout} from {LayoutProvider}", layout, provider!.GetType().FullName);
layout = null;
}
if (layout == null)
device.ApplyLayout(null, false, false);
else
device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported);
provider!.ApplyLayout(device, layout);
UpdateLeds();
}
@@ -230,7 +248,7 @@ internal class DeviceService : IDeviceService
device = new ArtemisDevice(rgbDevice, deviceProvider);
}
ApplyDeviceLayout(device, device.GetBestDeviceLayout());
LoadDeviceLayout(device);
return device;
}
@@ -43,11 +43,10 @@ public interface IDeviceService : IArtemisService
void AutoArrangeDevices();
/// <summary>
/// Apples the provided <see cref="ArtemisLayout" /> to the provided <see cref="ArtemisDevice" />
/// Apples the best available to the provided <see cref="ArtemisDevice" />
/// </summary>
/// <param name="device"></param>
/// <param name="layout"></param>
void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout);
void LoadDeviceLayout(ArtemisDevice device);
/// <summary>
/// Enables the provided device