[next] Add function to retrieve ALL USB-attached COM ports

This commit is contained in:
Dmitry K
2026-07-07 20:31:25 +04:00
committed by Adam Honse
parent 73cd84f43c
commit 0f971b0cb9
7 changed files with 367 additions and 0 deletions

View File

@@ -390,6 +390,18 @@ std::vector<std::string> ResourceManager::GetSerialPorts()
}
}
std::vector<SerialDeviceInfo> ResourceManager::GetUSBSerialPorts()
{
if(IsLocalClient())
{
return(find_usb_serial_ports());
}
else
{
return(find_usb_serial_ports());
}
}
std::vector<USBDeviceInfo> ResourceManager::GetUSBDeviceInfo()
{
if(IsLocalClient())

View File

@@ -25,6 +25,7 @@
#include "i2c_smbus.h"
#include "ResourceManagerCallback.h"
#include "filesystem.h"
#include "find_usb_serial_port.h"
using json = nlohmann::json;
@@ -82,6 +83,7 @@ public:
std::vector<i2c_smbus_interface*>& GetI2CBuses();
std::vector<i2c_smbus_info> GetI2CBusInfo();
std::vector<std::string> GetSerialPorts();
std::vector<SerialDeviceInfo> GetUSBSerialPorts();
std::vector<USBDeviceInfo> GetUSBDeviceInfo();
PluginManagerInterface* GetPluginManager();
ProfileManager* GetProfileManager();

View File

@@ -90,9 +90,26 @@ int OpenRGBHardwareIDsDialog::show()
strings.push_back(line);
}
std::vector<SerialDeviceInfo> serial_device_info = ResourceManager::get()->GetUSBSerialPorts();
QTreeWidgetItem* serial_top = new QTreeWidgetItem(ui->HardwareIdsList, {"USB Serial devices"});
strings.push_back("\n[ USB Serial devices ]");
for(SerialDeviceInfo device_info : serial_device_info)
{
char line[550];
snprintf(line, 550, "[%04X:%04X]", device_info.vendor_id, device_info.product_id);
new QTreeWidgetItem(serial_top, {line, QString::fromStdString(device_info.port_path), QString::fromStdString(device_info.usb_path)});
snprintf(line, 550, "[%04X:%04X] %s (%s)", device_info.vendor_id, device_info.product_id, device_info.port_path.c_str(), device_info.usb_path.c_str());
strings.push_back(line);
}
i2c_top->setExpanded(true);
hid_top->setExpanded(true);
usb_top->setExpanded(true);
serial_top->setExpanded(true);
return this->exec();
}

View File

@@ -19,3 +19,13 @@
#include <vector>
std::vector<std::string> find_usb_serial_port(unsigned short vid, unsigned short pid);
struct SerialDeviceInfo
{
unsigned short vendor_id;
unsigned short product_id;
std::string port_path;
std::string usb_path;
} ;
std::vector<SerialDeviceInfo> find_usb_serial_ports();

View File

@@ -10,6 +10,7 @@
#include "find_usb_serial_port.h"
#include <sstream>
#include <unistd.h>
#include <dirent.h>
@@ -144,3 +145,117 @@ std::vector<std::string> find_usb_serial_port(unsigned short vid, unsigned short
return ret_vector;
} /* find_usb_serial_port() */
std::vector<SerialDeviceInfo> find_usb_serial_ports()
{
std::vector<SerialDeviceInfo> ret_vector;
DIR* dir;
char symlink_path[1024] = {0};
struct dirent* ent;
/*-----------------------------------------------------------------*\
| Open /sys/class/tty |
\*-----------------------------------------------------------------*/
dir = opendir("/sys/class/tty");
if(dir == NULL)
{
return ret_vector;
}
/*-----------------------------------------------------------------*\
| Loop through all symlinks in /sys/class/tty directory to find |
| paths with "usb" in them. These links should have the USB device |
| index which can be used to find the VID/PID |
\*-----------------------------------------------------------------*/
ent = readdir(dir);
while(ent != NULL)
{
if(ent->d_type == DT_LNK)
{
char tty_path[1024];
strcpy(tty_path, "/sys/class/tty/");
strcat(tty_path, ent->d_name);
/*-----------------------------------------------------------------*\
| readlink() does not null-terminate, so manually terminate it |
\*-----------------------------------------------------------------*/
ssize_t link_path_size = readlink(tty_path, symlink_path, 1024);
if(link_path_size < 0 || link_path_size >= 1024)
{
/*-----------------------------------------------------------------*\
| readlink failed or buffer too small, skip this device |
\*-----------------------------------------------------------------*/
ent = readdir(dir);
continue;
}
symlink_path[link_path_size] = '\0';
char * usb_string = strstr(symlink_path, "usb");
if(usb_string != NULL)
{
char * usb_dev = strstr(usb_string, "/");
usb_dev++;
char * usb_end = strstr(usb_dev, "/tty");
*usb_end = '\0';
usb_end = strrchr(usb_dev, '/');
*usb_end = '\0';
char usb_path[1024];
strcpy(usb_path, "/sys/bus/usb/devices/");
strcat(usb_path, usb_dev);
char vendor_path[1024];
char product_path[1024];
strcpy(vendor_path, usb_path);
strcat(vendor_path, "/idVendor");
strcpy(product_path, usb_path);
strcat(product_path, "/idProduct");
std::ifstream vendor_file;
std::ifstream product_file;
std::string vendor_string;
std::string product_string;
vendor_file.open(vendor_path);
product_file.open(product_path);
std::getline(vendor_file, vendor_string);
std::getline(product_file, product_string);
char* port_string = NULL;
for(int i = strlen(tty_path); i > 0; i--)
{
if(tty_path[i] == '/')
{
port_string = &tty_path[i + 1];
break;
}
}
SerialDeviceInfo entry;
entry.port_path = "/dev/";
entry.port_path.append(port_string);
entry.usb_path = usb_path;
entry.vendor_id = strtol(vendor_string.c_str(), NULL, 16);
entry.product_id = strtol(product_string.c_str(), NULL, 16);
ret_vector.push_back(entry);
}
}
ent = readdir(dir);
}
closedir(dir);
return ret_vector;
} /* find_usb_serial_ports() */

