Support "Winbond Gaming Keyboard", like Pulsar PCMK or WIANXP/Nautilus/Capturer KT108

This commit is contained in:
Daniel Gibson 2023-12-14 17:30:21 +00:00 committed by Adam Honse
parent 6efc7ff294
commit 7650ec67ba
6 changed files with 1457 additions and 0 deletions

View file

@ -0,0 +1,825 @@
/*---------------------------------------------*\
| RGBController_WinbondGamingKeyboard.cpp |
| |
| Driver for "Winbond Gaming Keyboard" boards, |
| like Pulsar PCMK TKL Keyboard |
| |
| Daniel Gibson 3 December 2023 |
\*---------------------------------------------*/
#include "RGBController_WinbondGamingKeyboard.h"
#include "RGBControllerKeyNames.h"
#include "KeyboardLayoutManager.h"
#include "LogManager.h"
/**------------------------------------------------------------------*\
@name Winbond Gaming Keyboard
@category Keyboard
@type USB
@save :robot:
@direct :x:
@effects :white_check_mark:
@detectors DetectWinbondGamingKeyboard
@comment Also known as Pulsar PCMK, and KT108 (by various manufacturers)
\*-------------------------------------------------------------------*/
/*-----------------------------------------------------------------------------------------------*\
| - MSG_NUM is between 0 and (incl) 7, indicating which HID message contains the key's LED |
| - IDX is between 0 and (incl.) 17 (0 to 5 for message 7), |
| indicating the index of that LED within the message. |
| - 1 << 16 is set so KV(0,0) doesn't have the same value as 0 (the default "not assigned" value) |
\*-----------------------------------------------------------------------------------------------*/
#define KV(MSG_NUM, IDX) ((MSG_NUM) << 8 | (IDX) | (1 << 16))
static std::vector<keyboard_led> additional_mm_leds =
{
{
0, // zone
0, // row
17, // col
KV(1,0), // value
KEY_EN_MEDIA_VOLUME_UP, // name
KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT // opcode
},
{
0, // zone
0, // row
18, // col
KV(1,1), // value
KEY_EN_MEDIA_VOLUME_DOWN, // name
KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT // opcode
},
{
0, // zone
0, // row
19, // col
KV(1,2), // value
KEY_EN_MEDIA_MUTE, // name
KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT // opcode
},
{
0, // zone
0, // row
20, // col
KV(1,3), // value
"Key: Cylinder?!", // name; TODO: no idea what the symbol meant, was a cylinder..
KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT // opcode
}
};
static layout_values winbond_gaming_keyboard_full_layouts =
{
{}, // "std::vector<unsigned int> ansi" is set in InitLayouts()
{ // std::map<KEYBOARD_LAYOUT, std::vector<keyboard_led> > regional_overlay;
{ KEYBOARD_LAYOUT_ISO_QWERTY,
{
/*---------------------------------------------------------*\
| just setting the values for some keys that should already |
| be defined by KeyboardLayoutManager's keyboard_zone_main |
\*---------------------------------------------------------*/
{
0, // zone
3, // row
12, // col
KV(4,7), // value
KEY_EN_POUND, // name
KEYBOARD_OPCODE_SWAP_ONLY // opcode
},
{
0, // zone
4, // row
1, // col
KV(4,17), // value
KEY_EN_ISO_BACK_SLASH, // name
KEYBOARD_OPCODE_SWAP_ONLY // opcode
},
}
},
{ KEYBOARD_LAYOUT_JIS,
{
/*------------------------------------------------*\
| that extra key before backspace on JIS keyboards |
\*------------------------------------------------*/
{
0, // zone
1, // row
13, // col
KV(1,17), // value
KEY_JP_YEN, // name
KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT // opcode
}
}
}
/*----------------------------------------------------------------------------------*\
| TODO: KV(5,10) could be that extra key left of right shift on ABNT keyboards (/ ?) |
\*----------------------------------------------------------------------------------*/
}
};
static layout_values winbond_gaming_keyboard_tkl_layouts =
{
{}, // "std::vector<unsigned int> ansi" is set in InitLayouts()
{ // std::map<KEYBOARD_LAYOUT, std::vector<keyboard_led> > regional_overlay;
{ KEYBOARD_LAYOUT_ISO_QWERTY,
{
/*---------------------------------------------------------*\
| just setting the values for some keys that should already |
| be defined by KeyboardLayoutManager's keyboard_zone_main |
\*---------------------------------------------------------*/
{
0, // zone
3, // row
12, // col
KV(4,7), // value
KEY_EN_POUND, // name
KEYBOARD_OPCODE_SWAP_ONLY // opcode
},
{
0, // zone
4, // row
1, // col
KV(4,17), // value
KEY_EN_ISO_BACK_SLASH, // name
KEYBOARD_OPCODE_SWAP_ONLY // opcode
},
}
},
{ KEYBOARD_LAYOUT_JIS,
{
/*------------------------------------------------*\
| that extra key before backspace on JIS keyboards |
\*------------------------------------------------*/
{
0, // zone
1, // row
13, // col
KV(1,17), // value
KEY_JP_YEN, // name
KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT // opcode
}
}
}
/*----------------------------------------------------------------------------------*\
| TODO: KV(5,10) could be that extra key left of right shift on ABNT keyboards (/ ?) |
\*----------------------------------------------------------------------------------*/
}
};
static layout_values winbond_gaming_keyboard_60_layouts =
{
{}, // "std::vector<unsigned int> ansi" is set in InitLayouts()
{ // std::map<KEYBOARD_LAYOUT, std::vector<keyboard_led> > regional_overlay;
{ KEYBOARD_LAYOUT_ISO_QWERTY,
{
/*---------------------------------------------------------*\
| just setting the values for some keys that should already |
| be defined by KeyboardLayoutManager's keyboard_zone_main |
\*---------------------------------------------------------*/
{
0, // zone
3, // row
12, // col
KV(4,7), // value
KEY_EN_POUND, // name
KEYBOARD_OPCODE_SWAP_ONLY // opcode
},
{
0, // zone
4, // row
1, // col
KV(4,17), // value
KEY_EN_ISO_BACK_SLASH, // name
KEYBOARD_OPCODE_SWAP_ONLY // opcode
},
}
},
{ KEYBOARD_LAYOUT_JIS,
{
/*------------------------------------------------*\
| that extra key before backspace on JIS keyboards |
\*------------------------------------------------*/
{
0, // zone
1, // row
13, // col
KV(1,17), // value
KEY_JP_YEN, // name
KEYBOARD_OPCODE_INSERT_SHIFT_RIGHT // opcode
}
}
}
/*----------------------------------------------------------------------------------*\
| TODO: KV(5,10) could be that extra key left of right shift on ABNT keyboards (/ ?) |
\*----------------------------------------------------------------------------------*/
}
};
static void InitLayouts(layout_values& keyboard_layouts, KEYBOARD_SIZE kb_size)
{
/*-------------------------------------------------------------------*\
| using kvs ("keyvals" or sth like that) as an alias for |
| keyboard_layouts.ansi, to make the code below shorter/more readable |
\*-------------------------------------------------------------------*/
std::vector<unsigned int>& kvs = keyboard_layouts.ansi;
/*------------------------------------------------------------------------------------*\
| Message X: what indices the keys have within the USB HID messages to set their color |
| "??" means "that index exists, but doesn't seem to be used on my TKL ISO keyboard" |
| |
| A Note regarding kb_size: I wrote this with my Pulsar PCMK TKL keyboard, there the |
| LED positions ( KV(MSGNUM, IDX) ) were like on a fullsize keyboard, |
| i.e. there USB messages had space for NumPad keys (or their LEDs). |
| No idea if that's also true for e.g. 60% keyboards, and it's also possible that |
| some positions of NumPad LEDs are incorrect, I only guessed them. |
\*------------------------------------------------------------------------------------*/
/* Message 0: ---------------------------------------------------------------*\
| 0 = Esc, ??, 2 = F1, F2, F3, F4, ??, 7 = F5, F6, F7, F8, F9, F10, F11, F12, |
| 15 = Print, ScrollLock, 17 = Pause |
\*---------------------------------------------------------------------------*/
if(kb_size & KEYBOARD_ZONE_FN_ROW)
{
kvs.push_back( KV(0,0) ); // Esc
// F1 to F4
for(int i=2; i <= 5; ++i)
{
kvs.push_back( KV(0,i) );
}
// F5 to F12
for(int i=7; i <= 14; ++i)
{
kvs.push_back( KV(0,i) );
}
}
if(kb_size & KEYBOARD_ZONE_EXTRA)
{
// PrintScreen, ScrollLock, Pause
for(int i=15; i <= 17; ++i)
{
kvs.push_back( KV(0,i) );
}
}
/* Message 1: ------------------------------------------------------------------------*\
| 0-3 = (could be multimedia keys above numblock?), 4 = BKTK, 1, 2, 3, 4, 5, |
| 10 = 6, 7, 8, 9, 0, '-', 16 = '=', 17 = (that extra key before backspace on JIS?) |
\*------------------------------------------------------------------------------------*/
// KV(1,0) to KV(1,3) are probably 4 extra multimedia keys above the numblock
// BKTK ("Quake console key"), 1, 2, ... 0, -, =
if(kb_size & KEYBOARD_ZONE_MAIN)
{
for(int i=4; i <= 16; ++i)
{
kvs.push_back( KV(1,i) );
}
// KV(1,17) might be that extra Yen key before backspace on JIS keyboards
}
/* Message 2: ------------------------------------------------------------*\
| 0 = Backspace, Ins, Pos1, 3 = PgUp, 4-7 = (NumLock, Num/, Num*, Num- ?), |
| 8 = Tab, Q, W, E, R, T, ..., 16 = I, 17 = O |
\*------------------------------------------------------------------------*/
// Backspace
if(kb_size & KEYBOARD_ZONE_MAIN)
{
kvs.push_back( KV(2,0) );
}
// Ins, Home, PgUp
if(kb_size & KEYBOARD_ZONE_EXTRA)
{
for(int i=1; i <= 3; ++i)
{
kvs.push_back( KV(2,i) );
}
}
// NumLock, NP/, NP*, NP-
if(kb_size & KEYBOARD_ZONE_NUMPAD)
{
for(int i=4; i <= 7; ++i)
{
kvs.push_back( KV(2,i) );
}
}
// Tab, Q, ..., I, O
if(kb_size & KEYBOARD_ZONE_MAIN)
{
for(int i=8; i <= 17; ++i)
{
kvs.push_back( KV(2,i) );
}
}
/* Message 3: -----------------------------------------------------------*\
| 0 = P, [ (Ü), ] (+), 3 = (US-\ ?), ??, 5 = Del, End, 7 = PgDown, |
| 8-1 = (Num7, Num8, Num9, Num+ ?), 12 = CapsLock, ??, 14 = A, S, D, F |
\*-----------------------------------------------------------------------*/
// P, Ü/[, +/], US-Backslash
if(kb_size & KEYBOARD_ZONE_MAIN)
{
for(int i=0; i <= 3; ++i)
{
kvs.push_back( KV(3,i) );
}
}
// Del, End, PgDown
if(kb_size & KEYBOARD_ZONE_EXTRA)
{
for(int i=5; i <= 7; ++i)
{
kvs.push_back( KV(3,i) );
}
}
// NP7-NP9, NP+
if(kb_size & KEYBOARD_ZONE_NUMPAD)
{
for(int i=8; i <= 11; ++i)
{
kvs.push_back( KV(3,i) );
}
}
if(kb_size & KEYBOARD_ZONE_MAIN)
{
// CapsLock
kvs.push_back( KV(3,12) );
// A, S, D, F
for(int i=14; i <= 17; ++i)
{
kvs.push_back( KV(3,i) );
}
}
/* Message 4: -------------------------------------------------------------*\
| 0 = G, H, J, K, L, ; (Ö), " (Ä), 7 = #, 8 = Enter, 9 - 15 = ?? |
| (maybe 12-14 are Num4-6, maybe 15 = Num+ ?), 16 = Shift, 17 = ISO-\ (<) |
\*-------------------------------------------------------------------------*/
if(kb_size & KEYBOARD_ZONE_MAIN)
{
// G, H, ..., L, Ö/;, Ä/"
for(int i=0; i <= 6; ++i)
{
kvs.push_back( KV(4,i) );
}
// KV(4,7) is the ISO # key that doesn't exist on ANSI (set in overlay)
// even though # is no ANSI key, that ansi array expects it to be there..
kvs.push_back( KV(4,7) );
// Enter - assuming that on ANSI it uses the same LED index
kvs.push_back( KV(4,8) );
}
if(kb_size & KEYBOARD_ZONE_NUMPAD)
{
// NP4-6 - not sure if those are really at 12-14, would fit though
for(int i=12; i <= 14; ++i)
{
kvs.push_back( KV(4,i) );
}
}
if(kb_size & KEYBOARD_ZONE_MAIN)
{
// Left Shift
kvs.push_back( KV(4,16) );
// KV(4,17) is the ISO |\ (or <) key that doesn't exist on ANSI (set in overlay)
// apparently it must be listed anyway..
kvs.push_back( KV(4,17) );
}
/* Message 5: --------------------------------------------------------*\
| 0 = Z/Y, X, C, V, B, N, M, ',', '.' , 9 = / or -, ??, ??, |
| 12 = Shift, ??, 14 = Up, 15-17 ?? (maybe 16 and 17 are Num1, Num2, |
| and Num3, NumEnter are in next msg?) |
\*--------------------------------------------------------------------*/
if(kb_size & KEYBOARD_ZONE_MAIN)
{
// Z (Y), X, .., M, ',', '.', / (- on DE)
for(int i=0; i <= 9; ++i)
{
kvs.push_back( KV(5,i) );
}
// KV(5,10) could be that extra key left of right shift on ABNT keyboards (/ ?)
kvs.push_back( KV(5,12) ); // Right Shift
}
if(kb_size & KEYBOARD_ZONE_EXTRA)
{
kvs.push_back( KV(5,14) ); // Up
}
if(kb_size & KEYBOARD_ZONE_NUMPAD)
{
kvs.push_back( KV(5,16) ); // Num1
kvs.push_back( KV(5,17) ); // Num2
}
/* Message 6: -------------------------------------------------------------*\
| 0 = (Num3 ?), (Num Enter ?), 2 = Ctrl, Win, 4 = Alt, .. ?? .., 8 = Space, |
| .. ?? .., 12 = AltGr, Fn, Menu, 15 = Ctrl, ??, 17 = Left |
\*-------------------------------------------------------------------------*/
// Num3, Num Enter
if(kb_size & KEYBOARD_ZONE_NUMPAD)
{
for(int i=0; i <= 1; ++i)
{
kvs.push_back( KV(6,i) );
}
}
if(kb_size & KEYBOARD_ZONE_MAIN)
{
// LeftCtrl, Win, LeftAlt
for(int i=2; i <= 4; ++i)
{
kvs.push_back( KV(6,i) );
}
// Space
kvs.push_back( KV(6,8) );
// RightAlt, Fn, RMenu, RightCtrl
for(int i=12; i <= 15; ++i)
{
kvs.push_back( KV(6,i) );
}
}
if(kb_size & KEYBOARD_ZONE_EXTRA)
kvs.push_back( KV(6,17) ); // Left
/* Message 7: -------------------------------------------------------*\
| 0 = Down, 1 = Right, 2-5 = ?? (maybe 2 or 3 = Num0, 4 = NumDecimal, |
| 5 = NumEnter, more likely: 3 = Num0, 5 = NumDec) |
\*-------------------------------------------------------------------*/
if(kb_size & KEYBOARD_ZONE_EXTRA)
{
kvs.push_back( KV(7,0) ); // Down
kvs.push_back( KV(7,1) ); // Right
}
if(kb_size & KEYBOARD_ZONE_NUMPAD)
{
kvs.push_back( KV(7,3) ); // Num0 - TODO: or 7,2 or 7,4 ?
kvs.push_back( KV(7,5) ); // NumDec - TODO: or 7,4 ?
}
/*-------------------------------------------------------------------*\
| for fullsize keyboards, add multimedia keys to all layouts/overlays |
\*-------------------------------------------------------------------*/
if(kb_size == KEYBOARD_SIZE_FULL)
{
for(std::pair<const KEYBOARD_LAYOUT, std::vector<keyboard_led>>& overlay: keyboard_layouts.regional_overlay)
{
for(const keyboard_led& mm_key_led : additional_mm_leds)
{
overlay.second.push_back(mm_key_led);
}
}
/*---------------------------------------------------------------------------------*\
| there is no overlay for the ANSI layout defined yet, add one just for the MM keys |
\*---------------------------------------------------------------------------------*/
keyboard_layouts.regional_overlay.insert({ KEYBOARD_LAYOUT_ANSI_QWERTY, additional_mm_leds });
}
}
RGBController_WinbondGamingKeyboard::RGBController_WinbondGamingKeyboard(WinbondGamingKeyboardController* ctrl)
: controller(ctrl)
{
type = DEVICE_TYPE_KEYBOARD;
name = ctrl->GetName();
vendor = ctrl->GetVendor();
description = ctrl->GetDescription();
location = ctrl->GetDeviceLocation();
serial = ctrl->GetSerialString();
version = ctrl->GetVersion();
{
mode m;
m.name = "Custom";
m.value = WINBOND_GK_MODE_CUSTOM;
m.flags = MODE_FLAG_HAS_PER_LED_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
m.color_mode = MODE_COLORS_PER_LED;
m.brightness_min = 0;
m.brightness_max = 4;
m.brightness = 1;
m.speed_min = 0;
m.speed_max = 4;
m.speed = 2;
modes.push_back(m);
}
{
mode m;
m.name = "Static";
m.value = WINBOND_GK_MODE_STATIC;
m.flags = MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
m.color_mode = MODE_COLORS_MODE_SPECIFIC;
m.colors_min = m.colors_max = 1; // one color for all keys
m.colors.push_back(ToRGBColor(255,255,255)); // default to white
m.brightness_min = 0;
m.brightness_max = 4;
m.brightness = 1;
m.speed_min = 0;
m.speed_max = 4;
m.speed = 2;
modes.push_back(m);
}
{
mode m;
m.name = "Neon";
m.value = WINBOND_GK_MODE_NEON;
m.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
m.colors.push_back(ToRGBColor(255,0,0));
m.colors.push_back(ToRGBColor(0,0,0));
m.colors_min = 0;
m.colors_max = 2;
m.color_mode = MODE_COLORS_RANDOM;
m.brightness_min = 0;
m.brightness_max = 4;
m.brightness = 1;
m.speed_min = 0;
m.speed_max = 4;
m.speed = 2;
modes.push_back(m);
}
{
mode m;
m.name = "Breathing";
m.value = WINBOND_GK_MODE_BREATHE;
m.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
m.colors.push_back(ToRGBColor(255,0,0));
m.colors.push_back(ToRGBColor(0,0,0));
m.colors_min = 0;
m.colors_max = 2;
m.color_mode = MODE_COLORS_RANDOM;
m.brightness_min = 0;
m.brightness_max = 4;
m.brightness = 1;
m.speed_min = 0;
m.speed_max = 4;
m.speed = 2;
modes.push_back(m);
}
{
mode m;
m.name = "Wave";
m.value = WINBOND_GK_MODE_WAVE;
m.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE
| MODE_FLAG_HAS_DIRECTION_LR | MODE_FLAG_HAS_DIRECTION_UD
| MODE_FLAG_HAS_DIRECTION_HV; // NOTE: this is really outer to inner or inner to outer
m.direction = MODE_DIRECTION_RIGHT;
m.colors.push_back(ToRGBColor(255,0,0));
m.colors.push_back(ToRGBColor(0,0,0));
m.colors_min = 0;
m.colors_max = 2;
m.color_mode = MODE_COLORS_RANDOM;
m.brightness_min = 0;
m.brightness_max = 4;
m.brightness = 1;
m.speed_min = 0;
m.speed_max = 4;
m.speed = 2;
modes.push_back(m);
}
{
mode m;
m.name = "Snake";
m.value = WINBOND_GK_MODE_SNAKE;
m.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE
| MODE_FLAG_HAS_DIRECTION_UD; // NOTE: more like CW/CCW
m.direction = MODE_DIRECTION_DOWN;
m.colors.push_back(ToRGBColor(255,0,0));
m.colors.push_back(ToRGBColor(0,0,0));
m.colors_min = 0;
m.colors_max = 2;
m.color_mode = MODE_COLORS_RANDOM;
m.brightness_min = 0;
m.brightness_max = 4;
m.brightness = 1;
m.speed_min = 0;
m.speed_max = 4;
m.speed = 2;
modes.push_back(m);
}
/*------------------------------------------------------------------------*\
|the following modes are ones that only show effects when a key is pressed |
| and otherwise only a background color |
\*------------------------------------------------------------------------*/
{
mode m;
m.name = "Aurora (on keypress)";
m.value = WINBOND_GK_MODE_AURORA;
m.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE
| MODE_FLAG_HAS_DIRECTION_HV; // NOTE: more like to inner/to outer
m.direction = MODE_DIRECTION_HORIZONTAL;
m.colors.push_back(ToRGBColor(255,0,0));
m.colors.push_back(ToRGBColor(0,0,0)); // TODO: set background to white so keyboard isn't dark?
m.colors_min = 0;
m.colors_max = 2;
m.color_mode = MODE_COLORS_RANDOM;
m.brightness_min = 0;
m.brightness_max = 4;
m.brightness = 1;
m.speed_min = 0;
m.speed_max = 4;
m.speed = 2;
modes.push_back(m);
}
{
mode m;
m.name = "Ripple (on keypress)";
m.value = WINBOND_GK_MODE_RIPPLE;
m.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
m.colors.push_back(ToRGBColor(255,0,0));
m.colors.push_back(ToRGBColor(0,0,0)); // TODO: set background to white so keyboard isn't dark?
m.colors_min = 0;
m.colors_max = 2;
m.color_mode = MODE_COLORS_RANDOM;
m.brightness_min = 0;
m.brightness_max = 4;
m.brightness = 1;
m.speed_min = 0;
m.speed_max = 4;
m.speed = 2;
modes.push_back(m);
}
{
mode m;
m.name = "Reactive (on keypress)";
m.value = WINBOND_GK_MODE_REACTIVE;
m.flags = MODE_FLAG_HAS_SPEED | MODE_FLAG_HAS_MODE_SPECIFIC_COLOR | MODE_FLAG_HAS_RANDOM_COLOR | MODE_FLAG_HAS_BRIGHTNESS | MODE_FLAG_AUTOMATIC_SAVE;
m.colors.push_back(ToRGBColor(255,0,0));
m.colors.push_back(ToRGBColor(0,0,0)); // TODO: set background to white so keyboard isn't dark?
m.colors_min = 0;
m.colors_max = 2;
m.color_mode = MODE_COLORS_RANDOM;
m.brightness_min = 0;
m.brightness_max = 4;
m.brightness = 1;
m.speed_min = 0;
m.speed_max = 4;
m.speed = 2;
modes.push_back(m);
}
/*--------------------------------------------------------------------------------*\
| NOTE: logo light has static, neon (WINBOND_GK_MODE_LOGO_NEON), breathe, wave |
| all logo modes have brightness, all but static have speed and 2 colors + random |
\*--------------------------------------------------------------------------------*/
SetupZones();
}
RGBController_WinbondGamingKeyboard::~RGBController_WinbondGamingKeyboard()
{
delete controller;
}
void RGBController_WinbondGamingKeyboard::SetupZones()
{
zone new_zone;
new_zone.name = ZONE_EN_KEYBOARD;
new_zone.type = ZONE_TYPE_MATRIX;
layout_values* layouts = &winbond_gaming_keyboard_full_layouts;
KEYBOARD_SIZE kb_size = controller->GetSize();
if(kb_size == KEYBOARD_SIZE_TKL)
{
layouts = &winbond_gaming_keyboard_tkl_layouts;
}
else if(kb_size == KEYBOARD_SIZE_SIXTY)
{
layouts = &winbond_gaming_keyboard_60_layouts;
}
else // size is full or something not supported directly - default to full
{
kb_size = KEYBOARD_SIZE_FULL;
}
if(layouts->ansi.empty())
{
InitLayouts(*layouts, kb_size);
}
KeyboardLayoutManager new_kb(controller->GetLayout(), kb_size, *layouts);
matrix_map_type * new_map = new matrix_map_type;
new_zone.matrix_map = new_map;
new_zone.matrix_map->height = new_kb.GetRowCount();
new_zone.matrix_map->width = new_kb.GetColumnCount();
new_zone.matrix_map->map = new unsigned int[new_map->height * new_map->width];
new_zone.leds_count = new_kb.GetKeyCount();
new_zone.leds_min = new_zone.leds_count;
new_zone.leds_max = new_zone.leds_count;
/*---------------------------------------------------------*\
| Matrix map still uses declared zone rows and columns |
| as the packet structure depends on the matrix map |
\*---------------------------------------------------------*/
new_kb.GetKeyMap(new_map->map, KEYBOARD_MAP_FILL_TYPE_COUNT);
/*---------------------------------------------------------*\
| Create LEDs for the Matrix zone |
| Place keys in the layout to populate the matrix |
\*---------------------------------------------------------*/
for(size_t led_idx = 0; led_idx < new_zone.leds_count; led_idx++)
{
led new_led;
new_led.name = new_kb.GetKeyNameAt(led_idx);
new_led.value = new_kb.GetKeyValueAt(led_idx);
leds.push_back(new_led);
}
zones.push_back(new_zone);
/*---------------------------------------------------------------------------*\
| special case: logo LEDs |
| it's 3 LEDs, but the colors can't be set separately, so represent it as one |
\*---------------------------------------------------------------------------*/
if(controller->HasLogoLight())
{
zone logo_zone;
logo_zone.name = "Logo Light";
logo_zone.type = ZONE_TYPE_SINGLE;
logo_zone.leds_min = 1;
logo_zone.leds_max = 1;
logo_zone.leds_count = 1;
logo_zone.matrix_map = NULL;
led zone_led;
zone_led.name = "Logo LEDs";
zone_led.value = KV(255, 0); // using message num 255 as special case for logo LED
leds.push_back(zone_led);
zones.push_back(logo_zone);
}
SetupColors();
}
#undef KV
void RGBController_WinbondGamingKeyboard::ResizeZone(int /*zone*/, int /*new_size*/)
{
/*---------------------------------------------------------*\
| This device does not support resizing zones |
\*---------------------------------------------------------*/
}
void RGBController_WinbondGamingKeyboard::DeviceUpdateLEDs()
{
controller->SetLEDsData(colors, leds, modes[active_mode].brightness);
}
void RGBController_WinbondGamingKeyboard::UpdateZoneLEDs(int /*zone*/)
{
DeviceUpdateLEDs();
}
void RGBController_WinbondGamingKeyboard::UpdateSingleLED(int /*led*/)
{
DeviceUpdateLEDs();
}
void RGBController_WinbondGamingKeyboard::DeviceUpdateMode()
{
controller->SetMode(modes[active_mode]);
}

