Publish LumaOps source

This commit is contained in:
LumaOps release export
2026-09-03 01:18:36 +02:00
commit 7f1c0e5f71
2363 changed files with 501543 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,224 @@
/*---------------------------------------------------------*\
| LogitechG203LController.cpp |
| |
| Driver for Logitech G203L |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "LogitechG203LController.h"
#include "StringUtils.h"
#define PACKET_SIZE 20
LogitechG203LController::LogitechG203LController(hid_device* dev_handle, const char* path, std::string dev_name)
{
dev = dev_handle;
location = path;
name = dev_name;
// enable software control
unsigned char usb_buf[PACKET_SIZE];
memset(usb_buf, 0x00, PACKET_SIZE);
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x0E;
usb_buf[0x03] = 0x50;
usb_buf[0x04] = 0x01;
usb_buf[0x05] = 0x03;
usb_buf[0x06] = 0x07;
SendPacket(usb_buf);
}
LogitechG203LController::~LogitechG203LController()
{
if(dev != nullptr)
{
hid_close(dev);
}
}
std::string LogitechG203LController::GetDeviceLocation()
{
return("HID: " + location);
}
std::string LogitechG203LController::GetNameString()
{
return(name);
}
std::string LogitechG203LController::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev, serial_string, 128);
if(ret != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
void LogitechG203LController::SendApply()
{
unsigned char usb_buf[PACKET_SIZE];
memset(usb_buf, 0x00, PACKET_SIZE);
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x12;
usb_buf[0x03] = 0x70;
SendPacket(usb_buf);
}
void LogitechG203LController::SetSingleLED(int led, unsigned char red, unsigned char green, unsigned char blue)
{
unsigned char usb_buf[PACKET_SIZE];
memset(usb_buf, 0x00, PACKET_SIZE);
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x12;
usb_buf[0x03] = 0x10;
usb_buf[0x04] = (unsigned char)led;
usb_buf[0x05] = red;
usb_buf[0x06] = green;
usb_buf[0x07] = blue;
usb_buf[0x08] = 0xFF;
SendPacket(usb_buf);
SendApply();
}
void LogitechG203LController::SetMode(
int mode,
int speed,
unsigned char bright,
unsigned char dir,
unsigned char red,
unsigned char green,
unsigned char blue)
{
unsigned char usb_buf[PACKET_SIZE];
unsigned char brightness = bright * 5;
if(brightness == 0)
{
brightness = 1;
}
memset(usb_buf, 0x00, PACKET_SIZE);
//Header
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x0E;
usb_buf[0x03] = 0x10;
//Common Data
usb_buf[0x04] = 0x00;
usb_buf[0x05] = (unsigned char)mode;
usb_buf[0x06] = red;
usb_buf[0x07] = green;
usb_buf[0x08] = blue;
//mode specific Data and position
if(mode == LOGITECH_G203L_MODE_STATIC) usb_buf[0x09] = 0x02;
if(mode == LOGITECH_G203L_MODE_CYCLE)
{
usb_buf[0x0B] = (unsigned char)((speed>>8) & 0x000000FF);
usb_buf[0x0C] = (unsigned char)(speed & 0x000000FF);
usb_buf[0x0D] = brightness;
}
if(mode == LOGITECH_G203L_MODE_BREATHING)
{
usb_buf[0x09] = (unsigned char)((speed>>8) & 0x000000FF);
usb_buf[0x0A] = (unsigned char)(speed & 0x000000FF);
usb_buf[0x0C] = brightness;
}
if(mode == LOGITECH_G203L_MODE_WAVE)
{
usb_buf[0x0C] = (unsigned char)(speed & 0x000000FF);
usb_buf[0x0D] = dir ? 0x01 : 0x06; //0x01: Left->Right 0x06: Right->Left
usb_buf[0x0E] = brightness;
usb_buf[0x0F] = (unsigned char)((speed>>8) & 0x000000FF);
}
if(mode == LOGITECH_G203L_MODE_COLORMIXING)
{
usb_buf[0x0C] = (unsigned char)(speed & 0x000000FF);
usb_buf[0x0D] = (unsigned char)((speed>>8) & 0x000000FF);
usb_buf[0x0E] = brightness;
}
//END BYTE
usb_buf[0x10] = 0x01;
SendPacket(usb_buf);
}
void LogitechG203LController::SetDevice(std::vector<RGBColor> colors)
{
unsigned char usb_buf[PACKET_SIZE];
memset(usb_buf, 0x00, PACKET_SIZE);
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x12;
usb_buf[0x03] = 0x10;
usb_buf[0x04] = 0x01;
usb_buf[0x05] = RGBGetRValue(colors[0]);
usb_buf[0x06] = RGBGetGValue(colors[0]);
usb_buf[0x07] = RGBGetBValue(colors[0]);
usb_buf[0x08] = 0x02;
usb_buf[0x09] = RGBGetRValue(colors[1]);
usb_buf[0x0A] = RGBGetGValue(colors[1]);
usb_buf[0x0B] = RGBGetBValue(colors[1]);
usb_buf[0x0C] = 0x03;
usb_buf[0x0D] = RGBGetRValue(colors[2]);
usb_buf[0x0E] = RGBGetGValue(colors[2]);
usb_buf[0x0F] = RGBGetBValue(colors[2]);
usb_buf[0x10] = 0xFF;
SendPacket(usb_buf);
SendApply();
}
void LogitechG203LController::SendPacket(unsigned char* buffer)
{
if(dev != nullptr)
{
if(hid_write(dev, buffer, PACKET_SIZE) == -1)
{
hid_close(dev);
dev = hid_open_path(location.c_str());
return;
}
}
if(dev != nullptr)
{
if(hid_read_timeout(dev, buffer, PACKET_SIZE, 10) <= 0)
{
hid_close(dev);
dev = hid_open_path(location.c_str());
return;
}
}
}
@@ -0,0 +1,48 @@
/*---------------------------------------------------------*\
| LogitechG203LController.h |
| |
| Driver for Logitech G203L |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "RGBController.h"
enum
{
LOGITECH_G203L_MODE_DIRECT = 0x07,
LOGITECH_G203L_MODE_OFF = 0x00,
LOGITECH_G203L_MODE_STATIC = 0x01,
LOGITECH_G203L_MODE_CYCLE = 0x02,
LOGITECH_G203L_MODE_WAVE = 0x03,
LOGITECH_G203L_MODE_BREATHING = 0x04,
LOGITECH_G203L_MODE_COLORMIXING = 0x06,
};
class LogitechG203LController
{
public:
LogitechG203LController(hid_device* dev_handle, const char* path, std::string dev_name);
~LogitechG203LController();
std::string GetDeviceLocation();
std::string GetNameString();
std::string GetSerialString();
void SetSingleLED(int led, unsigned char red, unsigned char green, unsigned char blue);
void SetMode(int mode, int speed, unsigned char brightness, unsigned char dir, unsigned char red, unsigned char green, unsigned char blue);
void SetDevice(std::vector<RGBColor> colors);
private:
hid_device* dev;
std::string location;
std::string name;
void SendApply();
void SendPacket(unsigned char* buffer);
};
@@ -0,0 +1,202 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG203L.cpp |
| |
| Driver for Logitech G203L |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_LogitechG203L.h"
/**------------------------------------------------------------------*\
@name Logitech G203L
@category Mouse
@type USB
@save :robot:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectLogitechMouseG203L
@comment
\*-------------------------------------------------------------------*/
RGBController_LogitechG203L::RGBController_LogitechG203L(LogitechG203LController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetNameString();
vendor = "Logitech";
type = DEVICE_TYPE_MOUSE;
description = "Logitech Mouse Device";
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
mode Direct;
Direct.name = "Direct";
Direct.value = LOGITECH_G203L_MODE_DIRECT;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
mode Off;
Off.name = "Off";
Off.value = LOGITECH_G203L_MODE_OFF;
Off.flags = 0;
Off.color_mode = MODE_COLORS_NONE;
modes.push_back(Off);
mode Static;
Static.name = "Static";
Static.value = LOGITECH_G203L_MODE_STATIC;
Static.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Static.color_mode = MODE_COLORS_MODE_SPECIFIC;
Static.colors.resize(1);
modes.push_back(Static);
mode Cycle;
Cycle.name = "Cycle";
Cycle.value = LOGITECH_G203L_MODE_CYCLE;
Cycle.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Cycle.color_mode = MODE_COLORS_NONE;
Cycle.speed_min = 0x4E20;
Cycle.speed_max = 0x03E8;
Cycle.brightness = 20;
Cycle.brightness_min = 0;
Cycle.brightness_max = 20;
modes.push_back(Cycle);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = LOGITECH_G203L_MODE_BREATHING;
Breathing.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Breathing.color_mode = MODE_COLORS_MODE_SPECIFIC;
Breathing.speed_min = 0x4E20;
Breathing.speed_max = 0x03E8;
Breathing.brightness = 20;
Breathing.brightness_min = 0;
Breathing.brightness_max = 20;
Breathing.colors.resize(1);
modes.push_back(Breathing);
mode Wave;
Wave.name = "Wave";
Wave.value = LOGITECH_G203L_MODE_WAVE;
Wave.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_HAS_DIRECTION_LR;
Wave.color_mode = MODE_COLORS_NONE;
Wave.speed_min = 0x4E20;
Wave.speed_max = 0x03E8;
Wave.brightness = 20;
Wave.brightness_min = 0;
Wave.brightness_max = 20;
modes.push_back(Wave);
mode Colormixing;
Colormixing.name = "Colormixing";
Colormixing.value = LOGITECH_G203L_MODE_COLORMIXING;
Colormixing.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Colormixing.color_mode = MODE_COLORS_NONE;
Colormixing.speed_min = 0x4E20;
Colormixing.speed_max = 0x03E8;
Colormixing.brightness = 20;
Colormixing.brightness_min = 0;
Colormixing.brightness_max = 20;
modes.push_back(Colormixing);
SetupZones();
}
RGBController_LogitechG203L::~RGBController_LogitechG203L()
{
delete controller;
}
void RGBController_LogitechG203L::SetupZones()
{
zone g203L_zone;
g203L_zone.name = "Mouse Zone";
g203L_zone.type = ZONE_TYPE_LINEAR;
g203L_zone.leds_min = 3;
g203L_zone.leds_max = 3;
g203L_zone.leds_count = 3;
g203L_zone.matrix_map = NULL;
zones.push_back(g203L_zone);
led g203L_led_l;
g203L_led_l.name = "Mouse Left";
g203L_led_l.value = 1;
leds.push_back(g203L_led_l);
led g203L_led_c;
g203L_led_c.name = "Mouse Center";
g203L_led_c.value = 2;
leds.push_back(g203L_led_c);
led g203L_led_r;
g203L_led_r.name = "Mouse Right";
g203L_led_r.value = 3;
leds.push_back(g203L_led_r);
SetupColors();
}
void RGBController_LogitechG203L::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_LogitechG203L::DeviceUpdateLEDs()
{
controller->SetDevice(colors);
controller->SetDevice(colors); //dirty workaround for color lag
}
void RGBController_LogitechG203L::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_LogitechG203L::UpdateSingleLED(int led)
{
unsigned char red = RGBGetRValue(colors[led]);
unsigned char grn = RGBGetGValue(colors[led]);
unsigned char blu = RGBGetBValue(colors[led]);
controller->SetSingleLED(leds[led].value, red, grn, blu);
controller->SetSingleLED(leds[led].value, red, grn, blu); //dirty workaround for color lag
}
void RGBController_LogitechG203L::DeviceUpdateMode()
{
unsigned char red = 0;
unsigned char grn = 0;
unsigned char blu = 0;
unsigned char dir = 0;
if(modes[active_mode].color_mode & MODE_COLORS_MODE_SPECIFIC)
{
red = RGBGetRValue(modes[active_mode].colors[0]);
grn = RGBGetGValue(modes[active_mode].colors[0]);
blu = RGBGetBValue(modes[active_mode].colors[0]);
}
if(modes[active_mode].flags & MODE_FLAG_HAS_DIRECTION_LR)
{
dir = (unsigned char)modes[active_mode].direction;
}
if(modes[active_mode].flags & MODE_FLAG_HAS_BRIGHTNESS)
{
//dunno where brightness is
}
if(modes[active_mode].value == LOGITECH_G203L_MODE_DIRECT)
{
controller->SetDevice(colors);
}
else
{
controller->SetMode(modes[active_mode].value, modes[active_mode].speed, modes[active_mode].brightness, dir, red, grn, blu);
}
}
@@ -0,0 +1,33 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG203L.h |
| |
| Driver for Logitech G203L |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechG203LController.h"
class RGBController_LogitechG203L : public RGBController
{
public:
RGBController_LogitechG203L(LogitechG203LController* controller_ptr);
~RGBController_LogitechG203L();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
LogitechG203LController* controller;
};
@@ -0,0 +1,160 @@
/*---------------------------------------------------------*\
| LogitechG203LController.cpp |
| |
| Driver for Logitech G203L |
| |
| Eric Samuelson (edbgon) 06 Oct 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "LogitechG213Controller.h"
#include "StringUtils.h"
LogitechG213Controller::LogitechG213Controller(hid_device* dev_handle, const char* path, std::string dev_name)
{
dev = dev_handle;
location = path;
name = dev_name;
}
LogitechG213Controller::~LogitechG213Controller()
{
hid_close(dev);
}
std::string LogitechG213Controller::GetDeviceLocation()
{
return("HID: " + location);
}
std::string LogitechG213Controller::GetNameString()
{
return(name);
}
std::string LogitechG213Controller::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev, serial_string, 128);
if(ret != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
void LogitechG213Controller::SetDirect
(
unsigned char zone,
unsigned char r,
unsigned char g,
unsigned char b
)
{
unsigned char usb_buf[20];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Lighting Control packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x0C;
usb_buf[0x03] = 0x3A;
usb_buf[0x04] = zone;
usb_buf[0x05] = 0x01;
usb_buf[0x06] = r;
usb_buf[0x07] = g;
usb_buf[0x08] = b;
usb_buf[0x09] = 0x02;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev, usb_buf, 20);
hid_read(dev, usb_buf, 20);
}
void LogitechG213Controller::SetMode
(
unsigned char mode,
unsigned short speed,
unsigned char direction,
unsigned char red,
unsigned char green,
unsigned char blue
)
{
SendMode(LOGITECH_G213_ZONE_MODE_KEYBOARD, mode, speed, direction, red, green, blue);
}
void LogitechG213Controller::SendMode
(
unsigned char zone,
unsigned char mode,
unsigned short speed,
unsigned char direction,
unsigned char red,
unsigned char green,
unsigned char blue
)
{
unsigned char usb_buf[20];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Lighting Control packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x0C;
usb_buf[0x03] = 0x3C;
usb_buf[0x04] = zone;
usb_buf[0x05] = mode;
usb_buf[0x06] = red;
usb_buf[0x07] = green;
usb_buf[0x08] = blue;
speed = 100 * speed;
if(mode == LOGITECH_G213_MODE_BREATHING)
{
usb_buf[0x09] = speed >> 8;
usb_buf[0x0A] = speed & 0xFF;
usb_buf[0x0C] = 0x64;
}
else if(mode == LOGITECH_G213_MODE_CYCLE)
{
usb_buf[0x0B] = speed >> 8;
usb_buf[0x0C] = speed & 0xFF;
usb_buf[0x0D] = 0x64;
}
else if(mode == LOGITECH_G213_MODE_WAVE)
{
usb_buf[0x0B] = speed >> 8;
usb_buf[0x0C] = speed & 0xFF;
usb_buf[0x0D] = direction & 0xFF;
usb_buf[0x0F] = speed >> 8;
}
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev, usb_buf, 20);
hid_read(dev, usb_buf, 20);
}
@@ -0,0 +1,95 @@
/*---------------------------------------------------------*\
| LogitechG203LController.h |
| |
| Driver for Logitech G203L |
| |
| Eric Samuelson (edbgon) 06 Oct 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController.h"
#include <string>
#include <hidapi.h>
#pragma once
enum
{
LOGITECH_G213_ZONE_MODE_KEYBOARD = 0x00
};
enum
{
LOGITECH_G213_MODE_OFF = 0x00,
LOGITECH_G213_MODE_STATIC = 0x01,
LOGITECH_G213_MODE_BREATHING = 0x02,
LOGITECH_G213_MODE_CYCLE = 0x03,
LOGITECH_G213_MODE_WAVE = 0x04,
};
enum
{
LOGITECH_G213_WAVE_MODE_LEFT = 0x06,
LOGITECH_G213_WAVE_MODE_RIGHT = 0x01,
LOGITECH_G213_WAVE_MODE_CENTER_EDGE = 0x03,
LOGITECH_G213_WAVE_MODE_EDGE_CENTER = 0x08,
};
/*---------------------------------------------------------------------------------------------*\
| Speed is 1000 for fast and 20000 for slow. |
| Values are multiplied by 100 later to give lots of GUI steps. |
\*---------------------------------------------------------------------------------------------*/
enum
{
LOGITECH_G213_SPEED_SLOWEST = 0xC8, /* Slowest speed */
LOGITECH_G213_SPEED_NORMAL = 0x32, /* Normal speed */
LOGITECH_G213_SPEED_FASTEST = 0x0A, /* Fastest speed */
};
class LogitechG213Controller
{
public:
LogitechG213Controller(hid_device* dev_handle, const char* path, std::string dev_name);
~LogitechG213Controller();
std::string GetDeviceLocation();
std::string GetNameString();
std::string GetSerialString();
void SetDirect
(
unsigned char zone,
unsigned char r,
unsigned char g,
unsigned char b
);
void SetMode
(
unsigned char mode,
unsigned short speed,
unsigned char direction,
unsigned char red,
unsigned char green,
unsigned char blue
);
private:
hid_device* dev;
std::string location;
std::string name;
void SendMode
(
unsigned char zone,
unsigned char mode,
unsigned short speed,
unsigned char direction,
unsigned char red,
unsigned char green,
unsigned char blue
);
};
@@ -0,0 +1,209 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG213.cpp |
| |
| RGBController for Logitech G203L |
| |
| Eric Samuelson (edbgon) 06 Oct 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_LogitechG213.h"
static const char* led_names[] =
{
"Left Area",
"Middle Area",
"Right Area",
"Arrow and Homekeys",
"Numpad",
};
static const unsigned char led_values[] =
{
0x01,
0x02,
0x03,
0x04,
0x05,
};
#define LOGITECH_G213_ZONES (sizeof(led_values) / sizeof(led_values[ 0 ]))
/**------------------------------------------------------------------*\
@name Logitech G213
@category Keyboard
@type USB
@save :robot:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectLogitechKeyboardG213
@comment
\*-------------------------------------------------------------------*/
RGBController_LogitechG213::RGBController_LogitechG213(LogitechG213Controller* controller_ptr)
{
controller = controller_ptr;
name = controller->GetNameString();
vendor = "Logitech";
type = DEVICE_TYPE_KEYBOARD;
description = "Logitech G213 Keyboard Device";
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
mode Direct;
Direct.name = "Direct";
Direct.value = 0xFFFF;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
mode Off;
Off.name = "Off";
Off.value = LOGITECH_G213_MODE_OFF;
Off.flags = 0;
Off.color_mode = MODE_COLORS_NONE;
modes.push_back(Off);
mode Cycle;
Cycle.name = "Cycle";
Cycle.value = LOGITECH_G213_MODE_CYCLE;
Cycle.flags = MODE_FLAG_HAS_SPEED;
Cycle.color_mode = MODE_COLORS_NONE;
Cycle.speed_min = LOGITECH_G213_SPEED_SLOWEST;
Cycle.speed_max = LOGITECH_G213_SPEED_FASTEST;
Cycle.speed = LOGITECH_G213_SPEED_NORMAL;
modes.push_back(Cycle);
mode Wave;
Wave.name = "Wave";
Wave.value = LOGITECH_G213_MODE_WAVE;
Wave.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_DIRECTION_LR | MODE_FLAG_HAS_DIRECTION_UD;
Wave.color_mode = MODE_COLORS_NONE;
Wave.speed_min = LOGITECH_G213_SPEED_SLOWEST;
Wave.speed_max = LOGITECH_G213_SPEED_FASTEST;
Wave.speed = LOGITECH_G213_SPEED_NORMAL;
Wave.direction = MODE_DIRECTION_LEFT;
modes.push_back(Wave);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = LOGITECH_G213_MODE_BREATHING;
Breathing.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED;
Breathing.colors_min = 1;
Breathing.colors_max = 1;
Breathing.color_mode = MODE_COLORS_MODE_SPECIFIC;
Breathing.colors.resize(1);
Breathing.speed_min = LOGITECH_G213_SPEED_SLOWEST;
Breathing.speed_max = LOGITECH_G213_SPEED_FASTEST;
Breathing.speed = LOGITECH_G213_SPEED_NORMAL;
modes.push_back(Breathing);
SetupZones();
}
RGBController_LogitechG213::~RGBController_LogitechG213()
{
delete controller;
}
void RGBController_LogitechG213::SetupZones()
{
/*---------------------------------------------------------*\
| Set up zones |
\*---------------------------------------------------------*/
zone new_zone;
new_zone.name = "Keyboard";
new_zone.type = ZONE_TYPE_LINEAR;
new_zone.leds_min = 5;
new_zone.leds_max = 5;
new_zone.leds_count = 5;
new_zone.matrix_map = NULL;
zones.push_back(new_zone);
for(unsigned int led_idx = 0; led_idx < LOGITECH_G213_ZONES; led_idx++)
{
led new_led;
new_led.name = led_names[led_idx];
new_led.value = led_values[led_idx];
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_LogitechG213::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_LogitechG213::DeviceUpdateLEDs()
{
for(std::size_t led_idx = 0; led_idx < leds.size(); led_idx++)
{
controller->SetDirect((unsigned char)leds[led_idx].value, RGBGetRValue(colors[led_idx]), RGBGetGValue(colors[led_idx]), RGBGetBValue(colors[led_idx]));
}
}
void RGBController_LogitechG213::UpdateZoneLEDs(int zone)
{
controller->SetDirect((unsigned char) zone, RGBGetRValue(zones[zone].colors[0]), RGBGetGValue(zones[zone].colors[0]), RGBGetBValue(zones[zone].colors[0]));
}
void RGBController_LogitechG213::UpdateSingleLED(int led)
{
controller->SetDirect(leds[led].value, RGBGetRValue(colors[led]), RGBGetGValue(colors[led]), RGBGetBValue(colors[led]));
}
void RGBController_LogitechG213::DeviceUpdateMode()
{
/*---------------------------------------------------------*\
| Direct mode does not send a mode packet |
| Call UpdateLEDs to send direct packet |
\*---------------------------------------------------------*/
if(active_mode == 0xFFFF)
{
UpdateLEDs();
return;
}
unsigned char red = 0;
unsigned char grn = 0;
unsigned char blu = 0;
unsigned char direction = 0;
if(modes[active_mode].color_mode == MODE_COLORS_MODE_SPECIFIC)
{
red = RGBGetRValue(modes[active_mode].colors[0]);
grn = RGBGetGValue(modes[active_mode].colors[0]);
blu = RGBGetBValue(modes[active_mode].colors[0]);
}
switch (modes[active_mode].direction)
{
case MODE_DIRECTION_LEFT:
// Right to left
direction = LOGITECH_G213_WAVE_MODE_LEFT;
break;
case MODE_DIRECTION_RIGHT:
// Left to right
direction = LOGITECH_G213_WAVE_MODE_RIGHT;
break;
case MODE_DIRECTION_UP:
// Edge to center
direction = LOGITECH_G213_WAVE_MODE_EDGE_CENTER;
break;
case MODE_DIRECTION_DOWN:
// Center to edge
direction = LOGITECH_G213_WAVE_MODE_CENTER_EDGE;
break;
}
controller->SetMode(modes[active_mode].value, modes[active_mode].speed, direction, red, grn, blu);
}
@@ -0,0 +1,35 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG213.h |
| |
| RGBController for Logitech G203L |
| |
| Eric Samuelson (edbgon) 06 Oct 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechG213Controller.h"
class RGBController_LogitechG213 : public RGBController
{
public:
RGBController_LogitechG213(LogitechG213Controller* controller_ptr);
~RGBController_LogitechG213();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
LogitechG213Controller* controller;
};
@@ -0,0 +1,154 @@
/*---------------------------------------------------------*\
| LogitechG560Controller.cpp |
| |
| Driver for Logitech G560 |
| |
| Cheerpipe 28 Oct 2020 |
| based on TheRogueZeta 31 Aug 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include <stdio.h>
#include <stdlib.h>
#include "LogitechG560Controller.h"
using namespace std::chrono_literals;
LogitechG560Controller::LogitechG560Controller(hid_device* dev_handle, const char* path, std::string dev_name)
{
dev = dev_handle;
location = path;
name = dev_name;
}
LogitechG560Controller::~LogitechG560Controller()
{
hid_close(dev);
}
std::string LogitechG560Controller::GetDeviceLocation()
{
return("HID: " + location);
}
std::string LogitechG560Controller::GetDeviceName()
{
return(name);
}
void LogitechG560Controller::SetDirectMode(uint8_t zone)
{
unsigned char usb_buf[LOGI_G560_LED_PACKET_SIZE];
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x04;
usb_buf[0x03] = 0xCA;
usb_buf[0x04] = zone;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
fail_retry_write(dev, usb_buf, LOGI_G560_LED_PACKET_SIZE);
}
void LogitechG560Controller::SetOffMode(uint8_t zone)
{
unsigned char usb_buf[LOGI_G560_LED_PACKET_SIZE];
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x04;
usb_buf[0x03] = 0x3F;
usb_buf[0x04] = zone;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
fail_retry_write(dev, usb_buf, LOGI_G560_LED_PACKET_SIZE);
}
void LogitechG560Controller::SendSpeakerMode
(
unsigned char zone,
unsigned char mode,
unsigned char red,
unsigned char green,
unsigned char blue
)
{
unsigned char usb_buf[LOGI_G560_LED_PACKET_SIZE];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Lighting Control |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x04;
/*-----------------------------------------------------*\
| This packet sets speaker into direct mode. This mode |
| is used by Lightsync Ambilight and Music Visualizer |
| realtime effect. |
\*-----------------------------------------------------*/
usb_buf[0x03] = 0x3A;
/*-----------------------------------------------------*\
| Set up mode and speed |
\*-----------------------------------------------------*/
usb_buf[0x04] = zone;
usb_buf[0x05] = mode;
/*-----------------------------------------------------*\
| And set up the colors |
\*-----------------------------------------------------*/
usb_buf[0x06] = red;
usb_buf[0x07] = green;
usb_buf[0x08] = blue;
if(mode == LOGITECH_G560_MODE_DIRECT) //G560 only has Direct Mode.
{
usb_buf[0x09] = 0x02;
}
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
fail_retry_write(dev, usb_buf, LOGI_G560_LED_PACKET_SIZE);
}
void LogitechG560Controller::fail_retry_write(hid_device *device, const unsigned char *data, size_t length)
{
unsigned char usb_buf_out[LOGI_G560_LED_PACKET_SIZE];
unsigned int write_max_retry = LOGI_G560_LED_COMMAND_SEND_RETRIES;
do
{
std::this_thread::sleep_for(1ms);
int ret = hid_write(device, data, length);
/*-------------------------------------------------------------------------------------*\
| HID write fails if a change led color and set volume command are sent at |
| the same time because RGB controller and volume control shares the same interface. |
\*-------------------------------------------------------------------------------------*/
if(ret == 20)
{
std::this_thread::sleep_for(1ms);
hid_read_timeout(dev, usb_buf_out, LOGI_G560_LED_PACKET_SIZE, 20);
break;
}
else
{
write_max_retry--;
std::this_thread::sleep_for(10ms);
}
}while (write_max_retry > 0);
}
@@ -0,0 +1,59 @@
/*---------------------------------------------------------*\
| LogitechG560Controller.h |
| |
| Driver for Logitech G560 |
| |
| Cheerpipe 28 Oct 2020 |
| based on TheRogueZeta 31 Aug 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "RGBController.h"
#define LOGI_G560_LED_PACKET_SIZE 20
#define LOGI_G560_LED_COMMAND_SEND_RETRIES 3
enum
{
LOGITECH_G560_MODE_OFF = 0x00,
LOGITECH_G560_MODE_DIRECT = 0x01,
LOGITECH_G560_MODE_CYCLE = 0x02,
LOGITECH_G560_MODE_BREATHING = 0x03,
};
class LogitechG560Controller
{
public:
LogitechG560Controller(hid_device* dev_handle, const char* path, std::string dev_name);
~LogitechG560Controller();
std::string GetDeviceLocation();
std::string GetDeviceName();
void SetDirectMode(uint8_t zone);
void SetOffMode(uint8_t zone);
void SendSpeakerMode
(
unsigned char zone,
unsigned char mode,
unsigned char red,
unsigned char green,
unsigned char blue
);
private:
hid_device* dev;
std::string location;
std::string name;
void fail_retry_write(hid_device *device, const unsigned char *data, size_t length);
};
@@ -0,0 +1,164 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG560.cpp |
| |
| RGBController for Logitech G560 |
| |
| Cheerpipe 28 Oct 2020 |
| based on TheRogueZeta 31 Aug 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_LogitechG560.h"
/**------------------------------------------------------------------*\
@name Logitech G560
@category Speaker
@type USB
@save :x:
@direct :white_check_mark:
@effects :x:
@detectors DetectLogitechG560
@comment
\*-------------------------------------------------------------------*/
RGBController_LogitechG560::RGBController_LogitechG560(LogitechG560Controller* controller_ptr)
{
controller = controller_ptr;
name = controller->GetDeviceName();
vendor = "Logitech";
type = DEVICE_TYPE_SPEAKER;
description = "Logitech G560 Lightsync Speaker";
location = controller->GetDeviceLocation();
mode Off;
Off.name = "Off";
Off.value = LOGITECH_G560_MODE_OFF;
Off.flags = 0;
Off.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Off);
mode Direct;
Direct.name = "Direct";
Direct.value = LOGITECH_G560_MODE_DIRECT;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
SetupZones();
}
void RGBController_LogitechG560::SetupZones()
{
zone G560_left_front;
G560_left_front.name = "Left Front";
G560_left_front.type = ZONE_TYPE_SINGLE;
G560_left_front.leds_min = 1;
G560_left_front.leds_max = 1;
G560_left_front.leds_count = 1;
G560_left_front.matrix_map = NULL;
zones.push_back(G560_left_front);
led G560_left_front_led;
G560_left_front_led.name = "Left Front";
G560_left_front_led.value = 0x00;
leds.push_back(G560_left_front_led);
zone G560_right_front;
G560_right_front.name = "Right Front";
G560_right_front.type = ZONE_TYPE_SINGLE;
G560_right_front.leds_min = 1;
G560_right_front.leds_max = 1;
G560_right_front.leds_count = 1;
G560_right_front.matrix_map = NULL;
zones.push_back(G560_right_front);
led G560_right_front_led;
G560_right_front_led.name = "Right Front";
G560_right_front_led.value = 0x01;
leds.push_back(G560_right_front_led);
zone G560_left_rear;
G560_left_rear.name = "Left Rear";
G560_left_rear.type = ZONE_TYPE_SINGLE;
G560_left_rear.leds_min = 1;
G560_left_rear.leds_max = 1;
G560_left_rear.leds_count = 1;
G560_left_rear.matrix_map = NULL;
zones.push_back(G560_left_rear);
led G560_left_read_led;
G560_left_read_led.name = "Left Rear";
G560_left_read_led.value = 0x02;
leds.push_back(G560_left_read_led);
zone G560_right_rear;
G560_right_rear.name = "Right Rear";
G560_right_rear.type = ZONE_TYPE_SINGLE;
G560_right_rear.leds_min = 1;
G560_right_rear.leds_max = 1;
G560_right_rear.leds_count = 1;
G560_right_rear.matrix_map = NULL;
zones.push_back(G560_right_rear);
led G560_right_rear_led;
G560_right_rear_led.name = "Right Rear";
G560_right_rear_led.value = 0x03;
leds.push_back(G560_right_rear_led);
SetupColors();
}
void RGBController_LogitechG560::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_LogitechG560::DeviceUpdateLEDs()
{
for(std::size_t led_idx = 0; led_idx < leds.size(); led_idx++)
{
unsigned char red = RGBGetRValue(colors[led_idx]);
unsigned char grn = RGBGetGValue(colors[led_idx]);
unsigned char blu = RGBGetBValue(colors[led_idx]);
controller->SendSpeakerMode((unsigned char)leds[led_idx].value, modes[active_mode].value, red, grn, blu);
}
}
void RGBController_LogitechG560::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_LogitechG560::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_LogitechG560::DeviceUpdateMode()
{
for(std::size_t led_idx = 0; led_idx < leds.size(); led_idx++)
{
if(modes[active_mode].value == LOGITECH_G560_MODE_OFF)
{
controller->SetOffMode(leds[led_idx].value);
}
else
{
/*---------------------------------------------------------*\
| Required to "reset" RGB controller and start receiving |
| color in direct mode |
\*---------------------------------------------------------*/
controller->SetDirectMode(leds[led_idx].value);
}
}
DeviceUpdateLEDs();
}
@@ -0,0 +1,35 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG560.h |
| |
| RGBController for Logitech G560 |
| |
| Cheerpipe 28 Oct 2020 |
| based on TheRogueZeta 31 Aug 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechG560Controller.h"
class RGBController_LogitechG560 : public RGBController
{
public:
RGBController_LogitechG560(LogitechG560Controller* controller_ptr);
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
LogitechG560Controller* controller;
};
@@ -0,0 +1,68 @@
/*---------------------------------------------------------*\
| LogitechG600Controller.cpp |
| |
| Driver for Logitech G600 Gaming Mouse |
| |
| Austin B (austinleroy) 11 Sep 2025 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "LogitechG600Controller.h"
#include "StringUtils.h"
LogitechG600Controller::LogitechG600Controller(hid_device* dev_handle, const char* path, std::string dev_name)
{
dev = dev_handle;
location = path;
name = dev_name;
}
LogitechG600Controller::~LogitechG600Controller()
{
if(dev != nullptr)
{
hid_close(dev);
}
}
std::string LogitechG600Controller::GetNameString()
{
return(name);
}
std::string LogitechG600Controller::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev, serial_string, 128);
if(ret != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
void LogitechG600Controller::SetMode
(
unsigned char mode,
unsigned short speed,
RGBColor color
)
{
unsigned char usb_buf[8];
memset(usb_buf, 0x00, sizeof(usb_buf));
usb_buf[0x00] = 0xF1;
usb_buf[0x01] = RGBGetRValue(color);
usb_buf[0x02] = RGBGetGValue(color);
usb_buf[0x03] = RGBGetBValue(color);
usb_buf[0x04] = mode;
usb_buf[0x05] = speed & 0xFF;
hid_send_feature_report(dev, usb_buf, sizeof(usb_buf));
}
@@ -0,0 +1,56 @@
/*---------------------------------------------------------*\
| LogitechG600Controller.h |
| |
| Driver for Logitech G600 Gaming Mouse |
| |
| Austin B (austinleroy) 11 Sep 2025 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "RGBController.h"
enum
{
LOGITECH_G600_MODE_DIRECT = 0x00,
LOGITECH_G600_MODE_BREATHING = 0x01,
LOGITECH_G600_MODE_CYCLE = 0x02
};
/*---------------------------------------------------------------------------------------------*\
| Speed is number of seconds for cycle to complete. |
\*---------------------------------------------------------------------------------------------*/
enum
{
LOGITECH_G600_SPEED_SLOWEST = 0x0F, /* Slowest speed */
LOGITECH_G600_SPEED_NORMAL = 0x03, /* Normal speed */
LOGITECH_G600_SPEED_FASTEST = 0x01, /* Fastest speed */
};
class LogitechG600Controller
{
public:
LogitechG600Controller(hid_device* dev, const char* path, std::string dev_name);
~LogitechG600Controller();
std::string GetNameString();
std::string GetSerialString();
void SetMode
(
unsigned char mode,
unsigned short speed,
RGBColor color
);
private:
hid_device* dev;
std::string location;
std::string name;
};
@@ -0,0 +1,114 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG600.cpp |
| |
| RGBController for Logitech G600 Gaming Mouse |
| |
| Austin B (austinleroy) 11 Sep 2025 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_LogitechG600.h"
/**------------------------------------------------------------------*\
@name Logitech G600
@category Mouse
@type USB
@save :o:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectLogitechMouseG600
@comment
\*-------------------------------------------------------------------*/
RGBController_LogitechG600::RGBController_LogitechG600(LogitechG600Controller* controller_ptr)
{
controller = controller_ptr;
name = controller->GetNameString();
vendor = "Logitech";
type = DEVICE_TYPE_MOUSE;
description = "Logitech Mouse Device";
serial = controller->GetSerialString();
mode Direct;
Direct.name = "Direct";
Direct.value = LOGITECH_G600_MODE_DIRECT;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = LOGITECH_G600_MODE_BREATHING;
Breathing.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_SPEED;
Breathing.color_mode = MODE_COLORS_PER_LED;
Breathing.speed_min = LOGITECH_G600_SPEED_SLOWEST;
Breathing.speed_max = LOGITECH_G600_SPEED_FASTEST;
Breathing.speed = LOGITECH_G600_SPEED_NORMAL;
modes.push_back(Breathing);
mode Cycle;
Cycle.name = "Spectrum Cycle";
Cycle.value = LOGITECH_G600_MODE_CYCLE;
Cycle.flags = MODE_FLAG_HAS_SPEED;
Cycle.color_mode = MODE_COLORS_NONE;
Cycle.speed_min = LOGITECH_G600_SPEED_SLOWEST;
Cycle.speed_max = LOGITECH_G600_SPEED_FASTEST;
Cycle.speed = LOGITECH_G600_SPEED_NORMAL;
modes.push_back(Cycle);
SetupZones();
}
RGBController_LogitechG600::~RGBController_LogitechG600()
{
delete controller;
}
void RGBController_LogitechG600::SetupZones()
{
zone side_lights_zone;
side_lights_zone.name = "Side Lights";
side_lights_zone.type = ZONE_TYPE_SINGLE;
side_lights_zone.leds_min = 1;
side_lights_zone.leds_max = 1;
side_lights_zone.leds_count = 1;
side_lights_zone.matrix_map = NULL;
zones.push_back(side_lights_zone);
// Set up LED
led g600_led;
g600_led.name = "All";
leds.push_back(g600_led);
SetupColors();
}
void RGBController_LogitechG600::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| Currently does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_LogitechG600::DeviceUpdateLEDs()
{
controller->SetMode(modes[active_mode].value, modes[active_mode].speed, GetLED(0));
}
void RGBController_LogitechG600::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_LogitechG600::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_LogitechG600::DeviceUpdateMode()
{
controller->SetMode(modes[active_mode].value, modes[active_mode].speed, GetLED(0));
}
@@ -0,0 +1,35 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG600.h |
| |
| RGBController for Logitech G600 Gaming Mouse |
| |
| Austin B (austinleroy) 11 Sep 2025 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechG600Controller.h"
class RGBController_LogitechG600 : public RGBController
{
public:
RGBController_LogitechG600(LogitechG600Controller* controller_ptr);
~RGBController_LogitechG600();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
LogitechG600Controller* controller;
};
@@ -0,0 +1,192 @@
/*---------------------------------------------------------*\
| LogitechG810Controller.cpp |
| |
| Driver for Logitech G810 Orion Spectrum |
| |
| Adam Honse (CalcProgrammer1) 11 Jun 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "LogitechG810Controller.h"
#include "StringUtils.h"
LogitechG810Controller::LogitechG810Controller(hid_device* dev_handle_0x11, hid_device* dev_handle_0x12, std::string dev_name)
{
dev_pkt_0x11 = dev_handle_0x11;
dev_pkt_0x12 = dev_handle_0x12;
name = dev_name;
}
LogitechG810Controller::~LogitechG810Controller()
{
hid_close(dev_pkt_0x11);
hid_close(dev_pkt_0x12);
}
std::string LogitechG810Controller::GetNameString()
{
return(name);
}
std::string LogitechG810Controller::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev_pkt_0x11, serial_string, 128);
if(ret != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
void LogitechG810Controller::Commit()
{
SendCommit();
}
void LogitechG810Controller::SetDirect
(
unsigned char zone,
unsigned char frame_count,
unsigned char * frame_data
)
{
SendDirectFrame(zone, frame_count, frame_data);
}
void LogitechG810Controller::SetMode
(
unsigned char mode,
unsigned short speed,
unsigned char red,
unsigned char green,
unsigned char blue
)
{
SendMode(LOGITECH_G810_ZONE_MODE_KEYBOARD, mode, speed, red, green, blue);
SendMode(LOGITECH_G810_ZONE_MODE_LOGO, mode, speed, red, green, blue);
SendCommit();
}
/*-------------------------------------------------------------------------------------------------*\
| Private packet sending functions. |
\*-------------------------------------------------------------------------------------------------*/
void LogitechG810Controller::SendCommit()
{
unsigned char usb_buf[20];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Commit packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x0C;
usb_buf[0x03] = 0x5D;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_pkt_0x11, usb_buf, 20);
hid_read(dev_pkt_0x11, usb_buf, 20);
}
void LogitechG810Controller::SendDirectFrame
(
unsigned char zone,
unsigned char frame_count,
unsigned char * frame_data
)
{
unsigned char usb_buf[64];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Lighting Control packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x12;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x0C;
usb_buf[0x03] = 0x3D;
usb_buf[0x05] = zone;
usb_buf[0x07] = frame_count;
/*-----------------------------------------------------*\
| Copy in frame data |
\*-----------------------------------------------------*/
memcpy(&usb_buf[0x08], frame_data, frame_count * 4);
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_pkt_0x12, usb_buf, 64);
hid_read(dev_pkt_0x11, usb_buf, 20);
}
void LogitechG810Controller::SendMode
(
unsigned char zone,
unsigned char mode,
unsigned short speed,
unsigned char red,
unsigned char green,
unsigned char blue
)
{
unsigned char usb_buf[20];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Lighting Control packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x0D;
usb_buf[0x03] = 0x3D;
usb_buf[0x04] = zone;
usb_buf[0x05] = mode;
usb_buf[0x06] = red;
usb_buf[0x07] = green;
usb_buf[0x08] = blue;
speed = 100 * speed;
if(mode == LOGITECH_G810_MODE_CYCLE)
{
usb_buf[0x0B] = speed >> 8;
usb_buf[0x0C] = speed & 0xFF;
usb_buf[0x0D] = 0x64;
}
else if(mode == LOGITECH_G810_MODE_BREATHING)
{
usb_buf[0x09] = speed >> 8;
usb_buf[0x0A] = speed & 0xFF;
usb_buf[0x0C] = 0x64;
}
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_pkt_0x11, usb_buf, 20);
hid_read(dev_pkt_0x11, usb_buf, 20);
}
@@ -0,0 +1,102 @@
/*---------------------------------------------------------*\
| LogitechG810Controller.h |
| |
| Driver for Logitech G810 Orion Spectrum |
| |
| Adam Honse (CalcProgrammer1) 11 Jun 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "RGBController.h"
enum
{
LOGITECH_G810_ZONE_MODE_KEYBOARD = 0x00,
LOGITECH_G810_ZONE_MODE_LOGO = 0x01,
};
enum
{
LOGITECH_G810_ZONE_DIRECT_KEYBOARD = 0x01,
LOGITECH_G810_ZONE_DIRECT_MEDIA = 0x02,
LOGITECH_G810_ZONE_DIRECT_LOGO = 0x10,
LOGITECH_G810_ZONE_DIRECT_INDICATORS = 0x40,
};
enum
{
LOGITECH_G810_MODE_OFF = 0x00,
LOGITECH_G810_MODE_STATIC = 0x01,
LOGITECH_G810_MODE_BREATHING = 0x02,
LOGITECH_G810_MODE_CYCLE = 0x03,
LOGITECH_G810_MODE_WAVE = 0x04,
};
/*---------------------------------------------------------------------------------------------*\
| Speed is 1000 for fast and 20000 for slow. |
| Values are multiplied by 100 later to give lots of GUI steps. |
\*---------------------------------------------------------------------------------------------*/
enum
{
LOGITECH_G810_SPEED_SLOWEST = 0xC8, /* Slowest speed */
LOGITECH_G810_SPEED_NORMAL = 0x32, /* Normal speed */
LOGITECH_G810_SPEED_FASTEST = 0x0A, /* Fastest speed */
};
class LogitechG810Controller
{
public:
LogitechG810Controller(hid_device* dev_handle_0x11, hid_device* dev_handle_0x12, std::string dev_name);
~LogitechG810Controller();
std::string GetNameString();
std::string GetSerialString();
void Commit();
void SetDirect
(
unsigned char zone,
unsigned char frame_count,
unsigned char * frame_data
);
void SetMode
(
unsigned char mode,
unsigned short speed,
unsigned char red,
unsigned char green,
unsigned char blue
);
private:
hid_device* dev_pkt_0x11;
hid_device* dev_pkt_0x12;
std::string name;
void SendDirectFrame
(
unsigned char zone,
unsigned char frame_count,
unsigned char * frame_data
);
void SendMode
(
unsigned char zone,
unsigned char mode,
unsigned short speed,
unsigned char red,
unsigned char green,
unsigned char blue
);
void SendCommit();
};
@@ -0,0 +1,402 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG810.cpp |
| |
| RGBController for Logitech G810 Orion Spectrum |
| |
| Adam Honse (CalcProgrammer1) 12 Jun 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBControllerKeyNames.h"
#include "RGBController_LogitechG810.h"
//0xFFFFFFFF indicates an unused entry in matrix
#define NA 0xFFFFFFFF
static unsigned int matrix_map[7][23] =
{ { 111, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 116, 114, 115, NA, 113, NA, 112, 110, NA, NA, NA },
{ 37, NA, 54, 55, 56, 57, NA, 58, 59, 60, 61, NA, 62, 63, 64, 65, 66, 67, 68, 109, 108, 107, 106 },
{ 49, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, NA, 41, 42, 38, NA, 69, 70, 71, 79, 80, 81, 82 },
{ 39, NA, 16, 22, 4, 17, NA, 19, 24, 20, 8, 14, 15, 43, 44, 45, 72, 73, 74, 91, 92, 93, 83 },
{ 53, NA, 0, 18, 3, 5, NA, 6, 7, 9, 10, 11, 47, 48, 46, 36, NA, NA, NA, 88, 89, 90, NA },
{ 99, 96, 25, 23, 2, 21, NA, 1, NA, 13, 12, 50, 51, 52, 103, NA, NA, 78, NA, 85, 86, 87, 84 },
{ 98, 101, 100, NA, NA, NA, NA, 40, NA, NA, NA, NA, 104, 105, 97, 102, 76, 77, 75, 94, NA, 95, NA } };
static const char* zone_names[] =
{
ZONE_EN_KEYBOARD,
};
static zone_type zone_types[] =
{
ZONE_TYPE_MATRIX,
};
static const unsigned int zone_sizes[] =
{
117,
};
typedef struct
{
const char * name;
const unsigned char zone;
const unsigned char idx;
} led_type;
static const led_type led_names[] =
{
/* Key Label Zone, Index */
{ KEY_EN_A, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x04 },
{ KEY_EN_B, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x05 },
{ KEY_EN_C, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x06 },
{ KEY_EN_D, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x07 },
{ KEY_EN_E, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x08 },
{ KEY_EN_F, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x09 },
{ KEY_EN_G, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x0A },
{ KEY_EN_H, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x0B },
{ KEY_EN_I, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x0C },
{ KEY_EN_J, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x0D },
{ KEY_EN_K, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x0E },
{ KEY_EN_L, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x0F },
{ KEY_EN_M, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x10 },
{ KEY_EN_N, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x11 },
{ KEY_EN_O, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x12 },
{ KEY_EN_P, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x13 },
{ KEY_EN_Q, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x14 },
{ KEY_EN_R, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x15 },
{ KEY_EN_S, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x16 },
{ KEY_EN_T, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x17 },
{ KEY_EN_U, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x18 },
{ KEY_EN_V, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x19 },
{ KEY_EN_W, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x1A },
{ KEY_EN_X, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x1B },
{ KEY_EN_Y, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x1C },
{ KEY_EN_Z, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x1D },
{ KEY_EN_1, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x1E },
{ KEY_EN_2, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x1F },
{ KEY_EN_3, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x20 },
{ KEY_EN_4, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x21 },
{ KEY_EN_5, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x22 },
{ KEY_EN_6, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x23 },
{ KEY_EN_7, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x24 },
{ KEY_EN_8, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x25 },
{ KEY_EN_9, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x26 },
{ KEY_EN_0, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x27 },
{ KEY_EN_ANSI_ENTER, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x28 },
{ KEY_EN_ESCAPE, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x29 },
{ KEY_EN_BACKSPACE, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x2A },
{ KEY_EN_TAB, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x2B },
{ KEY_EN_SPACE, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x2C },
{ KEY_EN_MINUS, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x2D },
{ KEY_EN_EQUALS, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x2E },
{ KEY_EN_LEFT_BRACKET, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x2F },
{ KEY_EN_RIGHT_BRACKET, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x30 },
{ KEY_EN_ANSI_BACK_SLASH, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x31 },//ANSI only
{ KEY_EN_POUND, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x32 },//ISO only
{ KEY_EN_SEMICOLON, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x33 },
{ KEY_EN_QUOTE, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x34 },
{ KEY_EN_BACK_TICK, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x35 },
{ KEY_EN_COMMA, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x36 },
{ KEY_EN_PERIOD, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x37 },
{ KEY_EN_FORWARD_SLASH, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x38 },
{ KEY_EN_CAPS_LOCK, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x39 },
{ KEY_EN_F1, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x3A },
{ KEY_EN_F2, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x3B },
{ KEY_EN_F3, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x3C },
{ KEY_EN_F4, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x3D },
{ KEY_EN_F5, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x3E },
{ KEY_EN_F6, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x3F },
{ KEY_EN_F7, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x40 },
{ KEY_EN_F8, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x41 },
{ KEY_EN_F9, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x42 },
{ KEY_EN_F10, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x43 },
{ KEY_EN_F11, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x44 },
{ KEY_EN_F12, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x45 },
{ KEY_EN_PRINT_SCREEN, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x46 },
{ KEY_EN_SCROLL_LOCK, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x47 },
{ KEY_EN_PAUSE_BREAK, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x48 },
{ KEY_EN_INSERT, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x49 },
{ KEY_EN_HOME, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x4A },
{ KEY_EN_PAGE_UP, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x4B },
{ KEY_EN_DELETE, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x4C },
{ KEY_EN_END, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x4D },
{ KEY_EN_PAGE_DOWN, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x4E },
{ KEY_EN_RIGHT_ARROW, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x4F },
{ KEY_EN_LEFT_ARROW, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x50 },
{ KEY_EN_DOWN_ARROW, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x51 },
{ KEY_EN_UP_ARROW, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x52 },
{ KEY_EN_NUMPAD_LOCK, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x53 },
{ KEY_EN_NUMPAD_DIVIDE, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x54 },
{ KEY_EN_NUMPAD_TIMES, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x55 },
{ KEY_EN_NUMPAD_MINUS, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x56 },
{ KEY_EN_NUMPAD_PLUS, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x57 },
{ KEY_EN_NUMPAD_ENTER, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x58 },
{ KEY_EN_NUMPAD_1, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x59 },
{ KEY_EN_NUMPAD_2, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x5A },
{ KEY_EN_NUMPAD_3, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x5B },
{ KEY_EN_NUMPAD_4, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x5C },
{ KEY_EN_NUMPAD_5, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x5D },
{ KEY_EN_NUMPAD_6, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x5E },
{ KEY_EN_NUMPAD_7, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x5F },
{ KEY_EN_NUMPAD_8, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x60 },
{ KEY_EN_NUMPAD_9, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x61 },
{ KEY_EN_NUMPAD_0, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x62 },
{ KEY_EN_NUMPAD_PERIOD, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x63 },
{ KEY_EN_ISO_BACK_SLASH, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x64 },//ISO only
{ KEY_EN_MENU, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0x65 },
{ KEY_EN_LEFT_CONTROL, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0xE0 },
{ KEY_EN_LEFT_SHIFT, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0xE1 },
{ KEY_EN_LEFT_ALT, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0xE2 },
{ KEY_EN_LEFT_WINDOWS, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0xE3 },
{ KEY_EN_RIGHT_CONTROL, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0xE4 },
{ KEY_EN_RIGHT_SHIFT, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0xE5 },
{ KEY_EN_RIGHT_ALT, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0xE6 },
{ KEY_EN_RIGHT_WINDOWS, LOGITECH_G810_ZONE_DIRECT_KEYBOARD, 0xE7 },
{ KEY_EN_MEDIA_NEXT, LOGITECH_G810_ZONE_DIRECT_MEDIA, 0xB5 },
{ KEY_EN_MEDIA_PREVIOUS, LOGITECH_G810_ZONE_DIRECT_MEDIA, 0xB6 },
{ KEY_EN_MEDIA_STOP, LOGITECH_G810_ZONE_DIRECT_MEDIA, 0xB7 },
{ KEY_EN_MEDIA_PLAY_PAUSE, LOGITECH_G810_ZONE_DIRECT_MEDIA, 0xCD },
{ KEY_EN_MEDIA_MUTE, LOGITECH_G810_ZONE_DIRECT_MEDIA, 0xE2 },
{ "Logo", LOGITECH_G810_ZONE_DIRECT_LOGO, 0x01 },
{ "Lighting", LOGITECH_G810_ZONE_DIRECT_INDICATORS, 0x01 },
{ "Game Mode", LOGITECH_G810_ZONE_DIRECT_INDICATORS, 0x02 },
{ "Caps Lock Indicator", LOGITECH_G810_ZONE_DIRECT_INDICATORS, 0x03 },
{ "Scroll Lock Indicator", LOGITECH_G810_ZONE_DIRECT_INDICATORS, 0x04 },
{ "Num Lock Indicator", LOGITECH_G810_ZONE_DIRECT_INDICATORS, 0x05 },
};
/**------------------------------------------------------------------*\
@name Logitech G810
@category Keyboard
@type USB
@save :robot:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectLogitechKeyboardG810
@comment
\*-------------------------------------------------------------------*/
RGBController_LogitechG810::RGBController_LogitechG810(LogitechG810Controller* controller_ptr)
{
controller = controller_ptr;
name = controller->GetNameString();
vendor = "Logitech";
type = DEVICE_TYPE_KEYBOARD;
description = "Logitech Keyboard Device";
serial = controller->GetSerialString();
mode Direct;
Direct.name = "Direct";
Direct.value = 0xFFFF;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
mode Static;
Static.name = "Static";
Static.value = LOGITECH_G810_MODE_STATIC;
Static.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Static.colors_min = 1;
Static.colors_max = 1;
Static.color_mode = MODE_COLORS_MODE_SPECIFIC;
Static.colors.resize(1);
modes.push_back(Static);
mode Off;
Off.name = "Off";
Off.value = LOGITECH_G810_MODE_OFF;
Off.flags = 0;
Off.color_mode = MODE_COLORS_NONE;
modes.push_back(Off);
mode Cycle;
Cycle.name = "Cycle";
Cycle.value = LOGITECH_G810_MODE_CYCLE;
Cycle.flags = MODE_FLAG_HAS_SPEED;
Cycle.color_mode = MODE_COLORS_NONE;
Cycle.speed_min = LOGITECH_G810_SPEED_SLOWEST;
Cycle.speed_max = LOGITECH_G810_SPEED_FASTEST;
Cycle.speed = LOGITECH_G810_SPEED_NORMAL;
modes.push_back(Cycle);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = LOGITECH_G810_MODE_BREATHING;
Breathing.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED;
Breathing.colors_min = 1;
Breathing.colors_max = 1;
Breathing.color_mode = MODE_COLORS_MODE_SPECIFIC;
Breathing.colors.resize(1);
Breathing.speed_min = LOGITECH_G810_SPEED_SLOWEST;
Breathing.speed_max = LOGITECH_G810_SPEED_FASTEST;
Breathing.speed = LOGITECH_G810_SPEED_NORMAL;
modes.push_back(Breathing);
SetupZones();
}
RGBController_LogitechG810::~RGBController_LogitechG810()
{
/*---------------------------------------------------------*\
| Delete the matrix map |
\*---------------------------------------------------------*/
for(unsigned int zone_index = 0; zone_index < zones.size(); zone_index++)
{
if(zones[zone_index].matrix_map != NULL)
{
delete zones[zone_index].matrix_map;
}
}
delete controller;
}
void RGBController_LogitechG810::SetupZones()
{
/*---------------------------------------------------------*\
| Set up zones |
\*---------------------------------------------------------*/
unsigned int total_led_count = 0;
for(unsigned int zone_idx = 0; zone_idx < 1; zone_idx++)
{
zone new_zone;
new_zone.name = zone_names[zone_idx];
new_zone.type = zone_types[zone_idx];
new_zone.leds_min = zone_sizes[zone_idx];
new_zone.leds_max = zone_sizes[zone_idx];
new_zone.leds_count = zone_sizes[zone_idx];
if(zone_types[zone_idx] == ZONE_TYPE_MATRIX)
{
new_zone.matrix_map = new matrix_map_type;
new_zone.matrix_map->height = 7;
new_zone.matrix_map->width = 23;
new_zone.matrix_map->map = (unsigned int *)&matrix_map;
}
else
{
new_zone.matrix_map = NULL;
}
zones.push_back(new_zone);
total_led_count += zone_sizes[zone_idx];
}
for(unsigned int led_idx = 0; led_idx < total_led_count; led_idx++)
{
led new_led;
new_led.name = led_names[led_idx].name;
new_led.value = ( led_names[led_idx].zone << 8 ) + led_names[led_idx].idx;
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_LogitechG810::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_LogitechG810::DeviceUpdateLEDs()
{
#define MAX_FRAMES_PER_PACKET 0x0E
unsigned char frame_buf[MAX_FRAMES_PER_PACKET * 4];
unsigned char frame_cnt = 0;
unsigned char prev_zone = 0;
unsigned char zone = 0;
unsigned char idx = 0;
/*---------------------------------------------------------*\
| TODO: Send packets with multiple LED frames |
\*---------------------------------------------------------*/
for(std::size_t led_idx = 0; led_idx < leds.size(); led_idx++)
{
zone = ( leds[led_idx].value >> 8 );
idx = ( leds[led_idx].value & 0xFF );
if((zone != prev_zone) && (frame_cnt != 0))
{
controller->SetDirect(prev_zone, frame_cnt, frame_buf);
frame_cnt = 0;
}
frame_buf[(frame_cnt * 4) + 0] = idx;
frame_buf[(frame_cnt * 4) + 1] = RGBGetRValue(colors[led_idx]);
frame_buf[(frame_cnt * 4) + 2] = RGBGetGValue(colors[led_idx]);
frame_buf[(frame_cnt * 4) + 3] = RGBGetBValue(colors[led_idx]);
frame_cnt++;
prev_zone = zone;
if(frame_cnt == MAX_FRAMES_PER_PACKET)
{
controller->SetDirect(prev_zone, frame_cnt, frame_buf);
frame_cnt = 0;
}
}
if(frame_cnt != 0)
{
controller->SetDirect(prev_zone, frame_cnt, frame_buf);
}
controller->Commit();
}
void RGBController_LogitechG810::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_LogitechG810::UpdateSingleLED(int led)
{
unsigned char frame[4];
unsigned char zone;
unsigned char idx;
zone = ( leds[led].value >> 8 );
idx = ( leds[led].value & 0xFF );
frame[0] = idx;
frame[1] = RGBGetRValue(colors[led]);
frame[2] = RGBGetGValue(colors[led]);
frame[3] = RGBGetBValue(colors[led]);
controller->SetDirect(zone, 1, frame);
controller->Commit();
}
void RGBController_LogitechG810::DeviceUpdateMode()
{
/*---------------------------------------------------------*\
| Direct mode does not send a mode packet |
| Call UpdateLEDs to send direct packet |
\*---------------------------------------------------------*/
if(active_mode == 0xFFFF)
{
UpdateLEDs();
return;
}
unsigned char red = 0;
unsigned char grn = 0;
unsigned char blu = 0;
if(modes[active_mode].color_mode == MODE_COLORS_MODE_SPECIFIC)
{
red = RGBGetRValue(modes[active_mode].colors[0]);
grn = RGBGetGValue(modes[active_mode].colors[0]);
blu = RGBGetBValue(modes[active_mode].colors[0]);
}
controller->SetMode(modes[active_mode].value, modes[active_mode].speed, red, grn, blu);
}
@@ -0,0 +1,35 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG810.h |
| |
| RGBController for Logitech G810 Orion Spectrum |
| |
| Adam Honse (CalcProgrammer1) 12 Jun 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechG810Controller.h"
class RGBController_LogitechG810 : public RGBController
{
public:
RGBController_LogitechG810(LogitechG810Controller* controller_ptr);
~RGBController_LogitechG810();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
LogitechG810Controller* controller;
};
@@ -0,0 +1,311 @@
/*---------------------------------------------------------*\
| LogitechG815Controller.cpp |
| |
| Driver for Logitech G815 |
| |
| Cheerpipe 20 Mar 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "LogitechG815Controller.h"
#include "StringUtils.h"
LogitechG815Controller::LogitechG815Controller(hid_device* dev_handle_0x11, hid_device* dev_handle_0x12, std::string dev_name)
{
dev_pkt_0x11 = dev_handle_0x11;
dev_pkt_0x12 = dev_handle_0x12;
name = dev_name;
}
LogitechG815Controller::~LogitechG815Controller()
{
}
std::string LogitechG815Controller::GetNameString()
{
return(name);
}
std::string LogitechG815Controller::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev_pkt_0x11, serial_string, 128);
if(ret != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
void LogitechG815Controller::Commit()
{
SendCommit();
}
void LogitechG815Controller::SetDirect
(
unsigned char frame_type,
unsigned char * frame_data
)
{
SendDirectFrame(frame_type, frame_data);
}
void LogitechG815Controller::SetMode
(
unsigned char mode,
unsigned short speed,
unsigned char red,
unsigned char green,
unsigned char blue
)
{
SendMode(LOGITECH_G815_ZONE_MODE_KEYBOARD, mode, speed, red, green, blue);
SendMode(LOGITECH_G815_ZONE_MODE_LOGO, mode, speed, red, green, blue);
SendCommit();
}
/*-------------------------------------------------------------------------------------------------*\
| Private packet sending functions. |
\*-------------------------------------------------------------------------------------------------*/
void LogitechG815Controller::SendCommit()
{
unsigned char usb_buf[20];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Commit packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x10;
usb_buf[0x03] = LOGITECH_G815_COMMIT_BYTE;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_pkt_0x11, usb_buf, 20);
hid_read_timeout(dev_pkt_0x11, usb_buf, 20, LOGITECH_READ_TIMEOUT);
}
void LogitechG815Controller::InitializeDirect()
{
unsigned char usb_buf[20];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Commit packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x08;
usb_buf[0x03] = 0x3E;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_pkt_0x11, usb_buf, 20);
hid_read(dev_pkt_0x11, usb_buf, 20);
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Commit packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x08;
usb_buf[0x03] = 0x1E;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_pkt_0x11, usb_buf, 20);
hid_read(dev_pkt_0x11, usb_buf, 20);
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Commit packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x0F;
usb_buf[0x03] = 0x1E;
usb_buf[0x10] = 0x01;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_pkt_0x11, usb_buf, 20);
hid_read(dev_pkt_0x11, usb_buf, 20);
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Commit packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x0F;
usb_buf[0x03] = 0x1E;
usb_buf[0x04] = 0x01;
usb_buf[0x10] = 0x01;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_pkt_0x11, usb_buf, 20);
hid_read(dev_pkt_0x11, usb_buf, 20);
}
void LogitechG815Controller::SendSingleLed
(
unsigned char keyCode,
unsigned char r,
unsigned char g,
unsigned char b
)
{
unsigned char usb_buf[20];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up a 6F packet with a single color |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x10;
usb_buf[0x03] = LOGITECH_G815_ZONE_FRAME_TYPE_LITTLE;
usb_buf[0x04] = keyCode;
usb_buf[0x05] = r;
usb_buf[0x06] = g;
usb_buf[0x07] = b;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_pkt_0x11, usb_buf, 20);
hid_read(dev_pkt_0x11, usb_buf, 20);
}
void LogitechG815Controller::SendDirectFrame
(
unsigned char frame_type,
unsigned char * frame_data
)
{
unsigned char usb_buf[20];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Lighting Control packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x10;
usb_buf[0x03] = frame_type;
/*-----------------------------------------------------*\
| Copy in frame data |
\*-----------------------------------------------------*/
memcpy(&usb_buf[0x04], frame_data, 16);
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_pkt_0x11, usb_buf, 20);
hid_read_timeout(dev_pkt_0x11, usb_buf, 20, LOGITECH_READ_TIMEOUT);
}
void LogitechG815Controller::SendMode
(
unsigned char zone,
unsigned char mode,
unsigned short speed,
unsigned char red,
unsigned char green,
unsigned char blue
)
{
unsigned char usb_buf[20];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Lighting Control packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x0D;
usb_buf[0x03] = 0x3D; //TODO: Check if it is the correct value for G815
usb_buf[0x04] = zone;
usb_buf[0x05] = mode;
usb_buf[0x06] = red;
usb_buf[0x07] = green;
usb_buf[0x08] = blue;
speed = 100 * speed;
if(mode == LOGITECH_G815_MODE_CYCLE)
{
usb_buf[0x0B] = speed >> 8;
usb_buf[0x0C] = speed & 0xFF;
usb_buf[0x0D] = 0x64;
}
else if(mode == LOGITECH_G815_MODE_BREATHING)
{
usb_buf[0x09] = speed >> 8;
usb_buf[0x0A] = speed & 0xFF;
usb_buf[0x0C] = 0x64;
}
else
{
return;
}
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_pkt_0x11, usb_buf, 20);
hid_read(dev_pkt_0x11, usb_buf, 20);
}
@@ -0,0 +1,118 @@
/*---------------------------------------------------------*\
| LogitechG815Controller.h |
| |
| Driver for Logitech G815 |
| |
| Cheerpipe 20 Mar 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "RGBController.h"
#define LOGITECH_G815_COMMIT_BYTE 0x7F
#define LOGITECH_READ_TIMEOUT 300 //Timeout in ms
enum
{
LOGITECH_G815_ZONE_MODE_KEYBOARD = 0x00,
LOGITECH_G815_ZONE_MODE_LOGO = 0x01,
LOGITECH_G815_ZONE_MODE_MULTIMEDIA = 0X02,
LOGITECH_G815_ZONE_MODE_GKEYS = 0x03,
LOGITECH_G815_ZONE_MODE_MODIFIERS = 0x04
};
enum
{
LOGITECH_G815_ZONE_FRAME_TYPE_LITTLE = 0x1F,
LOGITECH_G815_ZONE_FRAME_TYPE_BIG = 0x6F
};
enum
{
LOGITECH_G815_ZONE_DIRECT_KEYBOARD = 0x01,
LOGITECH_G815_ZONE_DIRECT_MEDIA = 0x02,
LOGITECH_G815_ZONE_DIRECT_LOGO = 0x10,
LOGITECH_G815_ZONE_DIRECT_INDICATORS = 0x40,
};
enum
{
LOGITECH_G815_MODE_OFF = 0x00,
LOGITECH_G815_MODE_STATIC = 0x01,
LOGITECH_G815_MODE_BREATHING = 0x02,
LOGITECH_G815_MODE_CYCLE = 0x03,
LOGITECH_G815_MODE_WAVE = 0x04,
LOGITECH_G815_MODE_DIRECT = 0x05,
};
/*---------------------------------------------------------------------------------------------*\
| Speed is 1000 for fast and 20000 for slow. |
| Values are multiplied by 100 later to give lots of GUI steps. |
\*---------------------------------------------------------------------------------------------*/
enum
{
LOGITECH_G815_SPEED_SLOWEST = 0xC8, /* Slowest speed */
LOGITECH_G815_SPEED_NORMAL = 0x32, /* Normal speed */
LOGITECH_G815_SPEED_FASTEST = 0x0A, /* Fastest speed */
};
class LogitechG815Controller
{
public:
LogitechG815Controller(hid_device* dev_handle_0x11, hid_device* dev_handle_0x12, std::string dev_name);
~LogitechG815Controller();
std::string GetNameString();
std::string GetSerialString();
void Commit();
void InitializeDirect();
void SetDirect
(
unsigned char frame_type,
unsigned char * frame_data
);
void SendSingleLed
(
unsigned char keyCode,
unsigned char r,
unsigned char g,
unsigned char b
);
void SetMode
(
unsigned char mode,
unsigned short speed,
unsigned char red,
unsigned char green,
unsigned char blue
);
private:
hid_device* dev_pkt_0x11;
hid_device* dev_pkt_0x12;
std::string name;
void SendDirectFrame
(
unsigned char frame_type,
unsigned char * frame_data
);
void SendMode
(
unsigned char zone,
unsigned char mode,
unsigned short speed,
unsigned char red,
unsigned char green,
unsigned char blue
);
void SendCommit();
};
@@ -0,0 +1,537 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG815.cpp |
| |
| RGBController for Logitech G815 |
| |
| Cheerpipe 20 Mar 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include <iterator>
#include <map>
#include "RGBControllerKeyNames.h"
#include "RGBController_LogitechG815.h"
#define NA 0xFFFFFFFF
const size_t max_key_per_color = 13;
const size_t data_size = 16;
static unsigned int matrix_map[7][27] =
{ { 110, NA, NA, NA, NA, NA, NA, NA, NA, NA, 111, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA },
{ NA, NA, 37, NA, 54, 55, 56, 57, NA, 58, 59, 60, 61, NA, 62, 63, 64, 65, NA, 66, 67, 68, NA, 106, 107, 108, 109 },
{ 112, NA, 49, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, NA, 41, 42, 38, NA, NA, 69, 70, 71, NA, 79, 80, 81, 82 },
{ 113, NA, 39, NA, 16, 22, 4, 17, NA, 19, 24, 20, 8, 14, 15, 43, 44, 45, NA, 72, 73, 74, NA, 91, 92, 93, 83 },
{ 114, NA, 53, NA, 0, 18, 3, 5, NA, 6, 7, 9, 10, 11, 47, 48, 46, 36, NA, NA, NA, NA, NA, 88, 89, 90, NA },
{ 115, NA, 99, 96, 25, 23, 2, 21, NA, 1, NA, 13, 12, 50, 51, 52, 103, NA, NA, NA, 78, NA, NA, 85, 86, 87, 84 },
{ 116, NA, 98, 101, 100, NA, NA, NA, NA, 40, NA, NA, NA, NA, 104, 105, 97, 102, NA, 76, 77, 75, NA, 94, NA, 95, NA } };
static const char* zone_names[] =
{
ZONE_EN_KEYBOARD,
};
static zone_type zone_types[] =
{
ZONE_TYPE_MATRIX,
};
static const unsigned int zone_sizes[] =
{
117,
};
typedef struct
{
const char * name;
const unsigned char zone;
const unsigned char idx;
} logitech_g815_led;
static const logitech_g815_led led_names[] =
{
/* Key Label Zone, Index */
{ KEY_EN_A, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x04 },
{ KEY_EN_B, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x05 },
{ KEY_EN_C, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x06 },
{ KEY_EN_D, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x07 },
{ KEY_EN_E, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x08 },
{ KEY_EN_F, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x09 },
{ KEY_EN_G, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x0A },
{ KEY_EN_H, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x0B },
{ KEY_EN_I, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x0C },
{ KEY_EN_J, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x0D },
{ KEY_EN_K, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x0E },
{ KEY_EN_L, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x0F },
{ KEY_EN_M, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x10 },
{ KEY_EN_N, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x11 },
{ KEY_EN_O, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x12 },
{ KEY_EN_P, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x13 },
{ KEY_EN_Q, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x14 },
{ KEY_EN_R, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x15 },
{ KEY_EN_S, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x16 },
{ KEY_EN_T, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x17 },
{ KEY_EN_U, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x18 },
{ KEY_EN_V, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x19 },
{ KEY_EN_W, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x1A },
{ KEY_EN_X, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x1B },
{ KEY_EN_Y, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x1C },
{ KEY_EN_Z, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x1D },
{ KEY_EN_1, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x1E },
{ KEY_EN_2, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x1F },
{ KEY_EN_3, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x20 },
{ KEY_EN_4, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x21 },
{ KEY_EN_5, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x22 },
{ KEY_EN_6, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x23 },
{ KEY_EN_7, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x24 },
{ KEY_EN_8, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x25 },
{ KEY_EN_9, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x26 },
{ KEY_EN_0, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x27 },
{ KEY_EN_ANSI_ENTER, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x28 },
{ KEY_EN_ESCAPE, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x29 },
{ KEY_EN_BACKSPACE, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x2A },
{ KEY_EN_TAB, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x2B },
{ KEY_EN_SPACE, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x2C },
{ KEY_EN_MINUS, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x2D },
{ KEY_EN_EQUALS, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x2E },
{ KEY_EN_LEFT_BRACKET, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x2F },
{ KEY_EN_RIGHT_BRACKET, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x30 },
{ KEY_EN_ANSI_BACK_SLASH, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x31 },//ANSI only
{ KEY_EN_POUND, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x32 },//ISO only
{ KEY_EN_SEMICOLON, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x33 },
{ KEY_EN_QUOTE, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x34 },
{ KEY_EN_BACK_TICK, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x35 },
{ KEY_EN_COMMA, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x36 },
{ KEY_EN_PERIOD, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x37 },
{ KEY_EN_FORWARD_SLASH, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x38 },
{ KEY_EN_CAPS_LOCK, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x39 },
{ KEY_EN_F1, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x3A },
{ KEY_EN_F2, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x3B },
{ KEY_EN_F3, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x3C },
{ KEY_EN_F4, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x3D },
{ KEY_EN_F5, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x3E },
{ KEY_EN_F6, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x3F },
{ KEY_EN_F7, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x40 },
{ KEY_EN_F8, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x41 },
{ KEY_EN_F9, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x42 },
{ KEY_EN_F10, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x43 },
{ KEY_EN_F11, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x44 },
{ KEY_EN_F12, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x45 },
{ KEY_EN_PRINT_SCREEN, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x46 },
{ KEY_EN_SCROLL_LOCK, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x47 },
{ KEY_EN_PAUSE_BREAK, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x48 },
{ KEY_EN_INSERT, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x49 },
{ KEY_EN_HOME, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x4A },
{ KEY_EN_PAGE_UP, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x4B },
{ KEY_EN_DELETE, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x4C },
{ KEY_EN_END, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x4D },
{ KEY_EN_PAGE_DOWN, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x4E },
{ KEY_EN_RIGHT_ARROW, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x4F },
{ KEY_EN_LEFT_ARROW, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x50 },
{ KEY_EN_DOWN_ARROW, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x51 },
{ KEY_EN_UP_ARROW, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x52 },
{ KEY_EN_NUMPAD_LOCK, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x53 },
{ KEY_EN_NUMPAD_DIVIDE, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x54 },
{ KEY_EN_NUMPAD_TIMES, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x55 },
{ KEY_EN_NUMPAD_MINUS, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x56 },
{ KEY_EN_NUMPAD_PLUS, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x57 },
{ KEY_EN_NUMPAD_ENTER, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x58 },
{ KEY_EN_NUMPAD_1, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x59 },
{ KEY_EN_NUMPAD_2, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x5A },
{ KEY_EN_NUMPAD_3, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x5B },
{ KEY_EN_NUMPAD_4, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x5C },
{ KEY_EN_NUMPAD_5, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x5D },
{ KEY_EN_NUMPAD_6, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x5E },
{ KEY_EN_NUMPAD_7, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x5F },
{ KEY_EN_NUMPAD_8, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x60 },
{ KEY_EN_NUMPAD_9, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x61 },
{ KEY_EN_NUMPAD_0, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x62 },
{ KEY_EN_NUMPAD_PERIOD, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x63 },
{ KEY_EN_ISO_BACK_SLASH, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x64 },//ISO only
{ KEY_EN_MENU, LOGITECH_G815_ZONE_DIRECT_KEYBOARD, 0x65 },
{ KEY_EN_LEFT_CONTROL, LOGITECH_G815_ZONE_MODE_MODIFIERS, 0xE0 },
{ KEY_EN_LEFT_SHIFT, LOGITECH_G815_ZONE_MODE_MODIFIERS, 0xE1 },
{ KEY_EN_LEFT_ALT, LOGITECH_G815_ZONE_MODE_MODIFIERS, 0xE2 },
{ KEY_EN_LEFT_WINDOWS, LOGITECH_G815_ZONE_MODE_MODIFIERS, 0xE3 },
{ KEY_EN_RIGHT_CONTROL, LOGITECH_G815_ZONE_MODE_MODIFIERS, 0xE4 },
{ KEY_EN_RIGHT_SHIFT, LOGITECH_G815_ZONE_MODE_MODIFIERS, 0xE5 },
{ KEY_EN_RIGHT_ALT, LOGITECH_G815_ZONE_MODE_MODIFIERS, 0xE6 },
{ KEY_EN_RIGHT_WINDOWS, LOGITECH_G815_ZONE_MODE_MODIFIERS, 0xE7 },
{ KEY_EN_MEDIA_PREVIOUS, LOGITECH_G815_ZONE_DIRECT_MEDIA, 0x9E },
{ KEY_EN_MEDIA_PLAY_PAUSE, LOGITECH_G815_ZONE_DIRECT_MEDIA, 0x9B },
{ KEY_EN_MEDIA_NEXT, LOGITECH_G815_ZONE_DIRECT_MEDIA, 0x9D },
{ KEY_EN_MEDIA_MUTE, LOGITECH_G815_ZONE_DIRECT_MEDIA, 0x9C },
{ "Logo", LOGITECH_G815_ZONE_DIRECT_LOGO, 0x01 },
{ "Lighting", LOGITECH_G815_ZONE_DIRECT_INDICATORS, 0x99 },
{ "Key: G1", LOGITECH_G815_ZONE_MODE_GKEYS, 0x01 },
{ "Key: G2", LOGITECH_G815_ZONE_MODE_GKEYS, 0x02 },
{ "Key: G3", LOGITECH_G815_ZONE_MODE_GKEYS, 0x03 },
{ "Key: G4", LOGITECH_G815_ZONE_MODE_GKEYS, 0x04 },
{ "Key: G5", LOGITECH_G815_ZONE_MODE_GKEYS, 0x05 },
};
/**------------------------------------------------------------------*\
@name Logitech G815
@category Keyboard
@type USB
@save :robot:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectLogitechKeyboardG815
@comment
\*-------------------------------------------------------------------*/
RGBController_LogitechG815::RGBController_LogitechG815(LogitechG815Controller* controller_ptr)
{
controller = controller_ptr;
name = controller->GetNameString();
vendor = "Logitech";
type = DEVICE_TYPE_KEYBOARD;
description = "Logitech G815 Keyboard Device";
serial = controller->GetSerialString();
mode Direct;
Direct.name = "Direct";
Direct.value = LOGITECH_G815_MODE_DIRECT;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
mode Static;
Static.name = "Static";
Static.value = LOGITECH_G815_MODE_STATIC;
Static.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Static.colors_min = 1;
Static.colors_max = 1;
Static.color_mode = MODE_COLORS_MODE_SPECIFIC;
Static.colors.resize(1);
modes.push_back(Static);
mode Off;
Off.name = "Off";
Off.value = LOGITECH_G815_MODE_OFF;
Off.flags = 0;
Off.color_mode = MODE_COLORS_NONE;
modes.push_back(Off);
mode Cycle;
Cycle.name = "Cycle";
Cycle.value = LOGITECH_G815_MODE_CYCLE;
Cycle.flags = MODE_FLAG_HAS_SPEED;
Cycle.color_mode = MODE_COLORS_NONE;
Cycle.speed_min = LOGITECH_G815_SPEED_SLOWEST;
Cycle.speed_max = LOGITECH_G815_SPEED_FASTEST;
Cycle.speed = LOGITECH_G815_SPEED_NORMAL;
modes.push_back(Cycle);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = LOGITECH_G815_MODE_BREATHING;
Breathing.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED;
Breathing.colors_min = 1;
Breathing.colors_max = 1;
Breathing.color_mode = MODE_COLORS_MODE_SPECIFIC;
Breathing.colors.resize(1);
Breathing.speed_min = LOGITECH_G815_SPEED_SLOWEST;
Breathing.speed_max = LOGITECH_G815_SPEED_FASTEST;
Breathing.speed = LOGITECH_G815_SPEED_NORMAL;
modes.push_back(Breathing);
SetupZones();
std::copy(colors.begin(), colors.end(),std::back_inserter(current_colors));
}
RGBController_LogitechG815::~RGBController_LogitechG815()
{
/*---------------------------------------------------------*\
| Delete the matrix map |
\*---------------------------------------------------------*/
for(unsigned int zone_index = 0; zone_index < zones.size(); zone_index++)
{
if(zones[zone_index].matrix_map != NULL)
{
delete zones[zone_index].matrix_map;
}
}
delete controller;
}
void RGBController_LogitechG815::SetupZones()
{
/*---------------------------------------------------------*\
| Set up zones |
\*---------------------------------------------------------*/
unsigned int total_led_count = 0;
for(unsigned int zone_idx = 0; zone_idx < 1; zone_idx++)
{
zone new_zone;
new_zone.name = zone_names[zone_idx];
new_zone.type = zone_types[zone_idx];
new_zone.leds_min = zone_sizes[zone_idx];
new_zone.leds_max = zone_sizes[zone_idx];
new_zone.leds_count = zone_sizes[zone_idx];
if(zone_types[zone_idx] == ZONE_TYPE_MATRIX)
{
new_zone.matrix_map = new matrix_map_type;
new_zone.matrix_map->height = 7;
new_zone.matrix_map->width = 27;
new_zone.matrix_map->map = (unsigned int *)&matrix_map;
}
else
{
new_zone.matrix_map = NULL;
}
zones.push_back(new_zone);
total_led_count += zone_sizes[zone_idx];
}
for(unsigned int led_idx = 0; led_idx < total_led_count; led_idx++)
{
led new_led;
new_led.name = led_names[led_idx].name;
new_led.value = ( led_names[led_idx].zone << 8 ) + led_names[led_idx].idx;
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_LogitechG815::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_LogitechG815::DeviceUpdateLEDs()
{
std::map<RGBColor, std::vector<char>> ledsByColors;
std::vector<RGBColor> new_colors;
unsigned char zone = 0;
unsigned char idx = 0;
unsigned char frame_buffer_big_mode[data_size];
unsigned char frame_buffer_little_mode[data_size];
RGBColor colorkey;
/*---------------------------------------------------------*\
| Freeze colors array because prepare framebuffers |
| may take some time. |
\*---------------------------------------------------------*/
std::copy(colors.begin(), colors.end(),std::back_inserter(new_colors));
/*---------------------------------------------------------*\
| Get unique colors to create mode 1F and 6F frame_buffers |
\*---------------------------------------------------------*/
for(std::size_t led_idx = 0; led_idx < leds.size(); led_idx++)
{
zone = ( leds[led_idx].value >> 8 );
idx = ( leds[led_idx].value );
if(current_colors[led_idx]==new_colors[led_idx])
{
/*-------------------------------------------------*\
| Don't send if key color is not changed |
\*-------------------------------------------------*/
continue;
}
switch (zone)
{
case LOGITECH_G815_ZONE_MODE_GKEYS:
idx = ((idx & 0x00ff) + 0xb3);
break;
case LOGITECH_G815_ZONE_MODE_MODIFIERS:
idx = ((idx & 0x00ff) - 0x78);
break;
case LOGITECH_G815_ZONE_DIRECT_KEYBOARD:
idx = ((idx & 0x00ff) - 0x03);
break;
case LOGITECH_G815_ZONE_DIRECT_LOGO:
idx = ((idx & 0x00ff) + 0xd1);
break;
default:
idx = (idx & 0x00ff);
break;
}
colorkey = new_colors[led_idx];
if(ledsByColors.count(colorkey) == 0)
{
ledsByColors.insert(std::pair<RGBColor, std::vector<char>>(colorkey, {}));
}
ledsByColors[colorkey].push_back(idx);
}
uint8_t led_in_little_frame = 0;
uint8_t bi = 0;
size_t frame_pos = 3;
uint8_t li = 0;
/*---------------------------------------------------------*\
| Create frame_buffers of type 1F (Little, up to 4 leds |
| per packet) and 6F (big, up to 13 leds per packet). |
\*---------------------------------------------------------*/
for(std::pair<const RGBColor, std::vector<char>>& x: ledsByColors)
{
/*-----------------------------------------------------*\
| For colors with more than 4 keys. Better to use big |
| (6F) packets to save USB transfers. |
\*-----------------------------------------------------*/
if(x.second.size() > 4)
{
bi = 0;
while(bi < x.second.size())
{
frame_buffer_big_mode[0] = RGBGetRValue(x.first);
frame_buffer_big_mode[1] = RGBGetGValue(x.first);
frame_buffer_big_mode[2] = RGBGetBValue(x.first);
frame_pos = 3;
for(uint8_t i = 0; i < (uint8_t)max_key_per_color; i++)
{
if((bi + i) < (uint8_t)x.second.size())
{
frame_buffer_big_mode[frame_pos] = x.second[bi+i];
frame_pos++;
}
}
if(frame_pos < data_size)
{
/*-----------------------------------------*\
| Zeroing just what is needed and if needed |
\*-----------------------------------------*/
memset(frame_buffer_big_mode + frame_pos, 0x00, sizeof(frame_buffer_big_mode) - frame_pos);
/*-----------------------------------------*\
| End of Data byte |
\*-----------------------------------------*/
frame_buffer_big_mode[frame_pos] = 0xFF;
}
/*-----------------------------------------------------*\
| Zeroing just what is needed |
\*-----------------------------------------------------*/
controller->SetDirect(LOGITECH_G815_ZONE_FRAME_TYPE_BIG, frame_buffer_big_mode);
bi = bi + max_key_per_color;
}
}
/*-----------------------------------------------------*\
| For colors with up to 4 keys. Use 1F packet to send |
| up to 4 colors-keys combinations per packet. |
\*-----------------------------------------------------*/
else
{
li = 0;
while(li < x.second.size())
{
frame_buffer_little_mode[led_in_little_frame*4 + 0] = x.second[li];
frame_buffer_little_mode[led_in_little_frame*4 + 1] = RGBGetRValue(x.first);
frame_buffer_little_mode[led_in_little_frame*4 + 2] = RGBGetGValue(x.first);
frame_buffer_little_mode[led_in_little_frame*4 + 3] = RGBGetBValue(x.first);
li++;
led_in_little_frame++;
if(led_in_little_frame == 4)
{
/*-----------------------------------------*\
| No End of Data byte if the packet is full |
\*-----------------------------------------*/
controller->SetDirect(LOGITECH_G815_ZONE_FRAME_TYPE_LITTLE, frame_buffer_little_mode);
led_in_little_frame=0;
}
}
}
}
/*---------------------------------------------------------*\
| If there is a left 1F packet with less than 4 keys, send |
| it and add an End of Data byte. |
\*---------------------------------------------------------*/
if(led_in_little_frame > 0)
{
/*-----------------------------------------------------*\
| Zeroing just what is needed |
\*-----------------------------------------------------*/
memset(frame_buffer_little_mode + (led_in_little_frame * 4 - 1), 0x00, sizeof(frame_buffer_little_mode) - led_in_little_frame * 4);
/*-----------------------------------------------------*\
| Data byte |
\*-----------------------------------------------------*/
frame_buffer_little_mode[led_in_little_frame*4 + 0] = 0xFF;
/*-----------------------------------------------------*\
| Send little frame and clear little frame buffer |
\*-----------------------------------------------------*/
controller->SetDirect(LOGITECH_G815_ZONE_FRAME_TYPE_LITTLE, frame_buffer_little_mode);
}
if(ledsByColors.size() > 0)
{
/*-----------------------------------------------------*\
| Copy the current color vector to avoid set keys that |
| has not being |
\*-----------------------------------------------------*/
controller->Commit();
std::copy(new_colors.begin(), new_colors.end(),current_colors.begin());
}
}
void RGBController_LogitechG815::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_LogitechG815::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_LogitechG815::DeviceUpdateMode()
{
/*---------------------------------------------------------*\
| Direct mode does not send a mode packet |
| Call UpdateLEDs to send direct packet |
\*---------------------------------------------------------*/
if(modes[active_mode].value == LOGITECH_G815_MODE_DIRECT)
{
/*-----------------------------------------------------*\
| Send real direct mode initialization. I used same |
| sequence as GHUB for screen capture. |
\*-----------------------------------------------------*/
controller->InitializeDirect();
/*-----------------------------------------------------*\
| Set one key to get direct mode engaged. |
\*-----------------------------------------------------*/
controller->SendSingleLed(0x29,0,0,0);
controller->Commit();
return;
}
unsigned char red = 0;
unsigned char grn = 0;
unsigned char blu = 0;
if(modes[active_mode].color_mode == MODE_COLORS_MODE_SPECIFIC)
{
red = RGBGetRValue(modes[active_mode].colors[0]);
grn = RGBGetGValue(modes[active_mode].colors[0]);
blu = RGBGetBValue(modes[active_mode].colors[0]);
}
controller->SetMode(modes[active_mode].value, modes[active_mode].speed, red, grn, blu);
}
@@ -0,0 +1,36 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG815.h |
| |
| RGBController for Logitech G815 |
| |
| Cheerpipe 20 Mar 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechG815Controller.h"
class RGBController_LogitechG815 : public RGBController
{
public:
RGBController_LogitechG815(LogitechG815Controller* controller_ptr);
~RGBController_LogitechG815();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
LogitechG815Controller* controller;
std::vector<RGBColor> current_colors;
};
@@ -0,0 +1,192 @@
/*---------------------------------------------------------*\
| LogitechG910Controller.cpp |
| |
| Driver for Logitech G910 Orion Spectrum |
| |
| Adam Honse (CalcProgrammer1) 11 Jun 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "LogitechG910Controller.h"
#include "StringUtils.h"
LogitechG910Controller::LogitechG910Controller(hid_device* dev_handle_0x11, hid_device* dev_handle_0x12, std::string dev_name)
{
dev_pkt_0x11 = dev_handle_0x11;
dev_pkt_0x12 = dev_handle_0x12;
name = dev_name;
}
LogitechG910Controller::~LogitechG910Controller()
{
hid_close(dev_pkt_0x11);
hid_close(dev_pkt_0x12);
}
std::string LogitechG910Controller::GetNameString()
{
return(name);
}
std::string LogitechG910Controller::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev_pkt_0x11, serial_string, 128);
if(ret != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
void LogitechG910Controller::Commit()
{
SendCommit();
}
void LogitechG910Controller::SetDirect
(
unsigned char zone,
unsigned char frame_count,
unsigned char * frame_data
)
{
SendDirectFrame(zone, frame_count, frame_data);
}
void LogitechG910Controller::SetMode
(
unsigned char mode,
unsigned short speed,
unsigned char red,
unsigned char green,
unsigned char blue
)
{
SendMode(LOGITECH_G910_ZONE_MODE_KEYBOARD, mode, speed, red, green, blue);
SendMode(LOGITECH_G910_ZONE_MODE_LOGO, mode, speed, red, green, blue);
SendCommit();
}
/*-------------------------------------------------------------------------------------------------*\
| Private packet sending functions. |
\*-------------------------------------------------------------------------------------------------*/
void LogitechG910Controller::SendCommit()
{
unsigned char usb_buf[20];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Commit packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x0F;
usb_buf[0x03] = 0x5F;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_pkt_0x11, usb_buf, 20);
hid_read(dev_pkt_0x11, usb_buf, 20);
}
void LogitechG910Controller::SendDirectFrame
(
unsigned char zone,
unsigned char frame_count,
unsigned char * frame_data
)
{
unsigned char usb_buf[64];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Lighting Control packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x12;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x0F;
usb_buf[0x03] = 0x3F;
usb_buf[0x05] = zone;
usb_buf[0x07] = frame_count;
/*-----------------------------------------------------*\
| Copy in frame data |
\*-----------------------------------------------------*/
memcpy(&usb_buf[0x08], frame_data, frame_count * 4);
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_pkt_0x12, usb_buf, 64);
hid_read(dev_pkt_0x11, usb_buf, 20);
}
void LogitechG910Controller::SendMode
(
unsigned char zone,
unsigned char mode,
unsigned short speed,
unsigned char red,
unsigned char green,
unsigned char blue
)
{
unsigned char usb_buf[20];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Lighting Control packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x10;
usb_buf[0x03] = 0x3B;
usb_buf[0x04] = zone;
usb_buf[0x05] = mode;
usb_buf[0x06] = red;
usb_buf[0x07] = green;
usb_buf[0x08] = blue;
speed = 100 * speed;
if(mode == LOGITECH_G910_MODE_CYCLE)
{
usb_buf[0x0B] = speed >> 8;
usb_buf[0x0C] = speed & 0xFF;
usb_buf[0x0D] = 0x64;
}
else if(mode == LOGITECH_G910_MODE_BREATHING)
{
usb_buf[0x09] = speed >> 8;
usb_buf[0x0A] = speed & 0xFF;
usb_buf[0x0C] = 0x64;
}
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_pkt_0x11, usb_buf, 20);
hid_read(dev_pkt_0x11, usb_buf, 20);
}
@@ -0,0 +1,102 @@
/*---------------------------------------------------------*\
| LogitechG910Controller.h |
| |
| Driver for Logitech G910 Orion Spectrum |
| |
| Adam Honse (CalcProgrammer1) 11 Jun 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "RGBController.h"
enum
{
LOGITECH_G910_ZONE_MODE_KEYBOARD = 0x00,
LOGITECH_G910_ZONE_MODE_LOGO = 0x01,
};
enum
{
LOGITECH_G910_ZONE_DIRECT_KEYBOARD = 0x01,
LOGITECH_G910_ZONE_DIRECT_GKEYS = 0x04,
LOGITECH_G910_ZONE_DIRECT_LOGO = 0x10,
LOGITECH_G910_ZONE_DIRECT_INDICATORS = 0x40,
};
enum
{
LOGITECH_G910_MODE_OFF = 0x00,
LOGITECH_G910_MODE_STATIC = 0x01,
LOGITECH_G910_MODE_BREATHING = 0x02,
LOGITECH_G910_MODE_CYCLE = 0x03,
LOGITECH_G910_MODE_WAVE = 0x04,
};
/*---------------------------------------------------------------------------------------------*\
| Speed is 1000 for fast and 20000 for slow. |
| Values are multiplied by 100 later to give lots of GUI steps. |
\*---------------------------------------------------------------------------------------------*/
enum
{
LOGITECH_G910_SPEED_SLOWEST = 0xC8, /* Slowest speed */
LOGITECH_G910_SPEED_NORMAL = 0x32, /* Normal speed */
LOGITECH_G910_SPEED_FASTEST = 0x0A, /* Fastest speed */
};
class LogitechG910Controller
{
public:
LogitechG910Controller(hid_device* dev_handle_0x11, hid_device* dev_handle_0x12, std::string dev_name);
~LogitechG910Controller();
std::string GetNameString();
std::string GetSerialString();
void Commit();
void SetDirect
(
unsigned char zone,
unsigned char frame_count,
unsigned char * frame_data
);
void SetMode
(
unsigned char mode,
unsigned short speed,
unsigned char red,
unsigned char green,
unsigned char blue
);
private:
hid_device* dev_pkt_0x11;
hid_device* dev_pkt_0x12;
std::string name;
void SendDirectFrame
(
unsigned char zone,
unsigned char frame_count,
unsigned char * frame_data
);
void SendMode
(
unsigned char zone,
unsigned char mode,
unsigned short speed,
unsigned char red,
unsigned char green,
unsigned char blue
);
void SendCommit();
};
@@ -0,0 +1,403 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG910.cpp |
| |
| RGBController for Logitech G910 Orion Spectrum |
| |
| Adam Honse (CalcProgrammer1) 12 Jun 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBControllerKeyNames.h"
#include "RGBController_LogitechG910.h"
//0xFFFFFFFF indicates an unused entry in matrix
#define NA 0xFFFFFFFF
static unsigned int matrix_map[8][24] =
{ { NA, 111, 112, 113, 114, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA },
{ 115, 37, NA, 54, 55, 56, 57, NA, 58, 59, 60, 61, NA, 62, 63, 64, 65, 66, 67, 68, NA, NA, NA, NA },
{ 106, 49, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, NA, 41, 42, 38, NA, 69, 70, 71, 79, 80, 81, 82 },
{ 107, 39, NA, 16, 22, 4, 17, NA, 19, 24, 20, 8, 14, 15, 43, 44, 45, 72, 73, 74, 91, 92, 93, 83 },
{ 108, 53, NA, 0, 18, 3, 5, NA, 6, 7, 9, 10, 11, 47, 48, 46, 36, NA, NA, NA, 88, 89, 90, NA },
{ 109, 99, 96, 25, 23, 2, 21, NA, 1, NA, 13, 12, 50, 51, 52, 103, NA, NA, 78, NA, 85, 86, 87, 84 },
{ 110, 98, 101, 100, NA, NA, NA, NA, 40, NA, NA, NA, NA, 104, 105, 97, 102, 76, 77, 75, 94, NA, 95, NA },
{ NA, NA, NA, 116, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA } };
static const char* zone_names[] =
{
ZONE_EN_KEYBOARD,
};
static zone_type zone_types[] =
{
ZONE_TYPE_MATRIX,
};
static const unsigned int zone_sizes[] =
{
117,
};
typedef struct
{
const char * name;
const unsigned char zone;
const unsigned char idx;
} led_type;
static const led_type led_names[] =
{
/* Key Label Zone, Index */
{ KEY_EN_A, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x04 },
{ KEY_EN_B, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x05 },
{ KEY_EN_C, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x06 },
{ KEY_EN_D, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x07 },
{ KEY_EN_E, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x08 },
{ KEY_EN_F, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x09 },
{ KEY_EN_G, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x0A },
{ KEY_EN_H, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x0B },
{ KEY_EN_I, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x0C },
{ KEY_EN_J, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x0D },
{ KEY_EN_K, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x0E },
{ KEY_EN_L, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x0F },
{ KEY_EN_M, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x10 },
{ KEY_EN_N, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x11 },
{ KEY_EN_O, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x12 },
{ KEY_EN_P, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x13 },
{ KEY_EN_Q, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x14 },
{ KEY_EN_R, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x15 },
{ KEY_EN_S, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x16 },
{ KEY_EN_T, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x17 },
{ KEY_EN_U, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x18 },
{ KEY_EN_V, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x19 },
{ KEY_EN_W, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x1A },
{ KEY_EN_X, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x1B },
{ KEY_EN_Y, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x1C },
{ KEY_EN_Z, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x1D },
{ KEY_EN_1, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x1E },
{ KEY_EN_2, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x1F },
{ KEY_EN_3, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x20 },
{ KEY_EN_4, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x21 },
{ KEY_EN_5, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x22 },
{ KEY_EN_6, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x23 },
{ KEY_EN_7, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x24 },
{ KEY_EN_8, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x25 },
{ KEY_EN_9, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x26 },
{ KEY_EN_0, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x27 },
{ KEY_EN_ANSI_ENTER, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x28 },
{ KEY_EN_ESCAPE, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x29 },
{ KEY_EN_BACKSPACE, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x2A },
{ KEY_EN_TAB, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x2B },
{ KEY_EN_SPACE, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x2C },
{ KEY_EN_MINUS, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x2D },
{ KEY_EN_EQUALS, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x2E },
{ KEY_EN_LEFT_BRACKET, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x2F },
{ KEY_EN_RIGHT_BRACKET, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x30 },
{ KEY_EN_ANSI_BACK_SLASH, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x31 },//ANSI only
{ KEY_EN_POUND, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x32 },//ISO only
{ KEY_EN_SEMICOLON, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x33 },
{ KEY_EN_QUOTE, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x34 },
{ KEY_EN_BACK_TICK, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x35 },
{ KEY_EN_COMMA, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x36 },
{ KEY_EN_PERIOD, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x37 },
{ KEY_EN_FORWARD_SLASH, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x38 },
{ KEY_EN_CAPS_LOCK, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x39 },
{ KEY_EN_F1, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x3A },
{ KEY_EN_F2, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x3B },
{ KEY_EN_F3, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x3C },
{ KEY_EN_F4, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x3D },
{ KEY_EN_F5, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x3E },
{ KEY_EN_F6, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x3F },
{ KEY_EN_F7, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x40 },
{ KEY_EN_F8, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x41 },
{ KEY_EN_F9, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x42 },
{ KEY_EN_F10, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x43 },
{ KEY_EN_F11, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x44 },
{ KEY_EN_F12, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x45 },
{ KEY_EN_PRINT_SCREEN, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x46 },
{ KEY_EN_SCROLL_LOCK, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x47 },
{ KEY_EN_PAUSE_BREAK, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x48 },
{ KEY_EN_INSERT, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x49 },
{ KEY_EN_HOME, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x4A },
{ KEY_EN_PAGE_UP, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x4B },
{ KEY_EN_DELETE, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x4C },
{ KEY_EN_END, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x4D },
{ KEY_EN_PAGE_DOWN, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x4E },
{ KEY_EN_RIGHT_ARROW, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x4F },
{ KEY_EN_LEFT_ARROW, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x50 },
{ KEY_EN_DOWN_ARROW, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x51 },
{ KEY_EN_UP_ARROW, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x52 },
{ KEY_EN_NUMPAD_LOCK, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x53 },
{ KEY_EN_NUMPAD_DIVIDE, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x54 },
{ KEY_EN_NUMPAD_TIMES, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x55 },
{ KEY_EN_NUMPAD_MINUS, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x56 },
{ KEY_EN_NUMPAD_PLUS, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x57 },
{ KEY_EN_NUMPAD_ENTER, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x58 },
{ KEY_EN_NUMPAD_1, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x59 },
{ KEY_EN_NUMPAD_2, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x5A },
{ KEY_EN_NUMPAD_3, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x5B },
{ KEY_EN_NUMPAD_4, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x5C },
{ KEY_EN_NUMPAD_5, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x5D },
{ KEY_EN_NUMPAD_6, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x5E },
{ KEY_EN_NUMPAD_7, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x5F },
{ KEY_EN_NUMPAD_8, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x60 },
{ KEY_EN_NUMPAD_9, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x61 },
{ KEY_EN_NUMPAD_0, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x62 },
{ KEY_EN_NUMPAD_PERIOD, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x63 },
{ KEY_EN_ISO_BACK_SLASH, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x64 },//ISO only
{ KEY_EN_MENU, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0x65 },
{ KEY_EN_LEFT_CONTROL, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0xE0 },
{ KEY_EN_LEFT_SHIFT, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0xE1 },
{ KEY_EN_LEFT_ALT, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0xE2 },
{ KEY_EN_LEFT_WINDOWS, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0xE3 },
{ KEY_EN_RIGHT_CONTROL, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0xE4 },
{ KEY_EN_RIGHT_SHIFT, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0xE5 },
{ KEY_EN_RIGHT_ALT, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0xE6 },
{ KEY_EN_RIGHT_WINDOWS, LOGITECH_G910_ZONE_DIRECT_KEYBOARD, 0xE7 },
{ "Key: G1", LOGITECH_G910_ZONE_DIRECT_GKEYS, 0x01 },
{ "Key: G2", LOGITECH_G910_ZONE_DIRECT_GKEYS, 0x02 },
{ "Key: G3", LOGITECH_G910_ZONE_DIRECT_GKEYS, 0x03 },
{ "Key: G4", LOGITECH_G910_ZONE_DIRECT_GKEYS, 0x04 },
{ "Key: G5", LOGITECH_G910_ZONE_DIRECT_GKEYS, 0x05 },
{ "Key: G6", LOGITECH_G910_ZONE_DIRECT_GKEYS, 0x06 },
{ "Key: G7", LOGITECH_G910_ZONE_DIRECT_GKEYS, 0x07 },
{ "Key: G8", LOGITECH_G910_ZONE_DIRECT_GKEYS, 0x08 },
{ "Key: G9", LOGITECH_G910_ZONE_DIRECT_GKEYS, 0x09 },
{ "Logo", LOGITECH_G910_ZONE_DIRECT_LOGO, 0x01 },
{ "Nameplate", LOGITECH_G910_ZONE_DIRECT_LOGO, 0x02 },
};
/**------------------------------------------------------------------*\
@name Logitech G910
@category Keyboard
@type USB
@save :robot:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectLogitechKeyboardG910
@comment
\*-------------------------------------------------------------------*/
RGBController_LogitechG910::RGBController_LogitechG910(LogitechG910Controller* controller_ptr)
{
controller = controller_ptr;
name = controller->GetNameString();
vendor = "Logitech";
type = DEVICE_TYPE_KEYBOARD;
description = "Logitech Keyboard Device";
serial = controller->GetSerialString();
mode Direct;
Direct.name = "Direct";
Direct.value = 0xFFFF;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
mode Static;
Static.name = "Static";
Static.value = LOGITECH_G910_MODE_STATIC;
Static.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Static.colors_min = 1;
Static.colors_max = 1;
Static.color_mode = MODE_COLORS_MODE_SPECIFIC;
Static.colors.resize(1);
modes.push_back(Static);
mode Off;
Off.name = "Off";
Off.value = LOGITECH_G910_MODE_OFF;
Off.flags = 0;
Off.color_mode = MODE_COLORS_NONE;
modes.push_back(Off);
mode Cycle;
Cycle.name = "Cycle";
Cycle.value = LOGITECH_G910_MODE_CYCLE;
Cycle.flags = MODE_FLAG_HAS_SPEED;
Cycle.color_mode = MODE_COLORS_NONE;
Cycle.speed_min = LOGITECH_G910_SPEED_SLOWEST;
Cycle.speed_max = LOGITECH_G910_SPEED_FASTEST;
Cycle.speed = LOGITECH_G910_SPEED_NORMAL;
modes.push_back(Cycle);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = LOGITECH_G910_MODE_BREATHING;
Breathing.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED;
Breathing.colors_min = 1;
Breathing.colors_max = 1;
Breathing.color_mode = MODE_COLORS_MODE_SPECIFIC;
Breathing.colors.resize(1);
Breathing.speed_min = LOGITECH_G910_SPEED_SLOWEST;
Breathing.speed_max = LOGITECH_G910_SPEED_FASTEST;
Breathing.speed = LOGITECH_G910_SPEED_NORMAL;
modes.push_back(Breathing);
SetupZones();
}
RGBController_LogitechG910::~RGBController_LogitechG910()
{
/*---------------------------------------------------------*\
| Delete the matrix map |
\*---------------------------------------------------------*/
for(unsigned int zone_index = 0; zone_index < zones.size(); zone_index++)
{
if(zones[zone_index].matrix_map != NULL)
{
delete zones[zone_index].matrix_map;
}
}
delete controller;
}
void RGBController_LogitechG910::SetupZones()
{
/*---------------------------------------------------------*\
| Set up zones |
\*---------------------------------------------------------*/
unsigned int total_led_count = 0;
for(unsigned int zone_idx = 0; zone_idx < 1; zone_idx++)
{
zone new_zone;
new_zone.name = zone_names[zone_idx];
new_zone.type = zone_types[zone_idx];
new_zone.leds_min = zone_sizes[zone_idx];
new_zone.leds_max = zone_sizes[zone_idx];
new_zone.leds_count = zone_sizes[zone_idx];
if(zone_types[zone_idx] == ZONE_TYPE_MATRIX)
{
new_zone.matrix_map = new matrix_map_type;
new_zone.matrix_map->height = 8;
new_zone.matrix_map->width = 24;
new_zone.matrix_map->map = (unsigned int *)&matrix_map;
}
else
{
new_zone.matrix_map = NULL;
}
zones.push_back(new_zone);
total_led_count += zone_sizes[zone_idx];
}
for(unsigned int led_idx = 0; led_idx < total_led_count; led_idx++)
{
led new_led;
new_led.name = led_names[led_idx].name;
new_led.value = ( led_names[led_idx].zone << 8 ) + led_names[led_idx].idx;
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_LogitechG910::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_LogitechG910::DeviceUpdateLEDs()
{
#define MAX_FRAMES_PER_PACKET 0x0E
unsigned char frame_buf[MAX_FRAMES_PER_PACKET * 4];
unsigned char frame_cnt = 0;
unsigned char prev_zone = 0;
unsigned char zone = 0;
unsigned char idx = 0;
/*---------------------------------------------------------*\
| TODO: Send packets with multiple LED frames |
\*---------------------------------------------------------*/
for(std::size_t led_idx = 0; led_idx < leds.size(); led_idx++)
{
zone = ( leds[led_idx].value >> 8 );
idx = ( leds[led_idx].value & 0xFF );
if((zone != prev_zone) && (frame_cnt != 0))
{
controller->SetDirect(prev_zone, frame_cnt, frame_buf);
frame_cnt = 0;
}
frame_buf[(frame_cnt * 4) + 0] = idx;
frame_buf[(frame_cnt * 4) + 1] = RGBGetRValue(colors[led_idx]);
frame_buf[(frame_cnt * 4) + 2] = RGBGetGValue(colors[led_idx]);
frame_buf[(frame_cnt * 4) + 3] = RGBGetBValue(colors[led_idx]);
frame_cnt++;
prev_zone = zone;
if(frame_cnt == MAX_FRAMES_PER_PACKET)
{
controller->SetDirect(prev_zone, frame_cnt, frame_buf);
frame_cnt = 0;
}
}
if(frame_cnt != 0)
{
controller->SetDirect(prev_zone, frame_cnt, frame_buf);
}
controller->Commit();
}
void RGBController_LogitechG910::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_LogitechG910::UpdateSingleLED(int led)
{
unsigned char frame[4];
unsigned char zone;
unsigned char idx;
zone = ( leds[led].value >> 8 );
idx = ( leds[led].value & 0xFF );
frame[0] = idx;
frame[1] = RGBGetRValue(colors[led]);
frame[2] = RGBGetGValue(colors[led]);
frame[3] = RGBGetBValue(colors[led]);
controller->SetDirect(zone, 1, frame);
controller->Commit();
}
void RGBController_LogitechG910::DeviceUpdateMode()
{
/*---------------------------------------------------------*\
| Direct mode does not send a mode packet |
| Call UpdateLEDs to send direct packet |
\*---------------------------------------------------------*/
if(active_mode == 0xFFFF)
{
UpdateLEDs();
return;
}
unsigned char red = 0;
unsigned char grn = 0;
unsigned char blu = 0;
if(modes[active_mode].color_mode == MODE_COLORS_MODE_SPECIFIC)
{
red = RGBGetRValue(modes[active_mode].colors[0]);
grn = RGBGetGValue(modes[active_mode].colors[0]);
blu = RGBGetBValue(modes[active_mode].colors[0]);
}
controller->SetMode(modes[active_mode].value, modes[active_mode].speed, red, grn, blu);
}
@@ -0,0 +1,35 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG910.h |
| |
| RGBController for Logitech G910 Orion Spectrum |
| |
| Adam Honse (CalcProgrammer1) 12 Jun 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechG910Controller.h"
class RGBController_LogitechG910 : public RGBController
{
public:
RGBController_LogitechG910(LogitechG910Controller* controller_ptr);
~RGBController_LogitechG910();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
LogitechG910Controller* controller;
};
@@ -0,0 +1,438 @@
/*---------------------------------------------------------*\
| LogitechG915Controller.cpp |
| |
| Driver for Logitech G915 |
| |
| Cheerpipe 20 Mar 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "LogitechG915Controller.h"
#include "StringUtils.h"
const size_t MIN_DATA_FRAME_SIZE = 4;
const size_t MAX_DATA_FRAME_SIZE = 16;
const size_t HEADER_SIZE = 4;
const size_t MESSAGE_LEN = 20;
const size_t RESPONSE_LEN = 20;
LogitechG915Controller::LogitechG915Controller(hid_device* dev_handle, bool wired, std::string dev_name)
{
this->dev_handle = dev_handle;
this->name = dev_name;
if(wired)
{
device_index = 0xFF;
feature_4522_idx = 0x0E;
feature_8040_idx = 0x13;
feature_8071_idx = 0x09;
feature_8081_idx = 0x0A;
}
else
{
device_index = 0x01;
feature_4522_idx = 0x0F;
feature_8040_idx = 0x14;
feature_8071_idx = 0x0A;
feature_8081_idx = 0x0B;
}
}
LogitechG915Controller::~LogitechG915Controller()
{
}
std::string LogitechG915Controller::GetNameString()
{
return(name);
}
std::string LogitechG915Controller::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev_handle, serial_string, 128);
if(ret != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
void LogitechG915Controller::Commit()
{
SendCommit();
}
void LogitechG915Controller::SetDirect
(
unsigned char frame_type,
unsigned char * frame_data,
size_t length
)
{
SendDirectFrame(frame_type, frame_data, length);
}
void LogitechG915Controller::SetMode
(
unsigned char mode,
unsigned short speed,
unsigned short brightness,
unsigned char red,
unsigned char green,
unsigned char blue
)
{
BeginModeSet();
uint8_t logo_mode = mode;
switch(mode)
{
case LOGITECH_G915_MODE_OFF:
case LOGITECH_G915_MODE_STATIC:
{
logo_mode = mode; // static and off match
break;
}
case LOGITECH_G915_MODE_BREATHING:
{
logo_mode = LOGITECH_G915_LOGO_MODE_BREATHING; //0x03
break;
}
case LOGITECH_G915_MODE_CYCLE:
case LOGITECH_G915_MODE_WAVE:
{
logo_mode = LOGITECH_G915_LOGO_MODE_CYCLE; //0x02
break;
}
case LOGITECH_G915_MODE_RIPPLE:
{
logo_mode = LOGITECH_G915_LOGO_MODE_STATIC;
break;
}
}
SendMode(LOGITECH_G915_ZONE_MODE_KEYBOARD, mode, speed, brightness, red, green, blue);
SendMode(LOGITECH_G915_ZONE_MODE_LOGO, logo_mode, speed, brightness, red, green, blue);
}
/*-------------------------------------------------------------------------------------------------*\
| Private packet sending functions. |
\*-------------------------------------------------------------------------------------------------*/
void LogitechG915Controller::SendCommit()
{
unsigned char usb_buf[MESSAGE_LEN];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Commit packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = device_index;
usb_buf[0x02] = feature_8081_idx;
usb_buf[0x03] = LOGITECH_G915_COMMIT_BYTE;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read_timeout(dev_handle, usb_buf, RESPONSE_LEN, LOGITECH_READ_TIMEOUT);
}
void LogitechG915Controller::BeginModeSet()
{
unsigned char usb_buf[MESSAGE_LEN];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Commit packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = device_index;
usb_buf[0x02] = feature_4522_idx;
usb_buf[0x03] = 0x3E;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Commit packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = device_index;
usb_buf[0x02] = feature_4522_idx;
usb_buf[0x03] = 0x1E;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
}
void LogitechG915Controller::InitializeModeSet()
{
unsigned char usb_buf[20];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Commit packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = device_index;
usb_buf[0x02] = feature_8071_idx;
usb_buf[0x03] = 0x5E;
usb_buf[0x04] = 0x01;
usb_buf[0x05] = 0x03;
usb_buf[0x06] = 0x07;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
}
void LogitechG915Controller::InitializeDirect()
{
unsigned char usb_buf[MESSAGE_LEN];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Commit packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = device_index;
usb_buf[0x02] = feature_4522_idx;
usb_buf[0x03] = 0x3E;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Commit packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = device_index;
usb_buf[0x02] = feature_4522_idx;
usb_buf[0x03] = 0x1E;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Commit packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = device_index;
usb_buf[0x02] = feature_8071_idx;
usb_buf[0x03] = 0x1E;
usb_buf[0x10] = 0x01;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read(dev_handle, usb_buf, MESSAGE_LEN);
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Commit packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = device_index;
usb_buf[0x02] = feature_8071_idx;
usb_buf[0x03] = 0x1E;
usb_buf[0x04] = 0x01;
usb_buf[0x10] = 0x01;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
}
void LogitechG915Controller::SendSingleLed
(
unsigned char keyCode,
unsigned char r,
unsigned char g,
unsigned char b
)
{
unsigned char little_frame[4] = { keyCode, r, g, b };
SendDirectFrame(LOGITECH_G915_ZONE_FRAME_TYPE_LITTLE, little_frame, 4);
}
void LogitechG915Controller::SendDirectFrame
(
unsigned char frame_type,
unsigned char * frame_data,
size_t length
)
{
if(length < MIN_DATA_FRAME_SIZE)
{
return;
}
else if(length > MAX_DATA_FRAME_SIZE)
{
length = MAX_DATA_FRAME_SIZE;
}
unsigned char usb_buf[MESSAGE_LEN];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Lighting Control packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = device_index;
usb_buf[0x02] = feature_8081_idx;
usb_buf[0x03] = frame_type;
/*-----------------------------------------------------*\
| Copy in frame data |
\*-----------------------------------------------------*/
memcpy(&usb_buf[0x04], frame_data, length);
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read_timeout(dev_handle, usb_buf, RESPONSE_LEN, LOGITECH_READ_TIMEOUT);
}
void LogitechG915Controller::SendMode
(
unsigned char zone,
unsigned char mode,
unsigned short speed,
unsigned short brightness,
unsigned char red,
unsigned char green,
unsigned char blue
)
{
unsigned char usb_buf[MESSAGE_LEN];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Lighting Control packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = device_index;
usb_buf[0x02] = feature_8071_idx;
usb_buf[0x03] = LOGITECH_G915_ZONE_FRAME_TYPE_MODE;
usb_buf[0x04] = zone;
usb_buf[0x05] = mode;
usb_buf[0x06] = red;
usb_buf[0x07] = green;
usb_buf[0x08] = blue;
if(mode != LOGITECH_G915_MODE_RIPPLE)
{
speed = 100 * speed;
}
// mode == LOGITECH_G915_MODE_OFF; No data to set
if(mode == LOGITECH_G915_MODE_STATIC)
{
usb_buf[0x09] = 0x02;
}
else if((mode == LOGITECH_G915_MODE_BREATHING && zone == LOGITECH_G915_ZONE_MODE_KEYBOARD) \
|| (mode == LOGITECH_G915_LOGO_MODE_BREATHING && zone == LOGITECH_G915_ZONE_MODE_LOGO))
{
usb_buf[0x09] = speed >> 8;
usb_buf[0x0A] = speed & 0xFF;
usb_buf[0x0C] = brightness & 0xFF;
}
else if((mode == LOGITECH_G915_MODE_CYCLE && zone == LOGITECH_G915_ZONE_MODE_KEYBOARD) \
|| (mode == LOGITECH_G915_LOGO_MODE_CYCLE && zone == LOGITECH_G915_ZONE_MODE_LOGO))
{
usb_buf[0x0B] = speed >> 8;
usb_buf[0x0C] = speed & 0xFF;
usb_buf[0x0D] = brightness & 0xFF;
}
else if(mode == LOGITECH_G915_MODE_WAVE)
{
usb_buf[0x0C] = speed & 0xFF;
usb_buf[0x0D] = 0x01; // Direction control 0x01 is horizontal
usb_buf[0x0E] = brightness & 0xFF;
usb_buf[0x0F] = speed >> 8;
}
else if(mode == LOGITECH_G915_MODE_RIPPLE)
{
usb_buf[0x0B] = speed & 0xFF;
}
usb_buf[0x10] = 0x01;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_handle, usb_buf, MESSAGE_LEN);
hid_read(dev_handle, usb_buf, RESPONSE_LEN);
}
@@ -0,0 +1,146 @@
/*---------------------------------------------------------*\
| LogitechG915Controller.h |
| |
| Driver for Logitech G915 |
| |
| Cheerpipe 20 Mar 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "RGBController.h"
#define LOGITECH_G915_COMMIT_BYTE 0x7F
#define LOGITECH_READ_TIMEOUT 300 //Timeout in ms
enum
{
LOGITECH_G915_ZONE_MODE_KEYBOARD = 0x01,
LOGITECH_G915_ZONE_MODE_LOGO = 0x00,
LOGITECH_G915_ZONE_MODE_MULTIMEDIA = 0x02,
LOGITECH_G915_ZONE_MODE_GKEYS = 0x03,
LOGITECH_G915_ZONE_MODE_MODIFIERS = 0x04
};
enum
{
LOGITECH_G915_ZONE_FRAME_TYPE_LITTLE = 0x1F,
LOGITECH_G915_ZONE_FRAME_TYPE_BIG = 0x6F,
LOGITECH_G915_ZONE_FRAME_TYPE_MODE = 0x1E
};
enum
{
LOGITECH_G915_ZONE_DIRECT_KEYBOARD = 0x01,
LOGITECH_G915_ZONE_DIRECT_MEDIA = 0x02,
LOGITECH_G915_ZONE_DIRECT_LOGO = 0x10,
LOGITECH_G915_ZONE_DIRECT_INDICATORS = 0x40,
};
enum
{
LOGITECH_G915_MODE_OFF = 0x00,
LOGITECH_G915_MODE_STATIC = 0x01,
LOGITECH_G915_MODE_BREATHING = 0x02,
LOGITECH_G915_MODE_CYCLE = 0x03,
LOGITECH_G915_MODE_WAVE = 0x04,
LOGITECH_G915_MODE_RIPPLE = 0x05,
LOGITECH_G915_MODE_DIRECT = 0xFF,
};
enum
{
LOGITECH_G915_LOGO_MODE_OFF = 0x00,
LOGITECH_G915_LOGO_MODE_STATIC = 0x01,
LOGITECH_G915_LOGO_MODE_CYCLE = 0x02,
LOGITECH_G915_LOGO_MODE_BREATHING = 0x03,
};
/*---------------------------------------------------------------------------------------------*\
| Speed is 1000 for fast and 20000 for slow. |
| Values are multiplied by 100 later to give lots of GUI steps. |
\*---------------------------------------------------------------------------------------------*/
enum
{
LOGITECH_G915_SPEED_SLOWEST = 0xC8, /* Slowest speed */
LOGITECH_G915_SPEED_NORMAL = 0x32, /* Normal speed */
LOGITECH_G915_SPEED_FASTEST = 0x0A, /* Fastest speed */
};
/* Ripple speeds are in ms directly. */
enum
{
LOGITECH_G915_SPEED_RIPPLE_SLOW = 200,
LOGITECH_G915_SPEED_RIPPLE_NORMAL = 20,
LOGITECH_G915_SPEED_RIPPLE_FAST = 2,
};
class LogitechG915Controller
{
public:
LogitechG915Controller(hid_device* dev_handle, bool wired, std::string dev_name);
~LogitechG915Controller();
std::string GetNameString();
std::string GetSerialString();
void Commit();
void InitializeDirect();
void InitializeModeSet();
void BeginModeSet();
void SetDirect
(
unsigned char frame_type,
unsigned char * frame_data,
size_t length
);
void SendSingleLed
(
unsigned char keyCode,
unsigned char r,
unsigned char g,
unsigned char b
);
void SetMode
(
unsigned char mode,
unsigned short speed,
unsigned short brightness,
unsigned char red,
unsigned char green,
unsigned char blue
);
private:
hid_device* dev_handle;
unsigned char feature_4522_idx;
unsigned char device_index;
unsigned char feature_8040_idx;
unsigned char feature_8071_idx;
unsigned char feature_8081_idx;
std::string name;
void SendDirectFrame
(
unsigned char frame_type,
unsigned char * frame_data,
size_t length
);
void SendMode
(
unsigned char zone,
unsigned char mode,
unsigned short speed,
unsigned short brightness,
unsigned char red,
unsigned char green,
unsigned char blue
);
void SendCommit();
};
@@ -0,0 +1,655 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG915.cpp |
| |
| RGBController for Logitech G915 |
| |
| Cheerpipe 20 Mar 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include <iterator>
#include <map>
#include "RGBControllerKeyNames.h"
#include "RGBController_LogitechG915.h"
#define NA 0xFFFFFFFF
const size_t DATA_FRAME_SIZE = 16;
const size_t BIG_FRAME_MAX_KEYS = 13;
const size_t LITTLE_FRAME_MAX_KEYS = 4;
static unsigned int matrix_map[7][27] =
{ { 93, NA, NA, NA, NA, NA, NA, NA, NA, NA, 94, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA },
{ NA, NA, 37, NA, 54, 55, 56, 57, NA, 58, 59, 60, 61, NA, 62, 63, 64, 65, NA, 66, 67, 68, NA, 89, 90, 91, 92 },
{ 112, NA, 49, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, NA, 41, 42, 38, NA, NA, 69, 70, 71, NA, 95, 96, 97, 98 },
{ 113, NA, 39, NA, 16, 22, 4, 17, NA, 19, 24, 20, 8, 14, 15, 43, 44, 45, NA, 72, 73, 74, NA, 107, 108, 109, 99 },
{ 114, NA, 53, NA, 0, 18, 3, 5, NA, 6, 7, 9, 10, 11, 47, 48, 46, 36, NA, NA, NA, NA, NA, 104, 105, 106, NA },
{ 115, NA, 82, 79, 25, 23, 2, 21, NA, 1, NA, 13, 12, 50, 51, 52, 86, NA, NA, NA, 78, NA, NA, 101, 102, 103, 100 },
{ 116, NA, 81, 84, 83, NA, NA, NA, NA, 40, NA, NA, NA, NA, 87, 88, 80, 85, NA, 76, 77, 75, NA, 110, NA, 111, NA } };
static unsigned int matrix_map_tkl[7][20] =
{ { 93, NA, NA, NA, NA, 94, NA, NA, NA, NA, NA, NA, 89, 90, 91, 92, NA, NA, NA, NA },
{ 37, NA, 54, 55, 56, 57, NA, 58, 59, 60, 61, NA, 62, 63, 64, 65, NA, 66, 67, 68 },
{ 49, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, NA, 41, 42, 38, NA, NA, 69, 70, 71 },
{ 39, NA, 16, 22, 4, 17, NA, 19, 24, 20, 8, 14, 15, 43, 44, 45, NA, 72, 73, 74 },
{ 53, NA, 0, 18, 3, 5, NA, 6, 7, 9, 10, 11, 47, 48, 46, 36, NA, NA, NA, NA },
{ 82, 79, 25, 23, 2, 21, NA, 1, NA, 13, 12, 50, 51, 52, 86, NA, NA, NA, 78, NA },
{ 81, 84, 83, NA, NA, NA, NA, 40, NA, NA, NA, NA, 87, 88, 80, 85, NA, 76, 77, 75 } };
static const char* zone_names[] =
{
ZONE_EN_KEYBOARD,
};
static zone_type zone_types[] =
{
ZONE_TYPE_MATRIX,
};
static const unsigned int tkl_led_count = 95;
static const unsigned int full_led_count = 117;
typedef struct
{
const char * name;
const unsigned char zone;
const unsigned char idx;
} led_type;
static const led_type led_names[] =
{
/* Key Label Zone, Index */
{ KEY_EN_A, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x04 },
{ KEY_EN_B, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x05 },
{ KEY_EN_C, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x06 },
{ KEY_EN_D, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x07 },
{ KEY_EN_E, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x08 },
{ KEY_EN_F, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x09 },
{ KEY_EN_G, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x0A },
{ KEY_EN_H, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x0B },
{ KEY_EN_I, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x0C },
{ KEY_EN_J, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x0D },
{ KEY_EN_K, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x0E },
{ KEY_EN_L, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x0F },
{ KEY_EN_M, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x10 },
{ KEY_EN_N, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x11 },
{ KEY_EN_O, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x12 },
{ KEY_EN_P, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x13 },
{ KEY_EN_Q, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x14 },
{ KEY_EN_R, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x15 },
{ KEY_EN_S, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x16 },
{ KEY_EN_T, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x17 },
{ KEY_EN_U, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x18 },
{ KEY_EN_V, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x19 },
{ KEY_EN_W, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x1A },
{ KEY_EN_X, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x1B },
{ KEY_EN_Y, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x1C },
{ KEY_EN_Z, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x1D },
{ KEY_EN_1, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x1E },
{ KEY_EN_2, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x1F },
{ KEY_EN_3, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x20 },
{ KEY_EN_4, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x21 },
{ KEY_EN_5, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x22 },
{ KEY_EN_6, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x23 },
{ KEY_EN_7, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x24 },
{ KEY_EN_8, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x25 },
{ KEY_EN_9, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x26 },
{ KEY_EN_0, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x27 },
{ KEY_EN_ANSI_ENTER, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x28 },
{ KEY_EN_ESCAPE, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x29 },
{ KEY_EN_BACKSPACE, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x2A },
{ KEY_EN_TAB, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x2B },
{ KEY_EN_SPACE, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x2C },
{ KEY_EN_MINUS, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x2D },
{ KEY_EN_EQUALS, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x2E },
{ KEY_EN_LEFT_BRACKET, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x2F },
{ KEY_EN_RIGHT_BRACKET, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x30 },
{ KEY_EN_ANSI_BACK_SLASH, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x31 },//ANSI only
{ KEY_EN_POUND, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x32 },//ISO only
{ KEY_EN_SEMICOLON, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x33 },
{ KEY_EN_QUOTE, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x34 },
{ KEY_EN_BACK_TICK, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x35 },
{ KEY_EN_COMMA, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x36 },
{ KEY_EN_PERIOD, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x37 },
{ KEY_EN_FORWARD_SLASH, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x38 },
{ KEY_EN_CAPS_LOCK, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x39 },
{ KEY_EN_F1, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x3A },
{ KEY_EN_F2, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x3B },
{ KEY_EN_F3, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x3C },
{ KEY_EN_F4, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x3D },
{ KEY_EN_F5, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x3E },
{ KEY_EN_F6, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x3F },
{ KEY_EN_F7, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x40 },
{ KEY_EN_F8, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x41 },
{ KEY_EN_F9, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x42 },
{ KEY_EN_F10, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x43 },
{ KEY_EN_F11, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x44 },
{ KEY_EN_F12, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x45 },
{ KEY_EN_PRINT_SCREEN, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x46 },
{ KEY_EN_SCROLL_LOCK, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x47 },
{ KEY_EN_PAUSE_BREAK, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x48 },
{ KEY_EN_INSERT, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x49 },
{ KEY_EN_HOME, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x4A },
{ KEY_EN_PAGE_UP, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x4B },
{ KEY_EN_DELETE, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x4C },
{ KEY_EN_END, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x4D },
{ KEY_EN_PAGE_DOWN, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x4E },
{ KEY_EN_RIGHT_ARROW, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x4F },
{ KEY_EN_LEFT_ARROW, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x50 },
{ KEY_EN_DOWN_ARROW, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x51 },
{ KEY_EN_UP_ARROW, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x52 },
{ KEY_EN_ISO_BACK_SLASH, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x64 },//ISO only
{ KEY_EN_MENU, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x65 },
{ KEY_EN_LEFT_CONTROL, LOGITECH_G915_ZONE_MODE_MODIFIERS, 0xE0 },
{ KEY_EN_LEFT_SHIFT, LOGITECH_G915_ZONE_MODE_MODIFIERS, 0xE1 },
{ KEY_EN_LEFT_ALT, LOGITECH_G915_ZONE_MODE_MODIFIERS, 0xE2 },
{ KEY_EN_LEFT_WINDOWS, LOGITECH_G915_ZONE_MODE_MODIFIERS, 0xE3 },
{ KEY_EN_RIGHT_CONTROL, LOGITECH_G915_ZONE_MODE_MODIFIERS, 0xE4 },
{ KEY_EN_RIGHT_SHIFT, LOGITECH_G915_ZONE_MODE_MODIFIERS, 0xE5 },
{ KEY_EN_RIGHT_ALT, LOGITECH_G915_ZONE_MODE_MODIFIERS, 0xE6 },
{ KEY_EN_RIGHT_WINDOWS, LOGITECH_G915_ZONE_MODE_MODIFIERS, 0xE7 },
{ KEY_EN_MEDIA_PREVIOUS, LOGITECH_G915_ZONE_DIRECT_MEDIA, 0x9E },
{ KEY_EN_MEDIA_PLAY_PAUSE, LOGITECH_G915_ZONE_DIRECT_MEDIA, 0x9B },
{ KEY_EN_MEDIA_NEXT, LOGITECH_G915_ZONE_DIRECT_MEDIA, 0x9D },
{ KEY_EN_MEDIA_MUTE, LOGITECH_G915_ZONE_DIRECT_MEDIA, 0x9C },
{ "Logo", LOGITECH_G915_ZONE_DIRECT_LOGO, 0x01 },
{ "Key: Brightness", LOGITECH_G915_ZONE_DIRECT_INDICATORS, 0x99 },
{ KEY_EN_NUMPAD_LOCK, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x53 }, // First Non-TKL Key
{ KEY_EN_NUMPAD_DIVIDE, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x54 },
{ KEY_EN_NUMPAD_TIMES, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x55 },
{ KEY_EN_NUMPAD_MINUS, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x56 },
{ KEY_EN_NUMPAD_PLUS, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x57 },
{ KEY_EN_NUMPAD_ENTER, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x58 },
{ KEY_EN_NUMPAD_1, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x59 },
{ KEY_EN_NUMPAD_2, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x5A },
{ KEY_EN_NUMPAD_3, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x5B },
{ KEY_EN_NUMPAD_4, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x5C },
{ KEY_EN_NUMPAD_5, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x5D },
{ KEY_EN_NUMPAD_6, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x5E },
{ KEY_EN_NUMPAD_7, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x5F },
{ KEY_EN_NUMPAD_8, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x60 },
{ KEY_EN_NUMPAD_9, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x61 },
{ KEY_EN_NUMPAD_0, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x62 },
{ KEY_EN_NUMPAD_PERIOD, LOGITECH_G915_ZONE_DIRECT_KEYBOARD, 0x63 },
{ "Key: G1", LOGITECH_G915_ZONE_MODE_GKEYS, 0x01 },
{ "Key: G2", LOGITECH_G915_ZONE_MODE_GKEYS, 0x02 },
{ "Key: G3", LOGITECH_G915_ZONE_MODE_GKEYS, 0x03 },
{ "Key: G4", LOGITECH_G915_ZONE_MODE_GKEYS, 0x04 },
{ "Key: G5", LOGITECH_G915_ZONE_MODE_GKEYS, 0x05 },
};
/*--------------------------------------*\
| Small dataframe |
| Contains a max of 4 <key, color> pairs |
\*--------------------------------------*/
struct LittleFrame
{
std::pair<RGBColor, char> color_key[LITTLE_FRAME_MAX_KEYS];
size_t len = 0;
};
/*--------------------------------------*\
| Small dataframe |
| Contains 1 color for max 13 keys |
\*--------------------------------------*/
struct BigFrame
{
RGBColor color;
char keys[BIG_FRAME_MAX_KEYS];
size_t len = 0;
};
/*-------------------------------------------*\
| Add termination byte and zero out rest |
\*-------------------------------------------*/
void terminate_buffer(unsigned char buf[DATA_FRAME_SIZE], size_t idx)
{
memset(&buf[idx], 0x00, DATA_FRAME_SIZE - idx);
buf[idx] = 0xFF;
}
/*-------------------------------------------*\
| small frame: [KEY, R, G, B] |
| If less than 4 keys, terminate using 0xFF |
\*-------------------------------------------*/
size_t populate_little_frame_data(unsigned char buf[DATA_FRAME_SIZE], const LittleFrame& frame)
{
if(frame.len == 0)
{
return 0;
}
for(size_t i = 0; i < frame.len && i < LITTLE_FRAME_MAX_KEYS; i++)
{
buf[4 * i + 0] = frame.color_key[i].second;
buf[4 * i + 1] = RGBGetRValue(frame.color_key[i].first);
buf[4 * i + 2] = RGBGetGValue(frame.color_key[i].first);
buf[4 * i + 3] = RGBGetBValue(frame.color_key[i].first);
}
if(frame.len < LITTLE_FRAME_MAX_KEYS)
{
terminate_buffer(buf, 4 * frame.len);
return 4 * frame.len + 1; // termination byte
}
return DATA_FRAME_SIZE;
}
/*-------------------------------------------------*\
| Large frame: [R, G, B, Key0, Key1, ..., Key12] |
| If less than 13 keys, terminate using 0xFF |
\*--------------------------------------------------*/
size_t populate_big_frame_data(unsigned char buf[DATA_FRAME_SIZE], const BigFrame& frame)
{
if(frame.len == 0)
{
return 0;
}
buf[0] = RGBGetRValue(frame.color);
buf[1] = RGBGetGValue(frame.color);
buf[2] = RGBGetBValue(frame.color);
for(size_t i = 0; i < frame.len && i < BIG_FRAME_MAX_KEYS; i++)
{
buf[i + 3] = frame.keys[i];
}
if(frame.len < BIG_FRAME_MAX_KEYS)
{
terminate_buffer(buf, frame.len + 3);
return frame.len + 4; // color + termination byte
}
return DATA_FRAME_SIZE;
}
/**------------------------------------------------------------------*\
@name Logitech G915
@category Keyboard
@type USB
@save :robot:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectLogitechKeyboardG915,DetectLogitechKeyboardG915Wired
@comment
\*-------------------------------------------------------------------*/
RGBController_LogitechG915::RGBController_LogitechG915(LogitechG915Controller* controller_ptr, bool tkl)
{
controller = controller_ptr;
is_tkl = tkl;
if(is_tkl)
{
description = "Logitech G915TKL Keyboard Device";
}
else
{
description = "Logitech G915 Keyboard Device";
}
name = controller->GetNameString();
vendor = "Logitech";
type = DEVICE_TYPE_KEYBOARD;
serial = controller->GetSerialString();
mode Direct;
Direct.name = "Direct";
Direct.value = LOGITECH_G915_MODE_DIRECT;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
mode Static;
Static.name = "Static";
Static.value = LOGITECH_G915_MODE_STATIC;
Static.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Static.colors_min = 1;
Static.colors_max = 1;
Static.color_mode = MODE_COLORS_MODE_SPECIFIC;
Static.colors.resize(1);
modes.push_back(Static);
mode Off;
Off.name = "Off";
Off.value = LOGITECH_G915_MODE_OFF;
Off.flags = 0;
Off.color_mode = MODE_COLORS_NONE;
modes.push_back(Off);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = LOGITECH_G915_MODE_BREATHING;
Breathing.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Breathing.colors_min = 1;
Breathing.colors_max = 1;
Breathing.color_mode = MODE_COLORS_MODE_SPECIFIC;
Breathing.colors.resize(1);
Breathing.brightness_min = 1;
Breathing.brightness_max = 100;
Breathing.brightness = 100;
Breathing.speed_min = LOGITECH_G915_SPEED_SLOWEST;
Breathing.speed_max = LOGITECH_G915_SPEED_FASTEST;
Breathing.speed = LOGITECH_G915_SPEED_NORMAL;
modes.push_back(Breathing);
mode Cycle;
Cycle.name = "Spectrum Cycle";
Cycle.value = LOGITECH_G915_MODE_CYCLE;
Cycle.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Cycle.color_mode = MODE_COLORS_NONE;
Cycle.speed_min = LOGITECH_G915_SPEED_SLOWEST;
Cycle.speed_max = LOGITECH_G915_SPEED_FASTEST;
Cycle.speed = LOGITECH_G915_SPEED_NORMAL;
Cycle.brightness_min = 1;
Cycle.brightness_max = 100;
Cycle.brightness = 100;
modes.push_back(Cycle);
mode Wave;
Wave.name = "Rainbow Wave";
Wave.value = LOGITECH_G915_MODE_WAVE;
Wave.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
//Wave.flags |= MODE_FLAG_HAS_DIRECTION_LR | MODE_FLAG_HAS_DIRECTION_UD | MODE_FLAG_HAS_DIRECTION_HV;
Wave.brightness_min = 1;
Wave.brightness_max = 100;
Wave.brightness = 100;
Wave.color_mode = MODE_COLORS_NONE;
Wave.direction = MODE_DIRECTION_HORIZONTAL | MODE_DIRECTION_RIGHT;
Wave.speed_min = LOGITECH_G915_SPEED_SLOWEST;
Wave.speed_max = LOGITECH_G915_SPEED_FASTEST;
Wave.speed = LOGITECH_G915_SPEED_NORMAL;
modes.push_back(Wave);
mode Ripple;
Ripple.name = "Reactive (Ripple)";
Ripple.value = LOGITECH_G915_MODE_RIPPLE;
Ripple.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED;
Ripple.colors_min = 1;
Ripple.colors_max = 1;
Ripple.color_mode = MODE_COLORS_MODE_SPECIFIC;
Ripple.colors.resize(1);
Ripple.speed_min = LOGITECH_G915_SPEED_RIPPLE_SLOW;
Ripple.speed_max = LOGITECH_G915_SPEED_RIPPLE_FAST;
Ripple.speed = LOGITECH_G915_SPEED_RIPPLE_NORMAL;
modes.push_back(Ripple);
SetupZones();
std::copy(colors.begin(), colors.end(),std::back_inserter(current_colors));
}
RGBController_LogitechG915::~RGBController_LogitechG915()
{
/*---------------------------------------------------------*\
| Delete the matrix map |
\*---------------------------------------------------------*/
for(unsigned int zone_index = 0; zone_index < zones.size(); zone_index++)
{
if(zones[zone_index].matrix_map != NULL)
{
delete zones[zone_index].matrix_map;
}
}
delete controller;
}
void RGBController_LogitechG915::SetupZones()
{
/*---------------------------------------------------------*\
| Set up zones |
\*---------------------------------------------------------*/
unsigned int total_led_count = 0;
for(unsigned int zone_idx = 0; zone_idx < 1; zone_idx++)
{
zone new_zone;
new_zone.name = zone_names[zone_idx];
new_zone.type = zone_types[zone_idx];
new_zone.leds_count = (is_tkl) ? tkl_led_count : full_led_count;
new_zone.leds_min = new_zone.leds_count;
new_zone.leds_max = new_zone.leds_count;
if(zone_types[zone_idx] == ZONE_TYPE_MATRIX)
{
new_zone.matrix_map = new matrix_map_type;
if(is_tkl)
{
new_zone.matrix_map->map = (unsigned int *)&matrix_map_tkl;
new_zone.matrix_map->height = 7;
new_zone.matrix_map->width = 20;
}
else
{
new_zone.matrix_map->map = (unsigned int *)&matrix_map;
new_zone.matrix_map->height = 7;
new_zone.matrix_map->width = 27;
}
}
else
{
new_zone.matrix_map = NULL;
}
zones.push_back(new_zone);
total_led_count += new_zone.leds_count;
}
for(unsigned int led_idx = 0; led_idx < total_led_count; led_idx++)
{
led new_led;
new_led.name = led_names[led_idx].name;
new_led.value = ( led_names[led_idx].zone << 8 ) + led_names[led_idx].idx;
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_LogitechG915::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_LogitechG915::DeviceUpdateLEDs()
{
std::map<RGBColor, std::vector<char>> ledsByColors;
std::vector<RGBColor> new_colors;
unsigned char zone = 0;
unsigned char idx = 0;
RGBColor colorkey;
/*---------------------------------------------------------*\
| Freeze colors array because prepare framebuffers |
| may take some time. |
\*---------------------------------------------------------*/
std::copy(colors.begin(), colors.end(),std::back_inserter(new_colors));
/*---------------------------------------------------------*\
| Get unique colors to create mode 1F and 6F frame_buffers |
\*---------------------------------------------------------*/
for(std::size_t led_idx = 0; led_idx < leds.size(); led_idx++)
{
zone = ( leds[led_idx].value >> 8 );
idx = ( leds[led_idx].value );
if(current_colors[led_idx]==new_colors[led_idx])
{
/*-------------------------------------------------*\
| Don't send if key color is not changed |
\*-------------------------------------------------*/
continue;
}
switch(zone)
{
case LOGITECH_G915_ZONE_MODE_GKEYS:
idx = ((idx & 0x00ff) + 0xb3);
break;
case LOGITECH_G915_ZONE_MODE_MODIFIERS:
idx = ((idx & 0x00ff) - 0x78);
break;
case LOGITECH_G915_ZONE_DIRECT_KEYBOARD:
idx = ((idx & 0x00ff) - 0x03);
break;
case LOGITECH_G915_ZONE_DIRECT_LOGO:
idx = ((idx & 0x00ff) + 0xd1);
break;
default:
idx = (idx & 0x00ff);
break;
}
colorkey = new_colors[led_idx];
if(ledsByColors.count(colorkey) == 0)
{
ledsByColors.insert(std::pair<RGBColor, std::vector<char>>(colorkey, {}));
}
ledsByColors[colorkey].push_back(idx);
}
/*-------------------------------------------------*\
| Nothing to do, we can skip rest of work |
\*-------------------------------------------------*/
if(ledsByColors.size() == 0)
{
return;
}
/*-----------------------------------------------------*\
| Copy the current color vector to avoid set keys that |
| have not changed |
\*-----------------------------------------------------*/
std::copy(new_colors.begin(), new_colors.end(),current_colors.begin());
std::vector<LittleFrame> little_frames;
std::vector<BigFrame> big_frames;
LittleFrame cur_small;
/*---------------------------------------------------------*\
| Create frame_buffers of type 1F (Little, up to 4 leds |
| per packet) and 6F (big, up to 13 leds per packet). |
\*---------------------------------------------------------*/
for(std::pair<const RGBColor, std::vector<char>>& x: ledsByColors)
{
for(size_t bi = 0; bi < x.second.size(); bi += BIG_FRAME_MAX_KEYS)
{
size_t n_colors_left = x.second.size() - bi;
/*-----------------------------------------------------*\
| For colors with more than 4 keys. Better to use big |
| (6F) packets to save USB transfers. |
\*-----------------------------------------------------*/
if(n_colors_left > 4)
{
BigFrame b_frame;
b_frame.color = x.first;
for(size_t i = 0; i < BIG_FRAME_MAX_KEYS && i < n_colors_left; i++)
{
b_frame.keys[i] = x.second[bi + i];
b_frame.len++;
}
big_frames.push_back(b_frame);
}
/*-----------------------------------------------------*\
| For colors with up to 4 keys. Use 1F packet to send |
| up to 4 colors-keys combinations per packet. |
\*-----------------------------------------------------*/
else
{
for(size_t li = 0; li < n_colors_left; li++)
{
cur_small.color_key[cur_small.len] = std::make_pair(x.first, x.second[bi + li]);
cur_small.len++;
/*-------------------------------*\
| Frame is full, create a new one |
\*-------------------------------*/
if(cur_small.len >= LITTLE_FRAME_MAX_KEYS)
{
little_frames.push_back(std::move(cur_small));
cur_small = LittleFrame();
}
}
}
}
}
/*-------------------------------*\
| Move leftover small frame |
\*-------------------------------*/
if(cur_small.len > 0)
{
little_frames.push_back(std::move(cur_small));
}
unsigned char frame_buffer[DATA_FRAME_SIZE];
for(const BigFrame& frame : big_frames)
{
size_t length = populate_big_frame_data(frame_buffer, frame);
controller->SetDirect(LOGITECH_G915_ZONE_FRAME_TYPE_BIG, frame_buffer, length);
}
for(const LittleFrame& frame : little_frames)
{
size_t length = populate_little_frame_data(frame_buffer, frame);
controller->SetDirect(LOGITECH_G915_ZONE_FRAME_TYPE_LITTLE, frame_buffer, length);
}
controller->Commit();
}
void RGBController_LogitechG915::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_LogitechG915::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_LogitechG915::DeviceUpdateMode()
{
/*---------------------------------------------------------*\
| Direct mode does not send a mode packet |
| Call UpdateLEDs to send direct packet |
\*---------------------------------------------------------*/
if(modes[active_mode].value == LOGITECH_G915_MODE_DIRECT)
{
/*-----------------------------------------------------*\
| Send real direct mode initialization. I used same |
| sequence as GHUB for screen capture. |
\*-----------------------------------------------------*/
controller->InitializeDirect();
/*-----------------------------------------------------*\
| Set one key to get direct mode engaged. |
\*-----------------------------------------------------*/
controller->SendSingleLed(0x29,0,0,0);
controller->Commit();
return;
}
controller->InitializeModeSet();
unsigned char red = 0;
unsigned char grn = 0;
unsigned char blu = 0;
if(modes[active_mode].color_mode == MODE_COLORS_MODE_SPECIFIC)
{
red = RGBGetRValue(modes[active_mode].colors[0]);
grn = RGBGetGValue(modes[active_mode].colors[0]);
blu = RGBGetBValue(modes[active_mode].colors[0]);
}
controller->SetMode(modes[active_mode].value, modes[active_mode].speed, modes[active_mode].brightness, red, grn, blu);
}
@@ -0,0 +1,38 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG915.h |
| |
| RGBController for Logitech G915 |
| |
| Cheerpipe 20 Mar 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechG915Controller.h"
class RGBController_LogitechG915 : public RGBController
{
public:
RGBController_LogitechG915(LogitechG915Controller* controller_ptr, bool tkl);
~RGBController_LogitechG915();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
bool is_tkl;
LogitechG915Controller* controller;
std::vector<RGBColor> current_colors;
};
@@ -0,0 +1,154 @@
/*---------------------------------------------------------*\
| LogitechG933Controller.cpp |
| |
| Driver for Logitech G933 |
| |
| Edbgon 21 Jun 2021 |
| Based on TheRogueZeta 31 Aug 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include <stdio.h>
#include <stdlib.h>
#include "LogitechG933Controller.h"
using namespace std::chrono_literals;
LogitechG933Controller::LogitechG933Controller(hid_device* dev_handle, const char* path, std::string dev_name)
{
dev = dev_handle;
location = path;
name = dev_name;
}
LogitechG933Controller::~LogitechG933Controller()
{
hid_close(dev);
}
std::string LogitechG933Controller::GetDeviceLocation()
{
return("HID: " + location);
}
std::string LogitechG933Controller::GetDeviceName()
{
return(name);
}
void LogitechG933Controller::SetDirectMode(uint8_t zone)
{
unsigned char usb_buf[LOGI_G933_LED_PACKET_SIZE];
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x04;
usb_buf[0x03] = 0xCA;
usb_buf[0x04] = zone;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
fail_retry_write(dev, usb_buf, LOGI_G933_LED_PACKET_SIZE);
}
void LogitechG933Controller::SetOffMode(uint8_t zone)
{
unsigned char usb_buf[LOGI_G933_LED_PACKET_SIZE];
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x04;
usb_buf[0x03] = 0x3F;
usb_buf[0x04] = zone;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
fail_retry_write(dev, usb_buf, LOGI_G933_LED_PACKET_SIZE);
}
void LogitechG933Controller::SendHeadsetMode
(
unsigned char zone,
unsigned char mode,
unsigned char red,
unsigned char green,
unsigned char blue
)
{
unsigned char usb_buf[LOGI_G933_LED_PACKET_SIZE];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Lighting Control |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x04;
/*-----------------------------------------------------*\
| This packet sets speaker into direct mode. This mode |
| is used by Lightsync Ambilight and Music Visualizer |
| realtime effect. |
\*-----------------------------------------------------*/
usb_buf[0x03] = 0x3A;
/*-----------------------------------------------------*\
| Set up mode and speed |
\*-----------------------------------------------------*/
usb_buf[0x04] = zone;
usb_buf[0x05] = mode;
/*-----------------------------------------------------*\
| And set up the colors |
\*-----------------------------------------------------*/
usb_buf[0x06] = red;
usb_buf[0x07] = green;
usb_buf[0x08] = blue;
if(mode == LOGITECH_G933_MODE_DIRECT) //G933 only has Direct Mode.
{
usb_buf[0x09] = 0x02;
}
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
fail_retry_write(dev, usb_buf, LOGI_G933_LED_PACKET_SIZE);
}
void LogitechG933Controller::fail_retry_write(hid_device *device, const unsigned char *data, size_t length)
{
unsigned char usb_buf_out[LOGI_G933_LED_PACKET_SIZE];
unsigned int write_max_retry = LOGI_G933_LED_COMMAND_SEND_RETRIES;
do
{
std::this_thread::sleep_for(1ms);
int ret = hid_write(device, data, length);
/*-------------------------------------------------------------------------------------*\
| HID write fails if a change led color and set volume command are sent at |
| the same time because RGB controller and volume control shares the same interface. |
\*-------------------------------------------------------------------------------------*/
if(ret == 20)
{
std::this_thread::sleep_for(1ms);
hid_read_timeout(dev, usb_buf_out, LOGI_G933_LED_PACKET_SIZE, 20);
break;
}
else
{
write_max_retry--;
std::this_thread::sleep_for(10ms);
}
}while (write_max_retry > 0);
}
@@ -0,0 +1,59 @@
/*---------------------------------------------------------*\
| LogitechG933Controller.h |
| |
| Driver for Logitech G933 |
| |
| Edbgon 21 Jun 2021 |
| Based on TheRogueZeta 31 Aug 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "RGBController.h"
#define LOGI_G933_LED_PACKET_SIZE 20
#define LOGI_G933_LED_COMMAND_SEND_RETRIES 3
enum
{
LOGITECH_G933_MODE_OFF = 0x00,
LOGITECH_G933_MODE_DIRECT = 0x01,
LOGITECH_G933_MODE_CYCLE = 0x02,
LOGITECH_G933_MODE_BREATHING = 0x03,
};
class LogitechG933Controller
{
public:
LogitechG933Controller(hid_device* dev_handle, const char* path, std::string dev_name);
~LogitechG933Controller();
std::string GetDeviceLocation();
std::string GetDeviceName();
void SetDirectMode(uint8_t zone);
void SetOffMode(uint8_t zone);
void SendHeadsetMode
(
unsigned char zone,
unsigned char mode,
unsigned char red,
unsigned char green,
unsigned char blue
);
private:
hid_device* dev;
std::string location;
std::string name;
void fail_retry_write(hid_device *device, const unsigned char *data, size_t length);
};
@@ -0,0 +1,134 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG933.cpp |
| |
| RGBController for Logitech G933 |
| |
| Edbgon 21 Jun 2021 |
| Based on TheRogueZeta 31 Aug 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_LogitechG933.h"
/**------------------------------------------------------------------*\
@name Logitech G933
@category Headset
@type USB
@save :x:
@direct :white_check_mark:
@effects :x:
@detectors DetectLogitechG933
@comment
\*-------------------------------------------------------------------*/
RGBController_LogitechG933::RGBController_LogitechG933(LogitechG933Controller* controller_ptr)
{
controller = controller_ptr;
name = controller->GetDeviceName();
vendor = "Logitech";
type = DEVICE_TYPE_HEADSET;
description = "Logitech G933 Lightsync Headset";
location = controller->GetDeviceLocation();
mode Off;
Off.name = "Off";
Off.value = LOGITECH_G933_MODE_OFF;
Off.flags = 0;
Off.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Off);
mode Direct;
Direct.name = "Direct";
Direct.value = LOGITECH_G933_MODE_DIRECT;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
SetupZones();
}
void RGBController_LogitechG933::SetupZones()
{
zone G933_logo;
G933_logo.name = "Logo";
G933_logo.type = ZONE_TYPE_SINGLE;
G933_logo.leds_min = 1;
G933_logo.leds_max = 1;
G933_logo.leds_count = 1;
G933_logo.matrix_map = NULL;
zones.push_back(G933_logo);
led G933_logo_led;
G933_logo_led.name = "Logo";
G933_logo_led.value = 0x00;
leds.push_back(G933_logo_led);
zone G933_strip;
G933_strip.name = "LED Strip";
G933_strip.type = ZONE_TYPE_SINGLE;
G933_strip.leds_min = 1;
G933_strip.leds_max = 1;
G933_strip.leds_count = 1;
G933_strip.matrix_map = NULL;
zones.push_back(G933_strip);
led G933_strip_led;
G933_strip_led.name = "Led Strip";
G933_strip_led.value = 0x01;
leds.push_back(G933_strip_led);
SetupColors();
}
void RGBController_LogitechG933::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_LogitechG933::DeviceUpdateLEDs()
{
for(std::size_t led_idx = 0; led_idx < leds.size(); led_idx++)
{
unsigned char red = RGBGetRValue(colors[led_idx]);
unsigned char grn = RGBGetGValue(colors[led_idx]);
unsigned char blu = RGBGetBValue(colors[led_idx]);
controller->SendHeadsetMode((unsigned char)leds[led_idx].value, modes[active_mode].value, red, grn, blu);
}
}
void RGBController_LogitechG933::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_LogitechG933::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_LogitechG933::DeviceUpdateMode()
{
for(std::size_t led_idx = 0; led_idx < leds.size(); led_idx++)
{
if(modes[active_mode].value == LOGITECH_G933_MODE_OFF)
{
controller->SetOffMode(leds[led_idx].value);
}
else
{
/*---------------------------------------------------------*\
| Required to "reset" RGB controller and start receiving |
| color in direct mode |
\*---------------------------------------------------------*/
controller->SetDirectMode(leds[led_idx].value);
}
}
DeviceUpdateLEDs();
}
@@ -0,0 +1,35 @@
/*---------------------------------------------------------*\
| RGBController_LogitechG933.h |
| |
| RGBController for Logitech G933 |
| |
| Edbgon 21 Jun 2021 |
| Based on TheRogueZeta 31 Aug 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechG933Controller.h"
class RGBController_LogitechG933 : public RGBController
{
public:
RGBController_LogitechG933(LogitechG933Controller* controller_ptr);
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
LogitechG933Controller* controller;
};
@@ -0,0 +1,187 @@
/*---------------------------------------------------------*\
| LogitechGLightsyncController.cpp |
| |
| Driver for Logitech Lightsync |
| |
| TheRogueZeta 21 Apr 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "LogitechGLightsyncController.h"
#include "StringUtils.h"
LogitechGLightsyncController::LogitechGLightsyncController(hid_device* dev_cmd_handle, hid_device *dev_handle, const char *path, unsigned char hid_dev_index, unsigned char hid_feature_index, unsigned char hid_fctn_ase_id, std::string dev_name)
{
dev = dev_handle;
cmd_dev = dev_cmd_handle;
location = path;
dev_index = hid_dev_index;
feature_index = hid_feature_index;
fctn_ase_id = hid_fctn_ase_id;
mutex = nullptr;
name = dev_name;
}
LogitechGLightsyncController::LogitechGLightsyncController(hid_device* dev_cmd_handle, hid_device *dev_handle, const char *path, unsigned char hid_dev_index, unsigned char hid_feature_index, unsigned char hid_fctn_ase_id, std::shared_ptr<std::mutex> mutex_ptr, std::string dev_name)
{
dev = dev_handle;
cmd_dev = dev_cmd_handle;
location = path;
dev_index = hid_dev_index;
feature_index = hid_feature_index;
fctn_ase_id = hid_fctn_ase_id;
mutex = mutex_ptr;
name = dev_name;
}
LogitechGLightsyncController::~LogitechGLightsyncController()
{
hid_close(dev);
}
std::string LogitechGLightsyncController::GetDeviceLocation()
{
return ("HID: " + location);
}
std::string LogitechGLightsyncController::GetNameString()
{
return(name);
}
std::string LogitechGLightsyncController::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev, serial_string, 128);
if(ret != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
void LogitechGLightsyncController::UpdateMouseLED(
unsigned char mode,
std::uint16_t speed,
unsigned char zone,
unsigned char red,
unsigned char green,
unsigned char blue,
unsigned char brightness
)
{
unsigned char usb_buf[20];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Lighting Control packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = dev_index;
usb_buf[0x02] = feature_index;
usb_buf[0x03] = fctn_ase_id;
usb_buf[0x04] = zone;
usb_buf[0x05] = mode;
usb_buf[0x06] = red;
usb_buf[0x07] = green;
usb_buf[0x08] = blue;
speed = 100 * speed;
if (mode == LOGITECH_G_LIGHTSYNC_MODE_STATIC)
{
usb_buf[0x09] = 0x02;
}
if (mode == LOGITECH_G_LIGHTSYNC_MODE_CYCLE)
{
usb_buf[0x0B] = speed >> 8;
usb_buf[0x0C] = speed & 0xFF;
usb_buf[0x0D] = brightness;
}
else if (mode == LOGITECH_G_LIGHTSYNC_MODE_BREATHING)
{
usb_buf[0x09] = speed >> 8;
usb_buf[0x0A] = speed & 0xFF;
usb_buf[0x0C] = brightness;
}
/*-----------------------------------------------------*\
| Send packet |
| This code has to be protected to avoid crashes when |
| this is called at the same time to change a powerplay |
| mat and its paired wireless mouse leds. It will |
| happen when using effects engines with high framerate |
\*-----------------------------------------------------*/
if(mutex)
{
std::lock_guard<std::mutex> guard(*mutex);
hid_write(dev, usb_buf, 20);
hid_read(dev, usb_buf, 20);
}
else
{
hid_write(dev, usb_buf, 20);
hid_read(dev, usb_buf, 20);
}
}
void LogitechGLightsyncController::SetDirectMode(bool direct)
{
unsigned char cmd_buf[7];
unsigned char usb_buf[20];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(cmd_buf, 0x00, sizeof(cmd_buf));
/*-----------------------------------------------------*\
| Set up Command Control packet |
\*-----------------------------------------------------*/
cmd_buf[0x00] = 0x10;
cmd_buf[0x01] = dev_index;
cmd_buf[0x02] = feature_index;
cmd_buf[0x03] = 0x8A;
cmd_buf[0x04] = 0x00;
cmd_buf[0x05] = 0x00;
/*-----------------------------------------------------*\
| If direct, disable save to flash |
\*-----------------------------------------------------*/
if(direct)
{
cmd_buf[0x04] = 0x01;
cmd_buf[0x05] = 0x01;
}
/*-----------------------------------------------------*\
| Send packet |
| This code has to be protected to avoid crashes when |
| this is called at the same time to change a powerplay |
| mat and its paired wireless mouse leds. It will |
| happen when using effects engines with high framerate |
\*-----------------------------------------------------*/
if(mutex)
{
std::lock_guard<std::mutex> guard(*mutex);
hid_write(cmd_dev, cmd_buf, 7);
hid_read(dev, usb_buf, 20);
}
else
{
hid_write(cmd_dev, cmd_buf, 7);
hid_read(dev, usb_buf, 20);
}
}
@@ -0,0 +1,91 @@
/*---------------------------------------------------------*\
| LogitechGLightsyncController.h |
| |
| Driver for Logitech Lightsync |
| |
| TheRogueZeta 21 Apr 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <memory>
#include <string>
#include <hidapi.h>
#include "RGBController.h"
enum
{
LOGITECH_G_LIGHTSYNC_MODE_OFF = 0x00,
LOGITECH_G_LIGHTSYNC_MODE_STATIC = 0x01,
LOGITECH_G_LIGHTSYNC_MODE_CYCLE = 0x02,
LOGITECH_G_LIGHTSYNC_MODE_BREATHING = 0x03,
};
/*---------------------------------------------------------------------------------------------*\
| Speed is 1000 for fast and 20000 for slow. |
| Values are multiplied by 100 later to give lots of GUI steps. |
\*---------------------------------------------------------------------------------------------*/
enum
{
LOGITECH_G_LIGHTSYNC_SPEED_SLOWEST = 0xC8, /* Slowest speed */
LOGITECH_G_LIGHTSYNC_SPEED_NORMAL = 0x32, /* Normal speed */
LOGITECH_G_LIGHTSYNC_SPEED_FASTEST = 0x0A, /* Fastest speed */
};
class LogitechGLightsyncController
{
public:
LogitechGLightsyncController
(
hid_device* ev_cmd_handle,
hid_device* ev_handle,
const char* ath,
unsigned char id_dev_index,
unsigned char id_feature_index,
unsigned char id_fctn_ase_id,
std::string ev_name
);
LogitechGLightsyncController
(
hid_device* dev_cmd_handle,
hid_device* dev_handle,
const char* path,
unsigned char hid_dev_index,
unsigned char hid_feature_index,
unsigned char hid_fctn_ase_id,
std::shared_ptr<std::mutex> mutex_ptr,
std::string dev_name
);
~LogitechGLightsyncController();
std::string GetDeviceLocation();
std::string GetNameString();
std::string GetSerialString();
void UpdateMouseLED
(
unsigned char mode,
unsigned short speed,
unsigned char zone,
unsigned char red,
unsigned char green,
unsigned char blue,
unsigned char brightness
);
void SetDirectMode(bool direct);
private:
hid_device* dev;
hid_device* cmd_dev;
std::string location;
std::string name;
unsigned char dev_index;
unsigned char feature_index;
unsigned char fctn_ase_id;
std::shared_ptr<std::mutex> mutex;
};
@@ -0,0 +1,163 @@
/*---------------------------------------------------------*\
| RGBController_LogitechGLightsync.cpp |
| |
| RGBController for Logitech Lightsync |
| |
| TheRogueZeta 21 Apr 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_LogitechGLightsync.h"
/**------------------------------------------------------------------*\
@name Logitech Lightsync Mouse
@category Mouse
@type USB
@save :robot:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectLogitechMouseG303, DetectLogitechMouseG403
@comment
\*-------------------------------------------------------------------*/
RGBController_LogitechGLightsync::RGBController_LogitechGLightsync(LogitechGLightsyncController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetNameString();
vendor = "Logitech";
type = DEVICE_TYPE_MOUSE;
description = "Logitech G Lightsync Mouse";
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
mode Off;
Off.name = "Off";
Off.value = LOGITECH_G_LIGHTSYNC_MODE_OFF;
Off.flags = 0;
Off.color_mode = MODE_COLORS_NONE;
modes.push_back(Off);
mode Direct;
Direct.name = "Direct";
Direct.value = 0xFF;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
mode Static;
Static.name = "Static";
Static.value = LOGITECH_G_LIGHTSYNC_MODE_STATIC;
Static.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Static.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Static);
mode Cycle;
Cycle.name = "Spectrum Cycle";
Cycle.value = LOGITECH_G_LIGHTSYNC_MODE_CYCLE;
Cycle.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Cycle.color_mode = MODE_COLORS_NONE;
Cycle.speed_min = LOGITECH_G_LIGHTSYNC_SPEED_SLOWEST;
Cycle.speed_max = LOGITECH_G_LIGHTSYNC_SPEED_FASTEST;
Cycle.speed = LOGITECH_G_LIGHTSYNC_SPEED_NORMAL;
Cycle.brightness_min = 0;
Cycle.brightness_max = 100;
Cycle.brightness = 100;
modes.push_back(Cycle);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = LOGITECH_G_LIGHTSYNC_MODE_BREATHING;
Breathing.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Breathing.color_mode = MODE_COLORS_PER_LED;
Breathing.speed_min = LOGITECH_G_LIGHTSYNC_SPEED_SLOWEST;
Breathing.speed_max = LOGITECH_G_LIGHTSYNC_SPEED_FASTEST;
Breathing.speed = LOGITECH_G_LIGHTSYNC_SPEED_NORMAL;
Breathing.brightness_min = 0;
Breathing.brightness_max = 100;
Breathing.brightness = 100;
modes.push_back(Breathing);
SetupZones();
}
RGBController_LogitechGLightsync::~RGBController_LogitechGLightsync()
{
delete controller;
}
void RGBController_LogitechGLightsync::SetupZones()
{
zone GLightsync_primary_zone;
GLightsync_primary_zone.name = "DPI";
GLightsync_primary_zone.type = ZONE_TYPE_SINGLE;
GLightsync_primary_zone.leds_min = 1;
GLightsync_primary_zone.leds_max = 1;
GLightsync_primary_zone.leds_count = 1;
GLightsync_primary_zone.matrix_map = NULL;
zones.push_back(GLightsync_primary_zone);
led GLightsync_primary_led;
GLightsync_primary_led.name = "DPI";
leds.push_back(GLightsync_primary_led);
zone GLightsync_logo_zone;
GLightsync_logo_zone.name = "Logo";
GLightsync_logo_zone.type = ZONE_TYPE_SINGLE;
GLightsync_logo_zone.leds_min = 1;
GLightsync_logo_zone.leds_max = 1;
GLightsync_logo_zone.leds_count = 1;
GLightsync_logo_zone.matrix_map = NULL;
zones.push_back(GLightsync_logo_zone);
led GLightsync_logo_led;
GLightsync_logo_led.name = "Logo";
leds.push_back(GLightsync_logo_led);
SetupColors();
}
void RGBController_LogitechGLightsync::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_LogitechGLightsync::DeviceUpdateLEDs()
{
UpdateZoneLEDs(0);
UpdateZoneLEDs(1);
}
void RGBController_LogitechGLightsync::UpdateZoneLEDs(int zone)
{
unsigned char red = RGBGetRValue(colors[zone]);
unsigned char grn = RGBGetGValue(colors[zone]);
unsigned char blu = RGBGetBValue(colors[zone]);
/*---------------------------------------------------------*\
| Replace direct mode with static when sending to controller|
\*---------------------------------------------------------*/
unsigned char temp_mode = (modes[active_mode].value != 0xFF) ? modes[active_mode].value : LOGITECH_G_LIGHTSYNC_MODE_STATIC;
controller->UpdateMouseLED(temp_mode, modes[active_mode].speed, zone, red, grn, blu, modes[active_mode].brightness);
}
void RGBController_LogitechGLightsync::UpdateSingleLED(int led)
{
UpdateZoneLEDs(led);
}
void RGBController_LogitechGLightsync::DeviceUpdateMode()
{
/*---------------------------------------------------------*\
| If direct mode is true, then sent the packet to put the |
| mouse in direct mode. This code will only be called when |
| we change modes as to not spam the device. |
\*---------------------------------------------------------*/
controller->SetDirectMode(modes[active_mode].value == 0xFF);
DeviceUpdateLEDs();
}
@@ -0,0 +1,35 @@
/*---------------------------------------------------------*\
| RGBController_LogitechGLightsync.h |
| |
| RGBController for Logitech Lightsync |
| |
| TheRogueZeta 21 Apr 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechGLightsyncController.h"
class RGBController_LogitechGLightsync : public RGBController
{
public:
RGBController_LogitechGLightsync(LogitechGLightsyncController* controller_ptr);
~RGBController_LogitechGLightsync();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
LogitechGLightsyncController* controller;
};
@@ -0,0 +1,149 @@
/*---------------------------------------------------------*\
| RGBController_LogitechGLightsync1zone.cpp |
| |
| RGBController for single zone Logitech Lightsync |
| |
| TheRogueZeta 21 Apr 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_LogitechGLightsync1zone.h"
/**------------------------------------------------------------------*\
@name Logitech Lightsync Mouse (1 Zone)
@category Mouse
@type USB
@save :robot:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectLogitechMouseG203, DetectLogitechMouseGPRO
@comment
\*-------------------------------------------------------------------*/
RGBController_LogitechGLightsync1zone::RGBController_LogitechGLightsync1zone(LogitechGLightsyncController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetNameString();
vendor = "Logitech";
type = DEVICE_TYPE_MOUSE;
description = "Logitech G Lightsync Mouse Single Zone";
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
mode Off;
Off.name = "Off";
Off.value = LOGITECH_G_LIGHTSYNC_MODE_OFF;
Off.flags = 0;
Off.color_mode = MODE_COLORS_NONE;
modes.push_back(Off);
mode Direct;
Direct.name = "Direct";
Direct.value = 0xFF;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
mode Static;
Static.name = "Static";
Static.value = LOGITECH_G_LIGHTSYNC_MODE_STATIC;
Static.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Static.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Static);
mode Cycle;
Cycle.name = "Spectrum Cycle";
Cycle.value = LOGITECH_G_LIGHTSYNC_MODE_CYCLE;
Cycle.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Cycle.color_mode = MODE_COLORS_NONE;
Cycle.speed_min = LOGITECH_G_LIGHTSYNC_SPEED_SLOWEST;
Cycle.speed_max = LOGITECH_G_LIGHTSYNC_SPEED_FASTEST;
Cycle.speed = LOGITECH_G_LIGHTSYNC_SPEED_NORMAL;
Cycle.brightness_min = 0;
Cycle.brightness_max = 100;
Cycle.brightness = 100;
modes.push_back(Cycle);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = LOGITECH_G_LIGHTSYNC_MODE_BREATHING;
Breathing.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Breathing.color_mode = MODE_COLORS_PER_LED;
Breathing.speed_min = LOGITECH_G_LIGHTSYNC_SPEED_SLOWEST;
Breathing.speed_max = LOGITECH_G_LIGHTSYNC_SPEED_FASTEST;
Breathing.speed = LOGITECH_G_LIGHTSYNC_SPEED_NORMAL;
Breathing.brightness_min = 0;
Breathing.brightness_max = 100;
Breathing.brightness = 100;
modes.push_back(Breathing);
SetupZones();
}
RGBController_LogitechGLightsync1zone::~RGBController_LogitechGLightsync1zone()
{
delete controller;
}
void RGBController_LogitechGLightsync1zone::SetupZones()
{
zone GLightsync_logo_zone;
GLightsync_logo_zone.name = "Logo";
GLightsync_logo_zone.type = ZONE_TYPE_SINGLE;
GLightsync_logo_zone.leds_min = 1;
GLightsync_logo_zone.leds_max = 1;
GLightsync_logo_zone.leds_count = 1;
GLightsync_logo_zone.matrix_map = NULL;
zones.push_back(GLightsync_logo_zone);
led GLightsync_logo_led;
GLightsync_logo_led.name = "Logo";
leds.push_back(GLightsync_logo_led);
SetupColors();
}
void RGBController_LogitechGLightsync1zone::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_LogitechGLightsync1zone::DeviceUpdateLEDs()
{
UpdateZoneLEDs(0);
}
void RGBController_LogitechGLightsync1zone::UpdateZoneLEDs(int zone)
{
unsigned char red = RGBGetRValue(colors[zone]);
unsigned char grn = RGBGetGValue(colors[zone]);
unsigned char blu = RGBGetBValue(colors[zone]);
/*---------------------------------------------------------*\
| Replace direct mode with static when sending to controller|
\*---------------------------------------------------------*/
unsigned char temp_mode = (modes[active_mode].value != 0xFF) ? modes[active_mode].value : LOGITECH_G_LIGHTSYNC_MODE_STATIC;
controller->UpdateMouseLED(temp_mode, modes[active_mode].speed, zone, red, grn, blu, modes[active_mode].brightness);
}
void RGBController_LogitechGLightsync1zone::UpdateSingleLED(int led)
{
UpdateZoneLEDs(led);
}
void RGBController_LogitechGLightsync1zone::DeviceUpdateMode()
{
/*---------------------------------------------------------*\
| If direct mode is true, then sent the packet to put the |
| mouse in direct mode. This code will only be called when |
| we change modes as to not spam the device. |
\*---------------------------------------------------------*/
controller->SetDirectMode(modes[active_mode].value == 0xFF);
DeviceUpdateLEDs();
}
@@ -0,0 +1,35 @@
/*---------------------------------------------------------*\
| RGBController_LogitechGLightsync1zone.h |
| |
| RGBController for single zone Logitech Lightsync |
| |
| TheRogueZeta 21 Apr 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechGLightsyncController.h"
class RGBController_LogitechGLightsync1zone : public RGBController
{
public:
RGBController_LogitechGLightsync1zone(LogitechGLightsyncController* controller_ptr);
~RGBController_LogitechGLightsync1zone();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
LogitechGLightsyncController* controller;
};
@@ -0,0 +1,143 @@
/*---------------------------------------------------------*\
| RGBController_LogitechGPowerPlay.cpp |
| |
| RGBController for Logitech G PowerPlay |
| |
| TheRogueZeta 31 Aug 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_LogitechGPowerPlay.h"
/**------------------------------------------------------------------*\
@name Logitech Powerplay Mat
@category Mousemat
@type USB
@save :x:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors
@comment
\*-------------------------------------------------------------------*/
RGBController_LogitechGPowerPlay::RGBController_LogitechGPowerPlay(LogitechGLightsyncController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetNameString();
vendor = "Logitech";
type = DEVICE_TYPE_MOUSEMAT;
description = "Logitech G PowerPlay Wireless Charging System";
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
mode Off;
Off.name = "Off";
Off.value = LOGITECH_G_LIGHTSYNC_MODE_OFF;
Off.flags = 0;
Off.color_mode = MODE_COLORS_NONE;
modes.push_back(Off);
mode Direct;
Direct.name = "Direct";
Direct.value = 0xFF;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
mode Static;
Static.name = "Static";
Static.value = LOGITECH_G_LIGHTSYNC_MODE_STATIC;
Static.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Static.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Static);
mode Cycle;
Cycle.name = "Spectrum Cycle";
Cycle.value = LOGITECH_G_LIGHTSYNC_MODE_CYCLE;
Cycle.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Cycle.color_mode = MODE_COLORS_NONE;
Cycle.speed_min = LOGITECH_G_LIGHTSYNC_SPEED_SLOWEST;
Cycle.speed_max = LOGITECH_G_LIGHTSYNC_SPEED_FASTEST;
Cycle.speed = LOGITECH_G_LIGHTSYNC_SPEED_NORMAL;
modes.push_back(Cycle);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = LOGITECH_G_LIGHTSYNC_MODE_BREATHING;
Breathing.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Breathing.color_mode = MODE_COLORS_PER_LED;
Breathing.speed_min = LOGITECH_G_LIGHTSYNC_SPEED_SLOWEST;
Breathing.speed_max = LOGITECH_G_LIGHTSYNC_SPEED_FASTEST;
Breathing.speed = LOGITECH_G_LIGHTSYNC_SPEED_NORMAL;
modes.push_back(Breathing);
SetupZones();
}
RGBController_LogitechGPowerPlay::~RGBController_LogitechGPowerPlay()
{
delete controller;
}
void RGBController_LogitechGPowerPlay::SetupZones()
{
zone GPowerPlay_logo_zone;
GPowerPlay_logo_zone.name = "Logo";
GPowerPlay_logo_zone.type = ZONE_TYPE_SINGLE;
GPowerPlay_logo_zone.leds_min = 1;
GPowerPlay_logo_zone.leds_max = 1;
GPowerPlay_logo_zone.leds_count = 1;
GPowerPlay_logo_zone.matrix_map = NULL;
zones.push_back(GPowerPlay_logo_zone);
led GPowerPlay_logo_led;
GPowerPlay_logo_led.name = "Logo";
leds.push_back(GPowerPlay_logo_led);
SetupColors();
}
void RGBController_LogitechGPowerPlay::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_LogitechGPowerPlay::DeviceUpdateLEDs()
{
UpdateZoneLEDs(0);
}
void RGBController_LogitechGPowerPlay::UpdateZoneLEDs(int zone)
{
unsigned char red = RGBGetRValue(colors[zone]);
unsigned char grn = RGBGetGValue(colors[zone]);
unsigned char blu = RGBGetBValue(colors[zone]);
/*---------------------------------------------------------*\
| Replace direct mode with static when sending to controller|
\*---------------------------------------------------------*/
unsigned char temp_mode = (modes[active_mode].value != 0xFF) ? modes[active_mode].value : LOGITECH_G_LIGHTSYNC_MODE_STATIC;
controller->UpdateMouseLED(temp_mode, modes[active_mode].speed, zone, red, grn, blu, /* Brightness */ 0x64);
}
void RGBController_LogitechGPowerPlay::UpdateSingleLED(int led)
{
UpdateZoneLEDs(led);
}
void RGBController_LogitechGPowerPlay::DeviceUpdateMode()
{
/*---------------------------------------------------------*\
| If direct mode is true, then sent the packet to put the |
| mouse in direct mode. This code will only be called when |
| we change modes as to not spam the device. |
\*---------------------------------------------------------*/
controller->SetDirectMode(modes[active_mode].value == 0xFF);
DeviceUpdateLEDs();
}
@@ -0,0 +1,35 @@
/*---------------------------------------------------------*\
| RGBController_LogitechGPowerPlay.h |
| |
| RGBController for Logitech G PowerPlay |
| |
| TheRogueZeta 31 Aug 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechGLightsyncController.h"
class RGBController_LogitechGPowerPlay : public RGBController
{
public:
RGBController_LogitechGPowerPlay(LogitechGLightsyncController* controller_ptr);
~RGBController_LogitechGPowerPlay();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
LogitechGLightsyncController* controller;
};
@@ -0,0 +1,192 @@
/*---------------------------------------------------------*\
| LogitechGProController.cpp |
| |
| Driver for Logitech G Pro keyboard |
| |
| sanchezzzs 20 Oct 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "LogitechGProKeyboardController.h"
#include "StringUtils.h"
LogitechGProKeyboardController::LogitechGProKeyboardController(hid_device* dev_handle_0x11, hid_device* dev_handle_0x12, std::string dev_name)
{
dev_pkt_0x11 = dev_handle_0x11;
dev_pkt_0x12 = dev_handle_0x12;
name = dev_name;
}
LogitechGProKeyboardController::~LogitechGProKeyboardController()
{
hid_close(dev_pkt_0x11);
hid_close(dev_pkt_0x12);
}
std::string LogitechGProKeyboardController::GetNameString()
{
return(name);
}
std::string LogitechGProKeyboardController::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev_pkt_0x11, serial_string, 128);
if(ret != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
void LogitechGProKeyboardController::Commit()
{
SendCommit();
}
void LogitechGProKeyboardController::SetDirect
(
unsigned char zone,
unsigned char frame_count,
unsigned char * frame_data
)
{
SendDirectFrame(zone, frame_count, frame_data);
}
void LogitechGProKeyboardController::SetMode
(
unsigned char mode,
unsigned short speed,
unsigned char red,
unsigned char green,
unsigned char blue
)
{
SendMode(LOGITECH_GPRO_ZONE_MODE_KEYBOARD, mode, speed, red, green, blue);
SendMode(LOGITECH_GPRO_ZONE_MODE_LOGO, mode, speed, red, green, blue);
SendCommit();
}
/*-------------------------------------------------------------------------------------------------*\
| Private packet sending functions. |
\*-------------------------------------------------------------------------------------------------*/
void LogitechGProKeyboardController::SendCommit()
{
unsigned char usb_buf[20];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Commit packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x0C;
usb_buf[0x03] = 0x5D;
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_pkt_0x11, usb_buf, 20);
hid_read(dev_pkt_0x11, usb_buf, 20);
}
void LogitechGProKeyboardController::SendDirectFrame
(
unsigned char zone,
unsigned char frame_count,
unsigned char * frame_data
)
{
unsigned char usb_buf[64];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Lighting Control packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x12;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x0C;
usb_buf[0x03] = 0x3D;
usb_buf[0x05] = zone;
usb_buf[0x07] = frame_count;
/*-----------------------------------------------------*\
| Copy in frame data |
\*-----------------------------------------------------*/
memcpy(&usb_buf[0x08], frame_data, frame_count * 4);
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_pkt_0x12, usb_buf, 64);
hid_read(dev_pkt_0x11, usb_buf, 20);
}
void LogitechGProKeyboardController::SendMode
(
unsigned char zone,
unsigned char mode,
unsigned short speed,
unsigned char red,
unsigned char green,
unsigned char blue
)
{
unsigned char usb_buf[20];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(usb_buf, 0x00, sizeof(usb_buf));
/*-----------------------------------------------------*\
| Set up Lighting Control packet |
\*-----------------------------------------------------*/
usb_buf[0x00] = 0x11;
usb_buf[0x01] = 0xFF;
usb_buf[0x02] = 0x0D;
usb_buf[0x03] = 0x3D;
usb_buf[0x04] = zone;
usb_buf[0x05] = mode;
usb_buf[0x06] = red;
usb_buf[0x07] = green;
usb_buf[0x08] = blue;
speed = 100 * speed;
if(mode == LOGITECH_GPRO_MODE_CYCLE)
{
usb_buf[0x0B] = speed >> 8;
usb_buf[0x0C] = speed & 0xFF;
usb_buf[0x0D] = 0x64;
}
else if(mode == LOGITECH_GPRO_MODE_BREATHING)
{
usb_buf[0x09] = speed >> 8;
usb_buf[0x0A] = speed & 0xFF;
usb_buf[0x0C] = 0x64;
}
/*-----------------------------------------------------*\
| Send packet |
\*-----------------------------------------------------*/
hid_write(dev_pkt_0x11, usb_buf, 20);
hid_read(dev_pkt_0x11, usb_buf, 20);
}
@@ -0,0 +1,102 @@
/*---------------------------------------------------------*\
| LogitechGProController.h |
| |
| Driver for Logitech G Pro keyboard |
| |
| sanchezzzs 20 Oct 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "RGBController.h"
enum
{
LOGITECH_GPRO_ZONE_MODE_KEYBOARD = 0x00,
LOGITECH_GPRO_ZONE_MODE_LOGO = 0x01,
};
enum
{
LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD = 0x01,
LOGITECH_GPRO_ZONE_DIRECT_MEDIA = 0x02,
LOGITECH_GPRO_ZONE_DIRECT_LOGO = 0x10,
LOGITECH_GPRO_ZONE_DIRECT_INDICATORS = 0x40,
};
enum
{
LOGITECH_GPRO_MODE_OFF = 0x00,
LOGITECH_GPRO_MODE_STATIC = 0x01,
LOGITECH_GPRO_MODE_BREATHING = 0x02,
LOGITECH_GPRO_MODE_CYCLE = 0x03,
LOGITECH_GPRO_MODE_WAVE = 0x04,
};
/*---------------------------------------------------------------------------------------------*\
| Speed is 1000 for fast and 20000 for slow. |
| Values are multiplied by 100 later to give lots of GUI steps. |
\*---------------------------------------------------------------------------------------------*/
enum
{
LOGITECH_GPRO_SPEED_SLOWEST = 0xC8, /* Slowest speed */
LOGITECH_GPRO_SPEED_NORMAL = 0x32, /* Normal speed */
LOGITECH_GPRO_SPEED_FASTEST = 0x0A, /* Fastest speed */
};
class LogitechGProKeyboardController
{
public:
LogitechGProKeyboardController(hid_device* dev_handle_0x11, hid_device* dev_handle_0x12, std::string dev_name);
~LogitechGProKeyboardController();
std::string GetNameString();
std::string GetSerialString();
void Commit();
void SetDirect
(
unsigned char zone,
unsigned char frame_count,
unsigned char * frame_data
);
void SetMode
(
unsigned char mode,
unsigned short speed,
unsigned char red,
unsigned char green,
unsigned char blue
);
private:
hid_device* dev_pkt_0x11;
hid_device* dev_pkt_0x12;
std::string name;
void SendDirectFrame
(
unsigned char zone,
unsigned char frame_count,
unsigned char * frame_data
);
void SendMode
(
unsigned char zone,
unsigned char mode,
unsigned short speed,
unsigned char red,
unsigned char green,
unsigned char blue
);
void SendCommit();
};
@@ -0,0 +1,379 @@
/*---------------------------------------------------------*\
| RGBController_LogitechGPro.cpp |
| |
| RGBController for Logitech G Pro keyboard |
| |
| sanchezzzs 20 Oct 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBControllerKeyNames.h"
#include "RGBController_LogitechGProKeyboard.h"
//0xFFFFFFFF indicates an unused entry in matrix
#define NA 0xFFFFFFFF
static unsigned int matrix_map[7][19] =
{ { 89, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 93, 92, NA, NA, 91, NA, 90 },
{ 37, NA, 54, 55, 56, 57, NA, 58, 59, 60, 61, NA, 62, 63, 64, 65, 66, 67, 68 },
{ 49, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, NA, 41, 42, 38, NA, 69, 70, 71 },
{ 39, NA, 16, 22, 4, 17, NA, 19, 24, 20, 8, 14, 15, 43, 44, 45, 72, 73, 74 },
{ 53, NA, 0, 18, 3, 5, NA, 6, 7, 9, 10, 11, 47, 48, 46, 36, NA, NA, NA },
{ 82, 79, 25, 23, 2, 21, NA, 1, NA, 13, 12, 50, 51, 52, 86, NA, NA, 78, NA },
{ 81, 84, 83, NA, NA, NA, NA, 40, NA, NA, NA, NA, 87, 88, 80, 85, 76, 77, 75 } };
static const char* zone_names[] =
{
ZONE_EN_KEYBOARD,
};
static zone_type zone_types[] =
{
ZONE_TYPE_MATRIX,
};
static const unsigned int zone_sizes[] =
{
94,
};
typedef struct
{
const char * name;
const unsigned char zone;
const unsigned char idx;
} led_type;
static const led_type led_names[] =
{
/* Key Label Zone, Index */
{ KEY_EN_A, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x04 },//00
{ KEY_EN_B, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x05 },
{ KEY_EN_C, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x06 },
{ KEY_EN_D, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x07 },
{ KEY_EN_E, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x08 },
{ KEY_EN_F, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x09 },
{ KEY_EN_G, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x0A },
{ KEY_EN_H, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x0B },
{ KEY_EN_I, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x0C },
{ KEY_EN_J, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x0D },
{ KEY_EN_K, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x0E },//10
{ KEY_EN_L, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x0F },
{ KEY_EN_M, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x10 },
{ KEY_EN_N, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x11 },
{ KEY_EN_O, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x12 },
{ KEY_EN_P, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x13 },
{ KEY_EN_Q, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x14 },
{ KEY_EN_R, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x15 },
{ KEY_EN_S, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x16 },
{ KEY_EN_T, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x17 },
{ KEY_EN_U, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x18 },//20
{ KEY_EN_V, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x19 },
{ KEY_EN_W, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x1A },
{ KEY_EN_X, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x1B },
{ KEY_EN_Y, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x1C },
{ KEY_EN_Z, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x1D },
{ KEY_EN_1, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x1E },
{ KEY_EN_2, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x1F },
{ KEY_EN_3, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x20 },
{ KEY_EN_4, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x21 },
{ KEY_EN_5, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x22 },//30
{ KEY_EN_6, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x23 },
{ KEY_EN_7, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x24 },
{ KEY_EN_8, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x25 },
{ KEY_EN_9, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x26 },
{ KEY_EN_0, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x27 },
{ KEY_EN_ANSI_ENTER, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x28 },
{ KEY_EN_ESCAPE, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x29 },
{ KEY_EN_BACKSPACE, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x2A },
{ KEY_EN_TAB, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x2B },
{ KEY_EN_SPACE, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x2C },//40
{ KEY_EN_MINUS, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x2D },
{ KEY_EN_EQUALS, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x2E },
{ KEY_EN_LEFT_BRACKET, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x2F },
{ KEY_EN_RIGHT_BRACKET, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x30 },
{ KEY_EN_ANSI_BACK_SLASH, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x31 },//ANSI only
{ KEY_EN_POUND, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x32 },//ISO only
{ KEY_EN_SEMICOLON, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x33 },
{ KEY_EN_QUOTE, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x34 },
{ KEY_EN_BACK_TICK, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x35 },
{ KEY_EN_COMMA, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x36 },//50
{ KEY_EN_PERIOD, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x37 },
{ KEY_EN_FORWARD_SLASH, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x38 },
{ KEY_EN_CAPS_LOCK, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x39 },
{ KEY_EN_F1, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x3A },
{ KEY_EN_F2, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x3B },
{ KEY_EN_F3, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x3C },
{ KEY_EN_F4, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x3D },
{ KEY_EN_F5, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x3E },
{ KEY_EN_F6, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x3F },
{ KEY_EN_F7, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x40 },//60
{ KEY_EN_F8, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x41 },
{ KEY_EN_F9, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x42 },
{ KEY_EN_F10, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x43 },
{ KEY_EN_F11, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x44 },
{ KEY_EN_F12, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x45 },
{ KEY_EN_PRINT_SCREEN, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x46 },
{ KEY_EN_SCROLL_LOCK, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x47 },
{ KEY_EN_PAUSE_BREAK, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x48 },
{ KEY_EN_INSERT, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x49 },
{ KEY_EN_HOME, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x4A },//70
{ KEY_EN_PAGE_UP, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x4B },
{ KEY_EN_DELETE, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x4C },
{ KEY_EN_END, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x4D },
{ KEY_EN_PAGE_DOWN, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x4E },
{ KEY_EN_RIGHT_ARROW, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x4F },
{ KEY_EN_LEFT_ARROW, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x50 },
{ KEY_EN_DOWN_ARROW, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x51 },
{ KEY_EN_UP_ARROW, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x52 },
{ KEY_EN_ISO_BACK_SLASH, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x64 },//ISO only
{ KEY_EN_MENU, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0x65 },//80
{ KEY_EN_LEFT_CONTROL, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0xE0 },
{ KEY_EN_LEFT_SHIFT, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0xE1 },
{ KEY_EN_LEFT_ALT, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0xE2 },
{ KEY_EN_LEFT_WINDOWS, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0xE3 },
{ KEY_EN_RIGHT_CONTROL, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0xE4 },
{ KEY_EN_RIGHT_SHIFT, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0xE5 },
{ KEY_EN_RIGHT_ALT, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0xE6 },
{ KEY_EN_RIGHT_FUNCTION, LOGITECH_GPRO_ZONE_DIRECT_KEYBOARD, 0xE7 },
{ "Logo", LOGITECH_GPRO_ZONE_DIRECT_LOGO, 0x01 },
{ "Lighting", LOGITECH_GPRO_ZONE_DIRECT_INDICATORS, 0x01 },//90
{ "Game Mode", LOGITECH_GPRO_ZONE_DIRECT_INDICATORS, 0x02 },
{ "Caps Lock Indicator", LOGITECH_GPRO_ZONE_DIRECT_INDICATORS, 0x03 },
{ "Scroll Lock Indicator", LOGITECH_GPRO_ZONE_DIRECT_INDICATORS, 0x04 },//93
};
/**------------------------------------------------------------------*\
@name Logitech G Pro
@category Keyboard
@type USB
@save :robot:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectLogitechKeyboardGPro
@comment
\*-------------------------------------------------------------------*/
RGBController_LogitechGProKeyboard::RGBController_LogitechGProKeyboard(LogitechGProKeyboardController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetNameString();
vendor = "Logitech";
type = DEVICE_TYPE_KEYBOARD;
description = "Logitech Keyboard Device";
serial = controller->GetSerialString();
mode Direct;
Direct.name = "Direct";
Direct.value = 0xFFFF;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
mode Static;
Static.name = "Static";
Static.value = LOGITECH_GPRO_MODE_STATIC;
Static.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR;
Static.colors_min = 1;
Static.colors_max = 1;
Static.color_mode = MODE_COLORS_MODE_SPECIFIC;
Static.colors.resize(1);
modes.push_back(Static);
mode Off;
Off.name = "Off";
Off.value = LOGITECH_GPRO_MODE_OFF;
Off.flags = 0;
Off.color_mode = MODE_COLORS_NONE;
modes.push_back(Off);
mode Cycle;
Cycle.name = "Cycle";
Cycle.value = LOGITECH_GPRO_MODE_CYCLE;
Cycle.flags = MODE_FLAG_HAS_SPEED;
Cycle.color_mode = MODE_COLORS_NONE;
Cycle.speed_min = LOGITECH_GPRO_SPEED_SLOWEST;
Cycle.speed_max = LOGITECH_GPRO_SPEED_FASTEST;
Cycle.speed = LOGITECH_GPRO_SPEED_NORMAL;
modes.push_back(Cycle);
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = LOGITECH_GPRO_MODE_BREATHING;
Breathing.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_SPEED;
Breathing.colors_min = 1;
Breathing.colors_max = 1;
Breathing.color_mode = MODE_COLORS_MODE_SPECIFIC;
Breathing.colors.resize(1);
Breathing.speed_min = LOGITECH_GPRO_SPEED_SLOWEST;
Breathing.speed_max = LOGITECH_GPRO_SPEED_FASTEST;
Breathing.speed = LOGITECH_GPRO_SPEED_NORMAL;
modes.push_back(Breathing);
SetupZones();
}
RGBController_LogitechGProKeyboard::~RGBController_LogitechGProKeyboard()
{
/*---------------------------------------------------------*\
| Delete the matrix map |
\*---------------------------------------------------------*/
for(unsigned int zone_index = 0; zone_index < zones.size(); zone_index++)
{
if(zones[zone_index].matrix_map != NULL)
{
delete zones[zone_index].matrix_map;
}
}
delete controller;
}
void RGBController_LogitechGProKeyboard::SetupZones()
{
/*---------------------------------------------------------*\
| Set up zones |
\*---------------------------------------------------------*/
unsigned int total_led_count = 0;
for(unsigned int zone_idx = 0; zone_idx < 1; zone_idx++)
{
zone new_zone;
new_zone.name = zone_names[zone_idx];
new_zone.type = zone_types[zone_idx];
new_zone.leds_min = zone_sizes[zone_idx];
new_zone.leds_max = zone_sizes[zone_idx];
new_zone.leds_count = zone_sizes[zone_idx];
if(zone_types[zone_idx] == ZONE_TYPE_MATRIX)
{
new_zone.matrix_map = new matrix_map_type;
new_zone.matrix_map->height = 7;
new_zone.matrix_map->width = 19;
new_zone.matrix_map->map = (unsigned int *)&matrix_map;
}
else
{
new_zone.matrix_map = NULL;
}
zones.push_back(new_zone);
total_led_count += zone_sizes[zone_idx];
}
for(unsigned int led_idx = 0; led_idx < total_led_count; led_idx++)
{
led new_led;
new_led.name = led_names[led_idx].name;
new_led.value = ( led_names[led_idx].zone << 8 ) + led_names[led_idx].idx;
leds.push_back(new_led);
}
SetupColors();
}
void RGBController_LogitechGProKeyboard::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_LogitechGProKeyboard::DeviceUpdateLEDs()
{
#define MAX_FRAMES_PER_PACKET 0x0E
unsigned char frame_buf[MAX_FRAMES_PER_PACKET * 4];
unsigned char frame_cnt = 0;
unsigned char prev_zone = 0;
unsigned char zone = 0;
unsigned char idx = 0;
/*---------------------------------------------------------*\
| TODO: Send packets with multiple LED frames |
\*---------------------------------------------------------*/
for(std::size_t led_idx = 0; led_idx < leds.size(); led_idx++)
{
zone = ( leds[led_idx].value >> 8 );
idx = ( leds[led_idx].value & 0xFF );
if((zone != prev_zone) && (frame_cnt != 0))
{
controller->SetDirect(prev_zone, frame_cnt, frame_buf);
frame_cnt = 0;
}
frame_buf[(frame_cnt * 4) + 0] = idx;
frame_buf[(frame_cnt * 4) + 1] = RGBGetRValue(colors[led_idx]);
frame_buf[(frame_cnt * 4) + 2] = RGBGetGValue(colors[led_idx]);
frame_buf[(frame_cnt * 4) + 3] = RGBGetBValue(colors[led_idx]);
frame_cnt++;
prev_zone = zone;
if(frame_cnt == MAX_FRAMES_PER_PACKET)
{
controller->SetDirect(prev_zone, frame_cnt, frame_buf);
frame_cnt = 0;
}
}
if(frame_cnt != 0)
{
controller->SetDirect(prev_zone, frame_cnt, frame_buf);
}
controller->Commit();
}
void RGBController_LogitechGProKeyboard::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_LogitechGProKeyboard::UpdateSingleLED(int led)
{
unsigned char frame[4];
unsigned char zone;
unsigned char idx;
zone = ( leds[led].value >> 8 );
idx = ( leds[led].value & 0xFF );
frame[0] = idx;
frame[1] = RGBGetRValue(colors[led]);
frame[2] = RGBGetGValue(colors[led]);
frame[3] = RGBGetBValue(colors[led]);
controller->SetDirect(zone, 1, frame);
controller->Commit();
}
void RGBController_LogitechGProKeyboard::DeviceUpdateMode()
{
/*---------------------------------------------------------*\
| Direct mode does not send a mode packet |
| Call UpdateLEDs to send direct packet |
\*---------------------------------------------------------*/
if(active_mode == 0xFFFF)
{
UpdateLEDs();
return;
}
unsigned char red = 0;
unsigned char grn = 0;
unsigned char blu = 0;
if(modes[active_mode].color_mode == MODE_COLORS_MODE_SPECIFIC)
{
red = RGBGetRValue(modes[active_mode].colors[0]);
grn = RGBGetGValue(modes[active_mode].colors[0]);
blu = RGBGetBValue(modes[active_mode].colors[0]);
}
controller->SetMode(modes[active_mode].value, modes[active_mode].speed, red, grn, blu);
}
@@ -0,0 +1,35 @@
/*---------------------------------------------------------*\
| RGBController_LogitechGPro.h |
| |
| RGBController for Logitech G Pro keyboard |
| |
| sanchezzzs 20 Oct 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechGProKeyboardController.h"
class RGBController_LogitechGProKeyboard : public RGBController
{
public:
RGBController_LogitechGProKeyboard(LogitechGProKeyboardController* controller_ptr);
~RGBController_LogitechGProKeyboard();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
LogitechGProKeyboardController* controller;
};
@@ -0,0 +1,874 @@
/*---------------------------------------------------------*\
| LogitechHIDPP20Controller.h |
| |
| Unified Logitech HID++ 2.0 controller |
| |
| Uses feature discovery (IRoot 0x0000) to dynamically |
| determine device capabilities and adapt to any HID++ |
| 2.0 device with RGB lighting support. |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <vector>
#include <map>
#include <mutex>
#include <memory>
#include <thread>
#include <atomic>
#include <functional>
#include <chrono>
#include <deque>
#include <condition_variable>
#include <hidapi.h>
#include "RGBController.h"
#include "LogitechProtocolCommon.h"
/*-----------------------------------------------------*\
| HID++ 2.0 Feature Page IDs |
\*-----------------------------------------------------*/
#define HIDPP20_FEAT_IROOT 0x0000
#define HIDPP20_FEAT_FEATURE_SET 0x0001
#define HIDPP20_FEAT_DEVICE_NAME_TYPE 0x0005
#define HIDPP20_FEAT_ONBOARD_PROFILES 0x8100
#define HIDPP20_FEAT_PROFILE_MANAGEMENT 0x8101
#define HIDPP20_FEAT_FIRMWARE_INFO 0x0003
#define HIDPP20_FEAT_CENTPPBRIDGE 0x0003 /* same ID, different meaning on Centurion */
#define HIDPP20_FEAT_COLOR_LED_EFFECTS 0x8070
#define HIDPP20_FEAT_RGB_EFFECTS 0x8071
#define HIDPP20_FEAT_PER_KEY_LIGHTING_V1 0x8080
#define HIDPP20_FEAT_PER_KEY_LIGHTING_V2 0x8081
#define HIDPP20_FEAT_KEYBOARD_LAYOUT 0x4540
#define HIDPP20_FEAT_DISABLE_KEYS_BY_USAGE 0x4522
#define HIDPP20_FEAT_CENTURION_RGB 0x0600
#define HIDPP20_FEAT_HEADSET_RGB_HOSTMODE 0x0620
#define HIDPP20_FEAT_CENTURION_DEVICE_INFO 0x0100
#define HIDPP20_FEAT_CENTURION_DEVICE_NAME 0x0101
#define HIDPP20_FEAT_UNIFIED_BATTERY 0x1004
#define HIDPP20_FEAT_WIRELESS_STATUS 0x1D4B
/*-----------------------------------------------------*\
| HID++ 2.0 Function IDs (byte 3 high nibble) |
| Function ID is shifted left 4 bits, low nibble = swID |
\*-----------------------------------------------------*/
/*-----------------------------------------------------*\
| HID++ Software ID — identifies our responses. |
| Must avoid: 0x00 (firmware), 0x01 (vendor app), |
| 0x02-0x0F (Solaar cycles these). |
| There are only 16 values (4-bit field), and all are |
| claimed. We pick a fixed value and will coordinate |
| with Solaar to exclude it from its cycle. |
\*-----------------------------------------------------*/
#define HIDPP20_SW_ID 0x07
/*-----------------------------------------------------*\
| Feature 0x8071 functions |
\*-----------------------------------------------------*/
#define FN_8071_GET_INFO 0x00
#define FN_8071_SET_EFFECT 0x10
#define FN_8071_SET_PATTERN 0x20
#define FN_8071_NV_CONFIG 0x30
#define FN_8071_BIN_INFO 0x40
#define FN_8071_SW_CONTROL 0x50
#define FN_8071_SYNC 0x60
#define FN_8071_PWR_CONFIG 0x70
#define FN_8071_PWR_MODE 0x80
/*-----------------------------------------------------*\
| Feature 0x8081 functions |
\*-----------------------------------------------------*/
#define FN_8081_GET_INFO 0x00
#define FN_8081_SET_INDIVIDUAL 0x10
#define FN_8081_SET_CONSECUTIVE 0x20
#define FN_8081_SET_DELTA_5BIT 0x30
#define FN_8081_SET_DELTA_4BIT 0x40
#define FN_8081_SET_RANGE 0x50
#define FN_8081_SET_SINGLE_VALUE 0x60
#define FN_8081_FRAME_END 0x70
/*-----------------------------------------------------*\
| Feature 0x0620 functions (headset RGB hostmode) |
\*-----------------------------------------------------*/
#define FN_0620_GET_INFO 0x00
#define FN_0620_GET_RGB_ZONE_INFO 0x10
#define FN_0620_SET_INDIVIDUAL_RGB_ZONES 0x20
#define FN_0620_SET_CONSECUTIVE_RGB_ZONES 0x30
#define FN_0620_SET_RANGE_RGB_ZONES 0x40
#define FN_0620_SET_RGB_ZONES_SINGLE_VALUE 0x50
#define FN_0620_FRAME_END 0x60
#define FN_0620_GET_HOST_MODE_STATE 0x70
#define FN_0620_SET_HOST_MODE_STATE 0x80
/*-----------------------------------------------------*\
| Feature 0x8100 functions |
\*-----------------------------------------------------*/
#define FN_8100_SET_ONBOARD_MODE 0x10
#define FN_8100_GET_ONBOARD_MODE 0x20
/*-----------------------------------------------------*\
| Feature 0x8101 functions |
\*-----------------------------------------------------*/
#define FN_8101_GET_SET_MODE 0x60
#define FN_8101_LOAD 0x80 // load(partition, sector, size)
#define FN_8101_READBUFFER 0xC0 // readBuffer(offset)
/*-----------------------------------------------------*\
| Zone cluster effect entry |
\*-----------------------------------------------------*/
struct HIDPP20Effect
{
uint8_t index;
uint16_t effect_id;
uint16_t capabilities;
uint16_t default_period;
};
/*-----------------------------------------------------*\
| Zone cluster info from 0x8071 GetRgbClusterInfo |
\*-----------------------------------------------------*/
struct HIDPP20ZoneCluster
{
uint8_t index;
uint16_t location;
uint8_t effect_count;
std::vector<HIDPP20Effect> effects;
};
/*-----------------------------------------------------*\
| Per-model device quirks — behavioral differences that |
| can't be detected via feature probing. |
\*-----------------------------------------------------*/
enum HIDPP20DeviceQuirks : uint32_t
{
HIDPP20_QUIRK_NONE = 0,
HIDPP20_QUIRK_FADE_ACCEPTS_WRITES = (1 << 0), // firmware accepts host frames during sleep fade without waking
};
struct HIDPP20DeviceQuirkEntry
{
uint16_t pid_wireless;
uint16_t pid_wired;
uint32_t quirks;
};
/*---------------------------------------------------------*\
| Default: suppress frames while SLEEPING. Safe everywhere |
| — it cannot wake a device that treats writes as activity. |
| Devices listed here opt out of suppression because their |
| firmware accepts writes during the fade without |
| cancelling sleep. |
\*---------------------------------------------------------*/
static constexpr HIDPP20DeviceQuirkEntry HIDPP20_DEVICE_QUIRK_TABLE[] =
{
{ 0x40B4, 0xC355, HIDPP20_QUIRK_FADE_ACCEPTS_WRITES }, // G515 LS TKL
};
/*-----------------------------------------------------*\
| Device capabilities discovered via feature probing |
\*-----------------------------------------------------*/
struct HIDPP20DeviceCapabilities
{
std::string device_name;
uint8_t device_type;
std::string firmware_version;
std::string serial_number;
std::string unit_id; // stable hardware ID (from FirmwareInfo fn0)
uint16_t pid_wireless; // wireless virtual PID (from FirmwareInfo fn0)
uint16_t pid_wired; // wired USB PID (from FirmwareInfo fn0)
uint32_t quirks; // resolved from HIDPP20_DEVICE_QUIRK_TABLE after PID discovery
/*--------------------------------------------------*\
| Complete feature map (feature_id → runtime index) |
| Built once by EnumerateFeatures, used by all |
| subsequent GetFeatureIndex lookups (no wire). |
\*--------------------------------------------------*/
std::map<uint16_t, uint8_t> feature_map;
std::map<uint16_t, uint8_t> feature_versions; /* feature_id -> version byte */
bool feature_map_complete; // true after bulk enumeration
/*-------------------------------------------------*\
| Feature indices (0 = not supported) |
\*-------------------------------------------------*/
uint8_t idx_onboard_profiles;
uint8_t idx_profile_management;
uint8_t idx_rgb_effects;
uint8_t idx_headset_rgb_hostmode; /* 0x0620 — Centurion headset RGB */
uint8_t idx_perkey_v2;
uint8_t idx_perkey_v1;
uint8_t idx_wireless_status;
uint8_t idx_disable_keys_by_usage; /* 0x4522 — keyboard-family handshake */
uint16_t rgb_feature_page;
/*--------------------------------------------------*\
| Resolved function IDs for the RGB effects feature |
| Varies between 0x8070, 0x8071, 0x0600 |
\*--------------------------------------------------*/
uint8_t fn_set_effect;
uint8_t fn_sw_control;
uint8_t fn_pwr_config;
uint8_t fn_pwr_mode;
bool has_power_mgmt;
bool sw_control_simple;
/*--------------------------------------------------*\
| Persistent NV settings read from RGBEffects fn3 |
| (FN_8071_NV_CONFIG). Capability 0x0020 is the |
| sleep ramp / off-ramp transition (enabled + |
| ramp_seconds). |
\*--------------------------------------------------*/
bool nv_sleep_ramp_known;
bool nv_sleep_ramp_enabled;
uint8_t nv_sleep_ramp_seconds;
/*---------------------------------------------------*\
| Device-firmware effect cards (0x8071 fn0 |
| GetEffectSpecificInfo). Populated by |
| DiscoverEffectCards at feature-discovery time. |
| |
| has_effect_cards — probe returned a valid response |
| for firmware card 0 page 1 (no InvalidArgument). |
| effect_card_template[0..1] — device-wide constant |
| bytes read from that response at data[10..11]. |
| Echoed back into prep1 of DoObservedPerKeyPrep |
| at SetEffectByIndex params[6..7]. |
| |
| Gate for the observed per-key prep is now |
| has_effect_cards — replaces the earlier |
| "effects.size() < 5" heuristic, which was a proxy |
| that correlated with "has cards" on the devices we |
| happened to know but had no principled meaning. |
\*---------------------------------------------------*/
bool has_effect_cards;
uint8_t effect_card_template[2];
/*-------------------------------------------------*\
| Discovered zone and LED data |
\*-------------------------------------------------*/
std::vector<HIDPP20ZoneCluster> zone_clusters;
std::vector<uint16_t> perkey_zone_ids;
std::vector<uint8_t> headset_rgb_hostmode_zone_ids; /* 0x0620 fn1 result */
bool has_perkey;
bool has_zone_effects;
bool is_headset_rgb_hostmode; /* 0x0620 path selected */
bool has_numpad;
uint8_t keyboard_layout_code;
};
/*------------------------------------------------------*\
| Transport type — determines wire framing |
\*------------------------------------------------------*/
enum HIDPP20TransportType
{
HIDPP20_TRANSPORT_STANDARD, // 0xFF00/0xFF43: report IDs 0x10/0x11, 7/20 bytes
HIDPP20_TRANSPORT_CENTURION // 0xFFA0: report ID 0x51 or 0x50, 64 bytes,
// with CPL framing and CentPPBridge sub-device routing
};
/*------------------------------------------------------*\
| Transport layer — abstracts wire format differences |
| |
| Standard HID++ and Centurion both carry the same |
| feature/function/data payload, but with different |
| report framing. This struct holds transport state |
| so SendMessage/ReadMessage can adapt. |
\*------------------------------------------------------*/
struct HIDPP20Transport
{
HIDPP20TransportType type;
uint16_t usage_page; // 0xFF00, 0xFF43, or 0xFFA0
uint8_t report_id; // 0x10/0x11 for standard, 0x51/0x50 for Centurion
bool addressed; // Centurion 0x50 has device address byte
uint8_t device_address; // Centurion 0x50: device address (e.g., 0x23)
uint8_t bridge_feat_idx; // CentPPBridge feature index on parent (0 if N/A)
uint8_t sub_device_id; // CentPPBridge sub-device ID (typically 0)
uint16_t bridge_mtu; // CentPPBridge MTU from getConnectionInfo:
// 0 = no sub-device, sendFragment will fail
// >0 = sub-device present, payload size in bytes
};
/*-----------------------------------------------------*\
| Power management state machine |
| Matches Solaar's RGBPowerManager states |
\*-----------------------------------------------------*/
enum HIDPP20PowerState
{
HIDPP20_POWER_ACTIVE = 0,
HIDPP20_POWER_DIMMING = 1,
HIDPP20_POWER_IDLE = 2,
HIDPP20_POWER_SLEEPING = 3,
};
/*-----------------------------------------------------*\
| Parsed HID++ message for the response queue |
\*-----------------------------------------------------*/
struct HIDPP20RawMessage
{
uint8_t feat;
uint8_t func;
uint8_t data[60];
int result;
};
/*------------------------------------------------------*\
| Per-key write tracking. SendPerKeyData is fire-and- |
| forget at the wire layer; we track which zones each |
| outstanding write covers so PerKeyFrameEnd can match |
| ACKs back by FIFO order and report which zones the |
| firmware actually committed. |
\*------------------------------------------------------*/
struct OutstandingPerKeyWrite
{
uint8_t function; // FN_8081_* (high nibble carries the type)
std::vector<uint8_t> zone_ids; // zones covered by this packet
};
/*-----------------------------------------------------*\
| Result of a per-key frame commit. The caller uses |
| these to update its delta-tracking state: |
| - frame_end_acked: did the FrameEnd packet ACK? |
| - acked_zones: zones whose write packet ACKed |
| - attempted_zones: every zone written this frame |
\*-----------------------------------------------------*/
struct PerKeyFrameResult
{
bool frame_end_acked;
std::vector<uint8_t> acked_zones;
std::vector<uint8_t> attempted_zones;
};
/*-------------------------------------------------------*\
| Retry policy for SendAcked. |
| |
| Controls the send/read/retry loop for a single HID++ |
| request. Three canned policies cover all use cases: |
| |
| Reliable: probe/discovery/set/get (~6s worst case) |
| FrameEnd: per-key commit gate (~230ms worst) |
| Streaming: per-key write inside the animation loop |
| (~80ms worst) |
| |
| backoff_ms[i] is the delay applied BEFORE the i-th |
| attempt. backoff_ms[0] is normally 0 (no delay before |
| the first send). The schedule mirrors the firmware's |
| own event burst pattern (63→125→250→500→1000→2000ms, |
| "catch at least one of N"). |
\*-------------------------------------------------------*/
struct HIDPP20RetryPolicy
{
const uint16_t* backoff_ms; // schedule[i] = delay before attempt i
uint8_t attempts; // length of backoff_ms (>=1)
uint16_t read_window_ms; // per-attempt read budget
bool flush_before; // flush response queue at call start
bool retry_on_busy; // BUSY (0x08) -> retry the send
const char* name; // for logging ("reliable", etc.)
};
/*------------------------------------------------------*\
| Canned backoff schedules. |
\*------------------------------------------------------*/
static constexpr uint16_t HIDPP20_BACKOFF_RELIABLE[] =
{ 0, 63, 125, 250, 500, 1000, 2000 };
static constexpr uint16_t HIDPP20_BACKOFF_PROBE[] =
{ 0, 100 };
/*------------------------------------------------------*\
| SW-control reclaim backoff. Used by ReconnectDevice |
| to retry the claim+push sequence after a wireless |
| reconnect, racing the firmware boot animation. The |
| vendor app typically lands control in ~50ms; this |
| schedule fits |
| 6 attempts inside ~620ms so the animation never gets |
| a chance to become visible. |
\*------------------------------------------------------*/
static constexpr uint16_t HIDPP20_RECLAIM_BACKOFF_MS[] =
{ 0, 20, 40, 80, 160, 320 };
/*------------------------------------------------------*\
| FrameEnd BUSY retry backoff. Used by PerKeyFrameEnd |
| when the firmware returns HID++ error 0x08 (BUSY) |
| because it's still draining the per-key write queue. |
| First retry is fast (~2 USB round trips) for the |
| common case where BUSY was transient; subsequent |
| retries give actual drain headroom. Total worst case |
| ~180ms, fits inside the PerKeyFrameEnd 300ms deadline |
| with margin for the eventual ACK to land. |
\*------------------------------------------------------*/
static constexpr uint16_t HIDPP20_FRAME_END_BUSY_BACKOFF_MS[] =
{ 30, 60, 90 };
/*-----------------------------------------------------*\
| Deep-sleep detection threshold. After StartSleep() |
| commands the firmware fade, the device eventually |
| enters deep sleep and returns BUSY to every FrameEnd. |
| Once this many consecutive FrameEnd attempts exhaust |
| all BUSY retries while power_state == SLEEPING, we |
| suppress further frame sends until Wake() fires. |
\*-----------------------------------------------------*/
static constexpr int HIDPP20_DEEP_SLEEP_FAILURE_THRESHOLD = 5;
/*------------------------------------------------------*\
| Per-key frame retry backoff. Used when a full |
| DeviceUpdateLEDs pass completes with some zones |
| unacked (partial commit). The retry re-runs a whole |
| frame from the power thread, so the backoff is |
| between full frames, not individual packets. |
| |
| First value aligned to the power thread's 50ms poll |
| cadence — anything shorter rounds up anyway, and |
| matching the tick makes latency predictable. |
| |
| Worst case: 5 retries, cumulative ~1550ms. This |
| covers the reconnect-transient window where the G502 |
| firmware silently drops per-key writes for several |
| hundred ms after the wireless link re-establishes. |
\*------------------------------------------------------*/
static constexpr uint16_t HIDPP20_REPAINT_RETRY_BACKOFF_MS[] =
{ 50, 100, 200, 400, 800 };
static constexpr HIDPP20RetryPolicy HIDPP20_POLICY_RELIABLE = {
HIDPP20_BACKOFF_RELIABLE,
sizeof(HIDPP20_BACKOFF_RELIABLE) / sizeof(uint16_t),
300, // read window
true, // flush_before
true, // retry_on_busy
"reliable"
};
/*------------------------------------------------------*\
| Probe policy: tight budget for is-this-HID++ checks |
| during initial discovery. ~500ms worst case for dead |
| devices, vs ~6s for reliable. One retry handles a |
| transient hiccup on the first IRoot call (e.g. on a |
| busy mouse), but we bail fast on truly non-responsive |
| or non-HID++ hidraws so probe latency stays bounded. |
\*------------------------------------------------------*/
static constexpr HIDPP20RetryPolicy HIDPP20_POLICY_PROBE = {
HIDPP20_BACKOFF_PROBE,
sizeof(HIDPP20_BACKOFF_PROBE) / sizeof(uint16_t),
200, // read window
true, // flush_before
true, // retry_on_busy
"probe"
};
class LogitechHIDPP20Controller
{
public:
LogitechHIDPP20Controller(hid_device* dev, const char* path,
uint8_t device_index, bool wireless,
std::shared_ptr<std::mutex> mutex_ptr,
uint16_t usage_page = 0xFF00);
~LogitechHIDPP20Controller();
/*-------------------------------------------------*\
| Lifecycle |
\*-------------------------------------------------*/
bool Probe();
void Initialize();
void Shutdown();
/*-------------------------------------------------*\
| Accessors |
\*-------------------------------------------------*/
const HIDPP20DeviceCapabilities& GetCapabilities() const;
std::string GetDeviceLocation();
std::string GetSerialString();
uint32_t GetInitGeneration() const;
/*--------------------------------------------------*\
| Per-key lighting (0x8081) |
| |
| Per-key writes are fire-and-forget at the wire |
| layer. SendPerKeyData enqueues an outstanding |
| write entry; PerKeyFrameEnd drains the response |
| queue, matches ACKs by FIFO, and returns which |
| zones the firmware actually committed plus |
| whether the FrameEnd itself ACKed. |
\*--------------------------------------------------*/
void SetPerKeyColors(const std::vector<std::pair<uint16_t, RGBColor>>& zone_colors);
void SetAllPerKeyColor(RGBColor color);
void SendPerKeyData(uint8_t perkey_idx, uint8_t function,
const uint8_t* data, size_t len,
const std::vector<uint8_t>& zone_ids);
PerKeyFrameResult PerKeyFrameEnd();
/*-------------------------------------------------*\
| Zone effects (0x8071 / 0x8070) |
\*-------------------------------------------------*/
void SetZoneEffect(uint8_t cluster_idx, uint8_t effect_idx,
uint16_t effect_id,
unsigned char r, unsigned char g, unsigned char b,
uint16_t period, unsigned char brightness,
unsigned char direction, bool persist);
/*---------------------------------------------------*\
| Headset RGB hostmode (0x0620). |
| Sticky-claim model: SetHostMode() claims once via |
| fn8, then each write is fn5 (single-value) or fn2 |
| (individual) + fn6 FrameEnd[0x01]. 0x02 persist was |
| tested and does not work on G522 firmware. |
\*---------------------------------------------------*/
void SetHeadsetRGBHostmodeColors(const std::vector<RGBColor>& zone_colors);
/*-------------------------------------------------*\
| SW control management |
\*-------------------------------------------------*/
int SetSWControl(uint8_t mode, uint8_t flags);
void SetRGBPowerMode(uint8_t mode);
void SetHostMode();
bool ClaimSWControlIfNeeded();
void UpgradeSwControlAfterFirstPaint();
/*-------------------------------------------------*\
| Keyboard-family handshake (0x4522 fn3 + fn1). |
| G815 / G915 / G Pro send this before any mode |
| write. Feature-gated no-op on devices (G502 / |
| G515) that don't enumerate 0x4522. |
\*-------------------------------------------------*/
void DoDisableKeysByUsageHandshake();
/*--------------------------------------------------*\
| Keyboard-family per-key takeover prep. |
| Per-cluster SetEffectByIndex(effectIdx=0=Off, |
| persist=1) + primer key via SetIndividualRgbZones |
| + FrameEnd. Matches G815 / G915 InitializeDirect. |
| Gated on 0x4522 + per-key V2 presence. |
\*--------------------------------------------------*/
void DoKeyboardFamilyPerKeyPrep();
/*--------------------------------------------------*\
| Wake-repaint flag. Set by Wake() before calling |
| request_repaint_fn so the repaint callback knows |
| to invalidate sent_colors (force a full per-key |
| push) without triggering the claim/prep sequence. |
\*--------------------------------------------------*/
bool ConsumeWakeFullRepaint();
bool NeedsPrepSequence() const { return sw_control_needs_upgrade_to_5; }
/*--------------------------------------------------*\
| Per-key retry scheduling. Called by the RGB |
| controller's DeviceUpdateLEDs on partial-commit |
| frames; the power thread polls and fires |
| request_repaint_fn when a retry deadline expires. |
\*--------------------------------------------------*/
void ScheduleRetryPaint();
void CancelRetryPaint();
void TickRetryPaintIfPending();
/*--------------------------------------------------*\
| Observed per-key prep sequence. Two |
| SetEffectByIndex calls cloned byte-for-byte from a |
| wire capture on a G502 X PLUS. |
| Used in place of the Static-pass-through prep when |
| the device's RGBEffects enumeration matches the |
| G502 shape — see DeviceUpdateLEDs for gating. |
\*--------------------------------------------------*/
void DoObservedPerKeyPrep();
/*--------------------------------------------------*\
| Power management (idle/dim/sleep/wake) |
\*--------------------------------------------------*/
void StartPowerManager();
void StopPowerManager();
void StartEventWatcher();
void StartProbeWatcher();
bool HasBridge() const;
void SetRepaintCallback(std::function<void()> repaint);
void SetReapplyActiveModeCallback(std::function<bool()> cb);
void SetRegisterCallback(std::function<void(RGBController*)> cb);
HIDPP20PowerState GetPowerState() const;
int GetDimBrightness() const;
bool IsOnline() const;
bool IsDeepSleep() const;
void SetWireless(bool w) { wireless = w; }
bool QueryWirelessStatus();
bool QueryExternalPower();
void FlushResponseQueue();
private:
hid_device* dev;
std::string location;
uint8_t device_index;
bool wireless;
std::shared_ptr<std::mutex> mutex;
HIDPP20DeviceCapabilities caps;
HIDPP20Transport transport;
bool initialized;
bool sw_control_claimed;
bool sw_control_needs_upgrade_to_5;
uint32_t frame_counter;
/*--------------------------------------------------*\
| Retry-paint state (partial-commit recovery). |
| retry_paint_deadline_ zero = no retry pending. |
| retry_paint_attempt_ indexes into |
| HIDPP20_REPAINT_RETRY_BACKOFF_MS; once it reaches |
| the array length, we give up for this sequence. |
| Atomic so both the paint thread (RGB controller) |
| and the power thread can access without locks. |
\*--------------------------------------------------*/
std::atomic<std::chrono::steady_clock::time_point> retry_paint_deadline_;
std::atomic<uint8_t> retry_paint_attempt_;
std::atomic<bool> wake_full_repaint_pending_;
uint32_t init_generation;
std::string log_tag;
/*---------------------------------------------------*\
| Transport-layer I/O |
| |
| SendMessage/ReadMessage handle wire framing based |
| on transport.type. Upper layers pass feature index, |
| function ID, and payload — the transport layer |
| wraps them in the correct report format. |
\*---------------------------------------------------*/
int SendMessage(uint8_t feat_idx, uint8_t function,
const uint8_t* data, size_t len);
int ReadMessage(uint8_t* feat_idx_out, uint8_t* function_out,
uint8_t* data_out, size_t data_max,
int timeout_ms = LOGITECH_PROTOCOL_TIMEOUT);
/*--------------------------------------------------*\
| Standard HID++ transport (0xFF00/0xFF43) |
\*--------------------------------------------------*/
int SendStandard(uint8_t feat_idx, uint8_t function,
const uint8_t* data, size_t len);
int ReadStandardDirect(uint8_t* feat_idx_out, uint8_t* function_out,
uint8_t* data_out, size_t data_max,
int timeout_ms);
/*--------------------------------------------------*\
| Centurion transport (0xFFA0) |
| Wraps messages in CPL framing, routes through |
| CentPPBridge for sub-device access. |
\*--------------------------------------------------*/
int SendCenturion(uint8_t feat_idx, uint8_t function,
const uint8_t* data, size_t len);
int ReadCenturionDirect(uint8_t* feat_idx_out, uint8_t* function_out,
uint8_t* data_out, size_t data_max,
int timeout_ms);
/*--------------------------------------------------*\
| Reader thread dispatch layer |
| ReadHIDDirect: raw HID read (used by reader thread |
| and during Probe before reader starts). |
| ReadFromQueue: waits on response queue filled by |
| the reader thread. |
\*--------------------------------------------------*/
int ReadHIDDirect(uint8_t* feat_idx_out, uint8_t* function_out,
uint8_t* data_out, size_t data_max,
int timeout_ms);
int ReadFromQueue(uint8_t* feat_idx_out, uint8_t* function_out,
uint8_t* data_out, size_t data_max,
int timeout_ms);
/*---------------------------------------------------*\
| High-level helpers |
| SendAndReceive is retained as a thin wrapper |
| around SendAcked with the reliable policy, for |
| call-site stability. |
\*---------------------------------------------------*/
int SendAndReceive(uint8_t feat_idx, uint8_t function,
const uint8_t* send_data, size_t send_len,
uint8_t* recv_data, size_t recv_max);
/*---------------------------------------------------*\
| Unified send-and-ack primitive with retry policy. |
| All command paths converge here. Returns: |
| >0 : bytes copied into recv_data |
| 0 : timeout / BUSY exhaustion |
| -1 : non-BUSY HID++ error |
| -2 : wire error (SendMessage failed) |
| If hidpp20_error_out is non-null and return is -1, |
| the HID++ error code is stored there. |
\*---------------------------------------------------*/
int SendAcked(uint8_t feat_idx, uint8_t function,
const uint8_t* send_data, size_t send_len,
uint8_t* recv_data, size_t recv_max,
const HIDPP20RetryPolicy& policy = HIDPP20_POLICY_RELIABLE,
uint8_t* hidpp20_error_out = nullptr);
/*--------------------------------------------------*\
| Compatibility shim: same as SendAcked but writes |
| the response into a blankFAPmessage. Used by |
| callers that inherited the SendLong+ReadResponse |
| interface and inspect response.data[] downstream. |
\*--------------------------------------------------*/
int SendAckedIntoFAP(uint8_t feat_idx, uint8_t function,
const uint8_t* send_data, size_t send_len,
blankFAPmessage& response,
const HIDPP20RetryPolicy& policy = HIDPP20_POLICY_RELIABLE);
/*-------------------------------------------------*\
| Feature discovery |
\*-------------------------------------------------*/
uint8_t GetFeatureIndex(uint16_t feature_page,
const HIDPP20RetryPolicy& policy = HIDPP20_POLICY_RELIABLE);
uint8_t GetFeatureVersion(uint16_t feature_page) const;
void DiscoverTransport();
void DiscoverDeviceName();
void DiscoverDeviceType();
void EnumerateFeatures(uint8_t feature_set_idx);
void DiscoverFirmwareInfo();
void DiscoverRGBEffects();
void DiscoverEffectCards();
void DiscoverHeadsetRGBHostmode();
void DiscoverPerKeyZones();
void DiscoverKeyboardLayout();
/*-------------------------------------------------*\
| Power management internals |
\*-------------------------------------------------*/
void ReaderThreadFunc();
void PowerThreadFunc();
void DispatchEvent(uint8_t feat, uint8_t func, const uint8_t* data);
void OnUserActivity(uint8_t activity_type);
void StartDimRamp();
void DimRampStep();
void StartSleep();
void Wake();
void ReadFirmwareTimers();
void ReadNvSleepRampConfig();
void WritePowerConfig(uint16_t idle_s, uint16_t sleep_s);
void ReadActiveProfileSector();
void ReprobeSubDevice();
void ReconnectDevice();
void FullReprobe();
void RediscoverFeatures();
/*----------------------------------------------------*\
| Platform-specific. ScanForDevice walks the OS-level |
| HID enumeration to find the same physical device on |
| a new path (USB<->wireless transitions). Linux uses |
| sysfs; Windows and macOS use hidapi + serial_number |
| matching. Bodies live in |
| LogitechHIDPP20Controller_Linux.cpp and |
| LogitechHIDPP20Controller_Windows_MacOS.cpp. |
\*----------------------------------------------------*/
bool ScanForDevice(bool force = false);
/*----------------------------------------------------*\
| Platform-specific. Returns the friendly name for a |
| Centurion sub-device at the given hidapi path, or "" |
| if no name is available. Linux reads HID_NAME from |
| sysfs; Windows uses hid_device_info::product_string. |
\*----------------------------------------------------*/
std::string GetCenturionSubDeviceName(const std::string& path);
void SwapHIDHandle(hid_device* new_dev, const std::string& new_path);
/*-------------------------------------------------*\
| Reader thread + response queue |
\*-------------------------------------------------*/
std::thread* reader_thread;
std::atomic<bool> reader_running;
std::mutex response_mutex;
std::condition_variable response_cv;
std::deque<HIDPP20RawMessage> response_queue;
/*-------------------------------------------------*\
| Power thread (state machine + command sender) |
\*-------------------------------------------------*/
std::thread* power_thread;
std::atomic<bool> power_thread_running;
std::atomic<int> pending_activity; // -1=none, 0=idle, 1+=active
std::atomic<int> pending_connection; // 0=none, +1=connected, -1=disconnected
std::atomic<int> pending_path_check; // HID++1.0 DJ notification → force-scan retries remaining (0=idle)
std::atomic<bool> device_online; // false when device is unreachable
std::atomic<int> consecutive_timeouts; // reset on successful response
std::atomic<bool> watcher_mode; // true when retrying failed probe
/*-------------------------------------------------*\
| Power management state |
\*-------------------------------------------------*/
HIDPP20PowerState power_state;
std::mutex power_mutex;
std::atomic<bool> deep_sleep; // true once device stops responding after StartSleep()
std::atomic<int> consecutive_frame_end_failures; // FrameEnd BUSY exhaustions while SLEEPING
/*-------------------------------------------------*\
| Dim ramp state |
| dim_brightness_pct is applied by DeviceUpdateLEDs |
| to scale colors before pushing to device. |
| This is our own host-side animation, independent |
| of any firmware dim/sleep timers. |
\*-------------------------------------------------*/
#define DIM_STEPS 25
#define DIM_INTERVAL_MS 200
#define DIM_TARGET_PCT 50
std::atomic<int> dim_brightness_pct; // 100=full, 50=dimmed
int dim_step;
std::chrono::steady_clock::time_point next_dim_time;
/*-------------------------------------------------*\
| Sleep timer |
\*-------------------------------------------------*/
std::chrono::steady_clock::time_point sleep_deadline;
/*--------------------------------------------------*\
| Last idle-settings re-read timestamp. Drives the |
| 500ms poll in PowerThreadFunc that re-reads the |
| LogitechHIDPP20IdleSettings JSON key so updates |
| from the plugin (or manual edits) apply within |
| about half a second without any callback plumbing. |
\*--------------------------------------------------*/
std::chrono::steady_clock::time_point last_idle_poll;
/*-------------------------------------------------*\
| Effective idle/sleep timers used by the state |
| machine. Populated from the firmware snapshot by |
| default, then possibly overridden by profile |
| values in ApplyPowerSavingProfile. |
\*-------------------------------------------------*/
uint16_t idle_timeout_s;
uint16_t sleep_timeout_s;
/*--------------------------------------------------*\
| Firmware-configured timer snapshot. Read at init |
| (and on reconnect) by ReadFirmwareTimers and |
| never overwritten by profile application, so |
| that the unconfigured fallback path and transition |
| back from a user profile both have a clean set |
| of defaults to return to. |
\*--------------------------------------------------*/
uint16_t fw_idle_timeout_s = 60;
uint16_t fw_sleep_timeout_s = 300;
uint16_t written_idle_s = 0; // last value written to device RAM (0 = not written yet)
uint16_t written_sleep_s = 0;
/*--------------------------------------------------*\
| Host-side idle/dim/sleep state. |
| Populated from LogitechHIDPP20IdleSettings on each |
| ApplyPowerSavingProfile() invocation. |
\*--------------------------------------------------*/
bool ps_dim_enabled = false;
int ps_dim_target_pct = DIM_TARGET_PCT;
bool ps_sleep_enabled = false;
bool ps_on_external_power = false;
bool ps_last_logged_external = false;
int ps_last_logged_pct = -1;
int ps_last_logged_idle = -1;
int ps_last_logged_sleep = -1;
uint16_t last_power_raw = 0xFFFF; // dedup for QueryExternalPower TRACE
uint8_t idx_unified_battery = 0;
void ApplyPowerSavingProfile();
bool IsCurrentlyWireless() const;
/*-------------------------------------------------*\
| Per-key write tracking (per active frame) |
| Populated by SendPerKeyData, drained by |
| PerKeyFrameEnd. Single-threaded — only the RGB |
| controller thread touches per-key state. |
\*-------------------------------------------------*/
std::vector<OutstandingPerKeyWrite> outstanding_writes;
/*-------------------------------------------------*\
| Callbacks |
\*-------------------------------------------------*/
std::function<void()> request_repaint_fn;
std::function<bool()> reapply_active_mode_fn;
std::function<void(RGBController*)> register_controller_fn;
};
@@ -0,0 +1,433 @@
/*---------------------------------------------------------*\
| LogitechHIDPP20Controller_Linux.cpp |
| |
| Linux-specific path-migration and device-name lookup |
| for the unified Logitech HID++ 2.0 controller. |
| |
| Uses sysfs (/sys/class/hidraw) to find the same |
| physical device on a new hidraw path after a USB <-> |
| wireless transition, and to read Centurion sub-device |
| friendly names from HID_NAME uevent fields. |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include <cctype>
#include <cstdio>
#include <chrono>
#include <vector>
#include <string>
#include <fstream>
#include <dirent.h>
#include "LogitechHIDPP20Controller.h"
#include "LogManager.h"
#define LOG_TAG log_tag.c_str()
std::string LogitechHIDPP20Controller::GetCenturionSubDeviceName(const std::string& path)
{
/*--------------------------------------------------------*\
| Centurion sub-device friendly name comes from sysfs |
| HID_NAME=, same field Solaar reads. The `path` arg is a |
| hidraw dev path like /dev/hidraw5; extract the hidrawN |
| basename and read /sys/class/hidraw/<basename>/device/ |
| uevent. |
\*--------------------------------------------------------*/
std::string sysfs_name;
size_t pos = path.rfind("hidraw");
if(pos == std::string::npos)
{
return sysfs_name;
}
std::string uevent_path = "/sys/class/hidraw/" + path.substr(pos) + "/device/uevent";
FILE* f = fopen(uevent_path.c_str(), "r");
if(!f)
{
return sysfs_name;
}
char line[256];
while(fgets(line, sizeof(line), f))
{
if(strncmp(line, "HID_NAME=", 9) == 0)
{
sysfs_name = line + 9;
while(!sysfs_name.empty() && (sysfs_name.back() == '\n' || sysfs_name.back() == '\r'))
{
sysfs_name.pop_back();
}
break;
}
}
fclose(f);
return sysfs_name;
}
bool LogitechHIDPP20Controller::ScanForDevice(bool force)
{
/*---------------------------------------------------------*\
| Scan sysfs for a hidraw with matching unitId. Called by |
| the power thread either periodically (normal reactive |
| mode — only when device_online==false, USB/wireless |
| transition or physical unplug) or on demand from the |
| reader thread after a HID++1.0 Device Connection |
| notification arrives (force=true, bypasses the online |
| gate). Finds the device on its new connection path. |
| |
| Reactive-only: we never migrate while the current path |
| still works. On devices like the G502 X PLUS that expose |
| both a USB-direct hidraw AND a wireless-via-dongle hidraw |
| simultaneously (when paired to the receiver and plugged |
| in at the same time), eager migration would bounce us off |
| a working path onto one the firmware has actively muted, |
| breaking control. The device signals which path is |
| active by returning errors/going silent on the inactive |
| path — our reader thread picks that up as a hid_read |
| failure and flips device_online=false, at which point the |
| power thread calls this function to pick the new path. |
| |
| Linux implementation: walks /sys/class/hidraw and matches |
| on HID_UNIQ. The Windows counterpart (same class method, |
| different .cpp file) uses hid_enumerate + serial_number. |
| |
| Match criteria: |
| - device_online == false (caller should already ensure) |
| - HID_UNIQ matches our unitId |
| - Logitech VID (046D) |
| - HID++ interface (usage_page 0xFF00, usage 2) |
| - Different from our current path (device moved) |
\*---------------------------------------------------------*/
if(caps.unit_id.empty() || caps.unit_id == "00000000")
{
return false;
}
/*---------------------------------------------------------*\
| Online guard: never migrate off a working path unless |
| the caller explicitly forces a re-check. The normal |
| periodic scan path stays gated on device_online==false |
| so it's a no-op when everything is healthy. |
| |
| The reader thread bypasses this guard (force=true) after |
| seeing a HID++1.0 Device Connection Status notification |
| from the Lightspeed receiver, which fires BEFORE the |
| firmware fully switches its data flow from wireless to |
| USB. At that moment device_online is still true (the |
| current path hasn't failed yet), but we want the scan to |
| run so we can migrate to the new path proactively. |
\*---------------------------------------------------------*/
if(!force && device_online.load())
{
return false;
}
/*---------------------------------------------------------*\
| Normalize our unitId to lowercase hex without dashes for |
| comparison. Sysfs HID_UNIQ varies between drivers: |
| - Lightspeed virtual: "0d-12-5d-47" (dashes) |
| - USB direct: "0D125D47" (no dashes) |
| We strip dashes and lowercase both sides for matching. |
\*---------------------------------------------------------*/
std::string target_norm;
for(char c : caps.unit_id)
{
if(c != '-')
{
target_norm += (char)tolower(c);
}
}
/*---------------------------------------------------------*\
| Read our current PID from sysfs. Only migrate to paths |
| with a DIFFERENT PID — same PID means same receiver, |
| just a different pairing slot (e.g., stale pairing). |
\*---------------------------------------------------------*/
unsigned int current_pid = 0;
{
std::string cur_hidraw = location.substr(location.rfind('/') + 1);
std::string cur_uevent = "/sys/class/hidraw/" + cur_hidraw + "/device/uevent";
std::ifstream cur_file(cur_uevent);
std::string line;
while(std::getline(cur_file, line))
{
if(line.compare(0, 7, "HID_ID=") == 0)
{
size_t lc = line.rfind(':');
if(lc != std::string::npos)
{
sscanf(line.c_str() + lc + 1, "%x", &current_pid);
}
break;
}
}
}
/*----------------------------------------------------------*\
| Collect ALL sysfs hidraws with matching unit_id + Logitech |
| VID + different PID. With multiple Lightspeed dongles in |
| the system, several hidraws can share the same HID_UNIQ: |
| one is the live virt-slot on the connected dongle, others |
| are stale virt-slots on dongles where our device is not |
| actually present. We have to probe each one to find out |
| which slot is real — sysfs alone can't tell them apart. |
\*----------------------------------------------------------*/
struct Candidate
{
std::string dev_path;
unsigned int pid;
};
std::vector<Candidate> candidates;
DIR* dir = opendir("/sys/class/hidraw");
if(!dir)
{
return false;
}
struct dirent* entry;
while((entry = readdir(dir)) != nullptr)
{
if(strncmp(entry->d_name, "hidraw", 6) != 0)
{
continue;
}
std::string uevent_path = "/sys/class/hidraw/" +
std::string(entry->d_name) + "/device/uevent";
std::ifstream uevent(uevent_path);
if(!uevent.is_open())
{
continue;
}
std::string line;
std::string hid_uniq;
std::string hid_id;
while(std::getline(uevent, line))
{
if(line.compare(0, 9, "HID_UNIQ=") == 0)
{
hid_uniq = line.substr(9);
}
else if(line.compare(0, 7, "HID_ID=") == 0)
{
hid_id = line.substr(7);
}
}
std::string uniq_norm;
for(char c : hid_uniq)
{
if(c != '-')
{
uniq_norm += (char)tolower(c);
}
}
if(uniq_norm != target_norm)
{
continue;
}
if(hid_id.find("0000046D") == std::string::npos &&
hid_id.find("0000046d") == std::string::npos)
{
continue;
}
std::string dev_path = "/dev/" + std::string(entry->d_name);
if(dev_path == location)
{
continue;
}
size_t last_colon = hid_id.rfind(':');
if(last_colon == std::string::npos)
{
continue;
}
unsigned int pid = 0;
sscanf(hid_id.c_str() + last_colon + 1, "%x", &pid);
if(pid == current_pid)
{
continue;
}
candidates.push_back({dev_path, pid});
}
closedir(dir);
if(candidates.empty())
{
return false;
}
/*----------------------------------------------------------*\
| Probe each candidate before committing. Two-dongle systems |
| can expose multiple sysfs hidraws with the same HID_UNIQ: |
| one is the live slot, others are stale pairings that |
| respond with short-format UNKNOWN_DEVICE errors. Issue a |
| cheap IRoot GetFeature (feat 0x0001) to distinguish them. |
| A live slot returns a long-form response with feat_idx=0 |
| and feat_byte matching the sub-index we asked for. A stale |
| slot returns r[10 xx 8F 00 00 08 ...] (short error, code |
| 0x08 UNKNOWN_DEVICE). We accept the first candidate that |
| passes; the overall pending_path_check retry loop will |
| naturally re-probe all candidates on subsequent scans if |
| none pass on the current pass (e.g., mid-transition). |
\*----------------------------------------------------------*/
std::string found_path;
hid_device* found_dev = nullptr;
for(size_t c = 0; c < candidates.size(); c++)
{
const Candidate& cand = candidates[c];
LOG_DEBUG("%s Scan: migration candidate at %s (pid=0x%04X, current=%s pid=0x%04X)",
LOG_TAG, cand.dev_path.c_str(), cand.pid,
location.c_str(), current_pid);
/*-----------------------------------------------------*\
| Multi-dongle caveat: hid_enumerate returns entries |
| for ALL dongles with this PID (046D:4099 Lightspeed |
| can appear several times in a single machine). Match |
| strictly on the sysfs candidate's dev_path so each |
| candidate resolves to its OWN dongle's HID++ |
| interface — not the first match we stumble across. |
\*-----------------------------------------------------*/
hid_device_info* devs = hid_enumerate(0x046D, (uint16_t)cand.pid);
std::string hidpp20_path;
for(hid_device_info* d = devs; d != nullptr; d = d->next)
{
LOG_TRACE("%s Scan: enumerate PID=0x%04X path=%s page=0x%04X usage=%d",
LOG_TAG, cand.pid, d->path, d->usage_page, d->usage);
if(std::string(d->path) == cand.dev_path &&
d->usage_page == 0xFF00 && d->usage == 2 &&
std::string(d->path) != location)
{
hidpp20_path = d->path;
break;
}
}
hid_free_enumeration(devs);
if(hidpp20_path.empty())
{
LOG_DEBUG("%s Scan: %s no matching LogitechHID++ interface in enum",
LOG_TAG, cand.dev_path.c_str());
continue;
}
hid_device* test_dev = hid_open_path(hidpp20_path.c_str());
if(!test_dev)
{
LOG_DEBUG("%s Scan: %s failed to open", LOG_TAG, hidpp20_path.c_str());
continue;
}
/*------------------------------------------------------*\
| Probe: HID++2.0 IRoot GetFeature for feat 0x0001 |
| (IFeatureSet). Wire: w[10 FF 0000 000100] |
| - report_id 0x10 (short) |
| - device_index 0xFF |
| - feat_idx 0x00 (IRoot) |
| - address 0x00 (func 0 GetFeature, sw_id 0) |
| - payload 00 01 00 (feat_id 0x0001) |
| |
| A live device returns r[11 xx 00 0X ...] — long form, |
| feat_idx 0x00, the address byte we sent back, and the |
| feature index in the payload. |
| A stale slot returns r[10 xx 8F 00 00 08 ...] — short |
| error form. Reject anything that starts with 0x10. |
\*------------------------------------------------------*/
uint8_t probe[7] = {0x10, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00};
uint8_t reply[20] = {};
int write_rc = hid_write(test_dev, probe, sizeof(probe));
bool probe_ok = false;
if(write_rc >= 0)
{
/*-----------------------------------------------------*\
| Drain up to ~100ms, looking for a long-form response |
| matching our probe. Skip stray reports from unrelated |
| firmware events that might be queued on the hidraw. |
\*-----------------------------------------------------*/
std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::now() +
std::chrono::milliseconds(100);
while(std::chrono::steady_clock::now() < deadline)
{
int read_rc = hid_read_timeout(test_dev, reply, sizeof(reply), 50);
if(read_rc <= 0)
{
continue;
}
if(reply[0] == 0x11 && reply[2] == 0x00 && reply[3] == 0x00)
{
probe_ok = true;
break;
}
if(reply[0] == 0x10 && reply[2] == 0x8F)
{
LOG_DEBUG("%s Scan: %s probe rejected — err=0x%02X (stale slot)",
LOG_TAG, hidpp20_path.c_str(), reply[5]);
break;
}
}
}
if(!probe_ok)
{
hid_close(test_dev);
continue;
}
LOG_DEBUG("%s Scan: %s probe accepted (feat_idx=0x%02X)",
LOG_TAG, hidpp20_path.c_str(), reply[4]);
found_path = hidpp20_path;
found_dev = test_dev;
break;
}
if(!found_dev)
{
return false;
}
LOG_INFO("%s Device migrated: %s -> %s", LOG_TAG, location.c_str(), found_path.c_str());
SwapHIDHandle(found_dev, found_path);
return true;
}
@@ -0,0 +1,302 @@
/*---------------------------------------------------------*\
| LogitechHIDPP20Controller_Windows_MacOS.cpp |
| |
| Path-migration and device-name lookup for the unified |
| Logitech HID++ 2.0 controller on Windows and macOS. |
| |
| Uses hidapi's hid_enumerate + hid_device_info fields |
| (serial_number, product_string, product_id, usage_page) |
| on platforms with no /sys/class/hidraw equivalent. |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <algorithm>
#include <chrono>
#include <cstring>
#include <string>
#include <vector>
#include "LogitechHIDPP20Controller.h"
#include "LogManager.h"
#include "StringUtils.h"
#define LOG_TAG log_tag.c_str()
std::string LogitechHIDPP20Controller::GetCenturionSubDeviceName(const std::string& path)
{
/*---------------------------------------------------------*\
| On Windows, hidapi's hid_device_info carries a |
| product_string field (wchar_t*). Enumerate all Logitech |
| devices and find the one whose path matches `path`. |
| |
| Caveat: Windows hidapi typically returns the parent |
| product string on every (interface, usage_page, usage) |
| entry that maps to the same USB device, so Centurion |
| sub-devices may share a name with the parent dongle. |
| That's a less specific name than Linux's HID_NAME, but |
| still better than "Logitech Centurion Device". |
\*---------------------------------------------------------*/
std::string friendly;
hid_device_info* devs = hid_enumerate(0x046D, 0x0000);
for(hid_device_info* d = devs; d != nullptr; d = d->next)
{
if(d->path == nullptr)
{
continue;
}
if(std::string(d->path) != path)
{
continue;
}
friendly = StringUtils::wchar_to_string(d->product_string);
break;
}
hid_free_enumeration(devs);
return friendly;
}
bool LogitechHIDPP20Controller::ScanForDevice(bool force)
{
/*---------------------------------------------------------*\
| Scan hidapi for a Logitech device with matching unitId. |
| Called by the power thread either periodically (normal |
| reactive mode — only when device_online==false, USB/ |
| wireless transition or physical unplug) or on demand |
| from the reader thread after a HID++1.0 Device |
| Connection notification arrives (force=true, bypasses |
| the online gate). Finds the device on its new |
| connection path. |
| |
| Reactive-only: we never migrate while the current path |
| still works (same rationale as Linux — see the Linux |
| companion file for the full explanation). |
| |
| Windows implementation: walks hid_enumerate(046D, *) and |
| matches on hid_device_info::serial_number, which for |
| Logitech devices generally corresponds to the same |
| stable identity as the HID++-reported unit_id. If |
| serial_number matching yields nothing (hidapi may not |
| populate it for some Logitech devices on Windows), we |
| fall through to IRoot probing every candidate on the |
| matching usage_page/usage so we can still find the |
| device albeit more slowly. |
\*---------------------------------------------------------*/
if(caps.unit_id.empty() || caps.unit_id == "00000000")
{
return false;
}
if(!force && device_online.load())
{
return false;
}
std::string target_norm = StringUtils::normalize_hex_id(caps.unit_id);
/*---------------------------------------------------------*\
| Candidate = {path, pid, has_serial_match}. Serial matches |
| sort first so we try the cheapest/most-likely candidate. |
\*---------------------------------------------------------*/
struct Candidate
{
std::string dev_path;
unsigned int pid;
bool serial_match;
};
std::vector<Candidate> candidates;
unsigned int current_pid = 0;
hid_device_info* devs = hid_enumerate(0x046D, 0x0000);
/*---------------------------------------------------------*\
| First pass: find the entry matching our current path and |
| record its product_id so we can skip same-PID candidates. |
\*---------------------------------------------------------*/
for(hid_device_info* d = devs; d != nullptr; d = d->next)
{
if(d->path != nullptr && std::string(d->path) == location)
{
current_pid = d->product_id;
break;
}
}
/*---------------------------------------------------------*\
| Second pass: collect candidates that (a) aren't our |
| current path, (b) speak the HID++ interface, and (c) have |
| a plausible identity match — either the serial_number |
| matches our unit_id, or at minimum their PID differs from |
| ours so they can't be another slot on the same dongle. |
\*---------------------------------------------------------*/
for(hid_device_info* d = devs; d != nullptr; d = d->next)
{
if(d->path == nullptr)
{
continue;
}
if(std::string(d->path) == location)
{
continue;
}
/*-----------------------------------------------------*\
| Only HID++ interface (usage_page 0xFF00, usage 2). |
| Note: on Windows hidapi may or may not populate |
| usage / usage_page consistently. If either is zero, |
| fall through — we'll probe unconditionally in that |
| case rather than dropping a potential candidate. |
\*-----------------------------------------------------*/
bool usage_known = (d->usage_page != 0 || d->usage != 0);
if(usage_known && (d->usage_page != 0xFF00 || d->usage != 2))
{
continue;
}
/*-----------------------------------------------------*\
| Compare serial_number (wchar_t*) against our unit_id. |
| Normalize both and do an exact-match comparison. |
| Empty/missing serial is allowed — falls through as a |
| serial_match=false candidate so the probe path can |
| still find it via a different-PID filter. |
\*-----------------------------------------------------*/
bool serial_match = false;
if(d->serial_number != nullptr && d->serial_number[0] != L'\0')
{
std::string sn = StringUtils::wchar_to_string(d->serial_number);
std::string sn_norm = StringUtils::normalize_hex_id(sn);
if(!sn_norm.empty() && sn_norm == target_norm)
{
serial_match = true;
}
}
/*-----------------------------------------------------*\
| If we have no serial match AND this path shares the |
| current PID, skip. Same PID with no identity evidence |
| is probably a sibling slot on the same dongle, not a |
| valid migration target. |
\*-----------------------------------------------------*/
if(!serial_match && d->product_id == current_pid && current_pid != 0)
{
continue;
}
Candidate c;
c.dev_path = d->path;
c.pid = d->product_id;
c.serial_match = serial_match;
candidates.push_back(c);
}
hid_free_enumeration(devs);
if(candidates.empty())
{
return false;
}
/*---------------------------------------------------------*\
| Sort so serial-matched candidates get probed first. |
\*---------------------------------------------------------*/
std::stable_sort(candidates.begin(), candidates.end(),
[](const Candidate& a, const Candidate& b)
{
return a.serial_match && !b.serial_match;
});
/*---------------------------------------------------------*\
| Probe each candidate with IRoot GetFeature feat 0x0001. |
| A live slot returns a long-form (0x11) response; a stale |
| slot returns a short-form (0x10) error. See the Linux |
| companion file for the wire-level explanation. |
\*---------------------------------------------------------*/
std::string found_path;
hid_device* found_dev = nullptr;
for(size_t c = 0; c < candidates.size(); c++)
{
const Candidate& cand = candidates[c];
LOG_DEBUG("%s Scan: migration candidate at %s (pid=0x%04X, serial_match=%d)",
LOG_TAG, cand.dev_path.c_str(), cand.pid,
cand.serial_match ? 1 : 0);
hid_device* test_dev = hid_open_path(cand.dev_path.c_str());
if(!test_dev)
{
LOG_DEBUG("%s Scan: %s failed to open",
LOG_TAG, cand.dev_path.c_str());
continue;
}
uint8_t probe[7] = {0x10, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00};
uint8_t reply[20] = {};
int write_rc = hid_write(test_dev, probe, sizeof(probe));
bool probe_ok = false;
if(write_rc >= 0)
{
std::chrono::steady_clock::time_point deadline = std::chrono::steady_clock::now() +
std::chrono::milliseconds(100);
while(std::chrono::steady_clock::now() < deadline)
{
int read_rc = hid_read_timeout(test_dev, reply, sizeof(reply), 50);
if(read_rc <= 0)
{
continue;
}
if(reply[0] == 0x11 && reply[2] == 0x00 && reply[3] == 0x00)
{
probe_ok = true;
break;
}
if(reply[0] == 0x10 && reply[2] == 0x8F)
{
LOG_DEBUG("%s Scan: %s probe rejected — err=0x%02X (stale slot)",
LOG_TAG, cand.dev_path.c_str(), reply[5]);
break;
}
}
}
if(!probe_ok)
{
hid_close(test_dev);
continue;
}
LOG_DEBUG("%s Scan: %s probe accepted (feat_idx=0x%02X)",
LOG_TAG, cand.dev_path.c_str(), reply[4]);
found_path = cand.dev_path;
found_dev = test_dev;
break;
}
if(!found_dev)
{
return false;
}
LOG_INFO("%s Device migrated: %s -> %s",
LOG_TAG, location.c_str(), found_path.c_str());
SwapHIDHandle(found_dev, found_path);
return true;
}
@@ -0,0 +1,146 @@
/*---------------------------------------------------------*\
| LogitechHIDPP20IdleSettings.cpp |
| |
| Host-side idle/dim/sleep settings storage helper for |
| Logitech HID++ 2.0 devices. Loads the configuration |
| block from SettingsManager JSON, caches it, and writes |
| changes back through SettingsManager::SetSettings(). |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "LogitechHIDPP20IdleSettings.h"
#include "ResourceManager.h"
#include "SettingsManager.h"
static const char* SETTINGS_KEY = "LogitechHIDPP20IdleSettings";
LogitechHIDPP20IdleSettings* LogitechHIDPP20IdleSettings::instance()
{
static LogitechHIDPP20IdleSettings inst;
return &inst;
}
/*---------------------------------------------------------*\
| Minimum idle / sleep values the controller will honor. |
| Matches what Logitech firmware typically clamps to on |
| HID++ 2.0 devices, and guarantees the skip-dim path's |
| sleep_delay math always produces a positive window. |
\*---------------------------------------------------------*/
static const int MIN_IDLE_S = 15;
static const int MIN_SLEEP_S = 45;
static const int MIN_DIM_WINDOW = 30; // sleep must exceed idle by at least this
static void ClampProfile(LogitechHIDPP20IdleProfile& p)
{
if(p.dim_brightness < 0) p.dim_brightness = 0;
if(p.dim_brightness > 100) p.dim_brightness = 100;
if(p.idle_timeout_s < MIN_IDLE_S)
{
p.idle_timeout_s = MIN_IDLE_S;
}
if(p.sleep_timeout_s < MIN_SLEEP_S)
{
p.sleep_timeout_s = MIN_SLEEP_S;
}
if(p.sleep_timeout_s < p.idle_timeout_s + MIN_DIM_WINDOW)
{
p.sleep_timeout_s = p.idle_timeout_s + MIN_DIM_WINDOW;
}
}
static LogitechHIDPP20IdleProfile ProfileFromJson(const json& j)
{
LogitechHIDPP20IdleProfile p;
if(j.contains("dim_when_idle")) p.dim_when_idle = j["dim_when_idle"];
if(j.contains("dim_brightness")) p.dim_brightness = j["dim_brightness"];
if(j.contains("idle_timeout_s")) p.idle_timeout_s = j["idle_timeout_s"];
if(j.contains("allow_sleep")) p.allow_sleep = j["allow_sleep"];
if(j.contains("sleep_timeout_s")) p.sleep_timeout_s = j["sleep_timeout_s"];
ClampProfile(p);
return p;
}
static json ProfileToJson(const LogitechHIDPP20IdleProfile& p)
{
json j;
j["dim_when_idle"] = p.dim_when_idle;
j["dim_brightness"] = p.dim_brightness;
j["idle_timeout_s"] = p.idle_timeout_s;
j["allow_sleep"] = p.allow_sleep;
j["sleep_timeout_s"] = p.sleep_timeout_s;
return j;
}
void LogitechHIDPP20IdleSettings::load()
{
json settings = ResourceManager::get()->GetSettingsManager()->GetSettings(SETTINGS_KEY);
/*---------------------------------------------------------*\
| Empty / missing key means the plugin is not in use. |
| Reset both profiles to defaults with configured=false so |
| the controller defers to firmware. |
\*---------------------------------------------------------*/
if(!settings.is_object() || settings.empty())
{
configured = false;
on_battery = LogitechHIDPP20IdleProfile{};
plugged_in = LogitechHIDPP20IdleProfile{};
return;
}
configured = true;
if(settings.contains("on_battery"))
{
on_battery = ProfileFromJson(settings["on_battery"]);
}
else
{
on_battery = LogitechHIDPP20IdleProfile{};
}
if(settings.contains("plugged_in"))
{
plugged_in = ProfileFromJson(settings["plugged_in"]);
}
else
{
plugged_in = LogitechHIDPP20IdleProfile{};
}
}
void LogitechHIDPP20IdleSettings::save()
{
json settings;
settings["on_battery"] = ProfileToJson(on_battery);
settings["plugged_in"] = ProfileToJson(plugged_in);
SettingsManager* mgr = ResourceManager::get()->GetSettingsManager();
mgr->SetSettings(SETTINGS_KEY, settings);
mgr->SaveSettings();
configured = true;
}
void LogitechHIDPP20IdleSettings::setOnBattery(const LogitechHIDPP20IdleProfile& p)
{
on_battery = p;
configured = true;
}
void LogitechHIDPP20IdleSettings::setPluggedIn(const LogitechHIDPP20IdleProfile& p)
{
plugged_in = p;
configured = true;
}
@@ -0,0 +1,47 @@
/*---------------------------------------------------------*\
| LogitechHIDPP20IdleSettings.h |
| |
| Host-side idle/dim/sleep configuration for Logitech |
| HID++ 2.0 devices. Two profiles (on_battery, plugged_in)|
| selected at runtime based on the device's external- |
| power flag. `configured == false` means the JSON key |
| is absent entirely — the controller defers to firmware. |
| Qt-free so the controller can consume it directly. |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
struct LogitechHIDPP20IdleProfile
{
bool dim_when_idle = false;
int dim_brightness = 50;
int idle_timeout_s = 60;
bool allow_sleep = false;
int sleep_timeout_s = 300;
};
class LogitechHIDPP20IdleSettings
{
public:
static LogitechHIDPP20IdleSettings* instance();
void load();
void save();
bool isConfigured() const { return configured; }
const LogitechHIDPP20IdleProfile& onBattery() const { return on_battery; }
const LogitechHIDPP20IdleProfile& pluggedIn() const { return plugged_in; }
void setOnBattery(const LogitechHIDPP20IdleProfile& p);
void setPluggedIn(const LogitechHIDPP20IdleProfile& p);
private:
LogitechHIDPP20IdleSettings() = default;
bool configured = false;
LogitechHIDPP20IdleProfile on_battery;
LogitechHIDPP20IdleProfile plugged_in;
};
@@ -0,0 +1,72 @@
/*---------------------------------------------------------*\
| RGBController_LogitechHIDPP20.h |
| |
| RGBController for unified Logitech HID++ 2.0 devices |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechHIDPP20Controller.h"
class RGBController_LogitechHIDPP20 : public RGBController
{
public:
RGBController_LogitechHIDPP20(LogitechHIDPP20Controller* controller_ptr);
~RGBController_LogitechHIDPP20();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
void DeviceSaveMode();
bool ReapplyActiveMode();
private:
LogitechHIDPP20Controller* controller;
/*---------------------------------------------------------*\
| Repaint callback handler. Registered with the controller |
| as request_repaint_fn and invoked from the power thread |
| for dim/wake when no animation is driving updates. |
\*---------------------------------------------------------*/
void OnRepaintRequest();
/*---------------------------------------------------------*\
| When true, the next DeviceUpdateMode cycle sends its |
| SetZoneEffect calls with persist=true instead of the |
| default ephemeral write. Used by DeviceSaveMode to replay |
| the active mode as a NVM-committed effect on 0x8070 |
| devices, which default to non-persistent live writes. |
\*---------------------------------------------------------*/
bool save_pending = false;
/*---------------------------------------------------------*\
| Maps OpenRGB LED index -> HID++ per-key zone ID |
\*---------------------------------------------------------*/
std::vector<uint16_t> led_to_zone_id;
/*---------------------------------------------------------*\
| Reverse map: zone_id -> LED index (-1 if no LED). |
| Indexed 0..255 (zone IDs are bytes). Built once in |
| SetupZones to avoid scanning led_to_zone_id at commit |
| time, which would be O(N) per acked zone. |
\*---------------------------------------------------------*/
std::vector<int> zone_id_to_led_idx;
/*---------------------------------------------------------*\
| Last successfully committed colors for delta updates. |
| An entry of HIDPP20_UNCOMMITTED (0xFF000000) marks an LED |
| whose last write didn't ACK and which therefore needs to |
| be re-pushed in the next frame regardless of color delta. |
| The high byte (0xFF) is impossible for any value produced |
| by ToRGBColor() so it never collides with a real color. |
\*---------------------------------------------------------*/
std::vector<RGBColor> sent_colors;
uint32_t last_init_gen = 0;
};
@@ -0,0 +1,68 @@
/*---------------------------------------------------------*\
| LogitechLightspeedController.cpp |
| |
| Driver for Logitech Lightspeed |
| |
| TheRogueZeta 05 Aug 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "LogitechLightspeedController.h"
#include "StringUtils.h"
LogitechLightspeedController::LogitechLightspeedController(hid_device* dev_handle, const char* path)
{
dev = dev_handle;
location = path;
}
LogitechLightspeedController::~LogitechLightspeedController()
{
delete lightspeed;
}
std::string LogitechLightspeedController::GetDeviceLocation()
{
return("HID: " + location + " (Receiver) \r\nWireless Index: " + std::to_string(lightspeed->device_index));
}
std::string LogitechLightspeedController::GetSerialString()
{
if (lightspeed->device_index == 255 && lightspeed->wireless)
{
LOG_DEBUG("[%s] Skipped get serial number as this is the reciever", lightspeed->device_name.c_str());
return("");
}
else
{
wchar_t serial_string[128];
//int ret = hid_get_serial_number_string(dev, serial_string, 128);
//LOG_DEBUG("[%s] hid_get_serial_number_string Returned status - %i : %s", lightspeed->device_name.c_str(), ret, ((ret == 0) ? "SUCCESS" : "FAILED"));
//if(ret != 0)
{
return("");
}
std::string return_string(StringUtils::wstring_to_string(serial_string));
return(return_string);
}
}
void LogitechLightspeedController::SendMouseMode
(
uint8_t mode,
uint16_t speed,
uint8_t zone,
uint8_t red,
uint8_t green,
uint8_t blue,
uint8_t brightness
)
{
lightspeed->setMode(mode, speed, zone, red, green, blue, brightness);
}
@@ -0,0 +1,59 @@
/*---------------------------------------------------------*\
| LogitechLightspeedController.h |
| |
| Driver for Logitech Lightspeed |
| |
| TheRogueZeta 05 Aug 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "RGBController.h"
#include "LogManager.h"
#include "LogitechProtocolCommon.h"
#define LOGITECH_G_PRO_WIRELESS_BRIGHTNESS_MIN 0x01
#define LOGITECH_G_PRO_WIRELESS_BRIGHTNESS_MAX 0x64
/*---------------------------------------------------------------------------------------------*\
| Speed is 1000 for fast and 20000 for slow. |
| Values are multiplied by 100 later to give lots of GUI steps. |
\*---------------------------------------------------------------------------------------------*/
enum
{
LOGITECH_G_PRO_WIRELESS_SPEED_SLOWEST = 0xC8, /* Slowest speed */
LOGITECH_G_PRO_WIRELESS_SPEED_NORMAL = 0x32, /* Normal speed */
LOGITECH_G_PRO_WIRELESS_SPEED_FASTEST = 0x0A, /* Fastest speed */
};
class LogitechLightspeedController
{
public:
LogitechLightspeedController(hid_device* dev_handle, const char* path);
~LogitechLightspeedController();
logitech_device* lightspeed;
std::string GetDeviceLocation();
std::string GetSerialString();
void SendMouseMode
(
uint8_t mode,
uint16_t speed,
uint8_t zone,
uint8_t red,
uint8_t green,
uint8_t blue,
uint8_t brightness
);
private:
hid_device* dev;
std::string location;
};
@@ -0,0 +1,248 @@
/*---------------------------------------------------------*\
| RGBController_LogitechLightspeed.cpp |
| |
| RGBController for Logitech Lightspeed |
| |
| TheRogueZeta 05 Aug 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_LogitechLightspeed.h"
/**------------------------------------------------------------------*\
@name Logitech Lightspeed
@category Keyboard,Mouse,Mousemat,Headset
@type USB
@save :white_check_mark:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectLogitechWireless,DetectLogitechWired
@comment The Lightspeed controller is the generic RGB Controller
for all Logitech HID++ devices that support feature page 8070.
\*-------------------------------------------------------------------*/
RGBController_LogitechLightspeed::RGBController_LogitechLightspeed(LogitechLightspeedController* controller_ptr)
{
controller = controller_ptr;
bool connected = controller->lightspeed->connected();
mode Off;
Off.name = "Off";
Off.value = LOGITECH_DEVICE_LED_OFF;
Off.flags = 0;
Off.color_mode = MODE_COLORS_NONE;
modes.push_back(Off);
if(connected)
{
name = controller->lightspeed->device_name;
vendor = "Logitech";
description = "Logitech Wireless Lightspeed Device";
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
switch(controller->lightspeed->logitech_device_type)
{
case LOGITECH_DEVICE_TYPE_KEYBOARD:
type = DEVICE_TYPE_KEYBOARD;
break;
case LOGITECH_DEVICE_TYPE_MOUSE:
type = DEVICE_TYPE_MOUSE;
break;
case LOGITECH_DEVICE_TYPE_MOUSEPAD:
type = DEVICE_TYPE_MOUSEMAT;
break;
case LOGITECH_DEVICE_TYPE_HEADSET:
type = DEVICE_TYPE_HEADSET;
break;
default:
type = DEVICE_TYPE_UNKNOWN;
LOG_INFO("Logitech device type not known: %i", controller->lightspeed->logitech_device_type);
}
logitech_led fx = controller->lightspeed->getLED_info(0);
for(uint8_t i = 0; i < fx.fx.size(); i++)
{
/*---------------------------------------------------------*\
| Logitech devices don't have a set order for effects and |
| each device needs to have the effect index mapped |
\*---------------------------------------------------------*/
switch(fx.fx[i].mode)
{
case LOGITECH_DEVICE_LED_OFF:
/* Do nothing as it's already added */
break;
case LOGITECH_DEVICE_LED_ON:
{
mode Direct;
Direct.name = "Direct";
Direct.value = i;
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR;
Direct.color_mode = MODE_COLORS_PER_LED;
modes.push_back(Direct);
mode Static;
Static.name = "Static";
Static.value = i;
Static.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
Static.color_mode = MODE_COLORS_PER_LED;
Static.brightness_min = LOGITECH_G_PRO_WIRELESS_BRIGHTNESS_MIN;
Static.brightness_max = LOGITECH_G_PRO_WIRELESS_BRIGHTNESS_MAX;
Static.brightness = LOGITECH_G_PRO_WIRELESS_BRIGHTNESS_MAX;
modes.push_back(Static);
LOG_DEBUG("[%s] Adding %s & %s modes at index - %02i", name.c_str(), Direct.name.c_str(), Static.name.c_str(), i);
break;
}
case LOGITECH_DEVICE_LED_SPECTRUM:
{
mode Cycle;
Cycle.name = "Spectrum Cycle";
Cycle.value = i;
Cycle.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Cycle.color_mode = MODE_COLORS_NONE;
Cycle.brightness_min = LOGITECH_G_PRO_WIRELESS_BRIGHTNESS_MIN;
Cycle.brightness_max = LOGITECH_G_PRO_WIRELESS_BRIGHTNESS_MAX;
Cycle.brightness = LOGITECH_G_PRO_WIRELESS_BRIGHTNESS_MAX;
Cycle.speed_min = LOGITECH_G_PRO_WIRELESS_SPEED_SLOWEST;
Cycle.speed_max = LOGITECH_G_PRO_WIRELESS_SPEED_FASTEST;
Cycle.speed = LOGITECH_G_PRO_WIRELESS_SPEED_NORMAL;
modes.push_back(Cycle);
LOG_DEBUG("[%s] Adding %s mode at index - %02i", name.c_str(), Cycle.name.c_str(), i);
break;
}
case LOGITECH_DEVICE_LED_BREATHING:
{
mode Breathing;
Breathing.name = "Breathing";
Breathing.value = i;
Breathing.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_BRIGHTNESS;
Breathing.color_mode = MODE_COLORS_PER_LED;
Breathing.brightness_min = LOGITECH_G_PRO_WIRELESS_BRIGHTNESS_MIN;
Breathing.brightness_max = LOGITECH_G_PRO_WIRELESS_BRIGHTNESS_MAX;
Breathing.brightness = LOGITECH_G_PRO_WIRELESS_BRIGHTNESS_MAX;
Breathing.speed_min = LOGITECH_G_PRO_WIRELESS_SPEED_SLOWEST;
Breathing.speed_max = LOGITECH_G_PRO_WIRELESS_SPEED_FASTEST;
Breathing.speed = LOGITECH_G_PRO_WIRELESS_SPEED_NORMAL;
modes.push_back(Breathing);
LOG_DEBUG("[%s] Adding %s mode at index - %02i", name.c_str(), Breathing.name.c_str(), i);
break;
}
default:
LOG_WARNING("[%s] Effect at index - %02i not added: Value %04X unrecognised", name.c_str(), i, fx.fx[i].mode);
break;
}
}
SetupZones();
}
else
{
name = "Idle Lightspeed Device";
vendor = "Logitech";
type = DEVICE_TYPE_UNKNOWN;
description = "Idle Logitech Wireless Lightspeed Device";
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
}
}
RGBController_LogitechLightspeed::~RGBController_LogitechLightspeed()
{
delete controller;
}
void RGBController_LogitechLightspeed::SetupZones()
{
const std::string zone_string = "Zone";
const std::string led_string = "LED";
uint8_t led_count = controller->lightspeed->getLED_count();
LOG_DEBUG("[%s] Setting up %d LEDs", name.c_str(), led_count);
if(led_count > 0)
{
for(size_t i = 0; i < led_count; i++)
{
zone Lightspeed_logo_zone;
led Lightspeed_logo_led;
logitech_led new_led = controller->lightspeed->getLED_info((uint8_t)i);
if(new_led.location < NUM_LOGITECH_LED_LOCATIONS )
{
Lightspeed_logo_zone.name = logitech_led_locations[new_led.location];
Lightspeed_logo_zone.name.append(" ");
Lightspeed_logo_led.name = Lightspeed_logo_zone.name;
Lightspeed_logo_led.name.append(led_string);
Lightspeed_logo_zone.name.append(zone_string);
}
else
{
std::string name = " " + std::to_string(i);
Lightspeed_logo_zone.name = zone_string + name;
Lightspeed_logo_led.name = led_string + name;
}
Lightspeed_logo_zone.type = ZONE_TYPE_SINGLE;
Lightspeed_logo_zone.leds_min = 1;
Lightspeed_logo_zone.leds_max = 1;
Lightspeed_logo_zone.leds_count = 1;
Lightspeed_logo_zone.matrix_map = NULL;
zones.push_back(Lightspeed_logo_zone);
Lightspeed_logo_led.value = (unsigned int)i;
leds.push_back(Lightspeed_logo_led);
}
}
SetupColors();
}
void RGBController_LogitechLightspeed::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_LogitechLightspeed::DeviceUpdateLEDs()
{
for(std::vector<led>::iterator led_index = leds.begin(); led_index != leds.end(); led_index++)
{
UpdateZoneLEDs(led_index->value);
}
}
void RGBController_LogitechLightspeed::UpdateZoneLEDs(int zone)
{
unsigned char red = RGBGetRValue(colors[zone]);
unsigned char grn = RGBGetGValue(colors[zone]);
unsigned char blu = RGBGetBValue(colors[zone]);
controller->SendMouseMode(modes[active_mode].value, modes[active_mode].speed, zone, red, grn, blu, modes[active_mode].brightness);
}
void RGBController_LogitechLightspeed::UpdateSingleLED(int led)
{
UpdateZoneLEDs(led);
}
void RGBController_LogitechLightspeed::DeviceUpdateMode()
{
/*---------------------------------------------------------*\
| If direct mode is true, then sent the packet to put the |
| mouse in direct mode. This code will only be called when |
| we change modes as to not spam the device. |
\*---------------------------------------------------------*/
controller->lightspeed->setDirectMode(modes[active_mode].name == "Direct");
DeviceUpdateLEDs();
}
@@ -0,0 +1,36 @@
/*---------------------------------------------------------*\
| RGBController_LogitechLightspeed.h |
| |
| RGBController for Logitech Lightspeed |
| |
| TheRogueZeta 05 Aug 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechLightspeedController.h"
class RGBController_LogitechLightspeed : public RGBController
{
public:
RGBController_LogitechLightspeed(LogitechLightspeedController* controller_ptr);
~RGBController_LogitechLightspeed();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
uint16_t pid; //This is a workaround fix for G502 mode breathing / spectrum cycle swap
private:
LogitechLightspeedController* controller;
};
@@ -0,0 +1,929 @@
/*---------------------------------------------------------*\
| LogitechProtocolCommon.cpp |
| |
| Common functionality for Logitech RAP and FAP protocols |
| |
| Chris M (Dr_No) 04 May 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <LogitechProtocolCommon.h>
const char* logitech_led_locations[] =
{
"Unknown",
"Primary",
"Logo",
"Left",
"Right",
"Combined",
"Group One",
"Group Two",
"Group Three",
"Group Four",
"Group Five",
"Group Six"
};
const int NUM_LOGITECH_LED_LOCATIONS = sizeof(logitech_led_locations);
static std::vector<uint16_t> logitech_RGB_pages =
{
LOGITECH_HIDPP_PAGE_RGB_EFFECTS1,
LOGITECH_HIDPP_PAGE_RGB_EFFECTS2
};
int getWirelessDevice(usages device_usages, uint16_t pid, wireless_map *wireless_devices)
{
hid_device* dev_use1;
usages::iterator find_usage = device_usages.find(1);
if (find_usage == device_usages.end())
{
LOG_INFO("Unable get_Wireless_Device due to missing FAP Short Message (0x10) usage");
LOG_DEBUG("Dumping device usages:");
for(usages::iterator dev = device_usages.begin(); dev != device_usages.end(); dev++)
{
LOG_DEBUG("Usage index:\t%i", dev->first);
}
}
else
{
dev_use1 = find_usage->second;
/*-----------------------------------------------------------------*\
| Create a buffer for reads |
\*-----------------------------------------------------------------*/
blankFAPmessage response;
response.init();
shortFAPrequest get_connected_devices;
get_connected_devices.init(LOGITECH_RECEIVER_DEVICE_INDEX, LOGITECH_GET_REGISTER_REQUEST);
hid_write(dev_use1, get_connected_devices.buffer, get_connected_devices.size());
hid_read_timeout(dev_use1, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
bool wireless_notifications = response.data[1] & 1; //Connected devices is a flag
if (!wireless_notifications)
{
response.init(); //zero out the response
get_connected_devices.init(LOGITECH_RECEIVER_DEVICE_INDEX, LOGITECH_SET_REGISTER_REQUEST);
get_connected_devices.data[1] = 1;
hid_write(dev_use1, get_connected_devices.buffer, get_connected_devices.size());
hid_read_timeout(dev_use1, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
if(get_connected_devices.feature_index == 0x8F)
{
LOG_ERROR("Logitech Protocol error: %02X %02X %02X %02X %02X %02X %02X", get_connected_devices.report_id, get_connected_devices.device_index, get_connected_devices.feature_index, get_connected_devices.feature_command, get_connected_devices.data[0], get_connected_devices.data[1], get_connected_devices.data[2]);
}
}
response.init(); //zero out the response
get_connected_devices.init(LOGITECH_RECEIVER_DEVICE_INDEX, LOGITECH_GET_REGISTER_REQUEST);
get_connected_devices.feature_command = 0x02; //0x02 Connection State register. Essentially asking for count of paired devices
hid_write(dev_use1, get_connected_devices.buffer, get_connected_devices.size());
hid_read_timeout(dev_use1, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
unsigned int device_count = response.data[1];
LOG_INFO("Count of connected devices to %4X: %i", pid, device_count);
if (device_count > 0)
{
LOG_INFO("Faking a reconnect to get device list");
device_count++; //Add 1 to the device_count to include the receiver
response.init();
get_connected_devices.init(LOGITECH_RECEIVER_DEVICE_INDEX, LOGITECH_SET_REGISTER_REQUEST);
get_connected_devices.feature_index = LOGITECH_SET_REGISTER_REQUEST;
get_connected_devices.feature_command = 0x02; //0x02 Connection State register
get_connected_devices.data[0] = 0x02; //Writting 0x02 to the connection state register will ask the receiver to fake a reconnect of paired devices
hid_write(dev_use1, get_connected_devices.buffer, get_connected_devices.size());
for(size_t i = 0; i < device_count; i++)
{
blankFAPmessage devices;
devices.init();
hid_read_timeout(dev_use1, devices.buffer, devices.size(), LOGITECH_PROTOCOL_TIMEOUT);
unsigned int wireless_PID = (devices.data[2] << 8) | devices.data[1];
LOG_INFO("Connected Device Index %i:\tVirtualID=%04X\t\t%02X %02X %02X %02X %02X %02X %02X", i, wireless_PID, devices.buffer[0], devices.buffer[1], devices.buffer[2], devices.buffer[3], devices.buffer[4], devices.buffer[5], devices.buffer[6]);
/*-----------------------------------------------------------------*\
| We need to read the receiver from the HID device queue but |
| there is no need to add it as it's own device |
\*-----------------------------------------------------------------*/
if(devices.device_index != LOGITECH_RECEIVER_DEVICE_INDEX)
{
wireless_devices->emplace(wireless_PID, devices.device_index);
}
}
}
else
{
LOG_WARNING("No devices were found connected to receiver!");
}
}
return((int)wireless_devices->size());
}
logitech_device::logitech_device(char *path, usages _usages, uint8_t _device_index, bool _wireless)
{
device_index = _device_index;
location = path;
device_usages = _usages;
wireless = _wireless;
RGB_feature_index = 0;
mutex = nullptr;
initialiseDevice();
}
logitech_device::logitech_device(char *path, usages _usages, uint8_t _device_index, bool _wireless, std::shared_ptr<std::mutex> mutex_ptr)
{
device_index = _device_index;
location = path;
device_usages = _usages;
wireless = _wireless;
RGB_feature_index = 0;
mutex = mutex_ptr;
initialiseDevice();
}
logitech_device::~logitech_device()
{
for(usages::iterator dev = device_usages.begin(); dev != device_usages.end(); dev++)
{
hid_close(dev->second);
}
}
void logitech_device::initialiseDevice()
{
bool is_connected = connected();
flushReadQueue();
if(is_connected)
{
getDeviceName();
/*-----------------------------------------------------------------*\
| If this is running with DEBUG or higher loglevel then |
| dump the entire Feature list to log |
\*-----------------------------------------------------------------*/
if(LogManager::get()->getLoglevel() > 4)
{
getDeviceFeatureList(); //This will populate the feature list
}
/*-----------------------------------------------------------------*\
| Check device for known RGB Effects Feature pages & save the index |
\*-----------------------------------------------------------------*/
for(std::vector<uint16_t>::iterator page = logitech_RGB_pages.begin(); page != logitech_RGB_pages.end(); page++)
{
int feature_index = getFeatureIndex(*page);
if(feature_index > 0)
{
feature_list.emplace(*page, feature_index);
RGB_feature_index = feature_index;
break;
}
}
/*-----------------------------------------------------------------*\
| If there was no RGB Effect Feature page found |
| dump the entire Feature list to log |
\*-----------------------------------------------------------------*/
if (RGB_feature_index == 0)
{
LOG_INFO("[%s] Unable add this device due to missing RGB Effects Feature", device_name.c_str());
}
else
{
getRGBconfig();
}
}
}
bool logitech_device::is_valid()
{
bool is_connected = connected();
bool valid_test = false;
if(is_connected)
{
LOG_DEBUG("[%s] valid_test - type %i led_count - %i RGB_index - %i", device_name.c_str(), logitech_device_type, leds.size(), RGB_feature_index);
valid_test = !device_name.empty() // Check if device name exists
&& logitech_device_type <= 8 // Check if device type has a valid index
&& (device_name[0] >= 32 && device_name[0] < 122) // Check for non valid characters in device name
&& device_name.length() > 3 // Check for valid device names lenght
&& leds.size() > 0 // Check if a device has at least 1 led
&& RGB_feature_index > 0; // Check if a feature index is "valid"
}
else
{
LOG_INFO("Unable add this Logitech device: Not Connected");
}
return(valid_test);
}
bool logitech_device::connected()
{
/*-----------------------------------------------------------------*\
| If this is a wireless device test that it's connected |
| Wired devices will always be connected. |
\*-----------------------------------------------------------------*/
if(wireless)
{
bool test = false;
hid_device* dev_use1 = getDevice(1);
if(dev_use1)
{
shortFAPrequest get_connected_devices;
get_connected_devices.init(device_index, LOGITECH_GET_REGISTER_REQUEST);
get_connected_devices.feature_command = 0x02; //0x02 Connection State register. Essentially asking for count of paired devices
hid_write(dev_use1, get_connected_devices.buffer, get_connected_devices.size());
//This hid_read will not timeout as we need to be sure the wireless device is connected
hid_read(dev_use1, get_connected_devices.buffer, get_connected_devices.size());
test = (get_connected_devices.data[1] != 0x09); //ERR_RESOURCE_ERROR i.e. not currently connected
LOG_DEBUG("Wireless device index %i is %s - %02X %02X %02X", get_connected_devices.device_index,
(test ? "connected" : "disconnected"), get_connected_devices.data[0], get_connected_devices.data[1], get_connected_devices.data[2]);
}
return(test);
}
else
{
return(true);
}
}
uint8_t logitech_device::getLED_count()
{
return((uint8_t)leds.size());
}
logitech_led logitech_device::getLED_info(uint8_t LED_num)
{
/*-----------------------------------------------------------------*\
| Get all info about the LEDs and Zones |
\*-----------------------------------------------------------------*/
if(!(LED_num > leds.size()))
{
return(leds[LED_num]);
}
else
{
return(leds[0]);
}
}
void logitech_device::flushReadQueue()
{
/*-----------------------------------------------------------------*\
| Create a buffer for reads |
\*-----------------------------------------------------------------*/
blankFAPmessage response;
response.init();
for(usages::iterator dev = device_usages.begin(); dev != device_usages.end(); dev++)
{
//Flush the buffer
int flushed = 0;
int result = 1;
while( result > 0 )
{
result = hid_read_timeout(dev->second, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
if (result > 0)
{
flushed++;
}
}
//device_name has not yet been set so can not use it in the log
LOG_DEBUG("Preparing read queue for device %i - flushed %i packet%s", dev->first, flushed, ((flushed == 1) ? "" : "s"));
}
}
hid_device* logitech_device::getDevice(uint8_t usage_index)
{
/*-----------------------------------------------------------------*\
| Check the usage map for usage_index |
| Return the associated device if found otherwise a nullptr |
\*-----------------------------------------------------------------*/
#ifdef WIN32
usages::iterator find_usage = device_usages.find(usage_index);
#else
//Linux does not need bundle the device usages hence .begin()
usages::iterator find_usage = device_usages.begin();
#endif //WIN32
if (find_usage == device_usages.end())
{
LOG_INFO("Unable add this device due to missing FAP Message usage %i", usage_index);
return(nullptr);
}
else
{
return(find_usage->second);
}
}
uint8_t logitech_device::getFeatureIndex(uint16_t feature_page)
{
/*-----------------------------------------------------------------*\
| Get the feature index from the Root Index |
| Return the mapped feature_index of the given feature page |
| for this device or else return 0 |
\*-----------------------------------------------------------------*/
uint8_t feature_index = 0;
hid_device* dev_use2 = getDevice(2);
if(dev_use2)
{
blankFAPmessage response;
longFAPrequest get_index;
get_index.init(device_index, LOGITECH_HIDPP_PAGE_ROOT_IDX, LOGITECH_CMD_ROOT_GET_FEATURE);
get_index.data[0] = feature_page >> 8;
get_index.data[1] = feature_page & 0xFF;
hid_write(dev_use2, get_index.buffer, get_index.size());
hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
feature_index = response.data[0];
LOG_DEBUG("[%s] Feature Page %04X found @ index %02X - %02X %02X %02X %02X %02X %02X %02X %02X", device_name.c_str(), feature_page, feature_index,
response.data[0], response.data[1], response.data[2], response.data[3], response.data[4], response.data[5], response.data[6], response.data[7]);
}
return(feature_index);
}
uint16_t logitech_device::getFeaturePage(uint8_t feature_index)
{
/*-----------------------------------------------------------------*\
| Get the feature page from the feature_list |
| Return the mapped feature page given the feature index |
| for this device or else return 0 |
\*-----------------------------------------------------------------*/
rvrse_features rvrse_feature_list = reverse_map(feature_list);
rvrse_features::iterator find_page = rvrse_feature_list.find(feature_index);
if (find_page == rvrse_feature_list.end())
{
LOG_DEBUG("[%s] Feature index %02X not found!", device_name.c_str(), feature_index);
//TODO: Handle cache miss
return(0);
}
else
{
return(find_page->second);
}
}
int logitech_device::getDeviceFeatureList()
{
/*-----------------------------------------------------------------*\
| Check the usage map for usage2 (0x11 Long FAP Message) |
| then list all features for device |
\*-----------------------------------------------------------------*/
hid_device* dev_use2 = getDevice(2);
if(dev_use2)
{
/*-----------------------------------------------------------------*\
| Create a buffer for reads |
\*-----------------------------------------------------------------*/
blankFAPmessage response;
response.init();
/*-----------------------------------------------------------------*\
| Query the root index for the index of the feature list |
| This is done for safety as it is generaly at feature index 0x01 |
\*-----------------------------------------------------------------*/
int feature_index = getFeatureIndex(LOGITECH_HIDPP_PAGE_FEATURE_SET);
/*-----------------------------------------------------------------*\
| Get the count of Features |
\*-----------------------------------------------------------------*/
longFAPrequest get_count;
get_count.init(device_index, feature_index, LOGITECH_CMD_FEATURE_SET_GET_COUNT);
hid_write(dev_use2, get_count.buffer, get_count.size());
hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
unsigned int feature_count = response.data[0];
longFAPrequest get_features;
get_features.init(device_index, feature_index, LOGITECH_CMD_FEATURE_SET_GET_ID);
for(std::size_t i = 1; feature_list.size() < feature_count; i++ )
{
get_features.data[0] = (uint8_t)i;
hid_write(dev_use2, get_features.buffer, get_features.size());
hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
LOG_DEBUG("[%s] Feature %04X @ index: %02X", device_name.c_str(), (response.data[0] << 8) | response.data[1], i);
feature_list.emplace((uint16_t)((response.data[0] << 8) | response.data[1]), (uint8_t)i);
}
}
else
{
LOG_INFO("[%s] Unable get the feature index list - missing FAP Long Message (0x11) usage", device_name.c_str());
}
return((int)feature_list.size());
}
int logitech_device::getDeviceName()
{
/*-----------------------------------------------------------------*\
| Check the usage map for usage2 (0x11 Long FAP Message) |
| Then use it to get the name for this device |
\*-----------------------------------------------------------------*/
hid_device* dev_use2 = getDevice(2);
if(dev_use2)
{
/*-----------------------------------------------------------------*\
| Create a buffer for reads |
\*-----------------------------------------------------------------*/
blankFAPmessage response;
response.init();
/*-----------------------------------------------------------------*\
| Query the root index for the index of the name feature |
\*-----------------------------------------------------------------*/
int feature_index = getFeatureIndex(LOGITECH_HIDPP_PAGE_DEVICE_NAME_TYPE);
/*-----------------------------------------------------------------*\
| Get the device name length |
\*-----------------------------------------------------------------*/
if(feature_index > 0)
{
longFAPrequest get_length;
get_length.init(device_index, feature_index, LOTITECH_CMD_DEVICE_NAME_TYPE_GET_COUNT);
hid_write(dev_use2, get_length.buffer, get_length.size());
hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
unsigned int name_length = response.data[0];
LOG_DEBUG("[%s] Name Length %02i - %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", device_name.c_str(), name_length,
response.data[0], response.data[1], response.data[2], response.data[3], response.data[4], response.data[5], response.data[6], response.data[7],
response.data[8], response.data[9], response.data[10], response.data[11], response.data[12], response.data[13], response.data[14], response.data[15]);
longFAPrequest get_name;
get_name.init(device_index, feature_index, LOGITECH_CMD_DEVICE_NAME_TYPE_GET_DEVICE_NAME);
while(device_name.length() < name_length)
{
get_name.data[0] = (uint8_t)device_name.length(); //This sets the character index to get from the device
hid_write(dev_use2, get_name.buffer, get_name.size());
hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
std::string temp = (char *)&response.data;
device_name.append(temp);
LOG_DEBUG("[%s] Get Name %02i - %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", device_name.c_str(), device_name.length(),
response.data[0], response.data[1], response.data[2], response.data[3], response.data[4], response.data[5], response.data[6], response.data[7],
response.data[8], response.data[9], response.data[10], response.data[11], response.data[12], response.data[13], response.data[14], response.data[15]);
}
get_name.init(device_index, feature_index, LOGITECH_CMD_DEVICE_NAME_TYPE_GET_TYPE);
hid_write(dev_use2, get_name.buffer, get_name.size());
hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
logitech_device_type = response.data[0];
LOG_DEBUG("[%s] Get Type %02i - %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", device_name.c_str(), logitech_device_type,
response.data[0], response.data[1], response.data[2], response.data[3], response.data[4], response.data[5], response.data[6], response.data[7],
response.data[8], response.data[9], response.data[10], response.data[11], response.data[12], response.data[13], response.data[14], response.data[15]);
}
}
return((int)device_name.length());
}
void logitech_device::getRGBconfig()
{
/*-----------------------------------------------------------------*\
| Check the usage map for usage2 (0x11 Long FAP Message) |
| Then use it to get the name for this device |
\*-----------------------------------------------------------------*/
hid_device* dev_use2 = getDevice(2);
uint16_t feature_page = getFeaturePage(RGB_feature_index);
uint8_t led_response = 0;
uint8_t led_counter = 0;
if(dev_use2)
{
/*-----------------------------------------------------------------*\
| Create a buffer for reads |
\*-----------------------------------------------------------------*/
blankFAPmessage response;
response.init();
int result;
longFAPrequest get_count;
get_count.init(device_index, RGB_feature_index, LOGITECH_CMD_RGB_EFFECTS_GET_COUNT);
if(feature_page == LOGITECH_HIDPP_PAGE_RGB_EFFECTS1)
{
result = hid_write(dev_use2, get_count.buffer, get_count.size());
do
{
result = hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
LOG_DEBUG("[%s] FP8070 - LED Count - %02X : %04X %04X %04X %04X %02X %02X %02X %02X %02X %02X %02X", device_name.c_str(),
response.data[0], (response.data[1] << 8 | response.data[2]), (response.data[3] << 8 | response.data[4]), (response.data[5] << 8 | response.data[6]),
(response.data[7] << 8 | response.data[8]), response.data[9], response.data[10], response.data[11], response.data[12], response.data[13], response.data[14], response.data[15]);
} while ((result == 20) && (get_count.feature_index != response.feature_index) && (get_count.feature_command != response.feature_command));
led_response = response.data[0];
get_count.feature_command = LOGITECH_CMD_RGB_EFFECTS_GET_INFO;
for(size_t i = 0; i < led_response; i++)
{
get_count.data[0] = (uint8_t)i;
result = hid_write(dev_use2, get_count.buffer, get_count.size());
result = hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
LOG_DEBUG("[%s] FP8070 - LED %02i - %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", device_name.c_str(), get_count.data[0],
response.data[0], response.data[1], response.data[2], response.data[3], response.data[4], response.data[5], response.data[6], response.data[7],
response.data[8], response.data[9], response.data[10], response.data[11], response.data[12], response.data[13], response.data[14], response.data[15]);
if( result == 20 &&
get_count.feature_index == response.feature_index &&
get_count.feature_command == response.feature_command &&
response.data[0] != 0x10 &&
response.data[1] != 0x02
)
{
//If the response is the correct length (i.e. no USB error) and is for the RGB_feature_index and LOGITECH_CMD_RGB_EFFECTS_GET_INFO and no error occured with the led_counter then bump the counter
logitech_led new_led;
new_led.location = response.data[1] << 8 | response.data[2];
new_led.fx_count = response.data[3];
for(uint8_t i = 0; i < new_led.fx_count; i++)
{
blankFAPmessage fx_response;
fx_response.init();
longFAPrequest get_effect;
get_effect.init(device_index, RGB_feature_index, LOGITECH_CMD_RGB_EFFECTS_GET_CONTROL);
get_effect.data[0] = get_count.data[0];
get_effect.data[1] = i;
result = hid_write(dev_use2, get_effect.buffer, get_effect.size());
result = hid_read_timeout(dev_use2, fx_response.buffer, fx_response.size(), LOGITECH_PROTOCOL_TIMEOUT);
LOG_DEBUG("[%s] FP8070 - LED %02i Effect %02X - %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", device_name.c_str(), get_count.data[0], i,
fx_response.data[0], fx_response.data[1], fx_response.data[2], fx_response.data[3], fx_response.data[4], fx_response.data[5], fx_response.data[6], fx_response.data[7],
fx_response.data[8], fx_response.data[9], fx_response.data[10], fx_response.data[11], fx_response.data[12], fx_response.data[13], fx_response.data[14], fx_response.data[15]);
logitech_fx new_fx;
new_fx.index = i;
new_fx.mode = static_cast<LOGITECH_DEVICE_MODE>(fx_response.data[2] << 8 | fx_response.data[3]);
new_fx.speed = fx_response.data[6] << 8 | fx_response.data[7];
new_led.fx.push_back(new_fx);
}
leds.emplace(response.data[0], new_led);
}
}
}
else if(feature_page == LOGITECH_HIDPP_PAGE_RGB_EFFECTS2)
{
get_count.data[0] = 0xFF;
get_count.data[1] = 0xFF;
get_count.data[2] = 0;
get_count.data[3] = 0;
get_count.data[4] = 0;
result = hid_write(dev_use2, get_count.buffer, get_count.size());
result = hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
LOG_DEBUG("[%s] FP8071 - LED Count - %04X : %02X %04X %04X %04X %04X %02X %02X %02X %02X %02X %02X %02X", device_name.c_str(),
(response.data[1] << 8 | response.data[2]), response.data[0], (response.data[1] << 8 | response.data[2]), (response.data[3] << 8 | response.data[4]), (response.data[5] << 8 | response.data[6]),
(response.data[7] << 8 | response.data[8]), response.data[9], response.data[10], response.data[11], response.data[12], response.data[13], response.data[14], response.data[15]);
led_response = (response.data[1] << 8 | response.data[2]);
for(size_t i = 0; i < led_response; i++)
{
get_count.data[0] = (uint8_t)i;
get_count.data[1] = 0xFF;
get_count.data[2] = 0;
result = hid_write(dev_use2, get_count.buffer, get_count.size());
result = hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
LOG_DEBUG("[%s] FP8071 - LED %02i - %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", device_name.c_str(), i,
response.data[0], response.data[1], response.data[2], response.data[3], response.data[4], response.data[5], response.data[6], response.data[7],
response.data[8], response.data[9], response.data[10], response.data[11], response.data[12], response.data[13], response.data[14], response.data[15]);
logitech_led new_led;
new_led.location = response.data[2] << 8 | response.data[3];
new_led.fx_count = response.data[4];
for(uint8_t i = 0; i < new_led.fx_count; i++)
{
blankFAPmessage fx_response;
fx_response.init();
longFAPrequest get_effect;
get_effect.init(device_index, RGB_feature_index, LOGITECH_CMD_RGB_EFFECTS_GET_COUNT);
get_effect.data[0] = get_count.data[0];
get_effect.data[1] = i;
result = hid_write(dev_use2, get_effect.buffer, get_effect.size());
result = hid_read_timeout(dev_use2, fx_response.buffer, fx_response.size(), LOGITECH_PROTOCOL_TIMEOUT);
LOG_DEBUG("[%s] FP8071 - LED %02i Effect %02X - %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X", device_name.c_str(), get_count.data[0], i,
fx_response.data[0], fx_response.data[1], fx_response.data[2], fx_response.data[3], fx_response.data[4], fx_response.data[5], fx_response.data[6], fx_response.data[7],
fx_response.data[8], fx_response.data[9], fx_response.data[10], fx_response.data[11], fx_response.data[12], fx_response.data[13], fx_response.data[14], fx_response.data[15]);
logitech_fx new_fx;
new_fx.index = i;
new_fx.mode = static_cast<LOGITECH_DEVICE_MODE>(fx_response.data[2] << 8 | fx_response.data[3]);
new_fx.speed = fx_response.data[6] << 8 | fx_response.data[7];
new_led.fx.push_back(new_fx);
}
leds.emplace(response.data[0], new_led);
}
/*-----------------------------------------------------------------*\
| Set the config to SW control mode |
\*-----------------------------------------------------------------*/
set8071Effects(5);
}
/*get_count.feature_command = LOGITECH_CMD_RGB_EFFECTS_GET_STATE;
for(std::size_t i = 0; i < feature_count; i++ )
{
get_count.data[0] = i;
result = hid_write(dev_use2, get_count.buffer, get_count.size());
result = hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
}
get_count.feature_command = LOGITECH_CMD_RGB_EFFECTS_GET_CONFIG;
for(std::size_t i = 0; i < feature_count; i++ )
{
get_count.data[0] = i;
result = hid_write(dev_use2, get_count.buffer, get_count.size());
result = hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
}*/
}
LOG_DEBUG("[%s] led_response returned %i led_counter returned %i : setting controller to %i LED%s", device_name.c_str(), led_response, led_counter, leds.size(), ((leds.size() == 1) ? "" : "s"));
}
uint8_t logitech_device::setDirectMode(bool direct)
{
int result = 0;
/*-----------------------------------------------------------------*\
| Check the usage map for usage2 (0x11 Long FAP Message) |
| then set the device into direct mode via register 0x80 |
\*-----------------------------------------------------------------*/
hid_device* dev_use2 = getDevice(2);
if(dev_use2)
{
/*-----------------------------------------------------------------*\
| Create a buffer for reads |
\*-----------------------------------------------------------------*/
blankFAPmessage response;
response.init();
/*-----------------------------------------------------------------*\
| Turn the direct mode on or off via the RGB_feature_index |
\*-----------------------------------------------------------------*/
longFAPrequest set_direct;
set_direct.init(device_index, RGB_feature_index, LOGITECH_FP8070_SET_SW_CTL);
set_direct.data[0] = (direct) ? 1 : 0;
set_direct.data[1] = set_direct.data[0];
/*-----------------------------------------------------*\
| Send packet |
| This code has to be protected to avoid crashes when |
| this is called at the same time to change a powerplay |
| mat and its paired wireless mouse leds. It will |
| happen when using effects engines with high framerate |
\*-----------------------------------------------------*/
if(mutex)
{
std::lock_guard<std::mutex> guard(*mutex);
result = hid_write(dev_use2, set_direct.buffer, set_direct.size());
result = hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
}
else
{
result = hid_write(dev_use2, set_direct.buffer, set_direct.size());
result = hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
}
}
return(result);
}
uint8_t logitech_device::setMode(uint8_t mode, uint16_t speed, uint8_t zone, uint8_t red, uint8_t green, uint8_t blue, uint8_t brightness)
{
/*-----------------------------------------------------------------*\
| Check the usage map for usage2 (0x11 Long FAP Message) then |
| set the device mode via LOGITECH_CMD_RGB_EFFECTS_SET_CONTROL |
\*-----------------------------------------------------------------*/
hid_device* dev_use2 = getDevice(2);
uint16_t feature_page = getFeaturePage(RGB_feature_index);
int result = 0;
LOGITECH_DEVICE_MODE fx = leds[zone].fx[mode].mode;
if(dev_use2)
{
/*-----------------------------------------------------------------*\
| Create a buffer for reads |
\*-----------------------------------------------------------------*/
blankFAPmessage response;
response.init();
/*-----------------------------------------------------------------*\
| Set the mode via the RGB_feature_index |
\*-----------------------------------------------------------------*/
longFAPrequest set_mode;
bool fp8070 = (feature_page == LOGITECH_HIDPP_PAGE_RGB_EFFECTS1);
set_mode.init(
device_index,
RGB_feature_index,
(fp8070 ? (uint8_t)LOGITECH_FP8070_SET_EFFECT : (uint8_t)LOGITECH_FP8071_SET_LED_EFFECT)
);
set_mode.data[0] = zone;
set_mode.data[1] = mode;
set_mode.data[2] = red;
set_mode.data[3] = green;
set_mode.data[4] = blue;
set_mode.data[12] = fp8070 ? 0x00 : 0x01; //Bit 2-3 Power Mode : Bit 1-0 Persistence
speed *= 100;
switch(fx)
{
case LOGITECH_DEVICE_LED_ON:
//set_mode.data[5] = 0x02; //zone;
break;
case LOGITECH_DEVICE_LED_SPECTRUM:
set_mode.data[7] = speed >> 8;
set_mode.data[8] = speed & 0xFF;
set_mode.data[9] = brightness;
break;
case LOGITECH_DEVICE_LED_BREATHING:
set_mode.data[5] = speed >> 8;
set_mode.data[6] = speed & 0xFF;
//set_mode.data[7] = curve_type; //Value 0-6: Default, Sine, Square, Triangle, Sawtooth, Reverse_Sawtooth, Exponent
set_mode.data[8] = brightness;
break;
/*-----------------------------------------------------*\
| Place holders for later implementation |
\*-----------------------------------------------------*/
case LOGITECH_DEVICE_LED_OFF:
case LOGITECH_DEVICE_LED_WAVE:
case LOGITECH_DEVICE_LED_STAR:
case LOGITECH_DEVICE_LED_RIPPLE:
case LOGITECH_DEVICE_LED_CUSTOM:
default:
break;
}
/*-----------------------------------------------------*\
| Send packet |
| This code has to be protected to avoid crashes when |
| this is called at the same time to change a powerplay |
| mat and its paired wireless mouse leds. It will |
| happen when using effects engines with high framerate |
\*-----------------------------------------------------*/
if(mutex)
{
std::lock_guard<std::mutex> guard(*mutex);
result = hid_write(dev_use2, set_mode.buffer, set_mode.size());
result = hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
}
else
{
result = hid_write(dev_use2, set_mode.buffer, set_mode.size());
result = hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
}
}
return result;
}
uint8_t logitech_device::set8071Effects(uint8_t control)
{
int result = 0;
/*-----------------------------------------------------------------*\
| Check the usage map for usage2 (0x11 Long FAP Message) |
| then set the device into direct mode via register 0x80 |
\*-----------------------------------------------------------------*/
hid_device* dev_use2 = getDevice(2);
if(dev_use2)
{
/*-----------------------------------------------------------------*\
| Create a buffer for reads |
\*-----------------------------------------------------------------*/
blankFAPmessage response;
response.init();
/*-----------------------------------------------------------------*\
| Use longFAPrequest (20 bytes) for FP8071 CONTROL command |
| Short messages (7 bytes) are not supported by some devices |
\*-----------------------------------------------------------------*/
longFAPrequest set_effects;
set_effects.init(device_index, RGB_feature_index, LOGITECH_FP8071_CONTROL);
set_effects.data[0] = 1;
set_effects.data[1] = 3; //Disables all FW control for PWR (0x02) and RGB (0x01)
set_effects.data[2] = control;
/*-----------------------------------------------------*\
| Send packet |
| This code has to be protected to avoid crashes when |
| this is called at the same time to change a powerplay |
| mat and its paired wireless mouse leds. It will |
| happen when using effects engines with high framerate |
\*-----------------------------------------------------*/
if(mutex)
{
std::lock_guard<std::mutex> guard(*mutex);
hid_write(dev_use2, set_effects.buffer, set_effects.size());
result = hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
}
else
{
hid_write(dev_use2, set_effects.buffer, set_effects.size());
result = hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
}
/*-----------------------------------------------------*\
| Check for HID++ error response (0x8F in feature_index)|
\*-----------------------------------------------------*/
if(response.feature_index == 0x8F)
{
LOG_WARNING("[%s] set8071Effects: HID++ ERROR! ErrCode=%02X",
device_name.c_str(), response.data[2]);
}
}
return result;
}
uint8_t logitech_device::set8071TimeoutControl(uint8_t /*control*/)
{
int result = 0;
/*-----------------------------------------------------------------*\
| Check the usage map for usage2 (0x11 Long FAP Message) |
| then set the device into direct mode via register 0x80 |
\*-----------------------------------------------------------------*/
hid_device* dev_use2 = getDevice(2);
if(dev_use2)
{
/*-----------------------------------------------------------------*\
| Create a buffer for reads |
\*-----------------------------------------------------------------*/
blankFAPmessage response;
response.init();
/*-----------------------------------------------------------------*\
| Turn the direct mode on or off via the RGB_feature_index |
\*-----------------------------------------------------------------*/
longFAPrequest set_control;
set_control.init(device_index, RGB_feature_index, LOGITECH_FP8071_PWR_CFG);
set_control.data[0] = 1; //1;
set_control.data[3] = 0; //0x3C; //Inactive Lighting timeout MSB
set_control.data[4] = 5; //0x3C; //Inactive Lighting timeout LSB
set_control.data[5] = 0; //1; //Lights off timeout MSB
set_control.data[6] = 20; //0x2C; //Lights off timeout LSB
/*-----------------------------------------------------*\
| Send packet |
| This code has to be protected to avoid crashes when |
| this is called at the same time to change a powerplay |
| mat and its paired wireless mouse leds. It will |
| happen when using effects engines with high framerate |
\*-----------------------------------------------------*/
if(mutex)
{
std::lock_guard<std::mutex> guard(*mutex);
result = hid_write(dev_use2, set_control.buffer, set_control.size());
result = hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
}
else
{
result = hid_write(dev_use2, set_control.buffer, set_control.size());
result = hid_read_timeout(dev_use2, response.buffer, response.size(), LOGITECH_PROTOCOL_TIMEOUT);
}
}
return(result);
}
@@ -0,0 +1,317 @@
/*---------------------------------------------------------*\
| LogitechProtocolCommon.h |
| |
| Common functionality for Logitech RAP and FAP protocols |
| |
| Chris M (Dr_No) 04 May 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <map>
#include <vector>
#include <hidapi.h>
#include "LogManager.h"
#define LOGITECH_PROTOCOL_TIMEOUT 300 //Timeout in ms
#define LOGITECH_HEADER_SIZE 3
#define LOGITECH_SHORT_MESSAGE 0x10
#define LOGITECH_SHORT_MESSAGE_LEN 7
#define LOGITECH_LONG_MESSAGE 0x11
#define LOGITECH_LONG_MESSAGE_LEN 20
#define LOGITECH_FAP_RESPONSE_LEN 64 //Define a universal response buffer and allow the hidapi to determine the size
#define LOGITECH_DEFAULT_DEVICE_INDEX 0xFF
#define LOGITECH_RECEIVER_DEVICE_INDEX 0xFF //The Unifying receiver uses RAP or register access protocol
#define LOGITECH_SET_REGISTER_REQUEST 0x80
#define LOGITECH_GET_REGISTER_REQUEST 0x81
#define LOGITECH_HIDPP_PAGE_ROOT_IDX 0x00 //Used for querying the feature index
#define LOGITECH_CMD_ROOT_GET_FEATURE 0x01
#define LOGITECH_CMD_ROOT_GET_PROTOCOL 0x11
#define LOGITECH_HIDPP_PAGE_FEATURE_SET 0x0001
#define LOGITECH_CMD_FEATURE_SET_GET_COUNT 0x01
#define LOGITECH_CMD_FEATURE_SET_GET_ID 0x11
#define LOGITECH_HIDPP_PAGE_DEVICE_NAME_TYPE 0x0005
#define LOTITECH_CMD_DEVICE_NAME_TYPE_GET_COUNT 0x01
#define LOGITECH_CMD_DEVICE_NAME_TYPE_GET_DEVICE_NAME 0x11
#define LOGITECH_CMD_DEVICE_NAME_TYPE_GET_TYPE 0x21
#define LOGITECH_HIDPP_PAGE_RGB_EFFECTS1 0x8070
#define LOGITECH_HIDPP_PAGE_RGB_EFFECTS2 0x8071
#define LOGITECH_CMD_RGB_EFFECTS_GET_COUNT 0x00
#define LOGITECH_CMD_RGB_EFFECTS_GET_INFO 0x10
#define LOGITECH_CMD_RGB_EFFECTS_GET_CONTROL 0x20
#define LOGITECH_CMD_RGB_EFFECTS_SET_CONTROL 0x30
#define LOGITECH_CMD_RGB_EFFECTS_GET_STATE 0x40
#define LOGITECH_CMD_RGB_EFFECTS_SET_STATE 0x50
#define LOGITECH_CMD_RGB_EFFECTS_GET_CONFIG 0x60
#define LOGITECH_CMD_RGB_EFFECTS_SET_CONFIG 0x70
enum LOGITECH_DEVICE_TYPE
{
LOGITECH_DEVICE_TYPE_KEYBOARD = 0,
LOGITECH_DEVICE_TYPE_REMOTECONTROL = 1,
LOGITECH_DEVICE_TYPE_NUMPAD = 2,
LOGITECH_DEVICE_TYPE_MOUSE = 3,
LOGITECH_DEVICE_TYPE_MOUSEPAD = 4,
LOGITECH_DEVICE_TYPE_TRACKBALL = 5,
LOGITECH_DEVICE_TYPE_PRESENTER = 6,
LOGITECH_DEVICE_TYPE_RECEIVER = 7,
LOGITECH_DEVICE_TYPE_HEADSET = 8
};
enum LOGITECH_DEVICE_MODE
{
LOGITECH_DEVICE_LED_OFF = 0x0000,
LOGITECH_DEVICE_LED_ON = 0x0001,
LOGITECH_DEVICE_LED_SPECTRUM = 0x0003,
LOGITECH_DEVICE_LED_WAVE = 0x0004,
LOGITECH_DEVICE_LED_STAR = 0x0005,
LOGITECH_DEVICE_LED_BREATHING = 0x000A,
LOGITECH_DEVICE_LED_RIPPLE = 0x000B,
LOGITECH_DEVICE_LED_CUSTOM = 0x000C
};
enum LOGITECH_FP8070
{
LOGITECH_FP8070_INFO = 0x00,
LOGITECH_FP8070_ZONE_INFO = 0x10,
LOGITECH_FP8070_EFFECT_INFO = 0x20,
LOGITECH_FP8070_SET_EFFECT = 0x30,
LOGITECH_FP8070_SET_CFG = 0x40,
LOGITECH_FP8070_GET_CFG = 0x50,
LOGITECH_FP8070_GET_BIN_INFO = 0x60,
LOGITECH_FP8070_GET_SW_CTL = 0x70,
LOGITECH_FP8070_SET_SW_CTL = 0x80,
LOGITECH_FP8070_GET_STATUS = 0x90,
LOGITECH_FP8070_CLEAR_EFFECT = 0xA0,
LOGITECH_FP8070_SET_DIR = 0xB0,
LOGITECH_FP8070_GET_COLOUR = 0xC0,
LOGITECH_FP8070_SYNC_CFG = 0xD0,
LOGITECH_FP8070_GET_EFFECT = 0xE0,
LOGITECH_FP8070_SET_BIN_INFO = 0xF0,
};
enum LOGITECH_FP8071
{
LOGITECH_FP8071_INFO = 0x00,
LOGITECH_FP8071_SET_LED_EFFECT = 0x10,
LOGITECH_FP8071_ZONE_PATTERN = 0x20,
LOGITECH_FP8071_CONFIG = 0x30,
LOGITECH_FP8070_BIN_INFO = 0x40,
LOGITECH_FP8071_CONTROL = 0x50,
LOGITECH_FP8071_SYNC_CFG = 0x60,
LOGITECH_FP8071_PWR_CFG = 0x70,
LOGITECH_FP8071_PWR_MODE = 0x80,
LOGITECH_FP8071_SHUTDOWN = 0x90
};
enum LOGITECH_FP8071_FLAGS
{
FP8071_SUPPORTS_GET_STATUS = 0x01,
FP8071_RESERVED = 0x02,
FP8071_SUPPORTS_SET_BIN_INFO = 0x04,
FP8071_MONOCHROME_ONLY = 0x08,
FP8071_NO_SYNC_SUPPORT = 0x10,
FP8071_SUPPORTS_SHUTDOWN = 0x20,
FP8071_SUPPORTS_CLUSTER_CHANGED = 0x40,
};
extern const char* logitech_led_locations[];
extern const int NUM_LOGITECH_LED_LOCATIONS;
// Used for: {GET,SET}_REGISTER_{REQ,RSP}, SET_LONG_REGISTER_RSP, GET_LONG_REGISTER_REQ
struct message_short
{
unsigned char address;
unsigned char data[3];
};
// Used for: SET_LONG_REGISTER_REQ, GET_LONG_REGISTER_RSP
struct message_long
{
unsigned char address;
unsigned char data[16];
};
// Used for: ERROR_MSG
struct message_error
{
unsigned char sub_id;
unsigned char address;
unsigned char error_code;
unsigned char padding; /* set to 0 */
};
union shortFAPrequest
{
uint8_t buffer[LOGITECH_SHORT_MESSAGE_LEN];
struct
{
uint8_t report_id;
uint8_t device_index;
uint8_t feature_index;
uint8_t feature_command;
uint8_t data[LOGITECH_SHORT_MESSAGE_LEN - 4];
};
void init(uint8_t device_index, uint8_t feature_index)
{
this->report_id = LOGITECH_SHORT_MESSAGE;
this->device_index = device_index;
this->feature_index = feature_index;
this->feature_command = feature_command;
for(size_t i = 0; i < sizeof(data); i++)
{
this->data[i] = 0;
}
};
int size()
{
return LOGITECH_SHORT_MESSAGE_LEN;
};
};
union longFAPrequest
{
uint8_t buffer[LOGITECH_LONG_MESSAGE_LEN];
struct
{
uint8_t report_id;
uint8_t device_index;
uint8_t feature_index;
uint8_t feature_command;
uint8_t data[LOGITECH_LONG_MESSAGE_LEN - 4];
};
void init(uint8_t device_index, uint8_t feature_index, uint8_t feature_command)
{
this->report_id = LOGITECH_LONG_MESSAGE;
this->device_index = device_index;
this->feature_index = feature_index;
this->feature_command = feature_command;
for(size_t i = 0; i < sizeof(data); i++)
{
this->data[i] = 0;
}
};
int size()
{
return LOGITECH_LONG_MESSAGE_LEN;
};
};
union blankFAPmessage
{
uint8_t buffer[LOGITECH_FAP_RESPONSE_LEN];
struct
{
uint8_t report_id;
uint8_t device_index;
uint8_t feature_index;
uint8_t feature_command;
uint8_t data[LOGITECH_FAP_RESPONSE_LEN - 4];
};
//blank this buffer entirely
void init()
{
for(size_t i = 0; i < sizeof(buffer); i++)
{
this->buffer[i] = 0;
}
};
int size()
{
return LOGITECH_FAP_RESPONSE_LEN;
};
};
template<typename K, typename V>
static std::map<V, K> reverse_map(const std::map<K, V>& map)
{
std::map<V, K> reversed_map;
for(const std::pair<const K, V>& entry : map)
{
reversed_map[entry.second] = entry.first;
}
return reversed_map;
}
struct logitech_fx
{
uint8_t index;
uint16_t speed; //period
LOGITECH_DEVICE_MODE mode;
};
typedef std::map<uint8_t, hid_device*> usages;
typedef std::map<uint16_t, uint8_t> features;
typedef std::map<uint8_t, uint16_t> rvrse_features;
typedef std::map<uint16_t, uint8_t> wireless_map;
typedef std::vector<logitech_fx> leds_fx;
struct logitech_led
{
uint16_t location;
uint8_t fx_count;
leds_fx fx;
};
int getWirelessDevice(usages _usages, uint16_t pid, wireless_map *wireless_devices); //Helper function needed outside of class
class logitech_device
{
public:
logitech_device(char *path, usages _usages, uint8_t _device_index, bool _wireless);
logitech_device(char *path, usages _usages, uint8_t _device_index, bool _wireless, std::shared_ptr<std::mutex> mutex_ptr);
~logitech_device();
/*-----------------------------------------------------------------*\
| usages is a std::map that stores all the devices HID usages |
| This is to ensure that we can communicate to all windows usages |
\*-----------------------------------------------------------------*/
usages device_usages;
features feature_list;
uint8_t device_index;
uint8_t RGB_feature_index; //Stored for quick use
uint8_t logitech_device_type;
bool wireless;
std::string device_name;
std::string location;
std::string protocol_version;
bool connected();
bool is_valid();
void flushReadQueue();
uint8_t getFeatureIndex(uint16_t feature_page);
uint8_t getLED_count();
logitech_led getLED_info(uint8_t LED_num);
uint8_t setDirectMode(bool direct);
uint8_t setMode(uint8_t mode, uint16_t speed, uint8_t zone, uint8_t red, uint8_t green, uint8_t blue, uint8_t brightness);
uint8_t set8071Effects(uint8_t control);
uint8_t set8071TimeoutControl(uint8_t control);
int getDeviceName();
private:
std::map<uint8_t, logitech_led> leds;
std::shared_ptr<std::mutex> mutex;
hid_device* getDevice(uint8_t usage_index);
uint16_t getFeaturePage(uint8_t feature_index);
int getDeviceFeatureList();
void getRGBconfig();
void initialiseDevice();
};
@@ -0,0 +1,102 @@
/*---------------------------------------------------------*\
| LogitechX56Controller.cpp |
| |
| Driver for Logitech X56 |
| |
| Edbgon 11 Jun 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "LogitechX56Controller.h"
#include "StringUtils.h"
LogitechX56Controller::LogitechX56Controller(hid_device* dev_handle, const char* path, std::string dev_name)
{
dev = dev_handle;
location = path;
name = dev_name;
}
LogitechX56Controller::~LogitechX56Controller()
{
hid_close(dev);
}
std::string LogitechX56Controller::GetDeviceLocation()
{
return("HID: " + location);
}
std::string LogitechX56Controller::GetDeviceName()
{
return(name);
}
std::string LogitechX56Controller::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev, serial_string, 128);
if(ret != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
void LogitechX56Controller::SetColor(RGBColor color, uint8_t brightness)
{
unsigned char buf[X56_CONTROLLER_PACKET_SIZE];
unsigned char cbuf[X56_CONTROLLER_PACKET_SIZE];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(buf, 0x00, X56_CONTROLLER_PACKET_SIZE);
memset(cbuf, 0x00, X56_CONTROLLER_PACKET_SIZE);
/*-----------------------------------------------------*\
| Set up init packet |
\*-----------------------------------------------------*/
buf[0x00] = 0x09;
buf[0x02] = 0x02;
buf[0x03] = brightness;
/*-----------------------------------------------------*\
| Set up color packet |
\*-----------------------------------------------------*/
cbuf[0x00] = 0x09;
cbuf[0x02] = 0x03;
cbuf[0x03] = RGBGetRValue(color);
cbuf[0x04] = RGBGetGValue(color);
cbuf[0x05] = RGBGetBValue(color);
/*-----------------------------------------------------*\
| Send packets |
\*-----------------------------------------------------*/
hid_send_feature_report(dev, buf, X56_CONTROLLER_PACKET_SIZE);
hid_send_feature_report(dev, cbuf, X56_CONTROLLER_PACKET_SIZE);
}
void LogitechX56Controller::Save()
{
uint8_t buffer[X56_CONTROLLER_PACKET_SIZE];
/*-----------------------------------------------------*\
| Zero out buffer |
\*-----------------------------------------------------*/
memset(buffer, 0x00, X56_CONTROLLER_PACKET_SIZE);
/*-----------------------------------------------------*\
| Set up init packet |
\*-----------------------------------------------------*/
buffer[0x00] = 0x01;
buffer[0x01] = 0x01;
hid_send_feature_report(dev, buffer, X56_CONTROLLER_PACKET_SIZE);
}
@@ -0,0 +1,38 @@
/*---------------------------------------------------------*\
| LogitechX56Controller.h |
| |
| Driver for Logitech X56 |
| |
| Edbgon 11 Jun 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <string>
#include <hidapi.h>
#include "RGBController.h"
#define X56_CONTROLLER_PACKET_SIZE 64
class LogitechX56Controller
{
public:
LogitechX56Controller(hid_device* dev_handle, const char* path, std::string dev_name);
~LogitechX56Controller();
std::string GetDeviceLocation();
std::string GetDeviceName();
std::string GetSerialString();
void SetColor(RGBColor colors, uint8_t brightness);
void Save();
private:
hid_device* dev;
std::string location;
std::string name;
};
@@ -0,0 +1,110 @@
/*---------------------------------------------------------*\
| RGBController_LogitechX56.cpp |
| |
| RGBController for Logitech X56 |
| |
| Edbgon 11 Jun 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include "RGBController_LogitechX56.h"
/**------------------------------------------------------------------*\
@name Logitech X56
@category Gamepad
@type USB
@save :white_check_mark:
@direct :white_check_mark:
@effects :x:
@detectors DetectLogitechX56
@comment
\*-------------------------------------------------------------------*/
RGBController_LogitechX56::RGBController_LogitechX56(LogitechX56Controller* controller_ptr)
{
controller = controller_ptr;
name = controller->GetDeviceName();
vendor = "Logitech";
type = DEVICE_TYPE_GAMEPAD;
description = "Logitech X56 Device";
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
mode Direct;
Direct.name = "Direct";
Direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_MANUAL_SAVE | MODE_FLAG_HAS_BRIGHTNESS;
Direct.color_mode = MODE_COLORS_PER_LED;
Direct.brightness_min = 0x00;
Direct.brightness_max = 0x64;
Direct.brightness = 0x64;
modes.push_back(Direct);
SetupZones();
}
RGBController_LogitechX56::~RGBController_LogitechX56()
{
delete controller;
}
void RGBController_LogitechX56::SetupZones()
{
/*---------------------------------------------------------*\
| Each device has only one zone and LED |
\*---------------------------------------------------------*/
zone x56_zone;
x56_zone.name = "X56";
x56_zone.type = ZONE_TYPE_SINGLE;
x56_zone.leds_min = 1;
x56_zone.leds_max = 1;
x56_zone.leds_count = 1;
x56_zone.matrix_map = NULL;
zones.push_back(x56_zone);
led x56_led;
x56_led.name = "X56";
leds.push_back(x56_led);
SetupColors();
}
void RGBController_LogitechX56::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_LogitechX56::DeviceUpdateLEDs()
{
controller->SetColor(colors[0], modes[active_mode].brightness);
}
void RGBController_LogitechX56::UpdateZoneLEDs(int /*zone*/)
{
/*---------------------------------------------------------*\
| Packet expects both LEDs |
\*---------------------------------------------------------*/
DeviceUpdateLEDs();
}
void RGBController_LogitechX56::UpdateSingleLED(int /*led*/)
{
/*---------------------------------------------------------*\
| Packet expects both LEDs |
\*---------------------------------------------------------*/
DeviceUpdateLEDs();
}
void RGBController_LogitechX56::DeviceUpdateMode()
{
DeviceUpdateLEDs();
}
void RGBController_LogitechX56::DeviceSaveMode()
{
controller->Save();
}
@@ -0,0 +1,36 @@
/*---------------------------------------------------------*\
| RGBController_LogitechX56.h |
| |
| RGBController for Logitech X56 |
| |
| Edbgon 11 Jun 2021 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "LogitechX56Controller.h"
class RGBController_LogitechX56 : public RGBController
{
public:
RGBController_LogitechX56(LogitechX56Controller* controller_ptr);
~RGBController_LogitechX56();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
void DeviceSaveMode();
private:
LogitechX56Controller* controller;
};