Support "Winbond Gaming Keyboard", like Pulsar PCMK or WIANXP/Nautilus/Capturer KT108
This commit is contained in:
parent
6efc7ff294
commit
7650ec67ba
6 changed files with 1457 additions and 0 deletions
|
|
@ -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]);
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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);
|
||||
Loading…
Add table
Add a link
Reference in a new issue