View file

@ -0,0 +1,50 @@
/*---------------------------------------------*\
| RGBController_WinbondGamingKeyboard.h |
| |
| Driver for "Winbond Gaming Keyboard" boards, |
| like Pulsar PCMK TKL Keyboard |
| |
| Daniel Gibson 3 December 2023 |
\*---------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "WinbondGamingKeyboardController.h"
enum
{
WINBOND_GK_MODE_STATIC = 0,
WINBOND_GK_MODE_BREATHE = 1,
WINBOND_GK_MODE_WAVE = 2,
WINBOND_GK_MODE_NEON = 3,
WINBOND_GK_MODE_LOGO_NEON = 4, // logo-only!
WINBOND_GK_MODE_SNAKE = 5,
/*-------------------------------------------------------------------------*\
| the following modes are ones that only show effects when a key is pressed |
| and otherwise only a background color |
\*-------------------------------------------------------------------------*/
WINBOND_GK_MODE_REACTIVE = 6,
WINBOND_GK_MODE_AURORA = 7,
WINBOND_GK_MODE_RIPPLE = 8,
WINBOND_GK_MODE_CUSTOM = 10
};
class RGBController_WinbondGamingKeyboard : public RGBController
{
public:
RGBController_WinbondGamingKeyboard(WinbondGamingKeyboardController* ctrl);
~RGBController_WinbondGamingKeyboard();
void SetupZones() override;
void ResizeZone(int zone, int new_size) override;
void DeviceUpdateLEDs() override;
void UpdateZoneLEDs(int zone) override;
void UpdateSingleLED(int led) override;
void DeviceUpdateMode() override;
private:
WinbondGamingKeyboardController* controller;
};

