1
0
mirror of https://github.com/DarthAffe/RGB.NET.git synced 2025-12-13 01:58:30 +00:00

Merge pull request #364 from DarthAffe/Development

v2.0.0
This commit is contained in:
DarthAffe 2024-01-01 00:00:50 +01:00 committed by GitHub
commit 5a104eaf24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
366 changed files with 7418 additions and 2326 deletions

View File

@ -11,16 +11,19 @@ on:
jobs:
build:
runs-on: windows-2022
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
dotnet-version: |
8.0.x
7.0.x
6.0.x
- name: Git Semantic Version
id: versioning
uses: PaulHatch/semantic-version@v4.0.3
@ -33,12 +36,6 @@ jobs:
run: dotnet build --no-restore --configuration Release /p:Version=${{ steps.versioning.outputs.version }}
- name: Test
run: dotnet test --no-build --verbosity normal --configuration Release
- name: Upload a Build Artifact NET5
uses: actions/upload-artifact@v2.2.4
with:
name: RGB.NET-NET5
path: bin/net5.0/RGB.NET.*.dll
if-no-files-found: error
- name: Upload a Build Artifact NET6
uses: actions/upload-artifact@v2.2.4
with:
@ -51,6 +48,12 @@ jobs:
name: RGB.NET-NET7
path: bin/net7.0/RGB.NET.*.dll
if-no-files-found: error
- name: Upload a Build Artifact NET8
uses: actions/upload-artifact@v2.2.4
with:
name: RGB.NET-NET8
path: bin/net8.0/RGB.NET.*.dll
if-no-files-found: error
- name: Upload Nuget Build Artifact
uses: actions/upload-artifact@v2.2.4
with:
@ -59,3 +62,11 @@ jobs:
if-no-files-found: error
- name: Nuget Push
run: dotnet nuget push **\*.nupkg --skip-duplicate --api-key ${{ secrets.NUGET_TOKEN }} --source https://api.nuget.org/v3/index.json
- name: Symbols Push
run: dotnet nuget push **\*.snupkg --skip-duplicate --api-key ${{ secrets.NUGET_TOKEN }} --source https://api.nuget.org/v3/index.json
- name: Create Tag
uses: mathieudutour/github-tag-action@v6.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
custom_tag: ${{ steps.versioning.outputs.version }}
tag_prefix: v

View File

@ -7,14 +7,17 @@ on:
jobs:
build:
runs-on: windows-2022
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
dotnet-version: |
8.0.x
7.0.x
6.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build

View File

@ -10,16 +10,19 @@ on:
jobs:
build:
runs-on: windows-2022
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
dotnet-version: |
8.0.x
7.0.x
6.0.x
- name: Git Semantic Version
id: versioning
uses: PaulHatch/semantic-version@v4.0.3
@ -32,12 +35,6 @@ jobs:
run: dotnet build --no-restore --configuration Release /p:Version=${{ steps.versioning.outputs.version }}
- name: Test
run: dotnet test --no-build --verbosity normal --configuration Release
- name: Upload a Build Artifact NET5
uses: actions/upload-artifact@v2.2.4
with:
name: RGB.NET-NET5
path: bin/net5.0/RGB.NET.*.dll
if-no-files-found: error
- name: Upload a Build Artifact NET6
uses: actions/upload-artifact@v2.2.4
with:
@ -50,6 +47,12 @@ jobs:
name: RGB.NET-NET7
path: bin/net7.0/RGB.NET.*.dll
if-no-files-found: error
- name: Upload a Build Artifact NET8
uses: actions/upload-artifact@v2.2.4
with:
name: RGB.NET-NET8
path: bin/net8.0/RGB.NET.*.dll
if-no-files-found: error
- name: Upload Nuget Build Artifact
uses: actions/upload-artifact@v2.2.4
with:
@ -61,6 +64,6 @@ jobs:
with:
tag_name: ${{ steps.versioning.outputs.version_tag }}
generate_release_notes: true
files: bin/net7.0/RGB.NET.*.dll
files: bin/net8.0/RGB.NET.*.dll
- name: Nuget Push
run: dotnet nuget push **\*.nupkg --skip-duplicate --api-key ${{ secrets.NUGET_TOKEN }} --source https://api.nuget.org/v3/index.json

View File

