Skip to content

Commit 3f342ae

Browse files
authored
Merge pull request #21 from elazarit/main
Fix severe hidapi & GDI memory leaks, clean up C++/CLI disposal, Startup bug and add Cloud III & III S support
2 parents d63df48 + 9318c93 commit 3f342ae

6 files changed

Lines changed: 148 additions & 34 deletions

File tree

Cloud2BatteryMonitorUI/MainForm.cpp

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,51 +7,84 @@ void main()
77
{
88
Application::EnableVisualStyles();
99
Application::SetCompatibleTextRenderingDefault(false);
10-
Cloud2BatteryMonitorUI::MainForm form;
1110
Environment::CurrentDirectory = IO::Path::GetDirectoryName(Application::ExecutablePath);
11+
Cloud2BatteryMonitorUI::MainForm form;
1212
Application::Run(% form);
1313
}
1414

1515
/**
16-
* Enumerates HID devices and returns the HID device info with the highest usage
16+
* Enumerates HID devices and returns the HID device path according to logic
1717
*
18-
* @return hid_device_info* of the device with the correct vendor/product ID and the highest usage.
18+
* @return string of the device path with the correct vendor/product ID and the highest usage / specific condition (like in iii 3s).
1919
*/
20-
hid_device_info* getHeadsetDeviceInfo()
20+
std::string getHeadsetDevicePath()
2121
{
2222
struct hid_device_info* devices = NULL;
23-
2423
int id_index = 0;
2524
int num_supported_devices = sizeof(VENDORS_ARRAY) / sizeof(VENDORS_ARRAY[0]);
2625

2726
// Loop through the supported device vendor and product IDs until we find a device.
2827
while (devices == NULL && id_index < num_supported_devices) {
2928
devices = hid_enumerate((unsigned short)VENDORS_ARRAY[id_index], (unsigned short)PRODUCTS_ARRAY[id_index]);
30-
3129
id_index++;
3230
}
3331

34-
struct hid_device_info* deviceInfo{};
32+
if (devices == NULL) {
33+
return ""; // Nothing found
34+
}
35+
36+
std::string foundPath = "";
37+
38+
// Special case for hyperx cloud iii S
39+
// the 'highest usage' method used below is not applicable here
40+
for (struct hid_device_info* current = devices; current != nullptr && current->vendor_id == HEADSET_VENDOR_ID_HP && current->product_id == HEADSET_PRODUCT_ID_HP_CLOUD_III_S; current = current->next)
41+
{
42+
//looks like the correct condition is usage_page 448 and usage 1 combination
43+
if (current->usage_page == 448 && current->usage == 1)
44+
{
45+
foundPath = current->path; // Copy the path
46+
hid_free_enumeration(devices); // FREE THE MEMORY
47+
return foundPath; // Return early
48+
}
49+
}
50+
51+
// Special case for HyperX Cloud III Wireless
52+
// the 'highest usage' method used below is not applicable here either
53+
for (struct hid_device_info* current = devices; current != nullptr && current->vendor_id == HEADSET_VENDOR_ID_HP && (current->product_id == HEADSET_PRODUCT_ID_HP_CLOUD_III_REV4106 || current->product_id == HEADSET_PRODUCT_ID_HP_CLOUD_III_REV4109); current = current->next)
54+
{
55+
// From log: the correct interface is usage_page 65299 and usage 1
56+
if (current->usage_page == 65299 && current->usage == 1)
57+
{
58+
foundPath = current->path; // Copy the path
59+
hid_free_enumeration(devices); // FREE THE MEMORY
60+
return foundPath; // Return early
61+
}
62+
}
3563

3664
int highest_usage = 0; //I think the highest usage device is the one that answers with battery level
3765
int highest_usage_page = 0; //If all devices have the same usage, use the one with the highest usage page
38-
for (struct hid_device_info* current = devices; current != nullptr; current = current->next)
66+
67+
for (struct hid_device_info* current = devices; current != nullptr; current = current->next)
3968
{
4069
if (current->usage > highest_usage)
4170
{
4271
highest_usage = current->usage;
4372
highest_usage_page = current->usage_page;
44-
deviceInfo = current;
73+
foundPath = current->path; // Keep track of the path
4574
}
4675
else if (current->usage == highest_usage && current->usage_page >= highest_usage_page)
4776
{
4877
highest_usage_page = current->usage_page;
49-
deviceInfo = current;
78+
foundPath = current->path; // Keep track of the path
5079
}
5180
}
5281

53-
return deviceInfo;
82+
// FREE THE MEMORY before returning
83+
hid_free_enumeration(devices);
84+
85+
return foundPath;
5486
}
87+
5588
/**
5689
* Sends a request to the given HID device, waits for the response and retrieves the battery level from it.
5790
*
@@ -97,6 +130,24 @@ int getBatteryLevel(hid_device* headsetDevice)
97130

98131
batteryByteInt = 3;
99132
}
133+
else if (wcsstr(productName, L"Cloud III S Wireless") != 0) {
134+
// HP Cloud III S Wireless data
135+
writeBuffer[0] = 0x0c;
136+
writeBuffer[1] = 0x02;
137+
writeBuffer[2] = 0x03;
138+
writeBuffer[3] = 0x01;
139+
writeBuffer[4] = 0x00;
140+
writeBuffer[5] = 0x06;
141+
142+
batteryByteInt = 6;
143+
}
144+
else if (wcsstr(productName, L"Cloud III Wireless") != 0) {
145+
// HP Cloud III Wireless data
146+
writeBuffer[0] = 0x66;
147+
writeBuffer[1] = 0x89;
148+
149+
batteryByteInt = 4;
150+
}
100151
}
101152
else
102153
{
@@ -121,11 +172,16 @@ int getBatteryLevel(hid_device* headsetDevice)
121172
}
122173

123174
int ret = hid_write(headsetDevice, writeBuffer, WRITE_BUFFER_SIZE);
124-
175+
const wchar_t* err = hid_error(headsetDevice);
125176
unsigned char dataBuffer[DATA_BUFFER_SIZE] = { 0 };
126177

127178
hid_read_timeout(headsetDevice, dataBuffer, DATA_BUFFER_SIZE, 1000);
128179

129-
return dataBuffer[batteryByteInt];
180+
//verify the battery level is not more then 100, as Cloud III S returns 0xff (255) at off state
181+
unsigned char battery = dataBuffer[batteryByteInt];
182+
if (battery > 100)
183+
return 0;
184+
185+
return battery;
130186
}
131187

Cloud2BatteryMonitorUI/MainForm.h

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ constexpr auto HEADSET_PRODUCT_ID_HP_CLOUD_II_B = 1686;
1414
constexpr auto HEADSET_PRODUCT_ID_HP_CLOUD_II_CORE = 2453;
1515
constexpr auto HEADSET_PRODUCT_ID_HP_CLOUD_ALPHA = 2445;
1616
constexpr auto HEADSET_PRODUCT_ID_HP_CLOUD_STINGER_II = 3475;
17+
constexpr auto HEADSET_PRODUCT_ID_HP_CLOUD_III_S = 1726;
18+
constexpr auto HEADSET_PRODUCT_ID_HP_CLOUD_III_REV4106 = 3229;
19+
constexpr auto HEADSET_PRODUCT_ID_HP_CLOUD_III_REV4109 = 1463;
1720

18-
const int VENDORS_ARRAY[] = { HEADSET_VENDOR_ID_KINGSTON, HEADSET_VENDOR_ID_HP, HEADSET_VENDOR_ID_HP, HEADSET_VENDOR_ID_HP, HEADSET_VENDOR_ID_HP, HEADSET_VENDOR_ID_HP};
19-
const int PRODUCTS_ARRAY[] = { HEADSET_PRODUCT_ID_KINGSTON_CLOUD_II, HEADSET_PRODUCT_ID_HP_CLOUD_II, HEADSET_PRODUCT_ID_HP_CLOUD_II_CORE, HEADSET_PRODUCT_ID_HP_CLOUD_ALPHA, HEADSET_PRODUCT_ID_HP_CLOUD_II_B, HEADSET_PRODUCT_ID_HP_CLOUD_STINGER_II};
21+
const int VENDORS_ARRAY[] = { HEADSET_VENDOR_ID_KINGSTON, HEADSET_VENDOR_ID_HP, HEADSET_VENDOR_ID_HP, HEADSET_VENDOR_ID_HP, HEADSET_VENDOR_ID_HP, HEADSET_VENDOR_ID_HP, HEADSET_VENDOR_ID_HP, HEADSET_VENDOR_ID_HP, HEADSET_VENDOR_ID_HP};
22+
const int PRODUCTS_ARRAY[] = { HEADSET_PRODUCT_ID_KINGSTON_CLOUD_II, HEADSET_PRODUCT_ID_HP_CLOUD_II, HEADSET_PRODUCT_ID_HP_CLOUD_II_CORE, HEADSET_PRODUCT_ID_HP_CLOUD_ALPHA, HEADSET_PRODUCT_ID_HP_CLOUD_II_B, HEADSET_PRODUCT_ID_HP_CLOUD_STINGER_II, HEADSET_PRODUCT_ID_HP_CLOUD_III_S, HEADSET_PRODUCT_ID_HP_CLOUD_III_REV4106, HEADSET_PRODUCT_ID_HP_CLOUD_III_REV4109};
2023

21-
hid_device_info* getHeadsetDeviceInfo();
24+
std::string getHeadsetDevicePath();
2225
int getBatteryLevel(hid_device*);
2326

2427

@@ -38,12 +41,19 @@ namespace Cloud2BatteryMonitorUI {
3841
{
3942
public:
4043
System::Windows::Forms::Timer^ timerRefresh = gcnew System::Windows::Forms::Timer();
44+
45+
//a variable to hold offline icon permanently
46+
System::Drawing::Icon^ defaultOfflineIcon;
47+
4148
MainForm(void)
4249
{
4350
InitializeComponent();
4451
//
4552
//TODO: Add the constructor code here
4653
//
54+
55+
//load the offline icon exactly once into memory
56+
defaultOfflineIcon = System::Drawing::Icon::ExtractAssociatedIcon("icons\\headset_icon_light.ico");
4757
}
4858

4959
protected:
@@ -56,7 +66,16 @@ namespace Cloud2BatteryMonitorUI {
5666
{
5767
delete components;
5868
}
69+
70+
//dispose of the cached offline icon when the app closes
71+
delete defaultOfflineIcon;
5972
}
73+
74+
//for cleaning the icon leak
75+
private:
76+
[System::Runtime::InteropServices::DllImportAttribute("user32.dll")]
77+
static bool DestroyIcon(System::IntPtr handle);
78+
6079
private: System::Windows::Forms::Label^ lbStatus;
6180
protected:
6281
private: System::Windows::Forms::Label^ lbHeadsetInfoManu;
@@ -298,13 +317,12 @@ namespace Cloud2BatteryMonitorUI {
298317

299318
private: System::Void MainForm_Load(System::Object^ sender, System::EventArgs^ e)
300319
{
301-
struct hid_device_info* cloud2DeviceInfo = getHeadsetDeviceInfo();
302-
303-
320+
//gets the path as a string instead of hid_device_info* which caused memory leak every cycle
321+
std::string devicePath = getHeadsetDevicePath();
304322

305-
if (cloud2DeviceInfo != NULL)
323+
if (!devicePath.empty())
306324
{
307-
hid_device* headsetDevice = hid_open_path(cloud2DeviceInfo->path);
325+
hid_device* headsetDevice = hid_open_path(devicePath.c_str());
308326

309327
if (headsetDevice != NULL)
310328
{
@@ -347,21 +365,25 @@ namespace Cloud2BatteryMonitorUI {
347365
System::String^ batStr2 = gcnew System::String(to_string(batteryLevel).c_str());
348366
System::String^ prodStr = gcnew System::String(productName);
349367
RefreshTrayIcon(batStr2, prodStr);
368+
369+
//close the connection only if it was opened.
370+
hid_close(headsetDevice);
371+
350372
}
351373
else
352374
{
353375
this->iconSystemTray->Text = NO_DEVICE_STRING;
354-
this->iconSystemTray->Icon = System::Drawing::Icon::ExtractAssociatedIcon("icons\\headset_icon_light.ico");
376+
//use loaded offline icon
377+
this->iconSystemTray->Icon = defaultOfflineIcon;
355378
this->lbStatus->Text = "Could not connect to headset.";
356379
this->btnRefresh->Visible = true;
357380
}
358-
359-
hid_close(headsetDevice);
360381
}
361382
else
362383
{
363384
this->iconSystemTray->Text = NO_DEVICE_STRING;
364-
this->iconSystemTray->Icon = System::Drawing::Icon::ExtractAssociatedIcon("icons\\headset_icon_light.ico");
385+
//use loaded offline icon
386+
this->iconSystemTray->Icon = defaultOfflineIcon;
365387
this->lbStatus->Text = "No headset device detected.";
366388
this->btnRefresh->Visible = true;
367389
}
@@ -419,27 +441,38 @@ namespace Cloud2BatteryMonitorUI {
419441

420442
if (battLvl > 0 && battLvl <= 100)
421443
{
444+
//a place to store the unmanaged handle
445+
System::IntPtr hIcon;
446+
422447
if (settingsHelper->getBatIcon())
423448
{
424-
this->iconSystemTray->Icon = System::Drawing::Icon::FromHandle(RefreshTrayBattery(battLvl, settingsHelper));
449+
//catch the handle from the Battery icon generator
450+
hIcon = RefreshTrayBattery(battLvl, settingsHelper);
425451
}
426452
else
427453
{
428-
this->iconSystemTray->Icon = System::Drawing::Icon::FromHandle(RefreshTrayNumber(batteryLevel, settingsHelper));
454+
//catch the handle from the Number icon generator
455+
hIcon = RefreshTrayNumber(batteryLevel, settingsHelper);
429456
}
430457

458+
//apply icon
459+
this->iconSystemTray->Icon = System::Drawing::Icon::FromHandle(hIcon);
460+
461+
//destroy the unmanaged handle!
462+
DestroyIcon(hIcon);
463+
431464
trayText += BATTERY_LEVEL_STRING + batteryLevel + "%";
432465
this->iconSystemTray->Text = trayText;
433466
}
434467
else
435468
{
436469
trayText += BATTERY_LEVEL_STRING + "N/A";
437470

438-
this->iconSystemTray->Icon = System::Drawing::Icon::ExtractAssociatedIcon("icons\\headset_icon_light.ico");
471+
//use loaded offline icon
472+
this->iconSystemTray->Icon = defaultOfflineIcon;
439473
this->iconSystemTray->Text = trayText;
440474
}
441475

442-
delete trayText;
443476
delete settingsHelper;
444477
}
445478

@@ -489,9 +522,11 @@ namespace Cloud2BatteryMonitorUI {
489522
delete iconBrush;
490523
delete iconFont;
491524
delete iconGraphics;
492-
delete strSize;
493525

494-
return textBitmap->GetHicon();
526+
System::IntPtr hIcon = textBitmap->GetHicon(); // Extract the handle first
527+
delete textBitmap; //dispose of the managed bitmap
528+
529+
return hIcon; //return the extracted handle
495530
}
496531

497532
private: System::IntPtr RefreshTrayBattery(int battLvl, SettingsHelper* settingsHelper)
@@ -534,7 +569,10 @@ namespace Cloud2BatteryMonitorUI {
534569
delete batGraphics;
535570
delete batPen;
536571

537-
return batBitmap->GetHicon();
572+
System::IntPtr hIcon = batBitmap->GetHicon(); // Extract the handle first
573+
delete batBitmap; // Dispose of the managed bitmap
574+
575+
return hIcon; // Return the extracted handle
538576
}
539577

540578
private: System::Void BtnRefresh_Click(System::Object^ sender, System::EventArgs^ e)

Cloud2BatteryMonitorUI/SettingsHelper.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ void SettingsHelper::saveSettings()
3232
LONG key = RegOpenKeyExA(HKEY_CURRENT_USER, REG_KEY_PATH, 0, KEY_WRITE, &hkey);
3333
if (ERROR_SUCCESS == key)
3434
{
35-
key = RegSetValueExA(hkey, APP_NAME, 0, REG_SZ, (BYTE*)FP.c_str(), strlen(FP.c_str()));
35+
key = RegSetValueExA(hkey, APP_NAME, 0, REG_SZ, (BYTE*)FP.c_str(), strlen(FP.c_str())+1);
3636
}
3737
RegCloseKey(hkey);
3838
}
44.4 KB
Binary file not shown.

Cloud2BatteryMonitorUI/resource.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//{{NO_DEPENDENCIES}}
2+
// Microsoft Visual C++ generated include file.
3+
// Used by Cloud2BatteryMonitorUI1.rc
4+
5+
// Next default values for new objects
6+
//
7+
#ifdef APSTUDIO_INVOKED
8+
#ifndef APSTUDIO_READONLY_SYMBOLS
9+
#define _APS_NEXT_RESOURCE_VALUE 101
10+
#define _APS_NEXT_COMMAND_VALUE 40001
11+
#define _APS_NEXT_CONTROL_VALUE 1001
12+
#define _APS_NEXT_SYMED_VALUE 101
13+
#endif
14+
#endif

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ The aim of the project is simple, to create a somewhat customizable battery moni
1515

1616
**HP HyperX Cloud Stinger 2 Wireless**
1717

18+
**HP HyperX Cloud III Wireless**
19+
20+
**HP HyperX Cloud III S Wireless**
21+
1822
If you would like a different Cloud Wireless headset supported, feel free to create an issue here on Github but beware that I will ask you for assistance (eg. Wireshark USB traffic recordings).
1923

2024
## Requirements
@@ -23,7 +27,7 @@ If you would like a different Cloud Wireless headset supported, feel free to cre
2327

2428
## How to install
2529

26-
You can download [release v1.8 here](https://github.com/auto94/HyperX-Cloud-2-Battery-Monitor/releases/download/Release_v1.8/Cloud2BatteryMonitor_v1.8.zip) or select the latest release on the right side of this Github repository and download the zip. After the download is complete, extract the zip and run "Cloud2BatteryMonitorUI.exe".
30+
You can download [release v2.1 here](https://github.com/elazarit/HyperX-Cloud-2-Battery-Monitor/releases/download/v2.1/HyperX-Cloud-2-Battery-Monitor-2.1.zip) or select the latest release on the right side of this Github repository and download the zip. After the download is complete, extract the zip and run "Cloud2BatteryMonitorUI.exe".
2731

2832
No installation is necessary.
2933

@@ -89,6 +93,8 @@ A: Basically I used wireshark to find the data and responses which had the batte
8993
| HP | HyperX Cloud II Core Wireless | v1.4+ | Windows 10
9094
| HP | HyperX Cloud Alpha Wireless | v1.5+ | Unknown
9195
| HP | HyperX Cloud Stinger 2 Wireless | v1.8+ | Unknown
96+
| HP | HyperX Cloud III S Wireless | v2.0+ | Unknown
97+
| HP | HyperX Cloud III Wireless | v2.02+ | Unknown
9298

9399

94100
## Special thanks

0 commit comments

Comments
 (0)