// ReSharper disable MemberCanBePrivate.Global // ReSharper disable UnusedMethodReturnValue.Global using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using CUE.NET.Devices.Generic.Enums; using CUE.NET.Devices.Generic.EventArgs; using CUE.NET.Native; namespace CUE.NET.Devices.Generic { /// /// Represents a generic CUE-device. (keyboard, mouse, headset, ...) /// public abstract class AbstractCueDevice : ICueDevice { #region Properties & Fields /// /// Gets generic information provided by CUE for the device. /// public IDeviceInfo DeviceInfo { get; } private UpdateMode _updateMode = UpdateMode.Manual; /// /// Gets or sets the update-mode for the device. /// public UpdateMode UpdateMode { get { return _updateMode; } set { _updateMode = value; CheckUpdateLoop(); } } /// /// Gets or sets the update-frequency in seconds. (Calculate by using '1f / updates per second') /// public float UpdateFrequency { get; set; } = 1f / 30f; /// /// Gets a dictionary containing all LEDs of the device. /// protected Dictionary Leds { get; } = new Dictionary(); private CancellationTokenSource _updateTokenSource; private CancellationToken _updateToken; private Task _updateTask; private DateTime _lastUpdate = DateTime.Now; #endregion #region Events /// /// Occurs when a catched exception is thrown inside the device. /// public event ExceptionEventHandler Exception; /// /// Occurs when the device starts updating. /// public event UpdatingEventHandler Updating; /// /// Occurs when the device update is done. /// public event UpdatedEventHandler Updated; /// /// Occurs when the device starts to update the leds. /// public event LedsUpdatingEventHandler LedsUpdating; /// /// Occurs when the device updated the leds. /// public event LedsUpdatedEventHandler LedsUpdated; #endregion #region Constructors /// /// Initializes a new instance of the class. /// /// The generic information provided by CUE for the device. protected AbstractCueDevice(IDeviceInfo info) { this.DeviceInfo = info; CheckUpdateLoop(); } #endregion #region Methods /// /// Gets the LED-Object with the specified id. /// /// The LED-Id to look for. /// protected CorsairLed GetLed(int ledId) { if (!Leds.ContainsKey(ledId)) Leds.Add(ledId, new CorsairLed()); return Leds[ledId]; } /// /// Checks if automatic updates should occur and starts/stops the update-loop if needed. /// /// Thrown if the requested update-mode is not available. protected async void CheckUpdateLoop() { bool shouldRun; switch (UpdateMode) { case UpdateMode.Manual: shouldRun = false; break; case UpdateMode.Continuous: shouldRun = true; break; default: throw new ArgumentOutOfRangeException(); } if (shouldRun && _updateTask == null) // Start task { _updateTokenSource?.Dispose(); _updateTokenSource = new CancellationTokenSource(); _updateTask = Task.Factory.StartNew(UpdateLoop, (_updateToken = _updateTokenSource.Token)); } else if (!shouldRun && _updateTask != null) // Stop task { _updateTokenSource.Cancel(); await _updateTask; _updateTask.Dispose(); _updateTask = null; } } private void UpdateLoop() { while (!_updateToken.IsCancellationRequested) { long preUpdateTicks = DateTime.Now.Ticks; Update(); int sleep = (int)((UpdateFrequency * 1000f) - ((DateTime.Now.Ticks - preUpdateTicks) / 10000f)); if (sleep > 0) Thread.Sleep(sleep); } } /// /// Performs an update for all dirty keys, or all keys if flushLeds is set to true. /// /// Specifies whether all keys (including clean ones) should be updated. public void Update(bool flushLeds = false) { OnUpdating(); DeviceUpdateEffects(); DeviceUpdate(); ICollection ledsToUpdate = (flushLeds ? Leds : Leds.Where(x => x.Value.IsDirty)).Select(x => new LedUpateRequest(x.Key, x.Value.RequestedColor)).ToList(); foreach (CorsairLed led in Leds.Values) led.Update(); UpdateLeds(ledsToUpdate); OnUpdated(); } /// /// Performs device specific updates. /// protected abstract void DeviceUpdate(); /// /// Performs device specific updates effect-updates. /// protected abstract void DeviceUpdateEffects(); private void UpdateLeds(ICollection updateRequests) { updateRequests = updateRequests.Where(x => x.Color != Color.Transparent).ToList(); OnLedsUpdating(updateRequests); if (updateRequests.Any()) // CUE seems to crash if 'CorsairSetLedsColors' is called with a zero length array { int structSize = Marshal.SizeOf(typeof(_CorsairLedColor)); IntPtr ptr = Marshal.AllocHGlobal(structSize * updateRequests.Count); IntPtr addPtr = new IntPtr(ptr.ToInt64()); foreach (LedUpateRequest ledUpdateRequest in updateRequests) { _CorsairLedColor color = new _CorsairLedColor { ledId = ledUpdateRequest.LedId, r = ledUpdateRequest.Color.R, g = ledUpdateRequest.Color.G, b = ledUpdateRequest.Color.B }; Marshal.StructureToPtr(color, addPtr, false); addPtr = new IntPtr(addPtr.ToInt64() + structSize); } _CUESDK.CorsairSetLedsColors(updateRequests.Count, ptr); Marshal.FreeHGlobal(ptr); } OnLedsUpdated(updateRequests); } /// /// Resets all loaded LEDs back to default. /// internal void ResetLeds() { foreach (CorsairLed led in Leds.Values) led.Reset(); } #region EventCaller /// /// Handles the needed event-calls for an exception. /// /// The exception previously thrown. protected virtual void OnException(Exception ex) { try { Exception?.Invoke(this, new ExceptionEventArgs(ex)); } catch { // Well ... that's not my fault } } /// /// Handles the needed event-calls before updating. /// protected virtual void OnUpdating() { try { long lastUpdateTicks = _lastUpdate.Ticks; _lastUpdate = DateTime.Now; Updating?.Invoke(this, new UpdatingEventArgs((float)((DateTime.Now.Ticks - lastUpdateTicks) / 10000000f))); } catch { // Well ... that's not my fault } } /// /// Handles the needed event-calls after an update. /// protected virtual void OnUpdated() { try { Updated?.Invoke(this, new UpdatedEventArgs()); } catch { // Well ... that's not my fault } } /// /// Handles the needed event-calls before the leds are updated. /// protected virtual void OnLedsUpdating(ICollection updatingLeds) { try { LedsUpdating?.Invoke(this, new LedsUpdatingEventArgs(updatingLeds)); } catch { // Well ... that's not my fault } } /// /// Handles the needed event-calls after the leds are updated. /// protected virtual void OnLedsUpdated(IEnumerable updatedLeds) { try { LedsUpdated?.Invoke(this, new LedsUpdatedEventArgs(updatedLeds)); } catch { // Well ... that's not my fault } } #endregion #endregion } }