소스 검색

修改完虚拟键盘识别

张洋 1 주 전
부모
커밋
76105da908
1개의 변경된 파일266개의 추가작업 그리고 53개의 파일을 삭제
  1. 266 53
      zhipuzi_pos_windows/helper/CSystem.cpp

+ 266 - 53
zhipuzi_pos_windows/helper/CSystem.cpp

@@ -19,6 +19,10 @@
 #define RIDI_PREPARSEDDATA     0x20000005
 #endif
 
+#ifndef SPDRP_PARENT_DEVICE_ID
+#define SPDRP_PARENT_DEVICE_ID 0x19
+#endif
+
 int CSystem::get_CPU_core_num()
 {
 #ifdef _WIN32
@@ -605,100 +609,276 @@ void CSystem::HideSystemKeyboard()
 	}
 }
 
-// 判断是否是真正物理键盘,排除鼠标侧键虚拟键盘接口
-static bool IsRealKeyboard(HANDLE hRawDevice)
+// 递归遍历PNP树,查找是否存在BTH\开头的蓝牙硬件子设备
+// 递归遍历PNP树,查找是否存在BTH\开头的蓝牙硬件子设备
+static bool HasBluetoothChildDevice(HDEVINFO hDevInfo, SP_DEVINFO_DATA* parentDevInfo)
 {
-	UINT pathLen = 0;
-	UINT ret = GetRawInputDeviceInfoW(hRawDevice, RIDI_DEVICENAME, nullptr, &pathLen);
-	if (ret == (UINT)-1 || pathLen == 0)
+	WCHAR parentInstanceId[4096]{};
+	if (!SetupDiGetDeviceInstanceIdW(hDevInfo, parentDevInfo, parentInstanceId, _countof(parentInstanceId), nullptr))
 		return false;
 
-	std::wstring devPath(pathLen, L'\0');
-	ret = GetRawInputDeviceInfoW(hRawDevice, RIDI_DEVICENAME, devPath.data(), &pathLen);
-	if (ret == (UINT)-1)
+	SP_DEVINFO_DATA childInfo{};
+	childInfo.cbSize = sizeof(SP_DEVINFO_DATA);
+
+	for (DWORD i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &childInfo); i++)
+	{
+		WCHAR childParentId[4096]{};
+		DWORD propType = 0;
+		// SPDRP_PARENT_DEVICE_ID 0x19
+		if (!SetupDiGetDeviceRegistryPropertyW(hDevInfo, &childInfo, SPDRP_PARENT_DEVICE_ID,
+			&propType, (PBYTE)childParentId, sizeof(childParentId), nullptr))
+			continue;
+
+		if (_wcsicmp(childParentId, parentInstanceId) != 0)
+			continue;
+
+		WCHAR hwIds[4096]{};
+		if (SetupDiGetDeviceRegistryPropertyW(hDevInfo, &childInfo, SPDRP_HARDWAREID,
+			&propType, (PBYTE)hwIds, sizeof(hwIds), nullptr))
+		{
+			std::wstring hwStr(hwIds);
+			if (hwStr.find(L"BTH\\") != std::wstring::npos)
+			{
+				return true;
+			}
+		}
+		if (HasBluetoothChildDevice(hDevInfo, &childInfo))
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+// 根据RawInput hDevice 判断设备是否挂在虚拟总线上(Oray VHID / VHF)
+static bool IsRawInputDeviceOnVirtualBus(HANDLE hRawDevice)
+{
+	UINT bufLen = 0;
+	GetRawInputDeviceInfoW(hRawDevice, RIDI_DEVICENAME, nullptr, &bufLen);
+	if (bufLen == 0) return false;
+
+	std::wstring devName(bufLen, L'\0');
+	bufLen = (UINT)devName.size();
+	UINT ret = GetRawInputDeviceInfoW(hRawDevice, RIDI_DEVICENAME, devName.data(), &bufLen);
+	if (ret == (UINT)-1) return false;
+
+	HDEVINFO hDevInfo = SetupDiGetClassDevsW(NULL, NULL, NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES);
+	if (hDevInfo == INVALID_HANDLE_VALUE)
 		return false;
 
-	//截断末尾'\0',纯STL,不依赖wcslen
-	size_t realLen = devPath.find(L'\0');
-	if (realLen != std::wstring::npos)
+	SP_DEVINFO_DATA devInfoData{};
+	devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
+	bool isVirtual = false;
+
+	for (DWORD i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &devInfoData); i++)
 	{
-		devPath.resize(realLen);
+		WCHAR szInstanceId[4096]{};
+		if (!SetupDiGetDeviceInstanceIdW(hDevInfo, &devInfoData, szInstanceId, _countof(szInstanceId), nullptr))
+			continue;
+
+		if (wcsstr(devName.c_str(), szInstanceId) != nullptr)
+		{
+			WCHAR szLocation[1024]{};
+			DWORD propType = 0;
+			if (SetupDiGetDeviceRegistryPropertyW(hDevInfo, &devInfoData, SPDRP_LOCATION_INFORMATION,
+				&propType, (PBYTE)szLocation, sizeof(szLocation), nullptr))
+			{
+				std::wstring locStr(szLocation);
+				if (locStr.find(L"Oray VHID") != std::wstring::npos
+					|| locStr.find(L"VHF") != std::wstring::npos)
+				{
+					isVirtual = true;
+				}
+			}
+			break;
+		}
 	}
+	SetupDiDestroyDeviceInfoList(hDevInfo);
+	return isVirtual;
+}
+
+// 核心判断:是否为真实物理键盘
+bool IsRealPhysicalKeyboard(LPCWSTR szDevicePath, std::wstring* pDebugOut)
+{
+	std::wstring dbg;
 
-	//微软蓝牙键盘特殊路径,无法CreateFile打开,直接判定真实键盘
-	if (devPath.find(L"Microsoft Keyboard RID") != std::wstring::npos)
+	// ========== 1. 过滤 Microsoft Keyboard RID 系统虚拟键盘 ==========
+	if (szDevicePath != nullptr && wcsstr(szDevicePath, L"Microsoft Keyboard RID") != nullptr)
 	{
-		return true;
+		dbg += L"RID虚拟键盘,直接过滤\n";
+		if (pDebugOut) *pDebugOut = dbg;
+		return false;
 	}
 
-	HANDLE hHid = CreateFileW(
-		devPath.c_str(),
-		0, // 不要GENERIC_READ!仅获取HID描述,不需要访问权限
+	// ========== 2. 打开HID设备 ==========
+	HANDLE hTestOnline = CreateFileW(
+		szDevicePath,
+		0,
 		FILE_SHARE_READ | FILE_SHARE_WRITE,
 		nullptr,
 		OPEN_EXISTING,
 		0,
 		nullptr
 	);
-	if (hHid == INVALID_HANDLE_VALUE)
+
+	if (hTestOnline == INVALID_HANDLE_VALUE)
 	{
+		dbg += L"CreateFileW 打开设备失败\n";
+		if (pDebugOut) *pDebugOut = dbg;
 		return false;
 	}
 
-	PHIDP_PREPARSED_DATA pPreparsed = nullptr;
-	BOOL bOk = HidD_GetPreparsedData(hHid, &pPreparsed);
-	CloseHandle(hHid);
-
-	if (!bOk || pPreparsed == nullptr)
+	PHIDP_PREPARSED_DATA pPreparse = nullptr;
+	BOOLEAN bRet = HidD_GetPreparsedData(hTestOnline, &pPreparse);
+	if (!bRet || pPreparse == nullptr)
 	{
+		dbg += L"HidD_GetPreparsedData 获取PreparsedData失败\n";
+		CloseHandle(hTestOnline);
+		if (pDebugOut) *pDebugOut = dbg;
 		return false;
 	}
 
+	// ========== 3. 获取HID顶层能力 ==========
 	HIDP_CAPS caps{};
-	LONG status = HidP_GetCaps(pPreparsed, &caps);
-	HidD_FreePreparsedData(pPreparsed);
+	NTSTATUS ntStatus = HidP_GetCaps(pPreparse, &caps);
+	if (ntStatus != HIDP_STATUS_SUCCESS)
+	{
+		dbg += L"HidP_GetCaps 获取Caps失败\n";
+		HidD_FreePreparsedData(pPreparse);
+		CloseHandle(hTestOnline);
+		if (pDebugOut) *pDebugOut = dbg;
+		return false;
+	}
 
-	if (status != 0)
+	// ========== 4. 校验顶层必须是标准键盘 UsagePage=0x01, Usage=0x06 ==========
+	if (!(caps.UsagePage == 0x01 && caps.Usage == 0x06))
 	{
+		dbg += L"HID: Top‑Level 不是标准键盘,过滤\n";
+		HidD_FreePreparsedData(pPreparse);
+		CloseHandle(hTestOnline);
+		if (pDebugOut) *pDebugOut = dbg;
 		return false;
 	}
 
-	if (caps.UsagePage == 0x01 && caps.Usage == 0x06)
+	//=====新增:过滤没有VID_的内置主板/笔记本内置HID键盘=====
+	if (wcsstr(szDevicePath, L"VID_") == nullptr)
 	{
-		return true;
+		dbg += L"HID: 设备路径无VID_,判定为本机内置HID,过滤\n";
+		HidD_FreePreparsedData(pPreparse);
+		CloseHandle(hTestOnline);
+		if (pDebugOut) *pDebugOut = dbg;
+		return false;
 	}
-	return false;
+
+	// ========== 5. OutputReportByteLength == 0 拦截鼠标侧键虚拟键盘 ==========
+	if (caps.OutputReportByteLength == 0)
+	{
+		dbg += L"HID: OutputReport长度为0,判定鼠标侧键虚拟键盘,过滤\n";
+		HidD_FreePreparsedData(pPreparse);
+		CloseHandle(hTestOnline);
+		if (pDebugOut) *pDebugOut = dbg;
+		return false;
+	}
+
+	// ========== 6. LED指示灯检测【仅调试打印,不做过滤条件】 ==========
+	bool hasKeyboardLed = false;
+	USHORT valueCapsCount = 0;
+	ntStatus = HidP_GetValueCaps(HidP_Output, nullptr, &valueCapsCount, pPreparse);
+	if (ntStatus == HIDP_STATUS_SUCCESS && valueCapsCount > 0)
+	{
+		std::vector<HIDP_VALUE_CAPS> valueCaps(valueCapsCount);
+		ntStatus = HidP_GetValueCaps(HidP_Output, valueCaps.data(), &valueCapsCount, pPreparse);
+		if (ntStatus == HIDP_STATUS_SUCCESS)
+		{
+			for (USHORT i = 0; i < valueCapsCount; i++)
+			{
+				auto& vc = valueCaps[i];
+				if (vc.UsagePage == 0x01 && !vc.IsRange)
+				{
+					USAGE u = vc.NotRange.Usage;
+					if (u == 0x45 || u == 0x46 || u == 0x47)
+					{
+						hasKeyboardLed = true;
+						break;
+					}
+				}
+			}
+		}
+	}
+	dbg += L"[Debug] HasKeyboardLed=" + std::to_wstring(hasKeyboardLed) + L"\n";
+
+	// ========== 全部校验通过,判定真实物理键盘 ==========
+	dbg += L"HID: 通过真实键盘判定\n";
+
+	// 资源释放
+	HidD_FreePreparsedData(pPreparse);
+	CloseHandle(hTestOnline);
+
+	if (pDebugOut) *pDebugOut = dbg;
+	return true;
 }
 
 bool CSystem::HasKeyboardDevice()
 {
 	UINT deviceCount = 0;
-	UINT ret = ::GetRawInputDeviceList(nullptr, &deviceCount, sizeof(RAWINPUTDEVICELIST));
+	UINT ret = GetRawInputDeviceList(nullptr, &deviceCount, sizeof(RAWINPUTDEVICELIST));
 	if (ret == (UINT)-1 || deviceCount == 0)
-	{
 		return false;
-	}
 
 	std::vector<RAWINPUTDEVICELIST> devices(deviceCount);
-	ret = ::GetRawInputDeviceList(devices.data(), &deviceCount, sizeof(RAWINPUTDEVICELIST));
+	ret = GetRawInputDeviceList(devices.data(), &deviceCount, sizeof(RAWINPUTDEVICELIST));
 	if (ret == (UINT)-1)
-	{
 		return false;
-	}
 
-	bool hasRealKeyboard = false;
-	for (UINT i = 0; i < deviceCount; ++i)
+	std::unordered_set<std::wstring> uniqueUsbId;
+
+	for (UINT i = 0; i < deviceCount; i++)
 	{
-		if (devices[i].dwType == RIM_TYPEKEYBOARD)
+		if (devices[i].dwType != RIM_TYPEKEYBOARD)
+			continue;
+
+		// 1. 先获取设备路径
+		UINT pathLen = 0;
+		GetRawInputDeviceInfoW(devices[i].hDevice, RIDI_DEVICENAME, nullptr, &pathLen);
+		if (pathLen == 0)
+			continue;
+
+		std::wstring devPath(pathLen, L'\0');
+		GetRawInputDeviceInfoW(devices[i].hDevice, RIDI_DEVICENAME, devPath.data(), &pathLen);
+
+		// 截断末尾多余'\0'
+		size_t zeroPos = devPath.find(L'\0');
+		if (zeroPos != std::wstring::npos)
+			devPath.resize(zeroPos);
+
+		// 2. 调用IsRealPhysicalKeyboard,传入路径;调试输出传nullptr(不需要调试就传nullptr)
+		std::wstring itemDebug;
+		if (!IsRealPhysicalKeyboard(devPath.c_str(), &itemDebug))
 		{
-			if (IsRealKeyboard(devices[i].hDevice))
-			{
-				hasRealKeyboard = true;
-				break;
-			}
+			continue;
+		}
+
+		// 3. 提取uniqueId,保留你原来的解析逻辑
+		std::wstring uniqueId;
+		size_t usbTag = devPath.find(L"#7&");
+		if (usbTag != std::wstring::npos)
+		{
+			size_t endTag = devPath.find(L"&0&", usbTag);
+			if (endTag != std::wstring::npos)
+				uniqueId = devPath.substr(0, endTag);
+		}
+		if (devPath.find(L"Microsoft Keyboard RID") != std::wstring::npos)
+		{
+			uniqueId = L"BT_KB";
+		}
+
+		if (!uniqueId.empty())
+		{
+			uniqueUsbId.insert(uniqueId);
 		}
 	}
-	return hasRealKeyboard;
+
+	return uniqueUsbId.size() > 0;
 }
 
 void CSystem::ShowKeyboardDeviceNames()
@@ -735,29 +915,62 @@ void CSystem::ShowKeyboardDeviceNames()
 		if (devices[i].dwType == RIM_TYPEKEYBOARD)
 		{
 			oss << L" (RIM_TYPEKEYBOARD)\r\n";
-			bool realKb = IsRealKeyboard(devices[i].hDevice);
-			oss << L"  IsRealKeyboard: " << (realKb ? L"YES(真实键盘)" : L"NO(鼠标侧键/虚拟键盘)") << L"\r\n";
 
-			if (realKb)
-			{
-				hasRealKeyboard = true;
-			}
+			// 先获取DevicePath,不能直接传hDevice给IsRealPhysicalKeyboard
+			bool realKb = false;
+			std::wstring devName;
+			std::wstring itemDebug;
 
 			UINT nameLen = 0;
 			UINT riRet = GetRawInputDeviceInfoW(devices[i].hDevice, RIDI_DEVICENAME, nullptr, &nameLen);
 			if (riRet != (UINT)-1 && nameLen > 0)
 			{
-				std::wstring devName(nameLen, L'\0');
+				devName.resize(nameLen, L'\0');
 				riRet = GetRawInputDeviceInfoW(devices[i].hDevice, RIDI_DEVICENAME, devName.data(), &nameLen);
 				if (riRet != (UINT)-1)
 				{
 					size_t realLen = devName.find(L'\0');
 					if (realLen != std::wstring::npos)
 						devName.resize(realLen);
-					oss << L"  DevicePath: " << devName.c_str() << L"\r\n";
+
+					// 传入路径,拿到判定结果与子函数调试信息
+					realKb = IsRealPhysicalKeyboard(devName.c_str(), &itemDebug);
 				}
 			}
 
+			oss << L"  IsRealKeyboard: " << (realKb ? L"YES(真实键盘)" : L"NO(鼠标侧键/虚拟键盘)") << L"\r\n";
+			// 输出IsRealPhysicalKeyboard内部调试(可选,方便排错)
+			if (!itemDebug.empty())
+			{
+				oss << L"  [SubDebug] " << itemDebug << L"\r\n";
+			}
+
+			if (realKb)
+			{
+				hasRealKeyboard = true;
+			}
+
+			// 打印是否虚拟总线(保留原有逻辑)
+			if (IsRawInputDeviceOnVirtualBus(devices[i].hDevice))
+			{
+				oss << L"  BusCheck: [Virtual Bus Detected!]\r\n";
+			}
+			else
+			{
+				oss << L"  BusCheck: [Physical Bus]\r\n";
+			}
+
+			// DevicePath
+			if (!devName.empty())
+			{
+				oss << L"  DevicePath: " << devName.c_str() << L"\r\n";
+			}
+			else
+			{
+				oss << L"  DevicePath: (获取失败)\r\n";
+			}
+
+			// FriendlyName
 			UINT friendlyLen = 0;
 			riRet = GetRawInputDeviceInfoW(devices[i].hDevice, RIDI_DEVICEFRIENDLYNAME, nullptr, &friendlyLen);
 			if (riRet != (UINT)-1 && friendlyLen > 0)