diff --git a/Controllers/WinbondGamingKeyboardController/RGBController_WinbondGamingKeyboard.cpp b/Controllers/WinbondGamingKeyboardController/RGBController_WinbondGamingKeyboard.cpp new file mode 100644 index 00000000..e1af956a --- /dev/null +++ b/Controllers/WinbondGamingKeyboardController/RGBController_WinbondGamingKeyboard.cpp @@ -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 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 ansi" is set in InitLayouts() + { // std::map > 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 ansi" is set in InitLayouts() + { // std::map > 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 ansi" is set in InitLayouts() + { // std::map > 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& 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>& 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]); +} diff --git a/Controllers/WinbondGamingKeyboardController/RGBController_WinbondGamingKeyboard.h b/Controllers/WinbondGamingKeyboardController/RGBController_WinbondGamingKeyboard.h new file mode 100644 index 00000000..5cbc5ee4 --- /dev/null +++ b/Controllers/WinbondGamingKeyboardController/RGBController_WinbondGamingKeyboard.h @@ -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; +}; diff --git a/Controllers/WinbondGamingKeyboardController/WinbondGamingKeyboardController.cpp b/Controllers/WinbondGamingKeyboardController/WinbondGamingKeyboardController.cpp new file mode 100644 index 00000000..a91f737f --- /dev/null +++ b/Controllers/WinbondGamingKeyboardController/WinbondGamingKeyboardController.cpp @@ -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 +#include + +#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& colors, const std::vector& 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); + } +} diff --git a/Controllers/WinbondGamingKeyboardController/WinbondGamingKeyboardController.h b/Controllers/WinbondGamingKeyboardController/WinbondGamingKeyboardController.h new file mode 100644 index 00000000..05278b43 --- /dev/null +++ b/Controllers/WinbondGamingKeyboardController/WinbondGamingKeyboardController.h @@ -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 +#include + +#include + + +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& colors, const std::vector& 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; +}; diff --git a/Controllers/WinbondGamingKeyboardController/WinbondGamingKeyboardControllerDetect.cpp b/Controllers/WinbondGamingKeyboardController/WinbondGamingKeyboardControllerDetect.cpp new file mode 100644 index 00000000..5fcf2fca --- /dev/null +++ b/Controllers/WinbondGamingKeyboardController/WinbondGamingKeyboardControllerDetect.cpp @@ -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 + +#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); diff --git a/OpenRGB.pro b/OpenRGB.pro index 970bedc1..72ad8988 100644 --- a/OpenRGB.pro +++ b/OpenRGB.pro @@ -794,6 +794,8 @@ HEADERS += Controllers/TrustController/TrustGXT180Controller.h \ Controllers/ViewSonicController/VS_XG270QG_Controller.h \ Controllers/ViewSonicController/RGBController_XG270QG.h \ + Controllers/WinbondGamingKeyboardController/RGBController_WinbondGamingKeyboard.h \ + Controllers/WinbondGamingKeyboardController/WinbondGamingKeyboardController.h \ Controllers/WootingKeyboardController/WootingKeyboardController.h \ Controllers/WootingKeyboardController/WootingOneKeyboardController.h \ Controllers/WootingKeyboardController/WootingTwoKeyboardController.h \ @@ -1531,6 +1533,9 @@ SOURCES += Controllers/ViewSonicController/ViewSonicControllerDetect.cpp \ Controllers/ViewSonicController/RGBController_XG270QG.cpp \ Controllers/ViewSonicController/VS_XG270QG_Controller.cpp \ + Controllers/WinbondGamingKeyboardController/RGBController_WinbondGamingKeyboard.cpp \ + Controllers/WinbondGamingKeyboardController/WinbondGamingKeyboardController.cpp \ + Controllers/WinbondGamingKeyboardController/WinbondGamingKeyboardControllerDetect.cpp \ Controllers/WootingKeyboardController/WootingKeyboardController.cpp \ Controllers/WootingKeyboardController/WootingKeyboardControllerDetect.cpp \ Controllers/WootingKeyboardController/WootingOneKeyboardController.cpp \