|  | 
|  | 1 | +/*-------------------------------------------------*\ | 
|  | 2 | +|  EVGAMouseController.cpp                          | | 
|  | 3 | +|                                                   | | 
|  | 4 | +|  Driver for EVGA X20 Gaming Mouse RGB Controller. | | 
|  | 5 | +|                                                   | | 
|  | 6 | +|  Cooper Knaak 1/23/2022                           | | 
|  | 7 | +\*-------------------------------------------------*/ | 
|  | 8 | + | 
|  | 9 | +#include "EVGAMouseController.h" | 
|  | 10 | +#include "LogManager.h" | 
|  | 11 | + | 
|  | 12 | +#include <algorithm> | 
|  | 13 | +#include <iostream> | 
|  | 14 | +#include <thread> | 
|  | 15 | +#include <chrono> | 
|  | 16 | + | 
|  | 17 | + | 
|  | 18 | +#define HID_MAX_STR 255 | 
|  | 19 | +#define EVGA_PERIPHERAL_LED_SOURCE_OF_TRUTH EVGA_PERIPHERAL_LED_LOGO | 
|  | 20 | +/*----------------------------------------------------------------*\ | 
|  | 21 | +| Maximum number of attempts to read from a device before failing. | | 
|  | 22 | +\*----------------------------------------------------------------*/ | 
|  | 23 | +#define EVGA_PERIPHERAL_MAX_ATTEMPTS 100 | 
|  | 24 | +/*-----------------------------------------------------------------*\ | 
|  | 25 | +| The delay between sending packets to the device in wireless mode. | | 
|  | 26 | +| In wireless mode, sending packets too close to each other causes  | | 
|  | 27 | +| them to have no effect, despite the device responding properly.   | | 
|  | 28 | +\*-----------------------------------------------------------------*/ | 
|  | 29 | +#define EVGA_PERIPHERAL_PACKET_DELAY std::chrono::milliseconds(10) | 
|  | 30 | + | 
|  | 31 | +/*--------------------------------------------------------------------------------*\ | 
|  | 32 | +| Returns true if both buffers have equal bytes at each position, false otherwise. | | 
|  | 33 | +| Each buffer must be an array of bytes at least size bytes long.                  | | 
|  | 34 | +\*--------------------------------------------------------------------------------*/ | 
|  | 35 | +static bool BuffersAreEqual(unsigned char *buffer1, unsigned char *buffer2, int size) | 
|  | 36 | +{ | 
|  | 37 | +    for(int i = 0; i < size; i++) | 
|  | 38 | +    { | 
|  | 39 | +        if(buffer1[i] != buffer2[i]) | 
|  | 40 | +        { | 
|  | 41 | +            return false; | 
|  | 42 | +        } | 
|  | 43 | +    } | 
|  | 44 | +    return true; | 
|  | 45 | +} | 
|  | 46 | + | 
|  | 47 | +EVGAMouseController::EVGAMouseController(hid_device* dev_handle, char *_path, int connection_type) | 
|  | 48 | +{ | 
|  | 49 | +    dev                     = dev_handle; | 
|  | 50 | +    location                = _path; | 
|  | 51 | +    this->connection_type   = connection_type; | 
|  | 52 | + | 
|  | 53 | +    const int szTemp = HID_MAX_STR; | 
|  | 54 | +    wchar_t tmpName[szTemp]; | 
|  | 55 | + | 
|  | 56 | +    hid_get_manufacturer_string(dev, tmpName, szTemp); | 
|  | 57 | +    std::wstring wName  = std::wstring(tmpName); | 
|  | 58 | +    device_name         = std::string(wName.begin(), wName.end()); | 
|  | 59 | + | 
|  | 60 | +    hid_get_product_string(dev, tmpName, szTemp); | 
|  | 61 | +    wName = std::wstring(tmpName); | 
|  | 62 | +    device_name.append(" ").append(std::string(wName.begin(), wName.end())); | 
|  | 63 | + | 
|  | 64 | +    hid_get_indexed_string(dev, 2, tmpName, szTemp); | 
|  | 65 | +    wName   = std::wstring(tmpName); | 
|  | 66 | +    serial  = std::string(wName.begin(), wName.end()); | 
|  | 67 | + | 
|  | 68 | +    led_states.resize(EVGA_PERIPHERAL_LED_COUNT); | 
|  | 69 | +    for(EVGAMouseControllerDeviceState &led_state : led_states) | 
|  | 70 | +    { | 
|  | 71 | +        led_state.mode          = EVGA_PERIPHERAL_MODE_STATIC; | 
|  | 72 | +        led_state.brightness    = 255; | 
|  | 73 | +        led_state.speed         = 100; | 
|  | 74 | +        led_state.colors.resize(1); | 
|  | 75 | +        led_state.colors[0]     = ToRGBColor(255, 255, 255); | 
|  | 76 | +    } | 
|  | 77 | +} | 
|  | 78 | + | 
|  | 79 | +EVGAMouseController::~EVGAMouseController() | 
|  | 80 | +{ | 
|  | 81 | + | 
|  | 82 | +} | 
|  | 83 | + | 
|  | 84 | +std::string EVGAMouseController::GetDeviceName() | 
|  | 85 | +{ | 
|  | 86 | +    return device_name; | 
|  | 87 | +} | 
|  | 88 | + | 
|  | 89 | +std::string EVGAMouseController::GetSerial() | 
|  | 90 | +{ | 
|  | 91 | +    return serial; | 
|  | 92 | +} | 
|  | 93 | + | 
|  | 94 | +std::string EVGAMouseController::GetLocation() | 
|  | 95 | +{ | 
|  | 96 | +    return location; | 
|  | 97 | +} | 
|  | 98 | + | 
|  | 99 | +uint8_t EVGAMouseController::GetMode() | 
|  | 100 | +{ | 
|  | 101 | +    return GetState().mode; | 
|  | 102 | +} | 
|  | 103 | + | 
|  | 104 | +EVGAMouseControllerDeviceState EVGAMouseController::GetState() | 
|  | 105 | +{ | 
|  | 106 | +    RefreshDeviceState(EVGA_PERIPHERAL_LED_SOURCE_OF_TRUTH); | 
|  | 107 | +    return led_states[EVGA_PERIPHERAL_LED_SOURCE_OF_TRUTH]; | 
|  | 108 | +} | 
|  | 109 | + | 
|  | 110 | +RGBColor EVGAMouseController::GetColorOfLed(int led) | 
|  | 111 | +{ | 
|  | 112 | +    RefreshDeviceState(led); | 
|  | 113 | +    return led_states[led].colors[0]; | 
|  | 114 | +} | 
|  | 115 | + | 
|  | 116 | +void EVGAMouseController::SetMode(uint8_t mode, uint8_t index) | 
|  | 117 | +{ | 
|  | 118 | +    unsigned char buffer[EVGA_PERIPHERAL_PACKET_SIZE] = | 
|  | 119 | +    { | 
|  | 120 | +        0x00,                   /* report id - must be 0x00 according to hid_send_feature_report */ | 
|  | 121 | +        0x00, 0x00, 0x00, 0x1D, /* header bits - always the same */ | 
|  | 122 | +        0x02, 0x81, 0x01        /* 0x81 sets the mode, which is specified below. */ | 
|  | 123 | +    }; | 
|  | 124 | + | 
|  | 125 | +    buffer[EVGA_PERIPHERAL_LED_INDEX_BYTE]  = index; | 
|  | 126 | +    buffer[EVGA_PERIPHERAL_MODE_BYTE]       = mode; | 
|  | 127 | +    int err                                 = hid_send_feature_report(dev, buffer, EVGA_PERIPHERAL_PACKET_SIZE); | 
|  | 128 | +    if(err == -1) | 
|  | 129 | +    { | 
|  | 130 | +        const wchar_t* err_str = hid_error(dev); | 
|  | 131 | +        LOG_DEBUG("[%s] Error writing buffer %s", device_name.c_str(), err_str); | 
|  | 132 | +    } | 
|  | 133 | +    led_states[index].mode  = mode; | 
|  | 134 | +    err                     = hid_get_feature_report(dev, buffer, EVGA_PERIPHERAL_PACKET_SIZE); | 
|  | 135 | +    if(err == -1) | 
|  | 136 | +    { | 
|  | 137 | +        const wchar_t* err_str = hid_error(dev); | 
|  | 138 | +        LOG_DEBUG("[%s] Error reading buffer %s", device_name.c_str(), err_str); | 
|  | 139 | +    } | 
|  | 140 | +} | 
|  | 141 | + | 
|  | 142 | +void EVGAMouseController::SetLed(uint8_t index, uint8_t brightness, uint8_t speed, RGBColor color) | 
|  | 143 | +{ | 
|  | 144 | +    SetLed(index, brightness, speed, std::vector({color}), false); | 
|  | 145 | +} | 
|  | 146 | + | 
|  | 147 | +void EVGAMouseController::SetLed(uint8_t index, uint8_t brightness, uint8_t speed, const std::vector<RGBColor>& colors) | 
|  | 148 | +{ | 
|  | 149 | +    SetLed(index, brightness, speed, colors, false); | 
|  | 150 | +} | 
|  | 151 | + | 
|  | 152 | +void EVGAMouseController::SetLedAndActivate(uint8_t index, uint8_t brightness, uint8_t speed, const std::vector<RGBColor>& colors) | 
|  | 153 | +{ | 
|  | 154 | +    /*------------------------------------------------------------------------------------------------------------------------------*\ | 
|  | 155 | +    | Activating some modes requires two identical packets: one for setting the color, and one for setting the color AND activating. | | 
|  | 156 | +    \*------------------------------------------------------------------------------------------------------------------------------*/ | 
|  | 157 | +    SetLed(index, brightness, speed, colors, false); | 
|  | 158 | +    SetLed(index, brightness, speed, colors, true); | 
|  | 159 | +} | 
|  | 160 | + | 
|  | 161 | +void EVGAMouseController::SetAllLeds(uint8_t brightness, uint8_t speed, const std::vector<RGBColor>& colors) | 
|  | 162 | +{ | 
|  | 163 | +    for(unsigned int i = 0; i < EVGA_PERIPHERAL_LED_COUNT; i++) | 
|  | 164 | +    { | 
|  | 165 | +        SetLed(i, brightness, speed, colors); | 
|  | 166 | +    } | 
|  | 167 | +} | 
|  | 168 | + | 
|  | 169 | +void EVGAMouseController::SetAllLedsAndActivate(uint8_t brightness, uint8_t speed, const std::vector<RGBColor>& colors) | 
|  | 170 | +{ | 
|  | 171 | +    for(unsigned int i = 0; i < EVGA_PERIPHERAL_LED_COUNT; i++) | 
|  | 172 | +    { | 
|  | 173 | +        SetLedAndActivate(i, brightness, speed, colors); | 
|  | 174 | +    } | 
|  | 175 | +} | 
|  | 176 | + | 
|  | 177 | +void EVGAMouseController::SetLed(uint8_t index, uint8_t brightness, uint8_t speed, const std::vector<RGBColor>& colors, bool activate) | 
|  | 178 | +{ | 
|  | 179 | +    unsigned char buffer[EVGA_PERIPHERAL_PACKET_SIZE] = | 
|  | 180 | +    { | 
|  | 181 | +        0x00,               /* report id - must be 0x00 according to hid_send_feature_report */ | 
|  | 182 | +        0x00, 0x00, static_cast<unsigned char>(connection_type), 0x1D, | 
|  | 183 | +        0x02, 0x00, 0x02    /* header bits - always the same */ | 
|  | 184 | +    }; | 
|  | 185 | + | 
|  | 186 | +    /*---------------------------------------------------------------------------------------------------------------*\ | 
|  | 187 | +    | Setting the mode to breathing sends 3 packets: first to activate the mode, second to set the list of colors and | | 
|  | 188 | +    | third to send a packet identical to the second but with the first byte set ot 0xA1. This "activates" the mode.  | | 
|  | 189 | +    \*---------------------------------------------------------------------------------------------------------------*/ | 
|  | 190 | +    if(activate) | 
|  | 191 | +    { | 
|  | 192 | +        buffer[1] = 0xA1; | 
|  | 193 | +    } | 
|  | 194 | + | 
|  | 195 | +    buffer[EVGA_PERIPHERAL_LED_INDEX_BYTE]  = index; | 
|  | 196 | +    /*-----------------------------------------------------------------------------------------*\ | 
|  | 197 | +    | Unleash RGB supports individual modes on the LEDs, but OpenRGB does not. Use one specific | | 
|  | 198 | +    | LED's mode for any LED.                                                                   | | 
|  | 199 | +    \*-----------------------------------------------------------------------------------------*/ | 
|  | 200 | +    buffer[EVGA_PERIPHERAL_MODE_BYTE]       = led_states[EVGA_PERIPHERAL_LED_SOURCE_OF_TRUTH].mode; | 
|  | 201 | +    buffer[EVGA_PERIPHERAL_BRIGHTNESS_BYTE] = brightness; | 
|  | 202 | +    buffer[EVGA_PERIPHERAL_SPEED_BYTE]      = speed; | 
|  | 203 | + | 
|  | 204 | +    /*-----------------------------------------------------------------------*\ | 
|  | 205 | +    | 7 is the maximum number of colors that can be set from the vendor's UI. | | 
|  | 206 | +    \*-----------------------------------------------------------------------*/ | 
|  | 207 | +    unsigned char color_count                   = std::min(colors.size(), static_cast<std::vector<RGBColor>::size_type>(7)); | 
|  | 208 | +    buffer[EVGA_PERIPHERAL_COLOR_COUNT_BYTE]    = color_count; | 
|  | 209 | +    for(unsigned char i = 0; i < color_count; i++) | 
|  | 210 | +    { | 
|  | 211 | +        buffer[15 + i * 3] = RGBGetRValue(colors[i]); | 
|  | 212 | +        buffer[16 + i * 3] = RGBGetGValue(colors[i]); | 
|  | 213 | +        buffer[17 + i * 3] = RGBGetBValue(colors[i]); | 
|  | 214 | +    } | 
|  | 215 | +    int err = hid_send_feature_report(dev, buffer, EVGA_PERIPHERAL_PACKET_SIZE); | 
|  | 216 | +    if(err == -1) | 
|  | 217 | +    { | 
|  | 218 | +        const wchar_t* err_str = hid_error(dev); | 
|  | 219 | +        LOG_DEBUG("[%s] Error writing buffer %s", device_name.c_str(), err_str); | 
|  | 220 | +    } | 
|  | 221 | +    led_states[index].brightness    = brightness; | 
|  | 222 | +    led_states[index].speed         = speed; | 
|  | 223 | +    led_states[index].colors        = colors; | 
|  | 224 | +    /*------------------------------------------------------------------------------------*\ | 
|  | 225 | +    | If the device returns a response not ready packet, future writes will silently fail. | | 
|  | 226 | +    | Wait until the device sends a valid packet to proceed.                               | | 
|  | 227 | +    \*------------------------------------------------------------------------------------*/ | 
|  | 228 | +    ReadPacketOrLogErrors(buffer, EVGA_PERIPHERAL_MAX_ATTEMPTS); | 
|  | 229 | +} | 
|  | 230 | + | 
|  | 231 | +void EVGAMouseController::RefreshDeviceState() | 
|  | 232 | +{ | 
|  | 233 | +    RefreshDeviceState(EVGA_PERIPHERAL_LED_FRONT); | 
|  | 234 | +    RefreshDeviceState(EVGA_PERIPHERAL_LED_WHEEL); | 
|  | 235 | +    RefreshDeviceState(EVGA_PERIPHERAL_LED_LOGO); | 
|  | 236 | +} | 
|  | 237 | + | 
|  | 238 | +void EVGAMouseController::RefreshDeviceState(int led) | 
|  | 239 | +{ | 
|  | 240 | +    unsigned char buffer[EVGA_PERIPHERAL_PACKET_SIZE] = | 
|  | 241 | +    { | 
|  | 242 | +        0x00, | 
|  | 243 | +        0x00, 0x00, static_cast<unsigned char>(connection_type), 0x1D, | 
|  | 244 | +        0x02, 0x80, 0x02 | 
|  | 245 | +    }; | 
|  | 246 | +    buffer[EVGA_PERIPHERAL_LED_INDEX_BYTE]  = static_cast<unsigned char>(led); | 
|  | 247 | +    int err                                 = hid_send_feature_report(dev, buffer, EVGA_PERIPHERAL_PACKET_SIZE); | 
|  | 248 | +    if(err == -1) | 
|  | 249 | +    { | 
|  | 250 | +        const wchar_t* err_str = hid_error(dev); | 
|  | 251 | +        LOG_DEBUG("[%s] Error writing buffer %s", device_name.c_str(), err_str); | 
|  | 252 | +    } | 
|  | 253 | +    /*------------------------------------------------------------------------------*\ | 
|  | 254 | +    | Wait in wireless mode or else packets might be sent too quickly to take effect | | 
|  | 255 | +    \*------------------------------------------------------------------------------*/ | 
|  | 256 | +    Wait(); | 
|  | 257 | +    if(ReadPacketOrLogErrors(buffer, EVGA_PERIPHERAL_MAX_ATTEMPTS)) | 
|  | 258 | +    { | 
|  | 259 | +        int color_count = buffer[EVGA_PERIPHERAL_COLOR_COUNT_BYTE]; | 
|  | 260 | +        if(color_count == 0) | 
|  | 261 | +        { | 
|  | 262 | +            LOG_VERBOSE("[%s] No colors read from response. The device is likely asleep.", device_name.c_str()); | 
|  | 263 | +            return; | 
|  | 264 | +        } | 
|  | 265 | +        led_states[led].mode        = buffer[EVGA_PERIPHERAL_MODE_BYTE]; | 
|  | 266 | +        led_states[led].brightness  = buffer[EVGA_PERIPHERAL_BRIGHTNESS_BYTE]; | 
|  | 267 | +        led_states[led].speed       = buffer[EVGA_PERIPHERAL_SPEED_BYTE]; | 
|  | 268 | +        led_states[led].colors.resize(std::max(color_count, 1)); | 
|  | 269 | +        for(int i = 0; i < color_count; i++) | 
|  | 270 | +        { | 
|  | 271 | +            uint8_t r                   = buffer[EVGA_PERIPHERAL_RED_BYTE + i * 3]; | 
|  | 272 | +            uint8_t g                   = buffer[EVGA_PERIPHERAL_GREEN_BYTE + i * 3]; | 
|  | 273 | +            uint8_t b                   = buffer[EVGA_PERIPHERAL_BLUE_BYTE + i * 3]; | 
|  | 274 | +            led_states[led].colors[i]   = ToRGBColor(r, g, b); | 
|  | 275 | +        } | 
|  | 276 | +    } | 
|  | 277 | +} | 
|  | 278 | + | 
|  | 279 | +bool EVGAMouseController::ReadPacketOrLogErrors(unsigned char *buffer, int max_attempts) | 
|  | 280 | +{ | 
|  | 281 | +    int bytes_read = ReadPacketOrWait(buffer, max_attempts); | 
|  | 282 | +    if(bytes_read == -1) | 
|  | 283 | +    { | 
|  | 284 | +        const wchar_t* err_str = hid_error(dev); | 
|  | 285 | +        LOG_DEBUG("[%s] Error reading buffer %s", device_name.c_str(), err_str); | 
|  | 286 | +        return false; | 
|  | 287 | +    } | 
|  | 288 | +    else if(IsResponseNotReadyPacket(buffer)) | 
|  | 289 | +    { | 
|  | 290 | +        LOG_VERBOSE("[%s] Retries exhausted reading from device. Write may have failed.", device_name.c_str()); | 
|  | 291 | +        return false; | 
|  | 292 | +    } | 
|  | 293 | +    else if(IsAsleepPacket(buffer)) | 
|  | 294 | +    { | 
|  | 295 | +        LOG_VERBOSE("[%s] Device is asleep. Cannot send or receive packets until the device is awoken.", device_name.c_str()); | 
|  | 296 | +        return false; | 
|  | 297 | +    } | 
|  | 298 | +    return true; | 
|  | 299 | +} | 
|  | 300 | + | 
|  | 301 | +int EVGAMouseController::ReadPacketOrWait(unsigned char *buffer, int max_attempts) | 
|  | 302 | +{ | 
|  | 303 | +    int attempts    = 1; | 
|  | 304 | +    Wait(); | 
|  | 305 | +    int bytes_read  = hid_get_feature_report(dev, buffer, EVGA_PERIPHERAL_PACKET_SIZE); | 
|  | 306 | +    while(bytes_read == EVGA_PERIPHERAL_PACKET_SIZE && attempts < max_attempts && IsResponseNotReadyPacket(buffer)) | 
|  | 307 | +    { | 
|  | 308 | +        Wait(); | 
|  | 309 | +        bytes_read = hid_get_feature_report(dev, buffer, EVGA_PERIPHERAL_PACKET_SIZE); | 
|  | 310 | +        attempts++; | 
|  | 311 | +    } | 
|  | 312 | +    return bytes_read; | 
|  | 313 | +} | 
|  | 314 | + | 
|  | 315 | +void EVGAMouseController::Wait() | 
|  | 316 | +{ | 
|  | 317 | +    if(connection_type == EVGA_PERIPHERAL_CONNECTION_TYPE_WIRELESS) | 
|  | 318 | +    { | 
|  | 319 | +        std::this_thread::sleep_for(EVGA_PERIPHERAL_PACKET_DELAY); | 
|  | 320 | +    } | 
|  | 321 | +} | 
|  | 322 | + | 
|  | 323 | +bool EVGAMouseController::IsAsleepPacket(unsigned char *buffer) | 
|  | 324 | +{ | 
|  | 325 | +    const int expected_packet_size = 8; | 
|  | 326 | +    unsigned char expected_buffer[expected_packet_size] = | 
|  | 327 | +    { | 
|  | 328 | +        0x00, | 
|  | 329 | +        0xA4, 0x00, 0x02, 0x1D, | 
|  | 330 | +        0x02, 0x80, 0x02 | 
|  | 331 | +    }; | 
|  | 332 | +    return BuffersAreEqual(buffer, expected_buffer, expected_packet_size); | 
|  | 333 | +} | 
|  | 334 | + | 
|  | 335 | +bool EVGAMouseController::IsResponseNotReadyPacket(unsigned char *buffer) | 
|  | 336 | +{ | 
|  | 337 | +    const int expected_packet_size = 8; | 
|  | 338 | +    unsigned char expected_buffer[expected_packet_size] = | 
|  | 339 | +    { | 
|  | 340 | +        0x00, | 
|  | 341 | +        0xA0, 0x00, 0x02, 0x1D, | 
|  | 342 | +        0x02, 0x80, 0x02 | 
|  | 343 | +    }; | 
|  | 344 | +    return BuffersAreEqual(buffer, expected_buffer, expected_packet_size); | 
|  | 345 | +} | 
|  | 346 | + | 
0 commit comments