diff --git a/RGB.NET.Devices.Logitech/Enum/LogitechDeviceType.cs b/RGB.NET.Devices.Logitech/Enum/LogitechDeviceType.cs new file mode 100644 index 0000000..6697b2d --- /dev/null +++ b/RGB.NET.Devices.Logitech/Enum/LogitechDeviceType.cs @@ -0,0 +1,11 @@ +namespace RGB.NET.Devices.Logitech +{ + public enum LogitechDeviceType + { + Keyboard = 0x0, + Mouse = 0x3, + Mousemat = 0x4, + Headset = 0x8, + Speaker = 0xE + } +} diff --git a/RGB.NET.Devices.Logitech/Generic/LogitechRGBDevice.cs b/RGB.NET.Devices.Logitech/Generic/LogitechRGBDevice.cs index edcbd29..b8f6eab 100644 --- a/RGB.NET.Devices.Logitech/Generic/LogitechRGBDevice.cs +++ b/RGB.NET.Devices.Logitech/Generic/LogitechRGBDevice.cs @@ -18,7 +18,7 @@ namespace RGB.NET.Devices.Logitech /// Gets information about the . /// public override TDeviceInfo DeviceInfo { get; } - + /// /// Gets or sets the update queue performing updates for this device. /// @@ -64,6 +64,7 @@ namespace RGB.NET.Devices.Logitech protected virtual void InitializeLayout() { if (!(DeviceInfo is LogitechRGBDeviceInfo info)) return; + string layout = info.ImageLayout; string layoutPath = info.LayoutPath; ApplyLayoutFromFile(PathHelper.GetAbsolutePath($@"Layouts\Logitech\{layoutPath}.xml"), layout, true); diff --git a/RGB.NET.Devices.Logitech/Generic/LogitechRGBDeviceInfo.cs b/RGB.NET.Devices.Logitech/Generic/LogitechRGBDeviceInfo.cs index ae13908..a476c7f 100644 --- a/RGB.NET.Devices.Logitech/Generic/LogitechRGBDeviceInfo.cs +++ b/RGB.NET.Devices.Logitech/Generic/LogitechRGBDeviceInfo.cs @@ -49,6 +49,11 @@ namespace RGB.NET.Devices.Logitech /// public LogitechDeviceCaps DeviceCaps { get; } + /// + /// Gets the amount of zones the is able to control (0 for single-color and per-key devices) + /// + public int Zones { get; } + /// /// Gets the layout used to decide which images to load. /// @@ -69,14 +74,16 @@ namespace RGB.NET.Devices.Logitech /// The type of the . /// The represented device model. /// The lighting-capabilities of the device. + /// The amount of zones the device is able to control. /// The layout used to decide which images to load. /// The path/name of the layout-file. internal LogitechRGBDeviceInfo(RGBDeviceType deviceType, string model, LogitechDeviceCaps deviceCaps, - string imageLayout, string layoutPath) + int zones, string imageLayout, string layoutPath) { this.DeviceType = deviceType; this.Model = model; this.DeviceCaps = deviceCaps; + this.Zones = zones; this.ImageLayout = imageLayout; this.LayoutPath = layoutPath; diff --git a/RGB.NET.Devices.Logitech/HID/DeviceChecker.cs b/RGB.NET.Devices.Logitech/HID/DeviceChecker.cs index dcd8043..68f7677 100644 --- a/RGB.NET.Devices.Logitech/HID/DeviceChecker.cs +++ b/RGB.NET.Devices.Logitech/HID/DeviceChecker.cs @@ -13,24 +13,29 @@ namespace RGB.NET.Devices.Logitech.HID private const int VENDOR_ID = 0x046D; //TODO DarthAffe 14.11.2017: Add devices - private static readonly List<(string model, RGBDeviceType deviceType, int id, string imageLayout, string layoutPath)> PER_KEY_DEVICES - = new List<(string model, RGBDeviceType deviceType, int id, string imageLayout, string layoutPath)> - { - ("G910", RGBDeviceType.Keyboard, 0xC32B, "DE", @"Keyboards\G910\UK"), //TODO DarthAffe 15.11.2017: Somehow detect the current layout - ("G810", RGBDeviceType.Keyboard, 0xC337, "DE", @"Keyboards\G810\UK"), - ("G610", RGBDeviceType.Keyboard, 0xC333, "DE", @"Keyboards\G610\UK"), - ("Pro", RGBDeviceType.Keyboard, 0xC339, "DE", @"Keyboards\Pro\UK"), - }; - - private static readonly List<(string model, RGBDeviceType deviceType, int id, string imageLayout, string layoutPath)> PER_DEVICE_DEVICES - = new List<(string model, RGBDeviceType deviceType, int id, string imageLayout, string layoutPath)> + private static readonly List<(string model, RGBDeviceType deviceType, int id, int zones, string imageLayout, string layoutPath)> PER_KEY_DEVICES + = new List<(string model, RGBDeviceType deviceType, int id, int zones, string imageLayout, string layoutPath)> { - ("G19", RGBDeviceType.Keyboard, 0xC228, "DE", @"Keyboards\G19\UK"), - ("G903", RGBDeviceType.Mouse, 0xC086, "default", @"Mice\G903"), - ("G403", RGBDeviceType.Mouse, 0xC083, "default", @"Mice\G403"), - ("G502", RGBDeviceType.Mouse, 0xC332, "default", @"Mice\G502"), - ("G600", RGBDeviceType.Mouse, 0xC24A, "default", @"Mice\G600"), - ("G Pro", RGBDeviceType.Mouse, 0xC085, "default", @"Mice\GPro"), + ("G910", RGBDeviceType.Keyboard, 0xC32B, 0, "DE", @"Keyboards\G910\UK"), //TODO DarthAffe 15.11.2017: Somehow detect the current layout + ("G810", RGBDeviceType.Keyboard, 0xC337, 0, "DE", @"Keyboards\G810\UK"), + ("G610", RGBDeviceType.Keyboard, 0xC333, 0, "DE", @"Keyboards\G610\UK"), + ("Pro", RGBDeviceType.Keyboard, 0xC339, 0, "DE", @"Keyboards\Pro\UK"), + }; + + private static readonly List<(string model, RGBDeviceType deviceType, int id, int zones, string imageLayout, string layoutPath)> PER_DEVICE_DEVICES + = new List<(string model, RGBDeviceType deviceType, int id, int zones, string imageLayout, string layoutPath)> + { + ("G19", RGBDeviceType.Keyboard, 0xC228, 0, "DE", @"Keyboards\G19\UK"), + ("G502", RGBDeviceType.Mouse, 0xC332, 0, "default", @"Mice\G502"), + ("G600", RGBDeviceType.Mouse, 0xC24A, 0, "default", @"Mice\G600"), + }; + + private static readonly List<(string model, RGBDeviceType deviceType, int id, int zones, string imageLayout, string layoutPath)> ZONE_DEVICES + = new List<(string model, RGBDeviceType deviceType, int id, int zones, string imageLayout, string layoutPath)> + { + ("G903", RGBDeviceType.Mouse, 0xC086, 2, "default", @"Mice\G903"), + ("G403", RGBDeviceType.Mouse, 0xC083, 2, "default", @"Mice\G403"), + ("G Pro", RGBDeviceType.Mouse, 0xC085, 1, "default", @"Mice\GPro"), }; #endregion @@ -38,10 +43,13 @@ namespace RGB.NET.Devices.Logitech.HID #region Properties & Fields public static bool IsPerKeyDeviceConnected { get; private set; } - public static (string model, RGBDeviceType deviceType, int id, string imageLayout, string layoutPath) PerKeyDeviceData { get; private set; } + public static (string model, RGBDeviceType deviceType, int id, int zones, string imageLayout, string layoutPath) PerKeyDeviceData { get; private set; } public static bool IsPerDeviceDeviceConnected { get; private set; } - public static (string model, RGBDeviceType deviceType, int id, string imageLayout, string layoutPath) PerDeviceDeviceData { get; private set; } + public static (string model, RGBDeviceType deviceType, int id, int zones, string imageLayout, string layoutPath) PerDeviceDeviceData { get; private set; } + + public static bool IsZoneDeviceConnected { get; private set; } + public static IEnumerable<(string model, RGBDeviceType deviceType, int id, int zones, string imageLayout, string layoutPath)> ZoneDeviceData { get; private set; } #endregion @@ -51,7 +59,7 @@ namespace RGB.NET.Devices.Logitech.HID { List ids = DeviceList.Local.GetHidDevices(VENDOR_ID).Select(x => x.ProductID).Distinct().ToList(); - foreach ((string model, RGBDeviceType deviceType, int id, string imageLayout, string layoutPath) deviceData in PER_KEY_DEVICES) + foreach ((string model, RGBDeviceType deviceType, int id, int zones, string imageLayout, string layoutPath) deviceData in PER_KEY_DEVICES) if (ids.Contains(deviceData.id)) { IsPerKeyDeviceConnected = true; @@ -59,13 +67,35 @@ namespace RGB.NET.Devices.Logitech.HID break; } - foreach ((string model, RGBDeviceType deviceType, int id, string imageLayout, string layoutPath) deviceData in PER_DEVICE_DEVICES) + foreach ((string model, RGBDeviceType deviceType, int id, int zones, string imageLayout, string layoutPath) deviceData in PER_DEVICE_DEVICES) if (ids.Contains(deviceData.id)) { IsPerDeviceDeviceConnected = true; PerDeviceDeviceData = deviceData; break; } + + Dictionary> connectedZoneDevices + = new Dictionary>(); + foreach ((string model, RGBDeviceType deviceType, int id, int zones, string imageLayout, string layoutPath) deviceData in ZONE_DEVICES) + { + if (ids.Contains(deviceData.id)) + { + IsZoneDeviceConnected = true; + if (!connectedZoneDevices.TryGetValue(deviceData.deviceType, out List<(string model, RGBDeviceType deviceType, int id, int zones, string imageLayout, string layoutPath)> deviceList)) + connectedZoneDevices.Add(deviceData.deviceType, deviceList = new List<(string model, RGBDeviceType deviceType, int id, int zones, string imageLayout, string layoutPath)>()); + deviceList.Add(deviceData); + } + } + List<(string model, RGBDeviceType deviceType, int id, int zones, string imageLayout, string layoutPath)> zoneDeviceData + = new List<(string model, RGBDeviceType deviceType, int id, int zones, string imageLayout, string layoutPath)>(); + foreach (KeyValuePair> connectedZoneDevice in connectedZoneDevices) + { + int maxZones = connectedZoneDevice.Value.Max(x => x.zones); + zoneDeviceData.Add(connectedZoneDevice.Value.First(x => x.zones == maxZones)); + } + + ZoneDeviceData = zoneDeviceData; } #endregion diff --git a/RGB.NET.Devices.Logitech/LogitechDeviceProvider.cs b/RGB.NET.Devices.Logitech/LogitechDeviceProvider.cs index 817665f..4f7d789 100644 --- a/RGB.NET.Devices.Logitech/LogitechDeviceProvider.cs +++ b/RGB.NET.Devices.Logitech/LogitechDeviceProvider.cs @@ -60,6 +60,9 @@ namespace RGB.NET.Devices.Logitech /// The used to trigger the updates for logitech devices. /// public DeviceUpdateTrigger UpdateTrigger { get; private set; } + + // ReSharper disable once CollectionNeverQueried.Local - for now this is just to make sure they're never collected + private readonly Dictionary _zoneUpdateQueues = new Dictionary(); private LogitechPerDeviceUpdateQueue _perDeviceUpdateQueue; private LogitechPerKeyUpdateQueue _perKeyUpdateQueue; @@ -111,12 +114,12 @@ namespace RGB.NET.Devices.Logitech try { - if (DeviceChecker.IsPerKeyDeviceConnected) + if (DeviceChecker.IsZoneDeviceConnected) { - (string model, RGBDeviceType deviceType, int _, string imageLayout, string layoutPath) = DeviceChecker.PerKeyDeviceData; + (string model, RGBDeviceType deviceType, int _, int _, string imageLayout, string layoutPath) = DeviceChecker.PerKeyDeviceData; if (loadFilter.HasFlag(deviceType)) //TODO DarthAffe 07.12.2017: Check if it's worth to try another device if the one returned doesn't match the filter { - ILogitechRGBDevice device = new LogitechPerKeyRGBDevice(new LogitechRGBDeviceInfo(deviceType, model, LogitechDeviceCaps.PerKeyRGB, imageLayout, layoutPath)); + ILogitechRGBDevice device = new LogitechPerKeyRGBDevice(new LogitechRGBDeviceInfo(deviceType, model, LogitechDeviceCaps.PerKeyRGB, 0, imageLayout, layoutPath)); device.Initialize(_perKeyUpdateQueue); devices.Add(device); } @@ -128,10 +131,10 @@ namespace RGB.NET.Devices.Logitech { if (DeviceChecker.IsPerDeviceDeviceConnected) { - (string model, RGBDeviceType deviceType, int _, string imageLayout, string layoutPath) = DeviceChecker.PerDeviceDeviceData; + (string model, RGBDeviceType deviceType, int _, int _, string imageLayout, string layoutPath) = DeviceChecker.PerDeviceDeviceData; if (loadFilter.HasFlag(deviceType)) //TODO DarthAffe 07.12.2017: Check if it's worth to try another device if the one returned doesn't match the filter { - ILogitechRGBDevice device = new LogitechPerDeviceRGBDevice(new LogitechRGBDeviceInfo(deviceType, model, LogitechDeviceCaps.DeviceRGB, imageLayout, layoutPath)); + ILogitechRGBDevice device = new LogitechPerDeviceRGBDevice(new LogitechRGBDeviceInfo(deviceType, model, LogitechDeviceCaps.DeviceRGB, 0, imageLayout, layoutPath)); device.Initialize(_perDeviceUpdateQueue); devices.Add(device); } @@ -139,6 +142,27 @@ namespace RGB.NET.Devices.Logitech } catch { if (throwExceptions) throw; } + try + { + if (DeviceChecker.IsZoneDeviceConnected) + { + foreach ((string model, RGBDeviceType deviceType, int _, int zones, string imageLayout, string layoutPath) in DeviceChecker.ZoneDeviceData) + try + { + if (loadFilter.HasFlag(deviceType)) + { + LogitechZoneUpdateQueue updateQueue = new LogitechZoneUpdateQueue(UpdateTrigger, deviceType); + ILogitechRGBDevice device = new LogitechZoneRGBDevice(new LogitechRGBDeviceInfo(deviceType, model, LogitechDeviceCaps.DeviceRGB, zones, imageLayout, layoutPath)); + device.Initialize(updateQueue); + devices.Add(device); + _zoneUpdateQueues.Add(deviceType, updateQueue); + } + } + catch { if (throwExceptions) throw; } + } + } + catch { if (throwExceptions) throw; } + UpdateTrigger?.Start(); Devices = new ReadOnlyCollection(devices); diff --git a/RGB.NET.Devices.Logitech/Native/_LogitechGSDK.cs b/RGB.NET.Devices.Logitech/Native/_LogitechGSDK.cs index c924515..39d0968 100644 --- a/RGB.NET.Devices.Logitech/Native/_LogitechGSDK.cs +++ b/RGB.NET.Devices.Logitech/Native/_LogitechGSDK.cs @@ -52,6 +52,7 @@ namespace RGB.NET.Devices.Logitech.Native _logiLedSetLightingPointer = (LogiLedSetLightingPointer)Marshal.GetDelegateForFunctionPointer(GetProcAddress(_dllHandle, "LogiLedSetLighting"), typeof(LogiLedSetLightingPointer)); _logiLedSetLightingForKeyWithKeyNamePointer = (LogiLedSetLightingForKeyWithKeyNamePointer)Marshal.GetDelegateForFunctionPointer(GetProcAddress(_dllHandle, "LogiLedSetLightingForKeyWithKeyName"), typeof(LogiLedSetLightingForKeyWithKeyNamePointer)); _logiLedSetLightingFromBitmapPointer = (LogiLedSetLightingFromBitmapPointer)Marshal.GetDelegateForFunctionPointer(GetProcAddress(_dllHandle, "LogiLedSetLightingFromBitmap"), typeof(LogiLedSetLightingFromBitmapPointer)); + _logiLedSetLightingForTargetZonePointer = (LogiLedSetLightingForTargetZonePointer)Marshal.GetDelegateForFunctionPointer(GetProcAddress(_dllHandle, "LogiLedSetLightingForTargetZone"), typeof(LogiLedSetLightingForTargetZonePointer)); } private static void UnloadLogitechGSDK() @@ -89,6 +90,7 @@ namespace RGB.NET.Devices.Logitech.Native private static LogiLedSetLightingPointer _logiLedSetLightingPointer; private static LogiLedSetLightingForKeyWithKeyNamePointer _logiLedSetLightingForKeyWithKeyNamePointer; private static LogiLedSetLightingFromBitmapPointer _logiLedSetLightingFromBitmapPointer; + private static LogiLedSetLightingForTargetZonePointer _logiLedSetLightingForTargetZonePointer; #endregion @@ -121,6 +123,9 @@ namespace RGB.NET.Devices.Logitech.Native [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate bool LogiLedSetLightingFromBitmapPointer(byte[] bitmap); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate bool LogiLedSetLightingForTargetZonePointer(LogitechDeviceType deviceType, int zone, int redPercentage, int greenPercentage, int bluePercentage); + #endregion // ReSharper disable EventExceptionNotDocumented @@ -149,11 +154,14 @@ namespace RGB.NET.Devices.Logitech.Native internal static bool LogiLedSetLighting(int redPercentage, int greenPercentage, int bluePercentage) => _logiLedSetLightingPointer(redPercentage, greenPercentage, bluePercentage); - internal static bool LogiLedSetLightingForKeyWithKeyName(int keyCode, - int redPercentage, int greenPercentage, int bluePercentage) => _logiLedSetLightingForKeyWithKeyNamePointer(keyCode, redPercentage, greenPercentage, bluePercentage); + internal static bool LogiLedSetLightingForKeyWithKeyName(int keyCode, int redPercentage, int greenPercentage, int bluePercentage) + => _logiLedSetLightingForKeyWithKeyNamePointer(keyCode, redPercentage, greenPercentage, bluePercentage); internal static bool LogiLedSetLightingFromBitmap(byte[] bitmap) => _logiLedSetLightingFromBitmapPointer(bitmap); + internal static bool LogiLedSetLightingForTargetZone(LogitechDeviceType deviceType, int zone, int redPercentage, int greenPercentage, int bluePercentage) + => _logiLedSetLightingForTargetZonePointer(deviceType, zone, redPercentage, greenPercentage, bluePercentage); + // ReSharper restore EventExceptionNotDocumented #endregion diff --git a/RGB.NET.Devices.Logitech/RGB.NET.Devices.Logitech.csproj.DotSettings b/RGB.NET.Devices.Logitech/RGB.NET.Devices.Logitech.csproj.DotSettings index 742b199..e94069e 100644 --- a/RGB.NET.Devices.Logitech/RGB.NET.Devices.Logitech.csproj.DotSettings +++ b/RGB.NET.Devices.Logitech/RGB.NET.Devices.Logitech.csproj.DotSettings @@ -5,4 +5,5 @@ True True True - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/RGB.NET.Devices.Logitech/Zone/LogitechZoneRGBDevice.cs b/RGB.NET.Devices.Logitech/Zone/LogitechZoneRGBDevice.cs new file mode 100644 index 0000000..c7263ae --- /dev/null +++ b/RGB.NET.Devices.Logitech/Zone/LogitechZoneRGBDevice.cs @@ -0,0 +1,66 @@ +using System.Collections.Generic; +using System.Linq; +using RGB.NET.Core; + +namespace RGB.NET.Devices.Logitech +{ + /// + /// + /// Represents a logitech zone-lightable device. + /// + public class LogitechZoneRGBDevice : LogitechRGBDevice + { + #region Constants + + private static readonly Dictionary BASE_LED_MAPPING = new Dictionary + { + {RGBDeviceType.Keyboard, LedId.Keyboard_Programmable1}, + {RGBDeviceType.Mouse, LedId.Mouse1}, + {RGBDeviceType.Headset, LedId.Headset1}, + {RGBDeviceType.Mousepad, LedId.Mousepad1}, + {RGBDeviceType.Speaker, LedId.Speaker1} + }; + + #endregion + + #region Properties & Fields + + private LedId _baseLedId; + + #endregion + + #region Constructors + + /// + /// + /// Initializes a new instance of the class. + /// + /// The specific information provided by logitech for the zone-lightable device + internal LogitechZoneRGBDevice(LogitechRGBDeviceInfo info) + : base(info) + { + _baseLedId = BASE_LED_MAPPING.TryGetValue(info.DeviceType, out LedId id) ? id : LedId.Custom1; + } + + #endregion + + #region Methods + + /// + protected override void InitializeLayout() + { + for (int i = 0; i < DeviceInfo.Zones; i++) + InitializeLed(_baseLedId + i, new Rectangle(i * 10, 0, 10, 10)); + + base.InitializeLayout(); + } + + /// + protected override object CreateLedCustomData(LedId ledId) => (int)(ledId - _baseLedId); + + /// + protected override void UpdateLeds(IEnumerable ledsToUpdate) => UpdateQueue.SetData(ledsToUpdate.Where(x => x.Color.A > 0)); + + #endregion + } +} diff --git a/RGB.NET.Devices.Logitech/Zone/LogitechZoneUpdateQueue.cs b/RGB.NET.Devices.Logitech/Zone/LogitechZoneUpdateQueue.cs new file mode 100644 index 0000000..ab0062c --- /dev/null +++ b/RGB.NET.Devices.Logitech/Zone/LogitechZoneUpdateQueue.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using RGB.NET.Core; +using RGB.NET.Devices.Logitech.Native; + +namespace RGB.NET.Devices.Logitech +{ + /// + /// Represents the update-queue performing updates for logitech zone devices. + /// + public class LogitechZoneUpdateQueue : UpdateQueue + { + #region Constants + + private static readonly Dictionary DEVICE_TYPE_MAPPING = new Dictionary + { + {RGBDeviceType.Keyboard, LogitechDeviceType.Keyboard}, + {RGBDeviceType.Mouse, LogitechDeviceType.Mouse}, + {RGBDeviceType.Headset, LogitechDeviceType.Headset}, + {RGBDeviceType.Mousepad, LogitechDeviceType.Mousemat}, + {RGBDeviceType.Speaker, LogitechDeviceType.Speaker} + }; + + #endregion + + #region Properties & Fields + + private readonly LogitechDeviceType _deviceType; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The update trigger used by this queue. + /// The tpye of the device this queue is updating. + public LogitechZoneUpdateQueue(IDeviceUpdateTrigger updateTrigger, RGBDeviceType deviceType) + : base(updateTrigger) + { + if (!DEVICE_TYPE_MAPPING.TryGetValue(deviceType, out _deviceType)) + throw new ArgumentException($"Invalid type '{deviceType.ToString()}'", nameof(deviceType)); + } + + #endregion + + #region Methods + + /// + protected override void Update(Dictionary dataSet) + { + _LogitechGSDK.LogiLedSetTargetDevice(LogitechDeviceCaps.All); + + foreach (KeyValuePair data in dataSet) + { + int zone = (int)data.Key; + _LogitechGSDK.LogiLedSetLightingForTargetZone(_deviceType, zone, + (int)Math.Round(data.Value.RPercent * 100), + (int)Math.Round(data.Value.GPercent * 100), + (int)Math.Round(data.Value.BPercent * 100)); + } + } + + #endregion + } +}