Initial commit for the Steelseries Apex 3 TKL to resolve #1902

* Combining the Apex3 Full size and TKL controllers
* Cleanup of controllers for readability and optimisation
This commit is contained in:
Chris 2022-11-25 22:59:51 +11:00 committed by Adam Honse
parent df1c0a0dad
commit 085e1fca41
16 changed files with 491 additions and 642 deletions

View file

@ -0,0 +1,126 @@
/*-----------------------------------------*\
| RGBController_SteelSeriesApexTZone.cpp |
| |
| Edbgon 06.10.21 |
\*-----------------------------------------*/
#include "RGBController_SteelSeriesApex3.h"
/**------------------------------------------------------------------*\
@name Steel Series Apex Tri Zone Keyboards
@category Keyboard
@type USB
@save :x:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectSteelSeriesApexTZone
@comment
\*-------------------------------------------------------------------*/
RGBController_SteelSeriesApex3::RGBController_SteelSeriesApex3(SteelSeriesApex3Controller* controller_ptr)
{
controller = controller_ptr;
name = "SteelSeries Apex 3 device";
vendor = "SteelSeries";
type = DEVICE_TYPE_KEYBOARD;
description = name;
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
mode direct;
direct.name = "Direct";
direct.value = static_cast<int>(APEX3_MODES::DIRECT);
direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
if(controller->SupportsSave())
{
direct.flags |= MODE_FLAG_MANUAL_SAVE;
}
direct.color_mode = MODE_COLORS_PER_LED;
direct.brightness_min = STEELSERIES_APEX3_BRIGHTNESS_MIN;
direct.brightness_max = controller->GetMaxBrightness();
direct.brightness = direct.brightness_max;
modes.push_back(direct);
if(controller->SupportsRainbowWave())
{
mode rainbow;
rainbow.name = "Rainbow Wave";
rainbow.value = static_cast<int>(APEX3_MODES::RAINBOW_WAVE);
rainbow.flags = MODE_FLAG_HAS_BRIGHTNESS;
rainbow.color_mode = MODE_COLORS_NONE;
rainbow.brightness_min = STEELSERIES_APEX3_BRIGHTNESS_MIN;
rainbow.brightness_max = controller->GetMaxBrightness();
rainbow.brightness = rainbow.brightness_max;
modes.push_back(rainbow);
}
SetupZones();
}
RGBController_SteelSeriesApex3::~RGBController_SteelSeriesApex3()
{
delete controller;
}
void RGBController_SteelSeriesApex3::DeviceSaveMode()
{
controller->Save();
}
void RGBController_SteelSeriesApex3::SetupZones()
{
uint8_t led_count = controller->GetLedCount();
zone curr_zone;
curr_zone.name = "Keyboard";
curr_zone.type = ZONE_TYPE_LINEAR;
curr_zone.leds_min = led_count;
curr_zone.leds_max = led_count;
curr_zone.leds_count = led_count;
curr_zone.matrix_map = NULL;
zones.push_back(curr_zone);
for(size_t i = 0; i < curr_zone.leds_count; i++)
{
led zone_led;
zone_led.name = "LED " + std::to_string(i);
leds.push_back(zone_led);
}
SetupColors();
}
void RGBController_SteelSeriesApex3::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_SteelSeriesApex3::DeviceUpdateLEDs()
{
controller->SetColor(colors, modes[active_mode].value, modes[active_mode].brightness);
}
void RGBController_SteelSeriesApex3::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_SteelSeriesApex3::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_SteelSeriesApex3::DeviceUpdateMode()
{
if(modes[active_mode].color_mode == MODE_FLAG_HAS_PER_LED_COLOR)
{
DeviceUpdateLEDs();
}
else
{
controller->SetColor(modes[active_mode].colors, modes[active_mode].value, modes[active_mode].brightness);
}
}

View file

@ -7,13 +7,19 @@
#pragma once
#include "RGBController.h"
#include "SteelSeriesApexTZoneController.h"
#include "SteelSeriesApex3Controller.h"
class RGBController_SteelSeriesApexTZone : public RGBController
enum class APEX3_MODES
{
DIRECT = 0,
RAINBOW_WAVE = 1
};
class RGBController_SteelSeriesApex3 : public RGBController
{
public:
RGBController_SteelSeriesApexTZone(SteelSeriesApexTZoneController* apex_tzone_ptr);
~RGBController_SteelSeriesApexTZone();
RGBController_SteelSeriesApex3(SteelSeriesApex3Controller* controller_ptr);
~RGBController_SteelSeriesApex3();
void SetupZones();
void ResizeZone(int zone, int new_size);
@ -26,5 +32,5 @@ public:
void DeviceSaveMode();
private:
SteelSeriesApexTZoneController* controller;
SteelSeriesApex3Controller* controller;
};

View file