View file

@ -0,0 +1,421 @@
/*---------------------------------------------*\
| WinbondGamingKeyboardController.cpp |
| |
| Driver for "Winbond Gaming Keyboard" boards, |
| like Pulsar PCMK TKL Keyboard |
| |
| Daniel Gibson 3 December 2023 |
\*---------------------------------------------*/
#include "WinbondGamingKeyboardController.h"
#include "RGBController_WinbondGamingKeyboard.h"
#include "LogManager.h"
#include <algorithm>
#include <string.h>
#define WINBOND_HID_DATA_LEN 64
WinbondGamingKeyboardController::WinbondGamingKeyboardController(hid_device *dev_handle, const hid_device_info &info, const std::string& name)
: dev(dev_handle)
{
location = "HID: ";
location += info.path;
SetNameVendorDescription(info, name);
SetVersionLayout();
}
void WinbondGamingKeyboardController::SetNameVendorDescription(const hid_device_info &info, const std::string& devname)
{
bool using_product_string = false;
if(info.product_string != nullptr && info.product_string[0] != 0)
{
using_product_string = true;
/*----------------------------------------------------------------------------------------------------*\
| info->product_string can have at most 126 wchars + terminating 0 |
| (according to |
| https://stackoverflow.com/questions/7193645/how-long-is-the-string-of-manufacturer-of-a-usb-device) |
| in UTF-8 that's at most 126*4 chars + terminating 0 |
\*----------------------------------------------------------------------------------------------------*/
char product_name[126*4 + 1];
snprintf(product_name, sizeof(product_name), "%ls", info.product_string);
name = product_name;
}
else
{
name = devname;
}
/*----------------------------------------------------------------------------*\
| the Pulsar PCMK TKL keyboard (barebone) uses "PCMK TKL" as product string, |
| at least with the latest firmware. I assume that other Pulsar keyboards also |
| contain "PCMK", so use that to set the vendor to Pulsar (unfortunately, |
| Pulsar doesn't seem to set iManufacturer in the USB device descriptor) |
\*----------------------------------------------------------------------------*/
if(name.find("PCMK") != std::string::npos)
{
vendor = "Pulsar";
if(name.find("TKL") != std::string::npos)
{
kb_size = KEYBOARD_SIZE_TKL;
hasLogoLight = true;
}
else if(name.find("60") != std::string::npos)
{
/*---------------------------------------------------*\
| I *guess* that the PCMK 60% has 60 in its name here |
| (if it even uses the same USB PID:VID ...) |
\*---------------------------------------------------*/
kb_size = KEYBOARD_SIZE_SIXTY;
}
}
else
{
vendor = "Winbond";
}
if(using_product_string)
{
description = vendor + " " + name;
}
else
{
description = name;
}
}
void WinbondGamingKeyboardController::ParseVersionString(const char *str)
{
/*------------------------------------------------------------------------------*\
| str should be something like "2NUC,01,KB,FL,K221UKCVRGB,V1.05.03" (for ISO/UK) |
| we're interested in the chars before "CVRGB" for the layout, |
| and the last "V" and following for the version string |
\*------------------------------------------------------------------------------*/
const char* ver = strrchr(str, 'V');
if(ver != nullptr)
{
version = ver;
}
else
{
version = "???";
}
LOG_DEBUG("[%s] Version response was: '%s'", name.c_str(), str);
const char* cvrgb = strstr(str, "CVRGB");
if(cvrgb != nullptr && cvrgb > str + 2)
{
const char* lang = cvrgb - 2;
if(lang[0] == 'U' && lang[1] == 'K')
{
LOG_DEBUG("[%s] Detected ISO layout from that version string", name.c_str());
layout = KEYBOARD_LAYOUT_ISO_QWERTY;
}
else if(lang[0] == 'J' && lang[1] == 'P')
{
LOG_DEBUG("[%s] Detected JIS layout from that version string", name.c_str());
layout = KEYBOARD_LAYOUT_JIS;
}
else
{
LOG_DEBUG("[%s] Detected ANSI layout from that version string", name.c_str());
layout = KEYBOARD_LAYOUT_ANSI_QWERTY;
}
}
else
{
LOG_DEBUG("[%s] Couldn't detect any layout from that string (didn't contain \"CVRGB\"), defaulting to ISO", name.c_str());
}
/*------------------------------------------------------------------------------------------*\
| NOTE: I don't know exactly how the string would look like on ANSI or JIS keyboards, |
| but the firmware updaters for Pulsar PCMK TKL are called |
| K221CVRGB_V10507.exe for ANSI |
| K221JPCVRGB_V10503.exe for JIS |
| K221UKCVRGB_V10503.exe for ISO |
| so I assume that ANSI indeed has no extra string before CVRGB and JIS has "JP" there |
| |
| For future reference, PCMK 60% JIS firmware name: GD147CKB_M252KBFL_K225JPCVRGB_V10408.exe |
| no idea if that even uses the same chip, and how the LEDs are handled, though |
| |
| Furthermore, no idea how to detect the layout of other Winbond Gaming Keyboards, |
| like KT108 or KT87 (if that one really uses the same chip). |
| Maybe they use a similar version reply, maybe not... |
\*------------------------------------------------------------------------------------------*/
}
void WinbondGamingKeyboardController::SetVersionLayout()
{
{
/*-------------------------------------------------------------*\
| this requests a string with information about the version etc |
\*-------------------------------------------------------------*/
unsigned char msg[WINBOND_HID_DATA_LEN] = { 0x01, 0x0D, 0 };
hid_write(dev, msg, WINBOND_HID_DATA_LEN);
}
/*--------------------------------------------------------*\
| the reply looks like |
| 0x1, 0x0D, 0, 0, 0, "2NUC,01,KB,FL,K221UKCVRGB,V1.05.03" |
\*--------------------------------------------------------*/
for(int i=0; i<10; ++i) // 10 retries
{
/*-----------------------------------------------------------------------------*\
| +1 to make sure there's always a terminating \0 byte at the end of the string |
\*-----------------------------------------------------------------------------*/
unsigned char reply[WINBOND_HID_DATA_LEN + 1] = {};
int len = hid_read_timeout(dev, reply, WINBOND_HID_DATA_LEN, 150);
if(len < 0)
{
continue;
}
if(reply[0] != 1 || reply[1] != 0x0D || reply[4] != 0) // not the message we want
{
continue;
}
const char* str = (const char*)reply + 2; // skip 0x01 0x0D bytes
/*-----------------------------------------------------------------*\
| skip any leading whitespace, \0 bytes and other unprintable chars |
\*-----------------------------------------------------------------*/
for(int j=0; j < WINBOND_HID_DATA_LEN - 2; ++j, ++str)
{
if(*str > ' ' )
break;
}
if(*str != '\0')
{
ParseVersionString(str);
return;
}
}
/*--------------*\
| fallback |
\*--------------*/
version = "???";
}
std::string WinbondGamingKeyboardController::GetSerialString() const
{
wchar_t serial_wchar[128] = {};
int ret = hid_get_serial_number_string(dev, serial_wchar, 128);
if(ret == -1)
{
return "";
}
std::wstring serial_wstring(serial_wchar);
std::string serial_string;
std::transform(serial_wstring.begin(), serial_wstring.end(), std::back_inserter(serial_string), [] (wchar_t i)
{
return (char)i;
});
return serial_string;
}
static void setModeImpl(hid_device* dev, bool is_logo, unsigned char effect_mode, unsigned char colors[2][3],
bool full_color, unsigned char direction, unsigned char speed, unsigned char brightness)
{
unsigned char buf[WINBOND_HID_DATA_LEN] =
{
1, // byte 0: Report ID (always 1)
7, // byte 1: the "command", in this case 7 (set key LED mode) or 8 (set logo LED mode)
0, 0, 0, 0x0E, // bytes 2-5: not sure about the meaning, they were like this..
effect_mode, // byte 6 (effect mode, like WINBOND_GK_MODE_STATIC)
brightness, // byte 7
speed // byte 8
// the remaining bytes are set below or remain 0
};
if(is_logo)
{
buf[1] = 8;
buf[5] = 0x0D;
}
/*------------------------------*\
| bytes 9-14 are fg and bg color |
\*------------------------------*/
memcpy(buf+9, colors, 2*3);
buf[15] = direction;
buf[16] = full_color;
// the rest of the buffer remains 0
hid_write(dev, buf, WINBOND_HID_DATA_LEN);
}
void WinbondGamingKeyboardController::SetLEDsData(const std::vector<RGBColor>& colors, const std::vector<led>& leds, int brightness)
{
/*---------------------------------------------------------------------------*\
| There are 8 HID messages to set the LEDs. |
| Each message starts with the bytes shown below, followed by 18 RGB triplets |
| (except for the last message that has 12 RGB triplets). These triplets only |
| use values up to 0xC1 (193), instead of the usual 0xFF (255). |
| Byte 0 is the Report ID (1), 1 is the command (9), 2 and 3 are always 0 (?),|
| 4 is the message index, 5 is the length of the following RGB data in bytes |
\*---------------------------------------------------------------------------*/
unsigned char msgs[8][WINBOND_HID_DATA_LEN] =
{
{ 1, 9, 0, 0, 0, 0x36, 0 },
{ 1, 9, 0, 0, 1, 0x36, 0 },
{ 1, 9, 0, 0, 2, 0x36, 0 },
{ 1, 9, 0, 0, 3, 0x36, 0 },
{ 1, 9, 0, 0, 4, 0x36, 0 },
{ 1, 9, 0, 0, 5, 0x36, 0 },
{ 1, 9, 0, 0, 6, 0x36, 0 },
{ 1, 9, 0, 0, 7, 0x12, 0 },
};
RGBColor logo_color = ToRGBColor(128, 128, 128);
for(size_t i = 0, n = colors.size(); i < n; ++i)
{
unsigned val = leds[i].value;
if(val == 0) // no value set
{
continue;
}
/*--------------------------------------------------------------------------------------------------*\
| the following two lines are the inverse of the KV() macro in RGBController_WinbondGamingKeyboard.h |
\*--------------------------------------------------------------------------------------------------*/
int msg_num = (val >> 8) & 255;
int r_offset = (val & 255);
r_offset = r_offset*3 + 6; // 6 is position of first color byte in message
/*----------------------------*\
| special case: logo LED color |
\*----------------------------*/
if(msg_num == 255)
{
logo_color = colors[i];
// logo light is set separately, just remember its color
continue;
}
msg_num &= 7; // 0..7
/*----------------------------*\
| transform 0..0xFF to 0..0xC1 |
\*----------------------------*/
int r = (RGBGetRValue(colors[i]) * 0xC1) / 0xFF;
int g = (RGBGetGValue(colors[i]) * 0xC1) / 0xFF;
int b = (RGBGetBValue(colors[i]) * 0xC1) / 0xFF;
msgs[msg_num][r_offset] = r & 255;
msgs[msg_num][r_offset+1] = g & 255;
msgs[msg_num][r_offset+2] = b & 255;
}
for(int i=0; i<8; ++i)
{
hid_write(dev, msgs[i], WINBOND_HID_DATA_LEN);
}
if(hasLogoLight)
{
unsigned char colors[2][3] = {};
colors[0][0] = RGBGetRValue(logo_color);
colors[0][1] = RGBGetGValue(logo_color);
colors[0][2] = RGBGetBValue(logo_color);
setModeImpl(dev, true, WINBOND_GK_MODE_STATIC, colors, false, 0, 3, brightness);
}
}
static unsigned char getDirection(unsigned int dir, int effect)
{
/*---------------------------------------------------------------*\
| Winbond effect directions: 0: right, 1: left, 2: up, 3: down, |
| 4: to outside, 5: to inside, 6: clockwise, 7: counter-clockwise |
\*---------------------------------------------------------------*/
switch(effect)
{
case WINBOND_GK_MODE_WAVE:
/*-----------------------------------*\
| LR, UD; HV for to inside/to outside |
\*-----------------------------------*/
switch(dir)
{
case MODE_DIRECTION_LEFT:
return 1;
case MODE_DIRECTION_RIGHT:
return 0;
case MODE_DIRECTION_UP:
return 2;
case MODE_DIRECTION_DOWN:
return 3;
case MODE_DIRECTION_HORIZONTAL:
return 4;
case MODE_DIRECTION_VERTICAL:
return 5;
}
break;
case WINBOND_GK_MODE_SNAKE: // DOWN/UP for CW/CCW
return dir == MODE_DIRECTION_DOWN ? 6 : 7;
case WINBOND_GK_MODE_AURORA: // HORIZONTAL/VERTICAL for to outside/to inside
return dir == MODE_DIRECTION_HORIZONTAL ? 4 : 5;
}
return 0; // effects without a direction
}
void WinbondGamingKeyboardController::SetMode(const mode& m)
{
unsigned char colors[2][3] = { {0, 0, 255}, {0, 0, 0} };
for(size_t i=0; i < std::min((size_t)2, m.colors.size()); ++i)
{
RGBColor c = m.colors[i];
colors[i][0] = RGBGetRValue(c);
colors[i][1] = RGBGetGValue(c);
colors[i][2] = RGBGetBValue(c);
}
unsigned char direction = getDirection(m.direction, m.value);
bool full_color = (m.color_mode == MODE_COLORS_RANDOM || m.value == WINBOND_GK_MODE_CUSTOM);
setModeImpl(dev, false, m.value, colors, full_color, direction, m.speed, m.brightness);
if(hasLogoLight)
{
/*---------------------------------------------------------------------------------*\
| logo light supports static, neon (WINBOND_GK_MODE_LOGO_NEON), breathe, wave |
| all logo modes have brightness, all but static have speed and 2 colors + random |
| select a mode supported by the logo that matches the key mode |
| |
| TODO: the keyboard allows selecting completely different modes for keys and the |
| logo light, but OpenRGB currently doesn't support different modes per zone. |
| if OpenRGB ever supports that, change the code accordingly |
\*---------------------------------------------------------------------------------*/
int mode = m.value;
int speed = m.speed;
if(mode == WINBOND_GK_MODE_NEON)
{
mode = WINBOND_GK_MODE_LOGO_NEON;
}
else if(mode == WINBOND_GK_MODE_SNAKE)
{
mode = WINBOND_GK_MODE_WAVE;
direction = (direction == 6) ? 1 : 0;
}
else if(mode > WINBOND_GK_MODE_SNAKE)
{
/*------------------------------------------------------*\
| these remaining modes are reactive, no real equivalent |
| => use static |
\*------------------------------------------------------*/
mode = WINBOND_GK_MODE_STATIC;
direction = 0;
speed = 0;
}
// else: the other modes < WINBOND_GK_MODE_SNAKE are supported as is
setModeImpl(dev, true, mode, colors, full_color, direction, speed, m.brightness);
}
}

