Skip to content

Instantly share code, notes, and snippets.

@georgyangelov
Last active September 9, 2019 13:55
Show Gist options
  • Save georgyangelov/297de067a00aebc0a4748fa798a0b234 to your computer and use it in GitHub Desktop.
Save georgyangelov/297de067a00aebc0a4748fa798a0b234 to your computer and use it in GitHub Desktop.
C++ wrapper class for working with the Creative Aurora LED SDK (for the SoundBlasterX Vanguard K08 keyboard)
#pragma once
#include <initguid.h>
#include <windows.h>
#include <vector>
#include "../sdk/Include/ICTLEDMgr.h"
#include "../sdk/Include/CLSID_CCTLEDMgr.h"
#include "../sdk/Include/CTIntrfc_DynamicLoad.h"
#include "../sdk/Include/CTLEDCommon.h"
#include "LEDGroupings.h"
struct ConnectedDevice {
USHORT vendorId;
USHORT productId;
WCHAR serialNumber[1000];
DWORD serialNumberSize = 1000;
WCHAR instance[1000];
DWORD instanceSize = 1000;
USHORT ledInfoFlag;
USHORT totalNumLeds;
WCHAR friendlyName[1000];
DWORD friendlyNameSize = 1000;
};
inline int timerCallback(CTLEDMGRTIMERPROCDATA*, LPARAM);
struct KeyboardController {
private:
ICTLEDMgr* api;
int groupCount = ALL_LEDS_COUNT;
int ledsInGroup = 1;
DWORD grouping[ALL_LEDS_COUNT][2] = {0};
CTLEDMGRCMDPARAM_SetLedSettings ledSettings = { };
CTLED_Pattern pattern[ALL_LEDS_COUNT] = { };
// TODO: Remove the padding
CTColour pixels[ALL_LEDS_COUNT + 1000] = {0};
bool running = false;
int targetIntervalMillis = 1;
friend int timerCallback(CTLEDMGRTIMERPROCDATA*, LPARAM);
public:
KeyboardController(ICTLEDMgr* api) {
this->api = api;
configureSingleLedGrouping(grouping);
for (int i = 0; i < groupCount; i++) {
pattern[i] = CTLED_Pattern_Static;
}
}
std::vector<ConnectedDevice> enumerateConnectedDevices() {
std::vector<ConnectedDevice> devices;
for (int i = 0; ; i++) {
ConnectedDevice device;
HRESULT result = api->EnumConnectedDevices(
i,
&device.vendorId,
&device.productId,
(LPWSTR)&device.serialNumber,
&device.serialNumberSize,
(LPWSTR)&device.instance,
&device.instanceSize,
&device.ledInfoFlag,
&device.totalNumLeds,
(LPWSTR)&device.friendlyName,
&device.friendlyNameSize,
0
);
if (!SUCCEEDED(result)) {
break;
}
devices.push_back(device);
}
return devices;
}
void openDevice(ConnectedDevice* device) {
DWORD detailErrorCode;
HRESULT result = api->Open(
device->vendorId,
device->productId,
device->serialNumber,
device->instance,
0,
0,
false,
&detailErrorCode,
0
);
if (!SUCCEEDED(result)) {
// TODO: Error handling
throw std::exception("Cannot open device");
}
configureLeds();
}
void closeDevice() {
api->Close(0);
}
int getPixelCount() {
return ALL_LEDS_COUNT;
}
CTColour* getPixelBuffer() {
return pixels;
}
void fillPixelBuffer(CTColour* colors, int colorCount) {
if (colorCount > ALL_LEDS_COUNT) {
colorCount = ALL_LEDS_COUNT;
}
memcpy(pixels, colors, colorCount * sizeof(CTColour));
}
void sendPixels() {
CTLEDINFOCMDPARAM_FillupAll fill = { };
fill.pLedInfo = &ledSettings.colourLed;
fill.dwNumLedGroups = groupCount;
fill.pPatternIndividualLedGroupArray1D = &pattern[0];
fill.pdwLedGroupingArray2D = &grouping[0][0];
fill.dwNumColumnsOfLedGroupArray2D = ledsInGroup + 1;
fill.dwNumColourLayers = 1;
fill.pColourIndividualLedGroupArray2D = &pixels[0];
fill.dwNumColumnsOfColourIndividualLedGroupArray2D = 1;
HRESULT result = api->PrepareLedInfo(
CTLEDINFOCMD_FillupAll,
(LPARAM)&fill,
0
);
if (!SUCCEEDED(result)) {
// TODO: Error handling
throw std::exception("Cannot prepare LED info");
}
CTInit_CTLEDMGRCMDPARAM_SetLedSettings(&ledSettings);
ledSettings.fIgnoreColour = false;
ledSettings.fIgnorePattern = false;
ledSettings.patternLed = CTLED_Pattern_Static;
ledSettings.fIgnorePatternDirection = true;
ledSettings.fIgnorePatternDirectionFlag = true;
ledSettings.flagLedPatternDirection = CTLED_PatternDirectionFlag_Looping;
ledSettings.fIgnorePeriodicTime = true;
ledSettings.patternLedThePeriodicTimeIsFor = CTLED_Pattern_Static;
ledSettings.dwPeriodicTimeInMilliseconds = 0;
ledSettings.dwPeriodicTimeInCyclesPerMinute = 0;
result = api->ExecuteCommand(CTLEDMGRCMD_SetLedSettings, (LPARAM)&ledSettings, 0);
if (!SUCCEEDED(result)) {
// TODO: Error handling
throw std::exception("Cannot set LEDs");
}
}
void enableTimerSending() {
CTTIMERINFOPARAM timerParameters = { };
timerParameters.dwDueTimeInMilliseconds = 0;
timerParameters.dwPeriodicTimeInMilliseconds = 1;
//timerParameters.dwPeriodicTimeInMilliseconds = 1000 / 60;
HRESULT result = api->RegisterTimerCallback(timerCallback, &timerParameters, (LPARAM)this, 0);
if (!SUCCEEDED(result)) {
throw std::exception("Cannot register timer callback");
}
running = true;
targetIntervalMillis = timerParameters.dwPeriodicTimeInMilliseconds;
}
void disableTimerSending() {
HRESULT result = api->UnregisterTimerCallback(0);
if (!SUCCEEDED(result)) {
throw std::exception("Cannot unregister timer callback");
}
running = false;
}
private:
void configureLeds() {
CTLEDINFOCMDPARAM_Initialize initializeConfig = { };
initializeConfig.dwMaxNumLedGroups = groupCount;
initializeConfig.dwMaxNumLedsInEachGroup = ledsInGroup;
initializeConfig.dwMaxNumColourLayersInEachGroup = 1;
initializeConfig.pLedInfo = &ledSettings.colourLed;
HRESULT result = api->PrepareLedInfo(CTLEDINFOCMD_Initialize, (LPARAM)&initializeConfig, 0);
if (!SUCCEEDED(result)) {
// TODO: Error handling
throw std::exception("Cannot initialize LED config");
}
}
};
inline int timerCallback(CTLEDMGRTIMERPROCDATA* timerData, LPARAM userData) {
auto controller = (KeyboardController*)userData;
if (controller->running){
controller->sendPixels();
}
return 0;
}
#define EXPORTED extern "C" __declspec(dllexport)
EXPORTED KeyboardController* create_controller() {
HMODULE interfaceUtilsDll = LoadLibraryW(L"CTIntrfu.dll");
ICTLEDMgr* manager;
CTINTRFCRESULT result = CTCreateInstanceEx_Dyn(
interfaceUtilsDll, CLSID_CCTLEDMgr, NULL, 0, IID_ICTLEDMgr, NULL, NULL,
L"CTLEDMgr.dll", NULL, (void**)&manager
);
if (result != CTINTRFCRESULT_Success) {
return nullptr;
}
DWORD dwFlag = 0;
HRESULT initResult = manager->Initialize(DEFINITION_CTLEDMgr_Interface_Version, dwFlag);
if (!SUCCEEDED(initResult)) {
manager->Release();
CTFreeUnusedLibrariesEx_Dyn(interfaceUtilsDll);
return nullptr;
}
return new KeyboardController(manager);
}
EXPORTED size_t controller_get_connected_devices(KeyboardController* self, ConnectedDevice* connectedDevices, int connectedDevicesCapacity) {
auto devices = self->enumerateConnectedDevices();
if (devices.size() < connectedDevicesCapacity) {
return -1;
}
for (int i = 0; i < devices.size(); i++) {
connectedDevices[i] = devices[i];
}
return devices.size();
}
EXPORTED void controller_open_device(KeyboardController* self, ConnectedDevice* device) {
self->openDevice(device);
}
EXPORTED void controller_close_device(KeyboardController* self) {
self->closeDevice();
}
EXPORTED int controller_get_pixel_count(KeyboardController* self) {
return self->getPixelCount();
}
EXPORTED void controller_fill_pixel_buffer(KeyboardController* self, CTColour* pixels, int pixelCount) {
self->fillPixelBuffer(pixels, pixelCount);
}
EXPORTED CTColour* controller_get_pixel_buffer(KeyboardController* self) {
return self->getPixelBuffer();
}
EXPORTED void controller_send_pixels(KeyboardController* self) {
self->sendPixels();
}
EXPORTED void controller_enable_timer_sending(KeyboardController* self) {
self->enableTimerSending();
}
#pragma once
const DWORD ALL_LEDS[] = {
CTKEYBOARD_LEDIndex_Esc,
CTKEYBOARD_LEDIndex_F1,
CTKEYBOARD_LEDIndex_F2,
CTKEYBOARD_LEDIndex_F3,
CTKEYBOARD_LEDIndex_F4,
CTKEYBOARD_LEDIndex_F5,
CTKEYBOARD_LEDIndex_F6,
CTKEYBOARD_LEDIndex_F7,
CTKEYBOARD_LEDIndex_F8,
CTKEYBOARD_LEDIndex_F9,
CTKEYBOARD_LEDIndex_F10,
CTKEYBOARD_LEDIndex_F11,
CTKEYBOARD_LEDIndex_F12,
CTKEYBOARD_LEDIndex_PrintScreen,
CTKEYBOARD_LEDIndex_ScrollLock,
CTKEYBOARD_LEDIndex_Pause,
CTKEYBOARD_LEDIndex_M1,
CTKEYBOARD_LEDIndex_BackQuote,
CTKEYBOARD_LEDIndex_1,
CTKEYBOARD_LEDIndex_2,
CTKEYBOARD_LEDIndex_3,
CTKEYBOARD_LEDIndex_4,
CTKEYBOARD_LEDIndex_5,
CTKEYBOARD_LEDIndex_6,
CTKEYBOARD_LEDIndex_7,
CTKEYBOARD_LEDIndex_8,
CTKEYBOARD_LEDIndex_9,
CTKEYBOARD_LEDIndex_0,
CTKEYBOARD_LEDIndex_Minus,
CTKEYBOARD_LEDIndex_Equal,
CTKEYBOARD_LEDIndex_Backspace,
CTKEYBOARD_LEDIndex_Insert,
CTKEYBOARD_LEDIndex_Home,
CTKEYBOARD_LEDIndex_PageUp,
CTKEYBOARD_LEDIndex_PadNumLock,
CTKEYBOARD_LEDIndex_PadForwardSlash,
CTKEYBOARD_LEDIndex_PadAsterisk,
CTKEYBOARD_LEDIndex_PadMinus,
CTKEYBOARD_LEDIndex_M2,
CTKEYBOARD_LEDIndex_Tab,
CTKEYBOARD_LEDIndex_Q,
CTKEYBOARD_LEDIndex_W,
CTKEYBOARD_LEDIndex_E,
CTKEYBOARD_LEDIndex_R,
CTKEYBOARD_LEDIndex_T,
CTKEYBOARD_LEDIndex_Y,
CTKEYBOARD_LEDIndex_U,
CTKEYBOARD_LEDIndex_I,
CTKEYBOARD_LEDIndex_O,
CTKEYBOARD_LEDIndex_P,
CTKEYBOARD_LEDIndex_OpenBracket,
CTKEYBOARD_LEDIndex_ClosedBracket,
CTKEYBOARD_LEDIndex_BackSlash,
CTKEYBOARD_LEDIndex_Delete,
CTKEYBOARD_LEDIndex_End,
CTKEYBOARD_LEDIndex_PageDown,
CTKEYBOARD_LEDIndex_Pad7,
CTKEYBOARD_LEDIndex_Pad8,
CTKEYBOARD_LEDIndex_Pad9,
CTKEYBOARD_LEDIndex_PadPlus,
CTKEYBOARD_LEDIndex_M3,
CTKEYBOARD_LEDIndex_CapsLock,
CTKEYBOARD_LEDIndex_A,
CTKEYBOARD_LEDIndex_S,
CTKEYBOARD_LEDIndex_D,
CTKEYBOARD_LEDIndex_F,
CTKEYBOARD_LEDIndex_G,
CTKEYBOARD_LEDIndex_H,
CTKEYBOARD_LEDIndex_J,
CTKEYBOARD_LEDIndex_K,
CTKEYBOARD_LEDIndex_L,
CTKEYBOARD_LEDIndex_Semicolon,
CTKEYBOARD_LEDIndex_Apostrophe,
CTKEYBOARD_LEDIndex_Enter,
CTKEYBOARD_LEDIndex_Pad4,
CTKEYBOARD_LEDIndex_Pad5,
CTKEYBOARD_LEDIndex_Pad6,
CTKEYBOARD_LEDIndex_M4,
CTKEYBOARD_LEDIndex_LeftShift,
CTKEYBOARD_LEDIndex_Z,
CTKEYBOARD_LEDIndex_X,
CTKEYBOARD_LEDIndex_C,
CTKEYBOARD_LEDIndex_V,
CTKEYBOARD_LEDIndex_B,
CTKEYBOARD_LEDIndex_N,
CTKEYBOARD_LEDIndex_M,
CTKEYBOARD_LEDIndex_Comma,
CTKEYBOARD_LEDIndex_Fullstop,
CTKEYBOARD_LEDIndex_ForwardSlash,
CTKEYBOARD_LEDIndex_RightShift,
CTKEYBOARD_LEDIndex_UpArrow,
CTKEYBOARD_LEDIndex_Pad1,
CTKEYBOARD_LEDIndex_Pad2,
CTKEYBOARD_LEDIndex_Pad3,
CTKEYBOARD_LEDIndex_PadEnter,
CTKEYBOARD_LEDIndex_M5,
CTKEYBOARD_LEDIndex_LeftCtrl,
CTKEYBOARD_LEDIndex_LeftWindows,
CTKEYBOARD_LEDIndex_LeftAlt,
CTKEYBOARD_LEDIndex_Space,
CTKEYBOARD_LEDIndex_RightAlt,
CTKEYBOARD_LEDIndex_Fn,
CTKEYBOARD_LEDIndex_Menu,
CTKEYBOARD_LEDIndex_RightCtrl,
CTKEYBOARD_LEDIndex_LeftArrow,
CTKEYBOARD_LEDIndex_DownArrow,
CTKEYBOARD_LEDIndex_RightArrow,
CTKEYBOARD_LEDIndex_Pad0,
CTKEYBOARD_LEDIndex_PadFullstop,
CTKEYBOARD_LEDIndex_Logo,
};
const int ALL_LEDS_COUNT = sizeof(ALL_LEDS) / sizeof(ALL_LEDS[0]);
inline void configureSingleLedGrouping(DWORD grouping[][2]) {
for (int i = 0; i < ALL_LEDS_COUNT; i++) {
grouping[i][0] = 1;
grouping[i][1] = ALL_LEDS[i];
}
}
#include "KeyboardController.h"
#include <iostream>
int main() {
KeyboardController* controller = create_controller();
auto devices = controller->enumerateConnectedDevices();
if (devices.empty()) {
std::cerr << "No connected devices\n";
return 1;
}
if (devices.size() > 1) {
std::cerr << "More than one supported device is connected. Cannot choose.\n";
return 2;
}
auto device = devices[0];
controller->openDevice(&device);
CTColour* colors = controller->getPixelBuffer();
for (int i = 0; i < controller->getPixelCount(); i++) {
colors[i] = CTColour{255, 0, 0, 255};
}
controller->enableTimerSending();
std::cout << "Press enter to stop...";
std::cin.get();
controller->disableTimerSending();
controller->closeDevice();
return 0;
}
@GhoulCZ
Copy link