View File

@@ -169,3 +169,116 @@ std::vector<std::string> find_usb_serial_port(unsigned short vid, unsigned short
\*-----------------------------------------------------*/
return(ret_vector);
}
std::vector<SerialDeviceInfo> find_usb_serial_ports()
{
/*-----------------------------------------------------*\
| Return variables |
\*-----------------------------------------------------*/
std::vector<SerialDeviceInfo> ret_vector;
/*-----------------------------------------------------*\
| Execute command to list USB devices |
| |
| Top level entry lines in ioreg output start with |
| "+-o". Start the string with an extra newline so |
| that we can search for "\n+-0" to identify only hits |
| at the beginning of a line. |
\*-----------------------------------------------------*/
std::string out_string = "\n" + exec("ioreg -r -c IOUSBHostDevice -l");
/*-----------------------------------------------------*\
| Start position counter at 0 |
\*-----------------------------------------------------*/
std::size_t pos = 0;
/*-----------------------------------------------------*\
| Loop through ioreg output, loop exits when "\n+-o" |
| string cannot be found. |
\*-----------------------------------------------------*/
while(1)
{
/*-------------------------------------------------*\
| Variables to store positions in string |
\*-------------------------------------------------*/
std::size_t next_pos;
std::size_t vid_pos;
std::size_t pid_pos;
/*-------------------------------------------------*\
| Search for the next 2 iterations of "\n+-o" so |
| that we can check if hits are in between them |
\*-------------------------------------------------*/
pos = out_string.find("\n+-o", pos);
next_pos = out_string.find("\n+-o", pos + 1);
/*-------------------------------------------------*\
| Search for the vendor and product ID strings in |
| and verify that they are between pos and next_pos |
\*-------------------------------------------------*/
vid_pos = out_string.find(ID_VENDOR_STR, pos);
pid_pos = out_string.find(ID_PRODUCT_STR, pos);
/*-------------------------------------------------*\
| Verify that VID/PID matches are within this |
| device block by checking that their positions are |
| less than next_pos. If next_pos is invalid, |
| this is the last block, in which case check if |
| VID/PID positions are valid. |
\*-------------------------------------------------*/
if(((vid_pos < next_pos) && (pid_pos < next_pos)) || ((pos == std::string::npos) && (vid_pos != std::string::npos) && (pid_pos != std::string::npos)))
{
/*---------------------------------------------*\
| Variables to store positions in string |
\*---------------------------------------------*/
std::size_t dev_pos;
std::size_t start_pos;
std::size_t end_pos;
/*---------------------------------------------*\
| Look for the IO callout device tag and then |
| get the start and end positions of its value |
\*---------------------------------------------*/
dev_pos = out_string.find(IO_CALLOUT_STR, pos + 1);
start_pos = out_string.find("\"", dev_pos + sizeof(IO_CALLOUT_STR)) + 1;
end_pos = out_string.find("\"\n", start_pos);
/*---------------------------------------------*\
| Ensure the IO callout device tag is within |
| this device's section |
\*---------------------------------------------*/
if(dev_pos < next_pos)
{
SerialDeviceInfo entry;
entry.port_path = out_string.substr(start_pos, end_pos-start_pos);
std::string vendor_string = out_string.substr(vid_pos + strlen(ID_VENDOR_STR), 6);
std::string product_string = out_string.substr(vid_pos + strlen(ID_PRODUCT_STR), 6);
/*---------------------------------------------*\
| This data SEEMS to be 10-based |
\*---------------------------------------------*/
entry.vendor_id = strtol(vendor_string.c_str(), NULL, 10);
entry.product_id = strtol(product_string.c_str(), NULL, 10);
}
}
/*-------------------------------------------------*\
| If we've reached the end of the string, break out |
| of the loop |
\*-------------------------------------------------*/
if(pos == std::string::npos)
{
break;
}
/*-------------------------------------------------*\
| Increment position |
\*-------------------------------------------------*/
pos++;
}
/*-----------------------------------------------------*\
| Return vector of detected strings |
\*-----------------------------------------------------*/
return(ret_vector);
}