@ -1,127 +0,0 @@
/*-----------------------------------------*\
| RGBController_SteelSeriesApex3TKL.cpp |
| |
| Paul K. Gerke - 27.10.2022 |
| Dr_no (Chris M) |
\*-----------------------------------------*/
#include "RGBController_SteelSeriesApex3TKL.h"
/**------------------------------------------------------------------*\
@name Steel Series Apex 3 TKL Keyboard
@category Keyboard
@type USB
@save :x:
@direct :white_check_mark:
@effects :white_check_mark:
@detectors DetectSteelSeriesApex3TKL
@comment
\*-------------------------------------------------------------------*/
RGBController_SteelSeriesApex3TKL::RGBController_SteelSeriesApex3TKL(SteelSeriesApex3TKLController* controller_ptr)
{
controller = controller_ptr;
name = "Apex 3 TKL";
vendor = "SteelSeries";
type = DEVICE_TYPE_KEYBOARD;
description = "SteelSeries Apex 3 TKL device";
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
current_brightness = controller->GetBrightness();
mode direct;
direct.name = "Direct";
direct.value = static_cast<int>(APEX3_TKL_MODES::DIRECT);
direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS;
direct.color_mode = MODE_COLORS_PER_LED;
direct.brightness_min = STEELSERIES_APEX3TKL_BRIGHTNESS_MIN;
direct.brightness_max = STEELSERIES_APEX3TKL_BRIGHTNESS_MAX;
direct.brightness = current_brightness;
modes.push_back(direct);
mode rainbow;
rainbow.name = "Rainbow Wave";
rainbow.value = static_cast<int>(APEX3_TKL_MODES::RAINBOW);
rainbow.flags = MODE_FLAG_HAS_BRIGHTNESS;
rainbow.color_mode = MODE_COLORS_NONE;
rainbow.brightness_min = STEELSERIES_APEX3TKL_BRIGHTNESS_MIN;
rainbow.brightness_max = STEELSERIES_APEX3TKL_BRIGHTNESS_MAX;
rainbow.brightness = current_brightness;
modes.push_back(rainbow);
SetupZones();
}
RGBController_SteelSeriesApex3TKL::~RGBController_SteelSeriesApex3TKL()
{
delete controller;
}
void RGBController_SteelSeriesApex3TKL::SetupZones()
{
zone curr_zone;
curr_zone.name = "Keyboard";
curr_zone.type = ZONE_TYPE_LINEAR;
curr_zone.leds_min = 8;
curr_zone.leds_max = 8;
curr_zone.leds_count = 8;
curr_zone.matrix_map = NULL;
zones.push_back(curr_zone);
for(unsigned int i = 0; i < curr_zone.leds_count; i++)
{
led zone_led;
zone_led.name = "LED " + std::to_string(i);
leds.push_back(zone_led);
}
SetupColors();
}
void RGBController_SteelSeriesApex3TKL::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_SteelSeriesApex3TKL::DeviceUpdateLEDs()
{
controller->SetColor(colors);
UpdateBrightness();
}
void RGBController_SteelSeriesApex3TKL::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_SteelSeriesApex3TKL::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_SteelSeriesApex3TKL::DeviceUpdateMode()
{
switch (static_cast<APEX3_TKL_MODES>(active_mode)) {
case APEX3_TKL_MODES::DIRECT:
DeviceUpdateLEDs();
break;
case APEX3_TKL_MODES::RAINBOW:
controller->SetRainbowMode();
break;
}
UpdateBrightness();
}
void RGBController_SteelSeriesApex3TKL::UpdateBrightness()
{
// Taken from Aerox3 Controller to prevent OpenRGB from automatically
// overriding user-set brightness values via Mod+F11/F12
if (current_brightness != modes[active_mode].brightness) {
controller->SetBrightness(modes[active_mode].brightness);
current_brightness = modes[active_mode].brightness;
}
}

View file

@ -1,38 +0,0 @@
/*-----------------------------------------*\
| RGBController_SteelSeriesApex3TKL.h |
| |
| Paul K. Gerke - 27.10.2022 |
| Dr_no (Chris M) |
\*-----------------------------------------*/
#pragma once
#include "RGBController.h"
#include "SteelSeriesApex3TKLController.h"
enum class APEX3_TKL_MODES
{
DIRECT = 0,
RAINBOW = 1
};
class RGBController_SteelSeriesApex3TKL : public RGBController
{
public:
RGBController_SteelSeriesApex3TKL(SteelSeriesApex3TKLController* apex_tzone_ptr);
~RGBController_SteelSeriesApex3TKL();
void SetupZones();
void ResizeZone(int zone, int new_size);
void DeviceUpdateLEDs();
void UpdateZoneLEDs(int zone);
void UpdateSingleLED(int led);
void DeviceUpdateMode();
private:
SteelSeriesApex3TKLController* controller;
unsigned int current_brightness;
void UpdateBrightness();
};

View file

@ -1,101 +0,0 @@
/*-----------------------------------------*\
| RGBController_SteelSeriesApexTZone.cpp |
| |
| Edbgon 06.10.21 |
\*-----------------------------------------*/
#include "RGBController_SteelSeriesApexTZone.h"
/**------------------------------------------------------------------*\
@name Steel Series Apex Tri Zone Keyboards
@category Keyboard
@type USB
@save :x:
@direct :white_check_mark:
@effects :x:
@detectors DetectSteelSeriesApexTZone
@comment
\*-------------------------------------------------------------------*/
RGBController_SteelSeriesApexTZone::RGBController_SteelSeriesApexTZone(SteelSeriesApexTZoneController* controller_ptr)
{
controller = controller_ptr;
name = controller->GetDeviceName();
vendor = "SteelSeries";
type = DEVICE_TYPE_KEYBOARD;
description = "SteelSeries Apex 3 device";
location = controller->GetDeviceLocation();
serial = controller->GetSerialString();
mode direct;
direct.name = "Direct";
direct.value = 0;
direct.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_MANUAL_SAVE;
direct.color_mode = MODE_COLORS_PER_LED;
direct.brightness_min = STEELSERIES_TZ_BRIGHTNESS_MIN;
direct.brightness_max = STEELSERIES_TZ_BRIGHTNESS_MAX;
direct.brightness = STEELSERIES_TZ_BRIGHTNESS_MAX;
modes.push_back(direct);
SetupZones();
}
RGBController_SteelSeriesApexTZone::~RGBController_SteelSeriesApexTZone()
{
delete controller;
}
void RGBController_SteelSeriesApexTZone::DeviceSaveMode()
{
controller->Save();
}
void RGBController_SteelSeriesApexTZone::SetupZones()
{
zone curr_zone;
curr_zone.name = "Keyboard";
curr_zone.type = ZONE_TYPE_LINEAR;
curr_zone.leds_min = 10;
curr_zone.leds_max = 10;
curr_zone.leds_count = 10;
curr_zone.matrix_map = NULL;
zones.push_back(curr_zone);
for(unsigned int i = 1; i <= curr_zone.leds_count; i++)
{
led zone_led;
zone_led.name = "LED " + std::to_string(i);
leds.push_back(zone_led);
}
SetupColors();
}
void RGBController_SteelSeriesApexTZone::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_SteelSeriesApexTZone::DeviceUpdateLEDs()
{
controller->SetColor(colors, modes[active_mode].brightness);
}
void RGBController_SteelSeriesApexTZone::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_SteelSeriesApexTZone::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_SteelSeriesApexTZone::DeviceUpdateMode()
{
DeviceUpdateLEDs();
}

