diff --git a/NuGet/RGB.NET.Devices.Logitech.nuspec b/NuGet/RGB.NET.Devices.Logitech.nuspec new file mode 100644 index 0000000..7c3c9eb --- /dev/null +++ b/NuGet/RGB.NET.Devices.Logitech.nuspec @@ -0,0 +1,31 @@ + + + + RGB.NET.Devices.Logitech + CUE.NET.Devices.Logitech + 1.0.0.0 + Darth Affe + Darth Affe + https://github.com/DarthAffe/RGB.NET + https://raw.githubusercontent.com/DarthAffe/RGB.NET/master/LICENSE + true + Logitech-Device-Implementations of RGB.NET + + Logitech-Device-Implementations of RGB.NET, a C# (.NET) library for accessing various RGB-peripherals + Copyright © Wyrez 2017 + en-US + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/RGB.NET.Devices.Logitech/Enum/LogitechDeviceCaps.cs b/RGB.NET.Devices.Logitech/Enum/LogitechDeviceCaps.cs new file mode 100644 index 0000000..0d72242 --- /dev/null +++ b/RGB.NET.Devices.Logitech/Enum/LogitechDeviceCaps.cs @@ -0,0 +1,18 @@ +// ReSharper disable InconsistentNaming + +using System; + +#pragma warning disable 1591 // Missing XML comment for publicly visible type or member + +namespace RGB.NET.Devices.Logitech +{ + [Flags] + public enum LogitechDeviceCaps + { + None = 0, + Monochrome = 1 << 0, + DeviceRGB = 1 << 1, + PerKeyRGB = 1 << 2, + All = Monochrome | DeviceRGB | PerKeyRGB + } +} diff --git a/RGB.NET.Devices.Logitech/Enum/LogitechLedIds.cs b/RGB.NET.Devices.Logitech/Enum/LogitechLedIds.cs new file mode 100644 index 0000000..d82f868 --- /dev/null +++ b/RGB.NET.Devices.Logitech/Enum/LogitechLedIds.cs @@ -0,0 +1,129 @@ +// ReSharper disable InconsistentNaming + +#pragma warning disable 1591 // Missing XML comment for publicly visible type or member + +namespace RGB.NET.Devices.Logitech +{ + /// + /// Contains list of all LEDs available for all logitech devices. + /// + public enum LogitechLedIds + { + Invalid = 0, + ESC = 0x01, + F1 = 0x3b, + F2 = 0x3c, + F3 = 0x3d, + F4 = 0x3e, + F5 = 0x3f, + F6 = 0x40, + F7 = 0x41, + F8 = 0x42, + F9 = 0x43, + F10 = 0x44, + F11 = 0x57, + F12 = 0x58, + PRINT_SCREEN = 0x137, + SCROLL_LOCK = 0x46, + PAUSE_BREAK = 0x45, + TILDE = 0x29, + ONE = 0x02, + TWO = 0x03, + THREE = 0x04, + FOUR = 0x05, + FIVE = 0x06, + SIX = 0x07, + SEVEN = 0x08, + EIGHT = 0x09, + NINE = 0x0A, + ZERO = 0x0B, + MINUS = 0x0C, + EQUALS = 0x0D, + BACKSPACE = 0x0E, + INSERT = 0x152, + HOME = 0x147, + PAGE_UP = 0x149, + NUM_LOCK = 0x145, + NUM_SLASH = 0x135, + NUM_ASTERISK = 0x37, + NUM_MINUS = 0x4A, + TAB = 0x0F, + Q = 0x10, + W = 0x11, + E = 0x12, + R = 0x13, + T = 0x14, + Y = 0x15, + U = 0x16, + I = 0x17, + O = 0x18, + P = 0x19, + OPEN_BRACKET = 0x1A, + CLOSE_BRACKET = 0x1B, + BACKSLASH = 0x2B, + KEYBOARD_DELETE = 0x153, + END = 0x14F, + PAGE_DOWN = 0x151, + NUM_SEVEN = 0x47, + NUM_EIGHT = 0x48, + NUM_NINE = 0x49, + NUM_PLUS = 0x4E, + CAPS_LOCK = 0x3A, + A = 0x1E, + S = 0x1F, + D = 0x20, + F = 0x21, + G = 0x22, + H = 0x23, + J = 0x24, + K = 0x25, + L = 0x26, + SEMICOLON = 0x27, + APOSTROPHE = 0x28, + ENTER = 0x1C, + NUM_FOUR = 0x4B, + NUM_FIVE = 0x4C, + NUM_SIX = 0x4D, + LEFT_SHIFT = 0x2A, + Z = 0x2C, + X = 0x2D, + C = 0x2E, + V = 0x2F, + B = 0x30, + N = 0x31, + M = 0x32, + COMMA = 0x33, + PERIOD = 0x34, + FORWARD_SLASH = 0x35, + RIGHT_SHIFT = 0x36, + ARROW_UP = 0x148, + NUM_ONE = 0x4F, + NUM_TWO = 0x50, + NUM_THREE = 0x51, + NUM_ENTER = 0x11C, + LEFT_CONTROL = 0x1D, + LEFT_WINDOWS = 0x15B, + LEFT_ALT = 0x38, + SPACE = 0x39, + RIGHT_ALT = 0x138, + RIGHT_WINDOWS = 0x15C, + APPLICATION_SELECT = 0x15D, + RIGHT_CONTROL = 0x11D, + ARROW_LEFT = 0x14B, + ARROW_DOWN = 0x150, + ARROW_RIGHT = 0x14D, + NUM_ZERO = 0x52, + NUM_PERIOD = 0x53, + G_1 = 0xFFF1, + G_2 = 0xFFF2, + G_3 = 0xFFF3, + G_4 = 0xFFF4, + G_5 = 0xFFF5, + G_6 = 0xFFF6, + G_7 = 0xFFF7, + G_8 = 0xFFF8, + G_9 = 0xFFF9, + G_LOGO = 0xFFFF1, + G_BADGE = 0xFFFF2, + } +} diff --git a/RGB.NET.Devices.Logitech/Enum/LogitechLogicalKeyboardLayout.cs b/RGB.NET.Devices.Logitech/Enum/LogitechLogicalKeyboardLayout.cs new file mode 100644 index 0000000..808c12d --- /dev/null +++ b/RGB.NET.Devices.Logitech/Enum/LogitechLogicalKeyboardLayout.cs @@ -0,0 +1,32 @@ +// ReSharper disable InconsistentNaming +// ReSharper disable UnusedMember.Global + +#pragma warning disable 1591 // Missing XML comment for publicly visible type or member + +namespace RGB.NET.Devices.Logitech +{ + /// + /// Contains list of available logical layouts for logitech keyboards. + /// + public enum LogitechLogicalKeyboardLayout + { + US_Int = 1, + NA = 2, + EU = 3, + UK = 4, + BE = 5, + BR = 6, + CH = 7, + CN = 8, + DE = 9, + ES = 10, + FR = 11, + IT = 12, + ND = 13, + RU = 14, + JP = 15, + KR = 16, + TW = 17, + MEX = 18 + }; +} diff --git a/RGB.NET.Devices.Logitech/Enum/LogitechPhysicalKeyboardLayout.cs b/RGB.NET.Devices.Logitech/Enum/LogitechPhysicalKeyboardLayout.cs new file mode 100644 index 0000000..95e608e --- /dev/null +++ b/RGB.NET.Devices.Logitech/Enum/LogitechPhysicalKeyboardLayout.cs @@ -0,0 +1,36 @@ +// ReSharper disable UnusedMember.Global +// ReSharper disable InconsistentNaming + +namespace RGB.NET.Devices.Logitech +{ + /// + /// Contains list of available physical layouts for logitech keyboards. + /// + public enum LogitechPhysicalKeyboardLayout + { + /// + /// US-Keyboard + /// + US = 1, + + /// + /// UK-Keyboard + /// + UK = 2, + + /// + /// BR-Keyboard + /// + BR = 3, + + /// + /// JP-Keyboard + /// + JP = 4, + + /// + /// KR-Keyboard + /// + KR = 5 + } +} diff --git a/RGB.NET.Devices.Logitech/Generic/LogitechLedId.cs b/RGB.NET.Devices.Logitech/Generic/LogitechLedId.cs new file mode 100644 index 0000000..58a27ed --- /dev/null +++ b/RGB.NET.Devices.Logitech/Generic/LogitechLedId.cs @@ -0,0 +1,107 @@ +using System.Diagnostics; +using RGB.NET.Core; + +namespace RGB.NET.Devices.Logitech +{ + /// + /// Represents a Id of a on a . + /// + [DebuggerDisplay("{" + nameof(LedId) + "}")] + public class LogitechLedId : ILedId + { + #region Properties & Fields + + internal readonly LogitechLedIds LedId; + + /// + public IRGBDevice Device { get; } + + /// + public bool IsValid => LedId != LogitechLedIds.Invalid; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The the belongs to. + /// The of the represented . + public LogitechLedId(IRGBDevice device, LogitechLedIds ledId) + { + this.Device = device; + this.LedId = ledId; + } + + #endregion + + #region Methods + + /// + /// Converts the Id of this to a human-readable string. + /// + /// A string that contains the Id of this . For example "Enter". + public override string ToString() + { + return LedId.ToString(); + } + + /// + /// Tests whether the specified object is a and is equivalent to this . + /// + /// The object to test. + /// true if is a equivalent to this ; otherwise, false. + public override bool Equals(object obj) + { + LogitechLedId compareLedId = obj as LogitechLedId; + if (ReferenceEquals(compareLedId, null)) + return false; + + if (ReferenceEquals(this, compareLedId)) + return true; + + if (GetType() != compareLedId.GetType()) + return false; + + return compareLedId.LedId == LedId; + } + + /// + /// Returns a hash code for this . + /// + /// An integer value that specifies the hash code for this . + public override int GetHashCode() + { + return LedId.GetHashCode(); + } + + #endregion + + #region Operators + + /// + /// Returns a value that indicates whether two specified are equal. + /// + /// The first to compare. + /// The second to compare. + /// true if and are equal; otherwise, false. + public static bool operator ==(LogitechLedId ledId1, LogitechLedId ledId2) + { + return ReferenceEquals(ledId1, null) ? ReferenceEquals(ledId2, null) : ledId1.Equals(ledId2); + } + + /// + /// Returns a value that indicates whether two specified are equal. + /// + /// The first to compare. + /// The second to compare. + /// true if and are not equal; otherwise, false. + public static bool operator !=(LogitechLedId ledId1, LogitechLedId ledId2) + { + return !(ledId1 == ledId2); + } + + #endregion + } +} diff --git a/RGB.NET.Devices.Logitech/Generic/LogitechRGBDevice.cs b/RGB.NET.Devices.Logitech/Generic/LogitechRGBDevice.cs new file mode 100644 index 0000000..8336467 --- /dev/null +++ b/RGB.NET.Devices.Logitech/Generic/LogitechRGBDevice.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using RGB.NET.Core; +using RGB.NET.Core.Layout; +using RGB.NET.Devices.Logitech.Native; + +namespace RGB.NET.Devices.Logitech +{ + /// + /// Represents a generic Logitech-device. (keyboard, mouse, headset, mousmat). + /// + public abstract class LogitechRGBDevice : AbstractRGBDevice + { + #region Properties & Fields + + /// + /// Gets information about the . + /// + public override IRGBDeviceInfo DeviceInfo { get; } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The generic information provided by Logitech for the device. + protected LogitechRGBDevice(IRGBDeviceInfo info) + { + this.DeviceInfo = info; + } + + #endregion + + #region Methods + + /// + /// Initializes the device. + /// + internal void Initialize() + { + InitializeLayout(); + + if (InternalSize == null) + { + Rectangle ledRectangle = new Rectangle(this.Select(x => x.LedRectangle)); + InternalSize = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y); + } + } + + /// + /// Initializes the and of the device. + /// + protected abstract void InitializeLayout(); + + /// + /// Applies the given layout. + /// + /// The file containing the layout. + protected void ApplyLayoutFromFile(string layoutPath) + { + DeviceLayout layout = DeviceLayout.Load(layoutPath); + if (layout != null) + { + InternalSize = new Size(layout.Width, layout.Height); + + if (layout.Leds != null) + foreach (LedLayout layoutLed in layout.Leds) + { + LogitechLedIds ledId; + if (Enum.TryParse(layoutLed.Id, true, out ledId)) + { + LogitechLedId id = new LogitechLedId(this, ledId); + Led led; + if (!LedMapping.TryGetValue(id, out led)) + led = InitializeLed(id, new Rectangle()); + + led.LedRectangle.Location.X = layoutLed.X; + led.LedRectangle.Location.Y = layoutLed.Y; + led.LedRectangle.Size.Width = layoutLed.Width; + led.LedRectangle.Size.Height = layoutLed.Height; + + led.Shape = layoutLed.Shape; + } + } + } + } + + /// + protected override void UpdateLeds(IEnumerable ledsToUpdate) + { + List leds = ledsToUpdate.Where(x => x.Color.A > 0).ToList(); + + foreach (Led led in leds) + _LogitechGSDK.LogiLedSetLightingForKeyWithKeyName((int)((LogitechLedId)led.Id).LedId, + (int)Math.Round(led.Color.RPercent * 100), + (int)Math.Round(led.Color.GPercent * 100), + (int)Math.Round(led.Color.BPercent * 100)); + } + + #endregion + } +} diff --git a/RGB.NET.Devices.Logitech/Generic/LogitechRGBDeviceInfo.cs b/RGB.NET.Devices.Logitech/Generic/LogitechRGBDeviceInfo.cs new file mode 100644 index 0000000..9876581 --- /dev/null +++ b/RGB.NET.Devices.Logitech/Generic/LogitechRGBDeviceInfo.cs @@ -0,0 +1,64 @@ +using System; +using RGB.NET.Core; + +namespace RGB.NET.Devices.Logitech +{ + /// + /// Represents a generic information for a Logitech-. + /// + public class LogitechRGBDeviceInfo : IRGBDeviceInfo + { + #region Properties & Fields + + /// + public RGBDeviceType DeviceType { get; } + + /// + public string Manufacturer => "Logitech"; + + /// + public string Model { get; } + + /// + public Uri Image { get; protected set; } + + /// + public RGBDeviceLighting Lighting + { + get + { + if (DeviceCaps.HasFlag(LogitechDeviceCaps.PerKeyRGB)) + return RGBDeviceLighting.Key; + + if (DeviceCaps.HasFlag(LogitechDeviceCaps.DeviceRGB)) + return RGBDeviceLighting.Keyboard; + + return RGBDeviceLighting.None; + } + } + + /// + /// Gets a flag that describes device capabilities. () + /// + public LogitechDeviceCaps DeviceCaps { get; } + + #endregion + + #region Constructors + + /// + /// Internal constructor of managed . + /// + /// The type of the . + /// The represented device model. + /// The lighting-capabilities of the device. + internal LogitechRGBDeviceInfo(RGBDeviceType deviceType, string model, LogitechDeviceCaps deviceCaps) + { + this.DeviceType = deviceType; + this.Model = model; + this.DeviceCaps = deviceCaps; + } + + #endregion + } +} diff --git a/RGB.NET.Devices.Logitech/HID/DeviceChecker.cs b/RGB.NET.Devices.Logitech/HID/DeviceChecker.cs new file mode 100644 index 0000000..8a753f1 --- /dev/null +++ b/RGB.NET.Devices.Logitech/HID/DeviceChecker.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using HidSharp; + +namespace RGB.NET.Devices.Logitech.HID +{ + //TODO DarthAffe 04.02.2017: Rewrite this once the SDK supports per-device lighting to get all the devices connected. + internal static class DeviceChecker + { + #region Constants + + //TODO DarthAffe 04.02.2017: Add IDs + private const int VENDOR_ID = 0x0; + private const int G910_ID = 0x0; + private const int G810_ID = 0x0; + + #endregion + + #region Properties & Fields + + public static string ConnectedDeviceModel + { + get + { + if (IsG910Connected) return "G910"; + if (IsG810Connected) return "G810"; + return null; + } + } + + public static bool IsDeviceConnected => IsG910Connected || IsG810Connected; + public static bool IsG910Connected { get; private set; } + public static bool IsG810Connected { get; private set; } + + #endregion + + #region Methods + + internal static void LoadDeviceList() + { + IsG910Connected = false; + IsG810Connected = false; + + HidDeviceLoader loader = new HidDeviceLoader(); + IEnumerable devices = loader.GetDevices(); + foreach (HidDevice hidDevice in devices) + if (hidDevice.VendorID == VENDOR_ID) + { + if (hidDevice.ProductID == G910_ID) + IsG910Connected = true; + else if (hidDevice.ProductID == G810_ID) + IsG810Connected = true; + } + } + + #endregion + } +} diff --git a/RGB.NET.Devices.Logitech/Helper/CultureHelper.cs b/RGB.NET.Devices.Logitech/Helper/CultureHelper.cs new file mode 100644 index 0000000..ee5e787 --- /dev/null +++ b/RGB.NET.Devices.Logitech/Helper/CultureHelper.cs @@ -0,0 +1,44 @@ +using System; +using System.Globalization; +using System.Runtime.InteropServices; + +namespace RGB.NET.Devices.Logitech +{ + /// + /// Offers some helper-methods for culture related things. + /// + public static class CultureHelper + { + #region DLLImports + + [DllImport("user32.dll")] + static extern IntPtr GetKeyboardLayout(uint thread); + + #endregion + + #region Constructors + + #endregion + + #region Methods + + /// + /// Gets the current keyboard-layout from the OS. + /// + /// The current keyboard-layout + public static CultureInfo GetCurrentCulture() + { + try + { + int keyboardLayout = GetKeyboardLayout(0).ToInt32() & 0xFFFF; + return new CultureInfo(keyboardLayout); + } + catch + { + return new CultureInfo(1033); // en-US on error. + } + } + + #endregion + } +} diff --git a/RGB.NET.Devices.Logitech/Keyboard/LogitechKeyboardRGBDevice.cs b/RGB.NET.Devices.Logitech/Keyboard/LogitechKeyboardRGBDevice.cs new file mode 100644 index 0000000..bb9e35e --- /dev/null +++ b/RGB.NET.Devices.Logitech/Keyboard/LogitechKeyboardRGBDevice.cs @@ -0,0 +1,49 @@ +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedMember.Global + +using System; +using System.IO; +using System.Reflection; + +namespace RGB.NET.Devices.Logitech +{ + /// + /// Represents a logitech keyboard. + /// + public class LogitechKeyboardRGBDevice : LogitechRGBDevice + { + #region Properties & Fields + + /// + /// Gets information about the . + /// + public LogitechKeyboardRGBDeviceInfo KeyboardDeviceInfo { get; } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The specific information provided by CUE for the keyboard + internal LogitechKeyboardRGBDevice(LogitechKeyboardRGBDeviceInfo info) + : base(info) + { + this.KeyboardDeviceInfo = info; + } + + #endregion + + #region Methods + + /// + protected override void InitializeLayout() + { + ApplyLayoutFromFile(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), + $@"Layouts\Logitech\Keyboards\{KeyboardDeviceInfo.Model.Replace(" ", string.Empty).ToUpper()}\{KeyboardDeviceInfo.PhysicalLayout.ToString().ToUpper()}.xml")); + } + + #endregion + } +} diff --git a/RGB.NET.Devices.Logitech/Keyboard/LogitechKeyboardRGBDeviceInfo.cs b/RGB.NET.Devices.Logitech/Keyboard/LogitechKeyboardRGBDeviceInfo.cs new file mode 100644 index 0000000..1a9d00b --- /dev/null +++ b/RGB.NET.Devices.Logitech/Keyboard/LogitechKeyboardRGBDeviceInfo.cs @@ -0,0 +1,63 @@ +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedMember.Global + +using System; +using System.Globalization; +using System.IO; +using System.Reflection; +using RGB.NET.Core; + +namespace RGB.NET.Devices.Logitech +{ + /// + /// Represents a generic information for a . + /// + public class LogitechKeyboardRGBDeviceInfo : LogitechRGBDeviceInfo + { + #region Properties & Fields + + /// + /// Gets the physical layout of the keyboard. + /// + public LogitechPhysicalKeyboardLayout PhysicalLayout { get; private set; } + + /// + /// Gets the logical layout of the keyboard. + /// + public LogitechLogicalKeyboardLayout LogicalLayout { get; private set; } + + #endregion + + #region Constructors + + /// + /// Internal constructor of managed . + /// + /// The type of the . + /// The represented device model. + /// The lighting-capabilities of the device. + /// The of the layout this keyboard is using + internal LogitechKeyboardRGBDeviceInfo(RGBDeviceType deviceType, string model, LogitechDeviceCaps deviceCaps, CultureInfo culture) + : base(deviceType, model, deviceCaps) + { + SetLayouts(culture.KeyboardLayoutId); + + Image = new Uri(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), + $@"Images\Logitech\Keyboards\{Model.Replace(" ", string.Empty).ToUpper()}\{LogicalLayout.ToString().ToUpper()}.png"), UriKind.Absolute); + } + + private void SetLayouts(int keyboardLayoutId) + { + switch (keyboardLayoutId) + { + //TODO DarthAffe 04.02.2017: Check all available keyboards and there layout-ids + default: + PhysicalLayout = LogitechPhysicalKeyboardLayout.US; + LogicalLayout = LogitechLogicalKeyboardLayout.NA; + break; + } + } + + #endregion + } +} diff --git a/RGB.NET.Devices.Logitech/LogitechDeviceProvider.cs b/RGB.NET.Devices.Logitech/LogitechDeviceProvider.cs new file mode 100644 index 0000000..c9c3939 --- /dev/null +++ b/RGB.NET.Devices.Logitech/LogitechDeviceProvider.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using RGB.NET.Core; +using RGB.NET.Devices.Logitech.HID; +using RGB.NET.Devices.Logitech.Native; + +namespace RGB.NET.Devices.Logitech +{ + /// + /// Represents a device provider responsible for logitech devices. + /// + public class LogitechDeviceProvider : IRGBDeviceProvider + { + #region Properties & Fields + + /// + /// Gets the singleton instance. + /// + public static LogitechDeviceProvider Instance { get; } = new LogitechDeviceProvider(); + + /// + public bool IsInitialized { get; private set; } + + /// + /// Gets the loaded architecture (x64/x86). + /// + public string LoadedArchitecture => _LogitechGSDK.LoadedArchitecture; + + /// + public IEnumerable Devices { get; private set; } + + /// + public bool HasExclusiveAccess => false; // Exclusive access isn't possible for logitech devices. + + /// + /// Gets or sets a function to get the culture for a specific device. + /// + public Func GetCulture { get; set; } = () => CultureHelper.GetCurrentCulture(); + + #endregion + + #region Constructors + + private LogitechDeviceProvider() + { } + + #endregion + + #region Methods + + /// + public bool Initialize(bool exclusiveAccessIfPossible = false, bool throwExceptions = false) + { + try + { + if (IsInitialized) + _LogitechGSDK.LogiLedRestoreLighting(); + } + catch + { /* At least we tried ... */ } + + IsInitialized = false; + + try + { + _LogitechGSDK.Reload(); + if (!_LogitechGSDK.LogiLedInit()) return false; + + _LogitechGSDK.LogiLedSaveCurrentLighting(); + _LogitechGSDK.LogiLedSetTargetDevice(LogitechDeviceCaps.PerKeyRGB); + + IList devices = new List(); + + DeviceChecker.LoadDeviceList(); + if (DeviceChecker.IsDeviceConnected) + { + LogitechRGBDevice device = new LogitechKeyboardRGBDevice(new LogitechKeyboardRGBDeviceInfo( + RGBDeviceType.Keyboard, DeviceChecker.ConnectedDeviceModel, LogitechDeviceCaps.PerKeyRGB, GetCulture())); + devices.Add(device); + + try + { + device.Initialize(); + } + catch + { + if (throwExceptions) + throw; + return false; + } + } + Devices = new ReadOnlyCollection(devices); + } + catch + { + if (throwExceptions) + throw; + else + return false; + } + + IsInitialized = true; + + return true; + } + + /// + public void ResetDevices() + { + _LogitechGSDK.LogiLedRestoreLighting(); + } + + #endregion + } +} diff --git a/RGB.NET.Devices.Logitech/Native/_LogitechGSDK.cs b/RGB.NET.Devices.Logitech/Native/_LogitechGSDK.cs new file mode 100644 index 0000000..e93e2cc --- /dev/null +++ b/RGB.NET.Devices.Logitech/Native/_LogitechGSDK.cs @@ -0,0 +1,166 @@ +// ReSharper disable UnusedMethodReturnValue.Global +// ReSharper disable UnusedMember.Global +// ReSharper disable MemberCanBePrivate.Global + +using System; +using System.IO; +using System.Runtime.InteropServices; +using RGB.NET.Core.Exceptions; + +namespace RGB.NET.Devices.Logitech.Native +{ + // ReSharper disable once InconsistentNaming + internal class _LogitechGSDK + { + #region Libary Management + + private static IntPtr _dllHandle = IntPtr.Zero; + + /// + /// Gets the loaded architecture (x64/x86). + /// + internal static string LoadedArchitecture { get; private set; } + + /// + /// Reloads the SDK. + /// + internal static void Reload() + { + UnloadLogitechGSDK(); + LoadLogitechGSDK(); + } + + private static void LoadLogitechGSDK() + { + if (_dllHandle != IntPtr.Zero) return; + + // HACK: Load library at runtime to support both, x86 and x64 with one managed dll + string dllPath = (LoadedArchitecture = Environment.Is64BitProcess ? "x64" : "x86") + "/LogitechLedEnginesWrapper.dll"; + if (!File.Exists(dllPath)) + throw new RGBDeviceException($"Can't find the Logitech-SDK at the expected location '{Path.GetFullPath(dllPath)}'"); + + _dllHandle = LoadLibrary(dllPath); + + _logiLedInitPointer = (LogiLedInitPointer)Marshal.GetDelegateForFunctionPointer(GetProcAddress(_dllHandle, "LogiLedInit"), typeof(LogiLedInitPointer)); + _logiLedShutdownPointer = (LogiLedShutdownPointer)Marshal.GetDelegateForFunctionPointer(GetProcAddress(_dllHandle, "LogiLedShutdown"), typeof(LogiLedShutdownPointer)); + _logiLedSetTargetDevicePointer = (LogiLedSetTargetDevicePointer)Marshal.GetDelegateForFunctionPointer(GetProcAddress(_dllHandle, "LogiLedSetTargetDevice"), typeof(LogiLedSetTargetDevicePointer)); + _logiLedGetSdkVersionPointer = (LogiLedGetSdkVersionPointer)Marshal.GetDelegateForFunctionPointer(GetProcAddress(_dllHandle, "LogiLedGetSdkVersion"), typeof(LogiLedGetSdkVersionPointer)); + _lgiLedSaveCurrentLightingPointer = (LogiLedSaveCurrentLightingPointer)Marshal.GetDelegateForFunctionPointer(GetProcAddress(_dllHandle, "LogiLedSaveCurrentLighting"), typeof(LogiLedSaveCurrentLightingPointer)); + _logiLedRestoreLightingPointer = (LogiLedRestoreLightingPointer)Marshal.GetDelegateForFunctionPointer(GetProcAddress(_dllHandle, "LogiLedRestoreLighting"), typeof(LogiLedRestoreLightingPointer)); + _logiLedSetLightingForKeyWithKeyNamePointer = (LogiLedSetLightingForKeyWithKeyNamePointer)Marshal.GetDelegateForFunctionPointer(GetProcAddress(_dllHandle, "LogiLedSetLightingForKeyWithKeyName"), typeof(LogiLedSetLightingForKeyWithKeyNamePointer)); + } + + private static void UnloadLogitechGSDK() + { + if (_dllHandle == IntPtr.Zero) return; + + LogiLedShutdown(); + + // ReSharper disable once EmptyEmbeddedStatement - DarthAffe 20.02.2016: We might need to reduce the internal reference counter more than once to set the library free + while (FreeLibrary(_dllHandle)) ; + _dllHandle = IntPtr.Zero; + } + + [DllImport("kernel32.dll")] + private static extern IntPtr LoadLibrary(string dllToLoad); + + [DllImport("kernel32.dll")] + private static extern bool FreeLibrary(IntPtr dllHandle); + + [DllImport("kernel32.dll")] + private static extern IntPtr GetProcAddress(IntPtr dllHandle, string name); + + #endregion + + #region SDK-METHODS + + #region Pointers + + private static LogiLedInitPointer _logiLedInitPointer; + private static LogiLedShutdownPointer _logiLedShutdownPointer; + private static LogiLedSetTargetDevicePointer _logiLedSetTargetDevicePointer; + private static LogiLedGetSdkVersionPointer _logiLedGetSdkVersionPointer; + private static LogiLedSaveCurrentLightingPointer _lgiLedSaveCurrentLightingPointer; + private static LogiLedRestoreLightingPointer _logiLedRestoreLightingPointer; + private static LogiLedSetLightingForKeyWithKeyNamePointer _logiLedSetLightingForKeyWithKeyNamePointer; + + #endregion + + #region Delegates + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate bool LogiLedInitPointer(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void LogiLedShutdownPointer(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate bool LogiLedSetTargetDevicePointer(int targetDevice); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate bool LogiLedGetSdkVersionPointer(ref int majorNum, ref int minorNum, ref int buildNum); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate bool LogiLedSaveCurrentLightingPointer(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate bool LogiLedRestoreLightingPointer(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate bool LogiLedSetLightingForKeyWithKeyNamePointer(int keyCode, int redPercentage, int greenPercentage, int bluePercentage); + + #endregion + + // ReSharper disable EventExceptionNotDocumented + + internal static bool LogiLedInit() + { + return _logiLedInitPointer(); + } + + internal static void LogiLedShutdown() + { + _logiLedShutdownPointer(); + } + + internal static bool LogiLedSetTargetDevice(LogitechDeviceCaps targetDevice) + { + return _logiLedSetTargetDevicePointer((int)targetDevice); + } + + internal static string LogiLedGetSdkVersion() + { + int major = 0; + int minor = 0; + int build = 0; + LogiLedGetSdkVersion(ref major, ref minor, ref build); + + return $"{major}.{minor}.{build}"; + } + + internal static bool LogiLedGetSdkVersion(ref int majorNum, ref int minorNum, ref int buildNum) + { + return _logiLedGetSdkVersionPointer(ref majorNum, ref minorNum, ref buildNum); + } + + internal static bool LogiLedSaveCurrentLighting() + { + return _lgiLedSaveCurrentLightingPointer(); + } + + internal static bool LogiLedRestoreLighting() + { + return _logiLedRestoreLightingPointer(); + } + + internal static bool LogiLedSetLightingForKeyWithKeyName(int keyCode, + int redPercentage, int greenPercentage, int bluePercentage) + { + return _logiLedSetLightingForKeyWithKeyNamePointer(keyCode, redPercentage, greenPercentage, bluePercentage); + } + + // ReSharper restore EventExceptionNotDocumented + + #endregion + } +} diff --git a/RGB.NET.Devices.Logitech/Properties/AssemblyInfo.cs b/RGB.NET.Devices.Logitech/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d3238fd --- /dev/null +++ b/RGB.NET.Devices.Logitech/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("RGB.NET.Devices.Logitech")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("RGB.NET.Devices.Logitech")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("e7b2f174-fcc6-4fc7-9970-3138b5f4c921")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/RGB.NET.Devices.Logitech/RGB.NET.Devices.Logitech.csproj b/RGB.NET.Devices.Logitech/RGB.NET.Devices.Logitech.csproj new file mode 100644 index 0000000..01fa985 --- /dev/null +++ b/RGB.NET.Devices.Logitech/RGB.NET.Devices.Logitech.csproj @@ -0,0 +1,88 @@ + + + + + Debug + AnyCPU + {E7B2F174-FCC6-4FC7-9970-3138B5F4C921} + Library + Properties + RGB.NET.Devices.Logitech + RGB.NET.Devices.Logitech + v4.5 + 512 + + + true + full + false + ..\bin\ + DEBUG;TRACE + prompt + 4 + ..\bin\RGB.NET.Devices.Logitech.XML + + + pdbonly + true + ..\bin\ + TRACE + prompt + 4 + ..\bin\RGB.NET.Devices.Logitech.XML + + + + ..\packages\HidSharp.1.5\lib\net35\HidSharp.dll + True + + + ..\packages\RGB.NET.Core.1.0.0\lib\net45\RGB.NET.Core.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/RGB.NET.Devices.Logitech/RGB.NET.Devices.Logitech.csproj.DotSettings b/RGB.NET.Devices.Logitech/RGB.NET.Devices.Logitech.csproj.DotSettings new file mode 100644 index 0000000..8babca3 --- /dev/null +++ b/RGB.NET.Devices.Logitech/RGB.NET.Devices.Logitech.csproj.DotSettings @@ -0,0 +1,5 @@ + + True + True + True + True \ No newline at end of file diff --git a/RGB.NET.Devices.Logitech/libs/x64/LogitechLedEnginesWrapper.dll b/RGB.NET.Devices.Logitech/libs/x64/LogitechLedEnginesWrapper.dll new file mode 100644 index 0000000..fb707e7 Binary files /dev/null and b/RGB.NET.Devices.Logitech/libs/x64/LogitechLedEnginesWrapper.dll differ diff --git a/RGB.NET.Devices.Logitech/libs/x86/LogitechLedEnginesWrapper.dll b/RGB.NET.Devices.Logitech/libs/x86/LogitechLedEnginesWrapper.dll new file mode 100644 index 0000000..a33fa40 Binary files /dev/null and b/RGB.NET.Devices.Logitech/libs/x86/LogitechLedEnginesWrapper.dll differ diff --git a/RGB.NET.Devices.Logitech/packages.config b/RGB.NET.Devices.Logitech/packages.config new file mode 100644 index 0000000..32c0f53 --- /dev/null +++ b/RGB.NET.Devices.Logitech/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/RGB.NET.Devices.Logitech/targets/RGB.NET.Devices.Logitech.targets b/RGB.NET.Devices.Logitech/targets/RGB.NET.Devices.Logitech.targets new file mode 100644 index 0000000..decacc5 --- /dev/null +++ b/RGB.NET.Devices.Logitech/targets/RGB.NET.Devices.Logitech.targets @@ -0,0 +1,150 @@ + + + + + + + + + + + False + + + False + + + + + + + + %(RecursiveDir)%(FileName)%(Extension) + Always + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bin\%(RecursiveDir)%(Filename)%(Extension) + + + + + + + + + + + + + + + + + + + + + + + $(PostBuildEventDependsOn); + CopyLogitechSDKFiles; + + + $(BuildDependsOn); + CopyLogitechSDKFiles; + + + $(CleanDependsOn); + CleanLogitechSDKFiles; + + + + + + + + CollectLogitechSDKFiles; + $(PipelineCollectFilesPhaseDependsOn); + + + diff --git a/RGB.NET.sln b/RGB.NET.sln index 3aca62e..94a8642 100644 --- a/RGB.NET.sln +++ b/RGB.NET.sln @@ -31,6 +31,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuGet", "NuGet", "{06416566 NuGet\RGB.NET.Brushes.nuspec = NuGet\RGB.NET.Brushes.nuspec NuGet\RGB.NET.Core.nuspec = NuGet\RGB.NET.Core.nuspec NuGet\RGB.NET.Devices.Corsair.nuspec = NuGet\RGB.NET.Devices.Corsair.nuspec + NuGet\RGB.NET.Devices.Logitech.nuspec = NuGet\RGB.NET.Devices.Logitech.nuspec NuGet\RGB.NET.Effects.nuspec = NuGet\RGB.NET.Effects.nuspec NuGet\RGB.NET.Groups.nuspec = NuGet\RGB.NET.Groups.nuspec NuGet\RGB.NET.Input.Corsair.nuspec = NuGet\RGB.NET.Input.Corsair.nuspec @@ -42,6 +43,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UI", "UI", "{BD7C9994-1747- EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RGB.NET.WPF", "RGB.NET.WPF\RGB.NET.WPF.csproj", "{8D6C4FE6-0046-4E98-876F-4C0B87249989}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RGB.NET.Devices.Logitech", "RGB.NET.Devices.Logitech\RGB.NET.Devices.Logitech.csproj", "{E7B2F174-FCC6-4FC7-9970-3138B5F4C921}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -80,6 +83,10 @@ Global {8D6C4FE6-0046-4E98-876F-4C0B87249989}.Debug|Any CPU.Build.0 = Debug|Any CPU {8D6C4FE6-0046-4E98-876F-4C0B87249989}.Release|Any CPU.ActiveCfg = Release|Any CPU {8D6C4FE6-0046-4E98-876F-4C0B87249989}.Release|Any CPU.Build.0 = Release|Any CPU + {E7B2F174-FCC6-4FC7-9970-3138B5F4C921}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E7B2F174-FCC6-4FC7-9970-3138B5F4C921}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E7B2F174-FCC6-4FC7-9970-3138B5F4C921}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E7B2F174-FCC6-4FC7-9970-3138B5F4C921}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -92,5 +99,6 @@ Global {F905C418-76BB-4BA6-88AB-0793BC2681D3} = {C854766D-9C1D-474B-B5B8-249DF4A9E552} {2A39F859-AAD0-4C16-94F8-78057820B376} = {FFBCAF88-6646-43EC-9F24-2D719D3779C8} {8D6C4FE6-0046-4E98-876F-4C0B87249989} = {BD7C9994-1747-4595-9C21-298E8FDCB657} + {E7B2F174-FCC6-4FC7-9970-3138B5F4C921} = {33D5E279-1C4E-4AB6-9D1E-6D18109A6C25} EndGlobalSection EndGlobal