Push layered Qt host source files
This commit is contained in:
49
MID/Mid_Ble.cpp
Normal file
49
MID/Mid_Ble.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "MID/Mid_Ble.h"
|
||||
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
QString Mid_GetBleOpcodeText(quint8 Opcode)
|
||||
{
|
||||
return QStringLiteral("Opcode 0x%1")
|
||||
.arg(Opcode, 2, 16, QLatin1Char('0'))
|
||||
.toUpper();
|
||||
}
|
||||
|
||||
QString Mid_NormalizeBleAddress(const QString& Text)
|
||||
{
|
||||
QString HexText;
|
||||
HexText.reserve(Text.size());
|
||||
|
||||
for (const QChar Character : Text.trimmed())
|
||||
{
|
||||
if (Character.isDigit() ||
|
||||
((Character >= QLatin1Char('a')) && (Character <= QLatin1Char('f'))) ||
|
||||
((Character >= QLatin1Char('A')) && (Character <= QLatin1Char('F'))))
|
||||
{
|
||||
HexText.append(Character.toUpper());
|
||||
}
|
||||
}
|
||||
|
||||
return HexText.size() == 12 ? HexText : QString();
|
||||
}
|
||||
|
||||
QString Mid_FormatBleAddress(const QString& Address)
|
||||
{
|
||||
const QString Normalized = Mid_NormalizeBleAddress(Address);
|
||||
if (Normalized.isEmpty())
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
QStringList PartList;
|
||||
for (int Index = 0; Index < Normalized.size(); Index += 2)
|
||||
{
|
||||
PartList.append(Normalized.mid(Index, 2));
|
||||
}
|
||||
return PartList.join(QLatin1Char(':'));
|
||||
}
|
||||
|
||||
bool Mid_IsBleAddressValid(const QString& Address)
|
||||
{
|
||||
return !Mid_NormalizeBleAddress(Address).isEmpty();
|
||||
}
|
||||
9
MID/Mid_Ble.h
Normal file
9
MID/Mid_Ble.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QString>
|
||||
|
||||
// BLE raw packet opcode text helpers.
|
||||
QString Mid_GetBleOpcodeText(quint8 Opcode);
|
||||
QString Mid_NormalizeBleAddress(const QString& Text);
|
||||
QString Mid_FormatBleAddress(const QString& Address);
|
||||
bool Mid_IsBleAddressValid(const QString& Address);
|
||||
168
MID/Mid_Def.cpp
168
MID/Mid_Def.cpp
@@ -1,5 +1,7 @@
|
||||
#include "MID/Mid_Def.h"
|
||||
#include "MID/Mid_Def.h"
|
||||
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QVector>
|
||||
#include <Windows.h>
|
||||
#include <SetupAPI.h>
|
||||
#include <hidsdi.h>
|
||||
@@ -8,62 +10,47 @@
|
||||
#pragma comment(lib, "hid.lib")
|
||||
#pragma comment(lib, "setupapi.lib")
|
||||
|
||||
/*
|
||||
* MID 层:介于逻辑与 DRI 之间,负责描述 HID 设备匹配与数据格式。
|
||||
* 高密注释用于教学,逐条解释如何把固件接口映射为 Win32 API 调用。
|
||||
*/
|
||||
|
||||
/* 构造 NKRO 接口的匹配条件(Usage Page / Usage 固定) */
|
||||
Mid_Struct_DeviceMatch Mid_Func_GetNkroMatch(const Mid_Struct_DeviceConfig& DeviceConfig)
|
||||
namespace
|
||||
{
|
||||
Mid_Struct_DeviceMatch Match;
|
||||
Match.VendorId = DeviceConfig.VendorId;
|
||||
Match.ProductId = DeviceConfig.ProductId;
|
||||
Match.UsagePage = MID_CONST_USAGE_PAGE_NKRO;
|
||||
Match.Usage = MID_CONST_USAGE_NKRO;
|
||||
Match.Name = QStringLiteral("NKRO Keyboard Interface");
|
||||
return Match;
|
||||
|
||||
QString Mid_GetDeviceInstanceId(HDEVINFO DeviceInfoSet, SP_DEVINFO_DATA* p_DeviceInfoData)
|
||||
{
|
||||
DWORD NeedLength = 0;
|
||||
SetupDiGetDeviceInstanceIdW(
|
||||
DeviceInfoSet,
|
||||
p_DeviceInfoData,
|
||||
nullptr,
|
||||
0,
|
||||
&NeedLength);
|
||||
if (NeedLength == 0)
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
QVector<wchar_t> Buffer(static_cast<int>(NeedLength) + 1, 0);
|
||||
return SetupDiGetDeviceInstanceIdW(
|
||||
DeviceInfoSet,
|
||||
p_DeviceInfoData,
|
||||
Buffer.data(),
|
||||
static_cast<DWORD>(Buffer.size()),
|
||||
nullptr)
|
||||
? QString::fromWCharArray(Buffer.constData()).trimmed()
|
||||
: QString();
|
||||
}
|
||||
|
||||
/* 构造 Consumer 接口的匹配条件 */
|
||||
Mid_Struct_DeviceMatch Mid_Func_GetConsumerMatch(const Mid_Struct_DeviceConfig& DeviceConfig)
|
||||
{
|
||||
Mid_Struct_DeviceMatch Match;
|
||||
Match.VendorId = DeviceConfig.VendorId;
|
||||
Match.ProductId = DeviceConfig.ProductId;
|
||||
Match.UsagePage = MID_CONST_USAGE_PAGE_CONSUMER;
|
||||
Match.Usage = MID_CONST_USAGE_CONSUMER;
|
||||
Match.Name = QStringLiteral("Consumer Interface");
|
||||
return Match;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/* 构造 Vendor(状态镜像)接口的匹配条件 */
|
||||
Mid_Struct_DeviceMatch Mid_Func_GetVendorMatch(const Mid_Struct_DeviceConfig& DeviceConfig)
|
||||
{
|
||||
Mid_Struct_DeviceMatch Match;
|
||||
Match.VendorId = DeviceConfig.VendorId;
|
||||
Match.ProductId = DeviceConfig.ProductId;
|
||||
Match.UsagePage = MID_CONST_USAGE_PAGE_VENDOR;
|
||||
Match.Usage = MID_CONST_USAGE_VENDOR;
|
||||
Match.Name = QStringLiteral("Vendor State Mirror Interface");
|
||||
return Match;
|
||||
}
|
||||
|
||||
/*
|
||||
* Mid_Func_FindHidInterface:按照匹配条件扫描系统中的 HID 接口。
|
||||
* 步骤:1) 获取 HID GUID 2) SetupAPI 列举接口 3) CreateFile 查询属性 4) 命中后返回路径和报文长度。
|
||||
*/
|
||||
bool Mid_Func_FindHidInterface(
|
||||
// Find the HID interface that matches VID/PID plus usage page/usage.
|
||||
bool Mid_FindHidInterface(
|
||||
const Mid_Struct_DeviceMatch& Match,
|
||||
QString* p_DevicePath,
|
||||
quint16* p_InputLength,
|
||||
quint16* p_OutputLength)
|
||||
quint16* p_OutputLength,
|
||||
QString* p_DeviceInstanceId)
|
||||
{
|
||||
/* Win32 提供的 HID GUID:列举所有 HID 接口都靠它 */
|
||||
GUID HidGuid;
|
||||
HidD_GetHidGuid(&HidGuid);
|
||||
|
||||
/* 构建“当前存在 + 暴露接口”的设备集合 */
|
||||
HDEVINFO DeviceInfoSet = SetupDiGetClassDevsW(&HidGuid, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||
if (DeviceInfoSet == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
@@ -78,25 +65,36 @@ bool Mid_Func_FindHidInterface(
|
||||
SetupDiEnumDeviceInterfaces(DeviceInfoSet, nullptr, &HidGuid, Index, &InterfaceData);
|
||||
++Index)
|
||||
{
|
||||
/* Query 设备路径前先得到所需缓冲区长度 */
|
||||
DWORD NeedLength = 0;
|
||||
SetupDiGetDeviceInterfaceDetailW(DeviceInfoSet, &InterfaceData, nullptr, 0, &NeedLength, nullptr);
|
||||
SP_DEVINFO_DATA DeviceInfoData = {};
|
||||
DeviceInfoData.cbSize = sizeof(DeviceInfoData);
|
||||
SetupDiGetDeviceInterfaceDetailW(
|
||||
DeviceInfoSet,
|
||||
&InterfaceData,
|
||||
nullptr,
|
||||
0,
|
||||
&NeedLength,
|
||||
&DeviceInfoData);
|
||||
if (NeedLength == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 申请一段缓冲区存储 SP_DEVICE_INTERFACE_DETAIL_DATA_W */
|
||||
QByteArray Buffer(static_cast<int>(NeedLength), 0);
|
||||
auto* p_Detail = reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_W*>(Buffer.data());
|
||||
p_Detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
|
||||
|
||||
if (!SetupDiGetDeviceInterfaceDetailW(DeviceInfoSet, &InterfaceData, p_Detail, NeedLength, nullptr, nullptr))
|
||||
if (!SetupDiGetDeviceInterfaceDetailW(
|
||||
DeviceInfoSet,
|
||||
&InterfaceData,
|
||||
p_Detail,
|
||||
NeedLength,
|
||||
nullptr,
|
||||
&DeviceInfoData))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 只需 0 访问权限即可读取属性 */
|
||||
HANDLE HandleQuery = CreateFileW(
|
||||
p_Detail->DevicePath,
|
||||
0,
|
||||
@@ -114,15 +112,14 @@ bool Mid_Func_FindHidInterface(
|
||||
Attributes.Size = sizeof(Attributes);
|
||||
PHIDP_PREPARSED_DATA p_Preparsed = nullptr;
|
||||
HIDP_CAPS Caps = {};
|
||||
/* 通过 Attributes + Caps 对比 VID / PID / Usage Page / Usage */
|
||||
const bool IsMatch =
|
||||
const bool IsExactMatch =
|
||||
HidD_GetAttributes(HandleQuery, &Attributes) &&
|
||||
HidD_GetPreparsedData(HandleQuery, &p_Preparsed) &&
|
||||
(HidP_GetCaps(p_Preparsed, &Caps) == HIDP_STATUS_SUCCESS) &&
|
||||
(Attributes.VendorID == Match.VendorId) &&
|
||||
(Attributes.ProductID == Match.ProductId) &&
|
||||
(Caps.UsagePage == Match.UsagePage) &&
|
||||
(Caps.Usage == Match.Usage);
|
||||
(Caps.Usage == Match.Usage) &&
|
||||
(Attributes.VendorID == Match.VendorId) &&
|
||||
(Attributes.ProductID == Match.ProductId);
|
||||
|
||||
if (p_Preparsed != nullptr)
|
||||
{
|
||||
@@ -130,36 +127,43 @@ bool Mid_Func_FindHidInterface(
|
||||
}
|
||||
CloseHandle(HandleQuery);
|
||||
|
||||
if (!IsMatch)
|
||||
if (IsExactMatch)
|
||||
{
|
||||
continue;
|
||||
if (p_DevicePath != nullptr)
|
||||
{
|
||||
*p_DevicePath = QString::fromWCharArray(p_Detail->DevicePath);
|
||||
}
|
||||
if (p_InputLength != nullptr)
|
||||
{
|
||||
*p_InputLength = Caps.InputReportByteLength;
|
||||
}
|
||||
if (p_OutputLength != nullptr)
|
||||
{
|
||||
*p_OutputLength = Caps.OutputReportByteLength;
|
||||
}
|
||||
if (p_DeviceInstanceId != nullptr)
|
||||
{
|
||||
*p_DeviceInstanceId = Mid_GetDeviceInstanceId(DeviceInfoSet, &DeviceInfoData);
|
||||
}
|
||||
|
||||
IsFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* 命中:写回设备路径及报文长度 */
|
||||
if (p_DevicePath != nullptr)
|
||||
{
|
||||
*p_DevicePath = QString::fromWCharArray(p_Detail->DevicePath);
|
||||
}
|
||||
if (p_InputLength != nullptr)
|
||||
{
|
||||
*p_InputLength = Caps.InputReportByteLength;
|
||||
}
|
||||
if (p_OutputLength != nullptr)
|
||||
{
|
||||
*p_OutputLength = Caps.OutputReportByteLength;
|
||||
}
|
||||
|
||||
IsFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* 枚举结束释放句柄 */
|
||||
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
|
||||
return IsFound;
|
||||
}
|
||||
|
||||
/* 调试输出:把字节数组格式化成“AA BB CC” */
|
||||
QString Mid_Func_GetHexText(const QByteArray& ByteArray)
|
||||
bool Mid_IsBluetoothHidInstanceId(const QString& DeviceInstanceId)
|
||||
{
|
||||
const QString UpperId = DeviceInstanceId.trimmed().toUpper();
|
||||
return UpperId.contains(QStringLiteral("BTHLEDEVICE")) ||
|
||||
UpperId.contains(QStringLiteral("{00001812-0000-1000-8000-00805F9B34FB}"));
|
||||
}
|
||||
|
||||
QString Mid_GetHexText(const QByteArray& ByteArray)
|
||||
{
|
||||
QStringList TextList;
|
||||
for (int Index = 0; Index < ByteArray.size(); ++Index)
|
||||
@@ -171,8 +175,7 @@ QString Mid_Func_GetHexText(const QByteArray& ByteArray)
|
||||
return TextList.join(QLatin1Char(' '));
|
||||
}
|
||||
|
||||
/* 把 Modifier 位图翻译成人类可读的组合 */
|
||||
QString Mid_Func_GetModifierText(quint8 Modifier)
|
||||
QString Mid_GetModifierText(quint8 Modifier)
|
||||
{
|
||||
QStringList TextList;
|
||||
if (Modifier == 0)
|
||||
@@ -191,8 +194,7 @@ QString Mid_Func_GetModifierText(quint8 Modifier)
|
||||
return TextList.join(QStringLiteral(", "));
|
||||
}
|
||||
|
||||
/* 键盘 Usage -> 中文/英文标签,方便 UI 展示 */
|
||||
QString Mid_Func_GetKeyboardUsageText(quint16 Usage)
|
||||
QString Mid_GetKeyboardUsageText(quint16 Usage)
|
||||
{
|
||||
switch (Usage)
|
||||
{
|
||||
@@ -226,8 +228,7 @@ QString Mid_Func_GetKeyboardUsageText(quint16 Usage)
|
||||
}
|
||||
}
|
||||
|
||||
/* Consumer Usage -> UI 标签 */
|
||||
QString Mid_Func_GetConsumerUsageText(quint16 Usage)
|
||||
QString Mid_GetConsumerUsageText(quint16 Usage)
|
||||
{
|
||||
switch (Usage)
|
||||
{
|
||||
@@ -239,3 +240,4 @@ QString Mid_Func_GetConsumerUsageText(quint16 Usage)
|
||||
return QStringLiteral("未知 Consumer Usage");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,86 +1,81 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QVector>
|
||||
|
||||
/*
|
||||
* MID 层公共定义:设备配置、匹配条件、HID 报文常量等都集中于此。
|
||||
* 高密注释放在每个结构/函数附近,方便教学时直接引用。
|
||||
*/
|
||||
// Shared protocol constants and raw packet definitions.
|
||||
|
||||
/* HID 报文 ID:用于区分 NKRO、Consumer、Vendor */
|
||||
enum Mid_Enum_ReportId : quint8
|
||||
{
|
||||
Mid_Enum_ReportId_None = 0x00,
|
||||
Mid_Enum_ReportId_Nkro = 0x01,
|
||||
Mid_Enum_ReportId_Consumer = 0x03,
|
||||
Mid_Enum_ReportId_Vendor = 0x04
|
||||
Mid_Enum_ReportId_Vendor = 0x04,
|
||||
Mid_Enum_ReportId_VendorCommand = 0x05
|
||||
};
|
||||
|
||||
enum Mid_Enum_RawPacketSource : quint8
|
||||
{
|
||||
Mid_Enum_RawPacketSource_None = 0,
|
||||
Mid_Enum_RawPacketSource_UsbNkroRaw,
|
||||
Mid_Enum_RawPacketSource_UsbConsumerHid,
|
||||
Mid_Enum_RawPacketSource_UsbVendorHid,
|
||||
Mid_Enum_RawPacketSource_BleGatt,
|
||||
Mid_Enum_RawPacketSource_BleHidKeyboard,
|
||||
Mid_Enum_RawPacketSource_BleHidConsumer,
|
||||
Mid_Enum_RawPacketSource_BleHidVendor,
|
||||
Mid_Enum_RawPacketSource_BleHidVendorCommand
|
||||
};
|
||||
|
||||
/* 默认 VID/PID:教学用固件的 USB 身份 */
|
||||
const quint16 MID_CONST_VENDOR_ID_DEFAULT = 0x1209;
|
||||
const quint16 MID_CONST_PRODUCT_ID_DEFAULT = 0x0001;
|
||||
|
||||
/* 上位机配置:允许 UI 快速切换 VID/PID */
|
||||
struct Mid_Struct_DeviceConfig
|
||||
{
|
||||
quint16 VendorId = MID_CONST_VENDOR_ID_DEFAULT;
|
||||
quint16 ProductId = MID_CONST_PRODUCT_ID_DEFAULT;
|
||||
};
|
||||
|
||||
/* 匹配条件:Win32 SetupAPI + HidP_Caps 的输入结构 */
|
||||
struct Mid_Struct_DeviceMatch
|
||||
{
|
||||
quint16 VendorId = 0;
|
||||
quint16 ProductId = 0;
|
||||
quint16 UsagePage = 0;
|
||||
quint16 Usage = 0;
|
||||
QString Name;
|
||||
};
|
||||
|
||||
/* 统一的“原始包”封装:记录来源端口与字节内容 */
|
||||
struct Mid_Struct_RawPacket
|
||||
{
|
||||
bool IsValid = false;
|
||||
Mid_Enum_RawPacketSource Source = Mid_Enum_RawPacketSource_None;
|
||||
QByteArray ByteArray;
|
||||
QString PortName;
|
||||
};
|
||||
|
||||
/* Vendor 状态:逻辑层解析后的结果(Modifier + Usage 列表) */
|
||||
struct Mid_Struct_VendorState
|
||||
{
|
||||
bool IsValid = false;
|
||||
quint8 Modifier = 0;
|
||||
QByteArray UsageBitmap;
|
||||
QVector<quint16> UsageList;
|
||||
QStringList UsageTextList;
|
||||
};
|
||||
|
||||
const quint16 MID_CONST_USAGE_PAGE_NKRO = 0x0001;
|
||||
const quint16 MID_CONST_USAGE_NKRO = 0x0006;
|
||||
const quint16 MID_CONST_USAGE_PAGE_CONSUMER = 0x000C;
|
||||
const quint16 MID_CONST_USAGE_CONSUMER = 0x0001;
|
||||
const quint16 MID_CONST_USAGE_PAGE_VENDOR = 0xFF00;
|
||||
const quint16 MID_CONST_USAGE_VENDOR = 0x0002;
|
||||
const quint16 MID_CONST_USAGE_PAGE_VENDOR_COMMAND = 0xFF01;
|
||||
const quint16 MID_CONST_USAGE_VENDOR_COMMAND = 0x0005;
|
||||
const int MID_CONST_KEYBOARD_USAGE_MAX = 0x00E7;
|
||||
const int MID_CONST_USAGE_BITMAP_SIZE = 29;
|
||||
const int MID_CONST_PACKET_SIZE_NKRO = 31;
|
||||
const int MID_CONST_PACKET_SIZE_VENDOR = 31;
|
||||
const int MID_CONST_PACKET_SIZE_VENDOR_COMMAND = 10;
|
||||
const int MID_CONST_PACKET_SIZE_VENDOR_COMMAND_DATA = 8;
|
||||
const int MID_CONST_PACKET_SIZE_CONSUMER = 3;
|
||||
|
||||
/* 设备匹配函数:针对 NKRO / Consumer / Vendor */
|
||||
Mid_Struct_DeviceMatch Mid_Func_GetNkroMatch(const Mid_Struct_DeviceConfig& DeviceConfig);
|
||||
Mid_Struct_DeviceMatch Mid_Func_GetConsumerMatch(const Mid_Struct_DeviceConfig& DeviceConfig);
|
||||
Mid_Struct_DeviceMatch Mid_Func_GetVendorMatch(const Mid_Struct_DeviceConfig& DeviceConfig);
|
||||
/* HID 接口枚举 & 辅助文本转换 */
|
||||
bool Mid_Func_FindHidInterface(
|
||||
bool Mid_FindHidInterface(
|
||||
const Mid_Struct_DeviceMatch& Match,
|
||||
QString* p_DevicePath,
|
||||
quint16* p_InputLength,
|
||||
quint16* p_OutputLength);
|
||||
QString Mid_Func_GetHexText(const QByteArray& ByteArray);
|
||||
QString Mid_Func_GetModifierText(quint8 Modifier);
|
||||
QString Mid_Func_GetKeyboardUsageText(quint16 Usage);
|
||||
QString Mid_Func_GetConsumerUsageText(quint16 Usage);
|
||||
quint16* p_OutputLength,
|
||||
QString* p_DeviceInstanceId = nullptr);
|
||||
bool Mid_IsBluetoothHidInstanceId(const QString& DeviceInstanceId);
|
||||
QString Mid_GetHexText(const QByteArray& ByteArray);
|
||||
QString Mid_GetModifierText(quint8 Modifier);
|
||||
QString Mid_GetKeyboardUsageText(quint16 Usage);
|
||||
QString Mid_GetConsumerUsageText(quint16 Usage);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user