View File

@@ -131,3 +131,101 @@ std::vector<std::string> find_usb_serial_port(unsigned short vid, unsigned short
return ret_vector;
} /* find_usb_serial_port() */
std::vector<SerialDeviceInfo> find_usb_serial_ports()
{
std::vector<SerialDeviceInfo> ret_vector;
HDEVINFO DeviceInfoSet;
DWORD DeviceIndex = 0;
SP_DEVINFO_DATA DeviceInfoData;
const char * DevEnum = "USB";
char szBuffer[1024] = {0};
DEVPROPTYPE ulPropertyType;
DWORD dwSize = 0;
/*-----------------------------------------------------------------*\
| SetupDiGetClassDevs returns a handle to a device information set |
\*-----------------------------------------------------------------*/
DeviceInfoSet = SetupDiGetClassDevs( NULL, DevEnum, NULL, DIGCF_ALLCLASSES | DIGCF_PRESENT);
if (DeviceInfoSet == INVALID_HANDLE_VALUE)
{
return ret_vector;
}
/*-----------------------------------------------------------------*\
| Set up Device Info Data |
\*-----------------------------------------------------------------*/
memset(&DeviceInfoData, 0, sizeof(SP_DEVINFO_DATA));
DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
/*-----------------------------------------------------------------*\
| Receive information about an enumerated device |
\*-----------------------------------------------------------------*/
while (SetupDiEnumDeviceInfo( DeviceInfoSet, DeviceIndex, &DeviceInfoData))
{
DeviceIndex++;
/*-------------------------------------------------------------*\
| Retrieves a specified Plug and Play device property |
\*-------------------------------------------------------------*/
if (SetupDiGetDeviceRegistryProperty (DeviceInfoSet, &DeviceInfoData, SPDRP_HARDWAREID, &ulPropertyType, (BYTE*)szBuffer, sizeof(szBuffer), &dwSize))
{
HKEY hDeviceRegistryKey;
/*-----------------------------------------------------*\
| Check if the string for this device property matches |
| our expected device string (i.e. starts with USB ID) |
\*-----------------------------------------------------*/
if(strncmp(szBuffer, "USB\\VID_", 9) == 0)
{
hDeviceRegistryKey = SetupDiOpenDevRegKey(DeviceInfoSet, &DeviceInfoData,DICS_FLAG_GLOBAL, 0,DIREG_DEV, KEY_READ);
if (hDeviceRegistryKey == INVALID_HANDLE_VALUE)
{
break;
}
else
{
char pszPortName[BUFF_LEN];
DWORD dwSize = sizeof(pszPortName);
DWORD dwType = 0;
/*-----------------------------------------------------*\
| Read in the name of the port |
\*-----------------------------------------------------*/
if( (RegQueryValueEx(hDeviceRegistryKey,"PortName", NULL, &dwType, (LPBYTE) pszPortName, &dwSize) == ERROR_SUCCESS) && (dwType == REG_SZ))
{
if(strncmp(pszPortName, "COM", 3) == 0)
{
char vid_str[5] = {0};
char pid_str[5] = {0};
strncpy(vid_str, szBuffer + 9, 4);
strncpy(pid_str, szBuffer + 18, 4);
SerialDeviceInfo entry;
entry.port_path = pszPortName;
entry.usb_path = szBuffer;
entry.vendor_id = (unsigned short)strtol(vid_str, NULL, 16);
entry.product_id = (unsigned short)strtol(pid_str, NULL, 16);
ret_vector.push_back(entry);
}
}
// Close the key now that we are finished with it
RegCloseKey(hDeviceRegistryKey);
}
}
}
}
if (DeviceInfoSet)
{
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
}
return ret_vector;
} /* find_usb_serial_ports() */