View file

@ -0,0 +1,43 @@
/*-------------------------------------------------------------------*\
| SteelSeriesApex3Controller.cpp |
| |
| Driver for Steelseries Apex3 Controllers |
| This is the base class for the Ten Zone and 8 Zone keyboards |
| |
| Chris M (Dr_No) 23rd Feb 2022 |
\*-------------------------------------------------------------------*/
#include <cstring>
#include "SteelSeriesApex3Controller.h"
SteelSeriesApex3Controller::SteelSeriesApex3Controller(hid_device* dev_handle, const char* path)
{
dev = dev_handle;
location = path;
}
SteelSeriesApex3Controller::~SteelSeriesApex3Controller()
{
hid_close(dev);
}
std::string SteelSeriesApex3Controller::GetDeviceLocation()
{
return("HID: " + location);
}
std::string SteelSeriesApex3Controller::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev, serial_string, 128);
if (ret != 0)
{
return("");
}
std::wstring return_wstring = serial_string;
std::string return_string(return_wstring.begin(), return_wstring.end());
return(return_string);
}

View file

@ -0,0 +1,42 @@
/*-------------------------------------------------------------------*\
| SteelSeriesApex3Controller.h |
| |
| Driver for Steelseries Apex3 Controllers |
| This is the base class for the Ten Zone and 8 Zone keyboards |
| |
| Chris M (Dr_No) 23rd Feb 2022 |
\*-------------------------------------------------------------------*/
#include <string>
#include <hidapi/hidapi.h>
#include "RGBController.h"
#include "SteelSeriesGeneric.h"
#pragma once
#define STEELSERIES_APEX3_BRIGHTNESS_MIN 0x00
#define STEELSERIES_APEX3_HID_TIMEOUT 100
class SteelSeriesApex3Controller
{
public:
SteelSeriesApex3Controller(hid_device* dev_handle, const char* path);
virtual ~SteelSeriesApex3Controller();
std::string GetDeviceLocation();
std::string GetSerialString();
steelseries_type GetKeyboardType();
virtual void SetColor(std::vector<RGBColor> colors, uint8_t mode, uint8_t brightness) = 0;
virtual void Save() = 0;
virtual uint8_t GetLedCount() = 0;
virtual uint8_t GetMaxBrightness() = 0;
virtual bool SupportsRainbowWave() = 0;
virtual bool SupportsSave() = 0;
hid_device* dev;
private:
std::string location;
};

View file

@ -1,123 +0,0 @@
/*-----------------------------------------*\
| SteelSeriesApex3TKLController.cpp |
| |
| Paul K. Gerke - 27.10.2022 |
| Dr_no (Chris M) |
\*-----------------------------------------*/
#include "SteelSeriesApex3TKLController.h"
#include <cstring>
#include <LogManager.h>
SteelSeriesApex3TKLController::SteelSeriesApex3TKLController
(
hid_device* dev_handle,
steelseries_type proto_type,
const char* path,
const char* device_name
)
{
dev = dev_handle;
location = path;
proto = proto_type;
strcpy(this->device_name, device_name);
}
SteelSeriesApex3TKLController::~SteelSeriesApex3TKLController()
{
hid_close(dev);
}
std::string SteelSeriesApex3TKLController::GetDeviceLocation()
{
return("HID: " + location);
}
char* SteelSeriesApex3TKLController::GetDeviceName()
{
return device_name;
}
std::string SteelSeriesApex3TKLController::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev, serial_string, 128);
if (ret != 0)
{
return("");
}
std::wstring return_wstring = serial_string;
std::string return_string(return_wstring.begin(), return_wstring.end());
return(return_string);
}
steelseries_type SteelSeriesApex3TKLController::GetKeyboardType()
{
return proto;
}
void SteelSeriesApex3TKLController::SetBrightness(unsigned char brightness)
{
unsigned char buf[STEELSERIES_APEX3TKL_WRITE_PACKET_SIZE];
memset(buf, 0x00, STEELSERIES_APEX3TKL_WRITE_PACKET_SIZE);
buf[0x01] = 0x23;
buf[0x02] = brightness;
hid_write(dev, buf, STEELSERIES_APEX3TKL_WRITE_PACKET_SIZE);
}
unsigned char SteelSeriesApex3TKLController::GetBrightness()
{
unsigned char buf[STEELSERIES_APEX3TKL_WRITE_PACKET_SIZE];
memset(buf, 0x00, STEELSERIES_APEX3TKL_WRITE_PACKET_SIZE);
buf[0x01] = 0xA3;
hid_write(dev, buf, STEELSERIES_APEX3TKL_WRITE_PACKET_SIZE);
int read_back = hid_read_timeout(dev, buf, STEELSERIES_APEX3TKL_WRITE_PACKET_SIZE, 100);
if (read_back >= 2 && buf[0x00] == 0xA3)
{
return(buf[0x01]);
}
LOG_ERROR("[%s] Failed load brightness; defaulting to max", device_name);
return(STEELSERIES_APEX3TKL_BRIGHTNESS_MAX);
}
void SteelSeriesApex3TKLController::SetRainbowMode()
{
unsigned char buf[STEELSERIES_APEX3TKL_WRITE_PACKET_SIZE];
/*-----------------------------------------------------*\
| Zero out buffer, set up packet and send |
\*-----------------------------------------------------*/
memset(buf, 0x00, STEELSERIES_APEX3TKL_WRITE_PACKET_SIZE);
buf[0] = 0x00;
buf[1] = 0x22;
buf[2] = 0xFF;
buf[3] = 0x00;
buf[4] = 0x00;
hid_write(dev, buf, STEELSERIES_APEX3TKL_WRITE_PACKET_SIZE);
}
void SteelSeriesApex3TKLController::SetColor(std::vector<RGBColor> colors)
{
unsigned char buf[STEELSERIES_APEX3TKL_WRITE_PACKET_SIZE];
memset(buf, 0x00, STEELSERIES_APEX3TKL_WRITE_PACKET_SIZE);
buf[0x01] = 0x21;
buf[0x02] = 0xFF;
for(unsigned int i = 0; i < colors.size(); i++)
{
uint8_t index = i * 3;
buf[index + 3] = RGBGetRValue(colors[i]);;
buf[index + 4] = RGBGetGValue(colors[i]);;
buf[index + 5] = RGBGetBValue(colors[i]);;
}
hid_write(dev, buf, STEELSERIES_APEX3TKL_WRITE_PACKET_SIZE);
}

