Code style - Use file scoped namespaces

Code style - Ran code cleanup
This commit is contained in:
Robert committed 2022-08-21 11:36:15 +02:00
1 parent e68e16df4d
commit f6090dc296
691 files changed
+37301 -38119

No files matched your search

@@ -1,67 +1,66 @@
using SkiaSharp;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using SkiaSharp;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
internal class ColorCube
{
internal class ColorCube
private readonly List<SKColor> _colors;
internal ColorCube(IEnumerable<SKColor> colors)
{
private readonly List<SKColor> _colors;
internal ColorCube(IEnumerable<SKColor> colors)
if (colors.Count() < 2)
{
if (colors.Count() < 2)
{
_colors = colors.ToList();
return;
}
int redRange = colors.Max(c => c.Red) - colors.Min(c => c.Red);
int greenRange = colors.Max(c => c.Green) - colors.Min(c => c.Green);
int blueRange = colors.Max(c => c.Blue) - colors.Min(c => c.Blue);
if (redRange > greenRange && redRange > blueRange)
_colors = colors.OrderBy(a => a.Red).ToList();
else if (greenRange > blueRange)
_colors = colors.OrderBy(a => a.Green).ToList();
else
_colors = colors.OrderBy(a => a.Blue).ToList();
_colors = colors.ToList();
return;
}
internal bool TrySplit([NotNullWhen(returnValue: true)] out ColorCube? a, [NotNullWhen(returnValue: true)] out ColorCube? b)
int redRange = colors.Max(c => c.Red) - colors.Min(c => c.Red);
int greenRange = colors.Max(c => c.Green) - colors.Min(c => c.Green);
int blueRange = colors.Max(c => c.Blue) - colors.Min(c => c.Blue);
if (redRange > greenRange && redRange > blueRange)
_colors = colors.OrderBy(a => a.Red).ToList();
else if (greenRange > blueRange)
_colors = colors.OrderBy(a => a.Green).ToList();
else
_colors = colors.OrderBy(a => a.Blue).ToList();
}
internal bool TrySplit([NotNullWhen(true)] out ColorCube? a, [NotNullWhen(true)] out ColorCube? b)
{
if (_colors.Count < 2)
{
if (_colors.Count < 2)
{
a = null;
b = null;
return false;
}
int median = _colors.Count / 2;
a = new ColorCube(_colors.GetRange(0, median));
b = new ColorCube(_colors.GetRange(median, _colors.Count - median));
return true;
a = null;
b = null;
return false;
}
internal SKColor GetAverageColor()
int median = _colors.Count / 2;
a = new ColorCube(_colors.GetRange(0, median));
b = new ColorCube(_colors.GetRange(median, _colors.Count - median));
return true;
}
internal SKColor GetAverageColor()
{
int r = 0, g = 0, b = 0;
for (int i = 0; i < _colors.Count; i++)
{
int r = 0, g = 0, b = 0;
for (int i = 0; i < _colors.Count; i++)
{
r += _colors[i].Red;
g += _colors[i].Green;
b += _colors[i].Blue;
}
return new SKColor(
(byte)(r / _colors.Count),
(byte)(g / _colors.Count),
(byte)(b / _colors.Count)
);
r += _colors[i].Red;
g += _colors[i].Green;
b += _colors[i].Blue;
}
return new SKColor(
(byte) (r / _colors.Count),
(byte) (g / _colors.Count),
(byte) (b / _colors.Count)
);
}
}
@@ -3,145 +3,145 @@ using System.Collections.Generic;
using System.Linq;
using SkiaSharp;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <inheritdoc />
internal class ColorQuantizerService : IColorQuantizerService
{
/// <inheritdoc />
internal class ColorQuantizerService : IColorQuantizerService
private static float GetScore(SKColor color, ColorType type, bool ignoreLimits = false)
{
/// <inheritdoc />
public SKColor[] Quantize(IEnumerable<SKColor> colors, int amount)
static float InvertDiff(float value, float target)
{
if ((amount & (amount - 1)) != 0)
throw new ArgumentException("Must be power of two", nameof(amount));
return 1 - Math.Abs(value - target);
}
Queue<ColorCube> cubes = new(amount);
cubes.Enqueue(new ColorCube(colors));
color.ToHsl(out float _, out float saturation, out float luma);
saturation /= 100f;
luma /= 100f;
while (cubes.Count < amount)
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;
}
/// <inheritdoc />
public SKColor[] Quantize(IEnumerable<SKColor> colors, int amount)
{
if ((amount & (amount - 1)) != 0)
throw new ArgumentException("Must be power of two", nameof(amount));
Queue<ColorCube> cubes = new(amount);
cubes.Enqueue(new ColorCube(colors));
while (cubes.Count < amount)
{
ColorCube cube = cubes.Dequeue();
if (cube.TrySplit(out ColorCube? a, out ColorCube? b))
{
ColorCube cube = cubes.Dequeue();
if (cube.TrySplit(out ColorCube? a, out ColorCube? b))
cubes.Enqueue(a);
cubes.Enqueue(b);
}
}
return cubes.Select(c => c.GetAverageColor()).ToArray();
}
/// <inheritdoc />
public SKColor FindColorVariation(IEnumerable<SKColor> colors, ColorType type, bool ignoreLimits = false)
{
float bestColorScore = 0;
SKColor bestColor = SKColor.Empty;
foreach (SKColor clr in colors)
{
float score = GetScore(clr, type, ignoreLimits);
if (score > bestColorScore)
{
bestColorScore = score;
bestColor = clr;
}
}
return bestColor;
}
/// <inheritdoc />
public ColorSwatch FindAllColorVariations(IEnumerable<SKColor> 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 (SKColor 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)
{
cubes.Enqueue(a);
cubes.Enqueue(b);
bestScore = newScore;
bestColor = newColor;
}
}
return cubes.Select(c => c.GetAverageColor()).ToArray();
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);
}
/// <inheritdoc />
public SKColor FindColorVariation(IEnumerable<SKColor> colors, ColorType type, bool ignoreLimits = false)
return new ColorSwatch
{
float bestColorScore = 0;
SKColor bestColor = SKColor.Empty;
Vibrant = bestVibrantColor,
LightVibrant = bestLightVibrantColor,
DarkVibrant = bestDarkVibrantColor,
Muted = bestMutedColor,
LightMuted = bestLightMutedColor,
DarkMuted = bestDarkMutedColor
};
}
foreach (SKColor clr in colors)
{
float score = GetScore(clr, type, ignoreLimits);
if (score > bestColorScore)
{
bestColorScore = score;
bestColor = clr;
}
}
#region Constants
return bestColor;
}
private const float targetDarkLuma = 0.26f;
private const float maxDarkLuma = 0.45f;
private const float minLightLuma = 0.55f;
private const float targetLightLuma = 0.74f;
private const float minNormalLuma = 0.3f;
private const float targetNormalLuma = 0.5f;
private const float maxNormalLuma = 0.7f;
private const float targetMutesSaturation = 0.3f;
private const float maxMutesSaturation = 0.3f;
private const float targetVibrantSaturation = 1.0f;
private const float minVibrantSaturation = 0.35f;
private const float weightSaturation = 3f;
private const float weightLuma = 5f;
/// <inheritdoc />
public ColorSwatch FindAllColorVariations(IEnumerable<SKColor> 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 (SKColor 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 ColorSwatch
{
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;
private const float maxDarkLuma = 0.45f;
private const float minLightLuma = 0.55f;
private const float targetLightLuma = 0.74f;
private const float minNormalLuma = 0.3f;
private const float targetNormalLuma = 0.5f;
private const float maxNormalLuma = 0.7f;
private const float targetMutesSaturation = 0.3f;
private const float maxMutesSaturation = 0.3f;
private const float targetVibrantSaturation = 1.0f;
private const float minVibrantSaturation = 0.35f;
private const float weightSaturation = 3f;
private const float weightLuma = 5f;
private static float GetTargetLuma(ColorType colorType) => colorType switch
private static float GetTargetLuma(ColorType colorType)
{
return colorType switch
{
ColorType.Vibrant => targetNormalLuma,
ColorType.LightVibrant => targetLightLuma,
@@ -151,8 +151,11 @@ namespace Artemis.Core.Services
ColorType.DarkMuted => targetDarkLuma,
_ => throw new ArgumentException(nameof(colorType))
};
}
private static float GetMinLuma(ColorType colorType) => colorType switch
private static float GetMinLuma(ColorType colorType)
{
return colorType switch
{
ColorType.Vibrant => minNormalLuma,
ColorType.LightVibrant => minLightLuma,
@@ -162,8 +165,11 @@ namespace Artemis.Core.Services
ColorType.DarkMuted => 0,
_ => throw new ArgumentException(nameof(colorType))
};
}
private static float GetMaxLuma(ColorType colorType) => colorType switch
private static float GetMaxLuma(ColorType colorType)
{
return colorType switch
{
ColorType.Vibrant => maxNormalLuma,
ColorType.LightVibrant => 1f,
@@ -173,8 +179,11 @@ namespace Artemis.Core.Services
ColorType.DarkMuted => maxDarkLuma,
_ => throw new ArgumentException(nameof(colorType))
};
}
private static float GetTargetSaturation(ColorType colorType) => colorType switch
private static float GetTargetSaturation(ColorType colorType)
{
return colorType switch
{
ColorType.Vibrant => targetVibrantSaturation,
ColorType.LightVibrant => targetVibrantSaturation,
@@ -184,8 +193,11 @@ namespace Artemis.Core.Services
ColorType.DarkMuted => targetMutesSaturation,
_ => throw new ArgumentException(nameof(colorType))
};
}
private static float GetMinSaturation(ColorType colorType) => colorType switch
private static float GetMinSaturation(ColorType colorType)
{
return colorType switch
{
ColorType.Vibrant => minVibrantSaturation,
ColorType.LightVibrant => minVibrantSaturation,
@@ -195,8 +207,11 @@ namespace Artemis.Core.Services
ColorType.DarkMuted => 0,
_ => throw new ArgumentException(nameof(colorType))
};
}
private static float GetMaxSaturation(ColorType colorType) => colorType switch
private static float GetMaxSaturation(ColorType colorType)
{
return colorType switch
{
ColorType.Vibrant => 1f,
ColorType.LightVibrant => 1f,
@@ -206,7 +221,7 @@ namespace Artemis.Core.Services
ColorType.DarkMuted => maxMutesSaturation,
_ => throw new ArgumentException(nameof(colorType))
};
#endregion
}
#endregion
}
@@ -1,40 +1,39 @@
using SkiaSharp;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Swatch containing the known useful color variations.
/// </summary>
public struct ColorSwatch
{
/// <summary>
/// Swatch containing the known useful color variations.
/// The <see cref="ColorType.Vibrant" /> component.
/// </summary>
public struct ColorSwatch
{
/// <summary>
/// The <see cref="ColorType.Vibrant"/> component.
/// </summary>
public SKColor Vibrant { get; init; }
public SKColor Vibrant { get; init; }
/// <summary>
/// The <see cref="ColorType.LightVibrant"/> component.
/// </summary>
public SKColor LightVibrant { get; init; }
/// <summary>
/// The <see cref="ColorType.LightVibrant" /> component.
/// </summary>
public SKColor LightVibrant { get; init; }
/// <summary>
/// The <see cref="ColorType.DarkVibrant"/> component.
/// </summary>
public SKColor DarkVibrant { get; init; }
/// <summary>
/// The <see cref="ColorType.DarkVibrant" /> component.
/// </summary>
public SKColor DarkVibrant { get; init; }
/// <summary>
/// The <see cref="ColorType.Muted"/> component.
/// </summary>
public SKColor Muted { get; init; }
/// <summary>
/// The <see cref="ColorType.Muted" /> component.
/// </summary>
public SKColor Muted { get; init; }
/// <summary>
/// The <see cref="ColorType.LightMuted"/> component.
/// </summary>
public SKColor LightMuted { get; init; }
/// <summary>
/// The <see cref="ColorType.LightMuted" /> component.
/// </summary>
public SKColor LightMuted { get; init; }
/// <summary>
/// The <see cref="ColorType.DarkMuted"/> component.
/// </summary>
public SKColor DarkMuted { get; init; }
}
}
/// <summary>
/// The <see cref="ColorType.DarkMuted" /> component.
/// </summary>
public SKColor DarkMuted { get; init; }
}
@@ -1,38 +1,37 @@
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// The types of relevant colors in an image.
/// </summary>
public enum ColorType
{
/// <summary>
/// The types of relevant colors in an image.
/// Represents a saturated color.
/// </summary>
public enum ColorType
{
/// <summary>
/// Represents a saturated color.
/// </summary>
Vibrant,
Vibrant,
/// <summary>
/// Represents a saturated and light color.
/// </summary>
LightVibrant,
/// <summary>
/// Represents a saturated and light color.
/// </summary>
LightVibrant,
/// <summary>
/// Represents a saturated and dark color.
/// </summary>
DarkVibrant,
/// <summary>
/// Represents a saturated and dark color.
/// </summary>
DarkVibrant,
/// <summary>
/// Represents a desaturated color.
/// </summary>
Muted,
/// <summary>
/// Represents a desaturated color.
/// </summary>
Muted,
/// <summary>
/// Represents a desaturated and light color.
/// </summary>
LightMuted,
/// <summary>
/// Represents a desaturated and light color.
/// </summary>
LightMuted,
/// <summary>
/// Represents a desaturated and dark color.
/// </summary>
DarkMuted
}
/// <summary>
/// Represents a desaturated and dark color.
/// </summary>
DarkMuted
}
@@ -1,38 +1,46 @@
using SkiaSharp;
using System.Collections.Generic;
using System.Collections.Generic;
using SkiaSharp;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// A service providing a pallette of colors in a bitmap based on vibrant.js
/// </summary>
public interface IColorQuantizerService : IArtemisService
{
/// <summary>
/// A service providing a pallette of colors in a bitmap based on vibrant.js
/// Reduces an <see cref="IEnumerable{SKColor}" /> to a given amount of relevant colors. Based on the Median Cut
/// algorithm
/// </summary>
public interface IColorQuantizerService : IArtemisService
{
/// <summary>
/// Reduces an <see cref="IEnumerable{SKColor}"/> to a given amount of relevant colors. Based on the Median Cut algorithm
/// </summary>
/// <param name="colors">The colors to quantize.</param>
/// <param name="amount">The number of colors that should be calculated. Must be a power of two.</param>
/// <returns>The quantized colors.</returns>
public SKColor[] Quantize(IEnumerable<SKColor> colors, int amount);
/// <param name="colors">The colors to quantize.</param>
/// <param name="amount">The number of colors that should be calculated. Must be a power of two.</param>
/// <returns>The quantized colors.</returns>
public SKColor[] Quantize(IEnumerable<SKColor> colors, int amount);
/// <summary>
/// Finds colors with certain characteristics in a given <see cref="IEnumerable{SKColor}"/>.<para />
/// Vibrant variants are more saturated, while Muted colors are less.<para />
/// Light and Dark colors have higher and lower lightness values, respectively.
/// </summary>
/// <param name="colors">The colors to find the variations in</param>
/// <param name="type">Which type of color to find</param>
/// <param name="ignoreLimits">Ignore hard limits on whether a color is considered for each category. Result may be <see cref="SKColor.Empty"/> if this is false</param>
/// <returns>The color found</returns>
public SKColor FindColorVariation(IEnumerable<SKColor> colors, ColorType type, bool ignoreLimits = false);
/// <summary>
/// Finds colors with certain characteristics in a given <see cref="IEnumerable{SKColor}" />.
/// <para />
/// Vibrant variants are more saturated, while Muted colors are less.
/// <para />
/// Light and Dark colors have higher and lower lightness values, respectively.
/// </summary>
/// <param name="colors">The colors to find the variations in</param>
/// <param name="type">Which type of color to find</param>
/// <param name="ignoreLimits">
/// Ignore hard limits on whether a color is considered for each category. Result may be
/// <see cref="SKColor.Empty" /> if this is false
/// </param>
/// <returns>The color found</returns>
public SKColor FindColorVariation(IEnumerable<SKColor> colors, ColorType type, bool ignoreLimits = false);
/// <summary>
/// Finds all the color variations available and returns a struct containing them all.
/// </summary>
/// <param name="colors">The colors to find the variations in</param>
/// <param name="ignoreLimits">Ignore hard limits on whether a color is considered for each category. Some colors may be <see cref="SKColor.Empty"/> if this is false</param>
/// <returns>A swatch containing all color variations</returns>
public ColorSwatch FindAllColorVariations(IEnumerable<SKColor> colors, bool ignoreLimits = false);
}
}
/// <summary>
/// Finds all the color variations available and returns a struct containing them all.
/// </summary>
/// <param name="colors">The colors to find the variations in</param>
/// <param name="ignoreLimits">
/// Ignore hard limits on whether a color is considered for each category. Some colors may be
/// <see cref="SKColor.Empty" /> if this is false
/// </param>
/// <returns>A swatch containing all color variations</returns>
public ColorSwatch FindAllColorVariations(IEnumerable<SKColor> colors, bool ignoreLimits = false);
}
+34 -35
View File
@@ -1,47 +1,46 @@
using System.Globalization;
using Newtonsoft.Json;
namespace Artemis.Core.Services.Core
namespace Artemis.Core.Services.Core;
/// <summary>
/// Represents build information related to the currently running Artemis build
/// </summary>
public class BuildInfo
{
/// <summary>
/// Represents build information related to the currently running Artemis build
/// Gets the unique ID of this build
/// </summary>
public class BuildInfo
{
/// <summary>
/// Gets the unique ID of this build
/// </summary>
[JsonProperty]
public int BuildId { get; internal set; }
[JsonProperty]
public int BuildId { get; internal set; }
/// <summary>
/// Gets the build number. This contains the date and the build count for that day.
/// <para>Per example <c>20210108.4</c></para>
/// </summary>
[JsonProperty]
public double BuildNumber { get; internal set; }
/// <summary>
/// Gets the build number. This contains the date and the build count for that day.
/// <para>Per example <c>20210108.4</c></para>
/// </summary>
[JsonProperty]
public double BuildNumber { get; internal set; }
/// <summary>
/// Gets the build number formatted as a string. This contains the date and the build count for that day.
/// <para>Per example <c>20210108.4</c></para>
/// </summary>
public string BuildNumberDisplay => BuildNumber.ToString(CultureInfo.InvariantCulture);
/// <summary>
/// Gets the build number formatted as a string. This contains the date and the build count for that day.
/// <para>Per example <c>20210108.4</c></para>
/// </summary>
public string BuildNumberDisplay => BuildNumber.ToString(CultureInfo.InvariantCulture);
/// <summary>
/// Gets the branch of the triggering repo the build was created for.
/// </summary>
[JsonProperty]
public string SourceBranch { get; internal set; } = null!;
/// <summary>
/// Gets the branch of the triggering repo the build was created for.
/// </summary>
[JsonProperty]
public string SourceBranch { get; internal set; } = null!;
/// <summary>
/// Gets the commit ID used to create this build
/// </summary>
[JsonProperty]
public string SourceVersion { get; internal set; } = null!;
/// <summary>
/// Gets the commit ID used to create this build
/// </summary>
[JsonProperty]
public string SourceVersion { get; internal set; } = null!;
/// <summary>
/// Gets a boolean indicating whether the current build is a local build
/// </summary>
public bool IsLocalBuild { get; internal set; }
}
/// <summary>
/// Gets a boolean indicating whether the current build is a local build
/// </summary>
public bool IsLocalBuild { get; internal set; }
}
+211 -212
View File
@@ -13,232 +13,231 @@ using Serilog;
using Serilog.Events;
using SkiaSharp;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Provides Artemis's core update loop
/// </summary>
internal class CoreService : ICoreService
{
/// <summary>
/// Provides Artemis's core update loop
/// </summary>
internal class CoreService : ICoreService
internal static IKernel? Kernel;
private readonly Stopwatch _frameStopWatch;
private readonly ILogger _logger;
private readonly PluginSetting<LogEventLevel> _loggingLevel;
private readonly IModuleService _moduleService;
private readonly IPluginManagementService _pluginManagementService;
private readonly IProfileService _profileService;
private readonly IRgbService _rgbService;
private readonly IScriptingService _scriptingService;
private readonly List<Exception> _updateExceptions = new();
private int _frames;
private DateTime _lastExceptionLog;
private DateTime _lastFrameRateSample;
// ReSharper disable UnusedParameter.Local
public CoreService(IKernel kernel,
ILogger logger,
StorageMigrationService _1, // injected to ensure migration runs early
ISettingsService settingsService,
IPluginManagementService pluginManagementService,
IRgbService rgbService,
IProfileService profileService,
IModuleService moduleService,
IScriptingService scriptingService,
IProcessMonitorService _2)
{
internal static IKernel? Kernel;
private readonly Stopwatch _frameStopWatch;
private readonly ILogger _logger;
private readonly PluginSetting<LogEventLevel> _loggingLevel;
private readonly IModuleService _moduleService;
private readonly IPluginManagementService _pluginManagementService;
private readonly IProfileService _profileService;
private readonly IRgbService _rgbService;
private readonly IScriptingService _scriptingService;
private readonly List<Exception> _updateExceptions = new();
Kernel = kernel;
Constants.CorePlugin.Kernel = kernel;
private int _frames;
private DateTime _lastExceptionLog;
private DateTime _lastFrameRateSample;
_logger = logger;
_pluginManagementService = pluginManagementService;
_rgbService = rgbService;
_profileService = profileService;
_moduleService = moduleService;
_scriptingService = scriptingService;
_loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug);
_frameStopWatch = new Stopwatch();
StartupArguments = new List<string>();
// ReSharper disable UnusedParameter.Local
public CoreService(IKernel kernel,
ILogger logger,
StorageMigrationService _1, // injected to ensure migration runs early
ISettingsService settingsService,
IPluginManagementService pluginManagementService,
IRgbService rgbService,
IProfileService profileService,
IModuleService moduleService,
IScriptingService scriptingService,
IProcessMonitorService _2)
_rgbService.Surface.Updating += SurfaceOnUpdating;
_loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel();
}
// ReSharper restore UnusedParameter.Local
protected virtual void OnFrameRendering(FrameRenderingEventArgs e)
{
FrameRendering?.Invoke(this, e);
}
protected virtual void OnFrameRendered(FrameRenderedEventArgs e)
{
FrameRendered?.Invoke(this, e);
}
private void ApplyLoggingLevel()
{
string? argument = StartupArguments.FirstOrDefault(a => a.StartsWith("--logging"));
if (argument != null)
{
Kernel = kernel;
Constants.CorePlugin.Kernel = kernel;
_logger = logger;
_pluginManagementService = pluginManagementService;
_rgbService = rgbService;
_profileService = profileService;
_moduleService = moduleService;
_scriptingService = scriptingService;
_loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug);
_frameStopWatch = new Stopwatch();
StartupArguments = new List<string>();
_rgbService.Surface.Updating += SurfaceOnUpdating;
_loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel();
}
// ReSharper restore UnusedParameter.Local
protected virtual void OnFrameRendering(FrameRenderingEventArgs e)
{
FrameRendering?.Invoke(this, e);
}
protected virtual void OnFrameRendered(FrameRenderedEventArgs e)
{
FrameRendered?.Invoke(this, e);
}
private void ApplyLoggingLevel()
{
string? argument = StartupArguments.FirstOrDefault(a => a.StartsWith("--logging"));
if (argument != null)
// Parse the provided log level
string[] parts = argument.Split('=');
if (parts.Length == 2 && Enum.TryParse(typeof(LogEventLevel), parts[1], true, out object? logLevelArgument))
{
// Parse the provided log level
string[] parts = argument.Split('=');
if (parts.Length == 2 && Enum.TryParse(typeof(LogEventLevel), parts[1], true, out object? logLevelArgument))
{
_logger.Information("Setting logging level to {loggingLevel} from startup argument", (LogEventLevel) logLevelArgument!);
LoggerProvider.LoggingLevelSwitch.MinimumLevel = (LogEventLevel) logLevelArgument;
}
else
{
_logger.Warning("Failed to set log level from startup argument {argument}", argument);
_logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value);
LoggerProvider.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value;
}
_logger.Information("Setting logging level to {loggingLevel} from startup argument", (LogEventLevel) logLevelArgument!);
LoggerProvider.LoggingLevelSwitch.MinimumLevel = (LogEventLevel) logLevelArgument;
}
else
{
_logger.Warning("Failed to set log level from startup argument {argument}", argument);
_logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value);
LoggerProvider.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value;
}
}
private void SurfaceOnUpdating(UpdatingEventArgs args)
else
{
if (_rgbService.IsRenderPaused)
return;
if (_rgbService.FlushLeds)
{
_rgbService.FlushLeds = false;
_rgbService.Surface.Update(true);
return;
}
try
{
_frameStopWatch.Restart();
foreach (GlobalScript script in _scriptingService.GlobalScripts)
script.OnCoreUpdating(args.DeltaTime);
_moduleService.UpdateActiveModules(args.DeltaTime);
SKTexture texture = _rgbService.OpenRender();
SKCanvas canvas = texture.Surface.Canvas;
canvas.Save();
if (Math.Abs(texture.RenderScale - 1) > 0.001)
canvas.Scale(texture.RenderScale);
canvas.Clear(new SKColor(0, 0, 0));
if (!ProfileRenderingDisabled)
{
_profileService.UpdateProfiles(args.DeltaTime);
_profileService.RenderProfiles(canvas);
}
OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface));
canvas.RestoreToCount(-1);
canvas.Flush();
OnFrameRendered(new FrameRenderedEventArgs(texture, _rgbService.Surface));
foreach (GlobalScript script in _scriptingService.GlobalScripts)
script.OnCoreUpdated(args.DeltaTime);
}
catch (Exception e)
{
_updateExceptions.Add(e);
}
finally
{
_rgbService.CloseRender();
_frameStopWatch.Stop();
_frames++;
if ((DateTime.Now - _lastFrameRateSample).TotalSeconds >= 1)
{
FrameRate = _frames;
_frames = 0;
_lastFrameRateSample = DateTime.Now;
}
FrameTime = _frameStopWatch.Elapsed;
LogUpdateExceptions();
}
_logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value);
LoggerProvider.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value;
}
private void LogUpdateExceptions()
{
// Only log update exceptions every 10 seconds to avoid spamming the logs
if (DateTime.Now - _lastExceptionLog < TimeSpan.FromSeconds(10))
return;
_lastExceptionLog = DateTime.Now;
if (!_updateExceptions.Any())
return;
// Group by stack trace, that should gather up duplicate exceptions
foreach (IGrouping<string?, Exception> exceptions in _updateExceptions.GroupBy(e => e.StackTrace))
_logger.Warning(exceptions.First(), "Exception was thrown {count} times during update in the last 10 seconds", exceptions.Count());
// When logging is finished start with a fresh slate
_updateExceptions.Clear();
}
private void OnInitialized()
{
IsInitialized = true;
Initialized?.Invoke(this, EventArgs.Empty);
}
public int FrameRate { get; private set; }
public TimeSpan FrameTime { get; private set; }
public bool ProfileRenderingDisabled { get; set; }
public List<string> StartupArguments { get; set; }
public bool IsElevated { get; set; }
public void Dispose()
{
// Dispose services
_pluginManagementService.Dispose();
}
public bool IsInitialized { get; set; }
public void Initialize()
{
if (IsInitialized)
throw new ArtemisCoreException("Cannot initialize the core as it is already initialized.");
AssemblyInformationalVersionAttribute? versionAttribute = typeof(CoreService).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
_logger.Information(
"Initializing Artemis Core version {version}, build {buildNumber} branch {branch}.",
versionAttribute?.InformationalVersion,
Constants.BuildInfo.BuildNumber,
Constants.BuildInfo.SourceBranch
);
_logger.Information("Startup arguments: {args}", StartupArguments);
_logger.Information("Elevated permissions: {perms}", IsElevated);
_logger.Information("Stopwatch high resolution: {perms}", Stopwatch.IsHighResolution);
ApplyLoggingLevel();
// Don't remove even if it looks useless
// Just this line should prevent a certain someone from removing HidSharp as an unused dependency as well
Version? hidSharpVersion = Assembly.GetAssembly(typeof(HidDevice))!.GetName().Version;
_logger.Debug("Forcing plugins to use HidSharp {hidSharpVersion}", hidSharpVersion);
// Initialize the services
_pluginManagementService.CopyBuiltInPlugins();
_pluginManagementService.LoadPlugins(StartupArguments, IsElevated);
_rgbService.ApplyPreferredGraphicsContext(StartupArguments.Contains("--force-software-render"));
_rgbService.SetRenderPaused(false);
OnInitialized();
}
public event EventHandler? Initialized;
public event EventHandler<FrameRenderingEventArgs>? FrameRendering;
public event EventHandler<FrameRenderedEventArgs>? FrameRendered;
}
private void SurfaceOnUpdating(UpdatingEventArgs args)
{
if (_rgbService.IsRenderPaused)
return;
if (_rgbService.FlushLeds)
{
_rgbService.FlushLeds = false;
_rgbService.Surface.Update(true);
return;
}
try
{
_frameStopWatch.Restart();
foreach (GlobalScript script in _scriptingService.GlobalScripts)
script.OnCoreUpdating(args.DeltaTime);
_moduleService.UpdateActiveModules(args.DeltaTime);
SKTexture texture = _rgbService.OpenRender();
SKCanvas canvas = texture.Surface.Canvas;
canvas.Save();
if (Math.Abs(texture.RenderScale - 1) > 0.001)
canvas.Scale(texture.RenderScale);
canvas.Clear(new SKColor(0, 0, 0));
if (!ProfileRenderingDisabled)
{
_profileService.UpdateProfiles(args.DeltaTime);
_profileService.RenderProfiles(canvas);
}
OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface));
canvas.RestoreToCount(-1);
canvas.Flush();
OnFrameRendered(new FrameRenderedEventArgs(texture, _rgbService.Surface));
foreach (GlobalScript script in _scriptingService.GlobalScripts)
script.OnCoreUpdated(args.DeltaTime);
}
catch (Exception e)
{
_updateExceptions.Add(e);
}
finally
{
_rgbService.CloseRender();
_frameStopWatch.Stop();
_frames++;
if ((DateTime.Now - _lastFrameRateSample).TotalSeconds >= 1)
{
FrameRate = _frames;
_frames = 0;
_lastFrameRateSample = DateTime.Now;
}
FrameTime = _frameStopWatch.Elapsed;
LogUpdateExceptions();
}
}
private void LogUpdateExceptions()
{
// Only log update exceptions every 10 seconds to avoid spamming the logs
if (DateTime.Now - _lastExceptionLog < TimeSpan.FromSeconds(10))
return;
_lastExceptionLog = DateTime.Now;
if (!_updateExceptions.Any())
return;
// Group by stack trace, that should gather up duplicate exceptions
foreach (IGrouping<string?, Exception> exceptions in _updateExceptions.GroupBy(e => e.StackTrace))
_logger.Warning(exceptions.First(), "Exception was thrown {count} times during update in the last 10 seconds", exceptions.Count());
// When logging is finished start with a fresh slate
_updateExceptions.Clear();
}
private void OnInitialized()
{
IsInitialized = true;
Initialized?.Invoke(this, EventArgs.Empty);
}
public int FrameRate { get; private set; }
public TimeSpan FrameTime { get; private set; }
public bool ProfileRenderingDisabled { get; set; }
public List<string> StartupArguments { get; set; }
public bool IsElevated { get; set; }
public void Dispose()
{
// Dispose services
_pluginManagementService.Dispose();
}
public bool IsInitialized { get; set; }
public void Initialize()
{
if (IsInitialized)
throw new ArtemisCoreException("Cannot initialize the core as it is already initialized.");
AssemblyInformationalVersionAttribute? versionAttribute = typeof(CoreService).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
_logger.Information(
"Initializing Artemis Core version {version}, build {buildNumber} branch {branch}.",
versionAttribute?.InformationalVersion,
Constants.BuildInfo.BuildNumber,
Constants.BuildInfo.SourceBranch
);
_logger.Information("Startup arguments: {args}", StartupArguments);
_logger.Information("Elevated permissions: {perms}", IsElevated);
_logger.Information("Stopwatch high resolution: {perms}", Stopwatch.IsHighResolution);
ApplyLoggingLevel();
// Don't remove even if it looks useless
// Just this line should prevent a certain someone from removing HidSharp as an unused dependency as well
Version? hidSharpVersion = Assembly.GetAssembly(typeof(HidDevice))!.GetName().Version;
_logger.Debug("Forcing plugins to use HidSharp {hidSharpVersion}", hidSharpVersion);
// Initialize the services
_pluginManagementService.CopyBuiltInPlugins();
_pluginManagementService.LoadPlugins(StartupArguments, IsElevated);
_rgbService.ApplyPreferredGraphicsContext(StartupArguments.Contains("--force-software-render"));
_rgbService.SetRenderPaused(false);
OnInitialized();
}
public event EventHandler? Initialized;
public event EventHandler<FrameRenderingEventArgs>? FrameRendering;
public event EventHandler<FrameRenderedEventArgs>? FrameRendered;
}
+29 -31
View File
@@ -1,46 +1,44 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using System.Threading.Tasks;
using RGB.NET.Core;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
internal class DeviceService : IDeviceService
{
internal class DeviceService : IDeviceService
private readonly IRgbService _rgbService;
public DeviceService(IRgbService rgbService)
{
private readonly IRgbService _rgbService;
_rgbService = rgbService;
}
public DeviceService(IRgbService rgbService)
private void BlinkDevice(ArtemisDevice device, int blinkCount)
{
// Create a LED group way at the top
ListLedGroup ledGroup = new(_rgbService.Surface, device.Leds.Select(l => l.RgbLed))
{
_rgbService = rgbService;
}
Brush = new SolidColorBrush(new Color(255, 255, 255)),
ZIndex = 999
};
public void IdentifyDevice(ArtemisDevice device)
// After 200ms, detach the LED group
Task.Run(async () =>
{
BlinkDevice(device, 0);
}
await Task.Delay(200);
ledGroup.Detach();
private void BlinkDevice(ArtemisDevice device, int blinkCount)
{
// Create a LED group way at the top
ListLedGroup ledGroup = new(_rgbService.Surface, device.Leds.Select(l => l.RgbLed))
{
Brush = new SolidColorBrush(new Color(255, 255, 255)),
ZIndex = 999
};
// After 200ms, detach the LED group
Task.Run(async () =>
if (blinkCount < 5)
{
// After another 200ms, start over, repeat six times
await Task.Delay(200);
ledGroup.Detach();
BlinkDevice(device, blinkCount + 1);
}
});
}
if (blinkCount < 5)
{
// After another 200ms, start over, repeat six times
await Task.Delay(200);
BlinkDevice(device, blinkCount + 1);
}
});
}
public void IdentifyDevice(ArtemisDevice device)
{
BlinkDevice(device, 0);
}
}
@@ -1,23 +1,22 @@
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Represents a device that provides input to the <see cref="IInputService" />
/// </summary>
public enum InputDeviceType
{
/// <summary>
/// Represents a device that provides input to the <see cref="IInputService" />
/// None
/// </summary>
public enum InputDeviceType
{
/// <summary>
/// None
/// </summary>
None,
None,
/// <summary>
/// A keyboard
/// </summary>
Keyboard,
/// <summary>
/// A keyboard
/// </summary>
Keyboard,
/// <summary>
/// A mouse
/// </summary>
Mouse
}
/// <summary>
/// A mouse
/// </summary>
Mouse
}
@@ -1,537 +1,536 @@
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>Specifies the possible key values on a keyboard.</summary>
public enum KeyboardKey
{
/// <summary>Specifies the possible key values on a keyboard.</summary>
public enum KeyboardKey
{
/// <summary>No key pressed.</summary>
None = 0,
/// <summary>No key pressed.</summary>
None = 0,
/// <summary>The Cancel key.</summary>
Cancel = 1,
/// <summary>The Cancel key.</summary>
Cancel = 1,
/// <summary>The Backspace key.</summary>
Back = 2,
/// <summary>The Backspace key.</summary>
Back = 2,
/// <summary>The Tab key.</summary>
Tab = 3,
/// <summary>The Tab key.</summary>
Tab = 3,
/// <summary>The Linefeed key.</summary>
LineFeed = 4,
/// <summary>The Linefeed key.</summary>
LineFeed = 4,
/// <summary>The Clear key.</summary>
Clear = 5,
/// <summary>The Clear key.</summary>
Clear = 5,
/// <summary>The Enter key.</summary>
Enter = 6,
/// <summary>The Enter key.</summary>
Enter = 6,
/// <summary>The Return key.</summary>
Return = 6,
/// <summary>The Return key.</summary>
Return = 6,
/// <summary>The Pause key.</summary>
Pause = 7,
/// <summary>The Pause key.</summary>
Pause = 7,
/// <summary>The Caps Lock key.</summary>
CapsLock = 8,
/// <summary>The Caps Lock key.</summary>
CapsLock = 8,
/// <summary>The IME Hangul mode key.</summary>
HangulMode = 9,
/// <summary>The IME Hangul mode key.</summary>
HangulMode = 9,
/// <summary>The IME Junja mode key.</summary>
JunjaMode = 10, // 0x0000000A
/// <summary>The IME Junja mode key.</summary>
JunjaMode = 10, // 0x0000000A
/// <summary>The IME Final mode key.</summary>
FinalMode = 11, // 0x0000000B
/// <summary>The IME Final mode key.</summary>
FinalMode = 11, // 0x0000000B
/// <summary>The IME Hanja mode key.</summary>
HanjaMode = 12, // 0x0000000C
/// <summary>The IME Hanja mode key.</summary>
HanjaMode = 12, // 0x0000000C
/// <summary>The ESC key.</summary>
Escape = 13, // 0x0000000D
/// <summary>The ESC key.</summary>
Escape = 13, // 0x0000000D
/// <summary>The IME Convert key.</summary>
ImeConvert = 14, // 0x0000000E
/// <summary>The IME Convert key.</summary>
ImeConvert = 14, // 0x0000000E
/// <summary>The IME NonConvert key.</summary>
ImeNonConvert = 15, // 0x0000000F
/// <summary>The IME NonConvert key.</summary>
ImeNonConvert = 15, // 0x0000000F
/// <summary>The IME Accept key.</summary>
ImeAccept = 16, // 0x00000010
/// <summary>The IME Accept key.</summary>
ImeAccept = 16, // 0x00000010
/// <summary>The IME Mode change request.</summary>
ImeModeChange = 17, // 0x00000011
/// <summary>The IME Mode change request.</summary>
ImeModeChange = 17, // 0x00000011
/// <summary>The Spacebar key.</summary>
Space = 18, // 0x00000012
/// <summary>The Spacebar key.</summary>
Space = 18, // 0x00000012
/// <summary>The Page Up key.</summary>
PageUp = 19, // 0x00000013
/// <summary>The Page Up key.</summary>
PageUp = 19, // 0x00000013
/// <summary>The Page Up key.</summary>
Prior = 19, // 0x00000013
/// <summary>The Page Up key.</summary>
Prior = 19, // 0x00000013
/// <summary>The Page Down key.</summary>
Next = 20, // 0x00000014
/// <summary>The Page Down key.</summary>
Next = 20, // 0x00000014
/// <summary>The Page Down key.</summary>
PageDown = 20, // 0x00000014
/// <summary>The Page Down key.</summary>
PageDown = 20, // 0x00000014
/// <summary>The End key.</summary>
End = 21, // 0x00000015
/// <summary>The End key.</summary>
End = 21, // 0x00000015
/// <summary>The Home key.</summary>
Home = 22, // 0x00000016
/// <summary>The Home key.</summary>
Home = 22, // 0x00000016
/// <summary>The Left Arrow key.</summary>
Left = 23, // 0x00000017
/// <summary>The Left Arrow key.</summary>
Left = 23, // 0x00000017
/// <summary>The Up Arrow key.</summary>
Up = 24, // 0x00000018
/// <summary>The Up Arrow key.</summary>
Up = 24, // 0x00000018
/// <summary>The Right Arrow key.</summary>
Right = 25, // 0x00000019
/// <summary>The Right Arrow key.</summary>
Right = 25, // 0x00000019
/// <summary>The Down Arrow key.</summary>
Down = 26, // 0x0000001A
/// <summary>The Down Arrow key.</summary>
Down = 26, // 0x0000001A
/// <summary>The Select key.</summary>
Select = 27, // 0x0000001B
/// <summary>The Select key.</summary>
Select = 27, // 0x0000001B
/// <summary>The Print key.</summary>
Print = 28, // 0x0000001C
/// <summary>The Print key.</summary>
Print = 28, // 0x0000001C
/// <summary>The Execute key.</summary>
Execute = 29, // 0x0000001D
/// <summary>The Execute key.</summary>
Execute = 29, // 0x0000001D
/// <summary>The Print Screen key.</summary>
PrintScreen = 30, // 0x0000001E
/// <summary>The Print Screen key.</summary>
PrintScreen = 30, // 0x0000001E
/// <summary>The Insert key.</summary>
Insert = 31, // 0x0000001F
/// <summary>The Insert key.</summary>
Insert = 31, // 0x0000001F
/// <summary>The Delete key.</summary>
Delete = 32, // 0x00000020
/// <summary>The Delete key.</summary>
Delete = 32, // 0x00000020
/// <summary>The Help key.</summary>
Help = 33, // 0x00000021
/// <summary>The Help key.</summary>
Help = 33, // 0x00000021
/// <summary>The 0 (zero) key.</summary>
D0 = 34, // 0x00000022
/// <summary>The 0 (zero) key.</summary>
D0 = 34, // 0x00000022
/// <summary>The 1 (one) key.</summary>
D1 = 35, // 0x00000023
/// <summary>The 1 (one) key.</summary>
D1 = 35, // 0x00000023
/// <summary>The 2 key.</summary>
D2 = 36, // 0x00000024
/// <summary>The 2 key.</summary>
D2 = 36, // 0x00000024
/// <summary>The 3 key.</summary>
D3 = 37, // 0x00000025
/// <summary>The 3 key.</summary>
D3 = 37, // 0x00000025
/// <summary>The 4 key.</summary>
D4 = 38, // 0x00000026
/// <summary>The 4 key.</summary>
D4 = 38, // 0x00000026
/// <summary>The 5 key.</summary>
D5 = 39, // 0x00000027
/// <summary>The 5 key.</summary>
D5 = 39, // 0x00000027
/// <summary>The 6 key.</summary>
D6 = 40, // 0x00000028
/// <summary>The 6 key.</summary>
D6 = 40, // 0x00000028
/// <summary>The 7 key.</summary>
D7 = 41, // 0x00000029
/// <summary>The 7 key.</summary>
D7 = 41, // 0x00000029
/// <summary>The 8 key.</summary>
D8 = 42, // 0x0000002A
/// <summary>The 8 key.</summary>
D8 = 42, // 0x0000002A
/// <summary>The 9 key.</summary>
D9 = 43, // 0x0000002B
/// <summary>The 9 key.</summary>
D9 = 43, // 0x0000002B
/// <summary>The A key.</summary>
A = 44, // 0x0000002C
/// <summary>The A key.</summary>
A = 44, // 0x0000002C
/// <summary>The B key.</summary>
B = 45, // 0x0000002D
/// <summary>The B key.</summary>
B = 45, // 0x0000002D
/// <summary>The C key.</summary>
C = 46, // 0x0000002E
/// <summary>The C key.</summary>
C = 46, // 0x0000002E
/// <summary>The D key.</summary>
D = 47, // 0x0000002F
/// <summary>The D key.</summary>
D = 47, // 0x0000002F
/// <summary>The E key.</summary>
E = 48, // 0x00000030
/// <summary>The E key.</summary>
E = 48, // 0x00000030
/// <summary>The F key.</summary>
F = 49, // 0x00000031
/// <summary>The F key.</summary>
F = 49, // 0x00000031
/// <summary>The G key.</summary>
G = 50, // 0x00000032
/// <summary>The G key.</summary>
G = 50, // 0x00000032
/// <summary>The H key.</summary>
H = 51, // 0x00000033
/// <summary>The H key.</summary>
H = 51, // 0x00000033
/// <summary>The I key.</summary>
I = 52, // 0x00000034
/// <summary>The I key.</summary>
I = 52, // 0x00000034
/// <summary>The J key.</summary>
J = 53, // 0x00000035
/// <summary>The J key.</summary>
J = 53, // 0x00000035
/// <summary>The K key.</summary>
K = 54, // 0x00000036
/// <summary>The K key.</summary>
K = 54, // 0x00000036
/// <summary>The L key.</summary>
L = 55, // 0x00000037
/// <summary>The L key.</summary>
L = 55, // 0x00000037
/// <summary>The M key.</summary>
M = 56, // 0x00000038
/// <summary>The M key.</summary>
M = 56, // 0x00000038
/// <summary>The N key.</summary>
N = 57, // 0x00000039
/// <summary>The N key.</summary>
N = 57, // 0x00000039
/// <summary>The O key.</summary>
O = 58, // 0x0000003A
/// <summary>The O key.</summary>
O = 58, // 0x0000003A
/// <summary>The P key.</summary>
P = 59, // 0x0000003B
/// <summary>The P key.</summary>
P = 59, // 0x0000003B
/// <summary>The Q key.</summary>
Q = 60, // 0x0000003C
/// <summary>The Q key.</summary>
Q = 60, // 0x0000003C
/// <summary>The R key.</summary>
R = 61, // 0x0000003D
/// <summary>The R key.</summary>
R = 61, // 0x0000003D
/// <summary>The S key.</summary>
S = 62, // 0x0000003E
/// <summary>The S key.</summary>
S = 62, // 0x0000003E
/// <summary>The T key.</summary>
T = 63, // 0x0000003F
/// <summary>The T key.</summary>
T = 63, // 0x0000003F
/// <summary>The U key.</summary>
U = 64, // 0x00000040
/// <summary>The U key.</summary>
U = 64, // 0x00000040
/// <summary>The V key.</summary>
V = 65, // 0x00000041
/// <summary>The V key.</summary>
V = 65, // 0x00000041
/// <summary>The W key.</summary>
W = 66, // 0x00000042
/// <summary>The W key.</summary>
W = 66, // 0x00000042
/// <summary>The X key.</summary>
X = 67, // 0x00000043
/// <summary>The X key.</summary>
X = 67, // 0x00000043
/// <summary>The Y key.</summary>
Y = 68, // 0x00000044
/// <summary>The Y key.</summary>
Y = 68, // 0x00000044
/// <summary>The Z key.</summary>
Z = 69, // 0x00000045
/// <summary>The Z key.</summary>
Z = 69, // 0x00000045
/// <summary>The left Windows logo key (Microsoft Natural Keyboard).</summary>
LWin = 70, // 0x00000046
/// <summary>The left Windows logo key (Microsoft Natural Keyboard).</summary>
LWin = 70, // 0x00000046
/// <summary>The right Windows logo key (Microsoft Natural Keyboard).</summary>
RWin = 71, // 0x00000047
/// <summary>The right Windows logo key (Microsoft Natural Keyboard).</summary>
RWin = 71, // 0x00000047
/// <summary>
/// The Application key (Microsoft Natural Keyboard). Also known as the Menu key, as it displays an
/// application-specific context menu.
/// </summary>
Apps = 72, // 0x00000048
/// <summary>
/// The Application key (Microsoft Natural Keyboard). Also known as the Menu key, as it displays an
/// application-specific context menu.
/// </summary>
Apps = 72, // 0x00000048
/// <summary>The Computer Sleep key.</summary>
Sleep = 73, // 0x00000049
/// <summary>The Computer Sleep key.</summary>
Sleep = 73, // 0x00000049
/// <summary>The 0 key on the numeric keypad.</summary>
NumPad0 = 74, // 0x0000004A
/// <summary>The 0 key on the numeric keypad.</summary>
NumPad0 = 74, // 0x0000004A
/// <summary>The 1 key on the numeric keypad.</summary>
NumPad1 = 75, // 0x0000004B
/// <summary>The 1 key on the numeric keypad.</summary>
NumPad1 = 75, // 0x0000004B
/// <summary>The 2 key on the numeric keypad.</summary>
NumPad2 = 76, // 0x0000004C
/// <summary>The 2 key on the numeric keypad.</summary>
NumPad2 = 76, // 0x0000004C
/// <summary>The 3 key on the numeric keypad.</summary>
NumPad3 = 77, // 0x0000004D
/// <summary>The 3 key on the numeric keypad.</summary>
NumPad3 = 77, // 0x0000004D
/// <summary>The 4 key on the numeric keypad.</summary>
NumPad4 = 78, // 0x0000004E
/// <summary>The 4 key on the numeric keypad.</summary>
NumPad4 = 78, // 0x0000004E
/// <summary>The 5 key on the numeric keypad.</summary>
NumPad5 = 79, // 0x0000004F
/// <summary>The 5 key on the numeric keypad.</summary>
NumPad5 = 79, // 0x0000004F
/// <summary>The 6 key on the numeric keypad.</summary>
NumPad6 = 80, // 0x00000050
/// <summary>The 6 key on the numeric keypad.</summary>
NumPad6 = 80, // 0x00000050
/// <summary>The 7 key on the numeric keypad.</summary>
NumPad7 = 81, // 0x00000051
/// <summary>The 7 key on the numeric keypad.</summary>
NumPad7 = 81, // 0x00000051
/// <summary>The 8 key on the numeric keypad.</summary>
NumPad8 = 82, // 0x00000052
/// <summary>The 8 key on the numeric keypad.</summary>
NumPad8 = 82, // 0x00000052
/// <summary>The 9 key on the numeric keypad.</summary>
NumPad9 = 83, // 0x00000053
/// <summary>The 9 key on the numeric keypad.</summary>
NumPad9 = 83, // 0x00000053
/// <summary>The Multiply key.</summary>
Multiply = 84, // 0x00000054
/// <summary>The Multiply key.</summary>
Multiply = 84, // 0x00000054
/// <summary>The Add key.</summary>
Add = 85, // 0x00000055
/// <summary>The Add key.</summary>
Add = 85, // 0x00000055
/// <summary>The Separator key.</summary>
Separator = 86, // 0x00000056
/// <summary>The Separator key.</summary>
Separator = 86, // 0x00000056
/// <summary>The Subtract key.</summary>
Subtract = 87, // 0x00000057
/// <summary>The Subtract key.</summary>
Subtract = 87, // 0x00000057
/// <summary>The Decimal key.</summary>
Decimal = 88, // 0x00000058
/// <summary>The Decimal key.</summary>
Decimal = 88, // 0x00000058
/// <summary>The Divide key.</summary>
Divide = 89, // 0x00000059
/// <summary>The Divide key.</summary>
Divide = 89, // 0x00000059
/// <summary>The F1 key.</summary>
F1 = 90, // 0x0000005A
/// <summary>The F1 key.</summary>
F1 = 90, // 0x0000005A
/// <summary>The F2 key.</summary>
F2 = 91, // 0x0000005B
/// <summary>The F2 key.</summary>
F2 = 91, // 0x0000005B
/// <summary>The F3 key.</summary>
F3 = 92, // 0x0000005C
/// <summary>The F3 key.</summary>
F3 = 92, // 0x0000005C
/// <summary>The F4 key.</summary>
F4 = 93, // 0x0000005D
/// <summary>The F4 key.</summary>
F4 = 93, // 0x0000005D
/// <summary>The F5 key.</summary>
F5 = 94, // 0x0000005E
/// <summary>The F5 key.</summary>
F5 = 94, // 0x0000005E
/// <summary>The F6 key.</summary>
F6 = 95, // 0x0000005F
/// <summary>The F6 key.</summary>
F6 = 95, // 0x0000005F
/// <summary>The F7 key.</summary>
F7 = 96, // 0x00000060
/// <summary>The F7 key.</summary>
F7 = 96, // 0x00000060
/// <summary>The F8 key.</summary>
F8 = 97, // 0x00000061
/// <summary>The F8 key.</summary>
F8 = 97, // 0x00000061
/// <summary>The F9 key.</summary>
F9 = 98, // 0x00000062
/// <summary>The F9 key.</summary>
F9 = 98, // 0x00000062
/// <summary>The F10 key.</summary>
F10 = 99, // 0x00000063
/// <summary>The F10 key.</summary>
F10 = 99, // 0x00000063
/// <summary>The F11 key.</summary>
F11 = 100, // 0x00000064
/// <summary>The F11 key.</summary>
F11 = 100, // 0x00000064
/// <summary>The F12 key.</summary>
F12 = 101, // 0x00000065
/// <summary>The F12 key.</summary>
F12 = 101, // 0x00000065
/// <summary>The F13 key.</summary>
F13 = 102, // 0x00000066
/// <summary>The F13 key.</summary>
F13 = 102, // 0x00000066
/// <summary>The F14 key.</summary>
F14 = 103, // 0x00000067
/// <summary>The F14 key.</summary>
F14 = 103, // 0x00000067
/// <summary>The F15 key.</summary>
F15 = 104, // 0x00000068
/// <summary>The F15 key.</summary>
F15 = 104, // 0x00000068
/// <summary>The F16 key.</summary>
F16 = 105, // 0x00000069
/// <summary>The F16 key.</summary>
F16 = 105, // 0x00000069
/// <summary>The F17 key.</summary>
F17 = 106, // 0x0000006A
/// <summary>The F17 key.</summary>
F17 = 106, // 0x0000006A
/// <summary>The F18 key.</summary>
F18 = 107, // 0x0000006B
/// <summary>The F18 key.</summary>
F18 = 107, // 0x0000006B
/// <summary>The F19 key.</summary>
F19 = 108, // 0x0000006C
/// <summary>The F19 key.</summary>
F19 = 108, // 0x0000006C
/// <summary>The F20 key.</summary>
F20 = 109, // 0x0000006D
/// <summary>The F20 key.</summary>
F20 = 109, // 0x0000006D
/// <summary>The F21 key.</summary>
F21 = 110, // 0x0000006E
/// <summary>The F21 key.</summary>
F21 = 110, // 0x0000006E
/// <summary>The F22 key.</summary>
F22 = 111, // 0x0000006F
/// <summary>The F22 key.</summary>
F22 = 111, // 0x0000006F
/// <summary>The F23 key.</summary>
F23 = 112, // 0x00000070
/// <summary>The F23 key.</summary>
F23 = 112, // 0x00000070
/// <summary>The F24 key.</summary>
F24 = 113, // 0x00000071
/// <summary>The F24 key.</summary>
F24 = 113, // 0x00000071
/// <summary>The Num Lock key.</summary>
NumLock = 114, // 0x00000072
/// <summary>The Num Lock key.</summary>
NumLock = 114, // 0x00000072
/// <summary>The Scroll Lock key.</summary>
Scroll = 115, // 0x00000073
/// <summary>The Scroll Lock key.</summary>
Scroll = 115, // 0x00000073
/// <summary>The left Shift key.</summary>
LeftShift = 116, // 0x00000074
/// <summary>The left Shift key.</summary>
LeftShift = 116, // 0x00000074
/// <summary>The right Shift key.</summary>
RightShift = 117, // 0x00000075
/// <summary>The right Shift key.</summary>
RightShift = 117, // 0x00000075
/// <summary>The left CTRL key.</summary>
LeftCtrl = 118, // 0x00000076
/// <summary>The left CTRL key.</summary>
LeftCtrl = 118, // 0x00000076
/// <summary>The right CTRL key.</summary>
RightCtrl = 119, // 0x00000077
/// <summary>The right CTRL key.</summary>
RightCtrl = 119, // 0x00000077
/// <summary>The left ALT key.</summary>
LeftAlt = 120, // 0x00000078
/// <summary>The left ALT key.</summary>
LeftAlt = 120, // 0x00000078
/// <summary>The right ALT key.</summary>
RightAlt = 121, // 0x00000079
/// <summary>The right ALT key.</summary>
RightAlt = 121, // 0x00000079
/// <summary>The Browser Back key.</summary>
BrowserBack = 122, // 0x0000007A
/// <summary>The Browser Back key.</summary>
BrowserBack = 122, // 0x0000007A
/// <summary>The Browser Forward key.</summary>
BrowserForward = 123, // 0x0000007B
/// <summary>The Browser Forward key.</summary>
BrowserForward = 123, // 0x0000007B
/// <summary>The Browser Refresh key.</summary>
BrowserRefresh = 124, // 0x0000007C
/// <summary>The Browser Refresh key.</summary>
BrowserRefresh = 124, // 0x0000007C
/// <summary>The Browser Stop key.</summary>
BrowserStop = 125, // 0x0000007D
/// <summary>The Browser Stop key.</summary>
BrowserStop = 125, // 0x0000007D
/// <summary>The Browser Search key.</summary>
BrowserSearch = 126, // 0x0000007E
/// <summary>The Browser Search key.</summary>
BrowserSearch = 126, // 0x0000007E
/// <summary>The Browser Favorites key.</summary>
BrowserFavorites = 127, // 0x0000007F
/// <summary>The Browser Favorites key.</summary>
BrowserFavorites = 127, // 0x0000007F
/// <summary>The Browser Home key.</summary>
BrowserHome = 128, // 0x00000080
/// <summary>The Browser Home key.</summary>
BrowserHome = 128, // 0x00000080
/// <summary>The Volume Mute key.</summary>
VolumeMute = 129, // 0x00000081
/// <summary>The Volume Mute key.</summary>
VolumeMute = 129, // 0x00000081
/// <summary>The Volume Down key.</summary>
VolumeDown = 130, // 0x00000082
/// <summary>The Volume Down key.</summary>
VolumeDown = 130, // 0x00000082
/// <summary>The Volume Up key.</summary>
VolumeUp = 131, // 0x00000083
/// <summary>The Volume Up key.</summary>
VolumeUp = 131, // 0x00000083
/// <summary>The Media Next Track key.</summary>
MediaNextTrack = 132, // 0x00000084
/// <summary>The Media Next Track key.</summary>
MediaNextTrack = 132, // 0x00000084
/// <summary>The Media Previous Track key.</summary>
MediaPreviousTrack = 133, // 0x00000085
/// <summary>The Media Previous Track key.</summary>
MediaPreviousTrack = 133, // 0x00000085
/// <summary>The Media Stop key.</summary>
MediaStop = 134, // 0x00000086
/// <summary>The Media Stop key.</summary>
MediaStop = 134, // 0x00000086
/// <summary>The Media Play Pause key.</summary>
MediaPlayPause = 135, // 0x00000087
/// <summary>The Media Play Pause key.</summary>
MediaPlayPause = 135, // 0x00000087
/// <summary>The Launch Mail key.</summary>
LaunchMail = 136, // 0x00000088
/// <summary>The Launch Mail key.</summary>
LaunchMail = 136, // 0x00000088
/// <summary>The Select Media key.</summary>
SelectMedia = 137, // 0x00000089
/// <summary>The Select Media key.</summary>
SelectMedia = 137, // 0x00000089
/// <summary>The Launch Application1 key.</summary>
LaunchApplication1 = 138, // 0x0000008A
/// <summary>The Launch Application1 key.</summary>
LaunchApplication1 = 138, // 0x0000008A
/// <summary>The Launch Application2 key.</summary>
LaunchApplication2 = 139, // 0x0000008B
/// <summary>The Launch Application2 key.</summary>
LaunchApplication2 = 139, // 0x0000008B
/// <summary>The OEM Semicolon key.</summary>
OemSemicolon = 140, // 0x0000008C
/// <summary>The OEM Semicolon key.</summary>
OemSemicolon = 140, // 0x0000008C
/// <summary>The OEM Addition key.</summary>
OemPlus = 141, // 0x0000008D
/// <summary>The OEM Addition key.</summary>
OemPlus = 141, // 0x0000008D
/// <summary>The OEM Comma key.</summary>
OemComma = 142, // 0x0000008E
/// <summary>The OEM Comma key.</summary>
OemComma = 142, // 0x0000008E
/// <summary>The OEM Minus key.</summary>
OemMinus = 143, // 0x0000008F
/// <summary>The OEM Minus key.</summary>
OemMinus = 143, // 0x0000008F
/// <summary>The OEM Period key.</summary>
OemPeriod = 144, // 0x00000091
/// <summary>The OEM Period key.</summary>
OemPeriod = 144, // 0x00000091
/// <summary>The OEM Question key.</summary>
OemQuestion = 145, // 0x00000092
/// <summary>The OEM Question key.</summary>
OemQuestion = 145, // 0x00000092
/// <summary>The OEM Tilde key.</summary>
OemTilde = 146, // 0x00000092
/// <summary>The OEM Tilde key.</summary>
OemTilde = 146, // 0x00000092
/// <summary>The ABNT_C1 (Brazilian) key.</summary>
AbntC1 = 147, // 0x00000093
/// <summary>The ABNT_C1 (Brazilian) key.</summary>
AbntC1 = 147, // 0x00000093
/// <summary>The ABNT_C2 (Brazilian) key.</summary>
AbntC2 = 148, // 0x00000095
/// <summary>The ABNT_C2 (Brazilian) key.</summary>
AbntC2 = 148, // 0x00000095
/// <summary>The OEM Open Brackets key.</summary>
OemOpenBrackets = 149, // 0x00000096
/// <summary>The OEM Open Brackets key.</summary>
OemOpenBrackets = 149, // 0x00000096
/// <summary>The OEM Pipe key.</summary>
OemPipe = 150, // 0x00000096
/// <summary>The OEM Pipe key.</summary>
OemPipe = 150, // 0x00000096
/// <summary>The OEM Close Brackets key.</summary>
OemCloseBrackets = 151, // 0x00000097
/// <summary>The OEM Close Brackets key.</summary>
OemCloseBrackets = 151, // 0x00000097
/// <summary>The OEM Quotes key.</summary>
OemQuotes = 152, // 0x00000098
/// <summary>The OEM Quotes key.</summary>
OemQuotes = 152, // 0x00000098
/// <summary>The OEM Backslash key.</summary>
OemBackslash = 154, // 0x0000009A
/// <summary>The OEM Backslash key.</summary>
OemBackslash = 154, // 0x0000009A
/// <summary>A special key masking the real key being processed by an IME.</summary>
ImeProcessed = 155, // 0x0000009B
/// <summary>A special key masking the real key being processed by an IME.</summary>
ImeProcessed = 155, // 0x0000009B
/// <summary>A special key masking the real key being processed as a system key.</summary>
System = 156, // 0x0000009C
/// <summary>A special key masking the real key being processed as a system key.</summary>
System = 156, // 0x0000009C
/// <summary>The OEM ATTN key.</summary>
OemAttn = 157, // 0x0000009D
/// <summary>The OEM ATTN key.</summary>
OemAttn = 157, // 0x0000009D
/// <summary>The OEM FINISH key.</summary>
OemFinish = 158, // 0x0000009E
/// <summary>The OEM FINISH key.</summary>
OemFinish = 158, // 0x0000009E
/// <summary>The OEM COPY key.</summary>
OemCopy = 159, // 0x0000009F
/// <summary>The OEM COPY key.</summary>
OemCopy = 159, // 0x0000009F
/// <summary>The OEM AUTO key.</summary>
OemAuto = 160, // 0x000000A0
/// <summary>The OEM AUTO key.</summary>
OemAuto = 160, // 0x000000A0
/// <summary>The OEM ENLW key.</summary>
OemEnlw = 161, // 0x000000A1
/// <summary>The OEM ENLW key.</summary>
OemEnlw = 161, // 0x000000A1
/// <summary>The OEM BACKTAB key.</summary>
OemBackTab = 162, // 0x000000A2
/// <summary>The OEM BACKTAB key.</summary>
OemBackTab = 162, // 0x000000A2
/// <summary>The ATTN key.</summary>
Attn = 163, // 0x000000A3
/// <summary>The ATTN key.</summary>
Attn = 163, // 0x000000A3
/// <summary>The CRSEL key.</summary>
CrSel = 164, // 0x000000A4
/// <summary>The CRSEL key.</summary>
CrSel = 164, // 0x000000A4
/// <summary>The EXSEL key.</summary>
ExSel = 165, // 0x000000A5
/// <summary>The EXSEL key.</summary>
ExSel = 165, // 0x000000A5
/// <summary>The ERASE EOF key.</summary>
EraseEof = 166, // 0x000000A6
/// <summary>The ERASE EOF key.</summary>
EraseEof = 166, // 0x000000A6
/// <summary>The PLAY key.</summary>
Play = 167, // 0x000000A7
/// <summary>The PLAY key.</summary>
Play = 167, // 0x000000A7
/// <summary>The ZOOM key.</summary>
Zoom = 168, // 0x000000A8
/// <summary>The ZOOM key.</summary>
Zoom = 168, // 0x000000A8
/// <summary>A constant reserved for future use.</summary>
NoName = 169, // 0x000000A9
/// <summary>A constant reserved for future use.</summary>
NoName = 169, // 0x000000A9
/// <summary>The PA1 key.</summary>
Pa1 = 170, // 0x000000AA
/// <summary>The PA1 key.</summary>
Pa1 = 170, // 0x000000AA
/// <summary>The OEM Clear key.</summary>
OemClear = 171, // 0x000000AB
/// <summary>The OEM Clear key.</summary>
OemClear = 171, // 0x000000AB
/// <summary>The key is used with another key to create a single combined character.</summary>
DeadCharProcessed = 172, // 0x000000AC,
/// <summary>The key is used with another key to create a single combined character.</summary>
DeadCharProcessed = 172, // 0x000000AC,
/// <summary>The NumPad enter key</summary>
NumPadEnter
}
/// <summary>The NumPad enter key</summary>
NumPadEnter
}
@@ -1,32 +1,26 @@
using System;
using System.ComponentModel;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Specifies the set of modifier keys.
/// </summary>
[Flags]
public enum KeyboardModifierKey
{
/// <summary>
/// Specifies the set of modifier keys.
/// </summary>
[Flags]
public enum KeyboardModifierKey
{
/// <summary>No modifiers are pressed.</summary>
[Description("None")]
None = 0,
/// <summary>No modifiers are pressed.</summary>
[Description("None")] None = 0,
/// <summary>The ALT key.</summary>
[Description("Alt")]
Alt = 1,
/// <summary>The ALT key.</summary>
[Description("Alt")] Alt = 1,
/// <summary>The CTRL key.</summary>
[Description("Ctrl")]
Control = 2,
/// <summary>The CTRL key.</summary>
[Description("Ctrl")] Control = 2,
/// <summary>The SHIFT key.</summary>
[Description("Shift")]
Shift = 4,
/// <summary>The SHIFT key.</summary>
[Description("Shift")] Shift = 4,
/// <summary>The Windows logo key.</summary>
[Description("Win")]
Windows = 8
}
/// <summary>The Windows logo key.</summary>
[Description("Win")] Windows = 8
}
@@ -1,33 +1,32 @@
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Specifies the buttons on a mouse
/// </summary>
public enum MouseButton
{
/// <summary>
/// Specifies the buttons on a mouse
/// The left mouse button
/// </summary>
public enum MouseButton
{
/// <summary>
/// The left mouse button
/// </summary>
Left,
Left,
/// <summary>
/// The middle mouse button
/// </summary>
Middle,
/// <summary>
/// The middle mouse button
/// </summary>
Middle,
/// <summary>
/// The right mouse button
/// </summary>
Right,
/// <summary>
/// The right mouse button
/// </summary>
Right,
/// <summary>
/// Extra mouse button 4 (backwards)
/// </summary>
Button4,
/// <summary>
/// Extra mouse button 4 (backwards)
/// </summary>
Button4,
/// <summary>
/// Extra mouse button 5 (forwards)
/// </summary>
Button5
}
/// <summary>
/// Extra mouse button 5 (forwards)
/// </summary>
Button5
}
@@ -1,14 +1,13 @@
namespace Artemis.Core.Services
{
/// <summary>
/// Specifies mouse scrolling directions
/// </summary>
public enum MouseScrollDirection
{
/// <summary>A vertical scroll direction</summary>
Vertical,
namespace Artemis.Core.Services;
/// <summary>A horizontal scroll direction</summary>
Horizontal
}
/// <summary>
/// Specifies mouse scrolling directions
/// </summary>
public enum MouseScrollDirection
{
/// <summary>A vertical scroll direction</summary>
Vertical,
/// <summary>A horizontal scroll direction</summary>
Horizontal
}
@@ -1,38 +1,37 @@
using System;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Contains data for keyboard input events
/// </summary>
public class ArtemisKeyboardKeyEventArgs : EventArgs
{
/// <summary>
/// Contains data for keyboard input events
/// </summary>
public class ArtemisKeyboardKeyEventArgs : EventArgs
internal ArtemisKeyboardKeyEventArgs(ArtemisDevice? device, ArtemisLed? led, KeyboardKey key, KeyboardModifierKey modifiers)
{
internal ArtemisKeyboardKeyEventArgs(ArtemisDevice? device, ArtemisLed? led, KeyboardKey key, KeyboardModifierKey modifiers)
{
Device = device;
Led = led;
Key = key;
Modifiers = modifiers;
}
/// <summary>
/// Gets the device that triggered the event
/// </summary>
public ArtemisDevice? Device { get; }
/// <summary>
/// Gets the LED that corresponds to the pressed key
/// </summary>
public ArtemisLed? Led { get; }
/// <summary>
/// Gets the key that triggered the event
/// </summary>
public KeyboardKey Key { get; }
/// <summary>
/// Gets the modifiers that are pressed
/// </summary>
public KeyboardModifierKey Modifiers { get; }
Device = device;
Led = led;
Key = key;
Modifiers = modifiers;
}
/// <summary>
/// Gets the device that triggered the event
/// </summary>
public ArtemisDevice? Device { get; }
/// <summary>
/// Gets the LED that corresponds to the pressed key
/// </summary>
public ArtemisLed? Led { get; }
/// <summary>
/// Gets the key that triggered the event
/// </summary>
public KeyboardKey Key { get; }
/// <summary>
/// Gets the modifiers that are pressed
/// </summary>
public KeyboardModifierKey Modifiers { get; }
}
@@ -1,18 +1,17 @@
namespace Artemis.Core.Services
{
/// <summary>
/// Contains data for keyboard input events
/// </summary>
public class ArtemisKeyboardKeyUpDownEventArgs : ArtemisKeyboardKeyEventArgs
{
internal ArtemisKeyboardKeyUpDownEventArgs(ArtemisDevice? device, ArtemisLed? led, KeyboardKey key, KeyboardModifierKey modifiers, bool isDown) : base(device, led, key, modifiers)
{
IsDown = isDown;
}
namespace Artemis.Core.Services;
/// <summary>
/// Whether the key is being pressed down, if <see langword="false"/> the key is being released
/// </summary>
public bool IsDown { get; set; }
/// <summary>
/// Contains data for keyboard input events
/// </summary>
public class ArtemisKeyboardKeyUpDownEventArgs : ArtemisKeyboardKeyEventArgs
{
internal ArtemisKeyboardKeyUpDownEventArgs(ArtemisDevice? device, ArtemisLed? led, KeyboardKey key, KeyboardModifierKey modifiers, bool isDown) : base(device, led, key, modifiers)
{
IsDown = isDown;
}
/// <summary>
/// Whether the key is being pressed down, if <see langword="false" /> the key is being released
/// </summary>
public bool IsDown { get; set; }
}
@@ -1,26 +1,25 @@
using System;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Contains data for keyboard input events
/// </summary>
public class ArtemisKeyboardToggleStatusArgs : EventArgs
{
/// <summary>
/// Contains data for keyboard input events
/// </summary>
public class ArtemisKeyboardToggleStatusArgs : EventArgs
internal ArtemisKeyboardToggleStatusArgs(KeyboardToggleStatus oldStatus, KeyboardToggleStatus newStatus)
{
internal ArtemisKeyboardToggleStatusArgs(KeyboardToggleStatus oldStatus, KeyboardToggleStatus newStatus)
{
OldStatus = oldStatus;
NewStatus = newStatus;
}
/// <summary>
/// Gets the keyboard status before the change
/// </summary>
public KeyboardToggleStatus OldStatus { get; }
/// <summary>
/// Gets the keyboard status after the change
/// </summary>
public KeyboardToggleStatus NewStatus { get; }
OldStatus = oldStatus;
NewStatus = newStatus;
}
/// <summary>
/// Gets the keyboard status before the change
/// </summary>
public KeyboardToggleStatus OldStatus { get; }
/// <summary>
/// Gets the keyboard status after the change
/// </summary>
public KeyboardToggleStatus NewStatus { get; }
}
@@ -1,32 +1,31 @@
using System;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Contains data for mouse input events
/// </summary>
public class ArtemisMouseButtonEventArgs : EventArgs
{
/// <summary>
/// Contains data for mouse input events
/// </summary>
public class ArtemisMouseButtonEventArgs : EventArgs
internal ArtemisMouseButtonEventArgs(ArtemisDevice? device, ArtemisLed? led, MouseButton button)
{
internal ArtemisMouseButtonEventArgs(ArtemisDevice? device, ArtemisLed? led, MouseButton button)
{
Device = device;
Led = led;
Button = button;
}
/// <summary>
/// Gets the device that triggered the event
/// </summary>
public ArtemisDevice? Device { get; }
/// <summary>
/// Gets the LED that corresponds to the pressed key
/// </summary>
public ArtemisLed? Led { get; }
/// <summary>
/// Gets the button that triggered the event
/// </summary>
public MouseButton Button { get; }
Device = device;
Led = led;
Button = button;
}
/// <summary>
/// Gets the device that triggered the event
/// </summary>
public ArtemisDevice? Device { get; }
/// <summary>
/// Gets the LED that corresponds to the pressed key
/// </summary>
public ArtemisLed? Led { get; }
/// <summary>
/// Gets the button that triggered the event
/// </summary>
public MouseButton Button { get; }
}
@@ -1,18 +1,17 @@
namespace Artemis.Core.Services
{
/// <summary>
/// Contains data for mouse input events
/// </summary>
public class ArtemisMouseButtonUpDownEventArgs : ArtemisMouseButtonEventArgs
{
internal ArtemisMouseButtonUpDownEventArgs(ArtemisDevice? device, ArtemisLed? led, MouseButton button, bool isDown) : base(device, led, button)
{
IsDown = isDown;
}
namespace Artemis.Core.Services;
/// <summary>
/// Whether the button is being pressed down, if <see langword="false"/> the button is being released
/// </summary>
public bool IsDown { get; set; }
/// <summary>
/// Contains data for mouse input events
/// </summary>
public class ArtemisMouseButtonUpDownEventArgs : ArtemisMouseButtonEventArgs
{
internal ArtemisMouseButtonUpDownEventArgs(ArtemisDevice? device, ArtemisLed? led, MouseButton button, bool isDown) : base(device, led, button)
{
IsDown = isDown;
}
/// <summary>
/// Whether the button is being pressed down, if <see langword="false" /> the button is being released
/// </summary>
public bool IsDown { get; set; }
}
@@ -1,51 +1,50 @@
using System;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Contains data for mouse movement events
/// </summary>
public class ArtemisMouseMoveEventArgs : EventArgs
{
/// <summary>
/// Contains data for mouse movement events
/// </summary>
public class ArtemisMouseMoveEventArgs : EventArgs
/// <param name="device">The device that triggered the event</param>
/// <param name="cursorX">The X position of the mouse cursor (not necessarily tied to the specific device)</param>
/// <param name="cursorY">The Y position of the mouse cursor (not necessarily tied to the specific device)</param>
/// <param name="deltaX">The movement delta in the horizontal direction</param>
/// <param name="deltaY">The movement delta in the vertical direction</param>
internal ArtemisMouseMoveEventArgs(ArtemisDevice? device, int cursorX, int cursorY, int deltaX, int deltaY)
{
/// <summary>
/// </summary>
/// <param name="device">The device that triggered the event</param>
/// <param name="cursorX">The X position of the mouse cursor (not necessarily tied to the specific device)</param>
/// <param name="cursorY">The Y position of the mouse cursor (not necessarily tied to the specific device)</param>
/// <param name="deltaX">The movement delta in the horizontal direction</param>
/// <param name="deltaY">The movement delta in the vertical direction</param>
internal ArtemisMouseMoveEventArgs(ArtemisDevice? device, int cursorX, int cursorY, int deltaX, int deltaY)
{
Device = device;
CursorX = cursorX;
CursorY = cursorY;
DeltaX = deltaX;
DeltaY = deltaY;
}
/// <summary>
/// Gets the device that triggered the event
/// </summary>
public ArtemisDevice? Device { get; }
/// <summary>
/// Gets the X position of the mouse cursor (not necessarily tied to the specific device)
/// </summary>
public int CursorX { get; }
/// <summary>
/// Gets the Y position of the mouse cursor (not necessarily tied to the specific device)
/// </summary>
public int CursorY { get; }
/// <summary>
/// Gets the movement delta in the horizontal direction
/// </summary>
public int DeltaX { get; }
/// <summary>
/// Gets the movement delta in the vertical direction
/// </summary>
public int DeltaY { get; }
Device = device;
CursorX = cursorX;
CursorY = cursorY;
DeltaX = deltaX;
DeltaY = deltaY;
}
/// <summary>
/// Gets the device that triggered the event
/// </summary>
public ArtemisDevice? Device { get; }
/// <summary>
/// Gets the X position of the mouse cursor (not necessarily tied to the specific device)
/// </summary>
public int CursorX { get; }
/// <summary>
/// Gets the Y position of the mouse cursor (not necessarily tied to the specific device)
/// </summary>
public int CursorY { get; }
/// <summary>
/// Gets the movement delta in the horizontal direction
/// </summary>
public int DeltaX { get; }
/// <summary>
/// Gets the movement delta in the vertical direction
/// </summary>
public int DeltaY { get; }
}
@@ -1,57 +1,56 @@
using System;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Contains data for mouse scroll events
/// </summary>
public class ArtemisMouseScrollEventArgs : EventArgs
{
/// <summary>
/// Contains data for mouse scroll events
/// </summary>
public class ArtemisMouseScrollEventArgs : EventArgs
/// <param name="device">The device that triggered the event</param>
/// <param name="direction">The direction in which was scrolled</param>
/// <param name="delta">The scroll delta (can positive or negative)</param>
internal ArtemisMouseScrollEventArgs(ArtemisDevice? device, MouseScrollDirection direction, int delta)
{
/// <summary>
/// </summary>
/// <param name="device">The device that triggered the event</param>
/// <param name="direction">The direction in which was scrolled</param>
/// <param name="delta">The scroll delta (can positive or negative)</param>
internal ArtemisMouseScrollEventArgs(ArtemisDevice? device, MouseScrollDirection direction, int delta)
{
Device = device;
Direction = direction;
Delta = delta;
}
/// <summary>
/// Gets the device that triggered the event
/// </summary>
public ArtemisDevice? Device { get; }
/// <summary>
/// Gets the direction in which was scrolled
/// </summary>
public MouseScrollDirection Direction { get; }
/// <summary>
/// Gets the scroll delta (can positive or negative)
/// </summary>
public int Delta { get; }
/// <summary>
/// Gets a boolean indicating whether the mouse scrolled up
/// </summary>
public bool IsScrollingUp => Direction == MouseScrollDirection.Vertical && Delta > 0;
/// <summary>
/// Gets a boolean indicating whether the mouse scrolled down
/// </summary>
public bool IsScrollingDown => Direction == MouseScrollDirection.Vertical && Delta < 0;
/// <summary>
/// Gets a boolean indicating whether the mouse scrolled right
/// </summary>
public bool IsScrollingRight => Direction == MouseScrollDirection.Horizontal && Delta > 0;
/// <summary>
/// Gets a boolean indicating whether the mouse scrolled left
/// </summary>
public bool IsScrollingLeft => Direction == MouseScrollDirection.Horizontal && Delta < 0;
Device = device;
Direction = direction;
Delta = delta;
}
/// <summary>
/// Gets the device that triggered the event
/// </summary>
public ArtemisDevice? Device { get; }
/// <summary>
/// Gets the direction in which was scrolled
/// </summary>
public MouseScrollDirection Direction { get; }
/// <summary>
/// Gets the scroll delta (can positive or negative)
/// </summary>
public int Delta { get; }
/// <summary>
/// Gets a boolean indicating whether the mouse scrolled up
/// </summary>
public bool IsScrollingUp => Direction == MouseScrollDirection.Vertical && Delta > 0;
/// <summary>
/// Gets a boolean indicating whether the mouse scrolled down
/// </summary>
public bool IsScrollingDown => Direction == MouseScrollDirection.Vertical && Delta < 0;
/// <summary>
/// Gets a boolean indicating whether the mouse scrolled right
/// </summary>
public bool IsScrollingRight => Direction == MouseScrollDirection.Horizontal && Delta > 0;
/// <summary>
/// Gets a boolean indicating whether the mouse scrolled left
/// </summary>
public bool IsScrollingLeft => Direction == MouseScrollDirection.Horizontal && Delta < 0;
}
@@ -1,31 +1,30 @@
using System;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Contains data for input provider identifier events
/// </summary>
public class InputProviderIdentifierEventArgs : EventArgs
{
/// <summary>
/// Contains data for input provider identifier events
/// Creates a new instance of the <see cref="InputProviderIdentifierEventArgs" /> class
/// </summary>
public class InputProviderIdentifierEventArgs : EventArgs
/// <param name="identifier">A value that can be used to identify this device</param>
/// <param name="deviceType">The type of device this identifier belongs to</param>
public InputProviderIdentifierEventArgs(object identifier, InputDeviceType deviceType)
{
/// <summary>
/// Creates a new instance of the <see cref="InputProviderIdentifierEventArgs" /> class
/// </summary>
/// <param name="identifier">A value that can be used to identify this device</param>
/// <param name="deviceType">The type of device this identifier belongs to</param>
public InputProviderIdentifierEventArgs(object identifier, InputDeviceType deviceType)
{
Identifier = identifier;
DeviceType = deviceType;
}
/// <summary>
/// Gets a value that can be used to identify this device
/// </summary>
public object Identifier { get; }
/// <summary>
/// Gets the type of device this identifier belongs to
/// </summary>
public InputDeviceType DeviceType { get; }
Identifier = identifier;
DeviceType = deviceType;
}
/// <summary>
/// Gets a value that can be used to identify this device
/// </summary>
public object Identifier { get; }
/// <summary>
/// Gets the type of device this identifier belongs to
/// </summary>
public InputDeviceType DeviceType { get; }
}
@@ -1,38 +1,37 @@
using System;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Contains data for input provider keyboard events
/// </summary>
public class InputProviderKeyboardEventArgs : EventArgs
{
/// <summary>
/// Contains data for input provider keyboard events
/// Creates a new instance of the <see cref="InputProviderKeyboardEventArgs " /> class
/// </summary>
public class InputProviderKeyboardEventArgs : EventArgs
/// <param name="device">The device that triggered the event</param>
/// <param name="key">The key that triggered the event</param>
/// <param name="isDown">Whether the key is pressed down</param>
public InputProviderKeyboardEventArgs(ArtemisDevice? device, KeyboardKey key, bool isDown)
{
/// <summary>
/// Creates a new instance of the <see cref="InputProviderKeyboardEventArgs " /> class
/// </summary>
/// <param name="device">The device that triggered the event</param>
/// <param name="key">The key that triggered the event</param>
/// <param name="isDown">Whether the key is pressed down</param>
public InputProviderKeyboardEventArgs(ArtemisDevice? device, KeyboardKey key, bool isDown)
{
Device = device;
Key = key;
IsDown = isDown;
}
/// <summary>
/// Gets the device that triggered the event
/// </summary>
public ArtemisDevice? Device { get; }
/// <summary>
/// Gets the key that triggered the event
/// </summary>
public KeyboardKey Key { get; }
/// <summary>
/// Gets whether the key is pressed down
/// </summary>
public bool IsDown { get; }
Device = device;
Key = key;
IsDown = isDown;
}
/// <summary>
/// Gets the device that triggered the event
/// </summary>
public ArtemisDevice? Device { get; }
/// <summary>
/// Gets the key that triggered the event
/// </summary>
public KeyboardKey Key { get; }
/// <summary>
/// Gets whether the key is pressed down
/// </summary>
public bool IsDown { get; }
}
@@ -1,24 +1,23 @@
using System;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Contains data for input provider keyboard status toggle events
/// </summary>
public class InputProviderKeyboardToggleEventArgs : EventArgs
{
/// <summary>
/// Contains data for input provider keyboard status toggle events
/// Creates a new instance of the <see cref="InputProviderKeyboardToggleEventArgs " /> class
/// </summary>
public class InputProviderKeyboardToggleEventArgs : EventArgs
/// <param name="keyboardToggleStatus">The toggle status of the keyboard</param>
public InputProviderKeyboardToggleEventArgs(KeyboardToggleStatus keyboardToggleStatus)
{
/// <summary>
/// Creates a new instance of the <see cref="InputProviderKeyboardToggleEventArgs " /> class
/// </summary>
/// <param name="keyboardToggleStatus">The toggle status of the keyboard</param>
public InputProviderKeyboardToggleEventArgs(KeyboardToggleStatus keyboardToggleStatus)
{
KeyboardToggleStatus = keyboardToggleStatus;
}
/// <summary>
/// Gets the toggle status of the keyboard
/// </summary>
public KeyboardToggleStatus KeyboardToggleStatus { get; }
KeyboardToggleStatus = keyboardToggleStatus;
}
/// <summary>
/// Gets the toggle status of the keyboard
/// </summary>
public KeyboardToggleStatus KeyboardToggleStatus { get; }
}
@@ -1,37 +1,36 @@
using System;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Contains data for input provider mouse button events
/// </summary>
public class InputProviderMouseButtonEventArgs : EventArgs
{
/// <summary>
/// Contains data for input provider mouse button events
/// </summary>
public class InputProviderMouseButtonEventArgs : EventArgs
/// <param name="device">The device that triggered the event</param>
/// <param name="button">The button that triggered the event</param>
/// <param name="isDown">Whether the button is pressed down</param>
public InputProviderMouseButtonEventArgs(ArtemisDevice? device, MouseButton button, bool isDown)
{
/// <summary>
/// </summary>
/// <param name="device">The device that triggered the event</param>
/// <param name="button">The button that triggered the event</param>
/// <param name="isDown">Whether the button is pressed down</param>
public InputProviderMouseButtonEventArgs(ArtemisDevice? device, MouseButton button, bool isDown)
{
Device = device;
Button = button;
IsDown = isDown;
}
/// <summary>
/// Gets the device that triggered the event
/// </summary>
public ArtemisDevice? Device { get; }
/// <summary>
/// Gets the button that triggered the event
/// </summary>
public MouseButton Button { get; }
/// <summary>
/// Gets whether the button is pressed down
/// </summary>
public bool IsDown { get; }
Device = device;
Button = button;
IsDown = isDown;
}
/// <summary>
/// Gets the device that triggered the event
/// </summary>
public ArtemisDevice? Device { get; }
/// <summary>
/// Gets the button that triggered the event
/// </summary>
public MouseButton Button { get; }
/// <summary>
/// Gets whether the button is pressed down
/// </summary>
public bool IsDown { get; }
}
@@ -1,51 +1,50 @@
using System;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Contains data for input provider mouse movement events
/// </summary>
public class InputProviderMouseMoveEventArgs : EventArgs
{
/// <summary>
/// Contains data for input provider mouse movement events
/// </summary>
public class InputProviderMouseMoveEventArgs : EventArgs
/// <param name="device">The device that triggered the event</param>
/// <param name="cursorX">The X position of the mouse cursor (not necessarily tied to the specific device)</param>
/// <param name="cursorY">The Y position of the mouse cursor (not necessarily tied to the specific device)</param>
/// <param name="deltaX">The movement delta in the horizontal direction</param>
/// <param name="deltaY">The movement delta in the vertical direction</param>
public InputProviderMouseMoveEventArgs(ArtemisDevice? device, int cursorX, int cursorY, int deltaX, int deltaY)
{
/// <summary>
/// </summary>
/// <param name="device">The device that triggered the event</param>
/// <param name="cursorX">The X position of the mouse cursor (not necessarily tied to the specific device)</param>
/// <param name="cursorY">The Y position of the mouse cursor (not necessarily tied to the specific device)</param>
/// <param name="deltaX">The movement delta in the horizontal direction</param>
/// <param name="deltaY">The movement delta in the vertical direction</param>
public InputProviderMouseMoveEventArgs(ArtemisDevice? device, int cursorX, int cursorY, int deltaX, int deltaY)
{
Device = device;
CursorX = cursorX;
CursorY = cursorY;
DeltaX = deltaX;
DeltaY = deltaY;
}
/// <summary>
/// Gets the device that triggered the event
/// </summary>
public ArtemisDevice? Device { get; }
/// <summary>
/// Gets the X position of the mouse cursor in pixels (not necessarily tied to the specific device)
/// </summary>
public int CursorX { get; }
/// <summary>
/// Gets the Y position of the mouse cursor in pixels (not necessarily tied to the specific device)
/// </summary>
public int CursorY { get; }
/// <summary>
/// Gets the movement delta in the horizontal direction in pixels
/// </summary>
public int DeltaX { get; }
/// <summary>
/// Gets the movement delta in the vertical direction in pixels
/// </summary>
public int DeltaY { get; }
Device = device;
CursorX = cursorX;
CursorY = cursorY;
DeltaX = deltaX;
DeltaY = deltaY;
}
/// <summary>
/// Gets the device that triggered the event
/// </summary>
public ArtemisDevice? Device { get; }
/// <summary>
/// Gets the X position of the mouse cursor in pixels (not necessarily tied to the specific device)
/// </summary>
public int CursorX { get; }
/// <summary>
/// Gets the Y position of the mouse cursor in pixels (not necessarily tied to the specific device)
/// </summary>
public int CursorY { get; }
/// <summary>
/// Gets the movement delta in the horizontal direction in pixels
/// </summary>
public int DeltaX { get; }
/// <summary>
/// Gets the movement delta in the vertical direction in pixels
/// </summary>
public int DeltaY { get; }
}
@@ -1,37 +1,36 @@
using System;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Contains data for input provider mouse button events
/// </summary>
public class InputProviderMouseScrollEventArgs : EventArgs
{
/// <summary>
/// Contains data for input provider mouse button events
/// </summary>
public class InputProviderMouseScrollEventArgs : EventArgs
/// <param name="device">The device that triggered the event</param>
/// <param name="direction">The direction in which was scrolled</param>
/// <param name="delta">The scroll delta (can positive or negative)</param>
public InputProviderMouseScrollEventArgs(ArtemisDevice? device, MouseScrollDirection direction, int delta)
{
/// <summary>
/// </summary>
/// <param name="device">The device that triggered the event</param>
/// <param name="direction">The direction in which was scrolled</param>
/// <param name="delta">The scroll delta (can positive or negative)</param>
public InputProviderMouseScrollEventArgs(ArtemisDevice? device, MouseScrollDirection direction, int delta)
{
Device = device;
Direction = direction;
Delta = delta;
}
/// <summary>
/// Gets the device that triggered the event
/// </summary>
public ArtemisDevice? Device { get; }
/// <summary>
/// Gets the direction in which was scrolled
/// </summary>
public MouseScrollDirection Direction { get; }
/// <summary>
/// Gets the scroll delta (can positive or negative)
/// </summary>
public int Delta { get; }
Device = device;
Direction = direction;
Delta = delta;
}
/// <summary>
/// Gets the device that triggered the event
/// </summary>
public ArtemisDevice? Device { get; }
/// <summary>
/// Gets the direction in which was scrolled
/// </summary>
public MouseScrollDirection Direction { get; }
/// <summary>
/// Gets the scroll delta (can positive or negative)
/// </summary>
public int Delta { get; }
}
@@ -1,203 +1,202 @@
using System.Collections.Generic;
using RGB.NET.Core;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Utilities for mapping keys and buttons to LEDs
/// </summary>
public static class InputKeyUtilities
{
/// <summary>
/// Utilities for mapping keys and buttons to LEDs
/// A dictionary of mappings between <see cref="KeyboardKey" /> and <see cref="LedId" />
/// </summary>
public static class InputKeyUtilities
public static readonly Dictionary<KeyboardKey, LedId> KeyboardKeyLedIdMap = new()
{
/// <summary>
/// A dictionary of mappings between <see cref="KeyboardKey" /> and <see cref="LedId" />
/// </summary>
public static readonly Dictionary<KeyboardKey, LedId> KeyboardKeyLedIdMap = new()
{
{KeyboardKey.None, LedId.Keyboard_Custom1},
{KeyboardKey.Cancel, LedId.Keyboard_Custom2},
{KeyboardKey.Back, LedId.Keyboard_Backspace},
{KeyboardKey.Tab, LedId.Keyboard_Tab},
{KeyboardKey.LineFeed, LedId.Keyboard_Custom3},
{KeyboardKey.Clear, LedId.Keyboard_Custom4},
{KeyboardKey.Enter, LedId.Keyboard_Enter},
{KeyboardKey.Pause, LedId.Keyboard_PauseBreak},
{KeyboardKey.CapsLock, LedId.Keyboard_CapsLock},
{KeyboardKey.HangulMode, LedId.Keyboard_Custom4},
{KeyboardKey.JunjaMode, LedId.Keyboard_Custom5},
{KeyboardKey.FinalMode, LedId.Keyboard_Custom6},
{KeyboardKey.HanjaMode, LedId.Keyboard_Custom7},
{KeyboardKey.Escape, LedId.Keyboard_Escape},
{KeyboardKey.ImeConvert, LedId.Keyboard_Custom8},
{KeyboardKey.ImeNonConvert, LedId.Keyboard_Custom9},
{KeyboardKey.ImeAccept, LedId.Keyboard_Custom10},
{KeyboardKey.ImeModeChange, LedId.Keyboard_Custom11},
{KeyboardKey.Space, LedId.Keyboard_Space},
{KeyboardKey.PageUp, LedId.Keyboard_PageUp},
{KeyboardKey.PageDown, LedId.Keyboard_PageDown},
{KeyboardKey.End, LedId.Keyboard_End},
{KeyboardKey.Home, LedId.Keyboard_Home},
{KeyboardKey.Left, LedId.Keyboard_ArrowLeft},
{KeyboardKey.Up, LedId.Keyboard_ArrowUp},
{KeyboardKey.Right, LedId.Keyboard_ArrowRight},
{KeyboardKey.Down, LedId.Keyboard_ArrowDown},
{KeyboardKey.Select, LedId.Keyboard_Custom12},
{KeyboardKey.Print, LedId.Keyboard_Custom13},
{KeyboardKey.Execute, LedId.Keyboard_Custom14},
{KeyboardKey.PrintScreen, LedId.Keyboard_PrintScreen},
{KeyboardKey.Insert, LedId.Keyboard_Insert},
{KeyboardKey.Delete, LedId.Keyboard_Delete},
{KeyboardKey.Help, LedId.Keyboard_Custom15},
{KeyboardKey.D0, LedId.Keyboard_0},
{KeyboardKey.D1, LedId.Keyboard_1},
{KeyboardKey.D2, LedId.Keyboard_2},
{KeyboardKey.D3, LedId.Keyboard_3},
{KeyboardKey.D4, LedId.Keyboard_4},
{KeyboardKey.D5, LedId.Keyboard_5},
{KeyboardKey.D6, LedId.Keyboard_6},
{KeyboardKey.D7, LedId.Keyboard_7},
{KeyboardKey.D8, LedId.Keyboard_8},
{KeyboardKey.D9, LedId.Keyboard_9},
{KeyboardKey.A, LedId.Keyboard_A},
{KeyboardKey.B, LedId.Keyboard_B},
{KeyboardKey.C, LedId.Keyboard_C},
{KeyboardKey.D, LedId.Keyboard_D},
{KeyboardKey.E, LedId.Keyboard_E},
{KeyboardKey.F, LedId.Keyboard_F},
{KeyboardKey.G, LedId.Keyboard_G},
{KeyboardKey.H, LedId.Keyboard_H},
{KeyboardKey.I, LedId.Keyboard_I},
{KeyboardKey.J, LedId.Keyboard_J},
{KeyboardKey.K, LedId.Keyboard_K},
{KeyboardKey.L, LedId.Keyboard_L},
{KeyboardKey.M, LedId.Keyboard_M},
{KeyboardKey.N, LedId.Keyboard_N},
{KeyboardKey.O, LedId.Keyboard_O},
{KeyboardKey.P, LedId.Keyboard_P},
{KeyboardKey.Q, LedId.Keyboard_Q},
{KeyboardKey.R, LedId.Keyboard_R},
{KeyboardKey.S, LedId.Keyboard_S},
{KeyboardKey.T, LedId.Keyboard_T},
{KeyboardKey.U, LedId.Keyboard_U},
{KeyboardKey.V, LedId.Keyboard_V},
{KeyboardKey.W, LedId.Keyboard_W},
{KeyboardKey.X, LedId.Keyboard_X},
{KeyboardKey.Y, LedId.Keyboard_Y},
{KeyboardKey.Z, LedId.Keyboard_Z},
{KeyboardKey.LWin, LedId.Keyboard_LeftGui},
{KeyboardKey.RWin, LedId.Keyboard_RightGui},
{KeyboardKey.Apps, LedId.Keyboard_Application},
{KeyboardKey.Sleep, LedId.Keyboard_Custom16},
{KeyboardKey.NumPad0, LedId.Keyboard_Num0},
{KeyboardKey.NumPad1, LedId.Keyboard_Num1},
{KeyboardKey.NumPad2, LedId.Keyboard_Num2},
{KeyboardKey.NumPad3, LedId.Keyboard_Num3},
{KeyboardKey.NumPad4, LedId.Keyboard_Num4},
{KeyboardKey.NumPad5, LedId.Keyboard_Num5},
{KeyboardKey.NumPad6, LedId.Keyboard_Num6},
{KeyboardKey.NumPad7, LedId.Keyboard_Num7},
{KeyboardKey.NumPad8, LedId.Keyboard_Num8},
{KeyboardKey.NumPad9, LedId.Keyboard_Num9},
{KeyboardKey.Multiply, LedId.Keyboard_NumAsterisk},
{KeyboardKey.Add, LedId.Keyboard_NumPlus},
{KeyboardKey.Separator, LedId.Keyboard_NumEnter}, // unverified
{KeyboardKey.Subtract, LedId.Keyboard_NumMinus},
{KeyboardKey.Decimal, LedId.Keyboard_NumPeriodAndDelete},
{KeyboardKey.Divide, LedId.Keyboard_NumSlash},
{KeyboardKey.F1, LedId.Keyboard_F1},
{KeyboardKey.F2, LedId.Keyboard_F2},
{KeyboardKey.F3, LedId.Keyboard_F3},
{KeyboardKey.F4, LedId.Keyboard_F4},
{KeyboardKey.F5, LedId.Keyboard_F5},
{KeyboardKey.F6, LedId.Keyboard_F6},
{KeyboardKey.F7, LedId.Keyboard_F7},
{KeyboardKey.F8, LedId.Keyboard_F8},
{KeyboardKey.F9, LedId.Keyboard_F9},
{KeyboardKey.F10, LedId.Keyboard_F10},
{KeyboardKey.F11, LedId.Keyboard_F11},
{KeyboardKey.F12, LedId.Keyboard_F12},
{KeyboardKey.F13, LedId.Keyboard_Custom17},
{KeyboardKey.F14, LedId.Keyboard_Custom18},
{KeyboardKey.F15, LedId.Keyboard_Custom19},
{KeyboardKey.F16, LedId.Keyboard_Custom20},
{KeyboardKey.F17, LedId.Keyboard_Custom21},
{KeyboardKey.F18, LedId.Keyboard_Custom22},
{KeyboardKey.F19, LedId.Keyboard_Custom23},
{KeyboardKey.F20, LedId.Keyboard_Custom24},
{KeyboardKey.F21, LedId.Keyboard_Custom25},
{KeyboardKey.F22, LedId.Keyboard_Custom26},
{KeyboardKey.F23, LedId.Keyboard_Custom27},
{KeyboardKey.F24, LedId.Keyboard_Custom28},
{KeyboardKey.NumLock, LedId.Keyboard_NumLock},
{KeyboardKey.Scroll, LedId.Keyboard_ScrollLock},
{KeyboardKey.LeftShift, LedId.Keyboard_LeftShift},
{KeyboardKey.RightShift, LedId.Keyboard_RightShift},
{KeyboardKey.LeftCtrl, LedId.Keyboard_LeftCtrl},
{KeyboardKey.RightCtrl, LedId.Keyboard_RightCtrl},
{KeyboardKey.LeftAlt, LedId.Keyboard_LeftAlt},
{KeyboardKey.RightAlt, LedId.Keyboard_RightAlt},
{KeyboardKey.BrowserBack, LedId.Keyboard_Custom29},
{KeyboardKey.BrowserForward, LedId.Keyboard_Custom30},
{KeyboardKey.BrowserRefresh, LedId.Keyboard_Custom31},
{KeyboardKey.BrowserStop, LedId.Keyboard_Custom32},
{KeyboardKey.BrowserSearch, LedId.Keyboard_Custom33},
{KeyboardKey.BrowserFavorites, LedId.Keyboard_Custom34},
{KeyboardKey.BrowserHome, LedId.Keyboard_Custom35},
{KeyboardKey.VolumeMute, LedId.Keyboard_MediaMute},
{KeyboardKey.VolumeDown, LedId.Keyboard_MediaVolumeDown},
{KeyboardKey.VolumeUp, LedId.Keyboard_MediaVolumeUp},
{KeyboardKey.MediaNextTrack, LedId.Keyboard_MediaNextTrack},
{KeyboardKey.MediaPreviousTrack, LedId.Keyboard_MediaPreviousTrack},
{KeyboardKey.MediaStop, LedId.Keyboard_MediaStop},
{KeyboardKey.MediaPlayPause, LedId.Keyboard_MediaPlay},
{KeyboardKey.LaunchMail, LedId.Keyboard_Custom36},
{KeyboardKey.SelectMedia, LedId.Keyboard_Custom37},
{KeyboardKey.LaunchApplication1, LedId.Keyboard_Custom38},
{KeyboardKey.LaunchApplication2, LedId.Keyboard_Custom39},
{KeyboardKey.OemSemicolon, LedId.Keyboard_SemicolonAndColon},
{KeyboardKey.OemPlus, LedId.Keyboard_EqualsAndPlus},
{KeyboardKey.OemMinus, LedId.Keyboard_MinusAndUnderscore},
{KeyboardKey.OemComma, LedId.Keyboard_CommaAndLessThan},
{KeyboardKey.OemPeriod, LedId.Keyboard_PeriodAndBiggerThan},
{KeyboardKey.OemQuestion, LedId.Keyboard_SlashAndQuestionMark},
{KeyboardKey.OemTilde, LedId.Keyboard_GraveAccentAndTilde},
{KeyboardKey.AbntC1, LedId.Keyboard_Custom40},
{KeyboardKey.AbntC2, LedId.Keyboard_Custom41},
{KeyboardKey.OemOpenBrackets, LedId.Keyboard_BracketLeft},
{KeyboardKey.OemPipe, LedId.Keyboard_Backslash},
{KeyboardKey.OemCloseBrackets, LedId.Keyboard_BracketRight},
{KeyboardKey.OemQuotes, LedId.Keyboard_ApostropheAndDoubleQuote},
{KeyboardKey.OemBackslash, LedId.Keyboard_Custom42}, // unverified
{KeyboardKey.ImeProcessed, LedId.Keyboard_Custom43},
{KeyboardKey.System, LedId.Keyboard_Custom44},
{KeyboardKey.OemAttn, LedId.Keyboard_Custom45},
{KeyboardKey.OemFinish, LedId.Keyboard_Custom46},
{KeyboardKey.OemCopy, LedId.Keyboard_Custom47},
{KeyboardKey.OemAuto, LedId.Keyboard_Custom48},
{KeyboardKey.OemEnlw, LedId.Keyboard_Custom49},
{KeyboardKey.OemBackTab, LedId.Keyboard_Custom50},
{KeyboardKey.Attn, LedId.Keyboard_Custom51},
{KeyboardKey.CrSel, LedId.Keyboard_Custom52},
{KeyboardKey.ExSel, LedId.Keyboard_Custom53},
{KeyboardKey.EraseEof, LedId.Keyboard_Custom54},
{KeyboardKey.Play, LedId.Keyboard_MediaPlay},
{KeyboardKey.Zoom, LedId.Keyboard_Custom55},
{KeyboardKey.NoName, LedId.Keyboard_Custom56},
{KeyboardKey.Pa1, LedId.Keyboard_Custom57},
{KeyboardKey.OemClear, LedId.Keyboard_Custom58},
{KeyboardKey.DeadCharProcessed, LedId.Keyboard_Custom59},
{KeyboardKey.NumPadEnter, LedId.Keyboard_NumEnter}
};
{KeyboardKey.None, LedId.Keyboard_Custom1},
{KeyboardKey.Cancel, LedId.Keyboard_Custom2},
{KeyboardKey.Back, LedId.Keyboard_Backspace},
{KeyboardKey.Tab, LedId.Keyboard_Tab},
{KeyboardKey.LineFeed, LedId.Keyboard_Custom3},
{KeyboardKey.Clear, LedId.Keyboard_Custom4},
{KeyboardKey.Enter, LedId.Keyboard_Enter},
{KeyboardKey.Pause, LedId.Keyboard_PauseBreak},
{KeyboardKey.CapsLock, LedId.Keyboard_CapsLock},
{KeyboardKey.HangulMode, LedId.Keyboard_Custom4},
{KeyboardKey.JunjaMode, LedId.Keyboard_Custom5},
{KeyboardKey.FinalMode, LedId.Keyboard_Custom6},
{KeyboardKey.HanjaMode, LedId.Keyboard_Custom7},
{KeyboardKey.Escape, LedId.Keyboard_Escape},
{KeyboardKey.ImeConvert, LedId.Keyboard_Custom8},
{KeyboardKey.ImeNonConvert, LedId.Keyboard_Custom9},
{KeyboardKey.ImeAccept, LedId.Keyboard_Custom10},
{KeyboardKey.ImeModeChange, LedId.Keyboard_Custom11},
{KeyboardKey.Space, LedId.Keyboard_Space},
{KeyboardKey.PageUp, LedId.Keyboard_PageUp},
{KeyboardKey.PageDown, LedId.Keyboard_PageDown},
{KeyboardKey.End, LedId.Keyboard_End},
{KeyboardKey.Home, LedId.Keyboard_Home},
{KeyboardKey.Left, LedId.Keyboard_ArrowLeft},
{KeyboardKey.Up, LedId.Keyboard_ArrowUp},
{KeyboardKey.Right, LedId.Keyboard_ArrowRight},
{KeyboardKey.Down, LedId.Keyboard_ArrowDown},
{KeyboardKey.Select, LedId.Keyboard_Custom12},
{KeyboardKey.Print, LedId.Keyboard_Custom13},
{KeyboardKey.Execute, LedId.Keyboard_Custom14},
{KeyboardKey.PrintScreen, LedId.Keyboard_PrintScreen},
{KeyboardKey.Insert, LedId.Keyboard_Insert},
{KeyboardKey.Delete, LedId.Keyboard_Delete},
{KeyboardKey.Help, LedId.Keyboard_Custom15},
{KeyboardKey.D0, LedId.Keyboard_0},
{KeyboardKey.D1, LedId.Keyboard_1},
{KeyboardKey.D2, LedId.Keyboard_2},
{KeyboardKey.D3, LedId.Keyboard_3},
{KeyboardKey.D4, LedId.Keyboard_4},
{KeyboardKey.D5, LedId.Keyboard_5},
{KeyboardKey.D6, LedId.Keyboard_6},
{KeyboardKey.D7, LedId.Keyboard_7},
{KeyboardKey.D8, LedId.Keyboard_8},
{KeyboardKey.D9, LedId.Keyboard_9},
{KeyboardKey.A, LedId.Keyboard_A},
{KeyboardKey.B, LedId.Keyboard_B},
{KeyboardKey.C, LedId.Keyboard_C},
{KeyboardKey.D, LedId.Keyboard_D},
{KeyboardKey.E, LedId.Keyboard_E},
{KeyboardKey.F, LedId.Keyboard_F},
{KeyboardKey.G, LedId.Keyboard_G},
{KeyboardKey.H, LedId.Keyboard_H},
{KeyboardKey.I, LedId.Keyboard_I},
{KeyboardKey.J, LedId.Keyboard_J},
{KeyboardKey.K, LedId.Keyboard_K},
{KeyboardKey.L, LedId.Keyboard_L},
{KeyboardKey.M, LedId.Keyboard_M},
{KeyboardKey.N, LedId.Keyboard_N},
{KeyboardKey.O, LedId.Keyboard_O},
{KeyboardKey.P, LedId.Keyboard_P},
{KeyboardKey.Q, LedId.Keyboard_Q},
{KeyboardKey.R, LedId.Keyboard_R},
{KeyboardKey.S, LedId.Keyboard_S},
{KeyboardKey.T, LedId.Keyboard_T},
{KeyboardKey.U, LedId.Keyboard_U},
{KeyboardKey.V, LedId.Keyboard_V},
{KeyboardKey.W, LedId.Keyboard_W},
{KeyboardKey.X, LedId.Keyboard_X},
{KeyboardKey.Y, LedId.Keyboard_Y},
{KeyboardKey.Z, LedId.Keyboard_Z},
{KeyboardKey.LWin, LedId.Keyboard_LeftGui},
{KeyboardKey.RWin, LedId.Keyboard_RightGui},
{KeyboardKey.Apps, LedId.Keyboard_Application},
{KeyboardKey.Sleep, LedId.Keyboard_Custom16},
{KeyboardKey.NumPad0, LedId.Keyboard_Num0},
{KeyboardKey.NumPad1, LedId.Keyboard_Num1},
{KeyboardKey.NumPad2, LedId.Keyboard_Num2},
{KeyboardKey.NumPad3, LedId.Keyboard_Num3},
{KeyboardKey.NumPad4, LedId.Keyboard_Num4},
{KeyboardKey.NumPad5, LedId.Keyboard_Num5},
{KeyboardKey.NumPad6, LedId.Keyboard_Num6},
{KeyboardKey.NumPad7, LedId.Keyboard_Num7},
{KeyboardKey.NumPad8, LedId.Keyboard_Num8},
{KeyboardKey.NumPad9, LedId.Keyboard_Num9},
{KeyboardKey.Multiply, LedId.Keyboard_NumAsterisk},
{KeyboardKey.Add, LedId.Keyboard_NumPlus},
{KeyboardKey.Separator, LedId.Keyboard_NumEnter}, // unverified
{KeyboardKey.Subtract, LedId.Keyboard_NumMinus},
{KeyboardKey.Decimal, LedId.Keyboard_NumPeriodAndDelete},
{KeyboardKey.Divide, LedId.Keyboard_NumSlash},
{KeyboardKey.F1, LedId.Keyboard_F1},
{KeyboardKey.F2, LedId.Keyboard_F2},
{KeyboardKey.F3, LedId.Keyboard_F3},
{KeyboardKey.F4, LedId.Keyboard_F4},
{KeyboardKey.F5, LedId.Keyboard_F5},
{KeyboardKey.F6, LedId.Keyboard_F6},
{KeyboardKey.F7, LedId.Keyboard_F7},
{KeyboardKey.F8, LedId.Keyboard_F8},
{KeyboardKey.F9, LedId.Keyboard_F9},
{KeyboardKey.F10, LedId.Keyboard_F10},
{KeyboardKey.F11, LedId.Keyboard_F11},
{KeyboardKey.F12, LedId.Keyboard_F12},
{KeyboardKey.F13, LedId.Keyboard_Custom17},
{KeyboardKey.F14, LedId.Keyboard_Custom18},
{KeyboardKey.F15, LedId.Keyboard_Custom19},
{KeyboardKey.F16, LedId.Keyboard_Custom20},
{KeyboardKey.F17, LedId.Keyboard_Custom21},
{KeyboardKey.F18, LedId.Keyboard_Custom22},
{KeyboardKey.F19, LedId.Keyboard_Custom23},
{KeyboardKey.F20, LedId.Keyboard_Custom24},
{KeyboardKey.F21, LedId.Keyboard_Custom25},
{KeyboardKey.F22, LedId.Keyboard_Custom26},
{KeyboardKey.F23, LedId.Keyboard_Custom27},
{KeyboardKey.F24, LedId.Keyboard_Custom28},
{KeyboardKey.NumLock, LedId.Keyboard_NumLock},
{KeyboardKey.Scroll, LedId.Keyboard_ScrollLock},
{KeyboardKey.LeftShift, LedId.Keyboard_LeftShift},
{KeyboardKey.RightShift, LedId.Keyboard_RightShift},
{KeyboardKey.LeftCtrl, LedId.Keyboard_LeftCtrl},
{KeyboardKey.RightCtrl, LedId.Keyboard_RightCtrl},
{KeyboardKey.LeftAlt, LedId.Keyboard_LeftAlt},
{KeyboardKey.RightAlt, LedId.Keyboard_RightAlt},
{KeyboardKey.BrowserBack, LedId.Keyboard_Custom29},
{KeyboardKey.BrowserForward, LedId.Keyboard_Custom30},
{KeyboardKey.BrowserRefresh, LedId.Keyboard_Custom31},
{KeyboardKey.BrowserStop, LedId.Keyboard_Custom32},
{KeyboardKey.BrowserSearch, LedId.Keyboard_Custom33},
{KeyboardKey.BrowserFavorites, LedId.Keyboard_Custom34},
{KeyboardKey.BrowserHome, LedId.Keyboard_Custom35},
{KeyboardKey.VolumeMute, LedId.Keyboard_MediaMute},
{KeyboardKey.VolumeDown, LedId.Keyboard_MediaVolumeDown},
{KeyboardKey.VolumeUp, LedId.Keyboard_MediaVolumeUp},
{KeyboardKey.MediaNextTrack, LedId.Keyboard_MediaNextTrack},
{KeyboardKey.MediaPreviousTrack, LedId.Keyboard_MediaPreviousTrack},
{KeyboardKey.MediaStop, LedId.Keyboard_MediaStop},
{KeyboardKey.MediaPlayPause, LedId.Keyboard_MediaPlay},
{KeyboardKey.LaunchMail, LedId.Keyboard_Custom36},
{KeyboardKey.SelectMedia, LedId.Keyboard_Custom37},
{KeyboardKey.LaunchApplication1, LedId.Keyboard_Custom38},
{KeyboardKey.LaunchApplication2, LedId.Keyboard_Custom39},
{KeyboardKey.OemSemicolon, LedId.Keyboard_SemicolonAndColon},
{KeyboardKey.OemPlus, LedId.Keyboard_EqualsAndPlus},
{KeyboardKey.OemMinus, LedId.Keyboard_MinusAndUnderscore},
{KeyboardKey.OemComma, LedId.Keyboard_CommaAndLessThan},
{KeyboardKey.OemPeriod, LedId.Keyboard_PeriodAndBiggerThan},
{KeyboardKey.OemQuestion, LedId.Keyboard_SlashAndQuestionMark},
{KeyboardKey.OemTilde, LedId.Keyboard_GraveAccentAndTilde},
{KeyboardKey.AbntC1, LedId.Keyboard_Custom40},
{KeyboardKey.AbntC2, LedId.Keyboard_Custom41},
{KeyboardKey.OemOpenBrackets, LedId.Keyboard_BracketLeft},
{KeyboardKey.OemPipe, LedId.Keyboard_Backslash},
{KeyboardKey.OemCloseBrackets, LedId.Keyboard_BracketRight},
{KeyboardKey.OemQuotes, LedId.Keyboard_ApostropheAndDoubleQuote},
{KeyboardKey.OemBackslash, LedId.Keyboard_Custom42}, // unverified
{KeyboardKey.ImeProcessed, LedId.Keyboard_Custom43},
{KeyboardKey.System, LedId.Keyboard_Custom44},
{KeyboardKey.OemAttn, LedId.Keyboard_Custom45},
{KeyboardKey.OemFinish, LedId.Keyboard_Custom46},
{KeyboardKey.OemCopy, LedId.Keyboard_Custom47},
{KeyboardKey.OemAuto, LedId.Keyboard_Custom48},
{KeyboardKey.OemEnlw, LedId.Keyboard_Custom49},
{KeyboardKey.OemBackTab, LedId.Keyboard_Custom50},
{KeyboardKey.Attn, LedId.Keyboard_Custom51},
{KeyboardKey.CrSel, LedId.Keyboard_Custom52},
{KeyboardKey.ExSel, LedId.Keyboard_Custom53},
{KeyboardKey.EraseEof, LedId.Keyboard_Custom54},
{KeyboardKey.Play, LedId.Keyboard_MediaPlay},
{KeyboardKey.Zoom, LedId.Keyboard_Custom55},
{KeyboardKey.NoName, LedId.Keyboard_Custom56},
{KeyboardKey.Pa1, LedId.Keyboard_Custom57},
{KeyboardKey.OemClear, LedId.Keyboard_Custom58},
{KeyboardKey.DeadCharProcessed, LedId.Keyboard_Custom59},
{KeyboardKey.NumPadEnter, LedId.Keyboard_NumEnter}
};
/// <summary>
/// A dictionary of mappings between <see cref="MouseButton" /> and <see cref="LedId" />
/// </summary>
public static readonly Dictionary<MouseButton, LedId> MouseButtonLedIdMap = new()
{
{MouseButton.Left, LedId.Mouse1},
{MouseButton.Middle, LedId.Mouse2},
{MouseButton.Right, LedId.Mouse3},
{MouseButton.Button4, LedId.Mouse4},
{MouseButton.Button5, LedId.Mouse5}
};
}
/// <summary>
/// A dictionary of mappings between <see cref="MouseButton" /> and <see cref="LedId" />
/// </summary>
public static readonly Dictionary<MouseButton, LedId> MouseButtonLedIdMap = new()
{
{MouseButton.Left, LedId.Mouse1},
{MouseButton.Middle, LedId.Mouse2},
{MouseButton.Right, LedId.Mouse3},
{MouseButton.Button4, LedId.Mouse4},
{MouseButton.Button5, LedId.Mouse5}
};
}
+131 -136
View File
@@ -1,147 +1,142 @@
using System;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Represents an interface for an input provider that provides and implementation for sending and receiving device
/// input
/// </summary>
public abstract class InputProvider : IDisposable
{
/// <summary>
/// Represents an interface for an input provider that provides and implementation for sending and receiving device
/// input
/// Called when the input service requests a <see cref="KeyboardToggleStatusReceived" /> event
/// </summary>
public abstract class InputProvider : IDisposable
public virtual void OnKeyboardToggleStatusRequested()
{
/// <summary>
/// Called when the input service requests a <see cref="KeyboardToggleStatusReceived"/> event
/// </summary>
public virtual void OnKeyboardToggleStatusRequested()
}
/// <summary>
/// Occurs when the input provided has received keyboard data
/// </summary>
public event EventHandler<InputProviderKeyboardEventArgs>? KeyboardDataReceived;
/// <summary>
/// Occurs when the input provider has received new toggle data for keyboards
/// </summary>
public event EventHandler<InputProviderKeyboardToggleEventArgs>? KeyboardToggleStatusReceived;
/// <summary>
/// Occurs when the input provided has received mouse button data
/// </summary>
public event EventHandler<InputProviderMouseButtonEventArgs>? MouseButtonDataReceived;
/// <summary>
/// Occurs when the input provided has received mouse scroll data
/// </summary>
public event EventHandler<InputProviderMouseScrollEventArgs>? MouseScrollDataReceived;
/// <summary>
/// Occurs when the input provided has received mouse move data
/// </summary>
public event EventHandler<InputProviderMouseMoveEventArgs>? MouseMoveDataReceived;
/// <summary>
/// Occurs when the input provided received a device identifier
/// </summary>
public event EventHandler<InputProviderIdentifierEventArgs>? IdentifierReceived;
/// <summary>
/// Invokes the <see cref="KeyboardDataReceived" /> event which the <see cref="IInputService" /> listens to as long as
/// this provider is registered
/// </summary>
/// <param name="device">The device that triggered the event</param>
/// <param name="key">The key that triggered the event</param>
/// <param name="isDown">Whether the key is pressed down</param>
protected virtual void OnKeyboardDataReceived(ArtemisDevice? device, KeyboardKey key, bool isDown)
{
KeyboardDataReceived?.Invoke(this, new InputProviderKeyboardEventArgs(device, key, isDown));
}
/// <summary>
/// Invokes the <see cref="MouseButtonDataReceived" /> event which the <see cref="IInputService" /> listens to as long
/// as this provider is registered
/// </summary>
/// <param name="device">The device that triggered the event</param>
/// <param name="button">The button that triggered the event</param>
/// <param name="isDown">Whether the button is pressed down</param>
protected virtual void OnMouseButtonDataReceived(ArtemisDevice? device, MouseButton button, bool isDown)
{
MouseButtonDataReceived?.Invoke(this, new InputProviderMouseButtonEventArgs(device, button, isDown));
}
/// <summary>
/// Invokes the <see cref="MouseScrollDataReceived" /> event which the <see cref="IInputService" /> listens to as long
/// as this provider is registered
/// </summary>
/// <param name="device">The device that triggered the event</param>
/// <param name="direction">The direction in which was scrolled</param>
/// <param name="delta">The scroll delta (can positive or negative)</param>
protected virtual void OnMouseScrollDataReceived(ArtemisDevice? device, MouseScrollDirection direction, int delta)
{
MouseScrollDataReceived?.Invoke(this, new InputProviderMouseScrollEventArgs(device, direction, delta));
}
/// <summary>
/// Invokes the <see cref="MouseMoveDataReceived" /> event which the <see cref="IInputService" /> listens to as long as
/// this provider is registered
/// </summary>
/// <param name="device">The device that triggered the event</param>
/// <param name="cursorX">The X position of the mouse cursor (not necessarily tied to the specific device)</param>
/// <param name="cursorY">The Y position of the mouse cursor (not necessarily tied to the specific device)</param>
/// <param name="deltaX">The movement delta in the horizontal direction</param>
/// <param name="deltaY">The movement delta in the vertical direction</param>
protected virtual void OnMouseMoveDataReceived(ArtemisDevice? device, int cursorX, int cursorY, int deltaX, int deltaY)
{
MouseMoveDataReceived?.Invoke(this, new InputProviderMouseMoveEventArgs(device, cursorX, cursorY, deltaX, deltaY));
}
// TODO: Remove this as the core should handle this in GetDeviceByIdentifier
/// <summary>
/// Invokes the <see cref="IdentifierReceived" /> event which the <see cref="IInputService" /> listens to as long as
/// this provider is registered
/// <para>Except for on mouse movement, call this whenever you have an identifier ready for the core to process</para>
/// </summary>
/// <param name="identifier">A value that can be used to identify this device</param>
/// <param name="deviceType">The type of device this identifier belongs to</param>
protected virtual void OnIdentifierReceived(object identifier, InputDeviceType deviceType)
{
IdentifierReceived?.Invoke(this, new InputProviderIdentifierEventArgs(identifier, deviceType));
}
/// <summary>
/// Invokes the <see cref="KeyboardToggleStatusReceived" /> event which the <see cref="IInputService" /> listens to as
/// long as
/// this provider is registered
/// </summary>
/// <param name="keyboardToggleStatus">The toggle status of the keyboard</param>
protected virtual void OnKeyboardToggleStatusReceived(KeyboardToggleStatus keyboardToggleStatus)
{
KeyboardToggleStatusReceived?.Invoke(this, new InputProviderKeyboardToggleEventArgs(keyboardToggleStatus));
}
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
}
}
/// <summary>
/// Occurs when the input provided has received keyboard data
/// </summary>
public event EventHandler<InputProviderKeyboardEventArgs>? KeyboardDataReceived;
/// <summary>
/// Occurs when the input provider has received new toggle data for keyboards
/// </summary>
public event EventHandler<InputProviderKeyboardToggleEventArgs>? KeyboardToggleStatusReceived;
/// <summary>
/// Occurs when the input provided has received mouse button data
/// </summary>
public event EventHandler<InputProviderMouseButtonEventArgs>? MouseButtonDataReceived;
/// <summary>
/// Occurs when the input provided has received mouse scroll data
/// </summary>
public event EventHandler<InputProviderMouseScrollEventArgs>? MouseScrollDataReceived;
/// <summary>
/// Occurs when the input provided has received mouse move data
/// </summary>
public event EventHandler<InputProviderMouseMoveEventArgs>? MouseMoveDataReceived;
/// <summary>
/// Occurs when the input provided received a device identifier
/// </summary>
public event EventHandler<InputProviderIdentifierEventArgs>? IdentifierReceived;
/// <summary>
/// Invokes the <see cref="KeyboardDataReceived" /> event which the <see cref="IInputService" /> listens to as long as
/// this provider is registered
/// </summary>
/// <param name="device">The device that triggered the event</param>
/// <param name="key">The key that triggered the event</param>
/// <param name="isDown">Whether the key is pressed down</param>
protected virtual void OnKeyboardDataReceived(ArtemisDevice? device, KeyboardKey key, bool isDown)
{
KeyboardDataReceived?.Invoke(this, new InputProviderKeyboardEventArgs(device, key, isDown));
}
/// <summary>
/// Invokes the <see cref="MouseButtonDataReceived" /> event which the <see cref="IInputService" /> listens to as long
/// as this provider is registered
/// </summary>
/// <param name="device">The device that triggered the event</param>
/// <param name="button">The button that triggered the event</param>
/// <param name="isDown">Whether the button is pressed down</param>
protected virtual void OnMouseButtonDataReceived(ArtemisDevice? device, MouseButton button, bool isDown)
{
MouseButtonDataReceived?.Invoke(this, new InputProviderMouseButtonEventArgs(device, button, isDown));
}
/// <summary>
/// Invokes the <see cref="MouseScrollDataReceived" /> event which the <see cref="IInputService" /> listens to as long
/// as this provider is registered
/// </summary>
/// <param name="device">The device that triggered the event</param>
/// <param name="direction">The direction in which was scrolled</param>
/// <param name="delta">The scroll delta (can positive or negative)</param>
protected virtual void OnMouseScrollDataReceived(ArtemisDevice? device, MouseScrollDirection direction, int delta)
{
MouseScrollDataReceived?.Invoke(this, new InputProviderMouseScrollEventArgs(device, direction, delta));
}
/// <summary>
/// Invokes the <see cref="MouseMoveDataReceived" /> event which the <see cref="IInputService" /> listens to as long as
/// this provider is registered
/// </summary>
/// <param name="device">The device that triggered the event</param>
/// <param name="cursorX">The X position of the mouse cursor (not necessarily tied to the specific device)</param>
/// <param name="cursorY">The Y position of the mouse cursor (not necessarily tied to the specific device)</param>
/// <param name="deltaX">The movement delta in the horizontal direction</param>
/// <param name="deltaY">The movement delta in the vertical direction</param>
protected virtual void OnMouseMoveDataReceived(ArtemisDevice? device, int cursorX, int cursorY, int deltaX, int deltaY)
{
MouseMoveDataReceived?.Invoke(this, new InputProviderMouseMoveEventArgs(device, cursorX, cursorY, deltaX, deltaY));
}
// TODO: Remove this as the core should handle this in GetDeviceByIdentifier
/// <summary>
/// Invokes the <see cref="IdentifierReceived" /> event which the <see cref="IInputService" /> listens to as long as
/// this provider is registered
/// <para>Except for on mouse movement, call this whenever you have an identifier ready for the core to process</para>
/// </summary>
/// <param name="identifier">A value that can be used to identify this device</param>
/// <param name="deviceType">The type of device this identifier belongs to</param>
protected virtual void OnIdentifierReceived(object identifier, InputDeviceType deviceType)
{
IdentifierReceived?.Invoke(this, new InputProviderIdentifierEventArgs(identifier, deviceType));
}
/// <summary>
/// Invokes the <see cref="KeyboardToggleStatusReceived" /> event which the <see cref="IInputService" /> listens to as
/// long as
/// this provider is registered
/// </summary>
/// <param name="keyboardToggleStatus">The toggle status of the keyboard</param>
protected virtual void OnKeyboardToggleStatusReceived(KeyboardToggleStatus keyboardToggleStatus)
{
KeyboardToggleStatusReceived?.Invoke(this, new InputProviderKeyboardToggleEventArgs(keyboardToggleStatus));
}
#region IDisposable
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
}
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
+415 -426
View File
@@ -4,434 +4,423 @@ using System.Linq;
using RGB.NET.Core;
using Serilog;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
internal class InputService : IInputService
{
internal class InputService : IInputService
private readonly ILogger _logger;
private readonly IRgbService _rgbService;
public InputService(ILogger logger, IRgbService rgbService)
{
private readonly ILogger _logger;
private readonly IRgbService _rgbService;
_logger = logger;
_rgbService = rgbService;
public InputService(ILogger logger, IRgbService rgbService)
{
_logger = logger;
_rgbService = rgbService;
BustIdentifierCache();
}
#region IDisposable
/// <inheritdoc />
public void Dispose()
{
while (_inputProviders.Any())
{
InputProvider provider = _inputProviders.First();
RemoveInputProvider(provider);
provider.Dispose();
}
}
#endregion
#region Providers
private readonly List<InputProvider> _inputProviders = new();
public KeyboardToggleStatus KeyboardToggleStatus { get; private set; } = new(false, false, false);
public void AddInputProvider(InputProvider inputProvider)
{
inputProvider.IdentifierReceived += InputProviderOnIdentifierReceived;
inputProvider.KeyboardDataReceived += InputProviderOnKeyboardDataReceived;
inputProvider.KeyboardToggleStatusReceived += InputProviderOnKeyboardToggleStatusReceived;
inputProvider.MouseButtonDataReceived += InputProviderOnMouseButtonDataReceived;
inputProvider.MouseScrollDataReceived += InputProviderOnMouseScrollDataReceived;
inputProvider.MouseMoveDataReceived += InputProviderOnMouseMoveDataReceived;
_inputProviders.Add(inputProvider);
inputProvider.OnKeyboardToggleStatusRequested();
}
public void RemoveInputProvider(InputProvider inputProvider)
{
if (!_inputProviders.Contains(inputProvider))
return;
_inputProviders.Remove(inputProvider);
inputProvider.IdentifierReceived -= InputProviderOnIdentifierReceived;
inputProvider.KeyboardDataReceived -= InputProviderOnKeyboardDataReceived;
inputProvider.KeyboardToggleStatusReceived -= InputProviderOnKeyboardToggleStatusReceived;
inputProvider.MouseButtonDataReceived -= InputProviderOnMouseButtonDataReceived;
inputProvider.MouseScrollDataReceived -= InputProviderOnMouseScrollDataReceived;
inputProvider.MouseMoveDataReceived -= InputProviderOnMouseMoveDataReceived;
}
#endregion
#region Identification
private readonly Dictionary<Tuple<InputProvider, object>, ArtemisDevice> _deviceCache = new();
private List<ArtemisDevice> _devices = new();
private ArtemisDevice? _cachedFallbackKeyboard;
private ArtemisDevice? _cachedFallbackMouse;
private ArtemisDevice? _identifyingDevice;
public void IdentifyDevice(ArtemisDevice device)
{
if (device.DeviceType != RGBDeviceType.Keyboard && device.DeviceType != RGBDeviceType.Mouse)
throw new ArtemisCoreException($"Cannot initialize input-identification for a device of type {device.DeviceType}. \r\n" +
"Only keyboard and mouse is supported.");
_identifyingDevice = device;
_logger.Debug("Start identifying device {device}", device);
}
public void StopIdentify()
{
if (_identifyingDevice == null)
return;
_logger.Debug("Stop identifying device {device}", _identifyingDevice);
_identifyingDevice = null;
_rgbService.SaveDevices();
BustIdentifierCache();
}
// TODO: Move the OnIdentifierReceived logic into here and get rid of OnIdentifierReceived, this and OnIdentifierReceived are always called in combination with each other
public ArtemisDevice? GetDeviceByIdentifier(InputProvider provider, object identifier, InputDeviceType type)
{
if (provider == null) throw new ArgumentNullException(nameof(provider));
if (identifier == null) throw new ArgumentNullException(nameof(identifier));
// Try cache first
ArtemisDevice? cacheMatch = GetDeviceFromCache(provider, identifier);
if (cacheMatch != null)
return cacheMatch;
string providerName = provider.GetType().FullName!;
ArtemisDevice? match = _devices.FirstOrDefault(m => m.InputIdentifiers.Any(i => Equals(i.InputProvider, providerName) && Equals(i.Identifier, identifier)));
// If a match was found cache it to speed up the next event and return the match
if (match != null)
{
AddDeviceToCache(match, provider, identifier);
return match;
}
// If there is no match, apply our fallback type
if (type == InputDeviceType.None)
return null;
if (type == InputDeviceType.Keyboard)
{
if (_cachedFallbackKeyboard != null)
return _cachedFallbackKeyboard;
_cachedFallbackKeyboard = _rgbService.EnabledDevices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Keyboard);
return _cachedFallbackKeyboard;
}
if (type == InputDeviceType.Mouse)
{
if (_cachedFallbackMouse != null)
return _cachedFallbackMouse;
_cachedFallbackMouse = _rgbService.EnabledDevices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Mouse);
return _cachedFallbackMouse;
}
return null;
}
public void BustIdentifierCache()
{
_deviceCache.Clear();
_cachedFallbackKeyboard = null;
_cachedFallbackMouse = null;
_devices = _rgbService.EnabledDevices.Where(d => d.InputIdentifiers.Any()).ToList();
}
private void AddDeviceToCache(ArtemisDevice match, InputProvider provider, object identifier)
{
_deviceCache.TryAdd(new Tuple<InputProvider, object>(provider, identifier), match);
}
private ArtemisDevice? GetDeviceFromCache(InputProvider provider, object identifier)
{
_deviceCache.TryGetValue(new Tuple<InputProvider, object>(provider, identifier), out ArtemisDevice? device);
return device;
}
private void SurfaceConfigurationChanged(object? sender, SurfaceConfigurationEventArgs e)
{
BustIdentifierCache();
}
private void InputProviderOnIdentifierReceived(object? sender, InputProviderIdentifierEventArgs e)
{
// Don't match if there is no device or if the device type differs from the event device type
if (_identifyingDevice == null ||
_identifyingDevice.DeviceType == RGBDeviceType.Keyboard && e.DeviceType == InputDeviceType.Mouse ||
_identifyingDevice.DeviceType == RGBDeviceType.Mouse && e.DeviceType == InputDeviceType.Keyboard)
return;
if (!(sender is InputProvider inputProvider))
return;
string providerName = inputProvider.GetType().FullName!;
// Remove existing identification
_identifyingDevice.InputIdentifiers.RemoveAll(i => i.InputProvider == providerName);
_identifyingDevice.InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(providerName, e.Identifier));
StopIdentify();
OnDeviceIdentified();
}
#endregion
#region Keyboard
private readonly Dictionary<ArtemisDevice, HashSet<KeyboardKey>> _pressedKeys = new();
private readonly Dictionary<ArtemisDevice, KeyboardModifierKey> _keyboardModifier = new();
private KeyboardModifierKey _globalModifiers;
private void InputProviderOnKeyboardDataReceived(object? sender, InputProviderKeyboardEventArgs e)
{
KeyboardModifierKey keyboardModifierKey = UpdateModifierKeys(e.Device, e.Key, e.IsDown);
// if UpdatePressedKeys is true, the key is already pressed, then we skip this event
if (UpdatePressedKeys(e.Device, e.Key, e.IsDown))
return;
// Get the LED
bool foundLedId = InputKeyUtilities.KeyboardKeyLedIdMap.TryGetValue(e.Key, out LedId ledId);
ArtemisLed? led = null;
if (foundLedId && e.Device != null)
led = e.Device.GetLed(ledId, true);
// Create the UpDown event args because it can be used for every event
ArtemisKeyboardKeyUpDownEventArgs eventArgs = new(e.Device, led, e.Key, keyboardModifierKey, e.IsDown);
OnKeyboardKeyUpDown(eventArgs);
if (e.IsDown)
OnKeyboardKeyDown(eventArgs);
else
OnKeyboardKeyUp(eventArgs);
// _logger.Verbose("Keyboard data: LED ID: {ledId}, key: {key}, is down: {isDown}, modifiers: {modifiers}, device: {device} ", ledId, e.Key, e.IsDown, keyboardModifierKey, e.Device);
}
private void InputProviderOnKeyboardToggleStatusReceived(object? sender, InputProviderKeyboardToggleEventArgs e)
{
KeyboardToggleStatus old = KeyboardToggleStatus;
if (KeyboardToggleStatus.CapsLock == e.KeyboardToggleStatus.CapsLock &&
KeyboardToggleStatus.NumLock == e.KeyboardToggleStatus.NumLock &&
KeyboardToggleStatus.ScrollLock == e.KeyboardToggleStatus.ScrollLock)
return;
KeyboardToggleStatus = e.KeyboardToggleStatus;
OnKeyboardToggleStatusChanged(new ArtemisKeyboardToggleStatusArgs(old, KeyboardToggleStatus));
}
private bool UpdatePressedKeys(ArtemisDevice? device, KeyboardKey key, bool isDown)
{
if (device != null)
{
// Ensure the device is in the dictionary
_pressedKeys.TryAdd(device, new HashSet<KeyboardKey>());
// Get the hash set of the device
HashSet<KeyboardKey> pressedDeviceKeys = _pressedKeys[device];
// See if the key is already pressed
bool alreadyPressed = pressedDeviceKeys.Contains(key);
// Prevent triggering already down keys again. When a key is held, we don't want to spam events like Windows does
if (isDown && alreadyPressed)
return true;
// Either add or remove the key depending on its status
if (isDown && !alreadyPressed)
pressedDeviceKeys.Add(key);
else if (!isDown && alreadyPressed)
pressedDeviceKeys.Remove(key);
}
return false;
}
private KeyboardModifierKey UpdateModifierKeys(ArtemisDevice? device, KeyboardKey key, in bool isDown)
{
KeyboardModifierKey modifiers = _globalModifiers;
if (device != null)
_keyboardModifier.TryGetValue(device, out modifiers);
if (key == KeyboardKey.LeftAlt || key == KeyboardKey.RightAlt)
{
if (isDown)
modifiers |= KeyboardModifierKey.Alt;
else
modifiers &= ~KeyboardModifierKey.Alt;
}
else if (key == KeyboardKey.LeftCtrl || key == KeyboardKey.RightCtrl)
{
if (isDown)
modifiers |= KeyboardModifierKey.Control;
else
modifiers &= ~KeyboardModifierKey.Control;
}
else if (key == KeyboardKey.LeftShift || key == KeyboardKey.RightShift)
{
if (isDown)
modifiers |= KeyboardModifierKey.Shift;
else
modifiers &= ~KeyboardModifierKey.Shift;
}
else if (key == KeyboardKey.LWin || key == KeyboardKey.RWin)
{
if (isDown)
modifiers |= KeyboardModifierKey.Windows;
else
modifiers &= ~KeyboardModifierKey.Windows;
}
if (device != null)
_keyboardModifier[device] = modifiers;
else
_globalModifiers = modifiers;
return modifiers;
}
public void ReleaseAll()
{
foreach ((ArtemisDevice? device, HashSet<KeyboardKey>? keys) in _pressedKeys.ToList())
{
foreach (KeyboardKey keyboardKey in keys)
{
InputProviderOnKeyboardDataReceived(this, new InputProviderKeyboardEventArgs(device, keyboardKey, false));
}
}
}
public bool IsKeyDown(KeyboardKey key)
{
return _pressedKeys.Any(p => p.Value.Contains(key));
}
#endregion
#region Mouse
private readonly HashSet<MouseButton> _pressedButtons = new();
private void InputProviderOnMouseButtonDataReceived(object? sender, InputProviderMouseButtonEventArgs e)
{
bool foundLedId = InputKeyUtilities.MouseButtonLedIdMap.TryGetValue(e.Button, out LedId ledId);
ArtemisLed? led = null;
if (foundLedId && e.Device != null)
led = e.Device.Leds.FirstOrDefault(l => l.RgbLed.Id == ledId);
// Create the UpDown event args because it can be used for every event
ArtemisMouseButtonUpDownEventArgs eventArgs = new(e.Device, led, e.Button, e.IsDown);
OnMouseButtonUpDown(eventArgs);
if (e.IsDown)
{
if (!_pressedButtons.Contains(e.Button))
_pressedButtons.Add(e.Button);
OnMouseButtonDown(eventArgs);
}
else
{
if (_pressedButtons.Contains(e.Button))
_pressedButtons.Remove(e.Button);
OnMouseButtonUp(eventArgs);
}
// _logger.Verbose("Mouse button data: LED ID: {ledId}, button: {button}, is down: {isDown}, device: {device} ", ledId, e.Button, e.IsDown, e.Device);
}
private void InputProviderOnMouseScrollDataReceived(object? sender, InputProviderMouseScrollEventArgs e)
{
OnMouseScroll(new ArtemisMouseScrollEventArgs(e.Device, e.Direction, e.Delta));
// _logger.Verbose("Mouse scroll data: Direction: {direction}, delta: {delta}, device: {device} ", e.Direction, e.Delta, e.Device);
}
private void InputProviderOnMouseMoveDataReceived(object? sender, InputProviderMouseMoveEventArgs e)
{
OnMouseMove(new ArtemisMouseMoveEventArgs(e.Device, e.CursorX, e.CursorY, e.DeltaX, e.DeltaY));
// _logger.Verbose("Mouse move data: XY: {X},{Y} - delta XY: {deltaX},{deltaY} - device: {device} ", e.CursorX, e.CursorY, e.DeltaX, e.DeltaY, e.Device);
}
public bool IsButtonDown(MouseButton button)
{
return _pressedButtons.Contains(button);
}
#endregion
#region Events
public event EventHandler<ArtemisKeyboardKeyUpDownEventArgs>? KeyboardKeyUpDown;
public event EventHandler<ArtemisKeyboardKeyEventArgs>? KeyboardKeyDown;
public event EventHandler<ArtemisKeyboardKeyEventArgs>? KeyboardKeyUp;
public event EventHandler<ArtemisKeyboardToggleStatusArgs>? KeyboardToggleStatusChanged;
public event EventHandler<ArtemisMouseButtonUpDownEventArgs>? MouseButtonUpDown;
public event EventHandler<ArtemisMouseButtonEventArgs>? MouseButtonDown;
public event EventHandler<ArtemisMouseButtonEventArgs>? MouseButtonUp;
public event EventHandler<ArtemisMouseScrollEventArgs>? MouseScroll;
public event EventHandler<ArtemisMouseMoveEventArgs>? MouseMove;
public event EventHandler? DeviceIdentified;
protected virtual void OnKeyboardKeyUpDown(ArtemisKeyboardKeyUpDownEventArgs e)
{
KeyboardKeyUpDown?.Invoke(this, e);
}
protected virtual void OnKeyboardKeyDown(ArtemisKeyboardKeyEventArgs e)
{
KeyboardKeyDown?.Invoke(this, e);
}
protected virtual void OnKeyboardKeyUp(ArtemisKeyboardKeyEventArgs e)
{
KeyboardKeyUp?.Invoke(this, e);
}
protected virtual void OnKeyboardToggleStatusChanged(ArtemisKeyboardToggleStatusArgs e)
{
KeyboardToggleStatusChanged?.Invoke(this, e);
}
protected virtual void OnMouseButtonUpDown(ArtemisMouseButtonUpDownEventArgs e)
{
MouseButtonUpDown?.Invoke(this, e);
}
protected virtual void OnMouseButtonDown(ArtemisMouseButtonEventArgs e)
{
MouseButtonDown?.Invoke(this, e);
}
protected virtual void OnMouseButtonUp(ArtemisMouseButtonEventArgs e)
{
MouseButtonUp?.Invoke(this, e);
}
protected virtual void OnMouseScroll(ArtemisMouseScrollEventArgs e)
{
MouseScroll?.Invoke(this, e);
}
protected virtual void OnMouseMove(ArtemisMouseMoveEventArgs e)
{
MouseMove?.Invoke(this, e);
}
protected virtual void OnDeviceIdentified()
{
DeviceIdentified?.Invoke(this, EventArgs.Empty);
}
#endregion
BustIdentifierCache();
}
protected virtual void OnKeyboardKeyUpDown(ArtemisKeyboardKeyUpDownEventArgs e)
{
KeyboardKeyUpDown?.Invoke(this, e);
}
protected virtual void OnKeyboardKeyDown(ArtemisKeyboardKeyEventArgs e)
{
KeyboardKeyDown?.Invoke(this, e);
}
protected virtual void OnKeyboardKeyUp(ArtemisKeyboardKeyEventArgs e)
{
KeyboardKeyUp?.Invoke(this, e);
}
protected virtual void OnKeyboardToggleStatusChanged(ArtemisKeyboardToggleStatusArgs e)
{
KeyboardToggleStatusChanged?.Invoke(this, e);
}
protected virtual void OnMouseButtonUpDown(ArtemisMouseButtonUpDownEventArgs e)
{
MouseButtonUpDown?.Invoke(this, e);
}
protected virtual void OnMouseButtonDown(ArtemisMouseButtonEventArgs e)
{
MouseButtonDown?.Invoke(this, e);
}
protected virtual void OnMouseButtonUp(ArtemisMouseButtonEventArgs e)
{
MouseButtonUp?.Invoke(this, e);
}
protected virtual void OnMouseScroll(ArtemisMouseScrollEventArgs e)
{
MouseScroll?.Invoke(this, e);
}
protected virtual void OnMouseMove(ArtemisMouseMoveEventArgs e)
{
MouseMove?.Invoke(this, e);
}
protected virtual void OnDeviceIdentified()
{
DeviceIdentified?.Invoke(this, EventArgs.Empty);
}
/// <inheritdoc />
public void Dispose()
{
while (_inputProviders.Any())
{
InputProvider provider = _inputProviders.First();
RemoveInputProvider(provider);
provider.Dispose();
}
}
public event EventHandler<ArtemisKeyboardKeyUpDownEventArgs>? KeyboardKeyUpDown;
public event EventHandler<ArtemisKeyboardKeyEventArgs>? KeyboardKeyDown;
public event EventHandler<ArtemisKeyboardKeyEventArgs>? KeyboardKeyUp;
public event EventHandler<ArtemisKeyboardToggleStatusArgs>? KeyboardToggleStatusChanged;
public event EventHandler<ArtemisMouseButtonUpDownEventArgs>? MouseButtonUpDown;
public event EventHandler<ArtemisMouseButtonEventArgs>? MouseButtonDown;
public event EventHandler<ArtemisMouseButtonEventArgs>? MouseButtonUp;
public event EventHandler<ArtemisMouseScrollEventArgs>? MouseScroll;
public event EventHandler<ArtemisMouseMoveEventArgs>? MouseMove;
public event EventHandler? DeviceIdentified;
#region Providers
private readonly List<InputProvider> _inputProviders = new();
public KeyboardToggleStatus KeyboardToggleStatus { get; private set; } = new(false, false, false);
public void AddInputProvider(InputProvider inputProvider)
{
inputProvider.IdentifierReceived += InputProviderOnIdentifierReceived;
inputProvider.KeyboardDataReceived += InputProviderOnKeyboardDataReceived;
inputProvider.KeyboardToggleStatusReceived += InputProviderOnKeyboardToggleStatusReceived;
inputProvider.MouseButtonDataReceived += InputProviderOnMouseButtonDataReceived;
inputProvider.MouseScrollDataReceived += InputProviderOnMouseScrollDataReceived;
inputProvider.MouseMoveDataReceived += InputProviderOnMouseMoveDataReceived;
_inputProviders.Add(inputProvider);
inputProvider.OnKeyboardToggleStatusRequested();
}
public void RemoveInputProvider(InputProvider inputProvider)
{
if (!_inputProviders.Contains(inputProvider))
return;
_inputProviders.Remove(inputProvider);
inputProvider.IdentifierReceived -= InputProviderOnIdentifierReceived;
inputProvider.KeyboardDataReceived -= InputProviderOnKeyboardDataReceived;
inputProvider.KeyboardToggleStatusReceived -= InputProviderOnKeyboardToggleStatusReceived;
inputProvider.MouseButtonDataReceived -= InputProviderOnMouseButtonDataReceived;
inputProvider.MouseScrollDataReceived -= InputProviderOnMouseScrollDataReceived;
inputProvider.MouseMoveDataReceived -= InputProviderOnMouseMoveDataReceived;
}
#endregion
#region Identification
private readonly Dictionary<Tuple<InputProvider, object>, ArtemisDevice> _deviceCache = new();
private List<ArtemisDevice> _devices = new();
private ArtemisDevice? _cachedFallbackKeyboard;
private ArtemisDevice? _cachedFallbackMouse;
private ArtemisDevice? _identifyingDevice;
public void IdentifyDevice(ArtemisDevice device)
{
if (device.DeviceType != RGBDeviceType.Keyboard && device.DeviceType != RGBDeviceType.Mouse)
throw new ArtemisCoreException($"Cannot initialize input-identification for a device of type {device.DeviceType}. \r\n" +
"Only keyboard and mouse is supported.");
_identifyingDevice = device;
_logger.Debug("Start identifying device {device}", device);
}
public void StopIdentify()
{
if (_identifyingDevice == null)
return;
_logger.Debug("Stop identifying device {device}", _identifyingDevice);
_identifyingDevice = null;
_rgbService.SaveDevices();
BustIdentifierCache();
}
// TODO: Move the OnIdentifierReceived logic into here and get rid of OnIdentifierReceived, this and OnIdentifierReceived are always called in combination with each other
public ArtemisDevice? GetDeviceByIdentifier(InputProvider provider, object identifier, InputDeviceType type)
{
if (provider == null) throw new ArgumentNullException(nameof(provider));
if (identifier == null) throw new ArgumentNullException(nameof(identifier));
// Try cache first
ArtemisDevice? cacheMatch = GetDeviceFromCache(provider, identifier);
if (cacheMatch != null)
return cacheMatch;
string providerName = provider.GetType().FullName!;
ArtemisDevice? match = _devices.FirstOrDefault(m => m.InputIdentifiers.Any(i => Equals(i.InputProvider, providerName) && Equals(i.Identifier, identifier)));
// If a match was found cache it to speed up the next event and return the match
if (match != null)
{
AddDeviceToCache(match, provider, identifier);
return match;
}
// If there is no match, apply our fallback type
if (type == InputDeviceType.None)
return null;
if (type == InputDeviceType.Keyboard)
{
if (_cachedFallbackKeyboard != null)
return _cachedFallbackKeyboard;
_cachedFallbackKeyboard = _rgbService.EnabledDevices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Keyboard);
return _cachedFallbackKeyboard;
}
if (type == InputDeviceType.Mouse)
{
if (_cachedFallbackMouse != null)
return _cachedFallbackMouse;
_cachedFallbackMouse = _rgbService.EnabledDevices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Mouse);
return _cachedFallbackMouse;
}
return null;
}
public void BustIdentifierCache()
{
_deviceCache.Clear();
_cachedFallbackKeyboard = null;
_cachedFallbackMouse = null;
_devices = _rgbService.EnabledDevices.Where(d => d.InputIdentifiers.Any()).ToList();
}
private void AddDeviceToCache(ArtemisDevice match, InputProvider provider, object identifier)
{
_deviceCache.TryAdd(new Tuple<InputProvider, object>(provider, identifier), match);
}
private ArtemisDevice? GetDeviceFromCache(InputProvider provider, object identifier)
{
_deviceCache.TryGetValue(new Tuple<InputProvider, object>(provider, identifier), out ArtemisDevice? device);
return device;
}
private void SurfaceConfigurationChanged(object? sender, SurfaceConfigurationEventArgs e)
{
BustIdentifierCache();
}
private void InputProviderOnIdentifierReceived(object? sender, InputProviderIdentifierEventArgs e)
{
// Don't match if there is no device or if the device type differs from the event device type
if (_identifyingDevice == null ||
(_identifyingDevice.DeviceType == RGBDeviceType.Keyboard && e.DeviceType == InputDeviceType.Mouse) ||
(_identifyingDevice.DeviceType == RGBDeviceType.Mouse && e.DeviceType == InputDeviceType.Keyboard))
return;
if (!(sender is InputProvider inputProvider))
return;
string providerName = inputProvider.GetType().FullName!;
// Remove existing identification
_identifyingDevice.InputIdentifiers.RemoveAll(i => i.InputProvider == providerName);
_identifyingDevice.InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(providerName, e.Identifier));
StopIdentify();
OnDeviceIdentified();
}
#endregion
#region Keyboard
private readonly Dictionary<ArtemisDevice, HashSet<KeyboardKey>> _pressedKeys = new();
private readonly Dictionary<ArtemisDevice, KeyboardModifierKey> _keyboardModifier = new();
private KeyboardModifierKey _globalModifiers;
private void InputProviderOnKeyboardDataReceived(object? sender, InputProviderKeyboardEventArgs e)
{
KeyboardModifierKey keyboardModifierKey = UpdateModifierKeys(e.Device, e.Key, e.IsDown);
// if UpdatePressedKeys is true, the key is already pressed, then we skip this event
if (UpdatePressedKeys(e.Device, e.Key, e.IsDown))
return;
// Get the LED
bool foundLedId = InputKeyUtilities.KeyboardKeyLedIdMap.TryGetValue(e.Key, out LedId ledId);
ArtemisLed? led = null;
if (foundLedId && e.Device != null)
led = e.Device.GetLed(ledId, true);
// Create the UpDown event args because it can be used for every event
ArtemisKeyboardKeyUpDownEventArgs eventArgs = new(e.Device, led, e.Key, keyboardModifierKey, e.IsDown);
OnKeyboardKeyUpDown(eventArgs);
if (e.IsDown)
OnKeyboardKeyDown(eventArgs);
else
OnKeyboardKeyUp(eventArgs);
// _logger.Verbose("Keyboard data: LED ID: {ledId}, key: {key}, is down: {isDown}, modifiers: {modifiers}, device: {device} ", ledId, e.Key, e.IsDown, keyboardModifierKey, e.Device);
}
private void InputProviderOnKeyboardToggleStatusReceived(object? sender, InputProviderKeyboardToggleEventArgs e)
{
KeyboardToggleStatus old = KeyboardToggleStatus;
if (KeyboardToggleStatus.CapsLock == e.KeyboardToggleStatus.CapsLock &&
KeyboardToggleStatus.NumLock == e.KeyboardToggleStatus.NumLock &&
KeyboardToggleStatus.ScrollLock == e.KeyboardToggleStatus.ScrollLock)
return;
KeyboardToggleStatus = e.KeyboardToggleStatus;
OnKeyboardToggleStatusChanged(new ArtemisKeyboardToggleStatusArgs(old, KeyboardToggleStatus));
}
private bool UpdatePressedKeys(ArtemisDevice? device, KeyboardKey key, bool isDown)
{
if (device != null)
{
// Ensure the device is in the dictionary
_pressedKeys.TryAdd(device, new HashSet<KeyboardKey>());
// Get the hash set of the device
HashSet<KeyboardKey> pressedDeviceKeys = _pressedKeys[device];
// See if the key is already pressed
bool alreadyPressed = pressedDeviceKeys.Contains(key);
// Prevent triggering already down keys again. When a key is held, we don't want to spam events like Windows does
if (isDown && alreadyPressed)
return true;
// Either add or remove the key depending on its status
if (isDown && !alreadyPressed)
pressedDeviceKeys.Add(key);
else if (!isDown && alreadyPressed)
pressedDeviceKeys.Remove(key);
}
return false;
}
private KeyboardModifierKey UpdateModifierKeys(ArtemisDevice? device, KeyboardKey key, in bool isDown)
{
KeyboardModifierKey modifiers = _globalModifiers;
if (device != null)
_keyboardModifier.TryGetValue(device, out modifiers);
if (key == KeyboardKey.LeftAlt || key == KeyboardKey.RightAlt)
{
if (isDown)
modifiers |= KeyboardModifierKey.Alt;
else
modifiers &= ~KeyboardModifierKey.Alt;
}
else if (key == KeyboardKey.LeftCtrl || key == KeyboardKey.RightCtrl)
{
if (isDown)
modifiers |= KeyboardModifierKey.Control;
else
modifiers &= ~KeyboardModifierKey.Control;
}
else if (key == KeyboardKey.LeftShift || key == KeyboardKey.RightShift)
{
if (isDown)
modifiers |= KeyboardModifierKey.Shift;
else
modifiers &= ~KeyboardModifierKey.Shift;
}
else if (key == KeyboardKey.LWin || key == KeyboardKey.RWin)
{
if (isDown)
modifiers |= KeyboardModifierKey.Windows;
else
modifiers &= ~KeyboardModifierKey.Windows;
}
if (device != null)
_keyboardModifier[device] = modifiers;
else
_globalModifiers = modifiers;
return modifiers;
}
public void ReleaseAll()
{
foreach ((ArtemisDevice? device, HashSet<KeyboardKey>? keys) in _pressedKeys.ToList())
{
foreach (KeyboardKey keyboardKey in keys)
InputProviderOnKeyboardDataReceived(this, new InputProviderKeyboardEventArgs(device, keyboardKey, false));
}
}
public bool IsKeyDown(KeyboardKey key)
{
return _pressedKeys.Any(p => p.Value.Contains(key));
}
#endregion
#region Mouse
private readonly HashSet<MouseButton> _pressedButtons = new();
private void InputProviderOnMouseButtonDataReceived(object? sender, InputProviderMouseButtonEventArgs e)
{
bool foundLedId = InputKeyUtilities.MouseButtonLedIdMap.TryGetValue(e.Button, out LedId ledId);
ArtemisLed? led = null;
if (foundLedId && e.Device != null)
led = e.Device.Leds.FirstOrDefault(l => l.RgbLed.Id == ledId);
// Create the UpDown event args because it can be used for every event
ArtemisMouseButtonUpDownEventArgs eventArgs = new(e.Device, led, e.Button, e.IsDown);
OnMouseButtonUpDown(eventArgs);
if (e.IsDown)
{
if (!_pressedButtons.Contains(e.Button))
_pressedButtons.Add(e.Button);
OnMouseButtonDown(eventArgs);
}
else
{
if (_pressedButtons.Contains(e.Button))
_pressedButtons.Remove(e.Button);
OnMouseButtonUp(eventArgs);
}
// _logger.Verbose("Mouse button data: LED ID: {ledId}, button: {button}, is down: {isDown}, device: {device} ", ledId, e.Button, e.IsDown, e.Device);
}
private void InputProviderOnMouseScrollDataReceived(object? sender, InputProviderMouseScrollEventArgs e)
{
OnMouseScroll(new ArtemisMouseScrollEventArgs(e.Device, e.Direction, e.Delta));
// _logger.Verbose("Mouse scroll data: Direction: {direction}, delta: {delta}, device: {device} ", e.Direction, e.Delta, e.Device);
}
private void InputProviderOnMouseMoveDataReceived(object? sender, InputProviderMouseMoveEventArgs e)
{
OnMouseMove(new ArtemisMouseMoveEventArgs(e.Device, e.CursorX, e.CursorY, e.DeltaX, e.DeltaY));
// _logger.Verbose("Mouse move data: XY: {X},{Y} - delta XY: {deltaX},{deltaY} - device: {device} ", e.CursorX, e.CursorY, e.DeltaX, e.DeltaY, e.Device);
}
public bool IsButtonDown(MouseButton button)
{
return _pressedButtons.Contains(button);
}
#endregion
}
@@ -1,125 +1,124 @@
using System;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// A service that allows you to interact with keyboard and mice input events
/// </summary>
public interface IInputService : IArtemisService, IDisposable
{
/// <summary>
/// A service that allows you to interact with keyboard and mice input events
/// Gets the current keyboard toggle status
/// </summary>
public interface IInputService : IArtemisService, IDisposable
{
/// <summary>
/// Gets the current keyboard toggle status
/// </summary>
KeyboardToggleStatus KeyboardToggleStatus { get; }
KeyboardToggleStatus KeyboardToggleStatus { get; }
/// <summary>
/// Adds an input provided
/// </summary>
/// <param name="inputProvider">The input provider the add</param>
void AddInputProvider(InputProvider inputProvider);
/// <summary>
/// Adds an input provided
/// </summary>
/// <param name="inputProvider">The input provider the add</param>
void AddInputProvider(InputProvider inputProvider);
/// <summary>
/// Removes an input provided
/// </summary>
/// <param name="inputProvider">The input provider the remove</param>
void RemoveInputProvider(InputProvider inputProvider);
/// <summary>
/// Removes an input provided
/// </summary>
/// <param name="inputProvider">The input provider the remove</param>
void RemoveInputProvider(InputProvider inputProvider);
/// <summary>
/// Identifies the provided <paramref name="device" /> by assigning the next received device identification to it
/// </summary>
/// <param name="device">The device to identify - Must be a keyboard or mouse</param>
void IdentifyDevice(ArtemisDevice device);
/// <summary>
/// Identifies the provided <paramref name="device" /> by assigning the next received device identification to it
/// </summary>
/// <param name="device">The device to identify - Must be a keyboard or mouse</param>
void IdentifyDevice(ArtemisDevice device);
/// <summary>
/// Cancels identifying the device last passed to <see cref="IdentifyDevice" />
/// </summary>
void StopIdentify();
/// <summary>
/// Cancels identifying the device last passed to <see cref="IdentifyDevice" />
/// </summary>
void StopIdentify();
/// <summary>
/// Flush all currently pressed buttons/keys
/// </summary>
void ReleaseAll();
/// <summary>
/// Flush all currently pressed buttons/keys
/// </summary>
void ReleaseAll();
/// <summary>
/// Determines whether the provided key is pressed by any device
/// </summary>
/// <param name="key">The key to check</param>
/// <returns><see langword="true" /> if the key is pressed; otherwise <see langword="false" /></returns>
bool IsKeyDown(KeyboardKey key);
/// <summary>
/// Determines whether the provided key is pressed by any device
/// </summary>
/// <param name="key">The key to check</param>
/// <returns><see langword="true" /> if the key is pressed; otherwise <see langword="false" /></returns>
bool IsKeyDown(KeyboardKey key);
/// <summary>
/// Determines whether the button key is pressed by any device
/// </summary>
/// <param name="button">The button to check</param>
/// <returns><see langword="true" /> if the button is pressed; otherwise <see langword="false" /></returns>
bool IsButtonDown(MouseButton button);
/// <summary>
/// Determines whether the button key is pressed by any device
/// </summary>
/// <param name="button">The button to check</param>
/// <returns><see langword="true" /> if the button is pressed; otherwise <see langword="false" /></returns>
bool IsButtonDown(MouseButton button);
/// <summary>
/// Occurs whenever a key on a keyboard was pressed or released
/// </summary>
event EventHandler<ArtemisKeyboardKeyUpDownEventArgs> KeyboardKeyUpDown;
/// <summary>
/// Occurs whenever a key on a keyboard was pressed or released
/// </summary>
event EventHandler<ArtemisKeyboardKeyUpDownEventArgs> KeyboardKeyUpDown;
/// <summary>
/// Occurs whenever a key on a keyboard was pressed
/// </summary>
event EventHandler<ArtemisKeyboardKeyEventArgs> KeyboardKeyDown;
/// <summary>
/// Occurs whenever a key on a keyboard was pressed
/// </summary>
event EventHandler<ArtemisKeyboardKeyEventArgs> KeyboardKeyDown;
/// <summary>
/// Occurs whenever a key on a keyboard was released
/// </summary>
event EventHandler<ArtemisKeyboardKeyEventArgs> KeyboardKeyUp;
/// <summary>
/// Occurs whenever a key on a keyboard was released
/// </summary>
event EventHandler<ArtemisKeyboardKeyEventArgs> KeyboardKeyUp;
/// <summary>
/// Occurs whenever a one or more of the keyboard toggle statuses changed
/// </summary>
event EventHandler<ArtemisKeyboardToggleStatusArgs> KeyboardToggleStatusChanged;
/// <summary>
/// Occurs whenever a one or more of the keyboard toggle statuses changed
/// </summary>
event EventHandler<ArtemisKeyboardToggleStatusArgs> KeyboardToggleStatusChanged;
/// <summary>
/// Occurs whenever a button on a mouse was pressed or released
/// </summary>
event EventHandler<ArtemisMouseButtonUpDownEventArgs> MouseButtonUpDown;
/// <summary>
/// Occurs whenever a button on a mouse was pressed or released
/// </summary>
event EventHandler<ArtemisMouseButtonUpDownEventArgs> MouseButtonUpDown;
/// <summary>
/// Occurs whenever a button on a mouse was pressed
/// </summary>
event EventHandler<ArtemisMouseButtonEventArgs> MouseButtonDown;
/// <summary>
/// Occurs whenever a button on a mouse was pressed
/// </summary>
event EventHandler<ArtemisMouseButtonEventArgs> MouseButtonDown;
/// <summary>
/// Occurs whenever a button on a mouse was released
/// </summary>
event EventHandler<ArtemisMouseButtonEventArgs> MouseButtonUp;
/// <summary>
/// Occurs whenever a button on a mouse was released
/// </summary>
event EventHandler<ArtemisMouseButtonEventArgs> MouseButtonUp;
/// <summary>
/// Occurs whenever a the scroll wheel of a mouse is turned
/// </summary>
event EventHandler<ArtemisMouseScrollEventArgs> MouseScroll;
/// <summary>
/// Occurs whenever a the scroll wheel of a mouse is turned
/// </summary>
event EventHandler<ArtemisMouseScrollEventArgs> MouseScroll;
/// <summary>
/// Occurs whenever a a mouse is moved
/// </summary>
event EventHandler<ArtemisMouseMoveEventArgs> MouseMove;
/// <summary>
/// Occurs whenever a a mouse is moved
/// </summary>
event EventHandler<ArtemisMouseMoveEventArgs> MouseMove;
/// <summary>
/// Occurs when a device has been identified after calling <see cref="IdentifyDevice" />
/// </summary>
public event EventHandler DeviceIdentified;
/// <summary>
/// Occurs when a device has been identified after calling <see cref="IdentifyDevice" />
/// </summary>
public event EventHandler DeviceIdentified;
#region Identification
#region Identification
/// <summary>
/// Attempts to identify the device using the provided <paramref name="identifier" />
/// </summary>
/// <param name="provider">The input provider to identify the device for</param>
/// <param name="identifier">The value to use to identify the device</param>
/// <param name="type">A device type to fall back to if no match is found</param>
/// <returns>If found, the Artemis device matching the provider and identifier</returns>
ArtemisDevice? GetDeviceByIdentifier(InputProvider provider, object identifier, InputDeviceType type);
/// <summary>
/// Attempts to identify the device using the provided <paramref name="identifier" />
/// </summary>
/// <param name="provider">The input provider to identify the device for</param>
/// <param name="identifier">The value to use to identify the device</param>
/// <param name="type">A device type to fall back to if no match is found</param>
/// <returns>If found, the Artemis device matching the provider and identifier</returns>
ArtemisDevice? GetDeviceByIdentifier(InputProvider provider, object identifier, InputDeviceType type);
/// <summary>
/// Clears the identifier cache
/// </summary>
void BustIdentifierCache();
/// <summary>
/// Clears the identifier cache
/// </summary>
void BustIdentifierCache();
#endregion
}
#endregion
}
@@ -1,36 +1,35 @@
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Represents the status of a keyboards special toggles
/// </summary>
public readonly struct KeyboardToggleStatus
{
/// <summary>
/// Represents the status of a keyboards special toggles
/// Creates a new <see cref="KeyboardToggleStatus" />
/// </summary>
public readonly struct KeyboardToggleStatus
/// <param name="numLock">A boolean indicating whether num lock is on</param>
/// <param name="capsLock">A boolean indicating whether caps lock is on</param>
/// <param name="scrollLock">A boolean indicating whether scroll lock is on</param>
public KeyboardToggleStatus(bool numLock, bool capsLock, bool scrollLock)
{
/// <summary>
/// Creates a new <see cref="KeyboardToggleStatus" />
/// </summary>
/// <param name="numLock">A boolean indicating whether num lock is on</param>
/// <param name="capsLock">A boolean indicating whether caps lock is on</param>
/// <param name="scrollLock">A boolean indicating whether scroll lock is on</param>
public KeyboardToggleStatus(bool numLock, bool capsLock, bool scrollLock)
{
NumLock = numLock;
CapsLock = capsLock;
ScrollLock = scrollLock;
}
/// <summary>
/// Gets a boolean indicating whether num lock is on
/// </summary>
public bool NumLock { get; }
/// <summary>
/// Gets a boolean indicating whether caps lock is on
/// </summary>
public bool CapsLock { get; }
/// <summary>
/// Gets a boolean indicating whether scroll lock is on
/// </summary>
public bool ScrollLock { get; }
NumLock = numLock;
CapsLock = capsLock;
ScrollLock = scrollLock;
}
/// <summary>
/// Gets a boolean indicating whether num lock is on
/// </summary>
public bool NumLock { get; }
/// <summary>
/// Gets a boolean indicating whether caps lock is on
/// </summary>
public bool CapsLock { get; }
/// <summary>
/// Gets a boolean indicating whether scroll lock is on
/// </summary>
public bool ScrollLock { get; }
}
@@ -1,9 +1,8 @@
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// A singleton service
/// </summary>
public interface IArtemisService
{
/// <summary>
/// A singleton service
/// </summary>
public interface IArtemisService
{
}
}
@@ -1,61 +1,60 @@
using System;
using System.Collections.Generic;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// A service that initializes the Core and manages the render loop
/// </summary>
public interface ICoreService : IArtemisService, IDisposable
{
/// <summary>
/// A service that initializes the Core and manages the render loop
/// Gets whether the or not the core has been initialized
/// </summary>
public interface ICoreService : IArtemisService, IDisposable
{
/// <summary>
/// Gets whether the or not the core has been initialized
/// </summary>
bool IsInitialized { get; }
bool IsInitialized { get; }
/// <summary>
/// The time the last frame took to render
/// </summary>
TimeSpan FrameTime { get; }
/// <summary>
/// The time the last frame took to render
/// </summary>
TimeSpan FrameTime { get; }
/// <summary>
/// The amount of frames rendered each second
/// </summary>
public int FrameRate { get; }
/// <summary>
/// The amount of frames rendered each second
/// </summary>
public int FrameRate { get; }
/// <summary>
/// Gets or sets whether profiles are rendered each frame by calling their Render method
/// </summary>
bool ProfileRenderingDisabled { get; set; }
/// <summary>
/// Gets or sets whether profiles are rendered each frame by calling their Render method
/// </summary>
bool ProfileRenderingDisabled { get; set; }
/// <summary>
/// Gets or sets a list of startup arguments
/// </summary>
List<string> StartupArguments { get; set; }
/// <summary>
/// Gets or sets a list of startup arguments
/// </summary>
List<string> StartupArguments { get; set; }
/// <summary>
/// Gets a boolean indicating whether Artemis is running in an elevated environment (admin permissions)
/// </summary>
bool IsElevated { get; set; }
/// <summary>
/// Gets a boolean indicating whether Artemis is running in an elevated environment (admin permissions)
/// </summary>
bool IsElevated { get; set; }
/// <summary>
/// Initializes the core, only call once
/// </summary>
void Initialize();
/// <summary>
/// Initializes the core, only call once
/// </summary>
void Initialize();
/// <summary>
/// Occurs the core has finished initializing
/// </summary>
event EventHandler Initialized;
/// <summary>
/// Occurs the core has finished initializing
/// </summary>
event EventHandler Initialized;
/// <summary>
/// Occurs whenever a frame is rendering, after modules have rendered
/// </summary>
event EventHandler<FrameRenderingEventArgs> FrameRendering;
/// <summary>
/// Occurs whenever a frame is rendering, after modules have rendered
/// </summary>
event EventHandler<FrameRenderingEventArgs> FrameRendering;
/// <summary>
/// Occurs whenever a frame is finished rendering and the render pipeline is closed
/// </summary>
event EventHandler<FrameRenderedEventArgs> FrameRendered;
}
/// <summary>
/// Occurs whenever a frame is finished rendering and the render pipeline is closed
/// </summary>
event EventHandler<FrameRenderedEventArgs> FrameRendered;
}
@@ -1,14 +1,13 @@
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// A service that allows you manage an <see cref="ArtemisDevice" />
/// </summary>
public interface IDeviceService : IArtemisService
{
/// <summary>
/// A service that allows you manage an <see cref="ArtemisDevice"/>
/// Identifies the device by making it blink white 5 times
/// </summary>
public interface IDeviceService : IArtemisService
{
/// <summary>
/// Identifies the device by making it blink white 5 times
/// </summary>
/// <param name="device"></param>
void IdentifyDevice(ArtemisDevice device);
}
/// <param name="device"></param>
void IdentifyDevice(ArtemisDevice device);
}
@@ -1,38 +1,36 @@
using System;
using System.Collections.Generic;
using Artemis.Core.Modules;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// A service providing module activation functionality
/// </summary>
public interface IModuleService : IArtemisService
{
/// <summary>
/// A service providing module activation functionality
/// Updates all currently active modules
/// </summary>
public interface IModuleService : IArtemisService
{
/// <summary>
/// Updates all currently active modules
/// </summary>
/// <param name="deltaTime"></param>
void UpdateActiveModules(double deltaTime);
/// <param name="deltaTime"></param>
void UpdateActiveModules(double deltaTime);
/// <summary>
/// Evaluates every enabled module's activation requirements and activates/deactivates modules accordingly
/// </summary>
void UpdateModuleActivation();
/// <summary>
/// Evaluates every enabled module's activation requirements and activates/deactivates modules accordingly
/// </summary>
void UpdateModuleActivation();
/// <summary>
/// Overrides activation on the provided module and restores regular activation to any remaining modules
/// </summary>
void SetActivationOverride(Module? module);
/// <summary>
/// Overrides activation on the provided module and restores regular activation to any remaining modules
/// </summary>
void SetActivationOverride(Module? module);
/// <summary>
/// Occurs whenever a module is activated
/// </summary>
event EventHandler<ModuleEventArgs> ModuleActivated;
/// <summary>
/// Occurs whenever a module is activated
/// </summary>
event EventHandler<ModuleEventArgs> ModuleActivated;
/// <summary>
/// Occurs whenever a module is deactivated
/// </summary>
event EventHandler<ModuleEventArgs> ModuleDeactivated;
}
/// <summary>
/// Occurs whenever a module is deactivated
/// </summary>
event EventHandler<ModuleEventArgs> ModuleDeactivated;
}
@@ -5,200 +5,199 @@ using System.Reflection;
using Artemis.Core.DeviceProviders;
using RGB.NET.Core;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// A service providing plugin management
/// </summary>
public interface IPluginManagementService : IArtemisService, IDisposable
{
/// <summary>
/// A service providing plugin management
/// Indicates whether or not plugins are currently being loaded
/// </summary>
public interface IPluginManagementService : IArtemisService, IDisposable
{
/// <summary>
/// Indicates whether or not plugins are currently being loaded
/// </summary>
bool LoadingPlugins { get; }
bool LoadingPlugins { get; }
/// <summary>
/// Copy built-in plugins from the executable directory to the plugins directory if the version is higher
/// (higher or equal if compiled as debug)
/// </summary>
void CopyBuiltInPlugins();
/// <summary>
/// Copy built-in plugins from the executable directory to the plugins directory if the version is higher
/// (higher or equal if compiled as debug)
/// </summary>
void CopyBuiltInPlugins();
/// <summary>
/// Loads all installed plugins. If plugins already loaded this will reload them all
/// </summary>
void LoadPlugins(List<string> startupArguments, bool isElevated);
/// <summary>
/// Loads all installed plugins. If plugins already loaded this will reload them all
/// </summary>
void LoadPlugins(List<string> startupArguments, bool isElevated);
/// <summary>
/// Unloads all installed plugins.
/// </summary>
void UnloadPlugins();
/// <summary>
/// Unloads all installed plugins.
/// </summary>
void UnloadPlugins();
/// <summary>
/// Loads the plugin located in the provided <paramref name="directory" />
/// </summary>
/// <param name="directory">The directory where the plugin is located</param>
Plugin? LoadPlugin(DirectoryInfo directory);
/// <summary>
/// Loads the plugin located in the provided <paramref name="directory" />
/// </summary>
/// <param name="directory">The directory where the plugin is located</param>
Plugin? LoadPlugin(DirectoryInfo directory);
/// <summary>
/// Enables the provided <paramref name="plugin" />
/// </summary>
/// <param name="plugin">The plugin to enable</param>
/// <param name="saveState">Whether or not to save the new enabled state</param>
/// <param name="ignorePluginLock">
/// Whether or not plugin lock files should be ignored. If set to <see langword="true" />,
/// plugins with lock files will load successfully
/// </param>
void EnablePlugin(Plugin plugin, bool saveState, bool ignorePluginLock = false);
/// <summary>
/// Enables the provided <paramref name="plugin" />
/// </summary>
/// <param name="plugin">The plugin to enable</param>
/// <param name="saveState">Whether or not to save the new enabled state</param>
/// <param name="ignorePluginLock">
/// Whether or not plugin lock files should be ignored. If set to <see langword="true" />,
/// plugins with lock files will load successfully
/// </param>
void EnablePlugin(Plugin plugin, bool saveState, bool ignorePluginLock = false);
/// <summary>
/// Unloads the provided <paramref name="plugin" />
/// </summary>
/// <param name="plugin">The plugin to unload</param>
void UnloadPlugin(Plugin plugin);
/// <summary>
/// Unloads the provided <paramref name="plugin" />
/// </summary>
/// <param name="plugin">The plugin to unload</param>
void UnloadPlugin(Plugin plugin);
/// <summary>
/// Disables the provided <paramref name="plugin" />
/// </summary>
/// <param name="plugin">The plugin to disable</param>
/// <param name="saveState">Whether or not to save the new enabled state</param>
void DisablePlugin(Plugin plugin, bool saveState);
/// <summary>
/// Disables the provided <paramref name="plugin" />
/// </summary>
/// <param name="plugin">The plugin to disable</param>
/// <param name="saveState">Whether or not to save the new enabled state</param>
void DisablePlugin(Plugin plugin, bool saveState);
/// <summary>
/// Imports the plugin contained in the provided ZIP file
/// </summary>
/// <param name="fileName">The full path to the ZIP file that contains the plugin</param>
/// <returns>The resulting plugin</returns>
Plugin? ImportPlugin(string fileName);
/// <summary>
/// Imports the plugin contained in the provided ZIP file
/// </summary>
/// <param name="fileName">The full path to the ZIP file that contains the plugin</param>
/// <returns>The resulting plugin</returns>
Plugin? ImportPlugin(string fileName);
/// <summary>
/// Unloads and permanently removes the provided plugin
/// </summary>
/// <param name="plugin">The plugin to remove</param>
/// <param name="removeSettings"></param>
void RemovePlugin(Plugin plugin, bool removeSettings);
/// <summary>
/// Unloads and permanently removes the provided plugin
/// </summary>
/// <param name="plugin">The plugin to remove</param>
/// <param name="removeSettings"></param>
void RemovePlugin(Plugin plugin, bool removeSettings);
/// <summary>
/// Removes the settings of a disabled plugin
/// </summary>
/// <param name="plugin">The plugin whose settings to remove</param>
void RemovePluginSettings(Plugin plugin);
/// <summary>
/// Removes the settings of a disabled plugin
/// </summary>
/// <param name="plugin">The plugin whose settings to remove</param>
void RemovePluginSettings(Plugin plugin);
/// <summary>
/// Enables the provided plugin feature
/// </summary>
/// <param name="pluginFeature">The feature to enable</param>
/// <param name="saveState">Whether or not to save the new enabled state</param>
/// <param name="isAutoEnable">If true, fails if there is a lock file present</param>
void EnablePluginFeature(PluginFeature pluginFeature, bool saveState, bool isAutoEnable = false);
/// <summary>
/// Enables the provided plugin feature
/// </summary>
/// <param name="pluginFeature">The feature to enable</param>
/// <param name="saveState">Whether or not to save the new enabled state</param>
/// <param name="isAutoEnable">If true, fails if there is a lock file present</param>
void EnablePluginFeature(PluginFeature pluginFeature, bool saveState, bool isAutoEnable = false);
/// <summary>
/// Disables the provided plugin feature
/// </summary>
/// <param name="pluginFeature">The feature to enable</param>
/// <param name="saveState">Whether or not to save the new enabled state</param>
void DisablePluginFeature(PluginFeature pluginFeature, bool saveState);
/// <summary>
/// Disables the provided plugin feature
/// </summary>
/// <param name="pluginFeature">The feature to enable</param>
/// <param name="saveState">Whether or not to save the new enabled state</param>
void DisablePluginFeature(PluginFeature pluginFeature, bool saveState);
/// <summary>
/// Gets the plugin info of all loaded plugins
/// </summary>
/// <returns>A list containing all the plugin info</returns>
List<Plugin> GetAllPlugins();
/// <summary>
/// Gets the plugin info of all loaded plugins
/// </summary>
/// <returns>A list containing all the plugin info</returns>
List<Plugin> GetAllPlugins();
/// <summary>
/// Finds all enabled <see cref="PluginFeature" /> instances of <typeparamref name="T" />
/// </summary>
/// <typeparam name="T">
/// Either <see cref="PluginFeature" /> or a plugin type implementing
/// <see cref="PluginFeature" />
/// </typeparam>
/// <returns>Returns a list of feature instances of <typeparamref name="T" /></returns>
List<T> GetFeaturesOfType<T>() where T : PluginFeature;
/// <summary>
/// Finds all enabled <see cref="PluginFeature" /> instances of <typeparamref name="T" />
/// </summary>
/// <typeparam name="T">
/// Either <see cref="PluginFeature" /> or a plugin type implementing
/// <see cref="PluginFeature" />
/// </typeparam>
/// <returns>Returns a list of feature instances of <typeparamref name="T" /></returns>
List<T> GetFeaturesOfType<T>() where T : PluginFeature;
/// <summary>
/// Gets the plugin that provided the specified assembly
/// </summary>
/// <param name="assembly"></param>
/// <returns></returns>
Plugin? GetPluginByAssembly(Assembly? assembly);
/// <summary>
/// Gets the plugin that provided the specified assembly
/// </summary>
/// <param name="assembly"></param>
/// <returns></returns>
Plugin? GetPluginByAssembly(Assembly? assembly);
/// <summary>
/// Returns the plugin info of the current call stack
/// </summary>
/// <returns>If the current call stack contains a plugin, the plugin. Otherwise null</returns>
Plugin? GetCallingPlugin();
/// <summary>
/// Returns the plugin info of the current call stack
/// </summary>
/// <returns>If the current call stack contains a plugin, the plugin. Otherwise null</returns>
Plugin? GetCallingPlugin();
/// <summary>
/// Gets the plugin that defined the specified device
/// </summary>
/// <param name="device"></param>
/// <returns></returns>
DeviceProvider GetDeviceProviderByDevice(IRGBDevice device);
/// <summary>
/// Gets the plugin that defined the specified device
/// </summary>
/// <param name="device"></param>
/// <returns></returns>
DeviceProvider GetDeviceProviderByDevice(IRGBDevice device);
/// <summary>
/// Queues the provided plugin to be deleted the next time Artemis starts, before plugins are loaded
/// </summary>
/// <param name="plugin">The plugin to delete</param>
void QueuePluginDeletion(Plugin plugin);
/// <summary>
/// Queues the provided plugin to be deleted the next time Artemis starts, before plugins are loaded
/// </summary>
/// <param name="plugin">The plugin to delete</param>
void QueuePluginDeletion(Plugin plugin);
/// <summary>
/// Removes the provided plugin for the deletion queue it was added to via <see cref="QueuePluginDeletion" />
/// </summary>
/// <param name="plugin">The plugin to dequeue</param>
void DequeuePluginDeletion(Plugin plugin);
/// <summary>
/// Removes the provided plugin for the deletion queue it was added to via <see cref="QueuePluginDeletion" />
/// </summary>
/// <param name="plugin">The plugin to dequeue</param>
void DequeuePluginDeletion(Plugin plugin);
/// <summary>
/// Occurs when built-in plugins are being loaded
/// </summary>
event EventHandler CopyingBuildInPlugins;
/// <summary>
/// Occurs when built-in plugins are being loaded
/// </summary>
event EventHandler CopyingBuildInPlugins;
/// <summary>
/// Occurs when a plugin has started loading
/// </summary>
event EventHandler<PluginEventArgs> PluginLoading;
/// <summary>
/// Occurs when a plugin has started loading
/// </summary>
event EventHandler<PluginEventArgs> PluginLoading;
/// <summary>
/// Occurs when a plugin has loaded
/// </summary>
event EventHandler<PluginEventArgs> PluginLoaded;
/// <summary>
/// Occurs when a plugin has loaded
/// </summary>
event EventHandler<PluginEventArgs> PluginLoaded;
/// <summary>
/// Occurs when a plugin has been unloaded
/// </summary>
event EventHandler<PluginEventArgs> PluginUnloaded;
/// <summary>
/// Occurs when a plugin has been unloaded
/// </summary>
event EventHandler<PluginEventArgs> PluginUnloaded;
/// <summary>
/// Occurs when a plugin is being enabled
/// </summary>
event EventHandler<PluginEventArgs> PluginEnabling;
/// <summary>
/// Occurs when a plugin is being enabled
/// </summary>
event EventHandler<PluginEventArgs> PluginEnabling;
/// <summary>
/// Occurs when a plugin has been enabled
/// </summary>
event EventHandler<PluginEventArgs> PluginEnabled;
/// <summary>
/// Occurs when a plugin has been enabled
/// </summary>
event EventHandler<PluginEventArgs> PluginEnabled;
/// <summary>
/// Occurs when a plugin has been disabled
/// </summary>
event EventHandler<PluginEventArgs> PluginDisabled;
/// <summary>
/// Occurs when a plugin has been disabled
/// </summary>
event EventHandler<PluginEventArgs> PluginDisabled;
/// <summary>
/// Occurs when a plugin feature is being enabled
/// </summary>
public event EventHandler<PluginFeatureEventArgs> PluginFeatureEnabling;
/// <summary>
/// Occurs when a plugin feature is being enabled
/// </summary>
public event EventHandler<PluginFeatureEventArgs> PluginFeatureEnabling;
/// <summary>
/// Occurs when a plugin feature has been enabled
/// </summary>
public event EventHandler<PluginFeatureEventArgs> PluginFeatureEnabled;
/// <summary>
/// Occurs when a plugin feature has been enabled
/// </summary>
public event EventHandler<PluginFeatureEventArgs> PluginFeatureEnabled;
/// <summary>
/// Occurs when a plugin feature could not be enabled
/// </summary>
public event EventHandler<PluginFeatureEventArgs> PluginFeatureEnableFailed;
/// <summary>
/// Occurs when a plugin feature could not be enabled
/// </summary>
public event EventHandler<PluginFeatureEventArgs> PluginFeatureEnableFailed;
/// <summary>
/// Occurs when a plugin feature has been disabled
/// </summary>
public event EventHandler<PluginFeatureEventArgs> PluginFeatureDisabled;
}
/// <summary>
/// Occurs when a plugin feature has been disabled
/// </summary>
public event EventHandler<PluginFeatureEventArgs> PluginFeatureDisabled;
}
@@ -1,13 +1,12 @@
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// An interface for services provided by plugins.
/// <para>
/// Any service implementing this interface will be available inside the plugin as a singleton through dependency
/// injection
/// </para>
/// </summary>
public interface IPluginService
{
/// <summary>
/// An interface for services provided by plugins.
/// <para>
/// Any service implementing this interface will be available inside the plugin as a singleton through dependency
/// injection
/// </para>
/// </summary>
public interface IPluginService
{
}
}
@@ -1,9 +1,8 @@
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// A singleton service that cannot be used by plugins
/// </summary>
public interface IProtectedArtemisService
{
/// <summary>
/// A singleton service that cannot be used by plugins
/// </summary>
public interface IProtectedArtemisService
{
}
}
@@ -3,167 +3,166 @@ using System.Collections.Generic;
using Artemis.Core.SkiaSharp;
using RGB.NET.Core;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// A service that allows you to manage the <see cref="RGBSurface" /> and its contents
/// </summary>
public interface IRgbService : IArtemisService, IDisposable
{
/// <summary>
/// A service that allows you to manage the <see cref="RGBSurface" /> and its contents
/// Gets a read-only collection containing all enabled devices
/// </summary>
public interface IRgbService : IArtemisService, IDisposable
{
/// <summary>
/// Gets a read-only collection containing all enabled devices
/// </summary>
IReadOnlyCollection<ArtemisDevice> EnabledDevices { get; }
IReadOnlyCollection<ArtemisDevice> EnabledDevices { get; }
/// <summary>
/// Gets a read-only collection containing all registered devices
/// </summary>
IReadOnlyCollection<ArtemisDevice> Devices { get; }
/// <summary>
/// Gets a read-only collection containing all registered devices
/// </summary>
IReadOnlyCollection<ArtemisDevice> Devices { get; }
/// <summary>
/// Gets a dictionary containing all <see cref="ArtemisLed" />s on the surface with their corresponding RGB.NET
/// <see cref="Led" /> as key
/// </summary>
IReadOnlyDictionary<Led, ArtemisLed> LedMap { get; }
/// <summary>
/// Gets a dictionary containing all <see cref="ArtemisLed" />s on the surface with their corresponding RGB.NET
/// <see cref="Led" /> as key
/// </summary>
IReadOnlyDictionary<Led, ArtemisLed> LedMap { get; }
/// <summary>
/// Gets or sets the RGB surface rendering is performed on
/// </summary>
RGBSurface Surface { get; set; }
/// <summary>
/// Gets or sets the RGB surface rendering is performed on
/// </summary>
RGBSurface Surface { get; set; }
/// <summary>
/// Gets or sets whether rendering should be paused
/// </summary>
bool IsRenderPaused { get; }
/// <summary>
/// Gets or sets whether rendering should be paused
/// </summary>
bool IsRenderPaused { get; }
/// <summary>
/// Gets a boolean indicating whether the render pipeline is open
/// </summary>
bool RenderOpen { get; }
/// <summary>
/// Gets a boolean indicating whether the render pipeline is open
/// </summary>
bool RenderOpen { get; }
/// <summary>
/// Gets or sets a boolean indicating whether to flush the RGB.NET LEDs during next update
/// </summary>
bool FlushLeds { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether to flush the RGB.NET LEDs during next update
/// </summary>
bool FlushLeds { get; set; }
/// <summary>
/// Opens the render pipeline
/// </summary>
SKTexture OpenRender();
/// <summary>
/// Opens the render pipeline
/// </summary>
SKTexture OpenRender();
/// <summary>
/// Closes the render pipeline
/// </summary>
void CloseRender();
/// <summary>
/// Closes the render pipeline
/// </summary>
void CloseRender();
/// <summary>
/// Applies the current value of the <c>Core.PreferredGraphicsContext</c> setting to the graphics context.
/// </summary>
/// <param name="forceSoftware">A boolean to indicate whether or not to force the graphics context to software mode.</param>
void ApplyPreferredGraphicsContext(bool forceSoftware);
/// <summary>
/// Applies the current value of the <c>Core.PreferredGraphicsContext</c> setting to the graphics context.
/// </summary>
/// <param name="forceSoftware">A boolean to indicate whether or not to force the graphics context to software mode.</param>
void ApplyPreferredGraphicsContext(bool forceSoftware);
/// <summary>
/// Updates the graphics context to the provided <paramref name="managedGraphicsContext"></paramref>.
/// <para>Note: The old graphics context will be used until the next frame starts rendering and is disposed afterwards.</para>
/// </summary>
/// <param name="managedGraphicsContext">
/// The new managed graphics context. If <see langword="null" />, software rendering
/// is used.
/// </param>
void UpdateGraphicsContext(IManagedGraphicsContext? managedGraphicsContext);
/// <summary>
/// Updates the graphics context to the provided <paramref name="managedGraphicsContext"></paramref>.
/// <para>Note: The old graphics context will be used until the next frame starts rendering and is disposed afterwards.</para>
/// </summary>
/// <param name="managedGraphicsContext">
/// The new managed graphics context. If <see langword="null" />, software rendering
/// is used.
/// </param>
void UpdateGraphicsContext(IManagedGraphicsContext? managedGraphicsContext);
/// <summary>
/// Adds the given device provider to the <see cref="Surface" />
/// </summary>
/// <param name="deviceProvider"></param>
void AddDeviceProvider(IRGBDeviceProvider deviceProvider);
/// <summary>
/// Adds the given device provider to the <see cref="Surface" />
/// </summary>
/// <param name="deviceProvider"></param>
void AddDeviceProvider(IRGBDeviceProvider deviceProvider);
/// <summary>
/// Removes the given device provider from the <see cref="Surface" />
/// </summary>
/// <param name="deviceProvider"></param>
void RemoveDeviceProvider(IRGBDeviceProvider deviceProvider);
/// <summary>
/// Removes the given device provider from the <see cref="Surface" />
/// </summary>
/// <param name="deviceProvider"></param>
void RemoveDeviceProvider(IRGBDeviceProvider deviceProvider);
/// <summary>
/// Applies auto-arranging logic to the surface
/// </summary>
void AutoArrangeDevices();
/// <summary>
/// Applies auto-arranging logic to the surface
/// </summary>
void AutoArrangeDevices();
/// <summary>
/// Applies the best available layout for the given <see cref="ArtemisDevice" />
/// </summary>
/// <param name="device">The device to apply the best available layout to</param>
/// <returns>The layout that was applied to the device</returns>
ArtemisLayout? ApplyBestDeviceLayout(ArtemisDevice device);
/// <summary>
/// Applies the best available layout for the given <see cref="ArtemisDevice" />
/// </summary>
/// <param name="device">The device to apply the best available layout to</param>
/// <returns>The layout that was applied to the device</returns>
ArtemisLayout? ApplyBestDeviceLayout(ArtemisDevice device);
/// <summary>
/// Apples the provided <see cref="ArtemisLayout" /> to the provided <see cref="ArtemisDevice" />
/// </summary>
/// <param name="device"></param>
/// <param name="layout"></param>
void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout);
/// <summary>
/// Apples the provided <see cref="ArtemisLayout" /> to the provided <see cref="ArtemisDevice" />
/// </summary>
/// <param name="device"></param>
/// <param name="layout"></param>
void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout);
/// <summary>
/// Attempts to retrieve the <see cref="ArtemisDevice" /> that corresponds the provided RGB.NET
/// <see cref="IRGBDevice" />
/// </summary>
/// <param name="rgbDevice">
/// The RGB.NET <see cref="IRGBDevice" /> to find the corresponding <see cref="ArtemisDevice" />
/// for
/// </param>
/// <returns>If found, the corresponding <see cref="ArtemisDevice" />; otherwise <see langword="null" />.</returns>
ArtemisDevice? GetDevice(IRGBDevice rgbDevice);
/// <summary>
/// Attempts to retrieve the <see cref="ArtemisDevice" /> that corresponds the provided RGB.NET
/// <see cref="IRGBDevice" />
/// </summary>
/// <param name="rgbDevice">
/// The RGB.NET <see cref="IRGBDevice" /> to find the corresponding <see cref="ArtemisDevice" />
/// for
/// </param>
/// <returns>If found, the corresponding <see cref="ArtemisDevice" />; otherwise <see langword="null" />.</returns>
ArtemisDevice? GetDevice(IRGBDevice rgbDevice);
/// <summary>
/// Attempts to retrieve the <see cref="ArtemisLed" /> that corresponds the provided RGB.NET <see cref="Led" />
/// </summary>
/// <param name="led">The RGB.NET <see cref="Led" /> to find the corresponding <see cref="ArtemisLed" /> for </param>
/// <returns>If found, the corresponding <see cref="ArtemisLed" />; otherwise <see langword="null" />.</returns>
ArtemisLed? GetLed(Led led);
/// <summary>
/// Attempts to retrieve the <see cref="ArtemisLed" /> that corresponds the provided RGB.NET <see cref="Led" />
/// </summary>
/// <param name="led">The RGB.NET <see cref="Led" /> to find the corresponding <see cref="ArtemisLed" /> for </param>
/// <returns>If found, the corresponding <see cref="ArtemisLed" />; otherwise <see langword="null" />.</returns>
ArtemisLed? GetLed(Led led);
/// <summary>
/// Saves the configuration of the provided device to persistent storage
/// </summary>
/// <param name="artemisDevice"></param>
void SaveDevice(ArtemisDevice artemisDevice);
/// <summary>
/// Saves the configuration of the provided device to persistent storage
/// </summary>
/// <param name="artemisDevice"></param>
void SaveDevice(ArtemisDevice artemisDevice);
/// <summary>
/// Saves the configuration of all current devices to persistent storage
/// </summary>
void SaveDevices();
/// <summary>
/// Saves the configuration of all current devices to persistent storage
/// </summary>
void SaveDevices();
/// <summary>
/// Enables the provided device
/// </summary>
/// <param name="device">The device to enable</param>
void EnableDevice(ArtemisDevice device);
/// <summary>
/// Enables the provided device
/// </summary>
/// <param name="device">The device to enable</param>
void EnableDevice(ArtemisDevice device);
/// <summary>
/// Disables the provided device
/// </summary>
/// <param name="device">The device to disable</param>
void DisableDevice(ArtemisDevice device);
/// <summary>
/// Disables the provided device
/// </summary>
/// <param name="device">The device to disable</param>
void DisableDevice(ArtemisDevice device);
/// <summary>
/// Pauses or resumes rendering, method won't return until the current frame finished rendering
/// </summary>
/// <param name="paused"></param>
/// <returns><see langword="true" /> if the pause state was changed; otherwise <see langword="false" />.</returns>
bool SetRenderPaused(bool paused);
/// <summary>
/// Pauses or resumes rendering, method won't return until the current frame finished rendering
/// </summary>
/// <param name="paused"></param>
/// <returns><see langword="true" /> if the pause state was changed; otherwise <see langword="false" />.</returns>
bool SetRenderPaused(bool paused);
/// <summary>
/// Occurs when a single device was added
/// </summary>
event EventHandler<DeviceEventArgs> DeviceAdded;
/// <summary>
/// Occurs when a single device was added
/// </summary>
event EventHandler<DeviceEventArgs> DeviceAdded;
/// <summary>
/// Occurs when a single device was removed
/// </summary>
event EventHandler<DeviceEventArgs> DeviceRemoved;
/// <summary>
/// Occurs when a single device was removed
/// </summary>
event EventHandler<DeviceEventArgs> DeviceRemoved;
/// <summary>
/// Occurs when the surface has had modifications to its LED collection
/// </summary>
event EventHandler LedsChanged;
}
/// <summary>
/// Occurs when the surface has had modifications to its LED collection
/// </summary>
event EventHandler LedsChanged;
}
@@ -1,47 +1,46 @@
using System.Collections.ObjectModel;
using Artemis.Core.ScriptingProviders;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// A service that allows you to manage various types of <see cref="NodeScript" /> instances
/// </summary>
public interface IScriptingService : IArtemisService
{
/// <summary>
/// A service that allows you to manage various types of <see cref="NodeScript" /> instances
/// Gets a list of all available scripting providers
/// </summary>
public interface IScriptingService : IArtemisService
{
/// <summary>
/// Gets a list of all available scripting providers
/// </summary>
ReadOnlyCollection<ScriptingProvider> ScriptingProviders { get; }
ReadOnlyCollection<ScriptingProvider> ScriptingProviders { get; }
/// <summary>
/// Gets a list of all currently active global scripts
/// </summary>
ReadOnlyCollection<GlobalScript> GlobalScripts { get; }
/// <summary>
/// Gets a list of all currently active global scripts
/// </summary>
ReadOnlyCollection<GlobalScript> GlobalScripts { get; }
/// <summary>
/// Adds a script by the provided script configuration to the provided profile and instantiates it.
/// </summary>
/// <param name="scriptConfiguration">The script configuration whose script to add.</param>
/// <param name="profile">The profile to add the script to.</param>
ProfileScript AddScript(ScriptConfiguration scriptConfiguration, Profile profile);
/// <summary>
/// Adds a script by the provided script configuration to the provided profile and instantiates it.
/// </summary>
/// <param name="scriptConfiguration">The script configuration whose script to add.</param>
/// <param name="profile">The profile to add the script to.</param>
ProfileScript AddScript(ScriptConfiguration scriptConfiguration, Profile profile);
/// <summary>
/// Removes a script by the provided script configuration from the provided profile and disposes it.
/// </summary>
/// <param name="scriptConfiguration">The script configuration whose script to remove.</param>
/// <param name="profile">The profile to remove the script from.</param>
void RemoveScript(ScriptConfiguration scriptConfiguration, Profile profile);
/// <summary>
/// Removes a script by the provided script configuration from the provided profile and disposes it.
/// </summary>
/// <param name="scriptConfiguration">The script configuration whose script to remove.</param>
/// <param name="profile">The profile to remove the script from.</param>
void RemoveScript(ScriptConfiguration scriptConfiguration, Profile profile);
/// <summary>
/// Adds a script by the provided script configuration to the global collection and instantiates it.
/// </summary>
/// <param name="scriptConfiguration">The script configuration whose script to add.</param>
GlobalScript AddScript(ScriptConfiguration scriptConfiguration);
/// <summary>
/// Adds a script by the provided script configuration to the global collection and instantiates it.
/// </summary>
/// <param name="scriptConfiguration">The script configuration whose script to add.</param>
GlobalScript AddScript(ScriptConfiguration scriptConfiguration);
/// <summary>
/// Removes a script by the provided script configuration from the global collection and disposes it.
/// </summary>
/// <param name="scriptConfiguration">The script configuration whose script to remove.</param>
void RemoveScript(ScriptConfiguration scriptConfiguration);
}
/// <summary>
/// Removes a script by the provided script configuration from the global collection and disposes it.
/// </summary>
/// <param name="scriptConfiguration">The script configuration whose script to remove.</param>
void RemoveScript(ScriptConfiguration scriptConfiguration);
}
+163 -163
View File
@@ -7,198 +7,198 @@ using Artemis.Core.Modules;
using Newtonsoft.Json;
using Serilog;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
internal class ModuleService : IModuleService
{
internal class ModuleService : IModuleService
private readonly Timer _activationUpdateTimer;
private readonly ILogger _logger;
private readonly List<Module> _modules;
private readonly IProfileService _profileService;
private readonly object _updateLock = new();
private Module? _activationOverride;
public ModuleService(ILogger logger, IPluginManagementService pluginManagementService, IProfileService profileService)
{
private readonly Timer _activationUpdateTimer;
private readonly ILogger _logger;
private readonly IProfileService _profileService;
private readonly List<Module> _modules;
private readonly object _updateLock = new();
_logger = logger;
_profileService = profileService;
private Module? _activationOverride;
_activationUpdateTimer = new Timer(2000);
_activationUpdateTimer.Start();
_activationUpdateTimer.Elapsed += ActivationUpdateTimerOnElapsed;
public ModuleService(ILogger logger, IPluginManagementService pluginManagementService, IProfileService profileService)
pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureEnabled;
pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled;
_modules = pluginManagementService.GetFeaturesOfType<Module>().ToList();
foreach (Module module in _modules)
ImportDefaultProfiles(module);
}
protected virtual void OnModuleActivated(ModuleEventArgs e)
{
ModuleActivated?.Invoke(this, e);
}
protected virtual void OnModuleDeactivated(ModuleEventArgs e)
{
ModuleDeactivated?.Invoke(this, e);
}
private void OverrideActivate(Module module)
{
try
{
_logger = logger;
_profileService = profileService;
if (module.IsActivated)
return;
_activationUpdateTimer = new Timer(2000);
_activationUpdateTimer.Start();
_activationUpdateTimer.Elapsed += ActivationUpdateTimerOnElapsed;
// If activating while it should be deactivated, its an override
bool shouldBeActivated = module.EvaluateActivationRequirements();
module.Activate(!shouldBeActivated);
}
catch (Exception e)
{
_logger.Error(new ArtemisPluginFeatureException(module, "Failed to activate module.", e), "Failed to activate module");
throw;
}
}
pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureEnabled;
pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled;
_modules = pluginManagementService.GetFeaturesOfType<Module>().ToList();
foreach (Module module in _modules)
private void OverrideDeactivate(Module module)
{
try
{
if (!module.IsActivated)
return;
// If deactivating while it should be activated, its an override
bool shouldBeActivated = module.EvaluateActivationRequirements();
// No need to deactivate if it is not in an overridden state
if (shouldBeActivated && !module.IsActivatedOverride)
return;
module.Deactivate(true);
}
catch (Exception e)
{
_logger.Error(new ArtemisPluginFeatureException(module, "Failed to deactivate module.", e), "Failed to deactivate module");
throw;
}
}
private void ActivationUpdateTimerOnElapsed(object? sender, ElapsedEventArgs e)
{
UpdateModuleActivation();
}
private void PluginManagementServiceOnPluginFeatureEnabled(object? sender, PluginFeatureEventArgs e)
{
lock (_updateLock)
{
if (e.PluginFeature is Module module && !_modules.Contains(module))
{
ImportDefaultProfiles(module);
_modules.Add(module);
}
}
}
protected virtual void OnModuleActivated(ModuleEventArgs e)
private void PluginManagementServiceOnPluginFeatureDisabled(object? sender, PluginFeatureEventArgs e)
{
lock (_updateLock)
{
ModuleActivated?.Invoke(this, e);
if (e.PluginFeature is Module module)
_modules.Remove(module);
}
}
protected virtual void OnModuleDeactivated(ModuleEventArgs e)
private void ImportDefaultProfiles(Module module)
{
try
{
ModuleDeactivated?.Invoke(this, e);
}
List<ProfileConfiguration> profileConfigurations = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).ToList();
foreach ((DefaultCategoryName categoryName, string profilePath) in module.DefaultProfilePaths)
{
ProfileConfigurationExportModel? profileConfigurationExportModel =
JsonConvert.DeserializeObject<ProfileConfigurationExportModel>(File.ReadAllText(profilePath), IProfileService.ExportSettings);
if (profileConfigurationExportModel?.ProfileEntity == null)
throw new ArtemisCoreException($"Default profile at path {profilePath} contains no valid profile data");
if (profileConfigurations.Any(p => p.Entity.ProfileId == profileConfigurationExportModel.ProfileEntity.Id))
continue;
private void OverrideActivate(Module module)
ProfileCategory category = _profileService.ProfileCategories.FirstOrDefault(c => c.Name == categoryName.ToString()) ??
_profileService.CreateProfileCategory(categoryName.ToString());
_profileService.ImportProfile(category, profileConfigurationExportModel, false, true, null);
}
}
catch (Exception e)
{
_logger.Warning(e, "Failed to import default profiles for module {module}", module);
}
}
public void UpdateModuleActivation()
{
lock (_updateLock)
{
try
{
if (module.IsActivated)
return;
// If activating while it should be deactivated, its an override
bool shouldBeActivated = module.EvaluateActivationRequirements();
module.Activate(!shouldBeActivated);
}
catch (Exception e)
{
_logger.Error(new ArtemisPluginFeatureException(module, "Failed to activate module.", e), "Failed to activate module");
throw;
}
}
private void OverrideDeactivate(Module module)
{
try
{
if (!module.IsActivated)
return;
// If deactivating while it should be activated, its an override
bool shouldBeActivated = module.EvaluateActivationRequirements();
// No need to deactivate if it is not in an overridden state
if (shouldBeActivated && !module.IsActivatedOverride)
return;
module.Deactivate(true);
}
catch (Exception e)
{
_logger.Error(new ArtemisPluginFeatureException(module, "Failed to deactivate module.", e), "Failed to deactivate module");
throw;
}
}
private void ActivationUpdateTimerOnElapsed(object? sender, ElapsedEventArgs e)
{
UpdateModuleActivation();
}
private void PluginManagementServiceOnPluginFeatureEnabled(object? sender, PluginFeatureEventArgs e)
{
lock (_updateLock)
{
if (e.PluginFeature is Module module && !_modules.Contains(module))
_activationUpdateTimer.Elapsed -= ActivationUpdateTimerOnElapsed;
foreach (Module module in _modules)
{
ImportDefaultProfiles(module);
_modules.Add(module);
}
}
}
private void PluginManagementServiceOnPluginFeatureDisabled(object? sender, PluginFeatureEventArgs e)
{
lock (_updateLock)
{
if (e.PluginFeature is Module module)
_modules.Remove(module);
}
}
private void ImportDefaultProfiles(Module module)
{
try
{
List<ProfileConfiguration> profileConfigurations = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).ToList();
foreach ((DefaultCategoryName categoryName, string profilePath) in module.DefaultProfilePaths)
{
ProfileConfigurationExportModel? profileConfigurationExportModel = JsonConvert.DeserializeObject<ProfileConfigurationExportModel>(File.ReadAllText(profilePath), IProfileService.ExportSettings);
if (profileConfigurationExportModel?.ProfileEntity == null)
throw new ArtemisCoreException($"Default profile at path {profilePath} contains no valid profile data");
if (profileConfigurations.Any(p => p.Entity.ProfileId == profileConfigurationExportModel.ProfileEntity.Id))
if (module.IsActivatedOverride)
continue;
ProfileCategory category = _profileService.ProfileCategories.FirstOrDefault(c => c.Name == categoryName.ToString()) ??
_profileService.CreateProfileCategory(categoryName.ToString());
_profileService.ImportProfile(category, profileConfigurationExportModel, false, true, null);
}
}
catch (Exception e)
{
_logger.Warning(e, "Failed to import default profiles for module {module}", module);
}
}
public void UpdateModuleActivation()
{
lock (_updateLock)
{
try
{
_activationUpdateTimer.Elapsed -= ActivationUpdateTimerOnElapsed;
foreach (Module module in _modules)
if (module.IsAlwaysAvailable)
{
if (module.IsActivatedOverride)
continue;
module.Activate(false);
continue;
}
if (module.IsAlwaysAvailable)
{
module.Activate(false);
continue;
}
module.Profiler.StartMeasurement("EvaluateActivationRequirements");
bool shouldBeActivated = module.IsEnabled && module.EvaluateActivationRequirements();
module.Profiler.StopMeasurement("EvaluateActivationRequirements");
module.Profiler.StartMeasurement("EvaluateActivationRequirements");
bool shouldBeActivated = module.IsEnabled && module.EvaluateActivationRequirements();
module.Profiler.StopMeasurement("EvaluateActivationRequirements");
if (shouldBeActivated && !module.IsActivated)
{
module.Activate(false);
OnModuleActivated(new ModuleEventArgs(module));
}
else if (!shouldBeActivated && module.IsActivated)
{
module.Deactivate(false);
OnModuleDeactivated(new ModuleEventArgs(module));
}
if (shouldBeActivated && !module.IsActivated)
{
module.Activate(false);
OnModuleActivated(new ModuleEventArgs(module));
}
else if (!shouldBeActivated && module.IsActivated)
{
module.Deactivate(false);
OnModuleDeactivated(new ModuleEventArgs(module));
}
}
finally
{
_activationUpdateTimer.Elapsed += ActivationUpdateTimerOnElapsed;
}
}
}
public void SetActivationOverride(Module? module)
{
lock (_updateLock)
finally
{
if (_activationOverride != null)
OverrideDeactivate(_activationOverride);
_activationOverride = module;
if (_activationOverride != null)
OverrideActivate(_activationOverride);
_activationUpdateTimer.Elapsed += ActivationUpdateTimerOnElapsed;
}
}
public void UpdateActiveModules(double deltaTime)
{
lock (_updateLock)
{
foreach (Module module in _modules)
module.InternalUpdate(deltaTime);
}
}
public event EventHandler<ModuleEventArgs>? ModuleActivated;
public event EventHandler<ModuleEventArgs>? ModuleDeactivated;
}
public void SetActivationOverride(Module? module)
{
lock (_updateLock)
{
if (_activationOverride != null)
OverrideDeactivate(_activationOverride);
_activationOverride = module;
if (_activationOverride != null)
OverrideActivate(_activationOverride);
}
}
public void UpdateActiveModules(double deltaTime)
{
lock (_updateLock)
{
foreach (Module module in _modules)
module.InternalUpdate(deltaTime);
}
}
public event EventHandler<ModuleEventArgs>? ModuleActivated;
public event EventHandler<ModuleEventArgs>? ModuleDeactivated;
}
+147 -149
View File
@@ -3,168 +3,166 @@ using System.Collections.Generic;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using Artemis.Core.VisualScripting;
using Artemis.Storage.Entities.Profile.Nodes;
using Newtonsoft.Json;
using Ninject;
using SkiaSharp;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
internal class NodeService : INodeService
{
internal class NodeService : INodeService
#region Constants
private static readonly Type TYPE_NODE = typeof(INode);
#endregion
private readonly IKernel _kernel;
#region Constructors
public NodeService(IKernel kernel)
{
#region Constants
private static readonly Type TYPE_NODE = typeof(INode);
#endregion
private readonly IKernel _kernel;
#region Constructors
public NodeService(IKernel kernel)
{
_kernel = kernel;
}
#endregion
#region Properties & Fields
public IEnumerable<NodeData> AvailableNodes => NodeTypeStore.GetAll();
#endregion
#region Methods
/// <inheritdoc />
public TypeColorRegistration GetTypeColorRegistration(Type type)
{
TypeColorRegistration? match = NodeTypeStore.GetColor(type);
if (match != null)
return match;
// Objects represent an input that can take any type, these are hardcoded white
if (type == typeof(object))
return new TypeColorRegistration(type, new SKColor(255, 255, 255, 255), Constants.CorePlugin);
// Come up with a random color based on the type name that should be the same each time
MD5 md5Hasher = MD5.Create();
byte[] hashed = md5Hasher.ComputeHash(Encoding.UTF8.GetBytes(type.FullName!));
int hash = BitConverter.ToInt32(hashed, 0);
SKColor baseColor = SKColor.FromHsl(hash % 255, 50 + hash % 50, 50);
return new TypeColorRegistration(type, baseColor, Constants.CorePlugin);
}
public NodeTypeRegistration RegisterNodeType(Plugin plugin, Type nodeType)
{
if (plugin == null) throw new ArgumentNullException(nameof(plugin));
if (nodeType == null) throw new ArgumentNullException(nameof(nodeType));
if (!TYPE_NODE.IsAssignableFrom(nodeType)) throw new ArgumentException("Node has to be a base type of the Node-Type.", nameof(nodeType));
NodeAttribute? nodeAttribute = nodeType.GetCustomAttribute<NodeAttribute>();
string name = nodeAttribute?.Name ?? nodeType.Name;
string description = nodeAttribute?.Description ?? string.Empty;
string category = nodeAttribute?.Category ?? string.Empty;
NodeData nodeData = new(plugin, nodeType, name, description, category, nodeAttribute?.InputType, nodeAttribute?.OutputType, (s, e) => CreateNode(s, e, nodeType));
return NodeTypeStore.Add(nodeData);
}
public TypeColorRegistration RegisterTypeColor(Plugin plugin, Type type, SKColor color)
{
if (plugin == null) throw new ArgumentNullException(nameof(plugin));
if (type == null) throw new ArgumentNullException(nameof(type));
return NodeTypeStore.AddColor(type, color, plugin);
}
public string ExportScript(NodeScript nodeScript)
{
nodeScript.Save();
return JsonConvert.SerializeObject(nodeScript.Entity, IProfileService.ExportSettings);
}
public void ImportScript(string json, NodeScript target)
{
NodeScriptEntity? entity = JsonConvert.DeserializeObject<NodeScriptEntity>(json);
if (entity == null)
throw new ArtemisCoreException("Failed to load node script");
target.LoadFromEntity(entity);
}
private INode CreateNode(INodeScript script, NodeEntity? entity, Type nodeType)
{
INode node = _kernel.Get(nodeType) as INode ?? throw new InvalidOperationException($"Node {nodeType} is not an INode");
if (entity != null)
{
node.X = entity.X;
node.Y = entity.Y;
try
{
if (node is Node nodeImplementation)
nodeImplementation.DeserializeStorage(entity.Storage);
}
catch
{
// ignored
}
}
node.Initialize(script);
return node;
}
#endregion
_kernel = kernel;
}
#endregion
#region Properties & Fields
public IEnumerable<NodeData> AvailableNodes => NodeTypeStore.GetAll();
#endregion
#region Methods
/// <inheritdoc />
public TypeColorRegistration GetTypeColorRegistration(Type type)
{
TypeColorRegistration? match = NodeTypeStore.GetColor(type);
if (match != null)
return match;
// Objects represent an input that can take any type, these are hardcoded white
if (type == typeof(object))
return new TypeColorRegistration(type, new SKColor(255, 255, 255, 255), Constants.CorePlugin);
// Come up with a random color based on the type name that should be the same each time
MD5 md5Hasher = MD5.Create();
byte[] hashed = md5Hasher.ComputeHash(Encoding.UTF8.GetBytes(type.FullName!));
int hash = BitConverter.ToInt32(hashed, 0);
SKColor baseColor = SKColor.FromHsl(hash % 255, 50 + hash % 50, 50);
return new TypeColorRegistration(type, baseColor, Constants.CorePlugin);
}
public NodeTypeRegistration RegisterNodeType(Plugin plugin, Type nodeType)
{
if (plugin == null) throw new ArgumentNullException(nameof(plugin));
if (nodeType == null) throw new ArgumentNullException(nameof(nodeType));
if (!TYPE_NODE.IsAssignableFrom(nodeType)) throw new ArgumentException("Node has to be a base type of the Node-Type.", nameof(nodeType));
NodeAttribute? nodeAttribute = nodeType.GetCustomAttribute<NodeAttribute>();
string name = nodeAttribute?.Name ?? nodeType.Name;
string description = nodeAttribute?.Description ?? string.Empty;
string category = nodeAttribute?.Category ?? string.Empty;
NodeData nodeData = new(plugin, nodeType, name, description, category, nodeAttribute?.InputType, nodeAttribute?.OutputType, (s, e) => CreateNode(s, e, nodeType));
return NodeTypeStore.Add(nodeData);
}
public TypeColorRegistration RegisterTypeColor(Plugin plugin, Type type, SKColor color)
{
if (plugin == null) throw new ArgumentNullException(nameof(plugin));
if (type == null) throw new ArgumentNullException(nameof(type));
return NodeTypeStore.AddColor(type, color, plugin);
}
public string ExportScript(NodeScript nodeScript)
{
nodeScript.Save();
return JsonConvert.SerializeObject(nodeScript.Entity, IProfileService.ExportSettings);
}
public void ImportScript(string json, NodeScript target)
{
NodeScriptEntity? entity = JsonConvert.DeserializeObject<NodeScriptEntity>(json);
if (entity == null)
throw new ArtemisCoreException("Failed to load node script");
target.LoadFromEntity(entity);
}
private INode CreateNode(INodeScript script, NodeEntity? entity, Type nodeType)
{
INode node = _kernel.Get(nodeType) as INode ?? throw new InvalidOperationException($"Node {nodeType} is not an INode");
if (entity != null)
{
node.X = entity.X;
node.Y = entity.Y;
try
{
if (node is Node nodeImplementation)
nodeImplementation.DeserializeStorage(entity.Storage);
}
catch
{
// ignored
}
}
node.Initialize(script);
return node;
}
#endregion
}
/// <summary>
/// A service that provides access to the node system
/// </summary>
public interface INodeService : IArtemisService
{
/// <summary>
/// Gets all available nodes
/// </summary>
IEnumerable<NodeData> AvailableNodes { get; }
/// <summary>
/// A service that provides access to the node system
/// Gets the best matching registration for the provided type
/// </summary>
public interface INodeService : IArtemisService
{
/// <summary>
/// Gets all available nodes
/// </summary>
IEnumerable<NodeData> AvailableNodes { get; }
TypeColorRegistration GetTypeColorRegistration(Type type);
/// <summary>
/// Gets the best matching registration for the provided type
/// </summary>
TypeColorRegistration GetTypeColorRegistration(Type type);
/// <summary>
/// Registers a node of the provided <paramref name="nodeType" />
/// </summary>
/// <param name="plugin">The plugin the node belongs to</param>
/// <param name="nodeType">The type of node to initialize</param>
NodeTypeRegistration RegisterNodeType(Plugin plugin, Type nodeType);
/// <summary>
/// Registers a node of the provided <paramref name="nodeType" />
/// </summary>
/// <param name="plugin">The plugin the node belongs to</param>
/// <param name="nodeType">The type of node to initialize</param>
NodeTypeRegistration RegisterNodeType(Plugin plugin, Type nodeType);
/// <summary>
/// Registers a type with a provided color for use in the node editor
/// </summary>
/// <param name="plugin">The plugin making the registration</param>
/// <param name="type">The type to associate the color with</param>
/// <param name="color">The color to display</param>
TypeColorRegistration RegisterTypeColor(Plugin plugin, Type type, SKColor color);
/// <summary>
/// Registers a type with a provided color for use in the node editor
/// </summary>
/// <param name="plugin">The plugin making the registration</param>
/// <param name="type">The type to associate the color with</param>
/// <param name="color">The color to display</param>
TypeColorRegistration RegisterTypeColor(Plugin plugin, Type type, SKColor color);
/// <summary>
/// Exports the provided node script to JSON.
/// </summary>
/// <param name="nodeScript">The node script to export.</param>
/// <returns>The resulting JSON.</returns>
string ExportScript(NodeScript nodeScript);
/// <summary>
/// Exports the provided node script to JSON.
/// </summary>
/// <param name="nodeScript">The node script to export.</param>
/// <returns>The resulting JSON.</returns>
string ExportScript(NodeScript nodeScript);
/// <summary>
/// Imports the provided JSON onto the provided node script, overwriting any existing contents.
/// </summary>
/// <param name="json">The JSON to import.</param>
/// <param name="target">The target node script whose contents to overwrite.</param>
void ImportScript(string json, NodeScript target);
}
/// <summary>
/// Imports the provided JSON onto the provided node script, overwriting any existing contents.
/// </summary>
/// <param name="json">The JSON to import.</param>
/// <param name="target">The target node script whose contents to overwrite.</param>
void ImportScript(string json, NodeScript target);
}
File diff suppressed because it is too large. Load diff
@@ -1,21 +1,20 @@
using System;
using System.Diagnostics;
namespace Artemis.Core.Services
{
/// <summary>
/// Contains data for the ProcessMonitor process events
/// </summary>
public class ProcessEventArgs : EventArgs
{
/// <summary>
/// Gets the process related to the event
/// </summary>
public Process Process { get; }
namespace Artemis.Core.Services;
internal ProcessEventArgs(Process process)
{
Process = process;
}
/// <summary>
/// Contains data for the ProcessMonitor process events
/// </summary>
public class ProcessEventArgs : EventArgs
{
internal ProcessEventArgs(Process process)
{
Process = process;
}
}
/// <summary>
/// Gets the process related to the event
/// </summary>
public Process Process { get; }
}
@@ -1,30 +1,26 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// A service that provides events for started and stopped processes and a list of all running processes.
/// </summary>
public interface IProcessMonitorService : IArtemisService
{
/// <summary>
/// A service that provides events for started and stopped processes and a list of all running processes.
/// Occurs when a process starts.
/// </summary>
public interface IProcessMonitorService : IArtemisService
{
/// <summary>
/// Occurs when a process starts.
/// </summary>
event EventHandler<ProcessEventArgs> ProcessStarted;
event EventHandler<ProcessEventArgs> ProcessStarted;
/// <summary>
/// Occurs when a process stops.
/// </summary>
event EventHandler<ProcessEventArgs> ProcessStopped;
/// <summary>
/// Occurs when a process stops.
/// </summary>
event EventHandler<ProcessEventArgs> ProcessStopped;
/// <summary>
/// Returns an enumerable with the processes running on the system.
/// </summary>
IEnumerable<Process> GetRunningProcesses();
}
}
/// <summary>
/// Returns an enumerable with the processes running on the system.
/// </summary>
IEnumerable<Process> GetRunningProcesses();
}
@@ -1,71 +1,70 @@
using Serilog;
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Timers;
using Artemis.Core.Modules;
using Serilog;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
internal class ProcessMonitorService : IProcessMonitorService
{
internal class ProcessMonitorService : IProcessMonitorService
private readonly ProcessComparer _comparer;
private readonly ILogger _logger;
private Process[] _lastScannedProcesses;
public ProcessMonitorService(ILogger logger)
{
private readonly ILogger _logger;
private readonly ProcessComparer _comparer;
private Process[] _lastScannedProcesses;
_logger = logger;
_lastScannedProcesses = Process.GetProcesses();
Timer processScanTimer = new(1000);
processScanTimer.Elapsed += OnTimerElapsed;
processScanTimer.Start();
_comparer = new ProcessComparer();
public ProcessMonitorService(ILogger logger)
{
_logger = logger;
_lastScannedProcesses = Process.GetProcesses();
Timer processScanTimer = new(1000);
processScanTimer.Elapsed += OnTimerElapsed;
processScanTimer.Start();
_comparer = new ProcessComparer();
ProcessActivationRequirement.ProcessMonitorService = this;
}
public event EventHandler<ProcessEventArgs>? ProcessStarted;
public event EventHandler<ProcessEventArgs>? ProcessStopped;
public IEnumerable<Process> GetRunningProcesses()
{
return _lastScannedProcesses;
}
private void OnTimerElapsed(object? sender, ElapsedEventArgs e)
{
Process[] newProcesses = Process.GetProcesses();
foreach (Process startedProcess in newProcesses.Except(_lastScannedProcesses, _comparer))
{
ProcessStarted?.Invoke(this, new ProcessEventArgs(startedProcess));
_logger.Verbose("Started Process: {startedProcess}", startedProcess.ProcessName);
}
foreach (Process stoppedProcess in _lastScannedProcesses.Except(newProcesses, _comparer))
{
ProcessStopped?.Invoke(this, new ProcessEventArgs(stoppedProcess));
_logger.Verbose("Stopped Process: {stoppedProcess}", stoppedProcess.ProcessName);
}
_lastScannedProcesses = newProcesses;
}
ProcessActivationRequirement.ProcessMonitorService = this;
}
internal class ProcessComparer : IEqualityComparer<Process>
private void OnTimerElapsed(object? sender, ElapsedEventArgs e)
{
public bool Equals(Process? x, Process? y)
Process[] newProcesses = Process.GetProcesses();
foreach (Process startedProcess in newProcesses.Except(_lastScannedProcesses, _comparer))
{
if (x == null && y == null) return true;
if (x == null || y == null) return false;
return x.Id == y.Id && x.ProcessName == y.ProcessName && x.SessionId == y.SessionId;
ProcessStarted?.Invoke(this, new ProcessEventArgs(startedProcess));
_logger.Verbose("Started Process: {startedProcess}", startedProcess.ProcessName);
}
public int GetHashCode(Process? obj)
foreach (Process stoppedProcess in _lastScannedProcesses.Except(newProcesses, _comparer))
{
if (obj == null) return 0;
return obj.Id;
ProcessStopped?.Invoke(this, new ProcessEventArgs(stoppedProcess));
_logger.Verbose("Stopped Process: {stoppedProcess}", stoppedProcess.ProcessName);
}
_lastScannedProcesses = newProcesses;
}
public event EventHandler<ProcessEventArgs>? ProcessStarted;
public event EventHandler<ProcessEventArgs>? ProcessStopped;
public IEnumerable<Process> GetRunningProcesses()
{
return _lastScannedProcesses;
}
}
internal class ProcessComparer : IEqualityComparer<Process>
{
public bool Equals(Process? x, Process? y)
{
if (x == null && y == null) return true;
if (x == null || y == null) return false;
return x.Id == y.Id && x.ProcessName == y.ProcessName && x.SessionId == y.SessionId;
}
public int GetHashCode(Process? obj)
{
if (obj == null) return 0;
return obj.Id;
}
}
@@ -3,66 +3,65 @@ using System.Collections.Generic;
using System.Linq;
using Artemis.Core.Modules;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
internal class DataModelService : IDataModelService
{
internal class DataModelService : IDataModelService
public DataModelService(IPluginManagementService pluginManagementService)
{
public DataModelService(IPluginManagementService pluginManagementService)
{
// Add data models of already loaded plugins
foreach (Module module in pluginManagementService.GetFeaturesOfType<Module>().Where(p => p.IsEnabled))
AddModuleDataModel(module);
// Add data models of already loaded plugins
foreach (Module module in pluginManagementService.GetFeaturesOfType<Module>().Where(p => p.IsEnabled))
AddModuleDataModel(module);
// Add data models of new plugins when they get enabled
pluginManagementService.PluginFeatureEnabled += OnPluginFeatureEnabled;
}
// Add data models of new plugins when they get enabled
pluginManagementService.PluginFeatureEnabled += OnPluginFeatureEnabled;
}
private void OnPluginFeatureEnabled(object? sender, PluginFeatureEventArgs e)
{
if (e.PluginFeature is Module module)
AddModuleDataModel(module);
}
private void AddModuleDataModel(Module module)
{
if (module.InternalDataModel == null)
return;
if (module.InternalDataModel.DataModelDescription == null)
throw new ArtemisPluginFeatureException(module, "Module overrides GetDataModelDescription but returned null");
module.InternalDataModel.IsExpansion = module.IsAlwaysAvailable;
RegisterDataModel(module.InternalDataModel);
}
public DataModelRegistration RegisterDataModel(DataModel dataModel)
{
if (dataModel == null)
throw new ArgumentNullException(nameof(dataModel));
return DataModelStore.Add(dataModel);
}
public DataModelRegistration RegisterDataModel(DataModel dataModel)
{
if (dataModel == null)
throw new ArgumentNullException(nameof(dataModel));
return DataModelStore.Add(dataModel);
}
public void RemoveDataModel(DataModelRegistration registration)
{
if (registration == null)
throw new ArgumentNullException(nameof(registration));
DataModelStore.Remove(registration);
}
public void RemoveDataModel(DataModelRegistration registration)
{
if (registration == null)
throw new ArgumentNullException(nameof(registration));
DataModelStore.Remove(registration);
}
public List<DataModel> GetDataModels()
{
return DataModelStore.GetAll().Select(d => d.DataModel).ToList();
}
public List<DataModel> GetDataModels()
{
return DataModelStore.GetAll().Select(d => d.DataModel).ToList();
}
public T? GetDataModel<T>() where T : DataModel
{
return (T?) DataModelStore.GetAll().FirstOrDefault(d => d.DataModel is T)?.DataModel;
}
public T? GetDataModel<T>() where T : DataModel
{
return (T?) DataModelStore.GetAll().FirstOrDefault(d => d.DataModel is T)?.DataModel;
}
public DataModel? GetPluginDataModel(PluginFeature pluginFeature)
{
return DataModelStore.Get(pluginFeature.Id)?.DataModel;
}
private void OnPluginFeatureEnabled(object? sender, PluginFeatureEventArgs e)
{
if (e.PluginFeature is Module module)
AddModuleDataModel(module);
}
private void AddModuleDataModel(Module module)
{
if (module.InternalDataModel == null)
return;
if (module.InternalDataModel.DataModelDescription == null)
throw new ArtemisPluginFeatureException(module, "Module overrides GetDataModelDescription but returned null");
module.InternalDataModel.IsExpansion = module.IsAlwaysAvailable;
RegisterDataModel(module.InternalDataModel);
}
public DataModel? GetPluginDataModel(PluginFeature pluginFeature)
{
return DataModelStore.Get(pluginFeature.Id)?.DataModel;
}
}
@@ -1,40 +1,38 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using Artemis.Core.Modules;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// A service that allows you to register and retrieve data models
/// </summary>
public interface IDataModelService : IArtemisService
{
/// <summary>
/// A service that allows you to register and retrieve data models
/// Add a data model to so that it is available to conditions and data bindings
/// </summary>
public interface IDataModelService : IArtemisService
{
/// <summary>
/// Add a data model to so that it is available to conditions and data bindings
/// </summary>
/// <param name="dataModel"></param>
DataModelRegistration RegisterDataModel(DataModel dataModel);
/// <param name="dataModel"></param>
DataModelRegistration RegisterDataModel(DataModel dataModel);
/// <summary>
/// Remove a previously added data model so that it is no longer available
/// </summary>
void RemoveDataModel(DataModelRegistration registration);
/// <summary>
/// Remove a previously added data model so that it is no longer available
/// </summary>
void RemoveDataModel(DataModelRegistration registration);
/// <summary>
/// Returns a list of all registered data models
/// </summary>
List<DataModel> GetDataModels();
/// <summary>
/// Returns a list of all registered data models
/// </summary>
List<DataModel> GetDataModels();
/// <summary>
/// If found, returns the registered data model of type <typeparamref name="T" />
/// </summary>
/// <typeparam name="T">The type of the data model to find</typeparam>
T? GetDataModel<T>() where T : DataModel;
/// <summary>
/// If found, returns the registered data model of type <typeparamref name="T" />
/// </summary>
/// <typeparam name="T">The type of the data model to find</typeparam>
T? GetDataModel<T>() where T : DataModel;
/// <summary>
/// If found, returns the data model of the provided plugin
/// </summary>
/// <param name="pluginFeature">The plugin to find the data model of</param>
DataModel? GetPluginDataModel(PluginFeature pluginFeature);
}
/// <summary>
/// If found, returns the data model of the provided plugin
/// </summary>
/// <param name="pluginFeature">The plugin to find the data model of</param>
DataModel? GetPluginDataModel(PluginFeature pluginFeature);
}
@@ -1,37 +1,36 @@
using System.Collections.Generic;
using Artemis.Core.LayerBrushes;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// A service that allows you to register and retrieve layer brushes
/// </summary>
public interface ILayerBrushService : IArtemisService
{
/// <summary>
/// A service that allows you to register and retrieve layer brushes
/// Add a layer brush descriptor so that it is available to layers
/// </summary>
public interface ILayerBrushService : IArtemisService
{
/// <summary>
/// Add a layer brush descriptor so that it is available to layers
/// </summary>
LayerBrushRegistration RegisterLayerBrush(LayerBrushDescriptor descriptor);
LayerBrushRegistration RegisterLayerBrush(LayerBrushDescriptor descriptor);
/// <summary>
/// Remove a previously added layer brush descriptor so that it is no longer available
/// </summary>
void RemoveLayerBrush(LayerBrushRegistration registration);
/// <summary>
/// Remove a previously added layer brush descriptor so that it is no longer available
/// </summary>
void RemoveLayerBrush(LayerBrushRegistration registration);
/// <summary>
/// Returns a list of all registered layer brush descriptors
/// </summary>
List<LayerBrushDescriptor> GetLayerBrushes();
/// <summary>
/// Returns a list of all registered layer brush descriptors
/// </summary>
List<LayerBrushDescriptor> GetLayerBrushes();
/// <summary>
/// Returns the descriptor of the default layer brush
/// </summary>
LayerBrushDescriptor? GetDefaultLayerBrush();
/// <summary>
/// Returns the descriptor of the default layer brush
/// </summary>
LayerBrushDescriptor? GetDefaultLayerBrush();
/// <summary>
/// Applies the configured default brush to the provided <paramref name="layer" />.
/// </summary>
/// <param name="layer">The layer to apply the default brush to.</param>
void ApplyDefaultBrush(Layer layer);
}
/// <summary>
/// Applies the configured default brush to the provided <paramref name="layer" />.
/// </summary>
/// <param name="layer">The layer to apply the default brush to.</param>
void ApplyDefaultBrush(Layer layer);
}
@@ -1,27 +1,25 @@
using System.Collections.Generic;
using Artemis.Core.LayerBrushes;
using Artemis.Core.LayerEffects;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// A service that allows you to register and retrieve layer brushes
/// </summary>
public interface ILayerEffectService : IArtemisService
{
/// <summary>
/// A service that allows you to register and retrieve layer brushes
/// Add an effect descriptor so that it is available to profile elements
/// </summary>
public interface ILayerEffectService : IArtemisService
{
/// <summary>
/// Add an effect descriptor so that it is available to profile elements
/// </summary>
LayerEffectRegistration RegisterLayerEffect(LayerEffectDescriptor descriptor);
LayerEffectRegistration RegisterLayerEffect(LayerEffectDescriptor descriptor);
/// <summary>
/// Remove a previously added layer effect descriptor so that it is no longer available
/// </summary>
void RemoveLayerEffect(LayerEffectRegistration registration);
/// <summary>
/// Remove a previously added layer effect descriptor so that it is no longer available
/// </summary>
void RemoveLayerEffect(LayerEffectRegistration registration);
/// <summary>
/// Returns a list of all registered layer effect descriptors
/// </summary>
List<LayerEffectDescriptor> GetLayerEffects();
}
/// <summary>
/// Returns a list of all registered layer effect descriptors
/// </summary>
List<LayerEffectDescriptor> GetLayerEffects();
}
@@ -3,56 +3,55 @@ using System.Collections.Generic;
using System.Linq;
using Artemis.Core.LayerBrushes;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
internal class LayerBrushService : ILayerBrushService
{
internal class LayerBrushService : ILayerBrushService
private readonly ISettingsService _settingsService;
public LayerBrushService(ISettingsService settingsService)
{
private readonly ISettingsService _settingsService;
_settingsService = settingsService;
}
public LayerBrushService(ISettingsService settingsService)
public LayerBrushRegistration RegisterLayerBrush(LayerBrushDescriptor descriptor)
{
if (descriptor == null)
throw new ArgumentNullException(nameof(descriptor));
return LayerBrushStore.Add(descriptor);
}
public void RemoveLayerBrush(LayerBrushRegistration registration)
{
if (registration == null)
throw new ArgumentNullException(nameof(registration));
LayerBrushStore.Remove(registration);
}
public List<LayerBrushDescriptor> GetLayerBrushes()
{
return LayerBrushStore.GetAll().Select(r => r.LayerBrushDescriptor).ToList();
}
public LayerBrushDescriptor? GetDefaultLayerBrush()
{
PluginSetting<LayerBrushReference> defaultReference = _settingsService.GetSetting("ProfileEditor.DefaultLayerBrushDescriptor", new LayerBrushReference
{
_settingsService = settingsService;
}
LayerBrushProviderId = "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba",
BrushType = "SolidBrush"
});
public LayerBrushRegistration RegisterLayerBrush(LayerBrushDescriptor descriptor)
{
if (descriptor == null)
throw new ArgumentNullException(nameof(descriptor));
defaultReference.Value.LayerBrushProviderId ??= "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba";
defaultReference.Value.BrushType ??= "SolidBrush";
return LayerBrushStore.Get(defaultReference.Value.LayerBrushProviderId, defaultReference.Value.BrushType)?.LayerBrushDescriptor;
}
return LayerBrushStore.Add(descriptor);
}
public void RemoveLayerBrush(LayerBrushRegistration registration)
{
if (registration == null)
throw new ArgumentNullException(nameof(registration));
LayerBrushStore.Remove(registration);
}
public List<LayerBrushDescriptor> GetLayerBrushes()
{
return LayerBrushStore.GetAll().Select(r => r.LayerBrushDescriptor).ToList();
}
public LayerBrushDescriptor? GetDefaultLayerBrush()
{
PluginSetting<LayerBrushReference> defaultReference = _settingsService.GetSetting("ProfileEditor.DefaultLayerBrushDescriptor", new LayerBrushReference
{
LayerBrushProviderId = "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba",
BrushType = "SolidBrush"
});
defaultReference.Value.LayerBrushProviderId ??= "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba";
defaultReference.Value.BrushType ??= "SolidBrush";
return LayerBrushStore.Get(defaultReference.Value.LayerBrushProviderId, defaultReference.Value.BrushType)?.LayerBrushDescriptor;
}
/// <inheritdoc />
public void ApplyDefaultBrush(Layer layer)
{
LayerBrushDescriptor? brush = GetDefaultLayerBrush();
if (brush != null)
layer.ChangeLayerBrush(brush.CreateInstance(layer, null));
}
/// <inheritdoc />
public void ApplyDefaultBrush(Layer layer)
{
LayerBrushDescriptor? brush = GetDefaultLayerBrush();
if (brush != null)
layer.ChangeLayerBrush(brush.CreateInstance(layer, null));
}
}
@@ -3,28 +3,27 @@ using System.Collections.Generic;
using System.Linq;
using Artemis.Core.LayerEffects;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
internal class LayerEffectService : ILayerEffectService
{
internal class LayerEffectService : ILayerEffectService
public LayerEffectRegistration RegisterLayerEffect(LayerEffectDescriptor descriptor)
{
public LayerEffectRegistration RegisterLayerEffect(LayerEffectDescriptor descriptor)
{
if (descriptor == null)
throw new ArgumentNullException(nameof(descriptor));
if (descriptor == null)
throw new ArgumentNullException(nameof(descriptor));
return LayerEffectStore.Add(descriptor);
}
return LayerEffectStore.Add(descriptor);
}
public void RemoveLayerEffect(LayerEffectRegistration registration)
{
if (registration == null)
throw new ArgumentNullException(nameof(registration));
LayerEffectStore.Remove(registration);
}
public void RemoveLayerEffect(LayerEffectRegistration registration)
{
if (registration == null)
throw new ArgumentNullException(nameof(registration));
LayerEffectStore.Remove(registration);
}
public List<LayerEffectDescriptor> GetLayerEffects()
{
return LayerEffectStore.GetAll().Select(r => r.LayerEffectDescriptor).ToList();
}
public List<LayerEffectDescriptor> GetLayerEffects()
{
return LayerEffectStore.GetAll().Select(r => r.LayerEffectDescriptor).ToList();
}
}
File diff suppressed because it is too large. Load diff
+150 -155
View File
@@ -7,185 +7,180 @@ using Artemis.Core.ScriptingProviders;
using Ninject;
using Ninject.Parameters;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
internal class ScriptingService : IScriptingService
{
internal class ScriptingService : IScriptingService
private readonly List<GlobalScript> _globalScripts;
private readonly IPluginManagementService _pluginManagementService;
private readonly IProfileService _profileService;
private readonly List<ScriptingProvider> _scriptingProviders;
public ScriptingService(IPluginManagementService pluginManagementService, IProfileService profileService)
{
private readonly IPluginManagementService _pluginManagementService;
private readonly IProfileService _profileService;
private readonly List<GlobalScript> _globalScripts;
private readonly List<ScriptingProvider> _scriptingProviders;
_pluginManagementService = pluginManagementService;
_profileService = profileService;
public ScriptingService(IPluginManagementService pluginManagementService, IProfileService profileService)
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
_pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled;
_scriptingProviders = _pluginManagementService.GetFeaturesOfType<ScriptingProvider>();
_globalScripts = new List<GlobalScript>();
ScriptingProviders = new ReadOnlyCollection<ScriptingProvider>(_scriptingProviders);
GlobalScripts = new ReadOnlyCollection<GlobalScript>(_globalScripts);
// No need to sub to Deactivated, scripts will deactivate themselves
profileService.ProfileActivated += ProfileServiceOnProfileActivated;
foreach (ProfileConfiguration profileConfiguration in _profileService.ProfileConfigurations)
{
_pluginManagementService = pluginManagementService;
_profileService = profileService;
if (profileConfiguration.Profile != null)
InitializeProfileScripts(profileConfiguration.Profile);
}
}
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
_pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled;
_scriptingProviders = _pluginManagementService.GetFeaturesOfType<ScriptingProvider>();
_globalScripts = new List<GlobalScript>();
private GlobalScript CreateScriptInstance(ScriptConfiguration scriptConfiguration)
{
GlobalScript? script = null;
try
{
if (scriptConfiguration.Script != null)
throw new ArtemisCoreException("The provided script configuration already has an active script");
ScriptingProviders = new ReadOnlyCollection<ScriptingProvider>(_scriptingProviders);
GlobalScripts = new ReadOnlyCollection<GlobalScript>(_globalScripts);
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
if (provider == null)
throw new ArtemisCoreException($"Can't create script instance as there is no matching scripting provider found for the script ({scriptConfiguration.ScriptingProviderId}).");
// No need to sub to Deactivated, scripts will deactivate themselves
profileService.ProfileActivated += ProfileServiceOnProfileActivated;
script = (GlobalScript) provider.Plugin.Kernel!.Get(
provider.GlobalScriptType,
CreateScriptConstructorArgument(provider.GlobalScriptType, scriptConfiguration)
);
foreach (ProfileConfiguration profileConfiguration in _profileService.ProfileConfigurations)
script.ScriptingProvider = provider;
script.ScriptingService = this;
scriptConfiguration.Script = script;
provider.InternalScripts.Add(script);
return script;
}
catch (Exception e)
{
script?.Dispose();
throw new ArtemisCoreException("Failed to initialize global script", e);
}
}
private ProfileScript CreateScriptInstance(ScriptConfiguration scriptConfiguration, Profile profile)
{
ProfileScript? script = null;
try
{
if (scriptConfiguration.Script != null)
throw new ArtemisCoreException("The provided script configuration already has an active script");
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
if (provider == null)
throw new ArtemisCoreException($"Can't create script instance as there is no matching scripting provider found for the script ({scriptConfiguration.ScriptingProviderId}).");
script = (ProfileScript) provider.Plugin.Kernel!.Get(
provider.ProfileScriptType,
CreateScriptConstructorArgument(provider.ProfileScriptType, profile),
CreateScriptConstructorArgument(provider.ProfileScriptType, scriptConfiguration)
);
script.ScriptingProvider = provider;
scriptConfiguration.Script = script;
provider.InternalScripts.Add(script);
lock (profile)
{
if (profileConfiguration.Profile != null)
InitializeProfileScripts(profileConfiguration.Profile);
profile.AddScript(script);
}
}
/// <inheritdoc />
public ReadOnlyCollection<ScriptingProvider> ScriptingProviders { get; }
/// <inheritdoc />
public ReadOnlyCollection<GlobalScript> GlobalScripts { get; }
/// <inheritdoc />
public ProfileScript AddScript(ScriptConfiguration scriptConfiguration, Profile profile)
{
profile.AddScriptConfiguration(scriptConfiguration);
return CreateScriptInstance(scriptConfiguration, profile);
return script;
}
/// <inheritdoc />
public void RemoveScript(ScriptConfiguration scriptConfiguration, Profile profile)
catch (Exception e)
{
profile.RemoveScriptConfiguration(scriptConfiguration);
}
/// <inheritdoc />
public GlobalScript AddScript(ScriptConfiguration scriptConfiguration)
{
throw new NotImplementedException("Global scripts are not yet implemented.");
}
/// <inheritdoc />
public void RemoveScript(ScriptConfiguration scriptConfiguration)
{
throw new NotImplementedException("Global scripts are not yet implemented.");
}
private GlobalScript CreateScriptInstance(ScriptConfiguration scriptConfiguration)
{
GlobalScript? script = null;
try
// If something went wrong but the script was created, clean up as best we can
if (script != null)
{
if (scriptConfiguration.Script != null)
throw new ArtemisCoreException("The provided script configuration already has an active script");
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
if (provider == null)
throw new ArtemisCoreException($"Can't create script instance as there is no matching scripting provider found for the script ({scriptConfiguration.ScriptingProviderId}).");
script = (GlobalScript) provider.Plugin.Kernel!.Get(
provider.GlobalScriptType,
CreateScriptConstructorArgument(provider.GlobalScriptType, scriptConfiguration)
);
script.ScriptingProvider = provider;
script.ScriptingService = this;
scriptConfiguration.Script = script;
provider.InternalScripts.Add(script);
return script;
if (profile.Scripts.Contains(script))
profile.RemoveScript(script);
else
script.Dispose();
}
catch (Exception e)
{
script?.Dispose();
throw new ArtemisCoreException("Failed to initialize global script", e);
}
}
private ProfileScript CreateScriptInstance(ScriptConfiguration scriptConfiguration, Profile profile)
throw new ArtemisCoreException("Failed to initialize profile script", e);
}
}
private ConstructorArgument CreateScriptConstructorArgument(Type scriptType, object value)
{
// Limit to one constructor, there's no need to have more and it complicates things anyway
ConstructorInfo[] constructors = scriptType.GetConstructors();
if (constructors.Length != 1)
throw new ArtemisCoreException("Scripts must have exactly one constructor");
// Find the ScriptConfiguration parameter, it is required by the base constructor so its there for sure
ParameterInfo configurationParameter = constructors.First().GetParameters().First(p => value.GetType().IsAssignableFrom(p.ParameterType));
if (configurationParameter.Name == null)
throw new ArtemisCoreException($"Couldn't find a valid constructor argument on {scriptType.Name} with type {value.GetType().Name}");
return new ConstructorArgument(configurationParameter.Name, value);
}
private void InitializeProfileScripts(Profile profile)
{
// Initialize the scripts on the profile
foreach (ScriptConfiguration scriptConfiguration in profile.ScriptConfigurations.Where(c => c.Script == null && _scriptingProviders.Any(p => p.Id == c.ScriptingProviderId)))
CreateScriptInstance(scriptConfiguration, profile);
}
private void PluginManagementServiceOnPluginFeatureToggled(object? sender, PluginFeatureEventArgs e)
{
_scriptingProviders.Clear();
_scriptingProviders.AddRange(_pluginManagementService.GetFeaturesOfType<ScriptingProvider>());
foreach (ProfileConfiguration profileConfiguration in _profileService.ProfileConfigurations)
{
ProfileScript? script = null;
try
{
if (scriptConfiguration.Script != null)
throw new ArtemisCoreException("The provided script configuration already has an active script");
ScriptingProvider? provider = _scriptingProviders.FirstOrDefault(p => p.Id == scriptConfiguration.ScriptingProviderId);
if (provider == null)
throw new ArtemisCoreException($"Can't create script instance as there is no matching scripting provider found for the script ({scriptConfiguration.ScriptingProviderId}).");
script = (ProfileScript) provider.Plugin.Kernel!.Get(
provider.ProfileScriptType,
CreateScriptConstructorArgument(provider.ProfileScriptType, profile),
CreateScriptConstructorArgument(provider.ProfileScriptType, scriptConfiguration)
);
script.ScriptingProvider = provider;
scriptConfiguration.Script = script;
provider.InternalScripts.Add(script);
lock (profile)
{
profile.AddScript(script);
}
return script;
}
catch (Exception e)
{
// If something went wrong but the script was created, clean up as best we can
if (script != null)
{
if (profile.Scripts.Contains(script))
profile.RemoveScript(script);
else
script.Dispose();
}
throw new ArtemisCoreException("Failed to initialize profile script", e);
}
if (profileConfiguration.Profile != null)
InitializeProfileScripts(profileConfiguration.Profile);
}
private ConstructorArgument CreateScriptConstructorArgument(Type scriptType, object value)
{
// Limit to one constructor, there's no need to have more and it complicates things anyway
ConstructorInfo[] constructors = scriptType.GetConstructors();
if (constructors.Length != 1)
throw new ArtemisCoreException("Scripts must have exactly one constructor");
}
// Find the ScriptConfiguration parameter, it is required by the base constructor so its there for sure
ParameterInfo configurationParameter = constructors.First().GetParameters().First(p => value.GetType().IsAssignableFrom(p.ParameterType));
private void ProfileServiceOnProfileActivated(object? sender, ProfileConfigurationEventArgs e)
{
if (e.ProfileConfiguration.Profile != null)
InitializeProfileScripts(e.ProfileConfiguration.Profile);
}
if (configurationParameter.Name == null)
throw new ArtemisCoreException($"Couldn't find a valid constructor argument on {scriptType.Name} with type {value.GetType().Name}");
return new ConstructorArgument(configurationParameter.Name, value);
}
/// <inheritdoc />
public ReadOnlyCollection<ScriptingProvider> ScriptingProviders { get; }
private void InitializeProfileScripts(Profile profile)
{
// Initialize the scripts on the profile
foreach (ScriptConfiguration scriptConfiguration in profile.ScriptConfigurations.Where(c => c.Script == null && _scriptingProviders.Any(p => p.Id == c.ScriptingProviderId)))
CreateScriptInstance(scriptConfiguration, profile);
}
/// <inheritdoc />
public ReadOnlyCollection<GlobalScript> GlobalScripts { get; }
#region Event handlers
/// <inheritdoc />
public ProfileScript AddScript(ScriptConfiguration scriptConfiguration, Profile profile)
{
profile.AddScriptConfiguration(scriptConfiguration);
return CreateScriptInstance(scriptConfiguration, profile);
}
private void PluginManagementServiceOnPluginFeatureToggled(object? sender, PluginFeatureEventArgs e)
{
_scriptingProviders.Clear();
_scriptingProviders.AddRange(_pluginManagementService.GetFeaturesOfType<ScriptingProvider>());
/// <inheritdoc />
public void RemoveScript(ScriptConfiguration scriptConfiguration, Profile profile)
{
profile.RemoveScriptConfiguration(scriptConfiguration);
}
foreach (ProfileConfiguration profileConfiguration in _profileService.ProfileConfigurations)
{
if (profileConfiguration.Profile != null)
InitializeProfileScripts(profileConfiguration.Profile);
}
}
/// <inheritdoc />
public GlobalScript AddScript(ScriptConfiguration scriptConfiguration)
{
throw new NotImplementedException("Global scripts are not yet implemented.");
}
private void ProfileServiceOnProfileActivated(object? sender, ProfileConfigurationEventArgs e)
{
if (e.ProfileConfiguration.Profile != null)
InitializeProfileScripts(e.ProfileConfiguration.Profile);
}
#endregion
/// <inheritdoc />
public void RemoveScript(ScriptConfiguration scriptConfiguration)
{
throw new NotImplementedException("Global scripts are not yet implemented.");
}
}
+37 -38
View File
@@ -1,47 +1,46 @@
using Artemis.Storage.Repositories.Interfaces;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <inheritdoc />
internal class SettingsService : ISettingsService
{
/// <inheritdoc />
internal class SettingsService : ISettingsService
private readonly PluginSettings _pluginSettings;
public SettingsService(IPluginRepository pluginRepository)
{
private readonly PluginSettings _pluginSettings;
public SettingsService(IPluginRepository pluginRepository)
{
_pluginSettings = new PluginSettings(Constants.CorePlugin, pluginRepository);
}
public PluginSetting<T> GetSetting<T>(string name, T? defaultValue = default)
{
return _pluginSettings.GetSetting(name, defaultValue);
}
/// <inheritdoc />
public void SaveAllSettings()
{
_pluginSettings.SaveAllSettings();
}
_pluginSettings = new PluginSettings(Constants.CorePlugin, pluginRepository);
}
public PluginSetting<T> GetSetting<T>(string name, T? defaultValue = default)
{
return _pluginSettings.GetSetting(name, defaultValue);
}
/// <inheritdoc />
public void SaveAllSettings()
{
_pluginSettings.SaveAllSettings();
}
}
/// <summary>
/// <para>A wrapper around plugin settings for miscellaneous use outside plugins</para>
/// <para>Do not inject into a plugin, for plugins inject <see cref="PluginSettings" /> instead.</para>
/// </summary>
public interface ISettingsService : IProtectedArtemisService
{
/// <summary>
/// Gets the setting with the provided name. If the setting does not exist yet, it is created.
/// </summary>
/// <typeparam name="T">The type of the setting, can be any serializable type</typeparam>
/// <param name="name">The name of the setting</param>
/// <param name="defaultValue">The default value to use if the setting does not exist yet</param>
/// <returns></returns>
PluginSetting<T> GetSetting<T>(string name, T? defaultValue = default);
/// <summary>
/// <para>A wrapper around plugin settings for miscellaneous use outside plugins</para>
/// <para>Do not inject into a plugin, for plugins inject <see cref="PluginSettings" /> instead.</para>
/// Saves all settings, obviously
/// </summary>
public interface ISettingsService : IProtectedArtemisService
{
/// <summary>
/// Gets the setting with the provided name. If the setting does not exist yet, it is created.
/// </summary>
/// <typeparam name="T">The type of the setting, can be any serializable type</typeparam>
/// <param name="name">The name of the setting</param>
/// <param name="defaultValue">The default value to use if the setting does not exist yet</param>
/// <returns></returns>
PluginSetting<T> GetSetting<T>(string name, T? defaultValue = default);
/// <summary>
/// Saves all settings, obviously
/// </summary>
void SaveAllSettings();
}
void SaveAllSettings();
}
@@ -3,159 +3,158 @@ using System.Collections.ObjectModel;
using Newtonsoft.Json;
using SkiaSharp;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Provides access to profile storage and is responsible for activating default profiles
/// </summary>
public interface IProfileService : IArtemisService
{
/// <summary>
/// Provides access to profile storage and is responsible for activating default profiles
/// Gets the JSON serializer settings used to import/export profiles
/// </summary>
public interface IProfileService : IArtemisService
{
/// <summary>
/// Gets the JSON serializer settings used to import/export profiles
/// </summary>
public static JsonSerializerSettings ExportSettings { get; } = new() {TypeNameHandling = TypeNameHandling.All, Formatting = Formatting.Indented};
public static JsonSerializerSettings ExportSettings { get; } = new() {TypeNameHandling = TypeNameHandling.All, Formatting = Formatting.Indented};
/// <summary>
/// Gets a read only collection containing all the profile categories
/// </summary>
ReadOnlyCollection<ProfileCategory> ProfileCategories { get; }
/// <summary>
/// Gets a read only collection containing all the profile categories
/// </summary>
ReadOnlyCollection<ProfileCategory> ProfileCategories { get; }
/// <summary>
/// Gets a read only collection containing all the profile configurations
/// </summary>
ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations { get; }
/// <summary>
/// Gets a read only collection containing all the profile configurations
/// </summary>
ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations { get; }
/// <summary>
/// Gets or sets a boolean indicating whether hotkeys are enabled
/// </summary>
bool HotkeysEnabled { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether hotkeys are enabled
/// </summary>
bool HotkeysEnabled { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether rendering should only be done for profiles being edited
/// </summary>
bool RenderForEditor { get; set; }
/// <summary>
/// Gets or sets the profile element to focus on while rendering for the editor
/// </summary>
ProfileElement? EditorFocus { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether rendering should only be done for profiles being edited
/// </summary>
bool RenderForEditor { get; set; }
/// <summary>
/// Activates the profile of the given <see cref="ProfileConfiguration" /> with the currently active surface
/// </summary>
/// <param name="profileConfiguration">The profile configuration of the profile to activate</param>
Profile ActivateProfile(ProfileConfiguration profileConfiguration);
/// <summary>
/// Gets or sets the profile element to focus on while rendering for the editor
/// </summary>
ProfileElement? EditorFocus { get; set; }
/// <summary>
/// Deactivates the profile of the given <see cref="ProfileConfiguration" /> with the currently active surface
/// </summary>
/// <param name="profileConfiguration">The profile configuration of the profile to activate</param>
void DeactivateProfile(ProfileConfiguration profileConfiguration);
/// <summary>
/// Activates the profile of the given <see cref="ProfileConfiguration" /> with the currently active surface
/// </summary>
/// <param name="profileConfiguration">The profile configuration of the profile to activate</param>
Profile ActivateProfile(ProfileConfiguration profileConfiguration);
/// <summary>
/// Permanently deletes the profile of the given <see cref="ProfileConfiguration" />
/// </summary>
/// <param name="profileConfiguration">The profile configuration of the profile to delete</param>
void DeleteProfile(ProfileConfiguration profileConfiguration);
/// <summary>
/// Deactivates the profile of the given <see cref="ProfileConfiguration" /> with the currently active surface
/// </summary>
/// <param name="profileConfiguration">The profile configuration of the profile to activate</param>
void DeactivateProfile(ProfileConfiguration profileConfiguration);
/// <summary>
/// Saves the provided <see cref="ProfileCategory" /> and it's <see cref="ProfileConfiguration" />s but not the
/// <see cref="Profile" />s themselves
/// </summary>
/// <param name="profileCategory">The profile category to update</param>
void SaveProfileCategory(ProfileCategory profileCategory);
/// <summary>
/// Permanently deletes the profile of the given <see cref="ProfileConfiguration" />
/// </summary>
/// <param name="profileConfiguration">The profile configuration of the profile to delete</param>
void DeleteProfile(ProfileConfiguration profileConfiguration);
/// <summary>
/// Creates a new profile category and saves it to persistent storage
/// </summary>
/// <param name="name">The name of the new profile category, must be unique</param>
/// <returns>The newly created profile category</returns>
ProfileCategory CreateProfileCategory(string name);
/// <summary>
/// Saves the provided <see cref="ProfileCategory" /> and it's <see cref="ProfileConfiguration" />s but not the
/// <see cref="Profile" />s themselves
/// </summary>
/// <param name="profileCategory">The profile category to update</param>
void SaveProfileCategory(ProfileCategory profileCategory);
/// <summary>
/// Permanently deletes the provided profile category
/// </summary>
void DeleteProfileCategory(ProfileCategory profileCategory);
/// <summary>
/// Creates a new profile category and saves it to persistent storage
/// </summary>
/// <param name="name">The name of the new profile category, must be unique</param>
/// <returns>The newly created profile category</returns>
ProfileCategory CreateProfileCategory(string name);
/// <summary>
/// Creates a new profile configuration and adds it to the provided <see cref="ProfileCategory" />
/// </summary>
/// <param name="category">The profile category to add the profile to</param>
/// <param name="name">The name of the new profile configuration</param>
/// <param name="icon">The icon of the new profile configuration</param>
/// <returns>The newly created profile configuration</returns>
ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, string name, string icon);
/// <summary>
/// Permanently deletes the provided profile category
/// </summary>
void DeleteProfileCategory(ProfileCategory profileCategory);
/// <summary>
/// Removes the provided profile configuration from the <see cref="ProfileCategory" />
/// </summary>
/// <param name="profileConfiguration"></param>
void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration);
/// <summary>
/// Creates a new profile configuration and adds it to the provided <see cref="ProfileCategory" />
/// </summary>
/// <param name="category">The profile category to add the profile to</param>
/// <param name="name">The name of the new profile configuration</param>
/// <param name="icon">The icon of the new profile configuration</param>
/// <returns>The newly created profile configuration</returns>
ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, string name, string icon);
/// <summary>
/// Loads the icon of this profile configuration if needed and puts it into <c>ProfileConfiguration.Icon.FileIcon</c>
/// </summary>
void LoadProfileConfigurationIcon(ProfileConfiguration profileConfiguration);
/// <summary>
/// Removes the provided profile configuration from the <see cref="ProfileCategory" />
/// </summary>
/// <param name="profileConfiguration"></param>
void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration);
/// <summary>
/// Saves the current icon of this profile
/// </summary>
void SaveProfileConfigurationIcon(ProfileConfiguration profileConfiguration);
/// <summary>
/// Writes the profile to persistent storage
/// </summary>
/// <param name="profile"></param>
/// <param name="includeChildren"></param>
void SaveProfile(Profile profile, bool includeChildren);
/// <summary>
/// Exports the profile described in the given <see cref="ProfileConfiguration" /> into an export model
/// </summary>
/// <param name="profileConfiguration">The profile configuration of the profile to export</param>
/// <returns>The resulting export model</returns>
ProfileConfigurationExportModel ExportProfile(ProfileConfiguration profileConfiguration);
/// <summary>
/// Loads the icon of this profile configuration if needed and puts it into <c>ProfileConfiguration.Icon.FileIcon</c>
/// </summary>
void LoadProfileConfigurationIcon(ProfileConfiguration profileConfiguration);
/// <summary>
/// Imports the provided base64 encoded GZIPed JSON as a profile configuration
/// </summary>
/// <param name="category">The <see cref="ProfileCategory" /> in which to import the profile</param>
/// <param name="exportModel">The model containing the profile to import</param>
/// <param name="makeUnique">Whether or not to give the profile a new GUID, making it unique</param>
/// <param name="markAsFreshImport">
/// Whether or not to mark the profile as a fresh import, causing it to be adapted until
/// any changes are made to it
/// </param>
/// <param name="nameAffix">Text to add after the name of the profile (separated by a dash)</param>
/// <returns>The resulting profile configuration</returns>
ProfileConfiguration ImportProfile(ProfileCategory category, ProfileConfigurationExportModel exportModel, bool makeUnique = true, bool markAsFreshImport = true,
string? nameAffix = "imported");
/// <summary>
/// Saves the current icon of this profile
/// </summary>
void SaveProfileConfigurationIcon(ProfileConfiguration profileConfiguration);
/// <summary>
/// Adapts a given profile to the currently active devices
/// </summary>
/// <param name="profile">The profile to adapt</param>
void AdaptProfile(Profile profile);
/// <summary>
/// Writes the profile to persistent storage
/// </summary>
/// <param name="profile"></param>
/// <param name="includeChildren"></param>
void SaveProfile(Profile profile, bool includeChildren);
/// <summary>
/// Updates all currently active profiles
/// </summary>
void UpdateProfiles(double deltaTime);
/// <summary>
/// Exports the profile described in the given <see cref="ProfileConfiguration" /> into an export model
/// </summary>
/// <param name="profileConfiguration">The profile configuration of the profile to export</param>
/// <returns>The resulting export model</returns>
ProfileConfigurationExportModel ExportProfile(ProfileConfiguration profileConfiguration);
/// <summary>
/// Renders all currently active profiles
/// </summary>
/// <param name="canvas"></param>
void RenderProfiles(SKCanvas canvas);
/// <summary>
/// Imports the provided base64 encoded GZIPed JSON as a profile configuration
/// </summary>
/// <param name="category">The <see cref="ProfileCategory" /> in which to import the profile</param>
/// <param name="exportModel">The model containing the profile to import</param>
/// <param name="makeUnique">Whether or not to give the profile a new GUID, making it unique</param>
/// <param name="markAsFreshImport">
/// Whether or not to mark the profile as a fresh import, causing it to be adapted until
/// any changes are made to it
/// </param>
/// <param name="nameAffix">Text to add after the name of the profile (separated by a dash)</param>
/// <returns>The resulting profile configuration</returns>
ProfileConfiguration ImportProfile(ProfileCategory category, ProfileConfigurationExportModel exportModel, bool makeUnique = true, bool markAsFreshImport = true,
string? nameAffix = "imported");
/// <summary>
/// Occurs whenever a profile has been activated
/// </summary>
public event EventHandler<ProfileConfigurationEventArgs>? ProfileActivated;
/// <summary>
/// Adapts a given profile to the currently active devices
/// </summary>
/// <param name="profile">The profile to adapt</param>
void AdaptProfile(Profile profile);
/// <summary>
/// Occurs whenever a profile has been deactivated
/// </summary>
public event EventHandler<ProfileConfigurationEventArgs>? ProfileDeactivated;
}
/// <summary>
/// Updates all currently active profiles
/// </summary>
void UpdateProfiles(double deltaTime);
/// <summary>
/// Renders all currently active profiles
/// </summary>
/// <param name="canvas"></param>
void RenderProfiles(SKCanvas canvas);
/// <summary>
/// Occurs whenever a profile has been activated
/// </summary>
public event EventHandler<ProfileConfigurationEventArgs>? ProfileActivated;
/// <summary>
/// Occurs whenever a profile has been deactivated
/// </summary>
public event EventHandler<ProfileConfigurationEventArgs>? ProfileDeactivated;
}
@@ -2,118 +2,113 @@
using System.Linq;
using RGB.NET.Core;
namespace Artemis.Core.Services.Models
namespace Artemis.Core.Services.Models;
internal class SurfaceArrangement
{
internal class SurfaceArrangement
public SurfaceArrangement()
{
public SurfaceArrangement()
Types = new List<SurfaceArrangementType>();
ArrangedDevices = new List<ArtemisDevice>();
}
public List<SurfaceArrangementType> Types { get; }
public List<ArtemisDevice> ArrangedDevices { get; }
public void Arrange(List<ArtemisDevice> devices)
{
ArrangedDevices.Clear();
// Not much to do here
if (!devices.Any())
return;
foreach (ArtemisDevice surfaceDevice in devices)
{
Types = new List<SurfaceArrangementType>();
ArrangedDevices = new List<ArtemisDevice>();
surfaceDevice.X = 0;
surfaceDevice.Y = 0;
surfaceDevice.ApplyToRgbDevice();
}
public List<SurfaceArrangementType> Types { get; }
public List<ArtemisDevice> ArrangedDevices { get; }
internal static SurfaceArrangement GetDefaultArrangement()
{
SurfaceArrangement arrangement = new();
SurfaceArrangementType keypad = arrangement.AddType(RGBDeviceType.Keypad, 1);
keypad.AddConfiguration(new SurfaceArrangementConfiguration(null, HorizontalArrangementPosition.Equal, VerticalArrangementPosition.Equal, 20));
SurfaceArrangementType keyboard = arrangement.AddType(RGBDeviceType.Keyboard, 1);
keyboard.AddConfiguration(new SurfaceArrangementConfiguration(keypad, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 20));
SurfaceArrangementType mousepad = arrangement.AddType(RGBDeviceType.Mousepad, 1);
mousepad.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 10));
SurfaceArrangementType mouse = arrangement.AddType(RGBDeviceType.Mouse, 2);
mouse.AddConfiguration(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0));
mouse.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Center, 100));
SurfaceArrangementType headset = arrangement.AddType(RGBDeviceType.Headset, 1);
headset.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Bottom, 100));
SurfaceArrangementType headsetStand = arrangement.AddType(RGBDeviceType.HeadsetStand, 1);
headsetStand.AddConfiguration(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Top, 100));
headsetStand.AddConfiguration(new SurfaceArrangementConfiguration(mouse, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Top, 100));
headsetStand.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Top, 100));
SurfaceArrangementType mainboard = arrangement.AddType(RGBDeviceType.Mainboard, 1);
mainboard.AddConfiguration(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Bottom, 500));
mainboard.AddConfiguration(new SurfaceArrangementConfiguration(mouse, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Bottom, 500));
mainboard.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Bottom, 500));
SurfaceArrangementType cooler = arrangement.AddType(RGBDeviceType.Cooler, 2);
cooler.AddConfiguration(new SurfaceArrangementConfiguration(mainboard, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0));
SurfaceArrangementType dram = arrangement.AddType(RGBDeviceType.DRAM, 2);
dram.AddConfiguration(new SurfaceArrangementConfiguration(cooler, HorizontalArrangementPosition.Left, VerticalArrangementPosition.Equal, 10));
dram.AddConfiguration(new SurfaceArrangementConfiguration(mainboard, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0));
SurfaceArrangementType graphicsCard = arrangement.AddType(RGBDeviceType.GraphicsCard, 2);
graphicsCard.AddConfiguration(new SurfaceArrangementConfiguration(cooler, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Bottom, 10));
graphicsCard.AddConfiguration(new SurfaceArrangementConfiguration(dram, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Bottom, 10));
graphicsCard.AddConfiguration(new SurfaceArrangementConfiguration(mainboard, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0));
SurfaceArrangementType fan = arrangement.AddType(RGBDeviceType.Fan, 2);
fan.AddConfiguration(new SurfaceArrangementConfiguration(mainboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 100));
SurfaceArrangementType ledStripe = arrangement.AddType(RGBDeviceType.LedStripe, 2);
ledStripe.AddConfiguration(new SurfaceArrangementConfiguration(fan, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 100));
arrangement.AddType(RGBDeviceType.Speaker, 1);
arrangement.AddType(RGBDeviceType.None, 1);
return arrangement;
}
private SurfaceArrangementType AddType(RGBDeviceType type, int zIndex)
{
SurfaceArrangementType surfaceArrangementType = new(this, type, zIndex);
Types.Add(surfaceArrangementType);
return surfaceArrangementType;
}
public void Arrange(List<ArtemisDevice> devices)
{
ArrangedDevices.Clear();
// Not much to do here
if (!devices.Any())
return;
foreach (SurfaceArrangementType surfaceArrangementType in Types)
surfaceArrangementType.Arrange(devices);
// See if we need to move the surface to keep X and Y values positive
float x = devices.Min(d => d.RgbDevice.Location.X);
float y = devices.Min(d => d.RgbDevice.Location.Y);
if (x < 0)
foreach (ArtemisDevice surfaceDevice in devices)
{
surfaceDevice.X = 0;
surfaceDevice.Y = 0;
surfaceDevice.X += x * -1;
surfaceDevice.ApplyToRgbDevice();
}
foreach (SurfaceArrangementType surfaceArrangementType in Types)
surfaceArrangementType.Arrange(devices);
// See if we need to move the surface to keep X and Y values positive
float x = devices.Min(d => d.RgbDevice.Location.X);
float y = devices.Min(d => d.RgbDevice.Location.Y);
if (x < 0)
if (y < 0)
foreach (ArtemisDevice surfaceDevice in devices)
{
foreach (ArtemisDevice surfaceDevice in devices)
{
surfaceDevice.X += x * -1;
surfaceDevice.ApplyToRgbDevice();
}
surfaceDevice.Y += y * -1;
surfaceDevice.ApplyToRgbDevice();
}
}
if (y < 0)
{
foreach (ArtemisDevice surfaceDevice in devices)
{
surfaceDevice.Y += y * -1;
surfaceDevice.ApplyToRgbDevice();
}
}
}
internal static SurfaceArrangement GetDefaultArrangement()
{
SurfaceArrangement arrangement = new();
SurfaceArrangementType keypad = arrangement.AddType(RGBDeviceType.Keypad, 1);
keypad.AddConfiguration(new SurfaceArrangementConfiguration(null, HorizontalArrangementPosition.Equal, VerticalArrangementPosition.Equal, 20));
SurfaceArrangementType keyboard = arrangement.AddType(RGBDeviceType.Keyboard, 1);
keyboard.AddConfiguration(new SurfaceArrangementConfiguration(keypad, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 20));
SurfaceArrangementType mousepad = arrangement.AddType(RGBDeviceType.Mousepad, 1);
mousepad.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 10));
SurfaceArrangementType mouse = arrangement.AddType(RGBDeviceType.Mouse, 2);
mouse.AddConfiguration(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0));
mouse.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Center, 100));
SurfaceArrangementType headset = arrangement.AddType(RGBDeviceType.Headset, 1);
headset.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Bottom, 100));
SurfaceArrangementType headsetStand = arrangement.AddType(RGBDeviceType.HeadsetStand, 1);
headsetStand.AddConfiguration(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Top, 100));
headsetStand.AddConfiguration(new SurfaceArrangementConfiguration(mouse, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Top, 100));
headsetStand.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Top, 100));
SurfaceArrangementType mainboard = arrangement.AddType(RGBDeviceType.Mainboard, 1);
mainboard.AddConfiguration(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Bottom, 500));
mainboard.AddConfiguration(new SurfaceArrangementConfiguration(mouse, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Bottom, 500));
mainboard.AddConfiguration(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Bottom, 500));
SurfaceArrangementType cooler = arrangement.AddType(RGBDeviceType.Cooler, 2);
cooler.AddConfiguration(new SurfaceArrangementConfiguration(mainboard, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0));
SurfaceArrangementType dram = arrangement.AddType(RGBDeviceType.DRAM, 2);
dram.AddConfiguration(new SurfaceArrangementConfiguration(cooler, HorizontalArrangementPosition.Left, VerticalArrangementPosition.Equal, 10));
dram.AddConfiguration(new SurfaceArrangementConfiguration(mainboard, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0));
SurfaceArrangementType graphicsCard = arrangement.AddType(RGBDeviceType.GraphicsCard, 2);
graphicsCard.AddConfiguration(new SurfaceArrangementConfiguration(cooler, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Bottom, 10));
graphicsCard.AddConfiguration(new SurfaceArrangementConfiguration(dram, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Bottom, 10));
graphicsCard.AddConfiguration(new SurfaceArrangementConfiguration(mainboard, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0));
SurfaceArrangementType fan = arrangement.AddType(RGBDeviceType.Fan, 2);
fan.AddConfiguration(new SurfaceArrangementConfiguration(mainboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 100));
SurfaceArrangementType ledStripe = arrangement.AddType(RGBDeviceType.LedStripe, 2);
ledStripe.AddConfiguration(new SurfaceArrangementConfiguration(fan, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 100));
arrangement.AddType(RGBDeviceType.Speaker, 1);
arrangement.AddType(RGBDeviceType.None, 1);
return arrangement;
}
private SurfaceArrangementType AddType(RGBDeviceType type, int zIndex)
{
SurfaceArrangementType surfaceArrangementType = new(this, type, zIndex);
Types.Add(surfaceArrangementType);
return surfaceArrangementType;
}
}
@@ -2,91 +2,90 @@
using System.Linq;
using RGB.NET.Core;
namespace Artemis.Core.Services.Models
namespace Artemis.Core.Services.Models;
internal class SurfaceArrangementConfiguration
{
internal class SurfaceArrangementConfiguration
public SurfaceArrangementConfiguration(SurfaceArrangementType? anchor, HorizontalArrangementPosition horizontalPosition, VerticalArrangementPosition verticalPosition,
int margin)
{
public SurfaceArrangementConfiguration(SurfaceArrangementType? anchor, HorizontalArrangementPosition horizontalPosition, VerticalArrangementPosition verticalPosition,
int margin)
Anchor = anchor;
HorizontalPosition = horizontalPosition;
VerticalPosition = verticalPosition;
MarginLeft = margin;
MarginTop = margin;
MarginRight = margin;
MarginBottom = margin;
SurfaceArrangement = null!;
}
public SurfaceArrangementType? Anchor { get; }
public HorizontalArrangementPosition HorizontalPosition { get; }
public VerticalArrangementPosition VerticalPosition { get; }
public int MarginLeft { get; }
public int MarginTop { get; }
public int MarginRight { get; }
public int MarginBottom { get; }
public SurfaceArrangement SurfaceArrangement { get; set; }
public bool Apply(List<ArtemisDevice> devices)
{
if (Anchor != null && !Anchor.HasDevices(devices))
return false;
// Start at the edge of the anchor, if there is no anchor start at any device
Point startPoint = Anchor?.GetEdge(HorizontalPosition, VerticalPosition) ??
new SurfaceArrangementType(SurfaceArrangement, RGBDeviceType.All, 1).GetEdge(HorizontalPosition, VerticalPosition);
// Stack multiple devices of the same type vertically if they are wider than they are tall
bool stackVertically = devices.Average(d => d.RgbDevice.Size.Width) >= devices.Average(d => d.RgbDevice.Size.Height);
ArtemisDevice? previous = null;
foreach (ArtemisDevice artemisDevice in devices)
{
Anchor = anchor;
HorizontalPosition = horizontalPosition;
VerticalPosition = verticalPosition;
MarginLeft = margin;
MarginTop = margin;
MarginRight = margin;
MarginBottom = margin;
SurfaceArrangement = null!;
}
public SurfaceArrangementType? Anchor { get; }
public HorizontalArrangementPosition HorizontalPosition { get; }
public VerticalArrangementPosition VerticalPosition { get; }
public int MarginLeft { get; }
public int MarginTop { get; }
public int MarginRight { get; }
public int MarginBottom { get; }
public SurfaceArrangement SurfaceArrangement { get; set; }
public bool Apply(List<ArtemisDevice> devices)
{
if (Anchor != null && !Anchor.HasDevices(devices))
return false;
// Start at the edge of the anchor, if there is no anchor start at any device
Point startPoint = Anchor?.GetEdge(HorizontalPosition, VerticalPosition) ??
new SurfaceArrangementType(SurfaceArrangement, RGBDeviceType.All, 1).GetEdge(HorizontalPosition, VerticalPosition);
// Stack multiple devices of the same type vertically if they are wider than they are tall
bool stackVertically = devices.Average(d => d.RgbDevice.Size.Width) >= devices.Average(d => d.RgbDevice.Size.Height);
ArtemisDevice? previous = null;
foreach (ArtemisDevice artemisDevice in devices)
if (previous != null)
{
if (previous != null)
if (stackVertically)
{
if (stackVertically)
{
artemisDevice.X = previous.X;
artemisDevice.Y = previous.RgbDevice.Location.Y + previous.RgbDevice.Size.Height + MarginTop / 2f;
}
else
{
artemisDevice.X = previous.RgbDevice.Location.X + previous.RgbDevice.Size.Width + MarginLeft / 2f;
artemisDevice.Y = previous.Y;
}
artemisDevice.X = previous.X;
artemisDevice.Y = previous.RgbDevice.Location.Y + previous.RgbDevice.Size.Height + MarginTop / 2f;
}
else
{
artemisDevice.X = HorizontalPosition switch
{
HorizontalArrangementPosition.Left => startPoint.X - artemisDevice.RgbDevice.Size.Width - MarginRight,
HorizontalArrangementPosition.Right => startPoint.X + MarginLeft,
HorizontalArrangementPosition.Center => startPoint.X - artemisDevice.RgbDevice.Size.Width / 2f,
HorizontalArrangementPosition.Equal => startPoint.X,
_ => artemisDevice.X
};
artemisDevice.Y = VerticalPosition switch
{
VerticalArrangementPosition.Top => startPoint.Y - artemisDevice.RgbDevice.Size.Height - MarginBottom,
VerticalArrangementPosition.Bottom => startPoint.Y + MarginTop,
VerticalArrangementPosition.Center => startPoint.Y - artemisDevice.RgbDevice.Size.Height / 2f,
VerticalArrangementPosition.Equal => startPoint.Y,
_ => artemisDevice.X
};
artemisDevice.X = previous.RgbDevice.Location.X + previous.RgbDevice.Size.Width + MarginLeft / 2f;
artemisDevice.Y = previous.Y;
}
artemisDevice.ApplyToRgbDevice();
previous = artemisDevice;
SurfaceArrangement.ArrangedDevices.Add(artemisDevice);
}
else
{
artemisDevice.X = HorizontalPosition switch
{
HorizontalArrangementPosition.Left => startPoint.X - artemisDevice.RgbDevice.Size.Width - MarginRight,
HorizontalArrangementPosition.Right => startPoint.X + MarginLeft,
HorizontalArrangementPosition.Center => startPoint.X - artemisDevice.RgbDevice.Size.Width / 2f,
HorizontalArrangementPosition.Equal => startPoint.X,
_ => artemisDevice.X
};
artemisDevice.Y = VerticalPosition switch
{
VerticalArrangementPosition.Top => startPoint.Y - artemisDevice.RgbDevice.Size.Height - MarginBottom,
VerticalArrangementPosition.Bottom => startPoint.Y + MarginTop,
VerticalArrangementPosition.Center => startPoint.Y - artemisDevice.RgbDevice.Size.Height / 2f,
VerticalArrangementPosition.Equal => startPoint.Y,
_ => artemisDevice.X
};
}
return true;
artemisDevice.ApplyToRgbDevice();
previous = artemisDevice;
SurfaceArrangement.ArrangedDevices.Add(artemisDevice);
}
return true;
}
}
@@ -3,105 +3,104 @@ using System.Collections.Generic;
using System.Linq;
using RGB.NET.Core;
namespace Artemis.Core.Services.Models
namespace Artemis.Core.Services.Models;
internal class SurfaceArrangementType
{
internal class SurfaceArrangementType
public SurfaceArrangementType(SurfaceArrangement surfaceArrangement, RGBDeviceType deviceType, int zIndex)
{
public SurfaceArrangementType(SurfaceArrangement surfaceArrangement, RGBDeviceType deviceType, int zIndex)
{
SurfaceArrangement = surfaceArrangement;
DeviceType = deviceType;
ZIndex = zIndex;
Configurations = new List<SurfaceArrangementConfiguration>();
}
SurfaceArrangement = surfaceArrangement;
DeviceType = deviceType;
ZIndex = zIndex;
Configurations = new List<SurfaceArrangementConfiguration>();
}
public SurfaceArrangement SurfaceArrangement { get; }
public RGBDeviceType DeviceType { get; }
public int ZIndex { get; }
public List<SurfaceArrangementConfiguration> Configurations { get; }
public SurfaceArrangementConfiguration? AppliedConfiguration { get; set; }
public SurfaceArrangement SurfaceArrangement { get; }
public RGBDeviceType DeviceType { get; }
public int ZIndex { get; }
public List<SurfaceArrangementConfiguration> Configurations { get; }
public SurfaceArrangementConfiguration? AppliedConfiguration { get; set; }
public bool HasDevices(List<ArtemisDevice> devices)
{
return devices.Any(d => d.DeviceType == DeviceType);
}
public bool HasDevices(List<ArtemisDevice> devices)
{
return devices.Any(d => d.DeviceType == DeviceType);
}
public void Arrange(List<ArtemisDevice> devices)
public void Arrange(List<ArtemisDevice> devices)
{
devices = devices.Where(d => d.DeviceType == DeviceType).ToList();
if (!devices.Any())
return;
AppliedConfiguration = null;
foreach (SurfaceArrangementConfiguration configuration in Configurations)
{
devices = devices.Where(d => d.DeviceType == DeviceType).ToList();
if (!devices.Any())
bool applied = configuration.Apply(devices);
if (applied)
{
AppliedConfiguration = configuration;
foreach (ArtemisDevice artemisDevice in devices)
artemisDevice.ZIndex = ZIndex;
return;
AppliedConfiguration = null;
foreach (SurfaceArrangementConfiguration configuration in Configurations)
{
bool applied = configuration.Apply(devices);
if (applied)
{
AppliedConfiguration = configuration;
foreach (ArtemisDevice artemisDevice in devices)
artemisDevice.ZIndex = ZIndex;
return;
}
}
// If nothing applied fall back to just basing on whatever is the furthers to the right
SurfaceArrangementConfiguration fallback = new(
null,
HorizontalArrangementPosition.Right,
VerticalArrangementPosition.Equal,
10
) {SurfaceArrangement = SurfaceArrangement};
fallback.Apply(devices);
AppliedConfiguration = fallback;
}
public Point GetEdge(HorizontalArrangementPosition horizontalPosition, VerticalArrangementPosition verticalPosition)
{
List<ArtemisDevice> devices = SurfaceArrangement.ArrangedDevices.Where(d => d.DeviceType == DeviceType || DeviceType == RGBDeviceType.All).ToList();
if (!devices.Any())
return new Point();
float x = horizontalPosition switch
{
HorizontalArrangementPosition.Left => devices.Min(d => d.RgbDevice.Location.X) - (AppliedConfiguration?.MarginLeft ?? 0.0f),
HorizontalArrangementPosition.Right => devices.Max(d => d.RgbDevice.Location.X + d.RgbDevice.Size.Width) + (AppliedConfiguration?.MarginRight ?? 0.0f),
HorizontalArrangementPosition.Center => devices.First().RgbDevice.Boundary.Center.X,
HorizontalArrangementPosition.Equal => devices.First().RgbDevice.Location.X,
_ => throw new ArgumentOutOfRangeException(nameof(horizontalPosition), horizontalPosition, null)
};
float y = verticalPosition switch
{
VerticalArrangementPosition.Top => devices.Min(d => d.RgbDevice.Location.Y) - (AppliedConfiguration?.MarginTop ?? 0.0f),
VerticalArrangementPosition.Bottom => devices.Max(d => d.RgbDevice.Location.Y + d.RgbDevice.Size.Height) + (AppliedConfiguration?.MarginBottom ?? 0.0f),
VerticalArrangementPosition.Center => devices.First().RgbDevice.Boundary.Center.Y,
VerticalArrangementPosition.Equal => devices.First().RgbDevice.Location.Y,
_ => throw new ArgumentOutOfRangeException(nameof(verticalPosition), verticalPosition, null)
};
return new Point(x, y);
}
public void AddConfiguration(SurfaceArrangementConfiguration surfaceArrangementConfiguration)
{
surfaceArrangementConfiguration.SurfaceArrangement = SurfaceArrangement;
Configurations.Add(surfaceArrangementConfiguration);
}
// If nothing applied fall back to just basing on whatever is the furthers to the right
SurfaceArrangementConfiguration fallback = new(
null,
HorizontalArrangementPosition.Right,
VerticalArrangementPosition.Equal,
10
) {SurfaceArrangement = SurfaceArrangement};
fallback.Apply(devices);
AppliedConfiguration = fallback;
}
internal enum HorizontalArrangementPosition
public Point GetEdge(HorizontalArrangementPosition horizontalPosition, VerticalArrangementPosition verticalPosition)
{
Left,
Right,
Equal,
Center
List<ArtemisDevice> devices = SurfaceArrangement.ArrangedDevices.Where(d => d.DeviceType == DeviceType || DeviceType == RGBDeviceType.All).ToList();
if (!devices.Any())
return new Point();
float x = horizontalPosition switch
{
HorizontalArrangementPosition.Left => devices.Min(d => d.RgbDevice.Location.X) - (AppliedConfiguration?.MarginLeft ?? 0.0f),
HorizontalArrangementPosition.Right => devices.Max(d => d.RgbDevice.Location.X + d.RgbDevice.Size.Width) + (AppliedConfiguration?.MarginRight ?? 0.0f),
HorizontalArrangementPosition.Center => devices.First().RgbDevice.Boundary.Center.X,
HorizontalArrangementPosition.Equal => devices.First().RgbDevice.Location.X,
_ => throw new ArgumentOutOfRangeException(nameof(horizontalPosition), horizontalPosition, null)
};
float y = verticalPosition switch
{
VerticalArrangementPosition.Top => devices.Min(d => d.RgbDevice.Location.Y) - (AppliedConfiguration?.MarginTop ?? 0.0f),
VerticalArrangementPosition.Bottom => devices.Max(d => d.RgbDevice.Location.Y + d.RgbDevice.Size.Height) + (AppliedConfiguration?.MarginBottom ?? 0.0f),
VerticalArrangementPosition.Center => devices.First().RgbDevice.Boundary.Center.Y,
VerticalArrangementPosition.Equal => devices.First().RgbDevice.Location.Y,
_ => throw new ArgumentOutOfRangeException(nameof(verticalPosition), verticalPosition, null)
};
return new Point(x, y);
}
internal enum VerticalArrangementPosition
public void AddConfiguration(SurfaceArrangementConfiguration surfaceArrangementConfiguration)
{
Top,
Bottom,
Equal,
Center
surfaceArrangementConfiguration.SurfaceArrangement = SurfaceArrangement;
Configurations.Add(surfaceArrangementConfiguration);
}
}
internal enum HorizontalArrangementPosition
{
Left,
Right,
Equal,
Center
}
internal enum VerticalArrangementPosition
{
Top,
Bottom,
Equal,
Center
}
File diff suppressed because it is too large. Load diff
@@ -5,54 +5,53 @@ using Artemis.Core.Modules;
using EmbedIO;
using Newtonsoft.Json;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Represents a plugin web endpoint receiving an object of type <typeparamref name="T" /> and returning any
/// <see cref="object" /> or <see langword="null" />.
/// <para>Note: Both will be deserialized and serialized respectively using JSON.</para>
/// </summary>
public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel, new()
{
/// <summary>
/// Represents a plugin web endpoint receiving an object of type <typeparamref name="T" /> and returning any
/// <see cref="object" /> or <see langword="null" />.
/// <para>Note: Both will be deserialized and serialized respectively using JSON.</para>
/// </summary>
public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel, new()
private readonly Module<T> _module;
internal DataModelJsonPluginEndPoint(Module<T> module, string name, PluginsModule pluginsModule) : base(module, name, pluginsModule)
{
private readonly Module<T> _module;
_module = module ?? throw new ArgumentNullException(nameof(module));
internal DataModelJsonPluginEndPoint(Module<T> module, string name, PluginsModule pluginsModule) : base(module, name, pluginsModule)
{
_module = module ?? throw new ArgumentNullException(nameof(module));
ThrowOnFail = true;
Accepts = MimeType.Json;
}
/// <summary>
/// Whether or not the end point should throw an exception if deserializing the received JSON fails.
/// If set to <see langword="false" /> malformed JSON is silently ignored; if set to <see langword="true" /> malformed
/// JSON throws a <see cref="JsonException" />.
/// </summary>
public bool ThrowOnFail { get; set; }
#region Overrides of PluginEndPoint
/// <inheritdoc />
protected override async Task ProcessRequest(IHttpContext context)
{
if (context.Request.HttpVerb != HttpVerbs.Post && context.Request.HttpVerb != HttpVerbs.Put)
throw HttpException.MethodNotAllowed("This end point only accepts POST and PUT calls");
context.Response.ContentType = MimeType.Json;
using TextReader reader = context.OpenRequestText();
try
{
JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _module.DataModel);
}
catch (JsonException)
{
if (ThrowOnFail)
throw;
}
}
#endregion
ThrowOnFail = true;
Accepts = MimeType.Json;
}
/// <summary>
/// Whether or not the end point should throw an exception if deserializing the received JSON fails.
/// If set to <see langword="false" /> malformed JSON is silently ignored; if set to <see langword="true" /> malformed
/// JSON throws a <see cref="JsonException" />.
/// </summary>
public bool ThrowOnFail { get; set; }
#region Overrides of PluginEndPoint
/// <inheritdoc />
protected override async Task ProcessRequest(IHttpContext context)
{
if (context.Request.HttpVerb != HttpVerbs.Post && context.Request.HttpVerb != HttpVerbs.Put)
throw HttpException.MethodNotAllowed("This end point only accepts POST and PUT calls");
context.Response.ContentType = MimeType.Json;
using TextReader reader = context.OpenRequestText();
try
{
JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _module.DataModel);
}
catch (JsonException)
{
if (ThrowOnFail)
throw;
}
}
#endregion
}
@@ -1,20 +1,19 @@
using System;
namespace Artemis.Core.Services
{
/// <summary>
/// Provides data about endpoint exception related events
/// </summary>
public class EndpointExceptionEventArgs : EventArgs
{
internal EndpointExceptionEventArgs(Exception exception)
{
Exception = exception;
}
namespace Artemis.Core.Services;
/// <summary>
/// Gets the exception that occurred
/// </summary>
public Exception Exception { get; }
/// <summary>
/// Provides data about endpoint exception related events
/// </summary>
public class EndpointExceptionEventArgs : EventArgs
{
internal EndpointExceptionEventArgs(Exception exception)
{
Exception = exception;
}
/// <summary>
/// Gets the exception that occurred
/// </summary>
public Exception Exception { get; }
}
@@ -1,21 +1,20 @@
using System;
using EmbedIO;
namespace Artemis.Core.Services
{
/// <summary>
/// Provides data about endpoint request related events
/// </summary>
public class EndpointRequestEventArgs : EventArgs
{
internal EndpointRequestEventArgs(IHttpContext context)
{
Context = context;
}
namespace Artemis.Core.Services;
/// <summary>
/// Gets the HTTP context of the request
/// </summary>
public IHttpContext Context { get; }
/// <summary>
/// Provides data about endpoint request related events
/// </summary>
public class EndpointRequestEventArgs : EventArgs
{
internal EndpointRequestEventArgs(IHttpContext context)
{
Context = context;
}
/// <summary>
/// Gets the HTTP context of the request
/// </summary>
public IHttpContext Context { get; }
}
@@ -4,79 +4,78 @@ using System.Threading.Tasks;
using EmbedIO;
using Newtonsoft.Json;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Represents a plugin web endpoint receiving an object of type <typeparamref name="T" /> and returning any
/// <see cref="object" /> or <see langword="null" />.
/// <para>Note: Both will be deserialized and serialized respectively using JSON.</para>
/// </summary>
public class JsonPluginEndPoint<T> : PluginEndPoint
{
/// <summary>
/// Represents a plugin web endpoint receiving an object of type <typeparamref name="T" /> and returning any
/// <see cref="object" /> or <see langword="null" />.
/// <para>Note: Both will be deserialized and serialized respectively using JSON.</para>
/// </summary>
public class JsonPluginEndPoint<T> : PluginEndPoint
private readonly Action<T>? _requestHandler;
private readonly Func<T, object?>? _responseRequestHandler;
internal JsonPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Action<T> requestHandler) : base(pluginFeature, name, pluginsModule)
{
private readonly Action<T>? _requestHandler;
private readonly Func<T, object?>? _responseRequestHandler;
internal JsonPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Action<T> requestHandler) : base(pluginFeature, name, pluginsModule)
{
_requestHandler = requestHandler;
ThrowOnFail = true;
Accepts = MimeType.Json;
}
internal JsonPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Func<T, object?> responseRequestHandler) : base(pluginFeature, name, pluginsModule)
{
_responseRequestHandler = responseRequestHandler;
ThrowOnFail = true;
Accepts = MimeType.Json;
Returns = MimeType.Json;
}
/// <summary>
/// Whether or not the end point should throw an exception if deserializing the received JSON fails.
/// If set to <see langword="false" /> malformed JSON is silently ignored; if set to <see langword="true" /> malformed
/// JSON throws a <see cref="JsonException" />.
/// </summary>
public bool ThrowOnFail { get; set; }
#region Overrides of PluginEndPoint
/// <inheritdoc />
protected override async Task ProcessRequest(IHttpContext context)
{
if (context.Request.HttpVerb != HttpVerbs.Post && context.Request.HttpVerb != HttpVerbs.Put)
throw HttpException.MethodNotAllowed("This end point only accepts POST and PUT calls");
context.Response.ContentType = MimeType.Json;
using TextReader reader = context.OpenRequestText();
object? response = null;
try
{
T? deserialized = JsonConvert.DeserializeObject<T>(await reader.ReadToEndAsync());
if (deserialized == null)
throw new JsonException("Deserialization returned null");
if (_requestHandler != null)
{
_requestHandler(deserialized);
return;
}
if (_responseRequestHandler != null)
response = _responseRequestHandler(deserialized);
else
throw new ArtemisCoreException("JSON plugin end point has no request handler");
}
catch (JsonException)
{
if (ThrowOnFail)
throw;
}
await using TextWriter writer = context.OpenResponseText();
await writer.WriteAsync(JsonConvert.SerializeObject(response));
}
#endregion
_requestHandler = requestHandler;
ThrowOnFail = true;
Accepts = MimeType.Json;
}
internal JsonPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Func<T, object?> responseRequestHandler) : base(pluginFeature, name, pluginsModule)
{
_responseRequestHandler = responseRequestHandler;
ThrowOnFail = true;
Accepts = MimeType.Json;
Returns = MimeType.Json;
}
/// <summary>
/// Whether or not the end point should throw an exception if deserializing the received JSON fails.
/// If set to <see langword="false" /> malformed JSON is silently ignored; if set to <see langword="true" /> malformed
/// JSON throws a <see cref="JsonException" />.
/// </summary>
public bool ThrowOnFail { get; set; }
#region Overrides of PluginEndPoint
/// <inheritdoc />
protected override async Task ProcessRequest(IHttpContext context)
{
if (context.Request.HttpVerb != HttpVerbs.Post && context.Request.HttpVerb != HttpVerbs.Put)
throw HttpException.MethodNotAllowed("This end point only accepts POST and PUT calls");
context.Response.ContentType = MimeType.Json;
using TextReader reader = context.OpenRequestText();
object? response = null;
try
{
T? deserialized = JsonConvert.DeserializeObject<T>(await reader.ReadToEndAsync());
if (deserialized == null)
throw new JsonException("Deserialization returned null");
if (_requestHandler != null)
{
_requestHandler(deserialized);
return;
}
if (_responseRequestHandler != null)
response = _responseRequestHandler(deserialized);
else
throw new ArtemisCoreException("JSON plugin end point has no request handler");
}
catch (JsonException)
{
if (ThrowOnFail)
throw;
}
await using TextWriter writer = context.OpenResponseText();
await writer.WriteAsync(JsonConvert.SerializeObject(response));
}
#endregion
}
@@ -3,120 +3,119 @@ using System.Threading.Tasks;
using EmbedIO;
using Newtonsoft.Json;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Represents a base type for plugin end points to be targeted by the <see cref="PluginsModule" />
/// </summary>
public abstract class PluginEndPoint
{
/// <summary>
/// Represents a base type for plugin end points to be targeted by the <see cref="PluginsModule" />
/// </summary>
public abstract class PluginEndPoint
private readonly PluginsModule _pluginsModule;
internal PluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule)
{
private readonly PluginsModule _pluginsModule;
_pluginsModule = pluginsModule;
PluginFeature = pluginFeature;
Name = name;
internal PluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule)
PluginFeature.Disabled += OnDisabled;
}
/// <summary>
/// Gets the name of the end point
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the full URL of the end point
/// </summary>
public string Url => $"{_pluginsModule.ServerUrl?.TrimEnd('/')}{_pluginsModule.BaseRoute}{PluginFeature.Plugin.Guid}/{Name}";
/// <summary>
/// Gets the plugin the end point is associated with
/// </summary>
[JsonIgnore]
public PluginFeature PluginFeature { get; }
/// <summary>
/// Gets the plugin info of the plugin the end point is associated with
/// </summary>
public PluginInfo PluginInfo => PluginFeature.Plugin.Info;
/// <summary>
/// Gets the mime type of the input this end point accepts
/// </summary>
public string? Accepts { get; protected set; }
/// <summary>
/// Gets the mime type of the output this end point returns
/// </summary>
public string? Returns { get; protected set; }
/// <summary>
/// Occurs whenever a request threw an unhandled exception
/// </summary>
public event EventHandler<EndpointExceptionEventArgs>? RequestException;
/// <summary>
/// Occurs whenever a request is about to be processed
/// </summary>
public event EventHandler<EndpointRequestEventArgs>? ProcessingRequest;
/// <summary>
/// Occurs whenever a request was processed
/// </summary>
public event EventHandler<EndpointRequestEventArgs>? ProcessedRequest;
/// <summary>
/// Called whenever the end point has to process a request
/// </summary>
/// <param name="context">The HTTP context of the request</param>
protected abstract Task ProcessRequest(IHttpContext context);
/// <summary>
/// Invokes the <see cref="RequestException" /> event
/// </summary>
/// <param name="e">The exception that occurred during the request</param>
protected virtual void OnRequestException(Exception e)
{
RequestException?.Invoke(this, new EndpointExceptionEventArgs(e));
}
/// <summary>
/// Invokes the <see cref="ProcessingRequest" /> event
/// </summary>
protected virtual void OnProcessingRequest(IHttpContext context)
{
ProcessingRequest?.Invoke(this, new EndpointRequestEventArgs(context));
}
/// <summary>
/// Invokes the <see cref="ProcessedRequest" /> event
/// </summary>
protected virtual void OnProcessedRequest(IHttpContext context)
{
ProcessedRequest?.Invoke(this, new EndpointRequestEventArgs(context));
}
internal async Task InternalProcessRequest(IHttpContext context)
{
try
{
_pluginsModule = pluginsModule;
PluginFeature = pluginFeature;
Name = name;
PluginFeature.Disabled += OnDisabled;
OnProcessingRequest(context);
await ProcessRequest(context);
OnProcessedRequest(context);
}
/// <summary>
/// Gets the name of the end point
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the full URL of the end point
/// </summary>
public string Url => $"{_pluginsModule.ServerUrl?.TrimEnd('/')}{_pluginsModule.BaseRoute}{PluginFeature.Plugin.Guid}/{Name}";
/// <summary>
/// Gets the plugin the end point is associated with
/// </summary>
[JsonIgnore]
public PluginFeature PluginFeature { get; }
/// <summary>
/// Gets the plugin info of the plugin the end point is associated with
/// </summary>
public PluginInfo PluginInfo => PluginFeature.Plugin.Info;
/// <summary>
/// Gets the mime type of the input this end point accepts
/// </summary>
public string? Accepts { get; protected set; }
/// <summary>
/// Gets the mime type of the output this end point returns
/// </summary>
public string? Returns { get; protected set; }
/// <summary>
/// Occurs whenever a request threw an unhandled exception
/// </summary>
public event EventHandler<EndpointExceptionEventArgs>? RequestException;
/// <summary>
/// Occurs whenever a request is about to be processed
/// </summary>
public event EventHandler<EndpointRequestEventArgs>? ProcessingRequest;
/// <summary>
/// Occurs whenever a request was processed
/// </summary>
public event EventHandler<EndpointRequestEventArgs>? ProcessedRequest;
/// <summary>
/// Called whenever the end point has to process a request
/// </summary>
/// <param name="context">The HTTP context of the request</param>
protected abstract Task ProcessRequest(IHttpContext context);
/// <summary>
/// Invokes the <see cref="RequestException" /> event
/// </summary>
/// <param name="e">The exception that occurred during the request</param>
protected virtual void OnRequestException(Exception e)
catch (Exception e)
{
RequestException?.Invoke(this, new EndpointExceptionEventArgs(e));
}
/// <summary>
/// Invokes the <see cref="ProcessingRequest" /> event
/// </summary>
protected virtual void OnProcessingRequest(IHttpContext context)
{
ProcessingRequest?.Invoke(this, new EndpointRequestEventArgs(context));
}
/// <summary>
/// Invokes the <see cref="ProcessedRequest" /> event
/// </summary>
protected virtual void OnProcessedRequest(IHttpContext context)
{
ProcessedRequest?.Invoke(this, new EndpointRequestEventArgs(context));
}
internal async Task InternalProcessRequest(IHttpContext context)
{
try
{
OnProcessingRequest(context);
await ProcessRequest(context);
OnProcessedRequest(context);
}
catch (Exception e)
{
OnRequestException(e);
throw;
}
}
private void OnDisabled(object? sender, EventArgs e)
{
PluginFeature.Disabled -= OnDisabled;
_pluginsModule.RemovePluginEndPoint(this);
OnRequestException(e);
throw;
}
}
private void OnDisabled(object? sender, EventArgs e)
{
PluginFeature.Disabled -= OnDisabled;
_pluginsModule.RemovePluginEndPoint(this);
}
}
@@ -2,52 +2,51 @@
using System.Threading.Tasks;
using EmbedIO;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Represents a plugin web endpoint that handles a raw <see cref="IHttpContext" />.
/// <para>
/// Note: This requires that you reference the EmbedIO
/// <see href="https://www.nuget.org/packages/embedio">Nuget package</see>.
/// </para>
/// </summary>
public class RawPluginEndPoint : PluginEndPoint
{
/// <summary>
/// Represents a plugin web endpoint that handles a raw <see cref="IHttpContext" />.
/// <para>
/// Note: This requires that you reference the EmbedIO
/// <see href="https://www.nuget.org/packages/embedio">Nuget package</see>.
/// </para>
/// </summary>
public class RawPluginEndPoint : PluginEndPoint
/// <inheritdoc />
internal RawPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Func<IHttpContext, Task> requestHandler) : base(pluginFeature, name, pluginsModule)
{
/// <inheritdoc />
internal RawPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Func<IHttpContext, Task> requestHandler) : base(pluginFeature, name, pluginsModule)
{
RequestHandler = requestHandler;
}
/// <summary>
/// Gets or sets the handler used to handle incoming requests to this endpoint
/// </summary>
public Func<IHttpContext, Task> RequestHandler { get; }
/// <summary>
/// Sets the mime type this plugin end point accepts
/// </summary>
public void SetAcceptType(string type)
{
Accepts = type;
}
/// <summary>
/// Sets the mime type this plugin end point returns
/// </summary>
public void SetReturnType(string type)
{
Returns = type;
}
#region Overrides of PluginEndPoint
/// <inheritdoc />
protected override async Task ProcessRequest(IHttpContext context)
{
await RequestHandler(context);
}
#endregion
RequestHandler = requestHandler;
}
/// <summary>
/// Gets or sets the handler used to handle incoming requests to this endpoint
/// </summary>
public Func<IHttpContext, Task> RequestHandler { get; }
/// <summary>
/// Sets the mime type this plugin end point accepts
/// </summary>
public void SetAcceptType(string type)
{
Accepts = type;
}
/// <summary>
/// Sets the mime type this plugin end point returns
/// </summary>
public void SetReturnType(string type)
{
Returns = type;
}
#region Overrides of PluginEndPoint
/// <inheritdoc />
protected override async Task ProcessRequest(IHttpContext context)
{
await RequestHandler(context);
}
#endregion
}
@@ -3,57 +3,56 @@ using System.IO;
using System.Threading.Tasks;
using EmbedIO;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Represents a plugin web endpoint receiving an a <see cref="string" /> and returning a <see cref="string" /> or
/// <see langword="null" />.
/// </summary>
public class StringPluginEndPoint : PluginEndPoint
{
/// <summary>
/// Represents a plugin web endpoint receiving an a <see cref="string" /> and returning a <see cref="string" /> or
/// <see langword="null" />.
/// </summary>
public class StringPluginEndPoint : PluginEndPoint
private readonly Action<string>? _requestHandler;
private readonly Func<string, string?>? _responseRequestHandler;
internal StringPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Action<string> requestHandler) : base(pluginFeature, name, pluginsModule)
{
private readonly Action<string>? _requestHandler;
private readonly Func<string, string?>? _responseRequestHandler;
internal StringPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Action<string> requestHandler) : base(pluginFeature, name, pluginsModule)
{
_requestHandler = requestHandler;
Accepts = MimeType.PlainText;
}
internal StringPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Func<string, string?> requestHandler) : base(pluginFeature, name, pluginsModule)
{
_responseRequestHandler = requestHandler;
Accepts = MimeType.PlainText;
Returns = MimeType.PlainText;
}
#region Overrides of PluginEndPoint
/// <inheritdoc />
protected override async Task ProcessRequest(IHttpContext context)
{
if (context.Request.HttpVerb != HttpVerbs.Post && context.Request.HttpVerb != HttpVerbs.Put)
throw HttpException.MethodNotAllowed("This end point only accepts POST and PUT calls");
context.Response.ContentType = MimeType.PlainText;
using TextReader reader = context.OpenRequestText();
string? response;
if (_requestHandler != null)
{
_requestHandler(await reader.ReadToEndAsync());
return;
}
if (_responseRequestHandler != null)
response = _responseRequestHandler(await reader.ReadToEndAsync());
else
throw new ArtemisCoreException("String plugin end point has no request handler");
await using TextWriter writer = context.OpenResponseText();
await writer.WriteAsync(response);
}
#endregion
_requestHandler = requestHandler;
Accepts = MimeType.PlainText;
}
internal StringPluginEndPoint(PluginFeature pluginFeature, string name, PluginsModule pluginsModule, Func<string, string?> requestHandler) : base(pluginFeature, name, pluginsModule)
{
_responseRequestHandler = requestHandler;
Accepts = MimeType.PlainText;
Returns = MimeType.PlainText;
}
#region Overrides of PluginEndPoint
/// <inheritdoc />
protected override async Task ProcessRequest(IHttpContext context)
{
if (context.Request.HttpVerb != HttpVerbs.Post && context.Request.HttpVerb != HttpVerbs.Put)
throw HttpException.MethodNotAllowed("This end point only accepts POST and PUT calls");
context.Response.ContentType = MimeType.PlainText;
using TextReader reader = context.OpenRequestText();
string? response;
if (_requestHandler != null)
{
_requestHandler(await reader.ReadToEndAsync());
return;
}
if (_responseRequestHandler != null)
response = _responseRequestHandler(await reader.ReadToEndAsync());
else
throw new ArtemisCoreException("String plugin end point has no request handler");
await using TextWriter writer = context.OpenResponseText();
await writer.WriteAsync(response);
}
#endregion
}
@@ -4,130 +4,129 @@ using Artemis.Core.Modules;
using EmbedIO;
using EmbedIO.WebApi;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// A service that provides access to the local Artemis web server
/// </summary>
public interface IWebServerService : IArtemisService
{
/// <summary>
/// A service that provides access to the local Artemis web server
/// Gets the current instance of the web server, replaced when <see cref="WebServerStarting" /> occurs.
/// </summary>
public interface IWebServerService : IArtemisService
{
/// <summary>
/// Gets the current instance of the web server, replaced when <see cref="WebServerStarting" /> occurs.
/// </summary>
WebServer? Server { get; }
WebServer? Server { get; }
/// <summary>
/// Gets the plugins module containing all plugin end points
/// </summary>
PluginsModule PluginsModule { get; }
/// <summary>
/// Gets the plugins module containing all plugin end points
/// </summary>
PluginsModule PluginsModule { get; }
/// <summary>
/// Adds a new endpoint for the given plugin feature receiving an object of type <typeparamref name="T" />
/// <para>Note: Object will be deserialized using JSON.</para>
/// </summary>
/// <typeparam name="T">The type of object to be received</typeparam>
/// <param name="feature">The plugin feature the end point is associated with</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <param name="requestHandler"></param>
/// <returns>The resulting end point</returns>
JsonPluginEndPoint<T> AddJsonEndPoint<T>(PluginFeature feature, string endPointName, Action<T> requestHandler);
/// <summary>
/// Adds a new endpoint for the given plugin feature receiving an object of type <typeparamref name="T" />
/// <para>Note: Object will be deserialized using JSON.</para>
/// </summary>
/// <typeparam name="T">The type of object to be received</typeparam>
/// <param name="feature">The plugin feature the end point is associated with</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <param name="requestHandler"></param>
/// <returns>The resulting end point</returns>
JsonPluginEndPoint<T> AddJsonEndPoint<T>(PluginFeature feature, string endPointName, Action<T> requestHandler);
/// <summary>
/// Adds a new endpoint for the given plugin feature receiving an object of type <typeparamref name="T" /> and
/// returning any <see cref="object" />.
/// <para>Note: Both will be deserialized and serialized respectively using JSON.</para>
/// </summary>
/// <typeparam name="T">The type of object to be received</typeparam>
/// <param name="feature">The plugin feature the end point is associated with</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <param name="requestHandler"></param>
/// <returns>The resulting end point</returns>
JsonPluginEndPoint<T> AddResponsiveJsonEndPoint<T>(PluginFeature feature, string endPointName, Func<T, object?> requestHandler);
/// <summary>
/// Adds a new endpoint for the given plugin feature receiving an object of type <typeparamref name="T" /> and
/// returning any <see cref="object" />.
/// <para>Note: Both will be deserialized and serialized respectively using JSON.</para>
/// </summary>
/// <typeparam name="T">The type of object to be received</typeparam>
/// <param name="feature">The plugin feature the end point is associated with</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <param name="requestHandler"></param>
/// <returns>The resulting end point</returns>
JsonPluginEndPoint<T> AddResponsiveJsonEndPoint<T>(PluginFeature feature, string endPointName, Func<T, object?> requestHandler);
/// <summary>
/// Adds a new endpoint that directly maps received JSON to the data model of the provided <paramref name="module" />.
/// </summary>
/// <typeparam name="T">The data model type of the module</typeparam>
/// <param name="module">The module whose datamodel to apply the received JSON to</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <returns>The resulting end point</returns>
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel, new();
/// <summary>
/// Adds a new endpoint that directly maps received JSON to the data model of the provided <paramref name="module" />.
/// </summary>
/// <typeparam name="T">The data model type of the module</typeparam>
/// <param name="module">The module whose datamodel to apply the received JSON to</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <returns>The resulting end point</returns>
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel, new();
/// <summary>
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />.
/// </summary>
/// <param name="feature">The plugin feature the end point is associated with</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <param name="requestHandler"></param>
/// <returns>The resulting end point</returns>
StringPluginEndPoint AddStringEndPoint(PluginFeature feature, string endPointName, Action<string> requestHandler);
/// <summary>
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />.
/// </summary>
/// <param name="feature">The plugin feature the end point is associated with</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <param name="requestHandler"></param>
/// <returns>The resulting end point</returns>
StringPluginEndPoint AddStringEndPoint(PluginFeature feature, string endPointName, Action<string> requestHandler);
/// <summary>
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" /> and returning a
/// <see cref="string" /> or <see langword="null" />.
/// </summary>
/// <param name="feature">The plugin feature the end point is associated with</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <param name="requestHandler"></param>
/// <returns>The resulting end point</returns>
StringPluginEndPoint AddResponsiveStringEndPoint(PluginFeature feature, string endPointName, Func<string, string?> requestHandler);
/// <summary>
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" /> and returning a
/// <see cref="string" /> or <see langword="null" />.
/// </summary>
/// <param name="feature">The plugin feature the end point is associated with</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <param name="requestHandler"></param>
/// <returns>The resulting end point</returns>
StringPluginEndPoint AddResponsiveStringEndPoint(PluginFeature feature, string endPointName, Func<string, string?> requestHandler);
/// <summary>
/// Adds a new endpoint for the given plugin feature that handles a raw <see cref="IHttpContext" />.
/// <para>
/// Note: This requires that you reference the EmbedIO
/// <see href="https://www.nuget.org/packages/embedio">Nuget package</see>.
/// </para>
/// </summary>
/// <param name="feature">The plugin feature the end point is associated with</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <param name="requestHandler"></param>
/// <returns>The resulting end point</returns>
RawPluginEndPoint AddRawEndPoint(PluginFeature feature, string endPointName, Func<IHttpContext, Task> requestHandler);
/// <summary>
/// Adds a new endpoint for the given plugin feature that handles a raw <see cref="IHttpContext" />.
/// <para>
/// Note: This requires that you reference the EmbedIO
/// <see href="https://www.nuget.org/packages/embedio">Nuget package</see>.
/// </para>
/// </summary>
/// <param name="feature">The plugin feature the end point is associated with</param>
/// <param name="endPointName">The name of the end point, must be unique</param>
/// <param name="requestHandler"></param>
/// <returns>The resulting end point</returns>
RawPluginEndPoint AddRawEndPoint(PluginFeature feature, string endPointName, Func<IHttpContext, Task> requestHandler);
/// <summary>
/// Removes an existing endpoint
/// </summary>
/// <param name="endPoint">The end point to remove</param>
void RemovePluginEndPoint(PluginEndPoint endPoint);
/// <summary>
/// Removes an existing endpoint
/// </summary>
/// <param name="endPoint">The end point to remove</param>
void RemovePluginEndPoint(PluginEndPoint endPoint);
/// <summary>
/// Adds a new Web API controller and restarts the web server
/// </summary>
/// <typeparam name="T">The type of Web API controller to remove</typeparam>
void AddController<T>(PluginFeature feature) where T : WebApiController;
/// <summary>
/// Adds a new Web API controller and restarts the web server
/// </summary>
/// <typeparam name="T">The type of Web API controller to remove</typeparam>
void AddController<T>(PluginFeature feature) where T : WebApiController;
/// <summary>
/// Removes an existing Web API controller and restarts the web server
/// </summary>
/// <typeparam name="T">The type of Web API controller to remove</typeparam>
void RemoveController<T>() where T : WebApiController;
/// <summary>
/// Adds a new EmbedIO module and restarts the web server
/// </summary>
void AddModule(PluginFeature feature, Func<IWebModule> create);
/// <summary>
/// Removes a EmbedIO module and restarts the web server
/// </summary>
void RemoveModule(Func<IWebModule> create);
/// <summary>
/// Removes an existing Web API controller and restarts the web server
/// </summary>
/// <typeparam name="T">The type of Web API controller to remove</typeparam>
void RemoveController<T>() where T : WebApiController;
/// <summary>
/// Adds a new EmbedIO module and restarts the web server
/// </summary>
/// <typeparam name="T">The type of module to add</typeparam>
void AddModule<T>(PluginFeature feature) where T : IWebModule;
/// <summary>
/// Adds a new EmbedIO module and restarts the web server
/// </summary>
void AddModule(PluginFeature feature, Func<IWebModule> create);
/// <summary>
/// Removes a EmbedIO module and restarts the web server
/// </summary>
/// <typeparam name="T">The type of module to remove</typeparam>
void RemoveModule<T>() where T : IWebModule;
/// <summary>
/// Removes a EmbedIO module and restarts the web server
/// </summary>
void RemoveModule(Func<IWebModule> create);
/// <summary>
/// Occurs when the web server has been created and is about to start. This is the ideal place to add your own modules.
/// </summary>
event EventHandler? WebServerStarting;
}
/// <summary>
/// Adds a new EmbedIO module and restarts the web server
/// </summary>
/// <typeparam name="T">The type of module to add</typeparam>
void AddModule<T>(PluginFeature feature) where T : IWebModule;
/// <summary>
/// Removes a EmbedIO module and restarts the web server
/// </summary>
/// <typeparam name="T">The type of module to remove</typeparam>
void RemoveModule<T>() where T : IWebModule;
/// <summary>
/// Occurs when the web server has been created and is about to start. This is the ideal place to add your own modules.
/// </summary>
event EventHandler? WebServerStarting;
}
@@ -3,90 +3,89 @@ using System.Linq;
using System.Threading.Tasks;
using EmbedIO;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
/// <summary>
/// Represents an EmbedIO web module used to process web requests and forward them to the right
/// <see cref="PluginEndPoint" />.
/// </summary>
public class PluginsModule : WebModuleBase
{
/// <summary>
/// Represents an EmbedIO web module used to process web requests and forward them to the right
/// <see cref="PluginEndPoint" />.
/// </summary>
public class PluginsModule : WebModuleBase
private readonly Dictionary<string, Dictionary<string, PluginEndPoint>> _pluginEndPoints;
internal PluginsModule(string baseRoute) : base(baseRoute)
{
private readonly Dictionary<string, Dictionary<string, PluginEndPoint>> _pluginEndPoints;
internal PluginsModule(string baseRoute) : base(baseRoute)
{
_pluginEndPoints = new Dictionary<string, Dictionary<string, PluginEndPoint>>();
}
internal void AddPluginEndPoint(PluginEndPoint registration)
{
string id = registration.PluginFeature.Plugin.Guid.ToString();
if (!_pluginEndPoints.TryGetValue(id, out Dictionary<string, PluginEndPoint>? registrations))
{
registrations = new Dictionary<string, PluginEndPoint>();
_pluginEndPoints.Add(id, registrations);
}
if (registrations.ContainsKey(registration.Name))
throw new ArtemisPluginException(registration.PluginFeature.Plugin, $"Plugin already registered an endpoint at {registration.Name}.");
registrations.Add(registration.Name, registration);
}
internal void RemovePluginEndPoint(PluginEndPoint registration)
{
string id = registration.PluginFeature.Plugin.Guid.ToString();
if (!_pluginEndPoints.TryGetValue(id, out Dictionary<string, PluginEndPoint>? registrations))
return;
if (!registrations.ContainsKey(registration.Name))
return;
registrations.Remove(registration.Name);
}
#region Overrides of WebModuleBase
/// <inheritdoc />
protected override async Task OnRequestAsync(IHttpContext context)
{
if (context.Route.SubPath == null)
return;
// Split the sub path
string[] pathParts = context.Route.SubPath.Substring(1).Split('/');
// Expect a plugin ID and an endpoint
if (pathParts.Length != 2)
return;
// Find a matching plugin
if (!_pluginEndPoints.TryGetValue(pathParts[0], out Dictionary<string, PluginEndPoint>? endPoints))
throw HttpException.NotFound($"Found no plugin with ID {pathParts[0]}.");
// Find a matching endpoint
if (!endPoints.TryGetValue(pathParts[1], out PluginEndPoint? endPoint))
throw HttpException.NotFound($"Found no endpoint called {pathParts[1]} for plugin with ID {pathParts[0]}.");
// If Accept-Charset contains a wildcard, remove the header so we default to UTF8
// This is a workaround for an EmbedIO ehh issue
string? acceptCharset = context.Request.Headers["Accept-Charset"];
if (acceptCharset != null && acceptCharset.Contains("*"))
context.Request.Headers.Remove("Accept-Charset");
// It is up to the registration how the request is eventually handled, it might even set a response here
await endPoint.InternalProcessRequest(context);
// No need to return ourselves, assume the request is fully handled by the end point
context.SetHandled();
}
/// <inheritdoc />
public override bool IsFinalHandler => false;
internal string? ServerUrl { get; set; }
/// <summary>
/// Gets a read only collection containing all current plugin end points
/// </summary>
public IReadOnlyCollection<PluginEndPoint> PluginEndPoints => new List<PluginEndPoint>(_pluginEndPoints.SelectMany(p => p.Value.Values));
#endregion
_pluginEndPoints = new Dictionary<string, Dictionary<string, PluginEndPoint>>();
}
internal void AddPluginEndPoint(PluginEndPoint registration)
{
string id = registration.PluginFeature.Plugin.Guid.ToString();
if (!_pluginEndPoints.TryGetValue(id, out Dictionary<string, PluginEndPoint>? registrations))
{
registrations = new Dictionary<string, PluginEndPoint>();
_pluginEndPoints.Add(id, registrations);
}
if (registrations.ContainsKey(registration.Name))
throw new ArtemisPluginException(registration.PluginFeature.Plugin, $"Plugin already registered an endpoint at {registration.Name}.");
registrations.Add(registration.Name, registration);
}
internal void RemovePluginEndPoint(PluginEndPoint registration)
{
string id = registration.PluginFeature.Plugin.Guid.ToString();
if (!_pluginEndPoints.TryGetValue(id, out Dictionary<string, PluginEndPoint>? registrations))
return;
if (!registrations.ContainsKey(registration.Name))
return;
registrations.Remove(registration.Name);
}
#region Overrides of WebModuleBase
/// <inheritdoc />
protected override async Task OnRequestAsync(IHttpContext context)
{
if (context.Route.SubPath == null)
return;
// Split the sub path
string[] pathParts = context.Route.SubPath.Substring(1).Split('/');
// Expect a plugin ID and an endpoint
if (pathParts.Length != 2)
return;
// Find a matching plugin
if (!_pluginEndPoints.TryGetValue(pathParts[0], out Dictionary<string, PluginEndPoint>? endPoints))
throw HttpException.NotFound($"Found no plugin with ID {pathParts[0]}.");
// Find a matching endpoint
if (!endPoints.TryGetValue(pathParts[1], out PluginEndPoint? endPoint))
throw HttpException.NotFound($"Found no endpoint called {pathParts[1]} for plugin with ID {pathParts[0]}.");
// If Accept-Charset contains a wildcard, remove the header so we default to UTF8
// This is a workaround for an EmbedIO ehh issue
string? acceptCharset = context.Request.Headers["Accept-Charset"];
if (acceptCharset != null && acceptCharset.Contains("*"))
context.Request.Headers.Remove("Accept-Charset");
// It is up to the registration how the request is eventually handled, it might even set a response here
await endPoint.InternalProcessRequest(context);
// No need to return ourselves, assume the request is fully handled by the end point
context.SetHandled();
}
/// <inheritdoc />
public override bool IsFinalHandler => false;
internal string? ServerUrl { get; set; }
/// <summary>
/// Gets a read only collection containing all current plugin end points
/// </summary>
public IReadOnlyCollection<PluginEndPoint> PluginEndPoints => new List<PluginEndPoint>(_pluginEndPoints.SelectMany(p => p.Value.Values));
#endregion
}
@@ -2,29 +2,28 @@
using EmbedIO.WebApi;
using Ninject;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
internal class WebApiControllerRegistration<T> : WebApiControllerRegistration where T : WebApiController
{
internal class WebApiControllerRegistration<T> : WebApiControllerRegistration where T : WebApiController
public WebApiControllerRegistration(PluginFeature feature) : base(feature, typeof(T))
{
public WebApiControllerRegistration(PluginFeature feature) : base(feature, typeof(T))
{
Factory = () => feature.Plugin.Kernel!.Get<T>();
}
public Func<T> Factory { get; set; }
public override object UntypedFactory => Factory;
Factory = () => feature.Plugin.Kernel!.Get<T>();
}
internal abstract class WebApiControllerRegistration
{
protected WebApiControllerRegistration(PluginFeature feature, Type controllerType)
{
Feature = feature;
ControllerType = controllerType;
}
public Func<T> Factory { get; set; }
public override object UntypedFactory => Factory;
}
public abstract object UntypedFactory { get; }
public Type ControllerType { get; set; }
public PluginFeature Feature { get; }
internal abstract class WebApiControllerRegistration
{
protected WebApiControllerRegistration(PluginFeature feature, Type controllerType)
{
Feature = feature;
ControllerType = controllerType;
}
public abstract object UntypedFactory { get; }
public Type ControllerType { get; set; }
public PluginFeature Feature { get; }
}
@@ -2,33 +2,32 @@
using EmbedIO;
using Ninject;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
internal class WebModuleRegistration
{
internal class WebModuleRegistration
public WebModuleRegistration(PluginFeature feature, Type webModuleType)
{
public PluginFeature Feature { get; }
public Type? WebModuleType { get; }
public Func<IWebModule>? Create { get; }
Feature = feature ?? throw new ArgumentNullException(nameof(feature));
WebModuleType = webModuleType ?? throw new ArgumentNullException(nameof(webModuleType));
}
public WebModuleRegistration(PluginFeature feature, Type webModuleType)
{
Feature = feature ?? throw new ArgumentNullException(nameof(feature));
WebModuleType = webModuleType ?? throw new ArgumentNullException(nameof(webModuleType));
}
public WebModuleRegistration(PluginFeature feature, Func<IWebModule> create)
{
Feature = feature ?? throw new ArgumentNullException(nameof(feature));
Create = create ?? throw new ArgumentNullException(nameof(create));
}
public WebModuleRegistration(PluginFeature feature, Func<IWebModule> create)
{
Feature = feature ?? throw new ArgumentNullException(nameof(feature));
Create = create ?? throw new ArgumentNullException(nameof(create));
}
public PluginFeature Feature { get; }
public Type? WebModuleType { get; }
public Func<IWebModule>? Create { get; }
public IWebModule CreateInstance()
{
if (Create != null)
return Create();
if (WebModuleType != null)
return (IWebModule) Feature.Plugin.Kernel!.Get(WebModuleType);
throw new ArtemisCoreException("WebModuleRegistration doesn't have a create function nor a web module type :(");
}
public IWebModule CreateInstance()
{
if (Create != null)
return Create();
if (WebModuleType != null)
return (IWebModule) Feature.Plugin.Kernel!.Get(WebModuleType);
throw new ArtemisCoreException("WebModuleRegistration doesn't have a create function nor a web module type :(");
}
}
@@ -10,283 +10,270 @@ using EmbedIO.WebApi;
using Newtonsoft.Json;
using Serilog;
namespace Artemis.Core.Services
namespace Artemis.Core.Services;
internal class WebServerService : IWebServerService, IDisposable
{
internal class WebServerService : IWebServerService, IDisposable
private readonly List<WebApiControllerRegistration> _controllers;
private readonly ILogger _logger;
private readonly List<WebModuleRegistration> _modules;
private readonly PluginSetting<int> _webServerPortSetting;
public WebServerService(ILogger logger, ISettingsService settingsService, IPluginManagementService pluginManagementService)
{
private readonly List<WebApiControllerRegistration> _controllers;
private readonly List<WebModuleRegistration> _modules;
private readonly ILogger _logger;
private readonly PluginSetting<int> _webServerPortSetting;
_logger = logger;
_controllers = new List<WebApiControllerRegistration>();
_modules = new List<WebModuleRegistration>();
public WebServerService(ILogger logger, ISettingsService settingsService, IPluginManagementService pluginManagementService)
{
_logger = logger;
_controllers = new List<WebApiControllerRegistration>();
_modules = new List<WebModuleRegistration>();
_webServerPortSetting = settingsService.GetSetting("WebServer.Port", 9696);
_webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged;
pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled;
_webServerPortSetting = settingsService.GetSetting("WebServer.Port", 9696);
_webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged;
pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled;
PluginsModule = new PluginsModule("/plugins");
StartWebServer();
}
public WebServer? Server { get; private set; }
public PluginsModule PluginsModule { get; }
#region Web server managament
private WebServer CreateWebServer()
{
Server?.Dispose();
Server = null;
WebApiModule apiModule = new("/", JsonNetSerializer);
PluginsModule.ServerUrl = $"http://localhost:{_webServerPortSetting.Value}/";
WebServer server = new WebServer(o => o.WithUrlPrefix($"http://*:{_webServerPortSetting.Value}/").WithMode(HttpListenerMode.EmbedIO))
.WithLocalSessionManager()
.WithModule(PluginsModule);
// Add registered modules
foreach (WebModuleRegistration? webModule in _modules)
server = server.WithModule(webModule.CreateInstance());
server = server
.WithModule(apiModule)
.HandleHttpException((context, exception) => HandleHttpExceptionJson(context, exception))
.HandleUnhandledException(JsonExceptionHandlerCallback);
// Add registered controllers to the API module
foreach (WebApiControllerRegistration registration in _controllers)
apiModule.RegisterController(registration.ControllerType, (Func<WebApiController>) registration.UntypedFactory);
// Listen for state changes.
server.StateChanged += (s, e) => _logger.Verbose("WebServer new state - {state}", e.NewState);
// Store the URL in a webserver.txt file so that remote applications can find it
File.WriteAllText(Path.Combine(Constants.DataFolder, "webserver.txt"), PluginsModule.ServerUrl);
return server;
}
private void StartWebServer()
{
Server = CreateWebServer();
OnWebServerStarting();
Server.Start();
}
#endregion
#region Plugin endpoint management
public JsonPluginEndPoint<T> AddJsonEndPoint<T>(PluginFeature feature, string endPointName, Action<T> requestHandler)
{
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
JsonPluginEndPoint<T> endPoint = new(feature, endPointName, PluginsModule, requestHandler);
PluginsModule.AddPluginEndPoint(endPoint);
return endPoint;
}
public JsonPluginEndPoint<T> AddResponsiveJsonEndPoint<T>(PluginFeature feature, string endPointName, Func<T, object?> requestHandler)
{
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
JsonPluginEndPoint<T> endPoint = new(feature, endPointName, PluginsModule, requestHandler);
PluginsModule.AddPluginEndPoint(endPoint);
return endPoint;
}
public StringPluginEndPoint AddStringEndPoint(PluginFeature feature, string endPointName, Action<string> requestHandler)
{
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
StringPluginEndPoint endPoint = new(feature, endPointName, PluginsModule, requestHandler);
PluginsModule.AddPluginEndPoint(endPoint);
return endPoint;
}
public StringPluginEndPoint AddResponsiveStringEndPoint(PluginFeature feature, string endPointName, Func<string, string?> requestHandler)
{
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
StringPluginEndPoint endPoint = new(feature, endPointName, PluginsModule, requestHandler);
PluginsModule.AddPluginEndPoint(endPoint);
return endPoint;
}
public RawPluginEndPoint AddRawEndPoint(PluginFeature feature, string endPointName, Func<IHttpContext, Task> requestHandler)
{
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
RawPluginEndPoint endPoint = new(feature, endPointName, PluginsModule, requestHandler);
PluginsModule.AddPluginEndPoint(endPoint);
return endPoint;
}
public DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel, new()
{
if (module == null) throw new ArgumentNullException(nameof(module));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
DataModelJsonPluginEndPoint<T> endPoint = new(module, endPointName, PluginsModule);
PluginsModule.AddPluginEndPoint(endPoint);
return endPoint;
}
private void HandleDataModelRequest<T>(Module<T> module, T value) where T : DataModel, new()
{
}
public void RemovePluginEndPoint(PluginEndPoint endPoint)
{
PluginsModule.RemovePluginEndPoint(endPoint);
}
#endregion
#region Controller management
public void AddController<T>(PluginFeature feature) where T : WebApiController
{
_controllers.Add(new WebApiControllerRegistration<T>(feature));
StartWebServer();
}
public void RemoveController<T>() where T : WebApiController
{
_controllers.RemoveAll(r => r.ControllerType == typeof(T));
StartWebServer();
}
#endregion
#region Module management
public void AddModule(PluginFeature feature, Func<IWebModule> create)
{
if (feature == null) throw new ArgumentNullException(nameof(feature));
_modules.Add(new WebModuleRegistration(feature, create));
StartWebServer();
}
public void RemoveModule(Func<IWebModule> create)
{
_modules.RemoveAll(r => r.Create == create);
StartWebServer();
}
public void AddModule<T>(PluginFeature feature) where T : IWebModule
{
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (_modules.Any(r => r.WebModuleType == typeof(T)))
return;
_modules.Add(new WebModuleRegistration(feature, typeof(T)));
StartWebServer();
}
public void RemoveModule<T>() where T : IWebModule
{
_modules.RemoveAll(r => r.WebModuleType == typeof(T));
StartWebServer();
}
#endregion
#region Handlers
private async Task JsonExceptionHandlerCallback(IHttpContext context, Exception exception)
{
context.Response.ContentType = MimeType.Json;
await using TextWriter writer = context.OpenResponseText();
string response = JsonConvert.SerializeObject(new Dictionary<string, object?>()
{
{"StatusCode", context.Response.StatusCode},
{"StackTrace", exception.StackTrace},
{"Type", exception.GetType().FullName},
{"Message", exception.Message},
{"Data", exception.Data},
{"InnerException", exception.InnerException},
{"HelpLink", exception.HelpLink},
{"Source", exception.Source},
{"HResult", exception.HResult}
});
await writer.WriteAsync(response);
}
private async Task JsonNetSerializer(IHttpContext context, object? data)
{
context.Response.ContentType = MimeType.Json;
await using TextWriter writer = context.OpenResponseText();
string json = JsonConvert.SerializeObject(data, new JsonSerializerSettings {PreserveReferencesHandling = PreserveReferencesHandling.Objects});
await writer.WriteAsync(json);
}
private async Task HandleHttpExceptionJson(IHttpContext context, IHttpException httpException)
{
await context.SendStringAsync(JsonConvert.SerializeObject(httpException, Formatting.Indented), MimeType.Json, Encoding.UTF8);
}
#endregion
#region Events
public event EventHandler? WebServerStarting;
protected virtual void OnWebServerStarting()
{
WebServerStarting?.Invoke(this, EventArgs.Empty);
}
#endregion
#region Event handlers
private void WebServerPortSettingOnSettingChanged(object? sender, EventArgs e)
{
StartWebServer();
}
private void PluginManagementServiceOnPluginFeatureDisabled(object? sender, PluginFeatureEventArgs e)
{
bool mustRestart = false;
if (_controllers.Any(c => c.Feature == e.PluginFeature))
{
mustRestart = true;
_controllers.RemoveAll(c => c.Feature == e.PluginFeature);
}
if (_modules.Any(m => m.Feature == e.PluginFeature))
{
mustRestart = true;
_modules.RemoveAll(m => m.Feature == e.PluginFeature);
}
if (mustRestart)
StartWebServer();
}
#endregion
#region IDisposable
/// <inheritdoc />
public void Dispose()
{
Server?.Dispose();
_webServerPortSetting.SettingChanged -= WebServerPortSettingOnSettingChanged;
}
#endregion
PluginsModule = new PluginsModule("/plugins");
StartWebServer();
}
protected virtual void OnWebServerStarting()
{
WebServerStarting?.Invoke(this, EventArgs.Empty);
}
private void WebServerPortSettingOnSettingChanged(object? sender, EventArgs e)
{
StartWebServer();
}
private void PluginManagementServiceOnPluginFeatureDisabled(object? sender, PluginFeatureEventArgs e)
{
bool mustRestart = false;
if (_controllers.Any(c => c.Feature == e.PluginFeature))
{
mustRestart = true;
_controllers.RemoveAll(c => c.Feature == e.PluginFeature);
}
if (_modules.Any(m => m.Feature == e.PluginFeature))
{
mustRestart = true;
_modules.RemoveAll(m => m.Feature == e.PluginFeature);
}
if (mustRestart)
StartWebServer();
}
/// <inheritdoc />
public void Dispose()
{
Server?.Dispose();
_webServerPortSetting.SettingChanged -= WebServerPortSettingOnSettingChanged;
}
public WebServer? Server { get; private set; }
public PluginsModule PluginsModule { get; }
public event EventHandler? WebServerStarting;
#region Web server managament
private WebServer CreateWebServer()
{
Server?.Dispose();
Server = null;
WebApiModule apiModule = new("/", JsonNetSerializer);
PluginsModule.ServerUrl = $"http://localhost:{_webServerPortSetting.Value}/";
WebServer server = new WebServer(o => o.WithUrlPrefix($"http://*:{_webServerPortSetting.Value}/").WithMode(HttpListenerMode.EmbedIO))
.WithLocalSessionManager()
.WithModule(PluginsModule);
// Add registered modules
foreach (WebModuleRegistration? webModule in _modules)
server = server.WithModule(webModule.CreateInstance());
server = server
.WithModule(apiModule)
.HandleHttpException((context, exception) => HandleHttpExceptionJson(context, exception))
.HandleUnhandledException(JsonExceptionHandlerCallback);
// Add registered controllers to the API module
foreach (WebApiControllerRegistration registration in _controllers)
apiModule.RegisterController(registration.ControllerType, (Func<WebApiController>) registration.UntypedFactory);
// Listen for state changes.
server.StateChanged += (s, e) => _logger.Verbose("WebServer new state - {state}", e.NewState);
// Store the URL in a webserver.txt file so that remote applications can find it
File.WriteAllText(Path.Combine(Constants.DataFolder, "webserver.txt"), PluginsModule.ServerUrl);
return server;
}
private void StartWebServer()
{
Server = CreateWebServer();
OnWebServerStarting();
Server.Start();
}
#endregion
#region Plugin endpoint management
public JsonPluginEndPoint<T> AddJsonEndPoint<T>(PluginFeature feature, string endPointName, Action<T> requestHandler)
{
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
JsonPluginEndPoint<T> endPoint = new(feature, endPointName, PluginsModule, requestHandler);
PluginsModule.AddPluginEndPoint(endPoint);
return endPoint;
}
public JsonPluginEndPoint<T> AddResponsiveJsonEndPoint<T>(PluginFeature feature, string endPointName, Func<T, object?> requestHandler)
{
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
JsonPluginEndPoint<T> endPoint = new(feature, endPointName, PluginsModule, requestHandler);
PluginsModule.AddPluginEndPoint(endPoint);
return endPoint;
}
public StringPluginEndPoint AddStringEndPoint(PluginFeature feature, string endPointName, Action<string> requestHandler)
{
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
StringPluginEndPoint endPoint = new(feature, endPointName, PluginsModule, requestHandler);
PluginsModule.AddPluginEndPoint(endPoint);
return endPoint;
}
public StringPluginEndPoint AddResponsiveStringEndPoint(PluginFeature feature, string endPointName, Func<string, string?> requestHandler)
{
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
StringPluginEndPoint endPoint = new(feature, endPointName, PluginsModule, requestHandler);
PluginsModule.AddPluginEndPoint(endPoint);
return endPoint;
}
public RawPluginEndPoint AddRawEndPoint(PluginFeature feature, string endPointName, Func<IHttpContext, Task> requestHandler)
{
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
if (requestHandler == null) throw new ArgumentNullException(nameof(requestHandler));
RawPluginEndPoint endPoint = new(feature, endPointName, PluginsModule, requestHandler);
PluginsModule.AddPluginEndPoint(endPoint);
return endPoint;
}
public DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel, new()
{
if (module == null) throw new ArgumentNullException(nameof(module));
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
DataModelJsonPluginEndPoint<T> endPoint = new(module, endPointName, PluginsModule);
PluginsModule.AddPluginEndPoint(endPoint);
return endPoint;
}
private void HandleDataModelRequest<T>(Module<T> module, T value) where T : DataModel, new()
{
}
public void RemovePluginEndPoint(PluginEndPoint endPoint)
{
PluginsModule.RemovePluginEndPoint(endPoint);
}
#endregion
#region Controller management
public void AddController<T>(PluginFeature feature) where T : WebApiController
{
_controllers.Add(new WebApiControllerRegistration<T>(feature));
StartWebServer();
}
public void RemoveController<T>() where T : WebApiController
{
_controllers.RemoveAll(r => r.ControllerType == typeof(T));
StartWebServer();
}
#endregion
#region Module management
public void AddModule(PluginFeature feature, Func<IWebModule> create)
{
if (feature == null) throw new ArgumentNullException(nameof(feature));
_modules.Add(new WebModuleRegistration(feature, create));
StartWebServer();
}
public void RemoveModule(Func<IWebModule> create)
{
_modules.RemoveAll(r => r.Create == create);
StartWebServer();
}
public void AddModule<T>(PluginFeature feature) where T : IWebModule
{
if (feature == null) throw new ArgumentNullException(nameof(feature));
if (_modules.Any(r => r.WebModuleType == typeof(T)))
return;
_modules.Add(new WebModuleRegistration(feature, typeof(T)));
StartWebServer();
}
public void RemoveModule<T>() where T : IWebModule
{
_modules.RemoveAll(r => r.WebModuleType == typeof(T));
StartWebServer();
}
#endregion
#region Handlers
private async Task JsonExceptionHandlerCallback(IHttpContext context, Exception exception)
{
context.Response.ContentType = MimeType.Json;
await using TextWriter writer = context.OpenResponseText();
string response = JsonConvert.SerializeObject(new Dictionary<string, object?>
{
{"StatusCode", context.Response.StatusCode},
{"StackTrace", exception.StackTrace},
{"Type", exception.GetType().FullName},
{"Message", exception.Message},
{"Data", exception.Data},
{"InnerException", exception.InnerException},
{"HelpLink", exception.HelpLink},
{"Source", exception.Source},
{"HResult", exception.HResult}
});
await writer.WriteAsync(response);
}
private async Task JsonNetSerializer(IHttpContext context, object? data)
{
context.Response.ContentType = MimeType.Json;
await using TextWriter writer = context.OpenResponseText();
string json = JsonConvert.SerializeObject(data, new JsonSerializerSettings {PreserveReferencesHandling = PreserveReferencesHandling.Objects});
await writer.WriteAsync(json);
}
private async Task HandleHttpExceptionJson(IHttpContext context, IHttpException httpException)
{
await context.SendStringAsync(JsonConvert.SerializeObject(httpException, Formatting.Indented), MimeType.Json, Encoding.UTF8);
}
#endregion
}