Initial commit for SteelSeriesApex3TKLController to resolve #1902
This commit is contained in:
parent
88136152fc
commit
df1c0a0dad
6 changed files with 460 additions and 0 deletions
|
|
@ -0,0 +1,127 @@
|
|||
/*-----------------------------------------*\
|
||||
| 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*-----------------------------------------*\
|
||||
| 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();
|
||||
};
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
/*-----------------------------------------*\
|
||||
| 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);
|
||||
}
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
/*-----------------------------------------*\
|
||||
| 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...?
|
||||
|
||||
*/
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
#include "SteelSeriesQCKMatController.h"
|
||||
#include "SteelSeriesApexController.h"
|
||||
#include "SteelSeriesApexTZoneController.h"
|
||||
#include "SteelSeriesApex3TKLController.h"
|
||||
#include "SteelSeriesOldApexController.h"
|
||||
#include "SteelSeriesApexMController.h"
|
||||
#include "SteelSeriesSenseiController.h"
|
||||
|
|
@ -18,6 +19,7 @@
|
|||
#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"
|
||||
|
|
@ -77,6 +79,7 @@
|
|||
| Keyboard product IDs |
|
||||
\*-----------------------------------------------------*/
|
||||
#define STEELSERIES_APEX_3_PID 0x161A
|
||||
#define STEELSERIES_APEX_3_TKL_PID 0x1622
|
||||
#define STEELSERIES_APEX_5_PID 0x161C
|
||||
#define STEELSERIES_APEX_7_PID 0x1612
|
||||
#define STEELSERIES_APEX_7_TKL_PID 0x1618
|
||||
|
|
@ -122,6 +125,18 @@ void DetectSteelSeriesApexTZone(hid_device_info* info, const std::string& name)
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
ResourceManager::get()->RegisterRGBController(rgb_controller);
|
||||
}
|
||||
}
|
||||
|
||||
void DetectSteelSeriesApex(hid_device_info* info, const std::string& name)
|
||||
{
|
||||
hid_device* dev = hid_open_path(info->path);
|
||||
|
|
@ -331,6 +346,7 @@ 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 );
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue