From c5417162252a560a87e670e20ad6ea4e37d27205 Mon Sep 17 00:00:00 2001 From: Patrick Uven Date: Sat, 29 Nov 2025 23:35:43 +0100 Subject: [PATCH] Add support for SteelSeries Apex 9 --- .../SteelSeriesApex9Controller.cpp | 160 ++++++++++++++++++ .../SteelSeriesApex9Controller.h | 38 +++++ .../SteelSeriesApexBaseController.h | 4 +- .../RGBController_SteelSeriesApex.cpp | 4 +- .../SteelSeriesApexRegions.h | 138 ++++++++++++++- .../SteelSeriesControllerDetect.cpp | 31 +++- .../SteelSeriesGeneric.h | 2 + 7 files changed, 370 insertions(+), 7 deletions(-) create mode 100644 Controllers/SteelSeriesController/SteelSeriesApex9Controller/SteelSeriesApex9Controller.cpp create mode 100644 Controllers/SteelSeriesController/SteelSeriesApex9Controller/SteelSeriesApex9Controller.h diff --git a/Controllers/SteelSeriesController/SteelSeriesApex9Controller/SteelSeriesApex9Controller.cpp b/Controllers/SteelSeriesController/SteelSeriesApex9Controller/SteelSeriesApex9Controller.cpp new file mode 100644 index 000000000..e6f0258e1 --- /dev/null +++ b/Controllers/SteelSeriesController/SteelSeriesApex9Controller/SteelSeriesApex9Controller.cpp @@ -0,0 +1,160 @@ +/*---------------------------------------------------------*\ +| SteelSeriesApex9Controller.cpp | +| | +| Driver for SteelSeries Apex 9 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#include +#include "SteelSeriesApex9Controller.h" + +using namespace std::chrono_literals; + +static unsigned int keys[] = {0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, //20 + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, + 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, //40 + 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x32, 0x33, 0x34, 0x35, 0x36, + 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, //60 + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, + 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x64, 0xE0, //80 + 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 }; + +SteelSeriesApex9Controller::SteelSeriesApex9Controller(hid_device* dev_handle, steelseries_type type, const char* path, std::string dev_name) : SteelSeriesApexBaseController (dev_handle, path, dev_name) +{ + proto_type = type; +} + +SteelSeriesApex9Controller::~SteelSeriesApex9Controller() +{ + hid_close(dev); +} + +void SteelSeriesApex9Controller::SetMode(unsigned char mode /*mode*/, std::vector /*colors*/ ) +{ + unsigned char mode_colors[9]; + + active_mode = mode; + + memset(mode_colors, 0x00, sizeof(mode_colors)); +} + +void SteelSeriesApex9Controller::SetLEDsDirect(std::vector colors) +{ + unsigned char buf[APEX_9_PACKET_LENGTH]; + int num_keys = 0; + + num_keys = sizeof(keys) / sizeof(*keys); + + /*-----------------------------------------------------*\ + | Zero out buffer | + \*-----------------------------------------------------*/ + memset(buf, 0x00, sizeof(buf)); + + /*-----------------------------------------------------*\ + | Set up Direct packet | + \*-----------------------------------------------------*/ + buf[0x00] = 0; + buf[0x01] = APEX_9_PACKET_ID_DIRECT; + buf[0x02] = num_keys; + + /*-----------------------------------------------------*\ + | Fill in color data | + \*-----------------------------------------------------*/ + for(int i = 0; i < num_keys; i++) + { + buf[(i*4)+3] = keys[i]; + buf[(i*4)+4] = RGBGetRValue(colors[i]); + buf[(i*4)+5] = RGBGetGValue(colors[i]); + buf[(i*4)+6] = RGBGetBValue(colors[i]); + } + + /*-----------------------------------------------------*\ + | Send packet | + \*-----------------------------------------------------*/ + hid_send_feature_report(dev, buf, APEX_9_PACKET_LENGTH); + +} + +std::string SteelSeriesApex9Controller::GetSerial() +{ + std::string return_string = ""; + + switch(proto_type) + { + case APEX_9_TKL: + return_string = "64847"; + break; + case APEX_9_MINI: + return_string = "64837"; + break; + default: + return_string = "Apex 9 GetSerial() error"; + } + + return(return_string); +} + +std::string SteelSeriesApex9Controller::GetVersion() +{ + std::string return_string = "Unsupported protocol"; + + unsigned char obuf[STEELSERIES_PACKET_OUT_SIZE]; + unsigned char ibuf[STEELSERIES_PACKET_IN_SIZE]; + int result; + + memset(obuf, 0x00, sizeof(obuf)); + obuf[0x00] = 0; + obuf[0x01] = 0x90; + hid_write(dev, obuf, STEELSERIES_PACKET_OUT_SIZE); + result = hid_read_timeout(dev, ibuf, STEELSERIES_PACKET_IN_SIZE, 2); + + if(result > 0) + { + std::string fwver(ibuf, ibuf+STEELSERIES_PACKET_IN_SIZE); + fwver = fwver.substr(2, fwver.size()); + fwver = fwver.c_str(); + + /*---------------------------------------------*\ + | Find 2 periods in string, if found we can | + | form a X.Y.Z revision. | + \*---------------------------------------------*/ + std::size_t majorp = fwver.find('.'); + if(majorp != std::string::npos) + { + 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; + } + } + } + + return(return_string); +} + +/*-------------------------------------------------------------------------------------------------*\ +| Private packet sending functions. | +\*-------------------------------------------------------------------------------------------------*/ + +void SteelSeriesApex9Controller::SelectProfile(unsigned char profile) +{ + unsigned char buf[65]; + + /*-----------------------------------------------------*\ + | Zero out buffer, set up packet and send | + \*-----------------------------------------------------*/ + memset(buf, 0x00, sizeof(buf)); + buf[0x00] = 0; + buf[0x01] = 0x89; + buf[0x02] = profile; + hid_send_feature_report(dev, buf, 65); +} diff --git a/Controllers/SteelSeriesController/SteelSeriesApex9Controller/SteelSeriesApex9Controller.h b/Controllers/SteelSeriesController/SteelSeriesApex9Controller/SteelSeriesApex9Controller.h new file mode 100644 index 000000000..8b836a1ad --- /dev/null +++ b/Controllers/SteelSeriesController/SteelSeriesApex9Controller/SteelSeriesApex9Controller.h @@ -0,0 +1,38 @@ +/*---------------------------------------------------------*\ +| SteelSeriesApex9Controller.h | +| | +| Driver for SteelSeries Apex 9 | +| | +| This file is part of the OpenRGB project | +| SPDX-License-Identifier: GPL-2.0-only | +\*---------------------------------------------------------*/ + +#pragma once + +#include +#include +#include "RGBController.h" +#include "SteelSeriesGeneric.h" +#include "SteelSeriesApexBaseController.h" + +enum +{ + APEX_9_PACKET_ID_DIRECT = 0x40, /* Direct mode */ + APEX_9_PACKET_LENGTH = 513, +}; + +class SteelSeriesApex9Controller : public SteelSeriesApexBaseController +{ +public: + SteelSeriesApex9Controller(hid_device* dev_handle, steelseries_type type, const char* path, std::string dev_name); + ~SteelSeriesApex9Controller(); + + void SetMode(unsigned char mode, std::vector colors); + void SetLEDsDirect(std::vector colors); + + std::string GetSerial() override; + std::string GetVersion() override; + +private: + void SelectProfile(unsigned char profile); +}; diff --git a/Controllers/SteelSeriesController/SteelSeriesApexBaseController.h b/Controllers/SteelSeriesController/SteelSeriesApexBaseController.h index 67ea5e10a..f9d622b41 100644 --- a/Controllers/SteelSeriesController/SteelSeriesApexBaseController.h +++ b/Controllers/SteelSeriesController/SteelSeriesApexBaseController.h @@ -27,8 +27,8 @@ public: std::string GetLocation(); std::string GetName(); - std::string GetSerial(); - std::string GetVersion(); + virtual std::string GetSerial(); + virtual std::string GetVersion(); virtual void SetMode(unsigned char mode, std::vector colors) = 0; diff --git a/Controllers/SteelSeriesController/SteelSeriesApexController/RGBController_SteelSeriesApex.cpp b/Controllers/SteelSeriesController/SteelSeriesApexController/RGBController_SteelSeriesApex.cpp index 5cb8669cb..863583aac 100644 --- a/Controllers/SteelSeriesController/SteelSeriesApexController/RGBController_SteelSeriesApex.cpp +++ b/Controllers/SteelSeriesController/SteelSeriesApexController/RGBController_SteelSeriesApex.cpp @@ -106,7 +106,7 @@ void RGBController_SteelSeriesApex::SetupZones() new_zone.matrix_map = new matrix_map_type; new_zone.matrix_map->map = (unsigned int *) malloc(matrix_mapsize*sizeof(unsigned int)); - if((proto_type == APEX) || (proto_type == APEX_M)) + if((proto_type == APEX) || (proto_type == APEX_M) || (proto_type == APEX_9_TKL) || (proto_type == APEX_9_MINI)) { SetSkuRegion(*new_zone.matrix_map, sku); } @@ -116,7 +116,7 @@ void RGBController_SteelSeriesApex::SetupZones() new_zone.matrix_map = NULL; } - if((proto_type == APEX) || (proto_type == APEX_M)) + if((proto_type == APEX) || (proto_type == APEX_M) || (proto_type == APEX_9_TKL) || (proto_type == APEX_9_MINI)) { new_zone.leds_min = zone_sizes[zone_idx]; new_zone.leds_max = zone_sizes[zone_idx]; diff --git a/Controllers/SteelSeriesController/SteelSeriesApexRegions.h b/Controllers/SteelSeriesController/SteelSeriesApexRegions.h index 5767c1782..cb65ba8b3 100644 --- a/Controllers/SteelSeriesController/SteelSeriesApexRegions.h +++ b/Controllers/SteelSeriesController/SteelSeriesApexRegions.h @@ -264,6 +264,58 @@ static const std::vector apex_tkl_us_region_patch = {5, 20, NA}, }; +/*-------------------------------------------------*\ +| The Mini region patch is common for all Mini SKUs | +\*-------------------------------------------------*/ + +static const std::vector apex_mini_us_region_patch = +{ + {0, 0, NA}, + {0, 2, NA}, + {0, 3, NA}, + {0, 4, NA}, + {0, 5, NA}, + {0, 7, NA}, + {0, 8, NA}, + {0, 9, NA}, + {0, 10, NA}, + {0, 11, NA}, + {0, 12, NA}, + {0, 13, NA}, + {0, 14, NA}, + {0, 15, NA}, + {0, 16, NA}, + {0, 17, NA}, + {1, 0, 37}, + {1, 15, NA}, + {1, 16, NA}, + {1, 17, NA}, + {1, 18, NA}, + {1, 19, NA}, + {1, 20, NA}, + {1, 21, NA}, + {2, 15, NA}, + {2, 16, NA}, + {2, 17, NA}, + {2, 18, NA}, + {2, 19, NA}, + {2, 20, NA}, + {2, 21, NA}, + {3, 18, NA}, + {3, 19, NA}, + {3, 20, NA}, + {4, 16, NA}, + {4, 18, NA}, + {4, 19, NA}, + {4, 20, NA}, + {4, 21, NA}, + {5, 15, NA}, + {5, 16, NA}, + {5, 17, NA}, + {5, 18, NA}, + {5, 20, NA}, +}; + /*-----------------------------------------------*\ | Keyname lookups change the character displayed | | on the LED view GUI by overriding the value | @@ -309,8 +361,27 @@ static const std::map patch_lookup = | base patch, then apply the regional patch on top (if any). | \*----------------------------------------------------------*/ + /*---------------------*\ + | APEX Pro Gen 3 | + \*---------------------*/ + { "64660", { {}, {}, {} }}, + + { "64740", { apex_tkl_us_region_patch, {}, {} }}, + { "64871", { apex_tkl_us_region_patch, {}, {} }}, + + { "64913", { apex_mini_us_region_patch, {}, {} }}, + + /*---------------------*\ + | APEX Pro Gen 2 / 2023 | + \*---------------------*/ + { "64856", { apex_tkl_us_region_patch, {}, {} }}, + { "64865", { apex_tkl_us_region_patch, {}, {} }}, + + { "64820", { apex_mini_us_region_patch, {}, {} }}, + { "64842", { apex_mini_us_region_patch, {}, {} }}, + /*--------*\ - | APEX PRO | + | APEX Pro | \*--------*/ { "64739", { apex_tkl_us_region_patch, apex_iso_region_patch, apex_uk_keyname_lookup }}, { "64738", { apex_tkl_us_region_patch, apex_iso_region_patch, apex_nor_keyname_lookup }}, @@ -322,6 +393,17 @@ static const std::map patch_lookup = { "64634", { {}, apex_iso_region_patch, apex_uk_keyname_lookup }}, { "64629", { {}, apex_jp_region_patch, apex_jp_keyname_lookup }}, + /*--------*\ + | APEX 9 | + \*--------*/ + { "64847", { apex_tkl_us_region_patch, {}, {} }}, + { "64848", { apex_tkl_us_region_patch, apex_iso_region_patch, apex_uk_keyname_lookup }}, + { "64849", { apex_tkl_us_region_patch, apex_iso_region_patch, apex_nor_keyname_lookup }}, + // { "64850", { apex_tkl_us_region_patch, apex_iso_region_patch, {} }}, + // { "64851", { apex_tkl_us_region_patch, apex_iso_region_patch, {} }}, + + { "64837", { apex_mini_us_region_patch, {}, {} }}, + /*--------*\ | APEX 7 | \*--------*/ @@ -406,6 +488,42 @@ static void SetSkuLedNames (std::vector& 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 | +| The product Pro Mini seem to belong to Gen 2 | +| | +| -- APEX PRO Gen 3 -- | +| | +| "64660", // US | +| | +| >> APEX PRO TKL Gen 3 | +| | +| "64740", // US TKL | +| | +| >> APEX PRO TKL Wireless Gen 3 | +| | +| "64871", // US TKL Wireless | +| | +| >> APEX PRO Mini Gen 3 | +| | +| "64913", // US Mini | +| | +| -- APEX PRO Gen2 / 2023 -- | +| | +| >> APEX PRO TKL Gen 2 / 2023 | +| | +| "64856", // US TKL | +| | +| >> APEX PRO TKL Wireless Gen 2 / 2023 | +| | +| "64865", // US TKL Wireless | +| | +| >> APEX PRO Mini Gen 2 / 2023 | +| | +| "64820", // US Mini | +| | +| >> APEX PRO Mini Wireless Gen 2 / 2023 | +| | +| "64842", // US Mini Wireless | | | | -- APEX PRO -- | | | @@ -428,6 +546,24 @@ static void SetSkuLedNames (std::vector& input, std::string& sku, unsigned | "64738", // Nordic TKL | | "64739", // UK TKL | | | +| -- APEX 9 -- | +| | +| >> APEX 9 TKL | +| | +| "64847", // US TKL | +| "64848", // UK TKL | +| "64849", // Nordic TKL | +| "64850", // German TKL | +| "64851", // French TKL | +| | +| >> APEX 9 Mini | +| | +| "64837", // US Mini | +| "64838", // UK Mini | +| "64839", // German Mini | +| "64840", // French Mini | +| "64841", // Nordic Mini | +| | | -- APEX 7 -- | | | | >> RED switches | diff --git a/Controllers/SteelSeriesController/SteelSeriesControllerDetect.cpp b/Controllers/SteelSeriesController/SteelSeriesControllerDetect.cpp index 8560177f5..e79abee13 100644 --- a/Controllers/SteelSeriesController/SteelSeriesControllerDetect.cpp +++ b/Controllers/SteelSeriesController/SteelSeriesControllerDetect.cpp @@ -14,6 +14,7 @@ #include "SteelSeriesAerox5Controller.h" #include "SteelSeriesArctis5Controller.h" #include "SteelSeriesApex8ZoneController.h" +#include "SteelSeriesApex9Controller.h" #include "SteelSeriesApexController.h" #include "SteelSeriesApexMController.h" #include "SteelSeriesApexTZoneController.h" @@ -110,6 +111,8 @@ #define STEELSERIES_APEX_5_PID 0x161C #define STEELSERIES_APEX_7_PID 0x1612 #define STEELSERIES_APEX_7_TKL_PID 0x1618 +#define STEELSERIES_APEX_9_TKL_PID 0x1634 +#define STEELSERIES_APEX_9_MINI_PID 0x1620 #define STEELSERIES_APEX_PRO_PID 0x1610 #define STEELSERIES_APEX_PRO_TKL_PID 0x1614 #define STEELSERIES_APEX_PRO_TKL_2023_PID 0x1628 @@ -246,6 +249,28 @@ void DetectSteelSeriesApex(hid_device_info* info, const std::string& name) } } +void DetectSteelSeriesApex9(hid_device_info* info, const std::string& name, steelseries_type proto_type) +{ + hid_device* dev = hid_open_path(info->path); + if(dev) + { + SteelSeriesApex9Controller* controller = new SteelSeriesApex9Controller(dev, proto_type, info->path, name); + RGBController_SteelSeriesApex* rgb_controller = new RGBController_SteelSeriesApex(controller); + + ResourceManager::get()->RegisterRGBController(rgb_controller); + } +} + +void DetectSteelSeriesApex9TKL(hid_device_info* info, const std::string& name) +{ + DetectSteelSeriesApex9(info, name, APEX_9_TKL); +} + +void DetectSteelSeriesApex9Mini(hid_device_info* info, const std::string& name) +{ + DetectSteelSeriesApex9(info, name, APEX_9_MINI); +} + void DetectSteelSeriesApexM(hid_device_info* info, const std::string& name) { hid_device* dev = hid_open_path(info->path); @@ -475,10 +500,12 @@ REGISTER_HID_DETECTOR_IPU("SteelSeries Apex 3 TKL", Dete 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 9 TKL", DetectSteelSeriesApex9TKL, STEELSERIES_VID, STEELSERIES_APEX_9_TKL_PID, 1 ); +REGISTER_HID_DETECTOR_I ("SteelSeries Apex 9 Mini", DetectSteelSeriesApex9Mini, STEELSERIES_VID, STEELSERIES_APEX_9_MINI_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 Pro TKL 2023", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_PRO_TKL_2023_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 Pro 3", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_PRO3_PID, 1 ); +REGISTER_HID_DETECTOR_I ("SteelSeries Apex 350", DetectSteelSeriesApexOld, STEELSERIES_VID, STEELSERIES_APEX_350_PID, 0 ); +REGISTER_HID_DETECTOR_I ("SteelSeries Apex Pro 3", DetectSteelSeriesApex, STEELSERIES_VID, STEELSERIES_APEX_PRO3_PID, 1 ); diff --git a/Controllers/SteelSeriesController/SteelSeriesGeneric.h b/Controllers/SteelSeriesController/SteelSeriesGeneric.h index c3aa8a54d..374c03753 100644 --- a/Controllers/SteelSeriesController/SteelSeriesGeneric.h +++ b/Controllers/SteelSeriesController/SteelSeriesGeneric.h @@ -43,4 +43,6 @@ typedef enum AEROX_5_DIABLO_WIRELESS_WIRED = 0x15, AEROX_9_WIRELESS = 0x16, AEROX_9_WIRELESS_WIRED = 0x17, + APEX_9_TKL = 0x18, + APEX_9_MINI = 0x19, } steelseries_type;