View file

@ -0,0 +1,88 @@
/*---------------------------------------------*\
| WinbondGamingKeyboardController.h |
| |
| Driver for "Winbond Gaming Keyboard" boards, |
| like Pulsar PCMK TKL Keyboard |
| |
| Daniel Gibson 3 December 2023 |
\*---------------------------------------------*/
#pragma once
#include "RGBController.h"
#include "KeyboardLayoutManager.h"
#include <string>
#include <vector>
#include <hidapi/hidapi.h>
class WinbondGamingKeyboardController
{
void SetNameVendorDescription(const hid_device_info &info, const std::string& name);
void SetVersionLayout();
void ParseVersionString(const char* str);
public:
WinbondGamingKeyboardController(hid_device *dev_handle, const hid_device_info &info, const std::string& name);
std::string GetSerialString() const;
const char* GetDeviceLocation() const
{
return location.c_str();
}
const char* GetName() const
{
return name.c_str();
}
const char* GetVendor() const
{
return vendor.c_str();
}
const char* GetDescription() const
{
return description.c_str();
}
const char* GetVersion() const
{
return version.c_str();
}
KEYBOARD_LAYOUT GetLayout() const
{
return layout;
}
bool HasLogoLight() const
{
return hasLogoLight;
}
KEYBOARD_SIZE GetSize() const
{
return kb_size;
}
void SetLEDsData(const std::vector<RGBColor>& colors, const std::vector<led>& leds, int brightness);
void SetMode(const mode& m);
protected:
hid_device* dev = nullptr;
private:
std::string location;
std::string name;
std::string description;
std::string vendor;
std::string version;
KEYBOARD_LAYOUT layout = KEYBOARD_LAYOUT_ISO_QWERTY; // default to ISO so most keys can be configured
KEYBOARD_SIZE kb_size = KEYBOARD_SIZE_FULL;
bool hasLogoLight = false;
};

