添加项目文件。

This commit is contained in:
2026-03-26 10:45:29 +08:00
parent 10441f488c
commit b576d3f19d
48 changed files with 4812 additions and 0 deletions

241
MID/Mid_Def.cpp Normal file
View File

@@ -0,0 +1,241 @@
#include "MID/Mid_Def.h"
#include <Windows.h>
#include <SetupAPI.h>
#include <hidsdi.h>
#include <hidpi.h>
#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)
{
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;
}
/* 构造 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;
}
/* 构造 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(
const Mid_Struct_DeviceMatch& Match,
QString* p_DevicePath,
quint16* p_InputLength,
quint16* p_OutputLength)
{
/* Win32 提供的 HID GUID列举所有 HID 接口都靠它 */
GUID HidGuid;
HidD_GetHidGuid(&HidGuid);
/* 构建“当前存在 + 暴露接口”的设备集合 */
HDEVINFO DeviceInfoSet = SetupDiGetClassDevsW(&HidGuid, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (DeviceInfoSet == INVALID_HANDLE_VALUE)
{
return false;
}
bool IsFound = false;
SP_DEVICE_INTERFACE_DATA InterfaceData = {};
InterfaceData.cbSize = sizeof(InterfaceData);
for (DWORD Index = 0;
SetupDiEnumDeviceInterfaces(DeviceInfoSet, nullptr, &HidGuid, Index, &InterfaceData);
++Index)
{
/* Query 设备路径前先得到所需缓冲区长度 */
DWORD NeedLength = 0;
SetupDiGetDeviceInterfaceDetailW(DeviceInfoSet, &InterfaceData, nullptr, 0, &NeedLength, nullptr);
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))
{
continue;
}
/* 只需 0 访问权限即可读取属性 */
HANDLE HandleQuery = CreateFileW(
p_Detail->DevicePath,
0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr,
OPEN_EXISTING,
0,
nullptr);
if (HandleQuery == INVALID_HANDLE_VALUE)
{
continue;
}
HIDD_ATTRIBUTES Attributes = {};
Attributes.Size = sizeof(Attributes);
PHIDP_PREPARSED_DATA p_Preparsed = nullptr;
HIDP_CAPS Caps = {};
/* 通过 Attributes + Caps 对比 VID / PID / Usage Page / Usage */
const bool IsMatch =
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);
if (p_Preparsed != nullptr)
{
HidD_FreePreparsedData(p_Preparsed);
}
CloseHandle(HandleQuery);
if (!IsMatch)
{
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;
}
IsFound = true;
break;
}
/* 枚举结束释放句柄 */
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
return IsFound;
}
/* 调试输出把字节数组格式化成“AA BB CC” */
QString Mid_Func_GetHexText(const QByteArray& ByteArray)
{
QStringList TextList;
for (int Index = 0; Index < ByteArray.size(); ++Index)
{
TextList.append(QStringLiteral("%1")
.arg(static_cast<quint8>(ByteArray.at(Index)), 2, 16, QLatin1Char('0'))
.toUpper());
}
return TextList.join(QLatin1Char(' '));
}
/* 把 Modifier 位图翻译成人类可读的组合 */
QString Mid_Func_GetModifierText(quint8 Modifier)
{
QStringList TextList;
if (Modifier == 0)
{
return QStringLiteral("");
}
if ((Modifier & 0x01) != 0) TextList.append(QStringLiteral("Left Ctrl"));
if ((Modifier & 0x02) != 0) TextList.append(QStringLiteral("Left Shift"));
if ((Modifier & 0x04) != 0) TextList.append(QStringLiteral("Left Alt"));
if ((Modifier & 0x08) != 0) TextList.append(QStringLiteral("Left GUI"));
if ((Modifier & 0x10) != 0) TextList.append(QStringLiteral("Right Ctrl"));
if ((Modifier & 0x20) != 0) TextList.append(QStringLiteral("Right Shift"));
if ((Modifier & 0x40) != 0) TextList.append(QStringLiteral("Right Alt"));
if ((Modifier & 0x80) != 0) TextList.append(QStringLiteral("Right GUI"));
return TextList.join(QStringLiteral(", "));
}
/* 键盘 Usage -> 中文/英文标签,方便 UI 展示 */
QString Mid_Func_GetKeyboardUsageText(quint16 Usage)
{
switch (Usage)
{
case 0x0053: return QStringLiteral("Num Lock");
case 0x0054: return QStringLiteral("小键盘 /");
case 0x0055: return QStringLiteral("小键盘 *");
case 0x0056: return QStringLiteral("小键盘 -");
case 0x0057: return QStringLiteral("小键盘 +");
case 0x0058: return QStringLiteral("小键盘 Enter");
case 0x0059: return QStringLiteral("小键盘 1");
case 0x005A: return QStringLiteral("小键盘 2");
case 0x005B: return QStringLiteral("小键盘 3");
case 0x005C: return QStringLiteral("小键盘 4");
case 0x005D: return QStringLiteral("小键盘 5");
case 0x005E: return QStringLiteral("小键盘 6");
case 0x005F: return QStringLiteral("小键盘 7");
case 0x0060: return QStringLiteral("小键盘 8");
case 0x0061: return QStringLiteral("小键盘 9");
case 0x0062: return QStringLiteral("小键盘 0");
case 0x0063: return QStringLiteral("小键盘 .");
case 0x00E0: return QStringLiteral("Left Ctrl");
case 0x00E1: return QStringLiteral("Left Shift");
case 0x00E2: return QStringLiteral("Left Alt");
case 0x00E3: return QStringLiteral("Left GUI");
case 0x00E4: return QStringLiteral("Right Ctrl");
case 0x00E5: return QStringLiteral("Right Shift");
case 0x00E6: return QStringLiteral("Right Alt");
case 0x00E7: return QStringLiteral("Right GUI");
default:
return QStringLiteral("未知 HID Usage");
}
}
/* Consumer Usage -> UI 标签 */
QString Mid_Func_GetConsumerUsageText(quint16 Usage)
{
switch (Usage)
{
case 0x0000: return QStringLiteral("释放");
case 0x00E2: return QStringLiteral("Mute");
case 0x00E9: return QStringLiteral("Volume Up");
case 0x00EA: return QStringLiteral("Volume Down");
default:
return QStringLiteral("未知 Consumer Usage");
}
}

86
MID/Mid_Def.h Normal file
View File

@@ -0,0 +1,86 @@
#pragma once
#include <QtCore/QByteArray>
#include <QtCore/QString>
#include <QtCore/QStringList>
#include <QtCore/QVector>
/*
* MID 层公共定义设备配置、匹配条件、HID 报文常量等都集中于此。
* 高密注释放在每个结构/函数附近,方便教学时直接引用。
*/
/* 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
};
/* 默认 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;
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 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_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(
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);