View file

@ -1,152 +0,0 @@
/*-----------------------------------------*\
| SteelSeriesApex3TKLController.h |
| |
| Paul K. Gerke - 27.10.2022 |
| Dr_no (Chris M) |
| |
| Paul K: I attached my notes on reverse |
| engineering the protocol to the end of |
| this file |
\*-----------------------------------------*/
#include <string>
#include <hidapi/hidapi.h>
#include "SteelSeriesGeneric.h"
#include "SteelSeriesApexBaseController.h"
#pragma once
#define STEELSERIES_APEX3TKL_WRITE_PACKET_SIZE 65
#define STEELSERIES_APEX3TKL_BRIGHTNESS_MIN 0x00
#define STEELSERIES_APEX3TKL_BRIGHTNESS_MAX 0x10
class SteelSeriesApex3TKLController
{
public:
SteelSeriesApex3TKLController
(
hid_device* dev_handle,
steelseries_type proto_type,
const char* path,
const char* device_name
);
~SteelSeriesApex3TKLController();
std::string GetDeviceLocation();
char* GetDeviceName();
std::string GetSerialString();
steelseries_type GetKeyboardType();
unsigned char GetBrightness();
void SetBrightness(unsigned char brightness);
void SetColor(std::vector<RGBColor> colors);
void SetRainbowMode();
private:
char device_name[32];
hid_device* dev;
std::string location;
steelseries_type proto;
};
/*
# Reverse engineering protocol of APEX 3 TKL
Paul K. Gerke - 2022-10-27
I used a Windows 10 VM on my Linux host and virtually attached my
SteelSeries Apex3 TKL keyboard to the Windows VM using VirtualBox. On Windows,
I used Wireshark 4.x to listen to the USB-HID traffic between the SteelSeries
keyboard and the SteelSeries GG Software version 26.0.0.
## General keyboard behavior overview
- The keyboard does not seem to have persistent memory: Cycling the usb
connection resets to keyboard to a "rainbow" pattern mode. SteelSeries GG
has to be running to set color patterns, indicating that the PC software does
most of the heavy lifting.
- The keyboard has 8 colorzones from left to right. Similar to the ApexTZone
controller, but that controller did not work for me.
- The keyboard has a "rainbow" pattern mode and "constant color" settings for
each color zone.
- The brightness settings of the keyboard can somehow be read back by the
SteelSeries GG Software from the keyboard. Changing the brightness with
Mod+F11/F12 changes the slider in the SteelSeries GG software. Also, when
changing the brightness and opening the configuration dialog, the slider
is updated to the value set via Mod+F11/F12. I found some messages
relating to this, but could not identify the full mechanism how to query this
data.
## Message protocol
The HID-Messages contain a data-packet that is sent with HID-Requests to the
keyboard to change the keyboard's mode. The first byte seems to be a command-ID.
The full message-length is 64 byes (or 65 for the OpenRGB controller which
uses the first byte as deviceId selector).
Values not explicitly mentioned below are zero-bytes.
Command IDs:
- 0x21: Set color of the 8 color regions. Message format:
0x21 0xFF R1 G1 B1 R2 G2 B2 ... [up to rgb values for zone 8]
The 0xFF value seems to be constant - purpose unknown.
The next sequence of bytes consist of 8 x 3 byte-RGB data for each of the
three regions. Value range from 0 up to 255 for each color value (as you
might expect).
- 0x23: Set overall brightness of all color channels. Seems to act as a factor
for the individual RGB values, but can be individually adjusted by the user
through the Mod+F11/F12 key combinations - making it interesting to maybe
decouple overall-brightness from the overall color settings. Message format:
0x23 [brightness]
Valid brightness values are in the range from 0x00 (dark) to 0x10 (full
brightness);
- 0x22: Set the keyboard to "rainbow animation" mode. Message
Message format:
0x22 0xFF
As for 0x21, 0xFF seems to be constant, purpose unknown.
Other observed commands/messages of unknown purpose were:
- 0x6c 0x00 0x01 0x01
- I am half sure that this triggers some sort of URB_INTERRUPT from USB
device 1.2.2 reporting back some data. No idea what data though. It seems
to be pretty 0x6c followed by only zeroes.
- 0xa3 !!! Query brightness???
- 0x90
- I guess "get firmware version" or similar... or some other settings. Again
a complex answer is echoed from device 1.2.2 starting with 0x90 and a
few bytes (~8-10) of data. No idea what this is.
## Reading back the set brightness from the keyboard
I was able to _which_ messages contain the information, but not how to get them.
On windows, when adjusting the overall brightness using the Mod+F11/F12 key
combination or sometimes when using the SteelSeries GG software, the keyboard
sent a URB_INTERRUPT from the device labeled as "1.2.5" with HID-data that was
formatted as follows:
0x01 [brightness]
0x01 seems to be the data-type(?) and then brightness is a value in the range
from 0x00 (dark) to 0x10 (full brightness).
However, I was not able to capture these interrupts on my Linux machine when
pressing the brightness-buttons.
Also I was not able to find a message that tells the keyboard to report this
data to the PC. Somehow it only works on Windows. To be continued...?
*/