GhoulCZ commented Sep 8, 2019

Hi Georgy,
good job here.
But when trying the code I've faced an issue at line 185 where RegisterTimerCallback expects CTLEDMGRTIMERPROC as a type of the first argument. Couldn't run it like that.
Any idea how to overcome this?
Thanks for any advice

@georgyangelov
Copy link
Author

Hey Ghoul,

What is the error you get? Are you using the same SDK on the same keyboard?

@GhoulCZ
Copy link

GhoulCZ commented Sep 9, 2019

Hi Georgy,
thanks for the quick reply.
I do have the SoundBlasterX K08 Vanguard Keyboard. The SDK I'm using is (according to the documentation provided) Revision 0.90 form 29 September 2017. And in the Include folder it does contain the same four libraries as you're including at the beginning of your KeyboardController.h.

The error code is C2664.
Unfortunately I've installed Visual Studio in my language so I can not simply copy the error because it's not in english. I'll try to translate:

error C2664: HRESULT ICTLEDMgr::RegisterTimerCallback(PFNCTLEDMGRTIMERPROC,PCTTIMERINFOPARAM,LPARAM,DWORD): cannot convert parameter 1 from: int (__cdecl *)(CTLEDMGRTIMERPROCDATA *,LPARAM) to: PFNCTLEDMGRTIMERPROC.
note: None of the functions with this name match the target type.

Error

@georgyangelov
Copy link
Author

Interesting. It seems like something related to the pointer width? Maybe the SDK dlls are compiled in 32bit mode while your code is trying to use it in 64bit? Or vice-versa. You can try changing the build setting in VS for your project and see if that does something

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment