diff --git a/src/Artemis.ConsoleUI/packages.lock.json b/src/Artemis.ConsoleUI/packages.lock.json index bbb02c9a2..feeaa74a8 100644 --- a/src/Artemis.ConsoleUI/packages.lock.json +++ b/src/Artemis.ConsoleUI/packages.lock.json @@ -313,13 +313,10 @@ }, "Serilog.Sinks.Console": { "type": "Transitive", - "resolved": "3.1.1", - "contentHash": "56mI5AqvyF/i/c2451nvV71kq370XOCE4Uu5qiaJ295sOhMb9q3BWwG7mWLOVSnmpWiq0SBT3SXfgRXGNP6vzA==", + "resolved": "4.0.0", + "contentHash": "yJQit9sTJ4xGLKgCujqDJsaGqBNJwGB/H898z+xYlMG06twy4//6LLnSrsmpduZxcHIG4im7cv+JmXLzXz2EkQ==", "dependencies": { - "Serilog": "2.5.0", - "System.Console": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.3.0" + "Serilog": "2.10.0" } }, "Serilog.Sinks.Debug": { @@ -332,19 +329,16 @@ }, "Serilog.Sinks.File": { "type": "Transitive", - "resolved": "4.1.0", - "contentHash": "U0b34w+ZikbqWEZ3ui7BdzxY/19zwrdhLtI3o6tfmLdD3oXxg7n2TZJjwCCTlKPgRuYic9CBWfrZevbb70mTaw==", + "resolved": "5.0.0", + "contentHash": "uwV5hdhWPwUH1szhO8PJpFiahqXmzPzJT/sOijH/kFgUx+cyoDTMM8MHD0adw9+Iem6itoibbUXHYslzXsLEAg==", "dependencies": { - "Serilog": "2.5.0", - "System.IO.FileSystem": "4.0.1", - "System.Text.Encoding.Extensions": "4.0.11", - "System.Threading.Timer": "4.0.1" + "Serilog": "2.10.0" } }, "SkiaSharp": { "type": "Transitive", - "resolved": "2.80.2", - "contentHash": "D25rzdCwh+3L+XyXqpNa+H/yiLJbE3/R3K/XexwHyQjGdzZvSufFW3oqf3En7hhqSIsxsJ8f5NEZ0J5W5wlGBg==", + "resolved": "2.80.3", + "contentHash": "qX6tGNP3+MXNYe2pKm0PCRiJ/cx+LTeLaggwZifB7sUMXhECfKKKHJq45VqZKt37xQegnCCdf1jHXwmHeJQs5Q==", "dependencies": { "System.Memory": "4.5.3" } @@ -1230,10 +1224,10 @@ "Ninject.Extensions.ChildKernel": "3.3.0", "Ninject.Extensions.Conventions": "3.3.0", "Serilog": "2.10.0", - "Serilog.Sinks.Console": "3.1.1", + "Serilog.Sinks.Console": "4.0.0", "Serilog.Sinks.Debug": "2.0.0", - "Serilog.Sinks.File": "4.1.0", - "SkiaSharp": "2.80.2", + "Serilog.Sinks.File": "5.0.0", + "SkiaSharp": "2.80.3", "System.Buffers": "4.5.1", "System.IO.FileSystem.AccessControl": "5.0.0", "System.Numerics.Vectors": "4.5.0", diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index 4cb259807..f73a999ab 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -48,10 +48,10 @@ - + - - + + diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index e978e8598..7921af0bc 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -27,7 +27,7 @@ namespace Artemis.Core /// /// The full path to the Artemis data folder /// - public static readonly string DataFolder = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\Artemis\\"; + public static readonly string DataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Artemis"); /// /// The plugin info used by core components of Artemis diff --git a/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs b/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs index 8d46099e0..245b9eeb1 100644 --- a/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs +++ b/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs @@ -169,29 +169,29 @@ namespace Artemis.Core internal static ArtemisLayout? GetDefaultLayout(ArtemisDevice device) { - string layoutFolder = Path.Combine(Constants.ApplicationFolder, "DefaultLayouts\\Artemis"); + 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(layoutFolder + "\\Keyboard\\Artemis XL keyboard-ANSI.xml", LayoutSource.Default); - return new ArtemisLayout(layoutFolder + "\\Keyboard\\Artemis XL keyboard-ISO.xml", LayoutSource.Default); + 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); } // 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(layoutFolder + "\\Keyboard\\Artemis L keyboard-ANSI.xml", LayoutSource.Default); - return new ArtemisLayout(layoutFolder + "\\Keyboard\\Artemis L keyboard-ISO.xml", LayoutSource.Default); + 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); } // No numpad will result in TKL if (device.PhysicalLayout == KeyboardLayoutType.ANSI) - return new ArtemisLayout(layoutFolder + "\\Keyboard\\Artemis TKL keyboard-ANSI.xml", LayoutSource.Default); - return new ArtemisLayout(layoutFolder + "\\Keyboard\\Artemis TKL keyboard-ISO.xml", LayoutSource.Default); + 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); } // if (device.DeviceType == RGBDeviceType.Mouse) @@ -199,21 +199,21 @@ namespace Artemis.Core // if (device.Leds.Count == 1) // { // if (device.Leds.Any(l => l.RgbLed.Id == LedId.Logo)) - // return new ArtemisLayout(layoutFolder + "\\Mouse\\1 LED mouse logo.xml", LayoutSource.Default); - // return new ArtemisLayout(layoutFolder + "\\Mouse\\1 LED mouse.xml", LayoutSource.Default); + // 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); // } // if (device.Leds.Any(l => l.RgbLed.Id == LedId.Logo)) - // return new ArtemisLayout(layoutFolder + "\\Mouse\\4 LED mouse logo.xml", LayoutSource.Default); - // return new ArtemisLayout(layoutFolder + "\\Mouse\\4 LED mouse.xml", LayoutSource.Default); + // 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); // } if (device.DeviceType == RGBDeviceType.Headset) { if (device.Leds.Count == 1) - return new ArtemisLayout(layoutFolder + "\\Headset\\Artemis 1 LED headset.xml", LayoutSource.Default); + return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 1 LED headset.xml"), LayoutSource.Default); if (device.Leds.Count == 2) - return new ArtemisLayout(layoutFolder + "\\Headset\\Artemis 2 LED headset.xml", LayoutSource.Default); - return new ArtemisLayout(layoutFolder + "\\Headset\\Artemis 4 LED headset.xml", LayoutSource.Default); + 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 null; diff --git a/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs b/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs index 838c62881..01fcce12d 100644 --- a/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs +++ b/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs @@ -61,7 +61,6 @@ namespace Artemis.Core if (LayoutCustomLedData.LogicalLayouts == null || !LayoutCustomLedData.LogicalLayouts.Any()) return; - Uri layoutDirectory = new(Path.GetDirectoryName(DeviceLayout.FilePath)! + "\\", UriKind.Absolute); // Prefer a matching layout or else a default layout (that has no name) LayoutCustomLedDataLogicalLayout logicalLayout = LayoutCustomLedData.LogicalLayouts .OrderBy(l => l.Name == artemisDevice.LogicalLayout) @@ -69,7 +68,7 @@ namespace Artemis.Core .First(); LogicalName = logicalLayout.Name; - Image = new Uri(layoutDirectory, logicalLayout.Image); + Image = new Uri(Path.Combine(Path.GetDirectoryName(DeviceLayout.FilePath)!, logicalLayout.Image!), UriKind.Absolute); } } } \ No newline at end of file diff --git a/src/Artemis.Core/Ninject/LoggerProvider.cs b/src/Artemis.Core/Ninject/LoggerProvider.cs index c39f7bace..64f5efcf3 100644 --- a/src/Artemis.Core/Ninject/LoggerProvider.cs +++ b/src/Artemis.Core/Ninject/LoggerProvider.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using Ninject.Activation; using Serilog; using Serilog.Core; @@ -12,7 +13,7 @@ namespace Artemis.Core.Ninject private static readonly ILogger Logger = new LoggerConfiguration() .Enrich.FromLogContext() - .WriteTo.File(Constants.DataFolder + "logs/Artemis log-.log", + .WriteTo.File(Path.Combine(Constants.DataFolder, "logs", "Artemis log-.log"), rollingInterval: RollingInterval.Day, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}") .WriteTo.Console() diff --git a/src/Artemis.Core/Plugins/PluginFeature.cs b/src/Artemis.Core/Plugins/PluginFeature.cs index 983f20438..7d58d31cb 100644 --- a/src/Artemis.Core/Plugins/PluginFeature.cs +++ b/src/Artemis.Core/Plugins/PluginFeature.cs @@ -11,8 +11,8 @@ namespace Artemis.Core public abstract class PluginFeature : CorePropertyChanged, IDisposable { private bool _isEnabled; - private Exception? _loadException; - + + /// /// Gets the plugin feature info related to this feature /// @@ -37,14 +37,7 @@ namespace Artemis.Core internal set => SetAndNotify(ref _isEnabled, value); } - /// - /// Gets the exception thrown while loading - /// - public Exception? LoadException - { - get => _loadException; - internal set => SetAndNotify(ref _loadException, value); - } + internal int AutoEnableAttempts { get; set; } /// /// Gets the identifier of this plugin feature @@ -82,7 +75,8 @@ namespace Artemis.Core /// protected virtual void Dispose(bool disposing) { - if (disposing) InternalDisable(); + if (disposing) + SetEnabled(false); } /// @@ -111,16 +105,6 @@ namespace Artemis.Core Profiler.StopMeasurement("Update"); } - internal void StartRenderMeasure() - { - Profiler.StartMeasurement("Render"); - } - - internal void StopRenderMeasure() - { - Profiler.StopMeasurement("Render"); - } - internal void SetEnabled(bool enable, bool isAutoEnable = false) { if (enable == IsEnabled) @@ -146,13 +130,16 @@ namespace Artemis.Core try { + if (isAutoEnable) + AutoEnableAttempts++; + if (isAutoEnable && GetLockFileCreated()) { // Don't wrap existing lock exceptions, simply rethrow them - if (LoadException is ArtemisPluginLockException) - throw LoadException; + if (Info.LoadException is ArtemisPluginLockException) + throw Info.LoadException; - throw new ArtemisPluginLockException(LoadException); + throw new ArtemisPluginLockException(Info.LoadException); } CreateLockFile(); @@ -165,21 +152,22 @@ namespace Artemis.Core if (!enableTask.Wait(TimeSpan.FromSeconds(15))) throw new ArtemisPluginException(Plugin, "Plugin load timeout"); - LoadException = null; + Info.LoadException = null; + AutoEnableAttempts = 0; OnEnabled(); } // If enable failed, put it back in a disabled state catch (Exception e) { IsEnabled = false; - LoadException = e; + Info.LoadException = e; throw; } finally { // Clean up the lock file unless the failure was due to the lock file // After all, we failed but not miserably :) - if (!(LoadException is ArtemisPluginLockException)) + if (Info.LoadException is not ArtemisPluginLockException) DeleteLockFile(); } } diff --git a/src/Artemis.Core/Plugins/PluginFeatureInfo.cs b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs index 4947ab20a..62b4a6c5c 100644 --- a/src/Artemis.Core/Plugins/PluginFeatureInfo.cs +++ b/src/Artemis.Core/Plugins/PluginFeatureInfo.cs @@ -17,6 +17,7 @@ namespace Artemis.Core [JsonObject(MemberSerialization.OptIn)] public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject { + private Exception? _loadException; private string? _description; private string? _icon; private PluginFeature? _instance; @@ -80,6 +81,15 @@ namespace Artemis.Core /// public Type FeatureType { get; } + /// + /// Gets the exception thrown while loading + /// + public Exception? LoadException + { + get => _loadException; + internal set => SetAndNotify(ref _loadException, value); + } + /// /// The name of the plugin /// diff --git a/src/Artemis.Core/Services/ColorQuantizer/ColorQuantizerService.cs b/src/Artemis.Core/Services/ColorQuantizer/ColorQuantizerService.cs index 4fe7d22c4..f61b70dd5 100644 --- a/src/Artemis.Core/Services/ColorQuantizer/ColorQuantizerService.cs +++ b/src/Artemis.Core/Services/ColorQuantizer/ColorQuantizerService.cs @@ -8,21 +8,6 @@ namespace Artemis.Core.Services /// internal class ColorQuantizerService : IColorQuantizerService { - private static float GetComparisonValue(float sat, float targetSaturation, float luma, float targetLuma) - { - static float InvertDiff(float value, float target) - { - return 1 - Math.Abs(value - target); - } - - const float totalWeight = weightSaturation + weightLuma; - - float totalValue = InvertDiff(sat, targetSaturation) * weightSaturation + - InvertDiff(luma, targetLuma) * weightLuma; - - return totalValue / totalWeight; - } - /// public SKColor[] Quantize(IEnumerable colors, int amount) { @@ -48,29 +33,12 @@ namespace Artemis.Core.Services /// public SKColor FindColorVariation(IEnumerable colors, ColorType type, bool ignoreLimits = false) { - (float targetLuma, float minLuma, float maxLuma, float targetSaturation, float minSaturation, float maxSaturation) = type switch - { - ColorType.Vibrant => (targetNormalLuma, minNormalLuma, maxNormalLuma, targetVibrantSaturation, minVibrantSaturation, 1f), - ColorType.LightVibrant => (targetLightLuma, minLightLuma, 1f, targetVibrantSaturation, minVibrantSaturation, 1f), - ColorType.DarkVibrant => (targetDarkLuma, 0f, maxDarkLuma, targetVibrantSaturation, minVibrantSaturation, 1f), - ColorType.Muted => (targetNormalLuma, minNormalLuma, maxNormalLuma, targetMutesSaturation, 0, maxMutesSaturation), - ColorType.LightMuted => (targetLightLuma, minLightLuma, 1f, targetMutesSaturation, 0, maxMutesSaturation), - ColorType.DarkMuted => (targetDarkLuma, 0, maxDarkLuma, targetMutesSaturation, 0, maxMutesSaturation), - _ => (0.5f, 0f, 1f, 0.5f, 0f, 1f) - }; - - float bestColorScore = float.MinValue; + float bestColorScore = 0; SKColor bestColor = SKColor.Empty; + foreach (SKColor clr in colors) { - clr.ToHsl(out float _, out float sat, out float luma); - sat /= 100f; - luma /= 100f; - - if (!ignoreLimits && (sat <= minSaturation || sat >= maxSaturation || luma <= minLuma || luma >= maxLuma)) - continue; - - float score = GetComparisonValue(sat, targetSaturation, luma, targetLuma); + float score = GetScore(clr, type, ignoreLimits); if (score > bestColorScore) { bestColorScore = score; @@ -81,6 +49,82 @@ namespace Artemis.Core.Services return bestColor; } + /// + public ColorSwatch FindAllColorVariations(IEnumerable colors, bool ignoreLimits = false) + { + SKColor bestVibrantColor = SKColor.Empty; + SKColor bestLightVibrantColor = SKColor.Empty; + SKColor bestDarkVibrantColor = SKColor.Empty; + SKColor bestMutedColor = SKColor.Empty; + SKColor bestLightMutedColor = SKColor.Empty; + SKColor bestDarkMutedColor = SKColor.Empty; + float bestVibrantScore = 0; + float bestLightVibrantScore = 0; + float bestDarkVibrantScore = 0; + float bestMutedScore = 0; + float bestLightMutedScore = 0; + float bestDarkMutedScore = 0; + + //ugly but at least we only loop through the enumerable once ¯\_(ツ)_/¯ + foreach (var color in colors) + { + static void SetIfBetterScore(ref float bestScore, ref SKColor bestColor, SKColor newColor, ColorType type, bool ignoreLimits) + { + float newScore = GetScore(newColor, type, ignoreLimits); + if (newScore > bestScore) + { + bestScore = newScore; + bestColor = newColor; + } + } + + SetIfBetterScore(ref bestVibrantScore, ref bestVibrantColor, color, ColorType.Vibrant, ignoreLimits); + SetIfBetterScore(ref bestLightVibrantScore, ref bestLightVibrantColor, color, ColorType.LightVibrant, ignoreLimits); + SetIfBetterScore(ref bestDarkVibrantScore, ref bestDarkVibrantColor, color, ColorType.DarkVibrant, ignoreLimits); + SetIfBetterScore(ref bestMutedScore, ref bestMutedColor, color, ColorType.Muted, ignoreLimits); + SetIfBetterScore(ref bestLightMutedScore, ref bestLightMutedColor, color, ColorType.LightMuted, ignoreLimits); + SetIfBetterScore(ref bestDarkMutedScore, ref bestDarkMutedColor, color, ColorType.DarkMuted, ignoreLimits); + } + + return new() + { + Vibrant = bestVibrantColor, + LightVibrant = bestLightVibrantColor, + DarkVibrant = bestDarkVibrantColor, + Muted = bestMutedColor, + LightMuted = bestLightMutedColor, + DarkMuted = bestDarkMutedColor, + }; + } + + private static float GetScore(SKColor color, ColorType type, bool ignoreLimits = false) + { + static float InvertDiff(float value, float target) + { + return 1 - Math.Abs(value - target); + } + + color.ToHsl(out float _, out float saturation, out float luma); + saturation /= 100f; + luma /= 100f; + + if (!ignoreLimits && + (saturation <= GetMinSaturation(type) || saturation >= GetMaxSaturation(type) + || luma <= GetMinLuma(type) || luma >= GetMaxLuma(type))) + { + //if either saturation or luma falls outside the min-max, return the + //lowest score possible unless we're ignoring these limits. + return float.MinValue; + } + + float totalValue = (InvertDiff(saturation, GetTargetSaturation(type)) * weightSaturation) + + (InvertDiff(luma, GetTargetLuma(type)) * weightLuma); + + const float totalWeight = weightSaturation + weightLuma; + + return totalValue / totalWeight; + } + #region Constants private const float targetDarkLuma = 0.26f; @@ -97,6 +141,72 @@ namespace Artemis.Core.Services private const float weightSaturation = 3f; private const float weightLuma = 5f; + private static float GetTargetLuma(ColorType colorType) => colorType switch + { + ColorType.Vibrant => targetNormalLuma, + ColorType.LightVibrant => targetLightLuma, + ColorType.DarkVibrant => targetDarkLuma, + ColorType.Muted => targetNormalLuma, + ColorType.LightMuted => targetLightLuma, + ColorType.DarkMuted => targetDarkLuma, + _ => throw new ArgumentException(nameof(colorType)) + }; + + private static float GetMinLuma(ColorType colorType) => colorType switch + { + ColorType.Vibrant => minNormalLuma, + ColorType.LightVibrant => minLightLuma, + ColorType.DarkVibrant => 0f, + ColorType.Muted => minNormalLuma, + ColorType.LightMuted => minLightLuma, + ColorType.DarkMuted => 0, + _ => throw new ArgumentException(nameof(colorType)) + }; + + private static float GetMaxLuma(ColorType colorType) => colorType switch + { + ColorType.Vibrant => maxNormalLuma, + ColorType.LightVibrant => 1f, + ColorType.DarkVibrant => maxDarkLuma, + ColorType.Muted => maxNormalLuma, + ColorType.LightMuted => 1f, + ColorType.DarkMuted => maxDarkLuma, + _ => throw new ArgumentException(nameof(colorType)) + }; + + private static float GetTargetSaturation(ColorType colorType) => colorType switch + { + ColorType.Vibrant => targetVibrantSaturation, + ColorType.LightVibrant => targetVibrantSaturation, + ColorType.DarkVibrant => targetVibrantSaturation, + ColorType.Muted => targetMutesSaturation, + ColorType.LightMuted => targetMutesSaturation, + ColorType.DarkMuted => targetMutesSaturation, + _ => throw new ArgumentException(nameof(colorType)) + }; + + private static float GetMinSaturation(ColorType colorType) => colorType switch + { + ColorType.Vibrant => minVibrantSaturation, + ColorType.LightVibrant => minVibrantSaturation, + ColorType.DarkVibrant => minVibrantSaturation, + ColorType.Muted => 0, + ColorType.LightMuted => 0, + ColorType.DarkMuted => 0, + _ => throw new ArgumentException(nameof(colorType)) + }; + + private static float GetMaxSaturation(ColorType colorType) => colorType switch + { + ColorType.Vibrant => 1f, + ColorType.LightVibrant => 1f, + ColorType.DarkVibrant => 1f, + ColorType.Muted => maxMutesSaturation, + ColorType.LightMuted => maxMutesSaturation, + ColorType.DarkMuted => maxMutesSaturation, + _ => throw new ArgumentException(nameof(colorType)) + }; + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/ColorQuantizer/ColorSwatch.cs b/src/Artemis.Core/Services/ColorQuantizer/ColorSwatch.cs new file mode 100644 index 000000000..81abb7cc2 --- /dev/null +++ b/src/Artemis.Core/Services/ColorQuantizer/ColorSwatch.cs @@ -0,0 +1,40 @@ +using SkiaSharp; + +namespace Artemis.Core.Services +{ + /// + /// Swatch containing the known useful color variations. + /// + public struct ColorSwatch + { + /// + /// The component. + /// + public SKColor Vibrant { get; init; } + + /// + /// The component. + /// + public SKColor LightVibrant { get; init; } + + /// + /// The component. + /// + public SKColor DarkVibrant { get; init; } + + /// + /// The component. + /// + public SKColor Muted { get; init; } + + /// + /// The component. + /// + public SKColor LightMuted { get; init; } + + /// + /// The component. + /// + public SKColor DarkMuted { get; init; } + } +} diff --git a/src/Artemis.Core/Services/ColorQuantizer/Interfaces/IColorQuantizerService.cs b/src/Artemis.Core/Services/ColorQuantizer/Interfaces/IColorQuantizerService.cs index 6b0971684..82d70715a 100644 --- a/src/Artemis.Core/Services/ColorQuantizer/Interfaces/IColorQuantizerService.cs +++ b/src/Artemis.Core/Services/ColorQuantizer/Interfaces/IColorQuantizerService.cs @@ -26,5 +26,13 @@ namespace Artemis.Core.Services /// Ignore hard limits on whether a color is considered for each category. Result may be if this is false /// The color found public SKColor FindColorVariation(IEnumerable colors, ColorType type, bool ignoreLimits = false); + + /// + /// Finds all the color variations available and returns a struct containing them all. + /// + /// The colors to find the variations in + /// Ignore hard limits on whether a color is considered for each category. Some colors may be if this is false + /// A swatch containing all color variations + public ColorSwatch FindAllColorVariations(IEnumerable colors, bool ignoreLimits = false); } } diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 7d9575166..25bc3cae9 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Threading; using System.Threading.Tasks; using Artemis.Core.Ninject; using Artemis.Core.ScriptingProviders; @@ -13,7 +14,6 @@ using RGB.NET.Core; using Serilog; using Serilog.Events; using SkiaSharp; -using Module = Artemis.Core.Modules.Module; namespace Artemis.Core.Services { @@ -59,7 +59,6 @@ namespace Artemis.Core.Services _frameStopWatch = new Stopwatch(); StartupArguments = new List(); - _rgbService.IsRenderPaused = true; _rgbService.Surface.Updating += SurfaceOnUpdating; _loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel(); } @@ -107,6 +106,13 @@ namespace Artemis.Core.Services if (_rgbService.IsRenderPaused) return; + if (_rgbService.FlushLeds) + { + _rgbService.FlushLeds = false; + _rgbService.Surface.Update(true); + return; + } + try { _frameStopWatch.Restart(); @@ -214,7 +220,7 @@ namespace Artemis.Core.Services _pluginManagementService.CopyBuiltInPlugins(); _pluginManagementService.LoadPlugins(StartupArguments, IsElevated); - _rgbService.IsRenderPaused = false; + _rgbService.SetRenderPaused(false); OnInitialized(); } diff --git a/src/Artemis.Core/Services/Interfaces/IRgbService.cs b/src/Artemis.Core/Services/Interfaces/IRgbService.cs index 10ec9fa1b..c9bed101a 100644 --- a/src/Artemis.Core/Services/Interfaces/IRgbService.cs +++ b/src/Artemis.Core/Services/Interfaces/IRgbService.cs @@ -34,12 +34,17 @@ namespace Artemis.Core.Services /// /// Gets or sets whether rendering should be paused /// - bool IsRenderPaused { get; set; } + bool IsRenderPaused { get; } /// /// Gets a boolean indicating whether the render pipeline is open /// bool RenderOpen { get; } + + /// + /// Gets or sets a boolean indicating whether to flush the RGB.NET LEDs during next update + /// + bool FlushLeds { get; set; } /// /// Opens the render pipeline @@ -133,6 +138,13 @@ namespace Artemis.Core.Services /// The device to disable void DisableDevice(ArtemisDevice device); + /// + /// Pauses or resumes rendering, method won't return until the current frame finished rendering + /// + /// + /// if the pause state was changed; otherwise . + bool SetRenderPaused(bool paused); + /// /// Occurs when a single device was added /// diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 00a74d2a5..5e4841829 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -5,6 +5,7 @@ using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using Artemis.Core.DeviceProviders; using Artemis.Core.Ninject; using Artemis.Storage.Entities.General; @@ -90,11 +91,6 @@ namespace Artemis.Core.Services using StreamReader reader = new(metaDataFileEntry.Open()); PluginInfo builtInPluginInfo = CoreJson.DeserializeObject(reader.ReadToEnd())!; string preferred = builtInPluginInfo.PreferredPluginDirectory; - string oldPreferred = Path.GetFileNameWithoutExtension(zipFile.Name); - // Rename folders to the new format - // TODO: Get rid of this eventually, it's nice to keep around but it's extra IO that's best avoided - if (pluginDirectory.EnumerateDirectories().FirstOrDefault(d => d.Name == oldPreferred) != null) - Directory.Move(Path.Combine(pluginDirectory.FullName, oldPreferred), Path.Combine(pluginDirectory.FullName, preferred)); // Find the matching plugin in the plugin folder DirectoryInfo? match = pluginDirectory.EnumerateDirectories().FirstOrDefault(d => d.Name == preferred); @@ -431,6 +427,7 @@ namespace Artemis.Core.Services catch (Exception e) { _logger.Warning(new ArtemisPluginException(plugin, "Failed to instantiate feature", e), "Failed to instantiate feature", plugin); + featureInfo.LoadException = e; } } @@ -614,10 +611,38 @@ namespace Artemis.Core.Services } catch (Exception e) { - _logger.Warning( - new ArtemisPluginException(pluginFeature.Plugin, $"Exception during SetEnabled(true) on {pluginFeature}", e), - "Failed to enable plugin" - ); + if (isAutoEnable) + { + // Schedule a retry based on the amount of attempts + if (pluginFeature.AutoEnableAttempts < 4) + { + TimeSpan retryDelay = TimeSpan.FromSeconds(pluginFeature.AutoEnableAttempts * 10); + _logger.Warning( + e, + "Plugin feature '{feature} - {plugin}' failed to enable during attempt ({attempt}/3), scheduling a retry in {retryDelay}.", + pluginFeature, + pluginFeature.Plugin, + pluginFeature.AutoEnableAttempts, + retryDelay + ); + + Task.Run(async () => + { + await Task.Delay(retryDelay); + if (!pluginFeature.IsEnabled) + EnablePluginFeature(pluginFeature, saveState, true); + }); + } + else + { + _logger.Warning(e, "Plugin feature '{feature} - {plugin}' failed to enable after 3 attempts, giving up.", pluginFeature, pluginFeature.Plugin); + } + } + else + { + _logger.Warning(e, "Plugin feature '{feature} - {plugin}' failed to enable.", pluginFeature, pluginFeature.Plugin); + throw; + } } finally { diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index feff46dc2..1922b4c64 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.IO; using System.Linq; using System.Threading; using Artemis.Core.DeviceProviders; @@ -28,7 +27,6 @@ namespace Artemis.Core.Services private readonly PluginSetting _targetFrameRateSetting; private readonly TextureBrush _textureBrush = new(ITexture.Empty) {CalculationMode = RenderMode.Absolute}; private Dictionary _ledMap; - private bool _modifyingProviders; private ListLedGroup? _surfaceLedGroup; private SKTexture? _texture; @@ -52,8 +50,9 @@ namespace Artemis.Core.Services _ledMap = new Dictionary(); UpdateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / _targetFrameRateSetting.Value}; + SetRenderPaused(true); Surface.RegisterUpdateTrigger(UpdateTrigger); - + Utilities.ShutdownRequested += UtilitiesOnShutdownRequested; } @@ -82,16 +81,14 @@ namespace Artemis.Core.Services private void UpdateLedGroup() { - lock (_devices) + bool changedRenderPaused = SetRenderPaused(true); + try { - if (_modifyingProviders) - return; - _ledMap = new Dictionary(_devices.SelectMany(d => d.Leds).ToDictionary(l => l.RgbLed)); if (_surfaceLedGroup == null) { - _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = _textureBrush}; + _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) { Brush = _textureBrush }; OnLedsChanged(); return; } @@ -102,10 +99,16 @@ namespace Artemis.Core.Services _surfaceLedGroup.Detach(); // Apply the application wide brush and decorator - _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = _textureBrush}; + _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) { Brush = _textureBrush }; OnLedsChanged(); } } + finally + { + if (changedRenderPaused) + SetRenderPaused(false); + } + } private void TargetFrameRateSettingOnSettingChanged(object? sender, EventArgs e) @@ -137,91 +140,90 @@ namespace Artemis.Core.Services public bool IsRenderPaused { get; set; } public bool RenderOpen { get; private set; } + /// + public bool FlushLeds { get; set; } + public void AddDeviceProvider(IRGBDeviceProvider deviceProvider) { - lock (_devices) + bool changedRenderPaused = SetRenderPaused(true); + + try { - try + List toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList(); + Surface.Detach(toRemove.Select(d => d.RgbDevice)); + foreach (ArtemisDevice device in toRemove) + RemoveDevice(device); + + List providerExceptions = new(); + + void DeviceProviderOnException(object? sender, ExceptionEventArgs e) { - _modifyingProviders = true; - - List toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList(); - Surface.Detach(toRemove.Select(d => d.RgbDevice)); - foreach (ArtemisDevice device in toRemove) - RemoveDevice(device); - - List providerExceptions = new(); - - void DeviceProviderOnException(object? sender, ExceptionEventArgs e) - { - if (e.IsCritical) - providerExceptions.Add(e.Exception); - else - _logger.Warning(e.Exception, "Device provider {deviceProvider} threw non-critical exception", deviceProvider.GetType().Name); - } - - deviceProvider.Exception += DeviceProviderOnException; - deviceProvider.Initialize(); - Surface.Attach(deviceProvider.Devices); - deviceProvider.Exception -= DeviceProviderOnException; - if (providerExceptions.Count == 1) - throw new ArtemisPluginException("RGB.NET threw exception: " + providerExceptions.First().Message, providerExceptions.First()); - if (providerExceptions.Count > 1) - throw new ArtemisPluginException("RGB.NET threw multiple exceptions", new AggregateException(providerExceptions)); - - if (!deviceProvider.Devices.Any()) - { - _logger.Warning("Device provider {deviceProvider} has no devices", deviceProvider.GetType().Name); - return; - } - - foreach (IRGBDevice rgbDevice in deviceProvider.Devices) - { - ArtemisDevice artemisDevice = GetArtemisDevice(rgbDevice); - AddDevice(artemisDevice); - _logger.Debug("Device provider {deviceProvider} added {deviceName}", deviceProvider.GetType().Name, rgbDevice.DeviceInfo.DeviceName); - } - - _devices.Sort((a, b) => a.ZIndex - b.ZIndex); + if (e.IsCritical) + providerExceptions.Add(e.Exception); + else + _logger.Warning(e.Exception, "Device provider {deviceProvider} threw non-critical exception", deviceProvider.GetType().Name); } - catch (Exception e) + + deviceProvider.Exception += DeviceProviderOnException; + deviceProvider.Initialize(); + Surface.Attach(deviceProvider.Devices); + deviceProvider.Exception -= DeviceProviderOnException; + if (providerExceptions.Count == 1) + throw new ArtemisPluginException("RGB.NET threw exception: " + providerExceptions.First().Message, providerExceptions.First()); + if (providerExceptions.Count > 1) + throw new ArtemisPluginException("RGB.NET threw multiple exceptions", new AggregateException(providerExceptions)); + + if (!deviceProvider.Devices.Any()) { - _logger.Error(e, "Exception during device loading for device provider {deviceProvider}", deviceProvider.GetType().Name); - throw; + _logger.Warning("Device provider {deviceProvider} has no devices", deviceProvider.GetType().Name); + return; } - finally + + foreach (IRGBDevice rgbDevice in deviceProvider.Devices) { - _modifyingProviders = false; - UpdateLedGroup(); + ArtemisDevice artemisDevice = GetArtemisDevice(rgbDevice); + AddDevice(artemisDevice); + _logger.Debug("Device provider {deviceProvider} added {deviceName}", deviceProvider.GetType().Name, rgbDevice.DeviceInfo.DeviceName); } + + _devices.Sort((a, b) => a.ZIndex - b.ZIndex); + } + catch (Exception e) + { + _logger.Error(e, "Exception during device loading for device provider {deviceProvider}", deviceProvider.GetType().Name); + throw; + } + finally + { + UpdateLedGroup(); + if (changedRenderPaused) + SetRenderPaused(false); } } public void RemoveDeviceProvider(IRGBDeviceProvider deviceProvider) { - lock (_devices) + bool changedRenderPaused = SetRenderPaused(true); + + try { - try - { - _modifyingProviders = true; + List toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList(); + Surface.Detach(toRemove.Select(d => d.RgbDevice)); + foreach (ArtemisDevice device in toRemove) + RemoveDevice(device); - List toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList(); - Surface.Detach(toRemove.Select(d => d.RgbDevice)); - foreach (ArtemisDevice device in toRemove) - RemoveDevice(device); - - _devices.Sort((a, b) => a.ZIndex - b.ZIndex); - } - catch (Exception e) - { - _logger.Error(e, "Exception during device removal for device provider {deviceProvider}", deviceProvider.GetType().Name); - throw; - } - finally - { - _modifyingProviders = false; - UpdateLedGroup(); - } + _devices.Sort((a, b) => a.ZIndex - b.ZIndex); + } + catch (Exception e) + { + _logger.Error(e, "Exception during device removal for device provider {deviceProvider}", deviceProvider.GetType().Name); + throw; + } + finally + { + UpdateLedGroup(); + if (changedRenderPaused) + SetRenderPaused(false); } } @@ -233,6 +235,24 @@ namespace Artemis.Core.Services Surface.Dispose(); } + public bool SetRenderPaused(bool paused) + { + if (IsRenderPaused == paused) + return false; + + if (paused) + { + UpdateTrigger.Stop(); + } + else + { + UpdateTrigger.Start(); + } + + IsRenderPaused = paused; + return true; + } + public event EventHandler? DeviceAdded; public event EventHandler? DeviceRemoved; public event EventHandler? LedsChanged; @@ -267,36 +287,39 @@ namespace Artemis.Core.Services if (RenderOpen) throw new ArtemisCoreException("Cannot update the texture while rendering"); - IManagedGraphicsContext? graphicsContext = Constants.ManagedGraphicsContext = _newGraphicsContext; - if (!ReferenceEquals(graphicsContext, _newGraphicsContext)) - graphicsContext = _newGraphicsContext; - - if (graphicsContext != null) - _logger.Debug("Creating SKTexture with graphics context {graphicsContext}", graphicsContext.GetType().Name); - else - _logger.Debug("Creating SKTexture with software-based graphics context"); - - float evenWidth = Surface.Boundary.Size.Width; - if (evenWidth % 2 != 0) - evenWidth++; - float evenHeight = Surface.Boundary.Size.Height; - if (evenHeight % 2 != 0) - evenHeight++; - - float renderScale = (float) _renderScaleSetting.Value; - int width = Math.Max(1, MathF.Min(evenWidth * renderScale, 4096).RoundToInt()); - int height = Math.Max(1, MathF.Min(evenHeight * renderScale, 4096).RoundToInt()); - - _texture?.Dispose(); - _texture = new SKTexture(graphicsContext, width, height, renderScale); - _textureBrush.Texture = _texture; - - - if (!ReferenceEquals(_newGraphicsContext, Constants.ManagedGraphicsContext = _newGraphicsContext)) + lock (_devices) { - Constants.ManagedGraphicsContext?.Dispose(); - Constants.ManagedGraphicsContext = _newGraphicsContext; - _newGraphicsContext = null; + IManagedGraphicsContext? graphicsContext = Constants.ManagedGraphicsContext = _newGraphicsContext; + if (!ReferenceEquals(graphicsContext, _newGraphicsContext)) + graphicsContext = _newGraphicsContext; + + if (graphicsContext != null) + _logger.Debug("Creating SKTexture with graphics context {graphicsContext}", graphicsContext.GetType().Name); + else + _logger.Debug("Creating SKTexture with software-based graphics context"); + + float evenWidth = Surface.Boundary.Size.Width; + if (evenWidth % 2 != 0) + evenWidth++; + float evenHeight = Surface.Boundary.Size.Height; + if (evenHeight % 2 != 0) + evenHeight++; + + float renderScale = (float) _renderScaleSetting.Value; + int width = Math.Max(1, MathF.Min(evenWidth * renderScale, 4096).RoundToInt()); + int height = Math.Max(1, MathF.Min(evenHeight * renderScale, 4096).RoundToInt()); + + _texture?.Dispose(); + _texture = new SKTexture(graphicsContext, width, height, renderScale); + _textureBrush.Texture = _texture; + + + if (!ReferenceEquals(_newGraphicsContext, Constants.ManagedGraphicsContext = _newGraphicsContext)) + { + Constants.ManagedGraphicsContext?.Dispose(); + Constants.ManagedGraphicsContext = _newGraphicsContext; + _newGraphicsContext = null; + } } } @@ -315,12 +338,22 @@ namespace Artemis.Core.Services public void AutoArrangeDevices() { - SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement(); - surfaceArrangement.Arrange(_devices); - foreach (ArtemisDevice artemisDevice in _devices) - artemisDevice.ApplyDefaultCategories(); + bool changedRenderPaused = SetRenderPaused(true); - SaveDevices(); + try + { + SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement(); + surfaceArrangement.Arrange(_devices); + foreach (ArtemisDevice artemisDevice in _devices) + artemisDevice.ApplyDefaultCategories(); + + SaveDevices(); + } + finally + { + if (changedRenderPaused) + SetRenderPaused(false); + } } public ArtemisLayout? ApplyBestDeviceLayout(ArtemisDevice device) @@ -391,7 +424,7 @@ namespace Artemis.Core.Services _deviceRepository.Save(device.DeviceEntity); DeviceProvider deviceProvider = device.DeviceProvider; - + // Feels bad but need to in order to get the initial LEDs back _pluginManagementService.DisablePluginFeature(deviceProvider, false); Thread.Sleep(500); diff --git a/src/Artemis.Core/Utilities/Utilities.cs b/src/Artemis.Core/Utilities/Utilities.cs index 8577185bc..c55b07c93 100644 --- a/src/Artemis.Core/Utilities/Utilities.cs +++ b/src/Artemis.Core/Utilities/Utilities.cs @@ -18,8 +18,8 @@ namespace Artemis.Core public static void PrepareFirstLaunch() { CreateAccessibleDirectory(Constants.DataFolder); - CreateAccessibleDirectory(Constants.DataFolder + "plugins"); - CreateAccessibleDirectory(Constants.DataFolder + "user layouts"); + CreateAccessibleDirectory(Path.Combine(Constants.DataFolder ,"plugins")); + CreateAccessibleDirectory(Path.Combine(Constants.DataFolder ,"user layouts")); } /// diff --git a/src/Artemis.Core/packages.lock.json b/src/Artemis.Core/packages.lock.json index 3f8b70e19..ea7b5f80c 100644 --- a/src/Artemis.Core/packages.lock.json +++ b/src/Artemis.Core/packages.lock.json @@ -81,14 +81,11 @@ }, "Serilog.Sinks.Console": { "type": "Direct", - "requested": "[3.1.1, )", - "resolved": "3.1.1", - "contentHash": "56mI5AqvyF/i/c2451nvV71kq370XOCE4Uu5qiaJ295sOhMb9q3BWwG7mWLOVSnmpWiq0SBT3SXfgRXGNP6vzA==", + "requested": "[4.0.0, )", + "resolved": "4.0.0", + "contentHash": "yJQit9sTJ4xGLKgCujqDJsaGqBNJwGB/H898z+xYlMG06twy4//6LLnSrsmpduZxcHIG4im7cv+JmXLzXz2EkQ==", "dependencies": { - "Serilog": "2.5.0", - "System.Console": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.3.0" + "Serilog": "2.10.0" } }, "Serilog.Sinks.Debug": { @@ -102,21 +99,18 @@ }, "Serilog.Sinks.File": { "type": "Direct", - "requested": "[4.1.0, )", - "resolved": "4.1.0", - "contentHash": "U0b34w+ZikbqWEZ3ui7BdzxY/19zwrdhLtI3o6tfmLdD3oXxg7n2TZJjwCCTlKPgRuYic9CBWfrZevbb70mTaw==", + "requested": "[5.0.0, )", + "resolved": "5.0.0", + "contentHash": "uwV5hdhWPwUH1szhO8PJpFiahqXmzPzJT/sOijH/kFgUx+cyoDTMM8MHD0adw9+Iem6itoibbUXHYslzXsLEAg==", "dependencies": { - "Serilog": "2.5.0", - "System.IO.FileSystem": "4.0.1", - "System.Text.Encoding.Extensions": "4.0.11", - "System.Threading.Timer": "4.0.1" + "Serilog": "2.10.0" } }, "SkiaSharp": { "type": "Direct", - "requested": "[2.80.2, )", - "resolved": "2.80.2", - "contentHash": "D25rzdCwh+3L+XyXqpNa+H/yiLJbE3/R3K/XexwHyQjGdzZvSufFW3oqf3En7hhqSIsxsJ8f5NEZ0J5W5wlGBg==", + "requested": "[2.80.3, )", + "resolved": "2.80.3", + "contentHash": "qX6tGNP3+MXNYe2pKm0PCRiJ/cx+LTeLaggwZifB7sUMXhECfKKKHJq45VqZKt37xQegnCCdf1jHXwmHeJQs5Q==", "dependencies": { "System.Memory": "4.5.3" } diff --git a/src/Artemis.Storage/StorageManager.cs b/src/Artemis.Storage/StorageManager.cs index 267f86fea..93775b16d 100644 --- a/src/Artemis.Storage/StorageManager.cs +++ b/src/Artemis.Storage/StorageManager.cs @@ -19,11 +19,11 @@ namespace Artemis.Storage if (_inUse) throw new Exception("Storage is already in use, can't backup now."); - string database = $"{dataFolder}\\database.db"; + string database = Path.Combine(dataFolder, "database.db"); if (!File.Exists(database)) return; - string backupFolder = $"{dataFolder}\\database backups"; + string backupFolder = Path.Combine(dataFolder, "database backups"); Directory.CreateDirectory(backupFolder); FileSystemInfo[] files = new DirectoryInfo(backupFolder).GetFileSystemInfos(); if (files.Length >= 5) @@ -36,7 +36,7 @@ namespace Artemis.Storage oldest.Delete(); } - File.Copy(database, $"{backupFolder}\\database-{DateTime.Now:yyyy-dd-M--HH-mm-ss}.db"); + File.Copy(database, Path.Combine(backupFolder, $"database-{DateTime.Now:yyyy-dd-M--HH-mm-ss}.db")); } /// @@ -51,7 +51,7 @@ namespace Artemis.Storage try { _inUse = true; - return new LiteRepository($"FileName={dataFolder}\\database.db"); + return new LiteRepository($"FileName={Path.Combine(dataFolder, "database.db")}"); } catch (LiteException e) { diff --git a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj index 857718991..6d9dc0fe0 100644 --- a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj +++ b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj @@ -40,8 +40,8 @@ - - + + diff --git a/src/Artemis.UI.Shared/packages.lock.json b/src/Artemis.UI.Shared/packages.lock.json index f83560583..3ed7085b2 100644 --- a/src/Artemis.UI.Shared/packages.lock.json +++ b/src/Artemis.UI.Shared/packages.lock.json @@ -60,21 +60,21 @@ }, "SkiaSharp": { "type": "Direct", - "requested": "[2.80.2, )", - "resolved": "2.80.2", - "contentHash": "D25rzdCwh+3L+XyXqpNa+H/yiLJbE3/R3K/XexwHyQjGdzZvSufFW3oqf3En7hhqSIsxsJ8f5NEZ0J5W5wlGBg==", + "requested": "[2.80.3, )", + "resolved": "2.80.3", + "contentHash": "qX6tGNP3+MXNYe2pKm0PCRiJ/cx+LTeLaggwZifB7sUMXhECfKKKHJq45VqZKt37xQegnCCdf1jHXwmHeJQs5Q==", "dependencies": { "System.Memory": "4.5.3" } }, "SkiaSharp.Views.WPF": { "type": "Direct", - "requested": "[2.80.2, )", - "resolved": "2.80.2", - "contentHash": "Fzo2+MNwHDh9Cob8sk7OO26kp3bhofjXMwlEK8IncF1ehu9hi3sH9iQDJrue9a88VEJJ+yyLISPUFcmXlGHSyQ==", + "requested": "[2.80.3, )", + "resolved": "2.80.3", + "contentHash": "oKUMm7WzFeoie6rW5nnwSGKZ94misyRsAc1wU6SEqgd6ssW17nyfohHxHuBHtmpLtIRwvjhfAu3cMLrpX/oNcw==", "dependencies": { - "SkiaSharp": "2.80.2", - "SkiaSharp.Views.Desktop.Common": "2.80.2" + "SkiaSharp": "2.80.3", + "SkiaSharp.Views.Desktop.Common": "2.80.3" } }, "Stylet": { @@ -378,13 +378,10 @@ }, "Serilog.Sinks.Console": { "type": "Transitive", - "resolved": "3.1.1", - "contentHash": "56mI5AqvyF/i/c2451nvV71kq370XOCE4Uu5qiaJ295sOhMb9q3BWwG7mWLOVSnmpWiq0SBT3SXfgRXGNP6vzA==", + "resolved": "4.0.0", + "contentHash": "yJQit9sTJ4xGLKgCujqDJsaGqBNJwGB/H898z+xYlMG06twy4//6LLnSrsmpduZxcHIG4im7cv+JmXLzXz2EkQ==", "dependencies": { - "Serilog": "2.5.0", - "System.Console": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.3.0" + "Serilog": "2.10.0" } }, "Serilog.Sinks.Debug": { @@ -397,21 +394,19 @@ }, "Serilog.Sinks.File": { "type": "Transitive", - "resolved": "4.1.0", - "contentHash": "U0b34w+ZikbqWEZ3ui7BdzxY/19zwrdhLtI3o6tfmLdD3oXxg7n2TZJjwCCTlKPgRuYic9CBWfrZevbb70mTaw==", + "resolved": "5.0.0", + "contentHash": "uwV5hdhWPwUH1szhO8PJpFiahqXmzPzJT/sOijH/kFgUx+cyoDTMM8MHD0adw9+Iem6itoibbUXHYslzXsLEAg==", "dependencies": { - "Serilog": "2.5.0", - "System.IO.FileSystem": "4.0.1", - "System.Text.Encoding.Extensions": "4.0.11", - "System.Threading.Timer": "4.0.1" + "Serilog": "2.10.0" } }, "SkiaSharp.Views.Desktop.Common": { "type": "Transitive", - "resolved": "2.80.2", - "contentHash": "0vBvweMysgl1wgjuTQUhdJMD5z5nBjtYqmnHPeX+qHfkc336Wj2L3jEqwmGb0YP+RV47gFGz0EzMAW6szZch9w==", + "resolved": "2.80.3", + "contentHash": "CMQu9fr3BxGRsRryDC6lkYbYaSI2CI+RqisFX0WIdbOdbigUOLhqchmKIMb4EdFAZk13vk862qiE9v97iDZS7g==", "dependencies": { - "SkiaSharp": "2.80.2" + "SkiaSharp": "2.80.3", + "System.Drawing.Common": "4.5.1" } }, "System.AppContext": { @@ -1309,10 +1304,10 @@ "Ninject.Extensions.ChildKernel": "3.3.0", "Ninject.Extensions.Conventions": "3.3.0", "Serilog": "2.10.0", - "Serilog.Sinks.Console": "3.1.1", + "Serilog.Sinks.Console": "4.0.0", "Serilog.Sinks.Debug": "2.0.0", - "Serilog.Sinks.File": "4.1.0", - "SkiaSharp": "2.80.2", + "Serilog.Sinks.File": "5.0.0", + "SkiaSharp": "2.80.3", "System.Buffers": "4.5.1", "System.IO.FileSystem.AccessControl": "5.0.0", "System.Numerics.Vectors": "4.5.0", diff --git a/src/Artemis.UI/ApplicationStateManager.cs b/src/Artemis.UI/ApplicationStateManager.cs index 45dc2098c..d2ac6eb02 100644 --- a/src/Artemis.UI/ApplicationStateManager.cs +++ b/src/Artemis.UI/ApplicationStateManager.cs @@ -92,7 +92,10 @@ namespace Artemis.UI using HttpClient client = new(); try { - HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground")); + CancellationTokenSource cts = new(); + cts.CancelAfter(2000); + + HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground"), cts.Token); httpResponseMessage.EnsureSuccessStatusCode(); return true; } diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index c7a744e8a..4576072f0 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -138,7 +138,7 @@ - + @@ -153,8 +153,8 @@ - - + + diff --git a/src/Artemis.UI/Screens/Home/HomeView.xaml b/src/Artemis.UI/Screens/Home/HomeView.xaml index ce8cd134b..142cd5d14 100644 --- a/src/Artemis.UI/Screens/Home/HomeView.xaml +++ b/src/Artemis.UI/Screens/Home/HomeView.xaml @@ -38,199 +38,193 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - + + - - + + - + - Have a chat - Plugins + - If you need help, have some feedback or have any other questions feel free to contact us through any of the - following channels. + Artemis is built up using plugins. This means devices, brushes, effects and modules (for supporting games!) can all be added via plugins. + Under Settings > Plugins you can find your currently installed plugins, these default plugins are created by Artemis developers. + + We're also keeping track of a list of third-party plugins on our wiki. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Open Source - - This project is completely open source. If you like it and want to say thanks you could hit the GitHub Star button, - I like numbers. You could even make plugins, there's a full documentation on the website - - - + - Feel like you want to make a donation? It would be gratefully received. Click the button to donate via PayPal. + Want more plugins? You can find them on our wiki. + + + + + + + + + + + + + + Have a chat + + If you need help, have some feedback or have any other questions feel free to contact us through any of the + following channels. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Open Source + + This project is completely open source. If you like it and want to say thanks you could hit the GitHub Star button, + I like numbers. You could even make plugins, there's a full documentation on the website + + + + + + + Feel like you want to make a donation? It would be gratefully received. Click the button to donate via PayPal. + + + + + + - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs index 40f94cf4e..f1b262dc6 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Visualization/ProfileViewModel.cs @@ -259,8 +259,14 @@ namespace Artemis.UI.Screens.ProfileEditor.Visualization public void ResetZoomAndPan() { + if (!Devices.Any()) + { + PanZoomViewModel.Reset(); + return; + } + // Create a rect surrounding all devices - SKRect rect = new SKRect( + SKRect rect = new( Devices.Min(d => d.Rectangle.Left), Devices.Min(d => d.Rectangle.Top), Devices.Max(d => d.Rectangle.Right), diff --git a/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs index 244b7e448..6d2800bfa 100644 --- a/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Device/Tabs/DevicePropertiesTabViewModel.cs @@ -140,7 +140,7 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs Device.GreenScale = GreenScale / 100f; Device.BlueScale = BlueScale / 100f; - _rgbService.Surface.Update(true); + _rgbService.FlushLeds = true; } public void BrowseCustomLayout(object sender, MouseEventArgs e) diff --git a/src/Artemis.UI/Screens/Settings/SettingsTabsView.xaml b/src/Artemis.UI/Screens/Settings/SettingsTabsView.xaml index 4b4908289..5c0c68c7a 100644 --- a/src/Artemis.UI/Screens/Settings/SettingsTabsView.xaml +++ b/src/Artemis.UI/Screens/Settings/SettingsTabsView.xaml @@ -5,6 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Artemis.UI.Screens.Settings" xmlns:s="https://github.com/canton7/Stylet" + xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:SettingsTabsViewModel}"> @@ -16,18 +17,31 @@ - - - - - - - + + + + + + + + + + + + + diff --git a/src/Artemis.UI/Screens/Settings/SettingsTabsViewModel.cs b/src/Artemis.UI/Screens/Settings/SettingsTabsViewModel.cs index b85fd4c8e..fd3cbb27c 100644 --- a/src/Artemis.UI/Screens/Settings/SettingsTabsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/SettingsTabsViewModel.cs @@ -2,18 +2,23 @@ using Artemis.UI.Screens.Settings.Tabs.Devices; using Artemis.UI.Screens.Settings.Tabs.General; using Artemis.UI.Screens.Settings.Tabs.Plugins; +using Artemis.UI.Services; using Stylet; namespace Artemis.UI.Screens.Settings { public class SettingsTabsViewModel : Conductor.Collection.OneActive { + private readonly IDebugService _debugService; + public SettingsTabsViewModel( GeneralSettingsTabViewModel generalSettingsTabViewModel, PluginSettingsTabViewModel pluginSettingsTabViewModel, DeviceSettingsTabViewModel deviceSettingsTabViewModel, - AboutTabViewModel aboutTabViewModel) + AboutTabViewModel aboutTabViewModel, + IDebugService debugService) { + _debugService = debugService; DisplayName = "Settings"; Items.Add(generalSettingsTabViewModel); @@ -23,5 +28,10 @@ namespace Artemis.UI.Screens.Settings ActiveItem = generalSettingsTabViewModel; } + + public void ShowDebugger() + { + _debugService.ShowDebugger(); + } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs index 97ca37389..542a24c5a 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginFeatureViewModel.cs @@ -37,7 +37,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins } public PluginFeatureInfo FeatureInfo { get; } - public Exception LoadException => FeatureInfo.Instance?.LoadException; + public Exception LoadException => FeatureInfo.LoadException; public bool ShowShield { get; } @@ -81,7 +81,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins public async Task InstallPrerequisites() { if (FeatureInfo.Prerequisites.Any()) - await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, new List { FeatureInfo }); + await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, new List {FeatureInfo}); } public async Task RemovePrerequisites() @@ -119,12 +119,19 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins private async Task UpdateEnabled(bool enable) { - if (IsEnabled == enable || FeatureInfo.Instance == null) + if (IsEnabled == enable) { NotifyOfPropertyChange(nameof(IsEnabled)); return; } + if (FeatureInfo.Instance == null) + { + NotifyOfPropertyChange(nameof(IsEnabled)); + _messageService.ShowMessage($"Feature '{FeatureInfo.Name}' is in a broken state and cannot enable.", "VIEW LOGS", ShowLogsFolder); + return; + } + if (enable) { Enabling = true; @@ -144,7 +151,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins // Check if all prerequisites are met async if (!FeatureInfo.ArePrerequisitesMet()) { - await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, new List { FeatureInfo }); + await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, new List {FeatureInfo}); if (!FeatureInfo.ArePrerequisitesMet()) { NotifyOfPropertyChange(nameof(IsEnabled)); @@ -156,7 +163,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins } catch (Exception e) { - _messageService.ShowMessage($"Failed to enable {FeatureInfo.Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder); + _messageService.ShowMessage($"Failed to enable '{FeatureInfo.Name}'.\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder); } finally { diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabView.xaml index e0f0eba86..bbd48ac62 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabView.xaml @@ -20,6 +20,7 @@ + The list below shows all loaded plugins. @@ -38,7 +39,17 @@ Margin="5 0" Text="{Binding SearchPluginInput, Delay=300, UpdateSourceTrigger=PropertyChanged}" /> - + +