添加项目文件。
This commit is contained in:
168
DRI/Dri_Consumer.cpp
Normal file
168
DRI/Dri_Consumer.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
#include "DRI/Dri_Consumer.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool Dri_Consumer_Func_BeginRead(Dri_Consumer_Struct_Port* p_Port, QString* p_TextStatus)
|
||||
{
|
||||
if (!p_Port->IsOpened || (p_Port->InputLength == 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_Port->IsReadPending)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ResetEvent(p_Port->HandleEvent);
|
||||
p_Port->ReadBuffer.fill(0, p_Port->InputLength);
|
||||
p_Port->OverlappedRead = {};
|
||||
p_Port->OverlappedRead.hEvent = p_Port->HandleEvent;
|
||||
|
||||
DWORD BytesRead = 0;
|
||||
if (ReadFile(
|
||||
p_Port->HandleRead,
|
||||
p_Port->ReadBuffer.data(),
|
||||
p_Port->InputLength,
|
||||
&BytesRead,
|
||||
&p_Port->OverlappedRead) ||
|
||||
(GetLastError() == ERROR_IO_PENDING))
|
||||
{
|
||||
p_Port->IsReadPending = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("Consumer 接口启动异步读取失败:%1").arg(GetLastError());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Dri_Consumer_Func_Close(Dri_Consumer_Struct_Port* p_Port)
|
||||
{
|
||||
if ((p_Port->HandleRead != INVALID_HANDLE_VALUE) && p_Port->IsReadPending)
|
||||
{
|
||||
CancelIoEx(p_Port->HandleRead, &p_Port->OverlappedRead);
|
||||
}
|
||||
if (p_Port->HandleRead != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(p_Port->HandleRead);
|
||||
}
|
||||
if (p_Port->HandleEvent != nullptr)
|
||||
{
|
||||
CloseHandle(p_Port->HandleEvent);
|
||||
}
|
||||
|
||||
*p_Port = Dri_Consumer_Struct_Port();
|
||||
}
|
||||
|
||||
bool Dri_Consumer_Func_Open(
|
||||
Dri_Consumer_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_Consumer_Func_Close(p_Port);
|
||||
|
||||
QString DevicePath;
|
||||
quint16 InputLength = 0;
|
||||
if (!Mid_Func_FindHidInterface(
|
||||
Mid_Func_GetConsumerMatch(DeviceConfig),
|
||||
&DevicePath,
|
||||
&InputLength,
|
||||
nullptr))
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("未找到 Consumer 接口:000C / 0001。");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->HandleRead = CreateFileW(
|
||||
reinterpret_cast<LPCWSTR>(DevicePath.utf16()),
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_OVERLAPPED,
|
||||
nullptr);
|
||||
if (p_Port->HandleRead == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("Consumer 接口打开失败:%1").arg(GetLastError());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->HandleEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
if (p_Port->HandleEvent == nullptr)
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("Consumer 接口创建事件失败:%1").arg(GetLastError());
|
||||
}
|
||||
Dri_Consumer_Func_Close(p_Port);
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->InputLength = InputLength;
|
||||
p_Port->ReadBuffer = QByteArray(InputLength, 0);
|
||||
p_Port->OverlappedRead.hEvent = p_Port->HandleEvent;
|
||||
p_Port->IsOpened = true;
|
||||
|
||||
Dri_Consumer_Func_BeginRead(p_Port, p_TextStatus);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dri_Consumer_Func_Read(
|
||||
Dri_Consumer_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
p_Packet->IsValid = false;
|
||||
p_Packet->ByteArray.clear();
|
||||
p_Packet->PortName = QStringLiteral("Consumer");
|
||||
|
||||
if (!p_Port->IsOpened)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!p_Port->IsReadPending)
|
||||
{
|
||||
Dri_Consumer_Func_BeginRead(p_Port, p_TextStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD BytesRead = 0;
|
||||
if (!GetOverlappedResult(p_Port->HandleRead, &p_Port->OverlappedRead, &BytesRead, FALSE))
|
||||
{
|
||||
const DWORD ErrorCode = GetLastError();
|
||||
if (ErrorCode == ERROR_IO_INCOMPLETE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("Consumer 接口读包失败:%1").arg(ErrorCode);
|
||||
}
|
||||
Dri_Consumer_Func_Close(p_Port);
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->IsReadPending = false;
|
||||
if (BytesRead > 0)
|
||||
{
|
||||
p_Packet->IsValid = true;
|
||||
p_Packet->ByteArray = p_Port->ReadBuffer.left(static_cast<int>(BytesRead));
|
||||
}
|
||||
|
||||
Dri_Consumer_Func_BeginRead(p_Port, p_TextStatus);
|
||||
return p_Packet->IsValid;
|
||||
}
|
||||
35
DRI/Dri_Consumer.h
Normal file
35
DRI/Dri_Consumer.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "MID/Mid_Def.h"
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QString>
|
||||
#include <Windows.h>
|
||||
|
||||
/*
|
||||
* DRI Consumer 层:抽象 HID Consumer 端点,封装 Win32 句柄、缓存与状态。
|
||||
*
|
||||
*/
|
||||
struct Dri_Consumer_Struct_Port
|
||||
{
|
||||
/* 设备读写所需的句柄与异步上下文 */
|
||||
HANDLE HandleRead = INVALID_HANDLE_VALUE;
|
||||
HANDLE HandleEvent = nullptr;
|
||||
OVERLAPPED OverlappedRead = {};
|
||||
/* 快速判断是否已经建立连接以及当前是否正挂起读 */
|
||||
bool IsOpened = false;
|
||||
bool IsReadPending = false;
|
||||
/* USB 输入报文长度 & 环形缓存 */
|
||||
quint16 InputLength = 0;
|
||||
QByteArray ReadBuffer;
|
||||
};
|
||||
|
||||
/* 主动释放 Consumer 端口,确保句柄/Overlapped 都被 reset。 */
|
||||
void Dri_Consumer_Func_Close(Dri_Consumer_Struct_Port* p_Port);
|
||||
/* 打开设备:按照 VID/PID 查询接口并创建 Overlapped 句柄。 */
|
||||
bool Dri_Consumer_Func_Open(Dri_Consumer_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus);
|
||||
/* 读取最新的 Consumer 原始包,失败时返回状态描述。 */
|
||||
bool Dri_Consumer_Func_Read(Dri_Consumer_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus);
|
||||
282
DRI/Dri_NkroRaw.cpp
Normal file
282
DRI/Dri_NkroRaw.cpp
Normal file
@@ -0,0 +1,282 @@
|
||||
#include "DRI/Dri_NkroRaw.h"
|
||||
|
||||
#include <QtCore/QVector>
|
||||
#include <Windows.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/* ---------- 设备过滤与 Usage 映射 ---------- */
|
||||
|
||||
QString Dri_NkroRaw_Func_GetDevicePath(HANDLE DeviceHandle)
|
||||
{
|
||||
if (DeviceHandle == nullptr)
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
UINT NeedChars = 0;
|
||||
GetRawInputDeviceInfoW(DeviceHandle, RIDI_DEVICENAME, nullptr, &NeedChars);
|
||||
if (NeedChars == 0)
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
QVector<wchar_t> Buffer(static_cast<int>(NeedChars) + 1, 0);
|
||||
if (GetRawInputDeviceInfoW(DeviceHandle, RIDI_DEVICENAME, Buffer.data(), &NeedChars) == static_cast<UINT>(-1))
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
return QString::fromWCharArray(Buffer.constData()).trimmed();
|
||||
}
|
||||
|
||||
bool Dri_NkroRaw_Func_IsTargetDevice(const QString& DevicePath, const Mid_Struct_DeviceConfig& DeviceConfig)
|
||||
{
|
||||
if (DevicePath.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString UpperPath = DevicePath.toUpper();
|
||||
return UpperPath.contains(QStringLiteral("VID_%1").arg(DeviceConfig.VendorId, 4, 16, QLatin1Char('0')).toUpper()) &&
|
||||
UpperPath.contains(QStringLiteral("PID_%1").arg(DeviceConfig.ProductId, 4, 16, QLatin1Char('0')).toUpper());
|
||||
}
|
||||
|
||||
quint16 Dri_NkroRaw_Func_GetUsage(const RAWKEYBOARD& Keyboard)
|
||||
{
|
||||
const bool IsE0 = (Keyboard.Flags & RI_KEY_E0) != 0;
|
||||
const bool IsE1 = (Keyboard.Flags & RI_KEY_E1) != 0;
|
||||
const USHORT ScanCode = Keyboard.MakeCode;
|
||||
|
||||
if (IsE1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (IsE0)
|
||||
{
|
||||
switch (ScanCode)
|
||||
{
|
||||
case 0x35: return 0x0054;
|
||||
case 0x1C: return 0x0058;
|
||||
case 0x1D: return 0x00E4;
|
||||
case 0x38: return 0x00E6;
|
||||
case 0x5B: return 0x00E3;
|
||||
case 0x5C: return 0x00E7;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
switch (ScanCode)
|
||||
{
|
||||
case 0x45: return 0x0053;
|
||||
case 0x37: return 0x0055;
|
||||
case 0x4A: return 0x0056;
|
||||
case 0x4E: return 0x0057;
|
||||
case 0x47: return 0x005F;
|
||||
case 0x48: return 0x0060;
|
||||
case 0x49: return 0x0061;
|
||||
case 0x4B: return 0x005C;
|
||||
case 0x4C: return 0x005D;
|
||||
case 0x4D: return 0x005E;
|
||||
case 0x4F: return 0x0059;
|
||||
case 0x50: return 0x005A;
|
||||
case 0x51: return 0x005B;
|
||||
case 0x52: return 0x0062;
|
||||
case 0x53: return 0x0063;
|
||||
case 0x1D: return 0x00E0;
|
||||
case 0x2A: return 0x00E1;
|
||||
case 0x36: return 0x00E5;
|
||||
case 0x38: return 0x00E2;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/* ---------- 生命周期 ---------- */
|
||||
|
||||
void Dri_NkroRaw_Func_Close(Dri_NkroRaw_Struct_Port* p_Port)
|
||||
{
|
||||
*p_Port = Dri_NkroRaw_Struct_Port();
|
||||
}
|
||||
|
||||
bool Dri_NkroRaw_Func_Open(
|
||||
Dri_NkroRaw_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
void* WindowHandle,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_NkroRaw_Func_Close(p_Port);
|
||||
|
||||
if (WindowHandle == nullptr)
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("NKRO 原生输入链路打开失败:窗口句柄为空。");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
RAWINPUTDEVICE Device = {};
|
||||
Device.usUsagePage = MID_CONST_USAGE_PAGE_NKRO;
|
||||
Device.usUsage = MID_CONST_USAGE_NKRO;
|
||||
Device.dwFlags = RIDEV_INPUTSINK;
|
||||
Device.hwndTarget = reinterpret_cast<HWND>(WindowHandle);
|
||||
|
||||
if (!RegisterRawInputDevices(&Device, 1, sizeof(Device)))
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("NKRO 原生输入链路注册失败:%1").arg(GetLastError());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->IsOpened = true;
|
||||
p_Port->WindowHandle = WindowHandle;
|
||||
p_Port->DeviceConfig = DeviceConfig;
|
||||
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("已启用 NKRO 原生输入链路。");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ---------- RawInput 主流程 ---------- */
|
||||
|
||||
bool Dri_NkroRaw_Func_HandleNativeMessage(
|
||||
Dri_NkroRaw_Struct_Port* p_Port,
|
||||
void* p_Message,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
if (!p_Port->IsOpened || (p_Message == nullptr))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
MSG* p_Msg = reinterpret_cast<MSG*>(p_Message);
|
||||
if (p_Msg->message != WM_INPUT)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
UINT NeedSize = 0;
|
||||
GetRawInputData(reinterpret_cast<HRAWINPUT>(p_Msg->lParam), RID_INPUT, nullptr, &NeedSize, sizeof(RAWINPUTHEADER));
|
||||
if (NeedSize == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray Buffer(static_cast<int>(NeedSize), 0);
|
||||
if (GetRawInputData(
|
||||
reinterpret_cast<HRAWINPUT>(p_Msg->lParam),
|
||||
RID_INPUT,
|
||||
Buffer.data(),
|
||||
&NeedSize,
|
||||
sizeof(RAWINPUTHEADER)) == static_cast<UINT>(-1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RAWINPUT* p_Input = reinterpret_cast<RAWINPUT*>(Buffer.data());
|
||||
if (p_Input->header.dwType != RIM_TYPEKEYBOARD)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString DevicePath = Dri_NkroRaw_Func_GetDevicePath(p_Input->header.hDevice);
|
||||
if (!Dri_NkroRaw_Func_IsTargetDevice(DevicePath, p_Port->DeviceConfig))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const quint16 Usage = Dri_NkroRaw_Func_GetUsage(p_Input->data.keyboard);
|
||||
if (Usage == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool IsPressed = (p_Input->data.keyboard.Flags & RI_KEY_BREAK) == 0;
|
||||
bool IsChanged = false;
|
||||
|
||||
if ((Usage >= 0x00E0) && (Usage <= 0x00E7))
|
||||
{
|
||||
const quint8 BitMask = static_cast<quint8>(1U << (Usage - 0x00E0));
|
||||
const quint8 OldModifier = p_Port->Modifier;
|
||||
if (IsPressed)
|
||||
{
|
||||
p_Port->Modifier = static_cast<quint8>(p_Port->Modifier | BitMask);
|
||||
}
|
||||
else
|
||||
{
|
||||
p_Port->Modifier = static_cast<quint8>(p_Port->Modifier & static_cast<quint8>(~BitMask));
|
||||
}
|
||||
IsChanged = (OldModifier != p_Port->Modifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
const int ByteIndex = Usage / 8;
|
||||
const quint8 BitMask = static_cast<quint8>(1U << (Usage % 8));
|
||||
quint8 Value = static_cast<quint8>(p_Port->UsageBitmap.at(ByteIndex));
|
||||
const bool OldPressed = (Value & BitMask) != 0;
|
||||
if (OldPressed == IsPressed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Value = IsPressed ? static_cast<quint8>(Value | BitMask) : static_cast<quint8>(Value & static_cast<quint8>(~BitMask));
|
||||
p_Port->UsageBitmap[ByteIndex] = static_cast<char>(Value);
|
||||
IsChanged = true;
|
||||
}
|
||||
|
||||
if (!IsChanged)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_Port->DevicePath != DevicePath)
|
||||
{
|
||||
p_Port->DevicePath = DevicePath;
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = QStringLiteral("原生输入已命中目标设备:%1").arg(DevicePath);
|
||||
}
|
||||
}
|
||||
|
||||
Mid_Struct_RawPacket Packet;
|
||||
Packet.IsValid = true;
|
||||
Packet.PortName = QStringLiteral("NKRO(原生输入)");
|
||||
Packet.ByteArray = QByteArray(MID_CONST_PACKET_SIZE_NKRO, 0);
|
||||
Packet.ByteArray[0] = static_cast<char>(Mid_Enum_ReportId_Nkro);
|
||||
Packet.ByteArray[1] = static_cast<char>(p_Port->Modifier);
|
||||
|
||||
for (int Index = 0; Index < MID_CONST_USAGE_BITMAP_SIZE; ++Index)
|
||||
{
|
||||
Packet.ByteArray[2 + Index] = p_Port->UsageBitmap.at(Index);
|
||||
}
|
||||
|
||||
p_Port->PacketQueue.append(Packet);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dri_NkroRaw_Func_Read(
|
||||
Dri_NkroRaw_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString*)
|
||||
{
|
||||
p_Packet->IsValid = false;
|
||||
p_Packet->ByteArray.clear();
|
||||
p_Packet->PortName = QStringLiteral("NKRO(原生输入)");
|
||||
|
||||
if (!p_Port->IsOpened || p_Port->PacketQueue.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
*p_Packet = p_Port->PacketQueue.takeFirst();
|
||||
return p_Packet->IsValid;
|
||||
}
|
||||
40
DRI/Dri_NkroRaw.h
Normal file
40
DRI/Dri_NkroRaw.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "MID/Mid_Def.h"
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QString>
|
||||
|
||||
/*
|
||||
* DRI NKRO RAW 层:负责注册 Windows RAWINPUT,接收键盘 104+ 通道。
|
||||
* 这里集中保存窗口句柄、设备配置、按键队列与 Modifier/Bitmap 缓冲。
|
||||
*/
|
||||
struct Dri_NkroRaw_Struct_Port
|
||||
{
|
||||
/* 运行状态:是否已注册 RAWINPUT 以及宿主窗口 HWND */
|
||||
bool IsOpened = false;
|
||||
void* WindowHandle = nullptr;
|
||||
Mid_Struct_DeviceConfig DeviceConfig;
|
||||
/* 最新的 Modifier 字节和 NKRO Usage 位图 */
|
||||
quint8 Modifier = 0;
|
||||
QByteArray UsageBitmap = QByteArray(MID_CONST_USAGE_BITMAP_SIZE, 0);
|
||||
/* RAWINPUT 读取结果放入 PacketQueue,供上层逻辑顺序消费 */
|
||||
QList<Mid_Struct_RawPacket> PacketQueue;
|
||||
QString DevicePath;
|
||||
};
|
||||
|
||||
/* 注销 RAWINPUT 并清空所有缓存。 */
|
||||
void Dri_NkroRaw_Func_Close(Dri_NkroRaw_Struct_Port* p_Port);
|
||||
/* 注册 RAWINPUT:绑定目标窗口并监听指定 VID/PID。 */
|
||||
bool Dri_NkroRaw_Func_Open(Dri_NkroRaw_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
void* WindowHandle,
|
||||
QString* p_TextStatus);
|
||||
/* 把 Windows 消息转为 NKRO 数据:核心教学入口。 */
|
||||
bool Dri_NkroRaw_Func_HandleNativeMessage(Dri_NkroRaw_Struct_Port* p_Port,
|
||||
void* p_Message,
|
||||
QString* p_TextStatus);
|
||||
/* 上层每次取一个封装好的 RawPacket。 */
|
||||
bool Dri_NkroRaw_Func_Read(Dri_NkroRaw_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus);
|
||||
338
DRI/Dri_Vendor.cpp
Normal file
338
DRI/Dri_Vendor.cpp
Normal file
@@ -0,0 +1,338 @@
|
||||
#include "DRI/Dri_Vendor.h"
|
||||
|
||||
#include <hidsdi.h>
|
||||
|
||||
#pragma comment(lib, "hid.lib")
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/* ---------- 句柄与状态小工具 ---------- */
|
||||
|
||||
void Dri_Vendor_Func_SetStatus(QString* p_TextStatus, const QString& Text)
|
||||
{
|
||||
if (p_TextStatus != nullptr)
|
||||
{
|
||||
*p_TextStatus = Text;
|
||||
}
|
||||
}
|
||||
|
||||
HANDLE Dri_Vendor_Func_OpenHandle(const QString& DevicePath, DWORD DesiredAccess, DWORD Flags)
|
||||
{
|
||||
return CreateFileW(
|
||||
reinterpret_cast<LPCWSTR>(DevicePath.utf16()),
|
||||
DesiredAccess,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
Flags,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
bool Dri_Vendor_Func_BeginRead(Dri_Vendor_Struct_Port* p_Port, QString* p_TextStatus)
|
||||
{
|
||||
if (!p_Port->IsOpened || (p_Port->InputLength == 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p_Port->IsReadPending)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ResetEvent(p_Port->HandleEvent);
|
||||
p_Port->ReadBuffer.fill(0, p_Port->InputLength);
|
||||
p_Port->OverlappedRead = {};
|
||||
p_Port->OverlappedRead.hEvent = p_Port->HandleEvent;
|
||||
|
||||
DWORD BytesRead = 0;
|
||||
if (ReadFile(
|
||||
p_Port->HandleRead,
|
||||
p_Port->ReadBuffer.data(),
|
||||
p_Port->InputLength,
|
||||
&BytesRead,
|
||||
&p_Port->OverlappedRead) ||
|
||||
(GetLastError() == ERROR_IO_PENDING))
|
||||
{
|
||||
p_Port->IsReadPending = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Dri_Vendor_Func_SetStatus(
|
||||
p_TextStatus,
|
||||
QStringLiteral("Vendor 接口启动异步读取失败:%1").arg(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
HANDLE Dri_Vendor_Func_OpenWriteHandle(const QString& DevicePath, QString* p_TextError)
|
||||
{
|
||||
HANDLE HandleWrite = Dri_Vendor_Func_OpenHandle(DevicePath, GENERIC_WRITE, 0);
|
||||
if (HandleWrite != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
return HandleWrite;
|
||||
}
|
||||
|
||||
const DWORD WriteError = GetLastError();
|
||||
HandleWrite = Dri_Vendor_Func_OpenHandle(DevicePath, 0, 0);
|
||||
|
||||
if ((HandleWrite == INVALID_HANDLE_VALUE) && (p_TextError != nullptr))
|
||||
{
|
||||
*p_TextError = QStringLiteral("GENERIC_WRITE=%1,ZeroAccess=%2")
|
||||
.arg(WriteError)
|
||||
.arg(GetLastError());
|
||||
}
|
||||
return HandleWrite;
|
||||
}
|
||||
|
||||
bool Dri_Vendor_Func_TryWritePacket(
|
||||
HANDLE HandleWrite,
|
||||
quint16 OutputLength,
|
||||
const QByteArray& Packet,
|
||||
QString* p_TextError)
|
||||
{
|
||||
if ((HandleWrite == INVALID_HANDLE_VALUE) || Packet.isEmpty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray Buffer = Packet;
|
||||
if ((OutputLength > 0) && (Buffer.size() < OutputLength))
|
||||
{
|
||||
Buffer.append(OutputLength - Buffer.size(), 0);
|
||||
}
|
||||
|
||||
if (HidD_SetOutputReport(HandleWrite, Buffer.data(), static_cast<ULONG>(Buffer.size())))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const DWORD SetReportError = GetLastError();
|
||||
DWORD BytesWritten = 0;
|
||||
if (WriteFile(
|
||||
HandleWrite,
|
||||
Buffer.constData(),
|
||||
static_cast<DWORD>(Buffer.size()),
|
||||
&BytesWritten,
|
||||
nullptr) &&
|
||||
(BytesWritten == static_cast<DWORD>(Buffer.size())))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_TextError != nullptr)
|
||||
{
|
||||
*p_TextError = QStringLiteral("SetOutputReport=%1,WriteFile=%2")
|
||||
.arg(SetReportError)
|
||||
.arg(GetLastError());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/* ---------- 生命周期 ---------- */
|
||||
|
||||
void Dri_Vendor_Func_Close(Dri_Vendor_Struct_Port* p_Port)
|
||||
{
|
||||
if ((p_Port->HandleRead != INVALID_HANDLE_VALUE) && p_Port->IsReadPending)
|
||||
{
|
||||
CancelIoEx(p_Port->HandleRead, &p_Port->OverlappedRead);
|
||||
}
|
||||
|
||||
for (HANDLE* p_Handle : { &p_Port->HandleRead, &p_Port->HandleWriteVendor, &p_Port->HandleWriteNkro, &p_Port->HandleEvent })
|
||||
{
|
||||
if ((*p_Handle != nullptr) && (*p_Handle != INVALID_HANDLE_VALUE))
|
||||
{
|
||||
CloseHandle(*p_Handle);
|
||||
}
|
||||
}
|
||||
|
||||
*p_Port = Dri_Vendor_Struct_Port();
|
||||
}
|
||||
|
||||
bool Dri_Vendor_Func_Open(
|
||||
Dri_Vendor_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
Dri_Vendor_Func_Close(p_Port);
|
||||
|
||||
QString VendorPath;
|
||||
quint16 InputLength = 0;
|
||||
quint16 OutputLength = 0;
|
||||
if (!Mid_Func_FindHidInterface(
|
||||
Mid_Func_GetVendorMatch(DeviceConfig),
|
||||
&VendorPath,
|
||||
&InputLength,
|
||||
&OutputLength))
|
||||
{
|
||||
Dri_Vendor_Func_SetStatus(p_TextStatus, QStringLiteral("未找到 Vendor 接口:FF00 / 0002。"));
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->HandleRead = Dri_Vendor_Func_OpenHandle(VendorPath, GENERIC_READ, FILE_FLAG_OVERLAPPED);
|
||||
if (p_Port->HandleRead == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
Dri_Vendor_Func_SetStatus(
|
||||
p_TextStatus,
|
||||
QStringLiteral("Vendor 接口打开读句柄失败:%1").arg(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->HandleEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
if (p_Port->HandleEvent == nullptr)
|
||||
{
|
||||
Dri_Vendor_Func_SetStatus(
|
||||
p_TextStatus,
|
||||
QStringLiteral("Vendor 接口创建事件失败:%1").arg(GetLastError()));
|
||||
Dri_Vendor_Func_Close(p_Port);
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->HandleWriteVendor = Dri_Vendor_Func_OpenWriteHandle(VendorPath, nullptr);
|
||||
|
||||
QString NkroPath;
|
||||
quint16 NkroOutputLength = 0;
|
||||
if (Mid_Func_FindHidInterface(
|
||||
Mid_Func_GetNkroMatch(DeviceConfig),
|
||||
&NkroPath,
|
||||
nullptr,
|
||||
&NkroOutputLength))
|
||||
{
|
||||
QString TextError;
|
||||
p_Port->HandleWriteNkro = Dri_Vendor_Func_OpenWriteHandle(NkroPath, &TextError);
|
||||
p_Port->NkroWriteOutputLength = NkroOutputLength;
|
||||
|
||||
if (p_Port->HandleWriteNkro == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
Dri_Vendor_Func_SetStatus(
|
||||
p_TextStatus,
|
||||
QStringLiteral("Vendor 读链路已打开,但 NKRO 写句柄打开失败:%1").arg(TextError));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Dri_Vendor_Func_SetStatus(
|
||||
p_TextStatus,
|
||||
QStringLiteral("Vendor 读链路已打开,但没有找到 NKRO 写接口。"));
|
||||
}
|
||||
|
||||
p_Port->InputLength = InputLength;
|
||||
p_Port->OutputLength = OutputLength;
|
||||
p_Port->ReadBuffer = QByteArray(InputLength, 0);
|
||||
p_Port->OverlappedRead.hEvent = p_Port->HandleEvent;
|
||||
p_Port->IsOpened = true;
|
||||
|
||||
if (!Dri_Vendor_Func_BeginRead(p_Port, p_TextStatus))
|
||||
{
|
||||
Dri_Vendor_Func_Close(p_Port);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ---------- 读写流程 ---------- */
|
||||
|
||||
bool Dri_Vendor_Func_Read(
|
||||
Dri_Vendor_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
*p_Packet = Mid_Struct_RawPacket();
|
||||
p_Packet->PortName = QStringLiteral("Vendor");
|
||||
|
||||
if (!p_Port->IsOpened)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!p_Port->IsReadPending)
|
||||
{
|
||||
Dri_Vendor_Func_BeginRead(p_Port, p_TextStatus);
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD BytesRead = 0;
|
||||
if (!GetOverlappedResult(p_Port->HandleRead, &p_Port->OverlappedRead, &BytesRead, FALSE))
|
||||
{
|
||||
const DWORD ErrorCode = GetLastError();
|
||||
if (ErrorCode == ERROR_IO_INCOMPLETE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Dri_Vendor_Func_SetStatus(
|
||||
p_TextStatus,
|
||||
QStringLiteral("Vendor 接口读包失败:%1").arg(ErrorCode));
|
||||
Dri_Vendor_Func_Close(p_Port);
|
||||
return false;
|
||||
}
|
||||
|
||||
p_Port->IsReadPending = false;
|
||||
if (BytesRead > 0)
|
||||
{
|
||||
p_Packet->IsValid = true;
|
||||
p_Packet->ByteArray = p_Port->ReadBuffer.left(static_cast<int>(BytesRead));
|
||||
}
|
||||
|
||||
Dri_Vendor_Func_BeginRead(p_Port, p_TextStatus);
|
||||
return p_Packet->IsValid;
|
||||
}
|
||||
|
||||
bool Dri_Vendor_Func_Write(
|
||||
Dri_Vendor_Struct_Port* p_Port,
|
||||
const QByteArray& ByteArray,
|
||||
QString* p_TextStatus)
|
||||
{
|
||||
if (!p_Port->IsOpened)
|
||||
{
|
||||
Dri_Vendor_Func_SetStatus(
|
||||
p_TextStatus,
|
||||
QStringLiteral("Vendor 读链路未打开,不能发送掩码。"));
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList Errors;
|
||||
const auto TryWriteTo = [&](HANDLE HandleWrite,
|
||||
quint16 OutputLength,
|
||||
const QString& SuccessText,
|
||||
const QString& ErrorPrefix)
|
||||
{
|
||||
QString TextError;
|
||||
if (Dri_Vendor_Func_TryWritePacket(HandleWrite, OutputLength, ByteArray, &TextError))
|
||||
{
|
||||
Dri_Vendor_Func_SetStatus(p_TextStatus, SuccessText);
|
||||
return true;
|
||||
}
|
||||
if (!TextError.isEmpty())
|
||||
{
|
||||
Errors.append(ErrorPrefix.arg(TextError));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (TryWriteTo(
|
||||
p_Port->HandleWriteNkro,
|
||||
p_Port->NkroWriteOutputLength,
|
||||
QStringLiteral("掩码已发送到 NKRO 写接口。"),
|
||||
QStringLiteral("NKRO 写接口发送失败:%1")))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (TryWriteTo(
|
||||
p_Port->HandleWriteVendor,
|
||||
p_Port->OutputLength,
|
||||
QStringLiteral("掩码已发送到 Vendor 写接口。"),
|
||||
QStringLiteral("Vendor 写接口发送失败:%1")))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Dri_Vendor_Func_SetStatus(
|
||||
p_TextStatus,
|
||||
Errors.isEmpty()
|
||||
? QStringLiteral("没有可用的写句柄,掩码发送未执行。")
|
||||
: Errors.join(QStringLiteral("\n")));
|
||||
return false;
|
||||
}
|
||||
42
DRI/Dri_Vendor.h
Normal file
42
DRI/Dri_Vendor.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "MID/Mid_Def.h"
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QString>
|
||||
#include <Windows.h>
|
||||
|
||||
/*
|
||||
* DRI Vendor 层:读写固件的 Vendor/NKRO HID 接口,支撑逻辑层功能诊断。
|
||||
*
|
||||
*/
|
||||
struct Dri_Vendor_Struct_Port
|
||||
{
|
||||
/* 读写句柄:Vendor 写、NKRO 写与通用读各占一个 */
|
||||
HANDLE HandleRead = INVALID_HANDLE_VALUE;
|
||||
HANDLE HandleWriteVendor = INVALID_HANDLE_VALUE;
|
||||
HANDLE HandleWriteNkro = INVALID_HANDLE_VALUE;
|
||||
HANDLE HandleEvent = nullptr;
|
||||
OVERLAPPED OverlappedRead = {};
|
||||
/* 运行状态 + 报文长度缓存,便于 UI/LOGIC 层快速判断 */
|
||||
bool IsOpened = false;
|
||||
bool IsReadPending = false;
|
||||
quint16 InputLength = 0;
|
||||
quint16 OutputLength = 0;
|
||||
quint16 NkroWriteOutputLength = 0;
|
||||
QByteArray ReadBuffer;
|
||||
};
|
||||
|
||||
/* 关闭全部句柄,安全退出时务必调用以便复用设备。 */
|
||||
void Dri_Vendor_Func_Close(Dri_Vendor_Struct_Port* p_Port);
|
||||
/* 根据 VID/PID 打开对应 HID 接口,并创建必需句柄。 */
|
||||
bool Dri_Vendor_Func_Open(Dri_Vendor_Struct_Port* p_Port,
|
||||
const Mid_Struct_DeviceConfig& DeviceConfig,
|
||||
QString* p_TextStatus);
|
||||
/* 读取 Vendor 报文:一次返回一个 Mid_Struct_RawPacket。 */
|
||||
bool Dri_Vendor_Func_Read(Dri_Vendor_Struct_Port* p_Port,
|
||||
Mid_Struct_RawPacket* p_Packet,
|
||||
QString* p_TextStatus);
|
||||
/* 写入 Vendor/NKRO 报文,ByteArray 按固件定义组包。 */
|
||||
bool Dri_Vendor_Func_Write(Dri_Vendor_Struct_Port* p_Port,
|
||||
const QByteArray& ByteArray,
|
||||
QString* p_TextStatus);
|
||||
Reference in New Issue
Block a user