View file

@ -0,0 +1,95 @@
/*-------------------------------------------------------------------*\
| SteelSeriesApex8ZoneController.cpp |
| |
| Driver for Steelseries Apex3 TKL 8 Zone Keyboard |
| |
| Chris M (Dr_No) 23rd Feb 2022 |
| Paul K. Gerke 27.10.2022 |
\*-------------------------------------------------------------------*/
#include "LogManager.h"
#include <string>
#include "SteelSeriesApex8ZoneController.h"
SteelSeriesApex8ZoneController::SteelSeriesApex8ZoneController(hid_device* dev_handle, const char* path) : SteelSeriesApex3Controller(dev_handle, path)
{
}
SteelSeriesApex8ZoneController::~SteelSeriesApex8ZoneController()
{
}
uint8_t SteelSeriesApex8ZoneController::GetLedCount()
{
return STEELSERIES_8Z_LED_COUNT;
}
uint8_t SteelSeriesApex8ZoneController::GetMaxBrightness()
{
return STEELSERIES_8Z_BRIGHTNESS_MAX;
}
bool SteelSeriesApex8ZoneController::SupportsRainbowWave()
{
return true;
}
bool SteelSeriesApex8ZoneController::SupportsSave()
{
return false;
}
void SteelSeriesApex8ZoneController::Save()
{
/*---------------------------------------------------------*\
| This device does not yet support saving |
\*---------------------------------------------------------*/
}
void SteelSeriesApex8ZoneController::SetBrightness(uint8_t brightness)
{
uint8_t buffer[STEELSERIES_8Z_WRITE_PACKET_SIZE] = { 0x00, 0x23, brightness };
hid_write(dev, buffer, STEELSERIES_8Z_WRITE_PACKET_SIZE);
}
uint8_t SteelSeriesApex8ZoneController::GetBrightness()
{
uint8_t buffer[STEELSERIES_8Z_WRITE_PACKET_SIZE] = { 0x00, 0xA3 };
hid_write(dev, buffer, STEELSERIES_8Z_WRITE_PACKET_SIZE);
int result = hid_read_timeout(dev, buffer, STEELSERIES_8Z_WRITE_PACKET_SIZE, STEELSERIES_APEX3_HID_TIMEOUT);
if (result > 1 && buffer[0x00] == 0xA3)
{
return(buffer[0x01]);
}
return(STEELSERIES_8Z_BRIGHTNESS_MAX);
}
void SteelSeriesApex8ZoneController::SetColor(std::vector<RGBColor> colors, uint8_t mode, uint8_t brightness)
{
uint8_t buffer[STEELSERIES_8Z_WRITE_PACKET_SIZE] = { 0x00, 0x21, 0xFF };
buffer[1] += mode;
for(int i = 0; i < colors.size(); i++)
{
uint8_t index = i * 3;
buffer[index + 3] = RGBGetRValue(colors[i]);;
buffer[index + 4] = RGBGetGValue(colors[i]);;
buffer[index + 5] = RGBGetBValue(colors[i]);;
}
hid_write(dev, buffer, STEELSERIES_8Z_WRITE_PACKET_SIZE);
if(current_brightness != brightness)
{
SetBrightness(brightness);
}
}

View file

@ -0,0 +1,105 @@
/*-------------------------------------------------------------------*\
| SteelSeriesApex8ZoneController.cpp |
| |
| Driver for Steelseries Apex3 TKL 8 Zone Keyboard |
| |
| Chris M (Dr_No) 23rd Feb 2022 |
| Paul K. Gerke 27.10.2022 |
\*-------------------------------------------------------------------*/
#include <string>
#include <hidapi/hidapi.h>
#include "RGBController.h"
#include "SteelSeriesGeneric.h"
#include "SteelSeriesApex3Controller.h"
#pragma once
#define STEELSERIES_8Z_LED_COUNT 8
#define STEELSERIES_8Z_WRITE_PACKET_SIZE 65
#define STEELSERIES_8Z_BRIGHTNESS_MAX 0x10
class SteelSeriesApex8ZoneController : public SteelSeriesApex3Controller
{
public:
SteelSeriesApex8ZoneController(hid_device *dev_handle, const char *path);
~SteelSeriesApex8ZoneController();
void SetColor(std::vector<RGBColor> colors, uint8_t mode, uint8_t brightness);
void Save();
uint8_t GetLedCount();
uint8_t GetMaxBrightness();
bool SupportsRainbowWave();
bool SupportsSave();
private:
uint8_t current_brightness;
void SetBrightness(uint8_t brightness);
uint8_t GetBrightness();
};
/*-----------------------------------------------------------------------------------------------*\
# General keyboard behavior overview
- The keyboard does not appear to have persistent memory: Cycling the USB
connection resets the keyboard to a `Rainbow Wave` mode. SteelSeries GG
has to be running to set color patterns, indicating `Direct` mode.
- The brightness settings of the keyboard can be read back by the
SteelSeries GG Software from the keyboard. Changing the brightness with
Mod+F11/F12 moves the slider in the SteelSeries GG software. Also, when
changing the brightness and opening the configuration dialog, the slider
is updated to the value set via Mod+F11/F12.
# Message protocol
The HID-Messages contain a data-packet that is sent with HID-Requests to the
keyboard to change the keyboard's mode. The first byte seems to be a command-ID.
The full message-length is 64 bytes.
Values not explicitly mentioned below are zero-bytes.
## 0x21: Set LED color
0x21 BM R1 G1 B1 R2 G2 B2 ... [up to rgb values for zone 8]
- Bit Mask<br/>A bit mask of LEDs to set where 0xFF sets all LEDs. Setting 2<sup>N</sup>
as 0 will ***not*** set LED N.
- The next sequence of bytes are RGB triplets for each of the 8 LEDs.
Value range from 0 up to 255 for each color value.
## 0x22: Set the keyboard to `Rainbow Wave` mode. Message
0x22 0xFF
- As for 0x21, 0xFF seems to be constant.
## 0x23: Set Brightness
Sets the overall brightness of all color channels. Seems to act as a factor
for the individual RGB values, but can be individually adjusted by the user
through the Mod+F11/F12 key combinations.
0x23 [brightness]
- Brightness values are in the range from 0x00 (dark) to 0x10 (full
brightness)
# Other observed commands/messages of unknown purpose were:
## 0x6C 0x00 0x01 0x01
This triggers some sort of URB_INTERRUPT from USB endpoint .2
reporting back some data however unsure what the data is. It seems
to be 0x6C followed by only zeroes.
## 0xA3
Query brightness?
## 0x90
Best guess "get firmware version" or similar... or some other settings. Again
a complex answer is echoed from endpoint .2 starting with 0x90 and a
few bytes (~8-10) of data.
\*-----------------------------------------------------------------------------------------------*/

