blob: 1bab3e9cd6c7062be115f0125b59c44d735a0ebf [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2011-2012 Denis Shienkov <denis.shienkov@gmail.com>
** Copyright (C) 2011 Sergey Belyashov <Sergey.Belyashov@gmail.com>
** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org>
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtSerialPort module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qserialportinfo.h"
#include "qserialportinfo_p.h"
#include "qserialport_p.h"
#include <QtCore/quuid.h>
#include <QtCore/qpair.h>
#include <QtCore/qstringlist.h>
#include <vector>
#include <initguid.h>
#include <devguid.h> // for GUID_DEVCLASS_PORTS and GUID_DEVCLASS_MODEM
#include <winioctl.h> // for GUID_DEVINTERFACE_COMPORT
#include <setupapi.h>
#include <cfgmgr32.h>
#ifdef QT_NO_REDEFINE_GUID_DEVINTERFACE_MODEM
# include <ntddmodm.h> // for GUID_DEVINTERFACE_MODEM
#else
DEFINE_GUID(GUID_DEVINTERFACE_MODEM, 0x2c7089aa, 0x2e0e, 0x11d1, 0xb1, 0x14, 0x00, 0xc0, 0x4f, 0xc2, 0xaa, 0xe4);
#endif
QT_BEGIN_NAMESPACE
static QStringList portNamesFromHardwareDeviceMap()
{
HKEY hKey = nullptr;
if (::RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM", 0, KEY_QUERY_VALUE, &hKey) != ERROR_SUCCESS)
return QStringList();
QStringList result;
DWORD index = 0;
// This is a maximum length of value name, see:
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724872%28v=vs.85%29.aspx
enum { MaximumValueNameInChars = 16383 };
std::vector<wchar_t> outputValueName(MaximumValueNameInChars, 0);
std::vector<wchar_t> outputBuffer(MAX_PATH + 1, 0);
DWORD bytesRequired = MAX_PATH;
for (;;) {
DWORD requiredValueNameChars = MaximumValueNameInChars;
const LONG ret = ::RegEnumValue(hKey, index, &outputValueName[0], &requiredValueNameChars,
nullptr, nullptr, reinterpret_cast<PBYTE>(&outputBuffer[0]), &bytesRequired);
if (ret == ERROR_MORE_DATA) {
outputBuffer.resize(bytesRequired / sizeof(wchar_t) + 2, 0);
} else if (ret == ERROR_SUCCESS) {
result.append(QString::fromWCharArray(&outputBuffer[0]));
++index;
} else {
break;
}
}
::RegCloseKey(hKey);
return result;
}
static QString deviceRegistryProperty(HDEVINFO deviceInfoSet,
PSP_DEVINFO_DATA deviceInfoData,
DWORD property)
{
DWORD dataType = 0;
std::vector<wchar_t> outputBuffer(MAX_PATH + 1, 0);
DWORD bytesRequired = MAX_PATH;
for (;;) {
if (::SetupDiGetDeviceRegistryProperty(deviceInfoSet, deviceInfoData, property, &dataType,
reinterpret_cast<PBYTE>(&outputBuffer[0]),
bytesRequired, &bytesRequired)) {
break;
}
if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER
|| (dataType != REG_SZ && dataType != REG_EXPAND_SZ)) {
return QString();
}
outputBuffer.resize(bytesRequired / sizeof(wchar_t) + 2, 0);
}
return QString::fromWCharArray(&outputBuffer[0]);
}
static QString deviceInstanceIdentifier(DEVINST deviceInstanceNumber)
{
std::vector<wchar_t> outputBuffer(MAX_DEVICE_ID_LEN + 1, 0);
if (::CM_Get_Device_ID(
deviceInstanceNumber,
&outputBuffer[0],
MAX_DEVICE_ID_LEN,
0) != CR_SUCCESS) {
return QString();
}
return QString::fromWCharArray(&outputBuffer[0]).toUpper();
}
static DEVINST parentDeviceInstanceNumber(DEVINST childDeviceInstanceNumber)
{
ULONG nodeStatus = 0;
ULONG problemNumber = 0;
if (::CM_Get_DevNode_Status(&nodeStatus, &problemNumber,
childDeviceInstanceNumber, 0) != CR_SUCCESS) {
return 0;
}
DEVINST parentInstanceNumber = 0;
if (::CM_Get_Parent(&parentInstanceNumber, childDeviceInstanceNumber, 0) != CR_SUCCESS)
return 0;
return parentInstanceNumber;
}
static QString devicePortName(HDEVINFO deviceInfoSet, PSP_DEVINFO_DATA deviceInfoData)
{
const HKEY key = ::SetupDiOpenDevRegKey(deviceInfoSet, deviceInfoData, DICS_FLAG_GLOBAL,
0, DIREG_DEV, KEY_READ);
if (key == INVALID_HANDLE_VALUE)
return QString();
static const wchar_t * const keyTokens[] = {
L"PortName\0",
L"PortNumber\0"
};
enum { KeyTokensCount = sizeof(keyTokens) / sizeof(keyTokens[0]) };
QString portName;
for (int i = 0; i < KeyTokensCount; ++i) {
DWORD dataType = 0;
std::vector<wchar_t> outputBuffer(MAX_PATH + 1, 0);
DWORD bytesRequired = MAX_PATH;
for (;;) {
const LONG ret = ::RegQueryValueEx(key, keyTokens[i], nullptr, &dataType,
reinterpret_cast<PBYTE>(&outputBuffer[0]), &bytesRequired);
if (ret == ERROR_MORE_DATA) {
outputBuffer.resize(bytesRequired / sizeof(wchar_t) + 2, 0);
continue;
} else if (ret == ERROR_SUCCESS) {
if (dataType == REG_SZ)
portName = QString::fromWCharArray(&outputBuffer[0]);
else if (dataType == REG_DWORD)
portName = QStringLiteral("COM%1").arg(*(PDWORD(&outputBuffer[0])));
}
break;
}
if (!portName.isEmpty())
break;
}
::RegCloseKey(key);
return portName;
}
static QString deviceDescription(HDEVINFO deviceInfoSet,
PSP_DEVINFO_DATA deviceInfoData)
{
return deviceRegistryProperty(deviceInfoSet, deviceInfoData, SPDRP_DEVICEDESC);
}
static QString deviceManufacturer(HDEVINFO deviceInfoSet,
PSP_DEVINFO_DATA deviceInfoData)
{
return deviceRegistryProperty(deviceInfoSet, deviceInfoData, SPDRP_MFG);
}
static quint16 parseDeviceIdentifier(const QString &instanceIdentifier,
const QString &identifierPrefix,
int identifierSize, bool &ok)
{
const int index = instanceIdentifier.indexOf(identifierPrefix);
if (index == -1)
return quint16(0);
return instanceIdentifier.mid(index + identifierPrefix.size(), identifierSize).toInt(&ok, 16);
}
static quint16 deviceVendorIdentifier(const QString &instanceIdentifier, bool &ok)
{
static const int vendorIdentifierSize = 4;
quint16 result = parseDeviceIdentifier(
instanceIdentifier, QStringLiteral("VID_"), vendorIdentifierSize, ok);
if (!ok)
result = parseDeviceIdentifier(
instanceIdentifier, QStringLiteral("VEN_"), vendorIdentifierSize, ok);
return result;
}
static quint16 deviceProductIdentifier(const QString &instanceIdentifier, bool &ok)
{
static const int productIdentifierSize = 4;
quint16 result = parseDeviceIdentifier(
instanceIdentifier, QStringLiteral("PID_"), productIdentifierSize, ok);
if (!ok)
result = parseDeviceIdentifier(
instanceIdentifier, QStringLiteral("DEV_"), productIdentifierSize, ok);
return result;
}
static QString parseDeviceSerialNumber(const QString &instanceIdentifier)
{
int firstbound = instanceIdentifier.lastIndexOf(QLatin1Char('\\'));
int lastbound = instanceIdentifier.indexOf(QLatin1Char('_'), firstbound);
if (instanceIdentifier.startsWith(QLatin1String("USB\\"))) {
if (lastbound != instanceIdentifier.size() - 3)
lastbound = instanceIdentifier.size();
int ampersand = instanceIdentifier.indexOf(QLatin1Char('&'), firstbound);
if (ampersand != -1 && ampersand < lastbound)
return QString();
} else if (instanceIdentifier.startsWith(QLatin1String("FTDIBUS\\"))) {
firstbound = instanceIdentifier.lastIndexOf(QLatin1Char('+'));
lastbound = instanceIdentifier.indexOf(QLatin1Char('\\'), firstbound);
if (lastbound == -1)
return QString();
} else {
return QString();
}
return instanceIdentifier.mid(firstbound + 1, lastbound - firstbound - 1);
}
static QString deviceSerialNumber(QString instanceIdentifier,
DEVINST deviceInstanceNumber)
{
for (;;) {
const QString result = parseDeviceSerialNumber(instanceIdentifier);
if (!result.isEmpty())
return result;
deviceInstanceNumber = parentDeviceInstanceNumber(deviceInstanceNumber);
if (deviceInstanceNumber == 0)
break;
instanceIdentifier = deviceInstanceIdentifier(deviceInstanceNumber);
if (instanceIdentifier.isEmpty())
break;
}
return QString();
}
static bool anyOfPorts(const QList<QSerialPortInfo> &ports, const QString &portName)
{
const auto end = ports.end();
auto isPortNamesEquals = [&portName](const QSerialPortInfo &portInfo) {
return portInfo.portName() == portName;
};
return std::find_if(ports.begin(), end, isPortNamesEquals) != end;
}
QList<QSerialPortInfo> QSerialPortInfo::availablePorts()
{
static const struct {
GUID guid; DWORD flags;
} setupTokens[] = {
{ GUID_DEVCLASS_PORTS, DIGCF_PRESENT },
{ GUID_DEVCLASS_MODEM, DIGCF_PRESENT },
{ GUID_DEVINTERFACE_COMPORT, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE },
{ GUID_DEVINTERFACE_MODEM, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE }
};
enum { SetupTokensCount = sizeof(setupTokens) / sizeof(setupTokens[0]) };
QList<QSerialPortInfo> serialPortInfoList;
for (int i = 0; i < SetupTokensCount; ++i) {
const HDEVINFO deviceInfoSet = ::SetupDiGetClassDevs(&setupTokens[i].guid, nullptr, nullptr, setupTokens[i].flags);
if (deviceInfoSet == INVALID_HANDLE_VALUE)
return serialPortInfoList;
SP_DEVINFO_DATA deviceInfoData;
::memset(&deviceInfoData, 0, sizeof(deviceInfoData));
deviceInfoData.cbSize = sizeof(deviceInfoData);
DWORD index = 0;
while (::SetupDiEnumDeviceInfo(deviceInfoSet, index++, &deviceInfoData)) {
const QString portName = devicePortName(deviceInfoSet, &deviceInfoData);
if (portName.isEmpty() || portName.contains(QLatin1String("LPT")))
continue;
if (anyOfPorts(serialPortInfoList, portName))
continue;
QSerialPortInfoPrivate priv;
priv.portName = portName;
priv.device = QSerialPortInfoPrivate::portNameToSystemLocation(portName);
priv.description = deviceDescription(deviceInfoSet, &deviceInfoData);
priv.manufacturer = deviceManufacturer(deviceInfoSet, &deviceInfoData);
const QString instanceIdentifier = deviceInstanceIdentifier(deviceInfoData.DevInst);
priv.serialNumber =
deviceSerialNumber(instanceIdentifier, deviceInfoData.DevInst);
priv.vendorIdentifier =
deviceVendorIdentifier(instanceIdentifier, priv.hasVendorIdentifier);
priv.productIdentifier =
deviceProductIdentifier(instanceIdentifier, priv.hasProductIdentifier);
serialPortInfoList.append(priv);
}
::SetupDiDestroyDeviceInfoList(deviceInfoSet);
}
const auto portNames = portNamesFromHardwareDeviceMap();
for (const QString &portName : portNames) {
if (!anyOfPorts(serialPortInfoList, portName)) {
QSerialPortInfoPrivate priv;
priv.portName = portName;
priv.device = QSerialPortInfoPrivate::portNameToSystemLocation(portName);
serialPortInfoList.append(priv);
}
}
return serialPortInfoList;
}
#if QT_DEPRECATED_SINCE(5, 6)
bool QSerialPortInfo::isBusy() const
{
const HANDLE handle = ::CreateFile(reinterpret_cast<const wchar_t*>(systemLocation().utf16()),
GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
if (handle == INVALID_HANDLE_VALUE) {
if (::GetLastError() == ERROR_ACCESS_DENIED)
return true;
} else {
::CloseHandle(handle);
}
return false;
}
#endif // QT_DEPRECATED_SINCE(5, 6)
#if QT_DEPRECATED_SINCE(5, 2)
bool QSerialPortInfo::isValid() const
{
const HANDLE handle = ::CreateFile(reinterpret_cast<const wchar_t*>(systemLocation().utf16()),
GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
if (handle == INVALID_HANDLE_VALUE) {
if (::GetLastError() != ERROR_ACCESS_DENIED)
return false;
} else {
::CloseHandle(handle);
}
return true;
}
#endif // QT_DEPRECATED_SINCE(5, 2)
QString QSerialPortInfoPrivate::portNameToSystemLocation(const QString &source)
{
return source.startsWith(QLatin1String("COM"))
? (QLatin1String("\\\\.\\") + source) : source;
}
QString QSerialPortInfoPrivate::portNameFromSystemLocation(const QString &source)
{
return (source.startsWith(QLatin1String("\\\\.\\"))
|| source.startsWith(QLatin1String("//./")))
? source.mid(4) : source;
}
QT_END_NAMESPACE