@ -6,7 +6,7 @@ namespace RGB.NET.Core;
/// <summary>
/// Represents the default-behavior for the work with colors.
/// </summary>
public class DefaultColorBehavior : IColorBehavior
public sealed class DefaultColorBehavior : IColorBehavior
{
#region Methods
@ -14,7 +14,7 @@ public class DefaultColorBehavior : IColorBehavior
/// Converts the individual byte values of this <see cref="Color"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the individual byte values of this <see cref="Color"/>. For example "[A: 255, R: 255, G: 0, B: 0]".</returns>
public virtual string ToString(in Color color) => $"[A: {color.GetA()}, R: {color.GetR()}, G: {color.GetG()}, B: {color.GetB()}]";
public string ToString(in Color color) => $"[A: {color.GetA()}, R: {color.GetR()}, G: {color.GetG()}, B: {color.GetB()}]";
/// <summary>
/// Tests whether the specified object is a <see cref="Color" /> and is equivalent to this <see cref="Color" />.
@ -22,28 +22,31 @@ public class DefaultColorBehavior : IColorBehavior
/// <param name="color">The color to test.</param>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Color" /> equivalent to this <see cref="Color" />; otherwise, <c>false</c>.</returns>
public virtual bool Equals(in Color color, object? obj)
{
if (obj is not Color color2) return false;
return color.A.EqualsInTolerance(color2.A)
&& color.R.EqualsInTolerance(color2.R)
&& color.G.EqualsInTolerance(color2.G)
&& color.B.EqualsInTolerance(color2.B);
}
public bool Equals(in Color color, object? obj) => obj is Color color2 && Equals(color, color2);
/// <summary>
/// Tests whether the specified object is a <see cref="Color" /> and is equivalent to this <see cref="Color" />.
/// </summary>
/// <param name="color">The first color to test.</param>
/// <param name="color2">The second color to test.</param>
/// <returns><c>true</c> if <paramref name="color2" /> equivalent to this <see cref="Color" />; otherwise, <c>false</c>.</returns>
public bool Equals(in Color color, in Color color2) => color.A.EqualsInTolerance(color2.A)
&& color.R.EqualsInTolerance(color2.R)
&& color.G.EqualsInTolerance(color2.G)
&& color.B.EqualsInTolerance(color2.B);
/// <summary>
/// Returns a hash code for this <see cref="Color" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Color" />.</returns>
public virtual int GetHashCode(in Color color) => HashCode.Combine(color.A, color.R, color.G, color.B);
public int GetHashCode(in Color color) => HashCode.Combine(color.A, color.R, color.G, color.B);
/// <summary>
/// Blends a <see cref="Color"/> over this color.
/// </summary>
/// <param name="baseColor">The <see cref="Color"/> to to blend over.</param>
/// <param name="blendColor">The <see cref="Color"/> to blend.</param>
public virtual Color Blend(in Color baseColor, in Color blendColor)
public Color Blend(in Color baseColor, in Color blendColor)
{
if (blendColor.A.EqualsInTolerance(0)) return baseColor;

View File

@ -20,6 +20,14 @@ public interface IColorBehavior
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Color" /> equivalent to this <see cref="Color" />; otherwise, <c>false</c>.</returns>
bool Equals(in Color color, object? obj);
/// <summary>
/// Tests whether the specified object is a <see cref="Color" /> and is equivalent to this <see cref="Color" />.
/// </summary>
/// <param name="color">The first color to test.</param>
/// <param name="color2">The second color to test.</param>
/// <returns><c>true</c> if <paramref name="color2" /> equivalent to this <see cref="Color" />; otherwise, <c>false</c>.</returns>
bool Equals(in Color color, in Color color2);
/// <summary>
/// Returns a hash code for this <see cref="Color" />.
/// </summary>

View File

@ -11,7 +11,7 @@ namespace RGB.NET.Core;
/// Represents an ARGB (alpha, red, green, blue) color.
/// </summary>
[DebuggerDisplay("[A: {A}, R: {R}, G: {G}, B: {B}]")]
public readonly struct Color
public readonly struct Color : IEquatable<Color>
{
#region Constants
@ -196,6 +196,13 @@ public readonly struct Color
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Color" /> equivalent to this <see cref="Color" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj) => Behavior.Equals(this, obj);
/// <summary>
/// Tests whether the specified <see cref="Color" /> is equivalent to this <see cref="Color" />, as defined by the current <see cref="Behavior"/>.
/// </summary>
/// <param name="other">The color to test.</param>
/// <returns><c>true</c> if <paramref name="other" /> is equivalent to this <see cref="Color" />; otherwise, <c>false</c>.</returns>
public bool Equals(Color other) => Behavior.Equals(this, other);
/// <summary>
/// Returns a hash code for this <see cref="Color" />, as defined by the current <see cref="Behavior"/>.
/// </summary>

View File

@ -11,7 +11,7 @@ public abstract class AbstractDecoratable<T> : AbstractBindable, IDecoratable<T>
{
#region Properties & Fields
private readonly List<T> _decorators = new();
private readonly List<T> _decorators = [];
/// <inheritdoc />
public IReadOnlyList<T> Decorators { get; }

View File

@ -29,7 +29,7 @@ public abstract class AbstractDecorator : AbstractBindable, IDecorator
/// <summary>
/// Gets a readonly-list of all <see cref="IDecoratable"/> this decorator is attached to.
/// </summary>
protected List<IDecoratable> DecoratedObjects { get; } = new();
protected List<IDecoratable> DecoratedObjects { get; } = [];
#endregion
@ -46,14 +46,14 @@ public abstract class AbstractDecorator : AbstractBindable, IDecorator
/// </summary>
protected virtual void Detach()
{
List<IDecoratable> decoratables = new(DecoratedObjects);
List<IDecoratable> decoratables = [..DecoratedObjects];
foreach (IDecoratable decoratable in decoratables)
{
IEnumerable<Type> types = decoratable.GetType().GetInterfaces().Where(t => t.IsGenericType
&& (t.Name == typeof(IDecoratable<>).Name)
&& t.GenericTypeArguments[0].IsInstanceOfType(this));
foreach (Type decoratableType in types)
decoratableType.GetMethod(nameof(IDecoratable<IDecorator>.RemoveDecorator))?.Invoke(decoratable, new object[] { this });
decoratableType.GetMethod(nameof(IDecoratable<IDecorator>.RemoveDecorator))?.Invoke(decoratable, [this]);
}
}

View File

@ -6,8 +6,7 @@ namespace RGB.NET.Core;
/// <summary>
/// Represents a basic decoratable.
/// </summary>
public interface IDecoratable : INotifyPropertyChanged
{ }
public interface IDecoratable : INotifyPropertyChanged;
/// <inheritdoc />
/// <summary>

View File

@ -4,5 +4,4 @@
/// <summary>
/// Represents a basic decorator decorating a <see cref="T:RGB.NET.Core.ILedGroup" />.
/// </summary>
public interface ILedGroupDecorator : IDecorator
{ }
public interface ILedGroupDecorator : IDecorator;

View File

@ -2,9 +2,12 @@
// ReSharper disable UnusedMember.Global
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
namespace RGB.NET.Core;
@ -50,7 +53,7 @@ public abstract class AbstractRGBDevice<TDeviceInfo> : Placeable, IRGBDevice<TDe
/// <summary>
/// Gets a dictionary containing all <see cref="Led"/> of the <see cref="IRGBDevice"/>.
/// </summary>
protected Dictionary<LedId, Led> LedMapping { get; } = new();
protected Dictionary<LedId, Led> LedMapping { get; } = [];
/// <summary>
/// Gets the update queue used to update this device.
@ -84,6 +87,8 @@ public abstract class AbstractRGBDevice<TDeviceInfo> : Placeable, IRGBDevice<TDe
{
this.DeviceInfo = deviceInfo;
this.UpdateQueue = updateQueue;
UpdateQueue.AddReferencingObject(this);
}
#endregion
@ -97,12 +102,7 @@ public abstract class AbstractRGBDevice<TDeviceInfo> : Placeable, IRGBDevice<TDe
DeviceUpdate();
// Send LEDs to SDK
List<Led> ledsToUpdate = GetLedsToUpdate(flushLeds).ToList();
foreach (Led led in ledsToUpdate)
led.Update();
UpdateLeds(ledsToUpdate);
UpdateLeds(GetLedsToUpdate(flushLeds));
}
/// <summary>
@ -110,7 +110,7 @@ public abstract class AbstractRGBDevice<TDeviceInfo> : Placeable, IRGBDevice<TDe
/// </summary>
/// <param name="flushLeds">Forces all LEDs to be treated as dirty.</param>
/// <returns>The collection LEDs to update.</returns>
protected virtual IEnumerable<Led> GetLedsToUpdate(bool flushLeds) => ((RequiresFlush || flushLeds) ? LedMapping.Values : LedMapping.Values.Where(x => x.IsDirty)).Where(led => led.RequestedColor?.A > 0);
protected virtual IEnumerable<Led> GetLedsToUpdate(bool flushLeds) => ((RequiresFlush || flushLeds || UpdateQueue.RequiresFlush) ? LedMapping.Values : LedMapping.Values.Where(x => x.IsDirty)).Where(led => led.RequestedColor?.A > 0);
/// <summary>
/// Gets an enumerable of a custom data and color tuple for the specified leds.
@ -121,45 +121,55 @@ public abstract class AbstractRGBDevice<TDeviceInfo> : Placeable, IRGBDevice<TDe
/// </remarks>
/// <param name="leds">The enumerable of leds to convert.</param>
/// <returns>The enumerable of custom data and color tuples for the specified leds.</returns>
protected virtual IEnumerable<(object key, Color color)> GetUpdateData(IEnumerable<Led> leds)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected (object key, Color color) GetUpdateData(Led led)
{
if (ColorCorrections.Count > 0)
{
foreach (Led led in leds)
{
Color color = led.Color;
object key = led.CustomData ?? led.Id;
Color color = led.Color;
object key = led.CustomData ?? led.Id;
foreach (IColorCorrection colorCorrection in ColorCorrections)
colorCorrection.ApplyTo(ref color);
// ReSharper disable once ForCanBeConvertedToForeach - This causes an allocation that's not really needed here
for (int i = 0; i < ColorCorrections.Count; i++)
ColorCorrections[i].ApplyTo(ref color);
yield return (key, color);
}
}
else
{
foreach (Led led in leds)
{
Color color = led.Color;
object key = led.CustomData ?? led.Id;
yield return (key, color);
}
}
return (key, color);
}
/// <summary>
/// Sends all the updated <see cref="Led"/> to the device.
/// </summary>
protected virtual void UpdateLeds(IEnumerable<Led> ledsToUpdate) => UpdateQueue.SetData(GetUpdateData(ledsToUpdate));
protected virtual void UpdateLeds(IEnumerable<Led> ledsToUpdate)
{
(object key, Color color)[] buffer = ArrayPool<(object, Color)>.Shared.Rent(LedMapping.Count);
int counter = 0;
foreach (Led led in ledsToUpdate)
{
led.Update();
buffer[counter] = GetUpdateData(led);
++counter;
}
UpdateQueue.SetData(new ReadOnlySpan<(object, Color)>(buffer)[..counter]);
ArrayPool<(object, Color)>.Shared.Return(buffer);
}
/// <inheritdoc />
public virtual void Dispose()
{
try { UpdateQueue.Dispose(); } catch { /* :( */ }
try
{
UpdateQueue.RemoveReferencingObject(this);
if (!UpdateQueue.HasActiveReferences())
UpdateQueue.Dispose();
}
catch { /* :( */ }
try { LedMapping.Clear(); } catch { /* this really shouldn't happen */ }
IdGenerator.ResetCounter(GetType().Assembly);
GC.SuppressFinalize(this);
}
/// <summary>

View File

@ -12,6 +12,8 @@ public abstract class AbstractRGBDeviceProvider : IRGBDeviceProvider
{
#region Properties & Fields
private bool _isDisposed = false;
private readonly double _defaultUpdateRateHardLimit;
/// <inheritdoc />
@ -20,14 +22,19 @@ public abstract class AbstractRGBDeviceProvider : IRGBDeviceProvider
/// <inheritdoc />
public bool ThrowsExceptions { get; protected set; }
/// <summary>
/// The list of devices managed by this device-provider.
/// </summary>
protected List<IRGBDevice> InternalDevices { get; } = [];
/// <inheritdoc />
public virtual IEnumerable<IRGBDevice> Devices { get; protected set; } = Enumerable.Empty<IRGBDevice>();
public virtual IReadOnlyList<IRGBDevice> Devices => new ReadOnlyCollection<IRGBDevice>(InternalDevices);
/// <summary>
/// Gets the dictionary containing the registered update triggers.
/// Normally <see cref="UpdateTriggers"/> should be used to access them.
/// </summary>
protected Dictionary<int, IDeviceUpdateTrigger> UpdateTriggerMapping { get; } = new();
protected Dictionary<int, IDeviceUpdateTrigger> UpdateTriggerMapping { get; } = [];
/// <inheritdoc />
public IReadOnlyList<(int id, IDeviceUpdateTrigger trigger)> UpdateTriggers => new ReadOnlyCollection<(int id, IDeviceUpdateTrigger trigger)>(UpdateTriggerMapping.Select(x => (x.Key, x.Value)).ToList());
@ -39,6 +46,9 @@ public abstract class AbstractRGBDeviceProvider : IRGBDeviceProvider
/// <inheritdoc />
public event EventHandler<ExceptionEventArgs>? Exception;
/// <inheritdoc />
public event EventHandler<DevicesChangedEventArgs>? DevicesChanged;
#endregion
#region Constructors
@ -52,6 +62,8 @@ public abstract class AbstractRGBDeviceProvider : IRGBDeviceProvider
this._defaultUpdateRateHardLimit = defaultUpdateRateHardLimit;
}
~AbstractRGBDeviceProvider() => Dispose(false);
#endregion
#region Methods
@ -59,6 +71,8 @@ public abstract class AbstractRGBDeviceProvider : IRGBDeviceProvider
/// <inheritdoc />
public bool Initialize(RGBDeviceType loadFilter = RGBDeviceType.All, bool throwExceptions = false)
{
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
ThrowsExceptions = throwExceptions;
try
@ -67,7 +81,8 @@ public abstract class AbstractRGBDeviceProvider : IRGBDeviceProvider
InitializeSDK();
Devices = new ReadOnlyCollection<IRGBDevice>(GetLoadedDevices(loadFilter).ToList());
foreach (IRGBDevice device in GetLoadedDevices(loadFilter))
AddDevice(device);
foreach (IDeviceUpdateTrigger updateTrigger in UpdateTriggerMapping.Values)
updateTrigger.Start();
@ -99,7 +114,9 @@ public abstract class AbstractRGBDeviceProvider : IRGBDeviceProvider
/// <returns>The filtered collection of loaded devices.</returns>
protected virtual IEnumerable<IRGBDevice> GetLoadedDevices(RGBDeviceType loadFilter)
{
List<IRGBDevice> devices = new();
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
List<IRGBDevice> devices = [];
foreach (IRGBDevice device in LoadDevices())
{
try
@ -143,6 +160,8 @@ public abstract class AbstractRGBDeviceProvider : IRGBDeviceProvider
/// <returns>The update trigger mapped to the specified id.</returns>
protected virtual IDeviceUpdateTrigger GetUpdateTrigger(int id = -1, double? updateRateHardLimit = null)
{
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
if (!UpdateTriggerMapping.TryGetValue(id, out IDeviceUpdateTrigger? updaeTrigger))
UpdateTriggerMapping[id] = (updaeTrigger = CreateUpdateTrigger(id, updateRateHardLimit ?? _defaultUpdateRateHardLimit));
@ -162,23 +181,61 @@ public abstract class AbstractRGBDeviceProvider : IRGBDeviceProvider
/// </summary>
protected virtual void Reset()
{
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
foreach (IDeviceUpdateTrigger updateTrigger in UpdateTriggerMapping.Values)
updateTrigger.Dispose();
foreach (IRGBDevice device in Devices)
device.Dispose();
Devices = Enumerable.Empty<IRGBDevice>();
List<IRGBDevice> devices = [..InternalDevices];
foreach (IRGBDevice device in devices)
RemoveDevice(device);
UpdateTriggerMapping.Clear();
IsInitialized = false;
}
/// <summary>
/// Adds the provided device to the list of managed devices.
/// </summary>
/// <param name="device">The device to add.</param>
/// <returns><c>true</c> if the device was added successfully; otherwise <c>false</c>.</returns>
protected virtual bool AddDevice(IRGBDevice device)
{
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
if (InternalDevices.Contains(device)) return false;
InternalDevices.Add(device);
try { OnDevicesChanged(DevicesChangedEventArgs.CreateDevicesAddedArgs(device)); } catch { /* we don't want to throw due to bad event handlers */ }
return true;
}
/// <summary>
/// Removes the provided device from the list of managed devices.
/// </summary>
/// <param name="device">The device to remove.</param>
/// <returns><c>true</c> if the device was removed successfully; otherwise <c>false</c>.</returns>
protected virtual bool RemoveDevice(IRGBDevice device)
{
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
if (!InternalDevices.Remove(device)) return false;
try { OnDevicesChanged(DevicesChangedEventArgs.CreateDevicesRemovedArgs(device)); } catch { /* we don't want to throw due to bad event handlers */ }
return true;
}
/// <summary>
/// Triggers the <see cref="Exception"/>-event and throws the specified exception if <see cref="ThrowsExceptions"/> is true and it is not overriden in the event.
/// </summary>
/// <param name="ex">The exception to throw.</param>
/// <param name="isCritical">Indicates if the exception is critical for device provider to work correctly.</param>
protected virtual void Throw(Exception ex, bool isCritical = false)
public virtual void Throw(Exception ex, bool isCritical = false)
{
ExceptionEventArgs args = new(ex, isCritical, ThrowsExceptions);
try { OnException(args); } catch { /* we don't want to throw due to bad event handlers */ }
@ -188,18 +245,38 @@ public abstract class AbstractRGBDeviceProvider : IRGBDeviceProvider
}
/// <summary>
/// Throws the <see cref="Exception"/> event.
/// Throws the <see cref="Exception"/>.event.
/// </summary>
/// <param name="args">The parameters passed to the event.</param>
protected virtual void OnException(ExceptionEventArgs args) => Exception?.Invoke(this, args);
/// <summary>
/// Throws the <see cref="DevicesChanged"/>-event.
/// </summary>
/// <param name="args">The parameters passed to the event.</param>
protected virtual void OnDevicesChanged(DevicesChangedEventArgs args) => DevicesChanged?.Invoke(this, args);
/// <inheritdoc />
public virtual void Dispose()
public void Dispose()
{
Reset();
if (_isDisposed) return;
try
{
Dispose(true);
}
catch { /* don't throw in dispose! */ }
GC.SuppressFinalize(this);
_isDisposed = true;
}
/// <summary>
/// Disposes the object and frees all resources.
/// </summary>
/// <param name="disposing"><c>true</c> if explicitely called through the Dispose-Method, <c>false</c> if called by the destructor.</param>
protected virtual void Dispose(bool disposing) => Reset();
#endregion
}

View File

@ -20,12 +20,16 @@ public interface IRGBDeviceProvider : IDisposable
/// <summary>
/// Indicates if exceptions in the device provider are thrown or silently ignored.
/// </summary>
/// <remarks>
/// This should only be set to <c>true</c> for debugging/development purposes.
/// Production code should use the <see cref="Exception"/>-Event to handle exceptions.
/// </remarks>
bool ThrowsExceptions { get; }
/// <summary>
/// Gets a collection of <see cref="IRGBDevice"/> loaded by this <see cref="IRGBDeviceProvider"/>.
/// </summary>
IEnumerable<IRGBDevice> Devices { get; }
IReadOnlyList<IRGBDevice> Devices { get; }
/// <summary>
/// Gets a collection <see cref="IDeviceUpdateTrigger"/> registered to this device provider.
@ -41,6 +45,11 @@ public interface IRGBDeviceProvider : IDisposable
/// </summary>
event EventHandler<ExceptionEventArgs>? Exception;
/// <summary>
/// Occures when the devices provided by this device provider changed.
/// </summary>
event EventHandler<DevicesChangedEventArgs>? DevicesChanged;
#endregion
#region Methods

View File

@ -93,6 +93,11 @@ public enum RGBDeviceType
/// </summary>
LedController = 1 << 15,
/// <summary>
/// Represents a game controller.
/// </summary>
GameController = 1 << 16,
/// <summary>
/// Represents a device where the type is not known or not present in the list.
/// </summary>

View File

@ -3,5 +3,4 @@
/// <summary>
/// Represents a cooler-device
/// </summary>
public interface ICooler : IRGBDevice
{ }
public interface ICooler : IRGBDevice;

View File

@ -3,5 +3,4 @@
/// <summary>
/// Represents a DRAM-device
/// </summary>
public interface IDRAM : IRGBDevice
{ }
public interface IDRAM : IRGBDevice;

View File

@ -3,5 +3,4 @@
/// <summary>
/// represents a fan-device
/// </summary>
public interface IFan : IRGBDevice
{ }
public interface IFan : IRGBDevice;

View File

@ -0,0 +1,6 @@
namespace RGB.NET.Core;
/// <summary>
/// Represents a gamecontroller-device
/// </summary>
public interface IGameController: IRGBDevice;

View File

@ -3,5 +3,4 @@
/// <summary>
/// Represents a graphics-card-device
/// </summary>
public interface IGraphicsCard : IRGBDevice
{ }
public interface IGraphicsCard : IRGBDevice;

View File

@ -3,5 +3,4 @@
/// <summary>
/// Represents a headset-device
/// </summary>
public interface IHeadset : IRGBDevice
{ }
public interface IHeadset : IRGBDevice;

View File

@ -3,5 +3,4 @@
/// <summary>
/// Represents a headset-stand-device
/// </summary>
public interface IHeadsetStand : IRGBDevice
{ }
public interface IHeadsetStand : IRGBDevice;

View File

@ -3,5 +3,4 @@
/// <summary>
/// Represents a keypad-device
/// </summary>
public interface IKeypad : IRGBDevice
{ }
public interface IKeypad : IRGBDevice;

View File

@ -3,5 +3,4 @@
/// <summary>
/// Represents a led-matrix-device
/// </summary>
public interface ILedMatrix : IRGBDevice
{ }
public interface ILedMatrix : IRGBDevice;

View File

@ -3,5 +3,4 @@
/// <summary>
/// Represents a led-stripe-device
/// </summary>
public interface ILedStripe : IRGBDevice
{ }
public interface ILedStripe : IRGBDevice;

View File

@ -3,5 +3,4 @@
/// <summary>
/// Represents a mainboard-device
/// </summary>
public interface IMainboard : IRGBDevice
{ }
public interface IMainboard : IRGBDevice;

View File

@ -3,5 +3,4 @@
/// <summary>
/// Represents a mouse-device
/// </summary>
public interface IMouse : IRGBDevice
{ }
public interface IMouse : IRGBDevice;

View File

@ -3,5 +3,4 @@
/// <summary>
/// Represents a mousepad-device
/// </summary>
public interface IMousepad : IRGBDevice
{ }
public interface IMousepad : IRGBDevice;

View File

@ -3,5 +3,4 @@
/// <summary>
/// Represents a speaker-device
/// </summary>
public interface ISpeaker : IRGBDevice
{ }
public interface ISpeaker : IRGBDevice;

View File

@ -3,5 +3,4 @@
/// <summary>
/// Represents a device with unkown or not specified type.
/// </summary>
public interface IUnknownDevice : IRGBDevice
{ }
public interface IUnknownDevice : IRGBDevice;

View File

@ -0,0 +1,27 @@
using System;
namespace RGB.NET.Core;
public sealed class DevicesChangedEventArgs(IRGBDevice device, DevicesChangedEventArgs.DevicesChangedAction action)
: EventArgs
{
#region Properties & Fields
public IRGBDevice Device { get; } = device;
public DevicesChangedAction Action { get; } = action;
#endregion
#region Methods
public static DevicesChangedEventArgs CreateDevicesAddedArgs(IRGBDevice addedDevice) => new(addedDevice, DevicesChangedAction.Added);
public static DevicesChangedEventArgs CreateDevicesRemovedArgs(IRGBDevice removedDevice) => new(removedDevice, DevicesChangedAction.Removed);
#endregion
public enum DevicesChangedAction
{
Added,
Removed
}
}

View File

@ -6,5 +6,4 @@ namespace RGB.NET.Core;
/// <summary>
/// Represents the information supplied with an <see cref="E:RGB.NET.Core.RGBSurface.Updated" />-event.
/// </summary>
public class UpdatedEventArgs : EventArgs
{ }
public class UpdatedEventArgs : EventArgs;

View File

@ -26,7 +26,7 @@ public class UpdatingEventArgs : EventArgs
/// <summary>
/// Gets the custom-data provided by the trigger for this update.
/// </summary>
public CustomUpdateData CustomData { get; }
public ICustomUpdateData CustomData { get; }
#endregion
@ -39,7 +39,7 @@ public class UpdatingEventArgs : EventArgs
/// <param name="deltaTime">The elapsed time (in seconds) since the last update.</param>
/// <param name="trigger">The trigger causing this update.</param>
/// <param name="customData">The custom-data provided by the trigger for this update.</param>
public UpdatingEventArgs(double deltaTime, IUpdateTrigger? trigger, CustomUpdateData customData)
public UpdatingEventArgs(double deltaTime, IUpdateTrigger? trigger, ICustomUpdateData customData)
{
this.DeltaTime = deltaTime;
this.Trigger = trigger;

View File

@ -151,12 +151,13 @@ public static class RectangleExtensions
/// <returns>A array of <see cref="Point"/> containing the new locations of the corners of the original rectangle.</returns>
public static Point[] Rotate(this in Rectangle rect, in Rotation rotation, in Point origin = new())
{
Point[] points = {
rect.Location, // top left
new(rect.Location.X + rect.Size.Width, rect.Location.Y), // top right
new(rect.Location.X + rect.Size.Width, rect.Location.Y + rect.Size.Height), // bottom right
new(rect.Location.X, rect.Location.Y + rect.Size.Height), // bottom right
};
Point[] points =
[
rect.Location, // top left
new Point(rect.Location.X + rect.Size.Width, rect.Location.Y), // top right
new Point(rect.Location.X + rect.Size.Width, rect.Location.Y + rect.Size.Height), // bottom right
new Point(rect.Location.X, rect.Location.Y + rect.Size.Height), // bottom right
];
float sin = MathF.Sin(rotation.Radians);
float cos = MathF.Cos(rotation.Radians);

View File

@ -0,0 +1,6 @@
namespace RGB.NET.Core;
public static class ReferenceCountingExtension
{
public static bool HasActiveReferences(this IReferenceCounting target) => target.ActiveReferenceCount > 0;
}

View File

@ -1,5 +1,7 @@
using System.Collections;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace RGB.NET.Core;
@ -51,11 +53,23 @@ public abstract class AbstractLedGroup : AbstractDecoratable<ILedGroupDecorator>
/// <inheritdoc />
public virtual void OnDetach() { }
/// <inheritdoc />
public virtual IList<Led> ToList() => GetLeds().ToList();
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <inheritdoc />
public IEnumerator<Led> GetEnumerator() => GetLeds().GetEnumerator();
/// <inheritdoc />
IDisposable? ILedGroup.ToListUnsafe(out IList<Led> leds) => ToListUnsafe(out leds);
protected virtual IDisposable? ToListUnsafe(out IList<Led> leds)
{
leds = ToList();
return null;
}
#endregion
}

View File

@ -1,6 +1,7 @@
// ReSharper disable UnusedMemberInSuper.Global
// ReSharper disable UnusedMember.Global
using System;
using System.Collections.Generic;
namespace RGB.NET.Core;
@ -39,4 +40,12 @@ public interface ILedGroup : IDecoratable<ILedGroupDecorator>, IEnumerable<Led>
/// Called when the <see cref="ILedGroup"/> is detached from the <see cref="RGBSurface"/>.
/// </summary>
void OnDetach();
/// <summary>
/// Returns a list containing all <see cref="Led"/> in this group.
/// </summary>
/// <returns>A list containing all <see cref="Led"/> in this group.</returns>
IList<Led> ToList();
internal IDisposable? ToListUnsafe(out IList<Led> leds);
}

View File

@ -1,7 +1,9 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
using System;
using System.Collections.Generic;
using System.Threading;
namespace RGB.NET.Core;
@ -9,14 +11,16 @@ namespace RGB.NET.Core;
/// <summary>
/// Represents a ledgroup containing arbitrary <see cref="T:RGB.NET.Core.Led" />.
/// </summary>
public class ListLedGroup : AbstractLedGroup
public sealed class ListLedGroup : AbstractLedGroup
{
#region Properties & Fields
private readonly ActionDisposable _unlockDisposable;
/// <summary>
/// Gets the list containing the <see cref="Led"/> of this <see cref="ListLedGroup"/>.
/// </summary>
protected IList<Led> GroupLeds { get; } = new List<Led>();
private readonly IList<Led> _groupLeds = new List<Led>();
#endregion
@ -29,7 +33,9 @@ public class ListLedGroup : AbstractLedGroup
/// <param name="surface">Specifies the surface to attach this group to or <c>null</c> if the group should not be attached on creation.</param>
public ListLedGroup(RGBSurface? surface)
: base(surface)
{ }
{
_unlockDisposable = new ActionDisposable(Unlock);
}
/// <inheritdoc />
/// <summary>
@ -40,6 +46,8 @@ public class ListLedGroup : AbstractLedGroup
public ListLedGroup(RGBSurface? surface, IEnumerable<Led> leds)
: base(surface)
{
_unlockDisposable = new ActionDisposable(Unlock);
AddLeds(leds);
}
@ -52,6 +60,8 @@ public class ListLedGroup : AbstractLedGroup
public ListLedGroup(RGBSurface? surface, params Led[] leds)
: base(surface)
{
_unlockDisposable = new ActionDisposable(Unlock);
AddLeds(leds);
}
@ -71,10 +81,10 @@ public class ListLedGroup : AbstractLedGroup
/// <param name="leds">The <see cref="Led"/> to add.</param>
public void AddLeds(IEnumerable<Led> leds)
{
lock (GroupLeds)
lock (_groupLeds)
foreach (Led led in leds)
if (!ContainsLed(led))
GroupLeds.Add(led);
_groupLeds.Add(led);
}
/// <summary>
@ -89,9 +99,9 @@ public class ListLedGroup : AbstractLedGroup
/// <param name="leds">The <see cref="Led"/> to remove.</param>
public void RemoveLeds(IEnumerable<Led> leds)
{
lock (GroupLeds)
lock (_groupLeds)
foreach (Led led in leds)
GroupLeds.Remove(led);
_groupLeds.Remove(led);
}
/// <summary>
@ -101,8 +111,8 @@ public class ListLedGroup : AbstractLedGroup
/// <returns><c>true</c> if the LED is contained by this ledgroup; otherwise, <c>false</c>.</returns>
public bool ContainsLed(Led led)
{
lock (GroupLeds)
return GroupLeds.Contains(led);
lock (_groupLeds)
return _groupLeds.Contains(led);
}
/// <summary>
@ -111,10 +121,10 @@ public class ListLedGroup : AbstractLedGroup
/// <param name="groupToMerge">The ledgroup to merge.</param>
public void MergeLeds(ILedGroup groupToMerge)
{
lock (GroupLeds)
lock (_groupLeds)
foreach (Led led in groupToMerge)
if (!GroupLeds.Contains(led))
GroupLeds.Add(led);
if (!_groupLeds.Contains(led))
_groupLeds.Add(led);
}
/// <inheritdoc />
@ -122,11 +132,27 @@ public class ListLedGroup : AbstractLedGroup
/// Gets a list containing the <see cref="T:RGB.NET.Core.Led" /> from this group.
/// </summary>
/// <returns>The list containing the <see cref="T:RGB.NET.Core.Led" />.</returns>
protected override IEnumerable<Led> GetLeds()
protected override IEnumerable<Led> GetLeds() => ToList();
/// <inheritdoc />
/// <summary>
/// Gets a list containing the <see cref="T:RGB.NET.Core.Led" /> from this group.
/// </summary>
/// <returns>The list containing the <see cref="T:RGB.NET.Core.Led" />.</returns>
public override IList<Led> ToList()
{
lock (GroupLeds)
return new List<Led>(GroupLeds);
lock (_groupLeds)
return new List<Led>(_groupLeds);
}
protected override IDisposable ToListUnsafe(out IList<Led> leds)
{
Monitor.Enter(_groupLeds);
leds = _groupLeds;
return _unlockDisposable;
}
private void Unlock() => Monitor.Exit(_groupLeds);
#endregion
}

View File

@ -46,7 +46,7 @@ public static class TimerHelper
}
// ReSharper disable once InconsistentNaming
private static readonly HashSet<HighResolutionTimerDisposable> _timerLeases = new();
private static readonly HashSet<HighResolutionTimerDisposable> _timerLeases = [];
#endregion
@ -143,7 +143,7 @@ public static class TimerHelper
/// </summary>
public static void DisposeAllHighResolutionTimerRequests()
{
List<HighResolutionTimerDisposable> timerLeases = new(_timerLeases);
List<HighResolutionTimerDisposable> timerLeases = [.._timerLeases];
foreach (HighResolutionTimerDisposable timer in timerLeases)
timer.Dispose();
}

View File

@ -12,9 +12,9 @@ public static class IdGenerator
#region Properties & Fields
// ReSharper disable InconsistentNaming
private static readonly HashSet<string> _registeredIds = new();
private static readonly Dictionary<Assembly, Dictionary<string, string>> _idMappings = new();
private static readonly Dictionary<Assembly, Dictionary<string, int>> _counter = new();
private static readonly HashSet<string> _registeredIds = [];
private static readonly Dictionary<Assembly, Dictionary<string, string>> _idMappings = [];
private static readonly Dictionary<Assembly, Dictionary<string, int>> _counter = [];
// ReSharper restore InconsistentNaming
#endregion
@ -33,8 +33,8 @@ public static class IdGenerator
{
if (!_idMappings.TryGetValue(callingAssembly, out Dictionary<string, string>? idMapping))
{
_idMappings.Add(callingAssembly, idMapping = new Dictionary<string, string>());
_counter.Add(callingAssembly, new Dictionary<string, int>());
_idMappings.Add(callingAssembly, idMapping = []);
_counter.Add(callingAssembly, []);
}
Dictionary<string, int> counterMapping = _counter[callingAssembly];
@ -50,8 +50,7 @@ public static class IdGenerator
idMapping.Add(id, mappedId);
}
if (!counterMapping.ContainsKey(mappedId))
counterMapping.Add(mappedId, 0);
counterMapping.TryAdd(mappedId, 0);
int counter = ++counterMapping[mappedId];
return counter <= 1 ? mappedId : $"{mappedId} ({counter})";

View File

@ -9,7 +9,7 @@ namespace RGB.NET.Core;
/// Represents a single LED of a RGB-device.
/// </summary>
[DebuggerDisplay("{Id} {Color}")]
public class Led : Placeable
public sealed class Led : Placeable
{
#region Properties & Fields

View File

@ -372,6 +372,14 @@ public enum LedId
Keyboard_Custom127 = 0x0000707F,
Keyboard_Custom128 = 0x00007080,
Keyboard_IndicatorNumLock = 0x00008001,
Keyboard_IndicatorCapsLock = 0x00008002,
Keyboard_IndicatorScrollLock = 0x00008003,
Keyboard_IndicatorWinLock = 0x00008004,
Keyboard_IndicatorPower = 0x00008005,
Keyboard_IndicatorMuted = 0x00008006,
Keyboard_IndicatorMacro = 0x00008007,
/*### Mouse ###*/
Mouse1 = 0x00100001,
Mouse2 = 0x00100002,
@ -6286,6 +6294,136 @@ public enum LedId
Cooler127 = 0x00D0007F,
Cooler128 = 0x00D00080,
/*### GameController ###*/
GameController1 = 0x00E00001,
GameController2 = 0x00E00002,
GameController3 = 0x00E00003,
GameController4 = 0x00E00004,
GameController5 = 0x00E00005,
GameController6 = 0x00E00006,
GameController7 = 0x00E00007,
GameController8 = 0x00E00008,
GameController9 = 0x00E00009,
GameController10 = 0x00E0000A,
GameController11 = 0x00E0000B,
GameController12 = 0x00E0000C,
GameController13 = 0x00E0000D,
GameController14 = 0x00E0000E,
GameController15 = 0x00E0000F,
GameController16 = 0x00E00010,
GameController17 = 0x00E00011,
GameController18 = 0x00E00012,
GameController19 = 0x00E00013,
GameController20 = 0x00E00014,
GameController21 = 0x00E00015,
GameController22 = 0x00E00016,
GameController23 = 0x00E00017,
GameController24 = 0x00E00018,
GameController25 = 0x00E00019,
GameController26 = 0x00E0001A,
GameController27 = 0x00E0001B,
GameController28 = 0x00E0001C,
GameController29 = 0x00E0001D,
GameController30 = 0x00E0001E,
GameController31 = 0x00E0001F,
GameController32 = 0x00E00020,
GameController33 = 0x00E00021,
GameController34 = 0x00E00022,
GameController35 = 0x00E00023,
GameController36 = 0x00E00024,
GameController37 = 0x00E00025,
GameController38 = 0x00E00026,
GameController39 = 0x00E00027,
GameController40 = 0x00E00028,
GameController41 = 0x00E00029,
GameController42 = 0x00E0002A,
GameController43 = 0x00E0002B,
GameController44 = 0x00E0002C,
GameController45 = 0x00E0002D,
GameController46 = 0x00E0002E,
GameController47 = 0x00E0002F,
GameController48 = 0x00E00030,
GameController49 = 0x00E00031,
GameController50 = 0x00E00032,
GameController51 = 0x00E00033,
GameController52 = 0x00E00034,
GameController53 = 0x00E00035,
GameController54 = 0x00E00036,
GameController55 = 0x00E00037,
GameController56 = 0x00E00038,
GameController57 = 0x00E00039,
GameController58 = 0x00E0003A,
GameController59 = 0x00E0003B,
GameController60 = 0x00E0003C,
GameController61 = 0x00E0003D,
GameController62 = 0x00E0003E,
GameController63 = 0x00E0003F,
GameController64 = 0x00E00040,
GameController65 = 0x00E00041,
GameController66 = 0x00E00042,
GameController67 = 0x00E00043,
GameController68 = 0x00E00044,
GameController69 = 0x00E00045,
GameController70 = 0x00E00046,
GameController71 = 0x00E00047,
GameController72 = 0x00E00048,
GameController73 = 0x00E00049,
GameController74 = 0x00E0004A,
GameController75 = 0x00E0004B,
GameController76 = 0x00E0004C,
GameController77 = 0x00E0004D,
GameController78 = 0x00E0004E,
GameController79 = 0x00E0004F,
GameController80 = 0x00E00050,
GameController81 = 0x00E00051,
GameController82 = 0x00E00052,
GameController83 = 0x00E00053,
GameController84 = 0x00E00054,
GameController85 = 0x00E00055,
GameController86 = 0x00E00056,
GameController87 = 0x00E00057,
GameController88 = 0x00E00058,
GameController89 = 0x00E00059,
GameController90 = 0x00E0005A,
GameController91 = 0x00E0005B,
GameController92 = 0x00E0005C,
GameController93 = 0x00E0005D,
GameController94 = 0x00E0005E,
GameController95 = 0x00E0005F,
GameController96 = 0x00E00060,
GameController97 = 0x00E00061,
GameController98 = 0x00E00062,
GameController99 = 0x00E00063,
GameController100 = 0x00E00064,
GameController101 = 0x00E00065,
GameController102 = 0x00E00066,
GameController103 = 0x00E00067,
GameController104 = 0x00E00068,
GameController105 = 0x00E00069,
GameController106 = 0x00E0006A,
GameController107 = 0x00E0006B,
GameController108 = 0x00E0006C,
GameController109 = 0x00E0006D,
GameController110 = 0x00E0006E,
GameController111 = 0x00E0006F,
GameController112 = 0x00E00070,
GameController113 = 0x00E00071,
GameController114 = 0x00E00072,
GameController115 = 0x00E00073,
GameController116 = 0x00E00074,
GameController117 = 0x00E00075,
GameController118 = 0x00E00076,
GameController119 = 0x00E00077,
GameController120 = 0x00E00078,
GameController121 = 0x00E00079,
GameController122 = 0x00E0007A,
GameController123 = 0x00E0007B,
GameController124 = 0x00E0007C,
GameController125 = 0x00E0007D,
GameController126 = 0x00E0007E,
GameController127 = 0x00E0007F,
GameController128 = 0x00E00080,
/*### Custom ###*/
Custom1 = 0x0FE00001,
Custom2 = 0x0FE00002,

View File

@ -8,13 +8,19 @@ namespace RGB.NET.Core;
/// Represents a mapping from <see cref="LedId"/> to a custom identifier.
/// </summary>
/// <typeparam name="T">The identifier the <see cref="LedId"/> is mapped to.</typeparam>
public class LedMapping<T> : IEnumerable<(LedId ledId, T mapping)>
public sealed class LedMapping<T> : IEnumerable<(LedId ledId, T mapping)>
where T : notnull
{
#region Constants
public static LedMapping<T> Empty { get; } = [];
#endregion
#region Properties & Fields
private readonly Dictionary<LedId, T> _mapping = new();
private readonly Dictionary<T, LedId> _reverseMapping = new();
private readonly Dictionary<LedId, T> _mapping = [];
private readonly Dictionary<T, LedId> _reverseMapping = [];
/// <summary>
/// Gets the number of entries in this mapping.

View File

@ -1,4 +1,5 @@
using System.ComponentModel;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace RGB.NET.Core;
@ -26,9 +27,9 @@ public abstract class AbstractBindable : IBindable
/// <typeparam name="T">Type of the property.</typeparam>
/// <param name="storage">Reference to the backing-filed.</param>
/// <param name="value">Value to apply.</param>
/// <returns><c>true</c> if the value needs to be updated; otherweise <c>false</c>.</returns>
/// <returns><c>true</c> if the value needs to be updated; otherwise <c>false</c>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected virtual bool RequiresUpdate<T>(ref T storage, T value) => !Equals(storage, value);
protected bool RequiresUpdate<T>(ref T storage, T value) => !EqualityComparer<T>.Default.Equals(storage, value);
/// <summary>
/// Checks if the property already matches the desired value and updates it if not.
@ -39,7 +40,7 @@ public abstract class AbstractBindable : IBindable
/// <param name="propertyName">Name of the property used to notify listeners. This value is optional
/// and can be provided automatically when invoked from compilers that support <see cref="CallerMemberNameAttribute"/>.</param>
/// <returns><c>true</c> if the value was changed, <c>false</c> if the existing value matched the desired value.</returns>
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string? propertyName = null)
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string? propertyName = null)
{
if (!RequiresUpdate(ref storage, value)) return false;
@ -54,7 +55,7 @@ public abstract class AbstractBindable : IBindable
/// </summary>
/// <param name="propertyName">Name of the property used to notify listeners. This value is optional
/// and can be provided automatically when invoked from compilers that support <see cref="CallerMemberNameAttribute"/>.</param>
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
#endregion

View File

@ -5,6 +5,4 @@ namespace RGB.NET.Core;
/// <summary>
/// Represents a basic bindable class which notifies when a property value changes.
/// </summary>
public interface IBindable : INotifyPropertyChanged
{
}
public interface IBindable : INotifyPropertyChanged;

View File

@ -0,0 +1,40 @@
using System.Collections.Generic;
namespace RGB.NET.Core;
public abstract class AbstractReferenceCounting : IReferenceCounting
{
#region Properties & Fields
private readonly HashSet<object> _referencingObjects = [];
/// <inheritdoc />
public int ActiveReferenceCount
{
get
{
lock (_referencingObjects)
return _referencingObjects.Count;
}
}
#endregion
#region Methods
/// <inheritdoc />
public void AddReferencingObject(object obj)
{
lock (_referencingObjects)
_referencingObjects.Add(obj);
}
/// <inheritdoc />
public void RemoveReferencingObject(object obj)
{
lock (_referencingObjects)
_referencingObjects.Remove(obj);
}
#endregion
}

View File

@ -0,0 +1,12 @@
using System;
namespace RGB.NET.Core;
public sealed class ActionDisposable(Action onDispose) : IDisposable
{
#region Methods
public void Dispose() => onDispose();
#endregion
}

View File

@ -0,0 +1,21 @@
namespace RGB.NET.Core;
public interface IReferenceCounting
{
/// <summary>
/// Gets the amount of currently registered referencing objects.
/// </summary>
public int ActiveReferenceCount { get; }
/// <summary>
/// Adds the given object to the list of referencing objects.
/// </summary>
/// <param name="obj">The object to add.</param>
public void AddReferencingObject(object obj);
/// <summary>
/// Removes the given object from the list of referencing objects.
/// </summary>
/// <param name="obj">The object to remove.</param>
public void RemoveReferencingObject(object obj);
}

View File

@ -211,7 +211,7 @@ public class Placeable : AbstractBindable, IPlaceable
/// </summary>
protected virtual void OnLocationChanged()
{
LocationChanged?.Invoke(this, new EventArgs());
LocationChanged?.Invoke(this, EventArgs.Empty);
UpdateActualPlaceableData();
}
@ -220,7 +220,7 @@ public class Placeable : AbstractBindable, IPlaceable
/// </summary>
protected virtual void OnSizeChanged()
{
SizeChanged?.Invoke(this, new EventArgs());
SizeChanged?.Invoke(this, EventArgs.Empty);
UpdateActualPlaceableData();
}
@ -229,7 +229,7 @@ public class Placeable : AbstractBindable, IPlaceable
/// </summary>
protected virtual void OnScaleChanged()
{
ScaleChanged?.Invoke(this, new EventArgs());
ScaleChanged?.Invoke(this, EventArgs.Empty);
UpdateActualPlaceableData();
}
@ -238,24 +238,24 @@ public class Placeable : AbstractBindable, IPlaceable
/// </summary>
protected virtual void OnRotationChanged()
{
RotationChanged?.Invoke(this, new EventArgs());
RotationChanged?.Invoke(this, EventArgs.Empty);
UpdateActualPlaceableData();
}
/// <summary>
/// Called when the <see cref="ActualLocation"/> property was changed.
/// </summary>
protected virtual void OnActualLocationChanged() => ActualLocationChanged?.Invoke(this, new EventArgs());
protected virtual void OnActualLocationChanged() => ActualLocationChanged?.Invoke(this, EventArgs.Empty);
/// <summary>
/// Called when the <see cref="ActualLocation"/> property was changed.
/// </summary>
protected virtual void OnActualSizeChanged() => ActualSizeChanged?.Invoke(this, new EventArgs());
protected virtual void OnActualSizeChanged() => ActualSizeChanged?.Invoke(this, EventArgs.Empty);
/// <summary>
/// Called when the <see cref="Boundary"/> property was changed.
/// </summary>
protected virtual void OnBoundaryChanged() => BoundaryChanged?.Invoke(this, new EventArgs());
protected virtual void OnBoundaryChanged() => BoundaryChanged?.Invoke(this, EventArgs.Empty);
#endregion
}

View File

@ -10,7 +10,7 @@ namespace RGB.NET.Core;
/// Represents a point consisting of a X- and a Y-position.
/// </summary>
[DebuggerDisplay("[X: {X}, Y: {Y}]")]
public readonly struct Point
public readonly struct Point : IEquatable<Point>
{
#region Constants
@ -59,18 +59,20 @@ public readonly struct Point
/// <returns>A string that contains the <see cref="X"/> and <see cref="Y"/> of this <see cref="Point"/>. For example "[X: 100, Y: 20]".</returns>
public override string ToString() => $"[X: {X}, Y: {Y}]";
/// <summary>
/// Tests whether the specified <see cref="Point" /> is equivalent to this <see cref="Point" />.
/// </summary>
/// <param name="other">The point to test.</param>
/// <returns><c>true</c> if <paramref name="other" /> is equivalent to this <see cref="Point" />; otherwise, <c>false</c>.</returns>
public bool Equals(Point other) => ((float.IsNaN(X) && float.IsNaN(other.X)) || X.EqualsInTolerance(other.X))
&& ((float.IsNaN(Y) && float.IsNaN(other.Y)) || Y.EqualsInTolerance(other.Y));
/// <summary>
/// Tests whether the specified object is a <see cref="Point" /> and is equivalent to this <see cref="Point" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Point" /> equivalent to this <see cref="Point" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj)
{
if (obj is not Point comparePoint) return false;
return ((float.IsNaN(X) && float.IsNaN(comparePoint.X)) || X.EqualsInTolerance(comparePoint.X))
&& ((float.IsNaN(Y) && float.IsNaN(comparePoint.Y)) || Y.EqualsInTolerance(comparePoint.Y));
}
public override bool Equals(object? obj) => obj is Point other && Equals(other);
/// <summary>
/// Returns a hash code for this <see cref="Point" />.

View File

@ -12,7 +12,7 @@ namespace RGB.NET.Core;
/// Represents a rectangle defined by it's position and it's size.
/// </summary>
[DebuggerDisplay("[Location: {Location}, Size: {Size}]")]
public readonly struct Rectangle
public readonly struct Rectangle : IEquatable<Rectangle>
{
#region Properties & Fields
@ -57,7 +57,8 @@ public readonly struct Rectangle
/// Initializes a new instance of the <see cref="Rectangle"/> class using the <see cref="Location"/>(0,0) and the specified <see cref="Core.Size"/>.
/// </summary>
/// <param name="size">The size of of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
public Rectangle(Size size) : this(new Point(), size)
public Rectangle(Size size)
: this(new Point(), size)
{ }
/// <summary>
@ -120,15 +121,13 @@ public readonly struct Rectangle
public Rectangle(params Point[] points)
: this(points.AsEnumerable())
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the specified list of <see cref="T:RGB.NET.Core.Point" />.
/// The <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" /> is calculated to contain all points provided as parameters.
/// </summary>
/// <param name="points">The list of <see cref="T:RGB.NET.Core.Point" /> used to calculate the <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" />.</param>
public Rectangle(IEnumerable<Point> points)
: this()
{
bool hasPoint = false;
float posX = float.MaxValue;
@ -145,13 +144,37 @@ public readonly struct Rectangle
posY2 = Math.Max(posY2, point.Y);
}
(Point location, Size size) = hasPoint ? InitializeFromPoints(new Point(posX, posY), new Point(posX2, posY2)) : InitializeFromPoints(new Point(0, 0), new Point(0, 0));
(Point location, Size size) = hasPoint ? InitializeFromPoints(new Point(posX, posY), new Point(posX2, posY2))
: InitializeFromPoints(new Point(0, 0), new Point(0, 0));
Location = location;
Size = size;
Center = new Point(Location.X + (Size.Width / 2.0f), Location.Y + (Size.Height / 2.0f));
}
internal Rectangle(IList<Led> leds)
{
float posX = float.MaxValue;
float posY = float.MaxValue;
float posX2 = float.MinValue;
float posY2 = float.MinValue;
// ReSharper disable once ForCanBeConvertedToForeach
for (int i = 0; i < leds.Count; i++)
{
Rectangle rectangle = leds[i].AbsoluteBoundary;
posX = Math.Min(posX, rectangle.Location.X);
posY = Math.Min(posY, rectangle.Location.Y);
posX2 = Math.Max(posX2, rectangle.Location.X + rectangle.Size.Width);
posY2 = Math.Max(posY2, rectangle.Location.Y + rectangle.Size.Height);
}
(Point location, Size size) = leds.Count > 0 ? InitializeFromPoints(new Point(posX, posY), new Point(posX2, posY2)) : InitializeFromPoints(new Point(0, 0), new Point(0, 0));
Location = location;
Size = size;
Center = new Point(Location.X + (Size.Width / 2.0f), Location.Y + (Size.Height / 2.0f));
}
#endregion
#region Methods
@ -172,35 +195,25 @@ public readonly struct Rectangle
/// <returns>A string that contains the <see cref="Location"/> and <see cref="Size"/> of this <see cref="Rectangle"/>. For example "[Location: [X: 100, Y: 10], Size: [Width: 20, Height: [40]]".</returns>
public override string ToString() => $"[Location: {Location}, Size: {Size}]";
/// <summary>
/// Tests whether the specified <see cref="Rectangle" /> is equivalent to this <see cref="Rectangle" />.
/// </summary>
/// <param name="other">The rectangle to test.</param>
/// <returns><c>true</c> if <paramref name="other" /> is equivalent to this <see cref="Rectangle" />; otherwise, <c>false</c>.</returns>
public bool Equals(Rectangle other) => (Location == other.Location) && (Size == other.Size);
/// <summary>
/// Tests whether the specified object is a <see cref="Rectangle" /> and is equivalent to this <see cref="Rectangle" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Rectangle" /> equivalent to this <see cref="Rectangle" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj)
{
if (obj is not Rectangle compareRect)
return false;
if (GetType() != compareRect.GetType())
return false;
return (Location == compareRect.Location) && (Size == compareRect.Size);
}
public override bool Equals(object? obj) => obj is Rectangle other && Equals(other);
/// <summary>
/// Returns a hash code for this <see cref="Rectangle" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Rectangle" />.</returns>
public override int GetHashCode()
{
unchecked
{
int hashCode = Location.GetHashCode();
hashCode = (hashCode * 397) ^ Size.GetHashCode();
return hashCode;
}
}
public override int GetHashCode() => HashCode.Combine(Location, Size);
#endregion

View File

@ -10,7 +10,7 @@ namespace RGB.NET.Core;
/// Represents an angular rotation.
/// </summary>
[DebuggerDisplay("[{" + nameof(Degrees) + "}°]")]
public readonly struct Rotation
public readonly struct Rotation : IEquatable<Rotation>
{
#region Constants

View File

@ -1,6 +1,7 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
using System;
using System.Diagnostics;
namespace RGB.NET.Core;
@ -9,7 +10,7 @@ namespace RGB.NET.Core;
/// Represents a scaling.
/// </summary>
[DebuggerDisplay("[Horizontal: {Horizontal}, Vertical: {Vertical}]")]
public readonly struct Scale
public readonly struct Scale : IEquatable<Scale>
{
#region Properties & Fields
@ -49,6 +50,12 @@ public readonly struct Scale
#region Methods
/// <summary>
/// Converts the <see cref="Horizontal"/> and <see cref="Vertical"/> value of this <see cref="Scale"/> to a human-readable string.
/// </summary>
/// <returns>A string that contains the <see cref="Horizontal"/> and <see cref="Vertical"/> value of this <see cref="Scale"/>. For example "[Horizontal: 1, Vertical: 0.5]".</returns>
public override string ToString() => $"[Horizontal: {Horizontal}, Vertical: {Vertical}]\"";
/// <summary>
/// Tests whether the specified <see cref="Scale"/> is equivalent to this <see cref="Scale" />.
/// </summary>
@ -67,7 +74,7 @@ public readonly struct Scale
/// Returns a hash code for this <see cref="Scale" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Scale" />.</returns>
public override int GetHashCode() { unchecked { return (Horizontal.GetHashCode() * 397) ^ Vertical.GetHashCode(); } }
public override int GetHashCode() => HashCode.Combine(Horizontal, Vertical);
/// <summary>
/// Deconstructs the scale into the horizontal and vertical value.

View File

@ -1,6 +1,7 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
using System;
using System.Diagnostics;
namespace RGB.NET.Core;
@ -9,7 +10,7 @@ namespace RGB.NET.Core;
/// Represents a size consisting of a width and a height.
/// </summary>
[DebuggerDisplay("[Width: {Width}, Height: {Height}]")]
public readonly struct Size
public readonly struct Size : IEquatable<Size>
{
#region Constants
@ -67,33 +68,26 @@ public readonly struct Size
/// <returns>A string that contains the <see cref="Width"/> and <see cref="Height"/> of this <see cref="Size"/>. For example "[Width: 100, Height: 20]".</returns>
public override string ToString() => $"[Width: {Width}, Height: {Height}]";
/// <summary>
/// Tests whether the specified <see cref="Size" /> is equivalent to this <see cref="Size" />.
/// </summary>
/// <param name="other">The size to test.</param>
/// <returns><c>true</c> if <paramref name="other" /> is equivalent to this <see cref="Size" />; otherwise, <c>false</c>.</returns>
public bool Equals(Size other) => ((float.IsNaN(Width) && float.IsNaN(other.Width)) || Width.EqualsInTolerance(other.Width))
&& ((float.IsNaN(Height) && float.IsNaN(other.Height)) || Height.EqualsInTolerance(other.Height));
/// <summary>
/// Tests whether the specified object is a <see cref="Size" /> and is equivalent to this <see cref="Size" />.
/// </summary>
/// <param name="obj">The object to test.</param>
/// <returns><c>true</c> if <paramref name="obj" /> is a <see cref="Size" /> equivalent to this <see cref="Size" />; otherwise, <c>false</c>.</returns>
public override bool Equals(object? obj)
{
if (obj is not Size size) return false;
(float width, float height) = size;
return ((float.IsNaN(Width) && float.IsNaN(width)) || Width.EqualsInTolerance(width))
&& ((float.IsNaN(Height) && float.IsNaN(height)) || Height.EqualsInTolerance(height));
}
public override bool Equals(object? obj) => obj is Size other && Equals(other);
/// <summary>
/// Returns a hash code for this <see cref="Size" />.
/// </summary>
/// <returns>An integer value that specifies the hash code for this <see cref="Size" />.</returns>
public override int GetHashCode()
{
unchecked
{
int hashCode = Width.GetHashCode();
hashCode = (hashCode * 397) ^ Height.GetHashCode();
return hashCode;
}
}
public override int GetHashCode() => HashCode.Combine(Width, Height);
/// <summary>
/// Deconstructs the size into the width and height value.

View File

@ -17,7 +17,7 @@ surface.AlignDevices();
surface.RegisterUpdateTrigger(new TimerUpdateTrigger());
```
## Basis Rendering
## Basic Rendering
```csharp
// Create a led-group containing all leds on the surface
ILedGroup allLeds = new ListLedGroup(surface, surface.Leds);

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net6.0;net5.0</TargetFrameworks>
<TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
@ -41,7 +41,7 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<DebugSymbols>true</DebugSymbols>
<Optimize>false</Optimize>
</PropertyGroup>
@ -49,7 +49,7 @@
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<Optimize>true</Optimize>
<NoWarn>$(NoWarn);CS1591;CS1572;CS1573</NoWarn>
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants>
<DefineConstants>RELEASE</DefineConstants>
</PropertyGroup>
<ItemGroup>

View File

@ -16,6 +16,7 @@
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=helper/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=ids/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=leds/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=misc/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=mvvm/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=positioning/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=rendering/@EntryIndexedValue">True</s:Boolean>

View File

@ -22,7 +22,7 @@ public sealed class RGBSurface : AbstractBindable, IDisposable
private readonly IList<IRGBDevice> _devices = new List<IRGBDevice>();
private readonly IList<IUpdateTrigger> _updateTriggers = new List<IUpdateTrigger>();
private readonly List<ILedGroup> _ledGroups = new();
private readonly List<ILedGroup> _ledGroups = [];
/// <summary>
/// Gets a readonly list containing all loaded <see cref="IRGBDevice"/>.
@ -132,11 +132,12 @@ public sealed class RGBSurface : AbstractBindable, IDisposable
/// Perform a full update for all devices. Updates only dirty <see cref="Led"/> by default, or all <see cref="Led"/>, if flushLeds is set to true.
/// </summary>
/// <param name="flushLeds">Specifies whether all <see cref="Led"/>, (including clean ones) should be updated.</param>
public void Update(bool flushLeds = false) => Update(null, new CustomUpdateData((CustomUpdateDataIndex.FLUSH_LEDS, flushLeds)));
//public void Update(bool flushLeds = false) => Update(null, new CustomUpdateData((CustomUpdateDataIndex.FLUSH_LEDS, flushLeds)));
public void Update(bool flushLeds = false) => Update(null, flushLeds ? DefaultCustomUpdateData.FLUSH : DefaultCustomUpdateData.NO_FLUSH);
private void Update(object? updateTrigger, CustomUpdateData customData) => Update(updateTrigger as IUpdateTrigger, customData);
private void Update(object? updateTrigger, ICustomUpdateData customData) => Update(updateTrigger as IUpdateTrigger, customData);
private void Update(IUpdateTrigger? updateTrigger, CustomUpdateData customData)
private void Update(IUpdateTrigger? updateTrigger, ICustomUpdateData customData)
{
try
{
@ -149,19 +150,25 @@ public sealed class RGBSurface : AbstractBindable, IDisposable
{
OnUpdating(updateTrigger, customData);
// ReSharper disable ForCanBeConvertedToForeach - 'for' has a performance benefit (no enumerator allocation) here and since 'Update' is considered a hot path it's optimized
if (render)
lock (_ledGroups)
{
// Render brushes
foreach (ILedGroup ledGroup in _ledGroups)
try { Render(ledGroup); }
for (int i = 0; i < _ledGroups.Count; i++)
{
try { Render(_ledGroups[i]); }
catch (Exception ex) { OnException(ex); }
}
}
if (updateDevices)
foreach (IRGBDevice device in _devices)
try { device.Update(flushLeds); }
for (int i = 0; i < _devices.Count; i++)
{
try { _devices[i].Update(flushLeds); }
catch (Exception ex) { OnException(ex); }
}
// ReSharper restore ForCanBeConvertedToForeach
OnUpdated();
}
@ -177,7 +184,7 @@ public sealed class RGBSurface : AbstractBindable, IDisposable
{
List<IRGBDevice> devices;
lock (Devices)
devices = new List<IRGBDevice>(_devices);
devices = [.._devices];
foreach (IRGBDevice device in devices)
try { Detach(device); }
@ -197,29 +204,31 @@ public sealed class RGBSurface : AbstractBindable, IDisposable
/// <exception cref="ArgumentException">Thrown if the <see cref="IBrush.CalculationMode"/> of the Brush is not valid.</exception>
private void Render(ILedGroup ledGroup)
{
IList<Led> leds = ledGroup.ToList();
IBrush? brush = ledGroup.Brush;
if ((brush == null) || !brush.IsEnabled) return;
IEnumerable<(RenderTarget renderTarget, Color color)> render;
switch (brush.CalculationMode)
using (ledGroup.ToListUnsafe(out IList<Led> leds))
{
case RenderMode.Relative:
Rectangle brushRectangle = new(leds.Select(led => led.AbsoluteBoundary));
Point offset = new(-brushRectangle.Location.X, -brushRectangle.Location.Y);
brushRectangle = brushRectangle.SetLocation(new Point(0, 0));
render = brush.Render(brushRectangle, leds.Select(led => new RenderTarget(led, led.AbsoluteBoundary.Translate(offset))));
break;
case RenderMode.Absolute:
render = brush.Render(Boundary, leds.Select(led => new RenderTarget(led, led.AbsoluteBoundary)));
break;
default:
throw new ArgumentException($"The CalculationMode '{brush.CalculationMode}' is not valid.");
}
IEnumerable<(RenderTarget renderTarget, Color color)> render;
switch (brush.CalculationMode)
{
case RenderMode.Relative:
Rectangle brushRectangle = new(leds);
Point offset = new(-brushRectangle.Location.X, -brushRectangle.Location.Y);
brushRectangle = brushRectangle.SetLocation(new Point(0, 0));
render = brush.Render(brushRectangle, leds.Select(led => new RenderTarget(led, led.AbsoluteBoundary.Translate(offset))));
break;
case RenderMode.Absolute:
render = brush.Render(Boundary, leds.Select(led => new RenderTarget(led, led.AbsoluteBoundary)));
break;
default:
throw new ArgumentException($"The CalculationMode '{brush.CalculationMode}' is not valid.");
}
foreach ((RenderTarget renderTarget, Color c) in render)
renderTarget.Led.Color = c;
foreach ((RenderTarget renderTarget, Color c) in render)
renderTarget.Led.Color = c;
}
}
/// <summary>
@ -358,7 +367,7 @@ public sealed class RGBSurface : AbstractBindable, IDisposable
/// <summary>
/// Handles the needed event-calls before updating.
/// </summary>
private void OnUpdating(IUpdateTrigger? trigger, CustomUpdateData customData)
private void OnUpdating(IUpdateTrigger? trigger, ICustomUpdateData customData)
{
try
{

View File

@ -74,9 +74,13 @@ public abstract class AbstractBrush : AbstractDecoratable<IBrushDecorator>, IBru
if (Decorators.Count == 0) return;
lock (Decorators)
foreach (IBrushDecorator decorator in Decorators)
// ReSharper disable once ForCanBeConvertedToForeach - Sadly this does not get optimized reliably and causes allocations if foreached
for (int i = 0; i < Decorators.Count; i++)
{
IBrushDecorator decorator = Decorators[i];
if (decorator.IsEnabled)
decorator.ManipulateColor(rectangle, renderTarget, ref color);
}
}
/// <summary>

View File

@ -7,7 +7,7 @@ namespace RGB.NET.Core;
/// <summary>
/// Represents a brush drawing only a single color.
/// </summary>
public class SolidColorBrush : AbstractBrush
public sealed class SolidColorBrush : AbstractBrush
{
#region Properties & Fields
@ -32,6 +32,8 @@ public class SolidColorBrush : AbstractBrush
public SolidColorBrush(Color color)
{
this.Color = color;
CalculationMode = RenderMode.Absolute;
}
#endregion

View File

@ -4,7 +4,7 @@
/// <summary>
/// Represents a brush drawing a texture.
/// </summary>
public class TextureBrush : AbstractBrush
public sealed class TextureBrush : AbstractBrush
{
#region Properties & Fields

View File

@ -1,6 +1,6 @@
namespace RGB.NET.Core;
internal class EmptyTexture : ITexture
internal sealed class EmptyTexture : ITexture
{
#region Properties & Fields

View File

@ -1,5 +1,6 @@
using System;
using System.Buffers;
// ReSharper disable MemberCanBePrivate.Global
using System;
using System.Runtime.CompilerServices;
namespace RGB.NET.Core;
@ -12,16 +13,22 @@ namespace RGB.NET.Core;
public abstract class PixelTexture<T> : ITexture
where T : unmanaged
{
#region Constants
private const int STACK_ALLOC_LIMIT = 1024;
#endregion
#region Properties & Fields
private readonly int _dataPerPixel;
private readonly int _stride;
/// <summary>
/// Gets the underlying pixel data.
/// </summary>
protected abstract ReadOnlySpan<T> Data { get; }
/// <summary>
/// Gets the amount of data-entries per pixel.
/// </summary>
protected int DataPerPixel { get; }
/// <summary>
/// Gets the stride of the data.
/// </summary>
protected int Stride { get; }
/// <summary>
/// Gets or sets the sampler used to get the color of a region.
@ -31,11 +38,6 @@ public abstract class PixelTexture<T> : ITexture
/// <inheritdoc />
public Size Size { get; }
/// <summary>
/// Gets the underlying pixel data.
/// </summary>
protected abstract ReadOnlySpan<T> Data { get; }
/// <inheritdoc />
public virtual Color this[in Point point]
{
@ -85,31 +87,12 @@ public abstract class PixelTexture<T> : ITexture
if ((width == 0) || (height == 0)) return Color.Transparent;
if ((width == 1) && (height == 1)) return GetColor(GetPixelData(x, y));
int bufferSize = width * height * _dataPerPixel;
if (bufferSize <= STACK_ALLOC_LIMIT)
{
Span<T> buffer = stackalloc T[bufferSize];
GetRegionData(x, y, width, height, buffer);
SamplerInfo<T> samplerInfo = new(x, y, width, height, Stride, DataPerPixel, Data);
Span<T> pixelData = stackalloc T[_dataPerPixel];
Sampler.Sample(new SamplerInfo<T>(width, height, buffer), pixelData);
Span<T> pixelData = stackalloc T[DataPerPixel];
Sampler.Sample(samplerInfo, pixelData);
return GetColor(pixelData);
}
else
{
T[] rent = ArrayPool<T>.Shared.Rent(bufferSize);
Span<T> buffer = new Span<T>(rent)[..bufferSize];
GetRegionData(x, y, width, height, buffer);
Span<T> pixelData = stackalloc T[_dataPerPixel];
Sampler.Sample(new SamplerInfo<T>(width, height, buffer), pixelData);
ArrayPool<T>.Shared.Return(rent);
return GetColor(pixelData);
}
return GetColor(pixelData);
}
}
@ -127,8 +110,8 @@ public abstract class PixelTexture<T> : ITexture
/// <param name="stride">The stride of the data or -1 if the width should be used.</param>
public PixelTexture(int with, int height, int dataPerPixel, ISampler<T> sampler, int stride = -1)
{
this._stride = stride == -1 ? with : stride;
this._dataPerPixel = dataPerPixel;
this.Stride = stride == -1 ? with : stride;
this.DataPerPixel = dataPerPixel;
this.Sampler = sampler;
Size = new Size(with, height);
@ -152,27 +135,7 @@ public abstract class PixelTexture<T> : ITexture
/// <param name="y">The y-location.</param>
/// <returns>The pixel-data on the specified location.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected virtual ReadOnlySpan<T> GetPixelData(int x, int y) => Data.Slice((y * _stride) + x, _dataPerPixel);
/// <summary>
/// Writes the pixel-data of the specified region to the passed buffer.
/// </summary>
/// <param name="x">The x-location of the region to get the data for.</param>
/// <param name="y">The y-location of the region to get the data for.</param>
/// <param name="width">The width of the region to get the data for.</param>
/// <param name="height">The height of the region to get the data for.</param>
/// <param name="buffer">The buffer to write the data to.</param>
protected virtual void GetRegionData(int x, int y, int width, int height, in Span<T> buffer)
{
int dataWidth = width * _dataPerPixel;
ReadOnlySpan<T> data = Data;
for (int i = 0; i < height; i++)
{
ReadOnlySpan<T> dataSlice = data.Slice((((y + i) * _stride) + x) * _dataPerPixel, dataWidth);
Span<T> destination = buffer.Slice(i * dataWidth, dataWidth);
dataSlice.CopyTo(destination);
}
}
private ReadOnlySpan<T> GetPixelData(int x, int y) => Data.Slice((y * Stride) + x, DataPerPixel);
#endregion
}
@ -225,6 +188,7 @@ public sealed class PixelTexture : PixelTexture<Color>
#region Methods
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override Color GetColor(in ReadOnlySpan<Color> pixel) => pixel[0];
#endregion

View File

@ -1,6 +1,5 @@
using System;
using System.Numerics;
using System.Runtime.InteropServices;
namespace RGB.NET.Core;
@ -10,7 +9,7 @@ namespace RGB.NET.Core;
/// <remarks>
/// Averages all components (A, R, G, B) of the colors separately which isn't ideal in cases where multiple different colors are combined.
/// </remarks>
public class AverageColorSampler : ISampler<Color>
public sealed class AverageColorSampler : ISampler<Color>
{
#region Constants
@ -30,20 +29,35 @@ public class AverageColorSampler : ISampler<Color>
float a = 0, r = 0, g = 0, b = 0;
if (Vector.IsHardwareAccelerated && (info.Data.Length >= Vector<float>.Count))
if (Vector.IsHardwareAccelerated && (info.Height > 1) && (info.Width >= ELEMENTS_PER_VECTOR))
{
int chunks = info.Data.Length / ELEMENTS_PER_VECTOR;
int missingElements = info.Data.Length - (chunks * ELEMENTS_PER_VECTOR);
int chunks = info.Width / ELEMENTS_PER_VECTOR;
int missingElements = info.Width - (chunks * ELEMENTS_PER_VECTOR);
Vector<float> sum = Vector<float>.Zero;
fixed (Color* colorPtr = &MemoryMarshal.GetReference(info.Data))
for (int y = 0; y < info.Height; y++)
{
Color* current = colorPtr;
for (int i = 0; i < chunks; i++)
ReadOnlySpan<Color> data = info[y];
fixed (Color* colorPtr = data)
{
sum = Vector.Add(sum, *(Vector<float>*)current);
current += ELEMENTS_PER_VECTOR;
Color* current = colorPtr;
for (int i = 0; i < chunks; i++)
{
sum = Vector.Add(sum, *(Vector<float>*)current);
current += ELEMENTS_PER_VECTOR;
}
}
for (int i = 0; i < missingElements; i++)
{
Color color = data[^(i + 1)];
a += color.A;
r += color.R;
g += color.G;
b += color.B;
}
}
@ -54,26 +68,17 @@ public class AverageColorSampler : ISampler<Color>
g += sum[i + 2];
b += sum[i + 3];
}
for (int i = 0; i < missingElements; i++)
{
Color color = info.Data[^(i + 1)];
a += color.A;
r += color.R;
g += color.G;
b += color.B;
}
}
else
{
foreach (Color color in info.Data)
{
a += color.A;
r += color.R;
g += color.G;
b += color.B;
}
for (int y = 0; y < info.Height; y++)
foreach (Color color in info[y])
{
a += color.A;
r += color.R;
g += color.G;
b += color.B;
}
}
pixelData[0] = new Color(a / count, r / count, g / count, b / count);

View File

@ -10,20 +10,29 @@ public readonly ref struct SamplerInfo<T>
{
#region Properties & Fields
private readonly ReadOnlySpan<T> _data;
private readonly int _x;
private readonly int _y;
private readonly int _stride;
private readonly int _dataPerPixel;
private readonly int _dataWidth;
/// <summary>
/// Gets the width of the region the data comes from.
/// </summary>
public int Width { get; }
public readonly int Width;
/// <summary>
/// Gets the height of region the data comes from.
/// </summary>
public int Height { get; }
public readonly int Height;
/// <summary>
/// Gets the data to sample.
/// Gets the data for the requested row.
/// </summary>
public ReadOnlySpan<T> Data { get; }
/// <param name="row">The row to get the data for.</param>
/// <returns>A readonly span containing the data of the row.</returns>
public ReadOnlySpan<T> this[int row] => _data.Slice((((_y + row) * _stride) + _x) * _dataPerPixel, _dataWidth);
#endregion
@ -35,11 +44,17 @@ public readonly ref struct SamplerInfo<T>
/// <param name="width">The width of the region the data comes from.</param>
/// <param name="height">The height of region the data comes from.</param>
/// <param name="data">The data to sample.</param>
public SamplerInfo(int width, int height, ReadOnlySpan<T> data)
public SamplerInfo(int x, int y, int width, int height, int stride, int dataPerPixel, in ReadOnlySpan<T> data)
{
this._x = x;
this._y = y;
this._data = data;
this._stride = stride;
this._dataPerPixel = dataPerPixel;
this.Width = width;
this.Height = height;
this.Data = data;
_dataWidth = width * dataPerPixel;
}
#endregion

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace RGB.NET.Core;
@ -34,11 +35,24 @@ public static class CustomUpdateDataIndex
/// <summary>
/// Represents a set of custom data, each indexed by a string-key.
/// </summary>
public class CustomUpdateData
public interface ICustomUpdateData
{
/// <summary>
/// Gets the value for a specific key.
/// </summary>
/// <param name="key">The key of the value.</param>
/// <returns>The value represented by the specified key.</returns>
object? this[string key] { get; }
}
/// <summary>
/// Represents a set of custom data, each indexed by a string-key.
/// </summary>
public sealed class CustomUpdateData : ICustomUpdateData
{
#region Properties & Fields
private Dictionary<string, object?> _data = new();
private readonly Dictionary<string, object?> _data = [];
#endregion
@ -51,8 +65,8 @@ public class CustomUpdateData
/// <returns>The value represented by the specified key.</returns>
public object? this[string key]
{
get => _data.TryGetValue(key.ToUpperInvariant(), out object? data) ? data : default;
set => _data[key.ToUpperInvariant()] = value;
get => _data.TryGetValue(key, out object? data) ? data : default;
set => _data[key] = value;
}
#endregion
@ -77,3 +91,39 @@ public class CustomUpdateData
#endregion
}
internal sealed class DefaultCustomUpdateData : ICustomUpdateData
{
#region Constants
public static readonly DefaultCustomUpdateData FLUSH = new(true);
public static readonly DefaultCustomUpdateData NO_FLUSH = new(false);
#endregion
#region Properties & Fields
private readonly bool _flushLeds;
public object? this[string key]
{
get
{
if (string.Equals(key, CustomUpdateDataIndex.FLUSH_LEDS, StringComparison.Ordinal))
return _flushLeds;
return null;
}
}
#endregion
#region Constructors
private DefaultCustomUpdateData(bool flushLeds)
{
this._flushLeds = flushLeds;
}
#endregion
}

View File

@ -1,5 +1,6 @@
// ReSharper disable MemberCanBePrivate.Global
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
@ -132,7 +133,7 @@ public class DeviceUpdateTrigger : AbstractUpdateTrigger, IDeviceUpdateTrigger
/// <summary>
/// Stops the trigger.
/// </summary>
public async void Stop()
public virtual async void Stop()
{
if (!IsRunning) return;
@ -140,7 +141,9 @@ public class DeviceUpdateTrigger : AbstractUpdateTrigger, IDeviceUpdateTrigger
UpdateTokenSource?.Cancel();
if (UpdateTask != null)
await UpdateTask;
try { await UpdateTask.ConfigureAwait(false); }
catch (TaskCanceledException) { }
catch (OperationCanceledException) { }
UpdateTask?.Dispose();
UpdateTask = null;
@ -156,11 +159,13 @@ public class DeviceUpdateTrigger : AbstractUpdateTrigger, IDeviceUpdateTrigger
using (TimerHelper.RequestHighResolutionTimer())
while (!UpdateToken.IsCancellationRequested)
if (HasDataEvent.WaitOne(Timeout))
LastUpdateTime = TimerHelper.Execute(() => OnUpdate(), UpdateFrequency * 1000);
LastUpdateTime = TimerHelper.Execute(TimerExecute, UpdateFrequency * 1000);
else if ((HeartbeatTimer > 0) && (LastUpdateTimestamp > 0) && (TimerHelper.GetElapsedTime(LastUpdateTimestamp) > HeartbeatTimer))
OnUpdate(new CustomUpdateData().Heartbeat());
}
private void TimerExecute() => OnUpdate();
protected override void OnUpdate(CustomUpdateData? updateData = null)
{
base.OnUpdate(updateData);
@ -178,7 +183,12 @@ public class DeviceUpdateTrigger : AbstractUpdateTrigger, IDeviceUpdateTrigger
}
/// <inheritdoc />
public override void Dispose() => Stop();
public override void Dispose()
{
Stop();
GC.SuppressFinalize(this);
}
#endregion
}

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
namespace RGB.NET.Core;
@ -8,15 +7,20 @@ namespace RGB.NET.Core;
/// </summary>
/// <typeparam name="TIdentifier">The identifier used to identify the data processed by this queue.</typeparam>
/// <typeparam name="TData">The type of the data processed by this queue.</typeparam>
public interface IUpdateQueue<TIdentifier, TData> : IDisposable
public interface IUpdateQueue<TIdentifier, TData> : IReferenceCounting, IDisposable
where TIdentifier : notnull
{
/// <summary>
/// Gets a bool indicating if the queue requires a flush of all data due to an internal error.
/// </summary>
bool RequiresFlush { get; }
/// <summary>
/// Sets or merges the provided data set in the current dataset and notifies the trigger that there is new data available.
/// </summary>
/// <param name="dataSet">The set of data.</param>
// ReSharper disable once MemberCanBeProtected.Global
void SetData(IEnumerable<(TIdentifier, TData)> dataSet);
void SetData(ReadOnlySpan<(TIdentifier, TData)> dataSet);
/// <summary>
/// Resets the current data set.
@ -27,5 +31,4 @@ public interface IUpdateQueue<TIdentifier, TData> : IDisposable
/// <summary>
/// Represents a generic update queue processing <see cref="Color"/>-data using <see cref="object"/>-identifiers.
/// </summary>
public interface IUpdateQueue : IUpdateQueue<object, Color>
{ }
public interface IUpdateQueue : IUpdateQueue<object, Color>;

View File

@ -1,7 +1,6 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
namespace RGB.NET.Core;
@ -10,14 +9,17 @@ namespace RGB.NET.Core;
/// </summary>
/// <typeparam name="TIdentifier">The type of the key used to identify some data.</typeparam>
/// <typeparam name="TData">The type of the data.</typeparam>
public abstract class UpdateQueue<TIdentifier, TData> : IUpdateQueue<TIdentifier, TData>
public abstract class UpdateQueue<TIdentifier, TData> : AbstractReferenceCounting, IUpdateQueue<TIdentifier, TData>
where TIdentifier : notnull
{
#region Properties & Fields
private readonly object _dataLock = new();
private readonly IDeviceUpdateTrigger _updateTrigger;
private readonly Dictionary<TIdentifier, TData> _currentDataSet = new();
private readonly Dictionary<TIdentifier, TData> _currentDataSet = [];
/// <inheritdoc />
public bool RequiresFlush { get; private set; }
#endregion
@ -62,7 +64,7 @@ public abstract class UpdateQueue<TIdentifier, TData> : IUpdateQueue<TIdentifier
_currentDataSet.Clear();
}
Update(data);
RequiresFlush = !Update(data);
ArrayPool<(TIdentifier, TData)>.Shared.Return(dataSet);
}
@ -78,17 +80,16 @@ public abstract class UpdateQueue<TIdentifier, TData> : IUpdateQueue<TIdentifier
/// Performs the update this queue is responsible for.
/// </summary>
/// <param name="dataSet">The set of data that needs to be updated.</param>
protected abstract void Update(in ReadOnlySpan<(TIdentifier key, TData color)> dataSet);
protected abstract bool Update(in ReadOnlySpan<(TIdentifier key, TData color)> dataSet);
/// <summary>
/// Sets or merges the provided data set in the current dataset and notifies the trigger that there is new data available.
/// </summary>
/// <param name="dataSet">The set of data.</param>
// ReSharper disable once MemberCanBeProtected.Global
public virtual void SetData(IEnumerable<(TIdentifier, TData)> dataSet)
public virtual void SetData(ReadOnlySpan<(TIdentifier, TData)> data)
{
IList<(TIdentifier, TData)> data = dataSet.ToList();
if (data.Count == 0) return;
if (data.Length == 0) return;
lock (_dataLock)
{

View File

@ -83,12 +83,12 @@ public sealed class ManualUpdateTrigger : AbstractUpdateTrigger
OnStartup();
while (!UpdateToken.IsCancellationRequested)
{
if (_mutex.WaitOne(100))
LastUpdateTime = TimerHelper.Execute(() => OnUpdate(_customUpdateData));
}
LastUpdateTime = TimerHelper.Execute(TimerExecute);
}
private void TimerExecute() => OnUpdate(_customUpdateData);
/// <inheritdoc />
public override void Dispose() => Stop();

View File

@ -10,7 +10,7 @@ namespace RGB.NET.Core;
/// <summary>
/// Represents an update trigger that triggers in a set interval.
/// </summary>
public class TimerUpdateTrigger : AbstractUpdateTrigger
public sealed class TimerUpdateTrigger : AbstractUpdateTrigger
{
#region Properties & Fields
@ -21,17 +21,17 @@ public class TimerUpdateTrigger : AbstractUpdateTrigger
/// <summary>
/// Gets or sets the update loop of this trigger.
/// </summary>
protected Task? UpdateTask { get; set; }
private Task? _updateTask;
/// <summary>
/// Gets or sets the cancellation token source used to create the cancellation token checked by the <see cref="UpdateTask"/>.
/// Gets or sets the cancellation token source used to create the cancellation token checked by the <see cref="_updateTask"/>.
/// </summary>
protected CancellationTokenSource? UpdateTokenSource { get; set; }
private CancellationTokenSource? _updateTokenSource;
/// <summary>
/// Gets or sets the cancellation token checked by the <see cref="UpdateTask"/>.
/// Gets or sets the cancellation token checked by the <see cref="_updateTask"/>.
/// </summary>
protected CancellationToken UpdateToken { get; set; }
private CancellationToken _updateToken;
private double _updateFrequency = 1.0 / 30.0;
/// <summary>
@ -88,11 +88,11 @@ public class TimerUpdateTrigger : AbstractUpdateTrigger
{
lock (_lock)
{
if (UpdateTask == null)
if (_updateTask == null)
{
UpdateTokenSource?.Dispose();
UpdateTokenSource = new CancellationTokenSource();
UpdateTask = Task.Factory.StartNew(UpdateLoop, (UpdateToken = UpdateTokenSource.Token), TaskCreationOptions.LongRunning, TaskScheduler.Default);
_updateTokenSource?.Dispose();
_updateTokenSource = new CancellationTokenSource();
_updateTask = Task.Factory.StartNew(UpdateLoop, (_updateToken = _updateTokenSource.Token), TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
}
}
@ -104,13 +104,13 @@ public class TimerUpdateTrigger : AbstractUpdateTrigger
{
lock (_lock)
{
if (UpdateTask != null)
if (_updateTask != null)
{
UpdateTokenSource?.Cancel();
_updateTokenSource?.Cancel();
try
{
// ReSharper disable once MethodSupportsCancellation
UpdateTask.Wait();
_updateTask.Wait();
}
catch (AggregateException)
{
@ -118,8 +118,8 @@ public class TimerUpdateTrigger : AbstractUpdateTrigger
}
finally
{
UpdateTask.Dispose();
UpdateTask = null;
_updateTask.Dispose();
_updateTask = null;
}
}
}
@ -130,16 +130,16 @@ public class TimerUpdateTrigger : AbstractUpdateTrigger
OnStartup();
using (TimerHelper.RequestHighResolutionTimer())
while (!UpdateToken.IsCancellationRequested)
LastUpdateTime = TimerHelper.Execute(() => OnUpdate(_customUpdateData), UpdateFrequency * 1000);
while (!_updateToken.IsCancellationRequested)
LastUpdateTime = TimerHelper.Execute(TimerExecute, UpdateFrequency * 1000);
}
private void TimerExecute() => OnUpdate(_customUpdateData);
/// <inheritdoc />
public override void Dispose()
{
Stop();
GC.SuppressFinalize(this);
}
#endregion

View File

@ -12,15 +12,25 @@ namespace RGB.NET.Devices.Asus;
/// <summary>
/// Represents a device provider responsible for Cooler Master devices.
/// </summary>
public class AsusDeviceProvider : AbstractRGBDeviceProvider
public sealed class AsusDeviceProvider : AbstractRGBDeviceProvider
{
#region Properties & Fields
// ReSharper disable once InconsistentNaming
private static readonly object _lock = new();
private static AsusDeviceProvider? _instance;
/// <summary>
/// Gets the singleton <see cref="AsusDeviceProvider"/> instance.
/// </summary>
public static AsusDeviceProvider Instance => _instance ?? new AsusDeviceProvider();
public static AsusDeviceProvider Instance
{
get
{
lock (_lock)
return _instance ?? new AsusDeviceProvider();
}
}
private IAuraSdk2? _sdk;
private IAuraSyncDeviceCollection? _devices; //HACK DarthAffe 05.04.2021: Due to some researches this might fix the access violation in the asus-sdk
@ -35,8 +45,11 @@ public class AsusDeviceProvider : AbstractRGBDeviceProvider
/// <exception cref="InvalidOperationException">Thrown if this constructor is called even if there is already an instance of this class.</exception>
public AsusDeviceProvider()
{
if (_instance != null) throw new InvalidOperationException($"There can be only one instance of type {nameof(AsusDeviceProvider)}");
_instance = this;
lock (_lock)
{
if (_instance != null) throw new InvalidOperationException($"There can be only one instance of type {nameof(AsusDeviceProvider)}");
_instance = this;
}
}
#endregion
@ -60,6 +73,7 @@ public class AsusDeviceProvider : AbstractRGBDeviceProvider
for (int i = 0; i < _devices.Count; i++)
{
IAuraSyncDevice device = _devices[i];
yield return (AsusDeviceType)device.Type switch
{
AsusDeviceType.MB_RGB => new AsusMainboardRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.Mainboard, device, WMIHelper.GetMainboardInfo()?.model ?? device.Name), GetUpdateTrigger()),
@ -68,26 +82,30 @@ public class AsusDeviceProvider : AbstractRGBDeviceProvider
AsusDeviceType.HEADSET_RGB => new AsusHeadsetRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.Headset, device), GetUpdateTrigger()),
AsusDeviceType.DRAM_RGB => new AsusDramRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.DRAM, device), GetUpdateTrigger()),
AsusDeviceType.KEYBOARD_RGB => new AsusKeyboardRGBDevice(new AsusKeyboardRGBDeviceInfo(device), LedMappings.KeyboardMapping, GetUpdateTrigger()),
AsusDeviceType.KEYBOARD_5ZONE_RGB => new AsusKeyboardRGBDevice(new AsusKeyboardRGBDeviceInfo(device), null, GetUpdateTrigger()),
AsusDeviceType.NB_KB_RGB => new AsusKeyboardRGBDevice(new AsusKeyboardRGBDeviceInfo(device), LedMappings.KeyboardMapping, GetUpdateTrigger()),
AsusDeviceType.NB_KB_4ZONE_RGB => new AsusKeyboardRGBDevice(new AsusKeyboardRGBDeviceInfo(device), null, GetUpdateTrigger()),
AsusDeviceType.MOUSE_RGB => new AsusMouseRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.Mouse, device), GetUpdateTrigger()),
_ => new AsusUnspecifiedRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.Unknown, device), LedId.Custom1, GetUpdateTrigger())
AsusDeviceType.TERMINAL_RGB => new AsusUnspecifiedRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.LedController, device), LedId.Custom1, GetUpdateTrigger()),
_ => new AsusUnspecifiedRGBDevice(new AsusRGBDeviceInfo(RGBDeviceType.Unknown, device), LedId.Unknown1, GetUpdateTrigger())
};
}
}
/// <inheritdoc />
public override void Dispose()
protected override void Dispose(bool disposing)
{
base.Dispose();
lock (_lock)
{
base.Dispose(disposing);
try { _sdk?.ReleaseControl(0); }
catch { /* at least we tried */ }
try { _sdk?.ReleaseControl(0); }
catch { /* at least we tried */ }
_devices = null;
_sdk = null;
GC.SuppressFinalize(this);
_devices = null;
_sdk = null;
_instance = null;
}
}
#endregion

View File

@ -16,9 +16,12 @@ internal enum AsusDeviceType : uint
EXTERNAL_BLUE_RAY_RGB = 0x61000,
DRAM_RGB = 0x70000,
KEYBOARD_RGB = 0x80000,
KEYBOARD_5ZONE_RGB = 0x80001,
NB_KB_RGB = 0x81000,
NB_KB_4ZONE_RGB = 0x81001,
MOUSE_RGB = 0x90000,
CHASSIS_RGB = 0xB0000,
PROJECTOR_RGB = 0xC0000
PROJECTOR_RGB = 0xC0000,
WATERCOOLER_RGB = 0xD1000,
TERMINAL_RGB = 0xE0000
}

View File

@ -4,9 +4,9 @@ namespace RGB.NET.Devices.Asus;
/// <inheritdoc cref="AsusRGBDevice{TDeviceInfo}" />
/// <summary>
/// Represents a Asus headset.
/// Represents a Asus device that is otherwise not handled by a more specific helper.
/// </summary>
public class AsusUnspecifiedRGBDevice : AsusRGBDevice<AsusRGBDeviceInfo>, IUnknownDevice
public sealed class AsusUnspecifiedRGBDevice : AsusRGBDevice<AsusRGBDeviceInfo>, IUnknownDevice
{
#region Properties & Fields
@ -18,9 +18,9 @@ public class AsusUnspecifiedRGBDevice : AsusRGBDevice<AsusRGBDeviceInfo>, IUnkno
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Devices.Asus.AsusHeadsetRGBDevice" /> class.
/// Initializes a new instance of the <see cref="T:RGB.NET.Devices.Asus.AsusUnspecifiedRGBDevice" /> class.
/// </summary>
/// <param name="info">The specific information provided by Asus for the headset.</param>
/// <param name="info">The specific information provided by Asus for the device.</param>
/// <param name="baseLedId">The ledId of the first led of this device. All other leds are created by incrementing this base-id by 1.</param>
/// <param name="updateTrigger">The update trigger used to update this device.</param>
internal AsusUnspecifiedRGBDevice(AsusRGBDeviceInfo info, LedId baseLedId, IDeviceUpdateTrigger updateTrigger)

View File

@ -8,7 +8,7 @@ namespace RGB.NET.Devices.Asus;
/// <summary>
/// Represents the update-queue performing updates for asus devices.
/// </summary>
public class AsusUpdateQueue : UpdateQueue
public sealed class AsusUpdateQueue : UpdateQueue
{
#region Properties & Fields
@ -17,7 +17,7 @@ public class AsusUpdateQueue : UpdateQueue
/// <summary>
/// The device to be updated.
/// </summary>
protected IAuraSyncDevice Device { get; }
private readonly IAuraSyncDevice _device;
#endregion
@ -31,7 +31,7 @@ public class AsusUpdateQueue : UpdateQueue
public AsusUpdateQueue(IDeviceUpdateTrigger updateTrigger, IAuraSyncDevice device)
: base(updateTrigger)
{
this.Device = device;
this._device = device;
this._lights = new IAuraRgbLight[device.Lights.Count];
for (int i = 0; i < device.Lights.Count; i++)
@ -43,14 +43,14 @@ public class AsusUpdateQueue : UpdateQueue
#region Methods
/// <inheritdoc />
protected override void Update(in ReadOnlySpan<(object key, Color color)> dataSet)
protected override bool Update(in ReadOnlySpan<(object key, Color color)> dataSet)
{
try
{
if ((Device.Type == (uint)AsusDeviceType.KEYBOARD_RGB) || (Device.Type == (uint)AsusDeviceType.NB_KB_RGB))
if ((_device.Type == (uint)AsusDeviceType.KEYBOARD_RGB) || (_device.Type == (uint)AsusDeviceType.NB_KB_RGB))
{
if (Device is not IAuraSyncKeyboard keyboard)
return;
if (_device is not IAuraSyncKeyboard keyboard)
return true;
foreach ((object customData, Color value) in dataSet)
{
@ -87,12 +87,17 @@ public class AsusUpdateQueue : UpdateQueue
}
}
Device.Apply();
}
catch
{ /* "The server threw an exception." seems to be a thing here ... */
}
}
_device.Apply();
return true;
}
catch (Exception ex)
{
AsusDeviceProvider.Instance.Throw(ex);
}
return false;
}
#endregion
}

View File

@ -5,5 +5,4 @@ namespace RGB.NET.Devices.Asus;
/// <summary>
/// Represents a asus RGB-device.
/// </summary>
public interface IAsusRGBDevice : IRGBDevice
{ }
public interface IAsusRGBDevice : IRGBDevice;

View File

@ -6,7 +6,7 @@ namespace RGB.NET.Devices.Asus;
/// <summary>
/// Represents a Asus graphicsCard.
/// </summary>
public class AsusGraphicsCardRGBDevice : AsusRGBDevice<AsusRGBDeviceInfo>, IGraphicsCard
public sealed class AsusGraphicsCardRGBDevice : AsusRGBDevice<AsusRGBDeviceInfo>, IGraphicsCard
{
#region Constructors

View File

@ -6,7 +6,7 @@ namespace RGB.NET.Devices.Asus;
/// <summary>
/// Represents a Asus headset.
/// </summary>
public class AsusHeadsetRGBDevice : AsusRGBDevice<AsusRGBDeviceInfo>, IHeadset
public sealed class AsusHeadsetRGBDevice : AsusRGBDevice<AsusRGBDeviceInfo>, IHeadset
{
#region Constructors

View File

@ -43,7 +43,7 @@ internal static class WMIHelper
if ((_systemModelInfo == null) && (_systemModelSearcher != null))
foreach (ManagementBaseObject managementBaseObject in _systemModelSearcher.Get())
{
_systemModelInfo = managementBaseObject["Model"]?.ToString();
_systemModelInfo = managementBaseObject["Model"].ToString();
break;
}
@ -57,7 +57,7 @@ internal static class WMIHelper
if (!_mainboardInfo.HasValue && (_mainboardSearcher != null))
foreach (ManagementBaseObject managementBaseObject in _mainboardSearcher.Get())
{
_mainboardInfo = (managementBaseObject["Manufacturer"]?.ToString() ?? string.Empty, managementBaseObject["Product"]?.ToString() ?? string.Empty);
_mainboardInfo = (managementBaseObject["Manufacturer"].ToString() ?? string.Empty, managementBaseObject["Product"].ToString() ?? string.Empty);
break;
}
@ -71,7 +71,7 @@ internal static class WMIHelper
if ((_graphicsCardInfo == null) && (_graphicsCardSearcher != null))
foreach (ManagementBaseObject managementBaseObject in _graphicsCardSearcher.Get())
{
_graphicsCardInfo = managementBaseObject["Name"]?.ToString();
_graphicsCardInfo = managementBaseObject["Name"].ToString();
break;
}

View File

@ -188,4 +188,49 @@ public static class LedMappings
{ LedId.Keyboard_Custom59, 131 },
{ LedId.Keyboard_Custom60, 133 },
};
/// <summary>
/// A LED mapping containing extra lights for the ROG Strix G15 (2021)
/// </summary>
/// <remarks>
/// <para>
/// ASUS notebooks have extra lights under wide keys like space and backspace, these do not appear as keys on the device.
/// Instead they only appear in the Lights enumerable, this mapping maps the matching keys to the index of these lights.
/// There are also some keys which do not use the default key scan code mappings for LEDs, and instead rely on lights.
/// </para>
/// <para>You may add more of these by further populating <see cref="AsusKeyboardRGBDevice.ExtraLedMappings"/>.</para>
/// </remarks>
public static LedMapping<int> ROGStrixG15 { get; } = new()
{
{ LedId.Keyboard_Custom71, 4 }, //Mic Mute
{ LedId.Keyboard_Custom72, 5 }, //Fan
{ LedId.Keyboard_Custom73, 6 }, //ROG Logo
//{ LedId.Keyboard_Function, 127 }, //commented out because adding a mapping fails if a mapping already exists for a key, even if it is incorrect for this device
//use Keyboard_Custom36 in the default mapping to get the Fn key on this laptop
{ LedId.Keyboard_Custom52, 55 }, //backspace extra LEDs (x2) - these are named to match the appropriate LEDs in the previous ROG Zephyrus mapping
{ LedId.Keyboard_Custom53, 57 },
{ LedId.Keyboard_Custom54, 97 }, //enter extra LEDs (x2)
{ LedId.Keyboard_Custom55, 99 },
{ LedId.Keyboard_Custom56, 118 }, //right shift extra LEDs (x2)
{ LedId.Keyboard_Custom57, 120 },
{ LedId.Keyboard_Custom58, 130 }, //space bar extra LEDs (x3)
{ LedId.Keyboard_Custom59, 131 }, //this one specifically is also exposed as Custom7 (AsusLedID.KEY_NOCONVERT) in the main map
{ LedId.Keyboard_Custom60, 133 },
{ LedId.Keyboard_MediaVolumeDown, 2 },
{ LedId.Keyboard_MediaVolumeUp, 3 },
{ LedId.Keyboard_MediaPlay, 58 },
{ LedId.Keyboard_MediaStop, 79 },
{ LedId.Keyboard_MediaPreviousTrack, 100 },
{ LedId.Keyboard_MediaNextTrack, 121 },
{ LedId.LedStripe1, 174 }, //front LED strip; yes, these are in reverse order, since the SDK exposes them from right to left
{ LedId.LedStripe2, 173 },
{ LedId.LedStripe3, 172 },
{ LedId.LedStripe4, 171 },
{ LedId.LedStripe5, 170 },
{ LedId.LedStripe6, 169 },
};
}

View File

@ -20,13 +20,13 @@ public record AsusKeyboardExtraMapping(Regex Regex, LedMapping<int> LedMapping);
/// <summary>
/// Represents a Asus keyboard.
/// </summary>
public class AsusKeyboardRGBDevice : AsusRGBDevice<AsusKeyboardRGBDeviceInfo>, IKeyboard
public sealed class AsusKeyboardRGBDevice : AsusRGBDevice<AsusKeyboardRGBDeviceInfo>, IKeyboard
{
#region Properties & Fields
private readonly LedMapping<AsusLedId>? _ledMapping;
private readonly Dictionary<LedId, AsusLedId> _ledAsusLed = new();
private readonly Dictionary<LedId, int> _ledAsusLights = new();
private readonly Dictionary<LedId, AsusLedId> _ledAsusLed = [];
private readonly Dictionary<LedId, int> _ledAsusLights = [];
IKeyboardDeviceInfo IKeyboard.DeviceInfo => DeviceInfo;
@ -35,10 +35,11 @@ public class AsusKeyboardRGBDevice : AsusRGBDevice<AsusKeyboardRGBDeviceInfo>, I
/// <para>Note: These LED mappings should be based on light indexes.</para>
/// </summary>
// ReSharper disable once InconsistentNaming
public static readonly List<AsusKeyboardExtraMapping> ExtraLedMappings = new()
{
new AsusKeyboardExtraMapping(new Regex("(ROG Zephyrus Duo 15).*?"), LedMappings.ROGZephyrusDuo15)
};
public static readonly List<AsusKeyboardExtraMapping> ExtraLedMappings =
[
new AsusKeyboardExtraMapping(new Regex("(ROG Zephyrus Duo 15).*?"), LedMappings.ROGZephyrusDuo15),
new AsusKeyboardExtraMapping(new Regex("(ROG Strix G513QM).*?"), LedMappings.ROGStrixG15)
];
#endregion
@ -65,7 +66,7 @@ public class AsusKeyboardRGBDevice : AsusRGBDevice<AsusKeyboardRGBDeviceInfo>, I
private void InitializeLayout()
{
if (DeviceInfo.Device.Type != (uint)AsusDeviceType.NB_KB_4ZONE_RGB)
if ((DeviceInfo.Device.Type != (uint)AsusDeviceType.NB_KB_4ZONE_RGB) && (DeviceInfo.Device.Type !=(uint)AsusDeviceType.KEYBOARD_5ZONE_RGB))
{
int pos = 0;
int unknownLed = (int)LedId.Unknown1;

View File

@ -7,7 +7,7 @@ namespace RGB.NET.Devices.Asus;
/// <summary>
/// Represents a generic information for a <see cref="T:RGB.NET.Devices.Asus.AsusKeyboardRGBDevice" />.
/// </summary>
public class AsusKeyboardRGBDeviceInfo : AsusRGBDeviceInfo, IKeyboardDeviceInfo
public sealed class AsusKeyboardRGBDeviceInfo : AsusRGBDeviceInfo, IKeyboardDeviceInfo
{
#region Properties & Fields
@ -15,7 +15,7 @@ public class AsusKeyboardRGBDeviceInfo : AsusRGBDeviceInfo, IKeyboardDeviceInfo
/// The ASUS SDK returns useless names for notebook keyboards, possibly for others as well.
/// Keep a list of those and rely on <see cref="WMIHelper.GetSystemModelInfo()"/> to get the real model
/// </summary>
private static readonly List<string> GENERIC_DEVICE_NAMES = new() { "NotebookKeyboard" };
private static readonly List<string> GENERIC_DEVICE_NAMES = ["NotebookKeyboard"];
/// <inheritdoc />
public KeyboardLayoutType Layout => KeyboardLayoutType.Unknown;

View File

@ -6,7 +6,7 @@ namespace RGB.NET.Devices.Asus;
/// <summary>
/// Represents a Asus mainboard.
/// </summary>
public class AsusMainboardRGBDevice : AsusRGBDevice<AsusRGBDeviceInfo>, IMainboard
public sealed class AsusMainboardRGBDevice : AsusRGBDevice<AsusRGBDeviceInfo>, IMainboard
{
#region Constructors

View File

@ -6,7 +6,7 @@ namespace RGB.NET.Devices.Asus;
/// <summary>
/// Represents a Asus mouse.
/// </summary>
public class AsusMouseRGBDevice : AsusRGBDevice<AsusRGBDeviceInfo>, IMouse
public sealed class AsusMouseRGBDevice : AsusRGBDevice<AsusRGBDeviceInfo>, IMouse
{
#region Constructors

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net6.0;net5.0</TargetFrameworks>
<TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
@ -40,7 +40,7 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<DebugSymbols>true</DebugSymbols>
<Optimize>false</Optimize>
</PropertyGroup>
@ -48,7 +48,7 @@
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<Optimize>true</Optimize>
<NoWarn>$(NoWarn);CS1591;CS1572;CS1573</NoWarn>
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants>
<DefineConstants>RELEASE</DefineConstants>
</PropertyGroup>
<ItemGroup>
@ -57,21 +57,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Management" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<_PackageFiles Include="$(OutputPath)\net6.0\Interop.AuraServiceLib.dll">
<BuildAction>None</BuildAction>
<PackagePath>lib\net6.0\</PackagePath>
</_PackageFiles>
</ItemGroup>
<ItemGroup>
<_PackageFiles Include="$(OutputPath)\net5.0\Interop.AuraServiceLib.dll">
<BuildAction>None</BuildAction>
<PackagePath>lib\net5.0\</PackagePath>
</_PackageFiles>
<PackageReference Include="System.Management" Version="7.0.0" />
</ItemGroup>
<ItemGroup>

View File

@ -8,7 +8,7 @@ namespace RGB.NET.Devices.CoolerMaster;
/// Specifies the <see cref="T:RGB.NET.Core.RGBDeviceType" /> of a field.
/// </summary>
[AttributeUsage(AttributeTargets.Field)]
public class DeviceTypeAttribute : Attribute
public sealed class DeviceTypeAttribute : Attribute
{
#region Properties & Fields

View File

@ -13,27 +13,37 @@ namespace RGB.NET.Devices.CoolerMaster;
/// <summary>
/// Represents a device provider responsible for Cooler Master devices.
/// </summary>
public class CoolerMasterDeviceProvider : AbstractRGBDeviceProvider
public sealed class CoolerMasterDeviceProvider : AbstractRGBDeviceProvider
{
#region Properties & Fields
// ReSharper disable once InconsistentNaming
private static readonly object _lock = new();
private static CoolerMasterDeviceProvider? _instance;
/// <summary>
/// Gets the singleton <see cref="CoolerMasterDeviceProvider"/> instance.
/// </summary>
public static CoolerMasterDeviceProvider Instance => _instance ?? new CoolerMasterDeviceProvider();
public static CoolerMasterDeviceProvider Instance
{
get
{
lock (_lock)
return _instance ?? new CoolerMasterDeviceProvider();
}
}
/// <summary>
/// Gets a modifiable list of paths used to find the native SDK-dlls for x86 applications.
/// The first match will be used.
/// </summary>
public static List<string> PossibleX86NativePaths { get; } = new() { "x86/CMSDK.dll" };
public static List<string> PossibleX86NativePaths { get; } = ["x86/CMSDK.dll"];
/// <summary>
/// Gets a modifiable list of paths used to find the native SDK-dlls for x64 applications.
/// The first match will be used.
/// </summary>
public static List<string> PossibleX64NativePaths { get; } = new() { "x64/CMSDK.dll" };
public static List<string> PossibleX64NativePaths { get; } = ["x64/CMSDK.dll"];
#endregion
@ -45,8 +55,11 @@ public class CoolerMasterDeviceProvider : AbstractRGBDeviceProvider
/// <exception cref="InvalidOperationException">Thrown if this constructor is called even if there is already an instance of this class.</exception>
public CoolerMasterDeviceProvider()
{
if (_instance != null) throw new InvalidOperationException($"There can be only one instance of type {nameof(CoolerMasterDeviceProvider)}");
_instance = this;
lock (_lock)
{
if (_instance != null) throw new InvalidOperationException($"There can be only one instance of type {nameof(CoolerMasterDeviceProvider)}");
_instance = this;
}
}
#endregion
@ -94,12 +107,17 @@ public class CoolerMasterDeviceProvider : AbstractRGBDeviceProvider
}
/// <inheritdoc />
public override void Dispose()
protected override void Dispose(bool disposing)
{
base.Dispose();
lock (_lock)
{
base.Dispose(disposing);
try { _CoolerMasterSDK.Reload(); }
catch { /* Unlucky.. */ }
try { _CoolerMasterSDK.Reload(); }
catch { /* Unlucky.. */ }
_instance = null;
}
}
#endregion

View File

@ -4,8 +4,6 @@
using System.ComponentModel;
using RGB.NET.Core;
#pragma warning disable 1591 // Missing XML comment for publicly visible type or member
namespace RGB.NET.Devices.CoolerMaster;
/// <summary>

View File

@ -1,40 +0,0 @@
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Global
#pragma warning disable 1591 // Missing XML comment for publicly visible type or member
namespace RGB.NET.Devices.CoolerMaster;
/// <summary>
/// Contains a list of available effects.
/// </summary>
public enum CoolerMasterEffects
{
FullOn = 0,
Breath = 1,
BreathCycle = 2,
Single = 3,
Wave = 4,
Ripple = 5,
Cross = 6,
Rain = 7,
Star = 8,
Snake = 9,
Rec = 10,
Spectrum = 11,
RapidFire = 12,
Indicator = 13, //mouse Effect
FireBall = 14,
WaterRipple = 15,
ReactivePunch = 16,
Snowing = 17,
HeartBeat = 18,
ReactiveTornade = 19,
Multi1 = 0xE0,
Multi2 = 0xE1,
Multi3 = 0xE2,
Multi4 = 0xE3,
Off = 0xFE
}

View File

@ -33,6 +33,8 @@ public abstract class CoolerMasterRGBDevice<TDeviceInfo> : AbstractRGBDevice<TDe
_CoolerMasterSDK.EnableLedControl(false, DeviceInfo.DeviceIndex);
base.Dispose();
GC.SuppressFinalize(this);
}
#endregion

View File

@ -8,7 +8,7 @@ namespace RGB.NET.Devices.CoolerMaster;
/// <summary>
/// Represents the update-queue performing updates for cooler master devices.
/// </summary>
public class CoolerMasterUpdateQueue : UpdateQueue
public sealed class CoolerMasterUpdateQueue : UpdateQueue
{
#region Properties & Fields
@ -37,15 +37,26 @@ public class CoolerMasterUpdateQueue : UpdateQueue
#region Methods
/// <inheritdoc />
protected override void Update(in ReadOnlySpan<(object key, Color color)> dataSet)
protected override bool Update(in ReadOnlySpan<(object key, Color color)> dataSet)
{
foreach ((object key, Color color) in dataSet)
try
{
(int row, int column) = ((int, int))key;
_deviceMatrix.KeyColor[row, column] = new _CoolerMasterKeyColor(color.GetR(), color.GetG(), color.GetB());
foreach ((object key, Color color) in dataSet)
{
(int row, int column) = ((int, int))key;
_deviceMatrix.KeyColor[row, column] = new _CoolerMasterKeyColor(color.GetR(), color.GetG(), color.GetB());
}
_CoolerMasterSDK.SetAllLedColor(_deviceMatrix, _deviceIndex);
return true;
}
catch (Exception ex)
{
CoolerMasterDeviceProvider.Instance.Throw(ex);
}
_CoolerMasterSDK.SetAllLedColor(_deviceMatrix, _deviceIndex);
return false;
}
#endregion

View File

@ -5,5 +5,4 @@ namespace RGB.NET.Devices.CoolerMaster;
/// <summary>
/// Represents a CoolerMaster RGB-device.
/// </summary>
public interface ICoolerMasterRGBDevice : IRGBDevice
{ }
public interface ICoolerMasterRGBDevice : IRGBDevice;

View File

@ -7,7 +7,7 @@ namespace RGB.NET.Devices.CoolerMaster;
/// <summary>
/// Represents a CoolerMaster keyboard.
/// </summary>
public class CoolerMasterKeyboardRGBDevice : CoolerMasterRGBDevice<CoolerMasterKeyboardRGBDeviceInfo>, IKeyboard
public sealed class CoolerMasterKeyboardRGBDevice : CoolerMasterRGBDevice<CoolerMasterKeyboardRGBDeviceInfo>, IKeyboard
{
#region Properties & Fields

View File

@ -5,7 +5,7 @@ namespace RGB.NET.Devices.CoolerMaster;
/// <summary>
/// Represents a generic information for a <see cref="T:RGB.NET.Devices.CoolerMaster.CoolerMasterKeyboardRGBDevice" />.
/// </summary>
public class CoolerMasterKeyboardRGBDeviceInfo : CoolerMasterRGBDeviceInfo, IKeyboardDeviceInfo
public sealed class CoolerMasterKeyboardRGBDeviceInfo : CoolerMasterRGBDeviceInfo, IKeyboardDeviceInfo
{
#region Properties & Fields

View File

@ -7,7 +7,7 @@ namespace RGB.NET.Devices.CoolerMaster;
/// <summary>
/// Represents a CoolerMaster mouse.
/// </summary>
public class CoolerMasterMouseRGBDevice : CoolerMasterRGBDevice<CoolerMasterMouseRGBDeviceInfo>, IMouse
public sealed class CoolerMasterMouseRGBDevice : CoolerMasterRGBDevice<CoolerMasterMouseRGBDeviceInfo>, IMouse
{
#region Constructors

View File

@ -6,7 +6,7 @@ namespace RGB.NET.Devices.CoolerMaster;
/// <summary>
/// Represents a generic information for a <see cref="T:RGB.NET.Devices.CoolerMaster.CoolerMasterMouseRGBDevice" />.
/// </summary>
public class CoolerMasterMouseRGBDeviceInfo : CoolerMasterRGBDeviceInfo
public sealed class CoolerMasterMouseRGBDeviceInfo : CoolerMasterRGBDeviceInfo
{
#region Constructors

View File

@ -16,14 +16,14 @@ internal static class _CoolerMasterSDK
{
#region Libary Management
private static IntPtr _handle = IntPtr.Zero;
private static nint _handle = 0;
/// <summary>
/// Reloads the SDK.
/// </summary>
internal static void Reload()
{
if (_handle != IntPtr.Zero)
if (_handle != 0)
{
foreach (CoolerMasterDevicesIndexes index in Enum.GetValues(typeof(CoolerMasterDevicesIndexes)))
EnableLedControl(false, index);
@ -34,7 +34,7 @@ internal static class _CoolerMasterSDK
private static void LoadCMSDK()
{
if (_handle != IntPtr.Zero) return;
if (_handle != 0) return;
// HACK: Load library at runtime to support both, x86 and x64 with one managed dll
List<string> possiblePathList = (Environment.Is64BitProcess ? CoolerMasterDeviceProvider.PossibleX64NativePaths : CoolerMasterDeviceProvider.PossibleX86NativePaths)
@ -47,7 +47,7 @@ internal static class _CoolerMasterSDK
#if NET6_0
if (_handle == IntPtr.Zero) throw new RGBDeviceException($"CoolerMaster LoadLibrary failed with error code {Marshal.GetLastPInvokeError()}");
#else
if (_handle == IntPtr.Zero) throw new RGBDeviceException($"CoolerMaster LoadLibrary failed with error code {Marshal.GetLastWin32Error()}");
if (_handle == 0) throw new RGBDeviceException($"CoolerMaster LoadLibrary failed with error code {Marshal.GetLastWin32Error()}");
#endif
_getSDKVersionPointer = (GetSDKVersionPointer)Marshal.GetDelegateForFunctionPointer(GetProcAddress(_handle, "GetCM_SDK_DllVer"), typeof(GetSDKVersionPointer));
@ -62,7 +62,7 @@ internal static class _CoolerMasterSDK
internal static void UnloadCMSDK()
{
if (_handle == IntPtr.Zero) return;
if (_handle == 0) return;
_getSDKVersionPointer = null;
_setControlDevicenPointer = null;
@ -74,14 +74,14 @@ internal static class _CoolerMasterSDK
_setAllLedColorPointer = null;
NativeLibrary.Free(_handle);
_handle = IntPtr.Zero;
_handle = 0;
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr LoadLibrary(string dllToLoad);
private static extern nint LoadLibrary(string dllToLoad);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi)]
private static extern IntPtr GetProcAddress(IntPtr dllHandle, string name);
private static extern nint GetProcAddress(nint dllHandle, string name);
#endregion

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net6.0;net5.0</TargetFrameworks>
<TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
@ -40,7 +40,7 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>$(DefineConstants);TRACE;DEBUG</DefineConstants>
<DefineConstants>TRACE;DEBUG</DefineConstants>
<DebugSymbols>true</DebugSymbols>
<Optimize>false</Optimize>
</PropertyGroup>
@ -48,7 +48,7 @@
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<Optimize>true</Optimize>
<NoWarn>$(NoWarn);CS1591;CS1572;CS1573</NoWarn>
<DefineConstants>$(DefineConstants);RELEASE</DefineConstants>
<DefineConstants>RELEASE</DefineConstants>
</PropertyGroup>
<ItemGroup>

View File

@ -0,0 +1,34 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
using RGB.NET.Core;
using System.Collections.Generic;
namespace RGB.NET.Devices.Corsair;
/// <inheritdoc cref="CorsairRGBDevice{TDeviceInfo}" />
/// <summary>
/// Represents a corsair cooler.
/// </summary>
public sealed class CorsairCoolerRGBDevice : CorsairRGBDevice<CorsairCoolerRGBDeviceInfo>, ICooler
{
#region Constructors
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Devices.Corsair.CorsairCoolerRGBDevice" /> class.
/// </summary>
/// <param name="info">The specific information provided by CUE for the cooler.</param>
/// <param name="updateQueue">The queue used to update this device.</param>
internal CorsairCoolerRGBDevice(CorsairCoolerRGBDeviceInfo info, CorsairDeviceUpdateQueue updateQueue)
: base(info, updateQueue)
{ }
#endregion
#region Methods
protected override LedMapping<CorsairLedId> CreateMapping(IEnumerable<CorsairLedId> ids) => LedMappings.CreateCoolerMapping(ids);
#endregion
}

View File

@ -0,0 +1,28 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMember.Global
using RGB.NET.Core;
using RGB.NET.Devices.Corsair.Native;
namespace RGB.NET.Devices.Corsair;
/// <inheritdoc />
/// <summary>
/// Represents a generic information for a <see cref="T:RGB.NET.Devices.Corsair.CorsairCoolerRGBDevice" />.
/// </summary>
public sealed class CorsairCoolerRGBDeviceInfo : CorsairRGBDeviceInfo
{
#region Constructors
/// <inheritdoc />
internal CorsairCoolerRGBDeviceInfo(_CorsairDeviceInfo nativeInfo, int ledCount, int ledOffset)
: base(RGBDeviceType.Cooler, nativeInfo, ledCount, ledOffset)
{ }
/// <inheritdoc />
internal CorsairCoolerRGBDeviceInfo(_CorsairDeviceInfo nativeInfo, int ledCount, int ledOffset, string modelName)
: base(RGBDeviceType.Cooler, nativeInfo, ledCount, ledOffset, modelName)
{ }
#endregion
}

View File

@ -3,8 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using RGB.NET.Core;
using RGB.NET.Devices.Corsair.Native;
@ -14,37 +13,72 @@ namespace RGB.NET.Devices.Corsair;
/// <summary>
/// Represents a device provider responsible for corsair (CUE) devices.
/// </summary>
public class CorsairDeviceProvider : AbstractRGBDeviceProvider
public sealed class CorsairDeviceProvider : AbstractRGBDeviceProvider
{
#region Properties & Fields
// ReSharper disable once InconsistentNaming
private static readonly object _lock = new();
private static CorsairDeviceProvider? _instance;
/// <summary>
/// Gets the singleton <see cref="CorsairDeviceProvider"/> instance.
/// </summary>
public static CorsairDeviceProvider Instance => _instance ?? new CorsairDeviceProvider();
public static CorsairDeviceProvider Instance
{
get
{
lock (_lock)
return _instance ?? new CorsairDeviceProvider();
}
}
/// <summary>
/// Gets a modifiable list of paths used to find the native SDK-dlls for x86 applications.
/// The first match will be used.
/// </summary>
public static List<string> PossibleX86NativePaths { get; } = new() { "x86/CUESDK.dll", "x86/CUESDK_2019.dll", "x86/CUESDK_2017.dll", "x86/CUESDK_2015.dll", "x86/CUESDK_2013.dll" };
public static List<string> PossibleX86NativePaths { get; } = ["x86/iCUESDK.dll", "x86/CUESDK_2019.dll"];
/// <summary>
/// Gets a modifiable list of paths used to find the native SDK-dlls for x64 applications.
/// The first match will be used.
/// </summary>
public static List<string> PossibleX64NativePaths { get; } = new() { "x64/CUESDK.dll", "x64/CUESDK.x64_2019.dll", "x64/CUESDK.x64_2017.dll", "x64/CUESDK_2019.dll", "x64/CUESDK_2017.dll", "x64/CUESDK_2015.dll", "x64/CUESDK_2013.dll" };
public static List<string> PossibleX64NativePaths { get; } = ["x64/iCUESDK.dll", "x64/iCUESDK.x64_2019.dll", "x64/CUESDK.dll", "x64/CUESDK.x64_2019.dll"];
/// <summary>
/// Gets the protocol details for the current SDK-connection.
/// Gets or sets the timeout used when connecting to the SDK.
/// </summary>
public CorsairProtocolDetails? ProtocolDetails { get; private set; }
public static TimeSpan ConnectionTimeout { get; set; } = TimeSpan.FromMilliseconds(500);
/// <summary>
/// Gets the last error documented by CUE.
/// Gets or sets a bool indicating if exclusive request should be requested through the iCUE-SDK.
/// </summary>
public static CorsairError LastError => _CUESDK.CorsairGetLastError();
public static bool ExclusiveAccess { get; set; } = false;
/// <summary>
/// Gets the details for the current SDK-session.
/// </summary>
public CorsairSessionDetails SessionDetails { get; private set; } = new();
private CorsairSessionState _sessionState = CorsairSessionState.Invalid;
public CorsairSessionState SessionState
{
get => _sessionState;
private set
{
_sessionState = value;
try { SessionStateChanged?.Invoke(this, SessionState); }
catch { /* catch faulty event-handlers*/ }
}
}
#endregion
#region Events
// ReSharper disable once UnassignedField.Global
public EventHandler<CorsairSessionState>? SessionStateChanged;
#endregion
@ -56,35 +90,56 @@ public class CorsairDeviceProvider : AbstractRGBDeviceProvider
/// <exception cref="InvalidOperationException">Thrown if this constructor is called even if there is already an instance of this class.</exception>
public CorsairDeviceProvider()
{
if (_instance != null) throw new InvalidOperationException($"There can be only one instance of type {nameof(CorsairDeviceProvider)}");
_instance = this;
lock (_lock)
{
if (_instance != null) throw new InvalidOperationException($"There can be only one instance of type {nameof(CorsairDeviceProvider)}");
_instance = this;
}
}
#endregion
#region Methods
/// <inheritdoc />
protected override void InitializeSDK()
{
_CUESDK.Reload();
ProtocolDetails = new CorsairProtocolDetails(_CUESDK.CorsairPerformProtocolHandshake());
using ManualResetEventSlim waitEvent = new(false);
CorsairError error = LastError;
if (error != CorsairError.Success)
Throw(new CUEException(error), true);
void OnInitializeSessionStateChanged(object? sender, CorsairSessionState state)
{
if (state == CorsairSessionState.Connected)
// ReSharper disable once AccessToDisposedClosure
waitEvent.Set();
}
if (ProtocolDetails.BreakingChanges)
Throw(new RGBDeviceException("The SDK currently used isn't compatible with the installed version of CUE.\r\n"
+ $"CUE-Version: {ProtocolDetails.ServerVersion} (Protocol {ProtocolDetails.ServerProtocolVersion})\r\n"
+ $"SDK-Version: {ProtocolDetails.SdkVersion} (Protocol {ProtocolDetails.SdkProtocolVersion})"), true);
try
{
_CUESDK.SessionStateChanged += OnSessionStateChanged;
_CUESDK.SessionStateChanged += OnInitializeSessionStateChanged;
// DarthAffe 02.02.2021: 127 is iCUE
if (!_CUESDK.CorsairSetLayerPriority(128))
Throw(new CUEException(LastError));
CorsairError errorCode = _CUESDK.CorsairConnect();
if (errorCode != CorsairError.Success)
Throw(new RGBDeviceException($"Failed to initialized Corsair-SDK. (ErrorCode: {errorCode})"));
if (!waitEvent.Wait(ConnectionTimeout))
Throw(new RGBDeviceException($"Failed to initialized Corsair-SDK. (Timeout - Current connection state: {_CUESDK.SesionState})"));
_CUESDK.CorsairGetSessionDetails(out _CorsairSessionDetails? details);
if (errorCode != CorsairError.Success)
Throw(new RGBDeviceException($"Failed to get session details. (ErrorCode: {errorCode})"));
SessionDetails = new CorsairSessionDetails(details!);
}
finally
{
_CUESDK.SessionStateChanged -= OnInitializeSessionStateChanged;
}
}
private void OnSessionStateChanged(object? sender, CorsairSessionState state) => SessionState = state;
/// <inheritdoc />
protected override IEnumerable<IRGBDevice> LoadDevices()
{
@ -97,119 +152,190 @@ public class CorsairDeviceProvider : AbstractRGBDeviceProvider
private IEnumerable<ICorsairRGBDevice> LoadCorsairDevices()
{
int deviceCount = _CUESDK.CorsairGetDeviceCount();
for (int i = 0; i < deviceCount; i++)
CorsairError error = _CUESDK.CorsairGetDevices(new _CorsairDeviceFilter(CorsairDeviceType.All), out _CorsairDeviceInfo[] devices);
if (error != CorsairError.Success)
Throw(new RGBDeviceException($"Failed to load devices. (ErrorCode: {error})"));
foreach (_CorsairDeviceInfo device in devices)
{
_CorsairDeviceInfo nativeDeviceInfo = (_CorsairDeviceInfo)Marshal.PtrToStructure(_CUESDK.CorsairGetDeviceInfo(i), typeof(_CorsairDeviceInfo))!;
if (!((CorsairDeviceCaps)nativeDeviceInfo.capsMask).HasFlag(CorsairDeviceCaps.Lighting))
continue; // Everything that doesn't support lighting control is useless
if (string.IsNullOrWhiteSpace(device.id)) continue;
CorsairDeviceUpdateQueue updateQueue = new(GetUpdateTrigger(), i);
switch (nativeDeviceInfo.type)
error = _CUESDK.CorsairRequestControl(device.id, ExclusiveAccess ? CorsairAccessLevel.ExclusiveLightingControl : CorsairAccessLevel.Shared);
if (error != CorsairError.Success)
Throw(new RGBDeviceException($"Failed to take control of device '{device.id}'. (ErrorCode: {error})"));
CorsairDeviceUpdateQueue updateQueue = new(GetUpdateTrigger(), device);
int channelLedCount = 0;
for (int i = 0; i < device.channelCount; i++)
{
case CorsairDeviceType.Keyboard:
yield return new CorsairKeyboardRGBDevice(new CorsairKeyboardRGBDeviceInfo(i, nativeDeviceInfo), updateQueue);
break;
Console.WriteLine($"Channel {i}/{device.channelCount}");
channelLedCount += _CUESDK.ReadDevicePropertySimpleInt32(device.id!, CorsairDevicePropertyId.ChannelLedCount, (uint)i);
}
case CorsairDeviceType.Mouse:
yield return new CorsairMouseRGBDevice(new CorsairMouseRGBDeviceInfo(i, nativeDeviceInfo), updateQueue);
break;
int deviceLedCount = device.ledCount - channelLedCount;
if (deviceLedCount > 0)
switch (device.type)
{
case CorsairDeviceType.Keyboard:
yield return new CorsairKeyboardRGBDevice(new CorsairKeyboardRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.Headset:
yield return new CorsairHeadsetRGBDevice(new CorsairHeadsetRGBDeviceInfo(i, nativeDeviceInfo), updateQueue);
break;
case CorsairDeviceType.Mouse:
yield return new CorsairMouseRGBDevice(new CorsairMouseRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.Mousepad:
yield return new CorsairMousepadRGBDevice(new CorsairMousepadRGBDeviceInfo(i, nativeDeviceInfo), updateQueue);
break;
case CorsairDeviceType.Headset:
yield return new CorsairHeadsetRGBDevice(new CorsairHeadsetRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.HeadsetStand:
yield return new CorsairHeadsetStandRGBDevice(new CorsairHeadsetStandRGBDeviceInfo(i, nativeDeviceInfo), updateQueue);
break;
case CorsairDeviceType.Mousemat:
yield return new CorsairMousepadRGBDevice(new CorsairMousepadRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.MemoryModule:
yield return new CorsairMemoryRGBDevice(new CorsairMemoryRGBDeviceInfo(i, nativeDeviceInfo), updateQueue);
break;
case CorsairDeviceType.HeadsetStand:
yield return new CorsairHeadsetStandRGBDevice(new CorsairHeadsetStandRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.Mainboard:
yield return new CorsairMainboardRGBDevice(new CorsairMainboardRGBDeviceInfo(i, nativeDeviceInfo), updateQueue);
break;
case CorsairDeviceType.MemoryModule:
yield return new CorsairMemoryRGBDevice(new CorsairMemoryRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.GraphicsCard:
yield return new CorsairGraphicsCardRGBDevice(new CorsairGraphicsCardRGBDeviceInfo(i, nativeDeviceInfo), updateQueue);
break;
case CorsairDeviceType.Motherboard:
yield return new CorsairMainboardRGBDevice(new CorsairMainboardRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.Touchbar:
yield return new CorsairTouchbarRGBDevice(new CorsairTouchbarRGBDeviceInfo(i, nativeDeviceInfo), updateQueue);
break;
case CorsairDeviceType.GraphicsCard:
yield return new CorsairGraphicsCardRGBDevice(new CorsairGraphicsCardRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.Cooler:
case CorsairDeviceType.CommanderPro:
case CorsairDeviceType.LightningNodePro:
List<_CorsairChannelInfo> channels = GetChannels(nativeDeviceInfo).ToList();
int channelsLedCount = channels.Sum(x => x.totalLedsCount);
int deviceLedCount = nativeDeviceInfo.ledsCount - channelsLedCount;
case CorsairDeviceType.Touchbar:
yield return new CorsairTouchbarRGBDevice(new CorsairTouchbarRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
if (deviceLedCount > 0)
yield return new CorsairCustomRGBDevice(new CorsairCustomRGBDeviceInfo(i, nativeDeviceInfo, deviceLedCount), updateQueue);
case CorsairDeviceType.Cooler:
yield return new CorsairCoolerRGBDevice(new CorsairCoolerRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
int ledOffset = deviceLedCount;
foreach (_CorsairChannelInfo channelInfo in channels)
case CorsairDeviceType.GameController:
yield return new CorsairGameControllerRGBDevice(new CorsairGameControllerRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
case CorsairDeviceType.FanLedController:
case CorsairDeviceType.LedController:
case CorsairDeviceType.Unknown:
yield return new CorsairUnknownRGBDevice(new CorsairUnknownRGBDeviceInfo(device, deviceLedCount, 0), updateQueue);
break;
default:
Throw(new RGBDeviceException("Unknown Device-Type"));
break;
}
int offset = deviceLedCount;
for (int i = 0; i < device.channelCount; i++)
{
int deviceCount = _CUESDK.ReadDevicePropertySimpleInt32(device.id!, CorsairDevicePropertyId.ChannelDeviceCount, (uint)i);
if (deviceCount <= 0) continue; // DarthAffe 10.02.2023: There seem to be an issue in the SDK where it reports empty channels and fails when getting ledCounts and device types from them
int[] ledCounts = _CUESDK.ReadDevicePropertySimpleInt32Array(device.id!, CorsairDevicePropertyId.ChannelDeviceLedCountArray, (uint)i);
int[] deviceTypes = _CUESDK.ReadDevicePropertySimpleInt32Array(device.id!, CorsairDevicePropertyId.ChannelDeviceTypeArray, (uint)i);
for (int j = 0; j < deviceCount; j++)
{
CorsairChannelDeviceType deviceType = (CorsairChannelDeviceType)deviceTypes[j];
int ledCount = ledCounts[j];
switch (deviceType)
{
int channelDeviceInfoStructSize = Marshal.SizeOf(typeof(_CorsairChannelDeviceInfo));
IntPtr channelDeviceInfoPtr = channelInfo.devices;
for (int device = 0; (device < channelInfo.devicesCount) && (ledOffset < nativeDeviceInfo.ledsCount); device++)
{
_CorsairChannelDeviceInfo channelDeviceInfo = (_CorsairChannelDeviceInfo)Marshal.PtrToStructure(channelDeviceInfoPtr, typeof(_CorsairChannelDeviceInfo))!;
case CorsairChannelDeviceType.FanHD:
yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "HD Fan"), updateQueue);
break;
yield return new CorsairCustomRGBDevice(new CorsairCustomRGBDeviceInfo(i, nativeDeviceInfo, channelDeviceInfo, ledOffset), updateQueue);
case CorsairChannelDeviceType.FanSP:
yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "SP Fan"), updateQueue);
break;
ledOffset += channelDeviceInfo.deviceLedCount;
channelDeviceInfoPtr = new IntPtr(channelDeviceInfoPtr.ToInt64() + channelDeviceInfoStructSize);
}
case CorsairChannelDeviceType.FanLL:
yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "LL Fan"), updateQueue);
break;
case CorsairChannelDeviceType.FanML:
yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "ML Fan"), updateQueue);
break;
case CorsairChannelDeviceType.FanQL:
yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "QL Fan"), updateQueue);
break;
case CorsairChannelDeviceType.FanQX:
yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "QX Fan"), updateQueue);
break;
case CorsairChannelDeviceType.EightLedSeriesFan:
yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "8-Led-Series Fan Fan"), updateQueue);
break;
case CorsairChannelDeviceType.DAP:
yield return new CorsairFanRGBDevice(new CorsairFanRGBDeviceInfo(device, ledCount, offset, "DAP Fan"), updateQueue);
break;
case CorsairChannelDeviceType.Pump:
yield return new CorsairCoolerRGBDevice(new CorsairCoolerRGBDeviceInfo(device, ledCount, offset, "Pump"), updateQueue);
break;
case CorsairChannelDeviceType.WaterBlock:
yield return new CorsairCoolerRGBDevice(new CorsairCoolerRGBDeviceInfo(device, ledCount, offset, "Water Block"), updateQueue);
break;
case CorsairChannelDeviceType.Strip:
string modelName = "LED Strip";
// LS100 Led Strips are reported as one big strip if configured in monitor mode in iCUE, 138 LEDs for dual monitor, 84 for single
if ((device.model == "LS100 Starter Kit") && (ledCount == 138))
modelName = "LS100 LED Strip (dual monitor)";
else if ((device.model == "LS100 Starter Kit") && (ledCount == 84))
modelName = "LS100 LED Strip (single monitor)";
// Any other value means an "External LED Strip" in iCUE, these are reported per-strip, 15 for short strips, 27 for long
else if ((device.model == "LS100 Starter Kit") && (ledCount == 15))
modelName = "LS100 LED Strip (short)";
else if ((device.model == "LS100 Starter Kit") && (ledCount == 27))
modelName = "LS100 LED Strip (long)";
yield return new CorsairLedStripRGBDevice(new CorsairLedStripRGBDeviceInfo(device, ledCount, offset, modelName), updateQueue);
break;
case CorsairChannelDeviceType.DRAM:
yield return new CorsairMemoryRGBDevice(new CorsairMemoryRGBDeviceInfo(device, ledCount, offset, "DRAM"), updateQueue);
break;
default:
Throw(new RGBDeviceException("Unknown Device-Type"));
break;
}
break;
default:
Throw(new RGBDeviceException("Unknown Device-Type"));
break;
offset += ledCount;
}
}
}
}
private static IEnumerable<_CorsairChannelInfo> GetChannels(_CorsairDeviceInfo deviceInfo)
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
_CorsairChannelsInfo? channelsInfo = deviceInfo.channels;
if (channelsInfo == null) yield break;
IntPtr channelInfoPtr = channelsInfo.channels;
for (int channel = 0; channel < channelsInfo.channelsCount; channel++)
lock (_lock)
{
yield return (_CorsairChannelInfo)Marshal.PtrToStructure(channelInfoPtr, typeof(_CorsairChannelInfo))!;
base.Dispose(disposing);
int channelInfoStructSize = Marshal.SizeOf(typeof(_CorsairChannelInfo));
channelInfoPtr = new IntPtr(channelInfoPtr.ToInt64() + channelInfoStructSize);
try { _CUESDK.CorsairDisconnect(); }
catch { /* at least we tried */ }
try { _CUESDK.UnloadCUESDK(); }
catch { /* at least we tried */ }
_instance = null;
}
}
/// <inheritdoc />
protected override void Reset()
{
ProtocolDetails = null;
base.Reset();
}
/// <inheritdoc />
public override void Dispose()
{
base.Dispose();
try { _CUESDK.UnloadCUESDK(); }
catch { /* at least we tried */ }
GC.SuppressFinalize(this);
}
#endregion
}

Some files were not shown because too many files have changed in this diff Show More