View file

@ -4,56 +4,37 @@
| Edbgon 06.10.21 |
\*-----------------------------------------*/
#include "SteelSeriesApexTZoneController.h"
#include <cstring>
SteelSeriesApexTZoneController::SteelSeriesApexTZoneController
(
hid_device* dev_handle,
steelseries_type proto_type,
const char* path
)
SteelSeriesApexTZoneController::SteelSeriesApexTZoneController(hid_device* dev_handle, const char* path) : SteelSeriesApex3Controller(dev_handle, path)
{
dev = dev_handle;
location = path;
proto = proto_type;
}
SteelSeriesApexTZoneController::~SteelSeriesApexTZoneController()
{
hid_close(dev);
}
std::string SteelSeriesApexTZoneController::GetDeviceLocation()
uint8_t SteelSeriesApexTZoneController::GetLedCount()
{
return("HID: " + location);
return STEELSERIES_TZ_LED_COUNT;
}
char* SteelSeriesApexTZoneController::GetDeviceName()
uint8_t SteelSeriesApexTZoneController::GetMaxBrightness()
{
return device_name;
return STEELSERIES_TZ_BRIGHTNESS_MAX;
}
std::string SteelSeriesApexTZoneController::GetSerialString()
bool SteelSeriesApexTZoneController::SupportsRainbowWave()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev, serial_string, 128);
if (ret != 0)
{
return("");
}
std::wstring return_wstring = serial_string;
std::string return_string(return_wstring.begin(), return_wstring.end());
return(return_string);
return false;
}
steelseries_type SteelSeriesApexTZoneController::GetKeyboardType()
bool SteelSeriesApexTZoneController::SupportsSave()
{
return proto;
return true;
}
void SteelSeriesApexTZoneController::Save()
@ -67,7 +48,7 @@ void SteelSeriesApexTZoneController::Save()
hid_write(dev, buf, STEELSERIES_TZ_WRITE_PACKET_SIZE);
}
void SteelSeriesApexTZoneController::SetColor(std::vector<RGBColor> colors, unsigned char brightness)
void SteelSeriesApexTZoneController::SetColor(std::vector<RGBColor> colors, uint8_t /*mode*/, uint8_t brightness)
{
unsigned char buf[STEELSERIES_TZ_WRITE_PACKET_SIZE] = { 0x00 };
@ -81,8 +62,7 @@ void SteelSeriesApexTZoneController::SetColor(std::vector<RGBColor> colors, unsi
hid_write(dev, buf, STEELSERIES_TZ_WRITE_PACKET_SIZE);
buf[0x01] = 0x0B;
for(unsigned int i = 0; i < colors.size(); i++)
for(size_t i = 0; i < colors.size(); i++)
{
uint8_t index = i * 3;

View file

@ -7,38 +7,29 @@
#include <string>
#include <hidapi/hidapi.h>
#include "RGBController.h"
#include "SteelSeriesGeneric.h"
#include "SteelSeriesApexBaseController.h"
#include "SteelSeriesApex3Controller.h"
#pragma once
#define STEELSERIES_TZ_LED_COUNT 10
#define STEELSERIES_TZ_WRITE_PACKET_SIZE 33
#define STEELSERIES_TZ_BRIGHTNESS_MIN 0x00
#define STEELSERIES_TZ_BRIGHTNESS_MAX 0x64
class SteelSeriesApexTZoneController
class SteelSeriesApexTZoneController : public SteelSeriesApex3Controller
{
public:
SteelSeriesApexTZoneController
(
hid_device* dev_handle,
steelseries_type proto_type,
const char* path
);
SteelSeriesApexTZoneController(hid_device *dev_handle, const char *path);
~SteelSeriesApexTZoneController();
std::string GetDeviceLocation();
char* GetDeviceName();
std::string GetSerialString();
steelseries_type GetKeyboardType();
void SetColor(std::vector<RGBColor> colors, unsigned char brightness);
void Save();
void SetColor(std::vector<RGBColor> colors, uint8_t mode, uint8_t brightness);
void Save();
uint8_t GetLedCount();
uint8_t GetMaxBrightness();
bool SupportsRainbowWave();
bool SupportsSave();
private:
char device_name[32];
hid_device* dev;
std::string location;
steelseries_type proto;
};

View file

@ -1,29 +1,30 @@
#include <hidapi/hidapi.h>
#include "Detector.h"
#include "RGBController.h"
#include "SteelSeriesGeneric.h"
#include "SteelSeriesAerox9Controller.h"
#include "SteelSeriesArctis5Controller.h"
#include "SteelSeriesApex8ZoneController.h"
#include "SteelSeriesApexController.h"
#include "SteelSeriesApexMController.h"
#include "SteelSeriesApexTZoneController.h"
#include "SteelSeriesOldApexController.h"
#include "SteelSeriesQCKMatController.h"
#include "SteelSeriesRivalController.h"
#include "SteelSeriesRival3Controller.h"
#include "SteelSeriesSiberiaController.h"
#include "SteelSeriesQCKMatController.h"
#include "SteelSeriesApexController.h"
#include "SteelSeriesApexTZoneController.h"
#include "SteelSeriesApex3TKLController.h"
#include "SteelSeriesOldApexController.h"
#include "SteelSeriesApexMController.h"
#include "SteelSeriesSenseiController.h"
#include "SteelSeriesGeneric.h"
#include "SteelSeriesArctis5Controller.h"
#include "SteelSeriesAerox9Controller.h"
#include "RGBController.h"
#include "SteelSeriesSiberiaController.h"
#include "RGBController_SteelSeriesArctis5.h"
#include "RGBController_SteelSeriesApex.h"
#include "RGBController_SteelSeriesApex3.h"
#include "RGBController_SteelSeriesOldApex.h"
#include "RGBController_SteelSeriesQCKMat.h"
#include "RGBController_SteelSeriesRival.h"
#include "RGBController_SteelSeriesRival3.h"
#include "RGBController_SteelSeriesSiberia.h"
#include "RGBController_SteelSeriesQCKMat.h"
#include "RGBController_SteelSeriesApex.h"
#include "RGBController_SteelSeriesApexTZone.h"
#include "RGBController_SteelSeriesApex3TKL.h"
#include "RGBController_SteelSeriesOldApex.h"
#include "RGBController_SteelSeriesSensei.h"
#include "RGBController_SteelSeriesArctis5.h"
#include <hidapi/hidapi.h>
#include "RGBController_SteelSeriesSiberia.h"
/*-----------------------------------------------------*\
| Vendor ID |
@ -113,13 +114,13 @@ void DetectSteelSeriesAerox9(hid_device_info* info, const std::string& name)
}
}
void DetectSteelSeriesApexTZone(hid_device_info* info, const std::string& name)
void DetectSteelSeriesApex3Full(hid_device_info* info, const std::string& name)
{
hid_device* dev = hid_open_path(info->path);
if(dev)
{
SteelSeriesApexTZoneController* controller = new SteelSeriesApexTZoneController(dev, APEX_TZONE, info->path);
RGBController_SteelSeriesApexTZone* rgb_controller = new RGBController_SteelSeriesApexTZone(controller);
SteelSeriesApexTZoneController* controller = new SteelSeriesApexTZoneController(dev, info->path);
RGBController_SteelSeriesApex3* rgb_controller = new RGBController_SteelSeriesApex3(controller);
rgb_controller->name = name;
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
@ -130,9 +131,9 @@ void DetectSteelSeriesApex3TKL(hid_device_info* info, const std::string& name)
hid_device* dev = hid_open_path(info->path);
if(dev)
{
SteelSeriesApex3TKLController* controller = new SteelSeriesApex3TKLController(dev, APEX_TZONE, info->path, name.c_str());
RGBController_SteelSeriesApex3TKL* rgb_controller = new RGBController_SteelSeriesApex3TKL(controller);
rgb_controller->name = name;
SteelSeriesApex8ZoneController* controller = new SteelSeriesApex8ZoneController(dev, info->path);
RGBController_SteelSeriesApex3* rgb_controller = new RGBController_SteelSeriesApex3(controller);
rgb_controller->name = name;
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
}
@ -345,13 +346,13 @@ REGISTER_HID_DETECTOR_I("SteelSeries QCK Prism Cloth 4XL",
/*-------------------------------------------------------------------------------------------------------------------------------------------------------------------------*\
| Keyboards |
\*-------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
REGISTER_HID_DETECTOR_I("SteelSeries Apex 3", DetectSteelSeriesApexTZone, STEELSERIES_VID, STEELSERIES_APEX_3_PID, 3 );
REGISTER_HID_DETECTOR_I("SteelSeries Apex 3 TKL", DetectSteelSeriesApex3TKL, STEELSERIES_VID, STEELSERIES_APEX_3_TKL_PID, 1 );
REGISTER_HID_DETECTOR_I("SteelSeries Apex 5", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_5_PID, 1 );
REGISTER_HID_DETECTOR_I("SteelSeries Apex 7", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_7_PID, 1 );
REGISTER_HID_DETECTOR_I("SteelSeries Apex 7 TKL", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_7_TKL_PID, 1 );
REGISTER_HID_DETECTOR_I("SteelSeries Apex Pro", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_PRO_PID, 1 );
REGISTER_HID_DETECTOR_I("SteelSeries Apex Pro TKL", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_PRO_TKL_PID, 1 );
REGISTER_HID_DETECTOR_I("SteelSeries Apex M750", DetectSteelSeriesApexM, STEELSERIES_VID, STEELSERIES_APEX_M750_PID, 2 );
REGISTER_HID_DETECTOR_I("SteelSeries Apex (OG)/Apex Fnatic", DetectSteelSeriesApexOld, STEELSERIES_VID, STEELSERIES_APEX_OG_PID, 0 );
REGISTER_HID_DETECTOR_I("SteelSeries Apex 350", DetectSteelSeriesApexOld, STEELSERIES_VID, STEELSERIES_APEX_350_PID, 0 );
REGISTER_HID_DETECTOR_I ("SteelSeries Apex 3", DetectSteelSeriesApex3Full, STEELSERIES_VID, STEELSERIES_APEX_3_PID, 3 );
REGISTER_HID_DETECTOR_IPU("SteelSeries Apex 3 TKL", DetectSteelSeriesApex3TKL, STEELSERIES_VID, STEELSERIES_APEX_3_TKL_PID, 1, 0xFFC0, 1 );
REGISTER_HID_DETECTOR_I ("SteelSeries Apex 5", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_5_PID, 1 );
REGISTER_HID_DETECTOR_I ("SteelSeries Apex 7", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_7_PID, 1 );
REGISTER_HID_DETECTOR_I ("SteelSeries Apex 7 TKL", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_7_TKL_PID, 1 );
REGISTER_HID_DETECTOR_I ("SteelSeries Apex Pro", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_PRO_PID, 1 );
REGISTER_HID_DETECTOR_I ("SteelSeries Apex Pro TKL", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_PRO_TKL_PID, 1 );
REGISTER_HID_DETECTOR_I ("SteelSeries Apex M750", DetectSteelSeriesApexM, STEELSERIES_VID, STEELSERIES_APEX_M750_PID, 2 );
REGISTER_HID_DETECTOR_I ("SteelSeries Apex (OG)/Apex Fnatic", DetectSteelSeriesApexOld, STEELSERIES_VID, STEELSERIES_APEX_OG_PID, 0 );
REGISTER_HID_DETECTOR_I ("SteelSeries Apex 350", DetectSteelSeriesApexOld, STEELSERIES_VID, STEELSERIES_APEX_350_PID, 0 );

View file

@ -30,5 +30,6 @@ typedef enum
APEX_TZONE = 0x0A,
RIVAL_700 = 0x0B,
AEROX_3 = 0x0C,
APEX_8ZONE = 0x0D,
} steelseries_type;

View file

@ -587,11 +587,12 @@ HEADERS +=
Controllers/SteelSeriesController/color32.h \
Controllers/SteelSeriesController/SteelSeriesAerox3Controller.h \
Controllers/SteelSeriesController/SteelSeriesAerox9Controller.h \
Controllers/SteelSeriesController/SteelSeriesApex3TKLController.h \
Controllers/SteelSeriesController/SteelSeriesApexBaseController.h \
Controllers/SteelSeriesController/SteelSeriesApexController.h \
Controllers/SteelSeriesController/SteelSeriesApexRegions.h \
Controllers/SteelSeriesController/SteelSeriesApex3Controller.h \
Controllers/SteelSeriesController/SteelSeriesApex8ZoneController.h \
Controllers/SteelSeriesController/SteelSeriesApexMController.h \
Controllers/SteelSeriesController/SteelSeriesApexRegions.h \
Controllers/SteelSeriesController/SteelSeriesApexTZoneController.h \
Controllers/SteelSeriesController/SteelSeriesArctis5Controller.h \
Controllers/SteelSeriesController/SteelSeriesGeneric.h \
@ -603,8 +604,7 @@ HEADERS +=
Controllers/SteelSeriesController/SteelSeriesSiberiaController.h \
Controllers/SteelSeriesController/SteelSeriesQCKMatController.h \
Controllers/SteelSeriesController/RGBController_SteelSeriesApex.h \
Controllers/SteelSeriesController/RGBController_SteelSeriesApex3TKL.h \
Controllers/SteelSeriesController/RGBController_SteelSeriesApexTZone.h \
Controllers/SteelSeriesController/RGBController_SteelSeriesApex3.h \
Controllers/SteelSeriesController/RGBController_SteelSeriesArctis5.h \
Controllers/SteelSeriesController/RGBController_SteelSeriesOldApex.h \
Controllers/SteelSeriesController/RGBController_SteelSeriesRival.h \
@ -1180,8 +1180,9 @@ SOURCES +=
Controllers/SRGBmodsController/RGBController_SRGBmodsPico.cpp \
Controllers/SteelSeriesController/SteelSeriesAerox3Controller.cpp \
Controllers/SteelSeriesController/SteelSeriesAerox9Controller.cpp \
Controllers/SteelSeriesController/SteelSeriesApex3TKLController.cpp \
Controllers/SteelSeriesController/SteelSeriesApexController.cpp \
Controllers/SteelSeriesController/SteelSeriesApex3Controller.cpp \
Controllers/SteelSeriesController/SteelSeriesApex8ZoneController.cpp \
Controllers/SteelSeriesController/SteelSeriesApexMController.cpp \
Controllers/SteelSeriesController/SteelSeriesApexTZoneController.cpp \
Controllers/SteelSeriesController/SteelSeriesArctis5Controller.cpp \
@ -1194,8 +1195,7 @@ SOURCES +=
Controllers/SteelSeriesController/SteelSeriesSiberiaController.cpp \
Controllers/SteelSeriesController/SteelSeriesQCKMatController.cpp \
Controllers/SteelSeriesController/RGBController_SteelSeriesApex.cpp \
Controllers/SteelSeriesController/RGBController_SteelSeriesApex3TKL.cpp \
Controllers/SteelSeriesController/RGBController_SteelSeriesApexTZone.cpp \
Controllers/SteelSeriesController/RGBController_SteelSeriesApex3.cpp \
Controllers/SteelSeriesController/RGBController_SteelSeriesArctis5.cpp \
Controllers/SteelSeriesController/RGBController_SteelSeriesOldApex.cpp \
Controllers/SteelSeriesController/RGBController_SteelSeriesRival.cpp \