View file

@ -0,0 +1,68 @@
/*---------------------------------------------*\
| WinbondGamingKeyboardControllerDetect.cpp |
| |
| Driver for "Winbond Gaming Keyboard" boards, |
| like Pulsar PCMK TKL Keyboard |
| |
| Daniel Gibson 3 December 2023 |
\*---------------------------------------------*/
#include "Detector.h"
#include "RGBController_WinbondGamingKeyboard.h"
#include <hidapi/hidapi.h>
#include "LogManager.h"
/*-----------------------------------------------------*\
| Winbond vendor ID |
\*-----------------------------------------------------*/
#define WINBOND_VID 0x0416
/*-----------------------------------------------------*\
| Winbond product ID |
\*-----------------------------------------------------*/
#define WINBOND_GAMING_KEYBOARD_PID 0xB23C
void DetectWinbondGamingKeyboard(hid_device_info* info, const std::string& name)
{
hid_device* dev = hid_open_path(info->path);
/*--------------------------------------------------------------------------------------------------*\
| NOTE: according to https://4pda.to/forum/index.php?showtopic=1061923, |
| the "KT108" keyboard, which has the same VID:PID, uses the product_string "KT108 keyboard" |
| that could be used for KT108-specific settings? OTOH, according to |
| https://usb-ids.gowdy.us/read/UD/0416/b23c there are also variants of KT108 with |
| product string "Gaming Keyboard". |
| Apart from the KT108, there's also a KT87, but no idea about its product_string, |
| or even its VID/PID (I *assume* it's also 0416:B23C) |
| KT87 and KT108 seem to be sold under the brands WIANXP, Nautilus and Capturer |
| |
| Apart from those noname keyboards that one might only find on aliexpress and similar shops, |
| the Pulsar PCMK TKL keyboard (barebone) uses this VID+PID, and that is the one this is |
| tested with - the ISO variant, specifically. |
| ANSI and JIS variants also exist, I'll try to support them as best as I can. |
| |
| Pulsar also offers a 60% barebone in ISO, ANSI and JIS, but no idea about its VID or PID, |
| or product_string, much less about its protocol (even if it uses the same firmware, |
| I don't know which key corresponds to which bytes in the HID message that sets the per-key colors) |
\*--------------------------------------------------------------------------------------------------*/
if(dev)
{
LOG_INFO("Detected WinbondGamingKeyboard at %s, product_string is %ls name is %s", info->path, info->product_string, name.c_str());
WinbondGamingKeyboardController* controller = new WinbondGamingKeyboardController(dev, *info, name);
RGBController* rgb_controller = new RGBController_WinbondGamingKeyboard(controller);
ResourceManager::get()->RegisterRGBController(rgb_controller);
}
else
{
LOG_WARNING("Couldn't open hid dev %s: %ls", info->path, hid_error(NULL));
}
}
REGISTER_HID_DETECTOR_PU("Winbond Gaming Keyboard", DetectWinbondGamingKeyboard, WINBOND_VID, WINBOND_GAMING_KEYBOARD_PID, 0xFF1B, 0x91);