diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs index 64822f713..e42a17541 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs @@ -38,7 +38,7 @@ namespace Artemis.Core Leds = rgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly(); LedIds = new ReadOnlyDictionary(Leds.ToDictionary(l => l.RgbLed.Id, l => l)); - LoadBestLayout(); + ApplyKeyboardLayout(); ApplyToEntity(); CalculateRenderProperties(); } @@ -56,7 +56,8 @@ namespace Artemis.Core Leds = rgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly(); LedIds = new ReadOnlyDictionary(Leds.ToDictionary(l => l.RgbLed.Id, l => l)); - LoadBestLayout(); + + ApplyKeyboardLayout(); } /// @@ -229,12 +230,12 @@ namespace Artemis.Core /// Gets or sets the physical layout of the device e.g. ISO or ANSI. /// Only applicable to keyboards /// - public string? PhysicalLayout + public KeyboardLayoutType PhysicalLayout { - get => DeviceEntity.PhysicalLayout; + get => (KeyboardLayoutType) DeviceEntity.PhysicalLayout; set { - DeviceEntity.PhysicalLayout = value; + DeviceEntity.PhysicalLayout = (int) value; OnPropertyChanged(nameof(PhysicalLayout)); } } @@ -296,8 +297,8 @@ namespace Artemis.Core { // Take out invalid file name chars, may not be perfect but neither are you string fileName = System.IO.Path.GetInvalidFileNameChars().Aggregate(RgbDevice.DeviceInfo.Model, (current, c) => current.Replace(c, '-')); - if (PhysicalLayout != null) - fileName = $"{fileName}-{PhysicalLayout.ToUpper()}"; + if (RgbDevice is IKeyboard) + fileName = $"{fileName}-{PhysicalLayout.ToString().ToUpper()}"; if (includeExtension) fileName = $"{fileName}.xml"; @@ -309,9 +310,7 @@ namespace Artemis.Core /// public void LoadBestLayout() { - // If supported, detect the device layout so that we can load the correct one - if (DeviceProvider.CanDetectLogicalLayout || DeviceProvider.CanDetectPhysicalLayout && RgbDevice is IKeyboard) - DeviceProvider.DetectDeviceLayout(this); + // Look for a user layout // ... here @@ -325,11 +324,23 @@ namespace Artemis.Core ApplyLayout(deviceProviderLayout); } + private void ApplyKeyboardLayout() + { + if (!(RgbDevice is IKeyboard keyboard)) + return; + + // If supported, detect the device layout so that we can load the correct one + if (DeviceProvider.CanDetectLogicalLayout ) + LogicalLayout = DeviceProvider.GetLogicalLayout(keyboard); + if (DeviceProvider.CanDetectPhysicalLayout) + PhysicalLayout = (KeyboardLayoutType) keyboard.DeviceInfo.Layout; + } + /// /// Applies the provided layout to the device /// /// The layout to apply - public void ApplyLayout(ArtemisLayout layout) + internal void ApplyLayout(ArtemisLayout layout) { if (layout.IsValid) layout.RgbLayout!.ApplyTo(RgbDevice); diff --git a/src/Artemis.Core/Models/Surface/KeyboardLayoutType.cs b/src/Artemis.Core/Models/Surface/KeyboardLayoutType.cs new file mode 100644 index 000000000..43cd2597f --- /dev/null +++ b/src/Artemis.Core/Models/Surface/KeyboardLayoutType.cs @@ -0,0 +1,41 @@ +// ReSharper disable InconsistentNaming + +namespace Artemis.Core +{ + // Copied from RGB.NET to avoid needing to reference RGB.NET + /// + /// Represents a physical layout type for a keyboard + /// + public enum KeyboardLayoutType + { + /// + /// An unknown layout type + /// + Unknown = 0, + + /// + /// The ANSI layout type, often used in the US (uses a short enter) + /// + ANSI = 1, + + /// + /// The ISO layout type, often used in the EU (uses a tall enter) + /// + ISO = 2, + + /// + /// The JIS layout type, often used in Japan (based on ISO) + /// + JIS = 3, + + /// + /// The ABNT layout type, often used in Brazil/Portugal (based on ISO) + /// + ABNT = 4, + + /// + /// The KS layout type, often used in South Korea + /// + KS = 5 + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs b/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs index d069d821a..838a5a87a 100644 --- a/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs +++ b/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs @@ -36,7 +36,7 @@ namespace Artemis.Core.DeviceProviders /// /// A boolean indicating whether this device provider detects the physical layout of connected keyboards. /// - /// Note: is only called when this or + /// Note: is only called when this or /// is . /// /// @@ -45,7 +45,7 @@ namespace Artemis.Core.DeviceProviders /// /// A boolean indicating whether this device provider detects the logical layout of connected keyboards /// - /// Note: is only called when this or + /// Note: is only called when this or /// is . /// /// @@ -77,12 +77,13 @@ namespace Artemis.Core.DeviceProviders /// /// Called when a specific RGB device's logical and physical layout must be detected /// - /// Note: Only called when or is . + /// Note: Only called when is . /// /// - /// The device to detect the layout for, always a keyboard - public virtual void DetectDeviceLayout(ArtemisDevice rgbDevice) + /// The device to detect the layout for, always a keyboard + public virtual string GetLogicalLayout(IKeyboard keyboard) { + throw new NotImplementedException("Device provider does not support detecting logical layouts (don't call base.GetLogicalLayout())"); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Interfaces/IRgbService.cs b/src/Artemis.Core/Services/Interfaces/IRgbService.cs index 8e8769d56..3e4dbcd6a 100644 --- a/src/Artemis.Core/Services/Interfaces/IRgbService.cs +++ b/src/Artemis.Core/Services/Interfaces/IRgbService.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Artemis.Core.DeviceProviders; using RGB.NET.Core; namespace Artemis.Core.Services @@ -56,6 +55,11 @@ namespace Artemis.Core.Services /// event EventHandler DeviceReloaded; + /// + /// Occurs when a single device was removed + /// + event EventHandler DeviceRemoved; + /// /// Recalculates the LED group used by the /// diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index c1f2bd5b7..218c55990 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -51,8 +51,10 @@ namespace Artemis.Core.Services { try { - // Device provider may have been attached before..? + List toRemove = deviceProvider.Devices.Where(d => Surface.Devices.Contains(d)).ToList(); Surface.Detach(deviceProvider.Devices); + foreach (IRGBDevice rgbDevice in toRemove) + OnDeviceRemoved(new DeviceEventArgs(rgbDevice)); deviceProvider.Initialize(RGBDeviceType.All, true); Surface.Attach(deviceProvider.Devices); @@ -111,6 +113,7 @@ namespace Artemis.Core.Services public event EventHandler? DeviceLoaded; public event EventHandler? DeviceReloaded; + public event EventHandler DeviceRemoved; public void UpdateSurfaceLedGroup(ArtemisSurface? artemisSurface) { @@ -147,6 +150,11 @@ namespace Artemis.Core.Services DeviceReloaded?.Invoke(this, e); } + protected virtual void OnDeviceRemoved(DeviceEventArgs e) + { + DeviceRemoved?.Invoke(this, e); + } #endregion + } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/SurfaceService.cs b/src/Artemis.Core/Services/Storage/SurfaceService.cs index ed8aed266..6adbc7bfe 100644 --- a/src/Artemis.Core/Services/Storage/SurfaceService.cs +++ b/src/Artemis.Core/Services/Storage/SurfaceService.cs @@ -32,8 +32,88 @@ namespace Artemis.Core.Services LoadFromRepository(); _rgbService.DeviceLoaded += RgbServiceOnDeviceLoaded; + _rgbService.DeviceRemoved += RgbServiceOnDeviceRemoved; } + #region Repository + + private void LoadFromRepository() + { + List configs = _surfaceRepository.GetAll(); + foreach (SurfaceEntity surfaceEntity in configs) + { + // Create the surface entity + ArtemisSurface surfaceConfiguration = new(_rgbService.Surface, surfaceEntity); + foreach (DeviceEntity position in surfaceEntity.DeviceEntities) + { + IRGBDevice? device = _rgbService.Surface.Devices.FirstOrDefault(d => d.GetDeviceIdentifier() == position.DeviceIdentifier); + if (device != null) + { + DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(device); + surfaceConfiguration.Devices.Add(new ArtemisDevice(device, deviceProvider, surfaceConfiguration, position)); + } + } + + // Finally, add the surface config to the collection + lock (_surfaceConfigurations) + { + _surfaceConfigurations.Add(surfaceConfiguration); + } + } + + // When all surface configs are loaded, apply the active surface config + ArtemisSurface? active = SurfaceConfigurations.FirstOrDefault(c => c.IsActive); + if (active != null) + { + SetActiveSurfaceConfiguration(active); + } + else + { + active = SurfaceConfigurations.FirstOrDefault(); + if (active != null) + SetActiveSurfaceConfiguration(active); + else + SetActiveSurfaceConfiguration(CreateSurfaceConfiguration("Default")); + } + } + + #endregion + + #region Utilities + + private void AddDeviceIfMissing(IRGBDevice rgbDevice, ArtemisSurface surface) + { + string deviceIdentifier = rgbDevice.GetDeviceIdentifier(); + ArtemisDevice? device = surface.Devices.FirstOrDefault(d => d.DeviceEntity.DeviceIdentifier == deviceIdentifier); + + if (device != null) + return; + + // Find an existing device config and use that + DeviceEntity? existingDeviceConfig = surface.SurfaceEntity.DeviceEntities.FirstOrDefault(d => d.DeviceIdentifier == deviceIdentifier); + if (existingDeviceConfig != null) + { + DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice); + device = new ArtemisDevice(rgbDevice, deviceProvider, surface, existingDeviceConfig); + } + // Fall back on creating a new device + else + { + _logger.Information( + "No device config found for {deviceInfo}, device hash: {deviceHashCode}. Adding a new entry.", + rgbDevice.DeviceInfo, + deviceIdentifier + ); + DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice); + device = new ArtemisDevice(rgbDevice, deviceProvider, surface); + } + + surface.Devices.Add(device); + } + + #endregion + + public ArtemisSurface ActiveSurface { get; private set; } public ReadOnlyCollection SurfaceConfigurations => _surfaceConfigurations.AsReadOnly(); @@ -97,14 +177,12 @@ namespace Artemis.Core.Services { surface.ApplyToEntity(); if (includeDevices) - { foreach (ArtemisDevice deviceConfiguration in surface.Devices) { deviceConfiguration.ApplyToEntity(); if (surface.IsActive) deviceConfiguration.ApplyToRgbDevice(); } - } surface.UpdateLedMap(); @@ -126,82 +204,6 @@ namespace Artemis.Core.Services } } - #region Repository - - private void LoadFromRepository() - { - List configs = _surfaceRepository.GetAll(); - foreach (SurfaceEntity surfaceEntity in configs) - { - // Create the surface entity - ArtemisSurface surfaceConfiguration = new(_rgbService.Surface, surfaceEntity); - foreach (DeviceEntity position in surfaceEntity.DeviceEntities) - { - IRGBDevice? device = _rgbService.Surface.Devices.FirstOrDefault(d => d.GetDeviceIdentifier() == position.DeviceIdentifier); - if (device != null) - { - DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(device); - surfaceConfiguration.Devices.Add(new ArtemisDevice(device, deviceProvider, surfaceConfiguration, position)); - } - } - - // Finally, add the surface config to the collection - lock (_surfaceConfigurations) - { - _surfaceConfigurations.Add(surfaceConfiguration); - } - } - - // When all surface configs are loaded, apply the active surface config - ArtemisSurface? active = SurfaceConfigurations.FirstOrDefault(c => c.IsActive); - if (active != null) - SetActiveSurfaceConfiguration(active); - else - { - active = SurfaceConfigurations.FirstOrDefault(); - if (active != null) - SetActiveSurfaceConfiguration(active); - else - SetActiveSurfaceConfiguration(CreateSurfaceConfiguration("Default")); - } - } - - #endregion - - #region Utilities - - private void AddDeviceIfMissing(IRGBDevice rgbDevice, ArtemisSurface surface) - { - string deviceIdentifier = rgbDevice.GetDeviceIdentifier(); - ArtemisDevice? device = surface.Devices.FirstOrDefault(d => d.DeviceEntity.DeviceIdentifier == deviceIdentifier); - - if (device != null) - return; - - // Find an existing device config and use that - DeviceEntity? existingDeviceConfig = surface.SurfaceEntity.DeviceEntities.FirstOrDefault(d => d.DeviceIdentifier == deviceIdentifier); - if (existingDeviceConfig != null) - { - DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice); - device = new ArtemisDevice(rgbDevice, deviceProvider, surface, existingDeviceConfig); - } - // Fall back on creating a new device - else - { - _logger.Information( - "No device config found for {deviceInfo}, device hash: {deviceHashCode}. Adding a new entry.", - rgbDevice.DeviceInfo, - deviceIdentifier - ); - DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice); - device = new ArtemisDevice(rgbDevice, deviceProvider, surface); - } - - surface.Devices.Add(device); - } - - #endregion - #region AutoLayout public void AutoArrange(ArtemisSurface? artemisSurface = null) @@ -209,7 +211,7 @@ namespace Artemis.Core.Services artemisSurface ??= ActiveSurface; if (!artemisSurface.Devices.Any()) return; - + SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement(); surfaceArrangement.Arrange(artemisSurface); UpdateSurfaceConfiguration(artemisSurface, true); @@ -230,6 +232,17 @@ namespace Artemis.Core.Services UpdateSurfaceConfiguration(ActiveSurface, true); } + private void RgbServiceOnDeviceRemoved(object? sender, DeviceEventArgs e) + { + lock (_surfaceConfigurations) + { + foreach (ArtemisSurface surfaceConfiguration in _surfaceConfigurations) + surfaceConfiguration.Devices.RemoveAll(d => d.RgbDevice == e.Device); + } + + UpdateSurfaceConfiguration(ActiveSurface, true); + } + #endregion #region Events diff --git a/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs b/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs index 20c986cd1..843a82bd1 100644 --- a/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs +++ b/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs @@ -20,7 +20,7 @@ namespace Artemis.Storage.Entities.Surface public double BlueScale { get; set; } public bool IsEnabled { get; set; } - public string PhysicalLayout { get; set; } + public int PhysicalLayout { get; set; } public string LogicalLayout { get; set; } public List InputIdentifiers { get; set; }