Skip to content

Commit 3fabbec

Browse files
CooperCoronaCalcProgrammer1
authored andcommitted
Add Support for EVGA X20 Gaming Mouse
Commit amended to remove udev rules (which is now autogenerated) by Adam Honse <[email protected]>
1 parent 3aa5e26 commit 3fabbec

File tree

6 files changed

+895
-7
lines changed

6 files changed

+895
-7
lines changed
Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
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

Comments
 (0)