mirror of
https://github.com/CalcProgrammer1/OpenRGB.git
synced 2026-04-21 14:27:26 -04:00
Apex Pro Gen 3 TKL wired, add media light and regional layout support
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
\*---------------------------------------------------------*/
|
||||
|
||||
#include "SteelSeriesApexBaseController.h"
|
||||
#include <algorithm>
|
||||
|
||||
SteelSeriesApexBaseController::SteelSeriesApexBaseController(hid_device* dev_handle, const char* path, std::string dev_name)
|
||||
{
|
||||
@@ -34,17 +35,15 @@ std::string SteelSeriesApexBaseController::GetName()
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------*\
|
||||
| The serial number of the keyboard is acquired by sending |
|
||||
| an output report to address 0xFF and reading the result. |
|
||||
| The HID capability table is not used. The serial number |
|
||||
| also contains the model number which can be used to |
|
||||
| determine the physical layout of different region |
|
||||
| keyboards throughout the product stack. |
|
||||
| Gen 1 Apex Pro stores the unit serial number in firmware. |
|
||||
| The first 5 digits determine the region of the keyboard. |
|
||||
| This is not the case for Gen 3, call to this function |
|
||||
| will be ignored. |
|
||||
\*---------------------------------------------------------*/
|
||||
std::string SteelSeriesApexBaseController::GetSerial()
|
||||
{
|
||||
std::string return_string = "";
|
||||
if(proto_type == APEX)
|
||||
if(proto_type == APEX && kbd_quirk == APEX_GEN1)
|
||||
{
|
||||
unsigned char obuf[STEELSERIES_PACKET_OUT_SIZE];
|
||||
unsigned char ibuf[STEELSERIES_PACKET_IN_SIZE];
|
||||
@@ -70,6 +69,27 @@ std::string SteelSeriesApexBaseController::GetSerial()
|
||||
return(return_string);
|
||||
}
|
||||
|
||||
std::string ExtractVersion(std::string version_string)
|
||||
{
|
||||
/*---------------------------------------------*\
|
||||
| Find 2 periods in string, if found we can |
|
||||
| form a X.Y.Z revision. |
|
||||
\*---------------------------------------------*/
|
||||
std::size_t majorp = version_string.find('.');
|
||||
if(majorp != std::string::npos)
|
||||
{
|
||||
std::size_t minorp = version_string.find('.', majorp+1);
|
||||
if(minorp != std::string::npos)
|
||||
{
|
||||
std::string major = version_string.substr(0, majorp);
|
||||
std::string minor = version_string.substr(majorp+1, (minorp-majorp-1));
|
||||
std::string build = version_string.substr(minorp+1);
|
||||
return major + "." + minor + "." + build;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string SteelSeriesApexBaseController::GetVersion()
|
||||
{
|
||||
std::string return_string = "Unsupported protocol";
|
||||
@@ -77,11 +97,9 @@ std::string SteelSeriesApexBaseController::GetVersion()
|
||||
if(proto_type == APEX)
|
||||
{
|
||||
/*-------------------------------------------------*\
|
||||
| For the Apex Pro there are two firmware versions |
|
||||
| which can be acquired, KBD and LED. We know |
|
||||
| where both are located, we do not know which is |
|
||||
| what. For now we'll make an assumption and fix |
|
||||
| if proven wrong. |
|
||||
| Gen 1 & 2 Apex Pro report KBD and LED firmware |
|
||||
| Gen 3 only reports the KBD firmware, ignoring |
|
||||
| requests to read the LED version |
|
||||
\*-------------------------------------------------*/
|
||||
unsigned char obuf[STEELSERIES_PACKET_OUT_SIZE];
|
||||
unsigned char ibuf[STEELSERIES_PACKET_IN_SIZE];
|
||||
@@ -96,50 +114,36 @@ std::string SteelSeriesApexBaseController::GetVersion()
|
||||
if(result > 0)
|
||||
{
|
||||
std::string fwver(ibuf, ibuf+STEELSERIES_PACKET_IN_SIZE);
|
||||
fwver = fwver.c_str();
|
||||
fwver.erase(std::remove(fwver.begin(), fwver.end(), '\0'), fwver.end());
|
||||
|
||||
/*---------------------------------------------*\
|
||||
| Find 2 periods in string, if found we can |
|
||||
| form a X.Y.Z revision. |
|
||||
| Apex Pro Gen 3 needs the first char dropped |
|
||||
\*---------------------------------------------*/
|
||||
std::size_t majorp = fwver.find('.');
|
||||
if(majorp != std::string::npos)
|
||||
if(kbd_quirk == APEX_GEN3)
|
||||
{
|
||||
std::size_t minorp = fwver.find('.', majorp+1);
|
||||
if(minorp != std::string::npos)
|
||||
{
|
||||
std::string major = fwver.substr(0, majorp);
|
||||
std::string minor = fwver.substr(majorp+1, (minorp-majorp-1));
|
||||
std::string build = fwver.substr(minorp+1);
|
||||
return_string = "KBD: " + major + "." + minor + "." + build;
|
||||
}
|
||||
fwver.erase(0,1);
|
||||
}
|
||||
|
||||
return_string = "KBD: " + ExtractVersion(fwver);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------*\
|
||||
| Clear and reuse buffer |
|
||||
\*-------------------------------------------------*/
|
||||
memset(ibuf, 0x00, sizeof(ibuf));
|
||||
obuf[0x02] = 0x01;
|
||||
hid_write(dev, obuf, STEELSERIES_PACKET_OUT_SIZE);
|
||||
result = hid_read_timeout(dev, ibuf, STEELSERIES_PACKET_IN_SIZE, 10);
|
||||
|
||||
if(result > 0)
|
||||
if(kbd_quirk != APEX_GEN3)
|
||||
{
|
||||
std::string fwver(ibuf, ibuf+STEELSERIES_PACKET_IN_SIZE);
|
||||
fwver = fwver.c_str();
|
||||
memset(ibuf, 0x00, sizeof(ibuf));
|
||||
obuf[0x02] = 0x01;
|
||||
hid_write(dev, obuf, STEELSERIES_PACKET_OUT_SIZE);
|
||||
result = hid_read_timeout(dev, ibuf, STEELSERIES_PACKET_IN_SIZE, 10);
|
||||
|
||||
std::size_t majorp = fwver.find('.');
|
||||
if(majorp != std::string::npos)
|
||||
if(result > 0)
|
||||
{
|
||||
std::size_t minorp = fwver.find('.', majorp+1);
|
||||
if(minorp != std::string::npos)
|
||||
{
|
||||
std::string major = fwver.substr(0, majorp);
|
||||
std::string minor = fwver.substr(majorp+1, (minorp-majorp-1));
|
||||
std::string build = fwver.substr(minorp+1);
|
||||
return_string = return_string + " / LED: " + major + "." + minor + "." + build;
|
||||
}
|
||||
std::string fwver(ibuf, ibuf+STEELSERIES_PACKET_IN_SIZE);
|
||||
fwver.erase(std::remove(fwver.begin(), fwver.end(), '\0'), fwver.end());
|
||||
fwver = fwver.c_str();
|
||||
|
||||
return_string = return_string + " / LED: " + ExtractVersion(fwver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,20 @@
|
||||
#define STEELSERIES_PACKET_IN_SIZE 64
|
||||
#define STEELSERIES_PACKET_OUT_SIZE STEELSERIES_PACKET_IN_SIZE + 1
|
||||
|
||||
/*-------------------------------------------------*\
|
||||
| Gen 1: 2019-22 models (all FW) & 2023 FW < 1.19.7 |
|
||||
| Gen 2: 2023 models with FW >= 1.19.7 |
|
||||
| Gen 3: 2025+ and may feature Gen 3 in the name |
|
||||
\*-------------------------------------------------*/
|
||||
|
||||
typedef enum
|
||||
{
|
||||
APEX_GEN1 = 0x00,
|
||||
APEX_GEN2 = 0x01,
|
||||
APEX_GEN3 = 0x02,
|
||||
|
||||
} protocol_quirk;
|
||||
|
||||
class SteelSeriesApexBaseController
|
||||
{
|
||||
public:
|
||||
@@ -41,4 +55,5 @@ protected:
|
||||
unsigned char active_mode;
|
||||
std::string location;
|
||||
std::string name;
|
||||
protocol_quirk kbd_quirk;
|
||||
};
|
||||
|
||||
@@ -33,18 +33,31 @@ static unsigned int keys[] = {0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x
|
||||
0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xF0, 0x31, 0x87,
|
||||
0x88, 0x89, 0x8A, 0x8B, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, //100
|
||||
0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62,
|
||||
0x63 };
|
||||
0x63, 0xFB };
|
||||
|
||||
SteelSeriesApexController::SteelSeriesApexController(hid_device* dev_handle, steelseries_type type, const char* path, std::string dev_name) : SteelSeriesApexBaseController(dev_handle, path, dev_name)
|
||||
{
|
||||
proto_type = type;
|
||||
use_new_protocol = false;
|
||||
kbd_quirk = APEX_GEN1;
|
||||
|
||||
SendInitialization();
|
||||
}
|
||||
|
||||
SteelSeriesApexController::~SteelSeriesApexController()
|
||||
{
|
||||
/*-----------------------------------------------------*\
|
||||
| Gen 3 models must be explicitly cleared for on-board |
|
||||
| config selection to apply without power cycling after |
|
||||
| OpenRGB shuts down. |
|
||||
\*-----------------------------------------------------*/
|
||||
if(kbd_quirk == APEX_GEN3)
|
||||
{
|
||||
unsigned char obuf[STEELSERIES_PACKET_OUT_SIZE];
|
||||
memset(obuf, 0x00, sizeof(obuf));
|
||||
obuf[0x00] = 0;
|
||||
obuf[0x01] = APEX_GEN3_PACKET_CLEAR_LIGHTING;
|
||||
hid_write(dev, obuf, STEELSERIES_PACKET_OUT_SIZE);
|
||||
}
|
||||
hid_close(dev);
|
||||
}
|
||||
|
||||
@@ -70,7 +83,7 @@ void SteelSeriesApexController::SetLEDsDirect(std::vector<RGBColor> colors)
|
||||
|
||||
num_keys = sizeof(keys) / sizeof(*keys);
|
||||
|
||||
if(use_new_protocol)
|
||||
if(kbd_quirk >= APEX_GEN2)
|
||||
{
|
||||
struct hid_device_info* info = hid_get_device_info(dev);
|
||||
|
||||
@@ -101,7 +114,7 @@ void SteelSeriesApexController::SetLEDsDirect(std::vector<RGBColor> colors)
|
||||
\*-----------------------------------------------------*/
|
||||
buf[0x00] = 0;
|
||||
buf[0x01] = packet_id;
|
||||
buf[0x02] = (use_new_protocol) ? (unsigned char)colors.size() : num_keys;
|
||||
buf[0x02] = kbd_quirk ? (unsigned char)colors.size() : num_keys;
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Fill in color data |
|
||||
@@ -151,7 +164,7 @@ void SteelSeriesApexController::SendInitialization()
|
||||
unsigned short pid = (info) ? info->product_id : 0;
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Firmware check |
|
||||
| Firmware check for TKL 2023 |
|
||||
\*-----------------------------------------------------*/
|
||||
if(pid == 0x1628)
|
||||
{
|
||||
@@ -192,17 +205,17 @@ void SteelSeriesApexController::SendInitialization()
|
||||
\*-----------------------------------------*/
|
||||
if(major > 1)
|
||||
{
|
||||
use_new_protocol = true;
|
||||
kbd_quirk = APEX_GEN2;
|
||||
}
|
||||
else if(major == 1)
|
||||
{
|
||||
if(minor > 19)
|
||||
{
|
||||
use_new_protocol = true;
|
||||
kbd_quirk = APEX_GEN2;
|
||||
}
|
||||
else if(minor == 19 && patch >= 7)
|
||||
{
|
||||
use_new_protocol = true;
|
||||
kbd_quirk = APEX_GEN2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,13 +230,13 @@ void SteelSeriesApexController::SendInitialization()
|
||||
|| pid == 0x162C || pid == 0x162D
|
||||
|| pid == 0x1642 || pid == 0x1644 || pid == 0x1646)
|
||||
{
|
||||
use_new_protocol = true;
|
||||
kbd_quirk = APEX_GEN3;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------*\
|
||||
| Send Initialization packet on new protocol. |
|
||||
\*-----------------------------------------------------*/
|
||||
if(use_new_protocol)
|
||||
if(kbd_quirk >= APEX_GEN2)
|
||||
{
|
||||
memset(buf, 0x00, sizeof(buf));
|
||||
buf[0x00] = 0x00;
|
||||
@@ -240,8 +253,64 @@ void SteelSeriesApexController::SendInitialization()
|
||||
|
||||
std::string SteelSeriesApexController::GetSerial()
|
||||
{
|
||||
if(use_new_protocol)
|
||||
/*-------------------------------------------------*\
|
||||
| Gen 3 doesn't expose the serial number in |
|
||||
| firmware. A region code is instead set by the |
|
||||
| user and subsequently read back. This region code |
|
||||
| is used by all 5 on-board configs. |
|
||||
| For consistency with other Apex keyboards, this |
|
||||
| region code in combination with the PID is mapped |
|
||||
| to an approximate product number as the region |
|
||||
| patch logic from that point is identical. |
|
||||
| The product number used may not be an exact match |
|
||||
| for the keyboard but should reflect the form |
|
||||
| factor, region and RGB layout |
|
||||
\*-------------------------------------------------*/
|
||||
if(kbd_quirk >= APEX_GEN2)
|
||||
{
|
||||
unsigned char obuf[STEELSERIES_PACKET_OUT_SIZE];
|
||||
unsigned char ibuf[STEELSERIES_PACKET_IN_SIZE];
|
||||
int result;
|
||||
struct hid_device_info* info = hid_get_device_info(dev);
|
||||
unsigned short pid = (info) ? info->product_id : 0;
|
||||
|
||||
memset(obuf, 0x00, sizeof(obuf));
|
||||
|
||||
if(pid == 0x1642)
|
||||
{
|
||||
obuf[0x00] = 0;
|
||||
obuf[0x01] = 0xF5;
|
||||
hid_write(dev, obuf, STEELSERIES_PACKET_OUT_SIZE);
|
||||
result = hid_read_timeout(dev, ibuf, STEELSERIES_PACKET_IN_SIZE, 2);
|
||||
|
||||
if(result > 3 && ibuf[0] == 0xF5)
|
||||
{
|
||||
switch(ibuf[2])
|
||||
{
|
||||
case 0x1:
|
||||
return "64740";
|
||||
break;
|
||||
case 0x3:
|
||||
return "64741";
|
||||
break;
|
||||
case 0x4:
|
||||
return "64743";
|
||||
break;
|
||||
case 0x6:
|
||||
return "64744";
|
||||
break;
|
||||
case 0xA:
|
||||
return "64742";
|
||||
break;
|
||||
case 0xD:
|
||||
return "64745";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "64865";
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ enum
|
||||
APEX_2023_PACKET_ID_DIRECT_WIRELESS = 0x61, /* New Wireless Direct mode */
|
||||
APEX_2023_PACKET_ID_INIT = 0x4B, /* New Initialization */
|
||||
APEX_2023_PACKET_LENGTH = 643,
|
||||
APEX_GEN3_PACKET_CLEAR_LIGHTING = 0x41,
|
||||
};
|
||||
|
||||
class SteelSeriesApexController : public SteelSeriesApexBaseController
|
||||
@@ -55,5 +56,4 @@ private:
|
||||
|
||||
void SendInitialization();
|
||||
|
||||
bool use_new_protocol;
|
||||
};
|
||||
|
||||
@@ -20,17 +20,16 @@
|
||||
#define NA 0xFFFFFFFF
|
||||
|
||||
/*----------------------------------------------------------------------*\
|
||||
| As of firmware 4.1.0 there are in total 111 possible standard keys |
|
||||
| which are shared across the Apex Pro / 7 / TKL / 5 and their regional |
|
||||
| SKUs in addition to the 6 media keys. No SKU has all 111, however |
|
||||
| regardless of the physial layout, the one configuration can be used |
|
||||
| across all SKUs with missing keys simply having no effect. |
|
||||
| The complication comes in the visualisation as different key layouts |
|
||||
| change the LED positions, additionally some labels / scancodes are |
|
||||
| overloaded based on the language which clashes with the OpenRGB |
|
||||
| defaults. In order to account for this a base SKU (ANSI) is assumed |
|
||||
| which is transformed into a regional SKU when device detection returns |
|
||||
| a known SKU number from the first 5 characters of the serial number. |
|
||||
| Steelseries keyboards share a library of 111 standard keys plus extra |
|
||||
| ambients across all physical form factors. No keyboard has every key |
|
||||
| fitted but the same format packet can be sent regardless of model and |
|
||||
| the firmware will pick the keys applicable to it, ignoring the rest. |
|
||||
| The complication comes in commuicating this to the OpenRGB GUI as |
|
||||
| different key layouts change the LED positions, additionally some |
|
||||
| labels / scancodes are overloaded based on the language which clashes |
|
||||
| with the OpenRGB defaults. In order to account for this a base SKU |
|
||||
| (ANSI) is assumed which is transformed into a regional SKU when device |
|
||||
| detection returns a known product number acquired from the keyboard |
|
||||
\*----------------------------------------------------------------------*/
|
||||
|
||||
#define MATRIX_HEIGHT 6
|
||||
@@ -50,6 +49,10 @@
|
||||
|
||||
static const int matrix_mapsize = MATRIX_HEIGHT * MATRIX_WIDTH;
|
||||
|
||||
/*-------------------------------------------------------*\
|
||||
| These map to the values defined in the keys array at |
|
||||
| SteelSeriesApexController |
|
||||
\*-------------------------------------------------------*/
|
||||
static const char* led_names[] =
|
||||
{
|
||||
KEY_EN_A,
|
||||
@@ -163,6 +166,7 @@ static const char* led_names[] =
|
||||
KEY_EN_NUMPAD_9,
|
||||
KEY_EN_NUMPAD_0,
|
||||
KEY_EN_NUMPAD_PERIOD,
|
||||
KEY_EN_MEDIA_PLAY_PAUSE
|
||||
};
|
||||
|
||||
struct matrix_region_patch
|
||||
@@ -244,7 +248,7 @@ static const std::vector<matrix_region_patch> apex_tkl_us_region_patch =
|
||||
{
|
||||
{0, 15, NA},
|
||||
{0, 16, NA},
|
||||
{0, 17, NA},
|
||||
{0, 17, 111},
|
||||
{1, 18, NA},
|
||||
{1, 19, NA},
|
||||
{1, 20, NA},
|
||||
@@ -367,6 +371,9 @@ static const std::map<std::string, sku_patch> patch_lookup =
|
||||
{ "64660", { {}, {}, {} }},
|
||||
|
||||
{ "64740", { apex_tkl_us_region_patch, {}, {} }},
|
||||
{ "64742", { apex_tkl_us_region_patch, apex_iso_region_patch, apex_nor_keyname_lookup }},
|
||||
{ "64743", { apex_tkl_us_region_patch, apex_iso_region_patch, apex_uk_keyname_lookup }},
|
||||
{ "64745", { apex_tkl_us_region_patch, apex_jp_region_patch, apex_jp_keyname_lookup }},
|
||||
{ "64871", { apex_tkl_us_region_patch, {}, {} }},
|
||||
|
||||
{ "64913", { apex_mini_us_region_patch, {}, {} }},
|
||||
@@ -488,7 +495,7 @@ static void SetSkuLedNames (std::vector<led>& input, std::string& sku, unsigned
|
||||
| SKU codes for all known Apex Pro / 7 / 5 & TKL variant |
|
||||
| keyboards as at Janauary 2022. Generated by cross-checking |
|
||||
| store listings aginst Steelseries website. |
|
||||
| Updated 2025 for Apex 9 and Pro Gen 2 & Gen 3 |
|
||||
| Updated 2026 for Apex 9 and Pro Gen 2 & Gen 3 |
|
||||
| The product Pro Mini seem to belong to Gen 2 |
|
||||
| |
|
||||
| -- APEX PRO Gen 3 -- |
|
||||
@@ -497,11 +504,17 @@ static void SetSkuLedNames (std::vector<led>& input, std::string& sku, unsigned
|
||||
| |
|
||||
| >> APEX PRO TKL Gen 3 |
|
||||
| |
|
||||
| "64740", // US TKL |
|
||||
| "64740", // US TKL Black |
|
||||
| "64741", // UK TKL Black |
|
||||
| "64742", // Nordic TKL Black |
|
||||
| "64743", // German TKL Black |
|
||||
| "64744", // French TKL Black |
|
||||
| "64745", // Japanese TKL Black |
|
||||
| |
|
||||
| >> APEX PRO TKL Wireless Gen 3 |
|
||||
| |
|
||||
| "64871", // US TKL Wireless |
|
||||
| "64871", // US TKL Black Wireless |
|
||||
| "64876", // Japanese TKL Black Wireless |
|
||||
| |
|
||||
| >> APEX PRO Mini Gen 3 |
|
||||
| |
|
||||
@@ -512,6 +525,7 @@ static void SetSkuLedNames (std::vector<led>& input, std::string& sku, unsigned
|
||||
| >> APEX PRO TKL Gen 2 / 2023 |
|
||||
| |
|
||||
| "64856", // US TKL |
|
||||
| "64861", // Japanese TKL |
|
||||
| |
|
||||
| >> APEX PRO TKL Wireless Gen 2 / 2023 |
|
||||
| |
|
||||
|
||||
Reference in New Issue
Block a user