以上代码存在问题:第一次直接运行的时候所有功能都完善,可以正常捕获键盘、鼠标输入,设置全屏等
然而,host应用改变分辨率或不是前台全屏之后无法重新捕获键盘输入,只能捕获鼠标,不能捕获键盘。
(附加信息:如果使用快捷键调起别的应用,比如微信,此时不会断键盘连结
我是64位系统,进行深度思考,理解为什么出现第一次可以之后不可以的问题,帮我看看如何修改,确保修改后无论怎么操作都可以正常鼠标操作和键盘操作,不准动我的底层逻辑
(附件:我使用的dll:
// KeyboardInput.dll
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <thread>
#include <string>
#include <vector>
#include <iostream> // For to_wstring, if used in logging
#define PIPE_NAME L"\\\\.\\pipe\\UnityKeyboardPipe" // Must match Host
#define KEY_EVENT_BUFFER_SIZE 1024 // Not directly used for pipe buffer, but good constant
// This MUST match the Host's structure
struct PipeKeyMessage {
UINT uMsg;
WPARAM vkCode;
DWORD scanCode;
DWORD flags; // KBDLLHOOKSTRUCT flags
};
HANDLE hPipe = INVALID_HANDLE_VALUE;
std::thread pipeThread;
// bool pipeConnected = false; // Status can be inferred from hPipe and ConnectNamedPipe result
HWND g_unityMainWindow = NULL;
bool g_stopPipeThread = false; // For graceful shutdown
HANDLE g_hStopEvent = NULL; // Event for signaling thread to stop
// Function to log messages (using OutputDebugString)
void Log(const std::wstring& message) {
OutputDebugStringW((L"DLL_LOG: " + message + L"\n").c_str());
}
HWND FindActualUnityWindow() {
HWND foundHwnd = NULL;
EnumWindows([](HWND currentHwnd, LPARAM lParam) -> BOOL {
DWORD processId;
GetWindowThreadProcessId(currentHwnd, &processId);
if (processId == GetCurrentProcessId()) {
if (IsWindowVisible(currentHwnd)) { // Only consider visible windows
wchar_t className[256];
GetClassName(currentHwnd, className, 256);
// Common Unity class name (may vary, e.g. UnityContainerWndClass for embedded via -parentHWND)
if (wcsstr(className, L"UnityWndClass") != NULL || wcsstr(className, L"UnityContainer") != NULL) {
// Further check: Does it have children? Is it a top-level window (if not embedded)?
// For simplicity, we'll take the first one that matches.
// A more robust solution might check GetParent() if we expect it to be a child of a known host.
*reinterpret_cast<HWND*>(lParam) = currentHwnd;
Log(L"FindActualUnityWindow: Found Unity window with class: " + std::wstring(className) + L", HWND: " + std::to_wstring((uintptr_t)currentHwnd));
return FALSE; // Found
}
// Fallback: if no specific class found yet, store any visible window of the process
// This is less reliable.
// if (*reinterpret_cast<HWND*>(lParam) == NULL && GetWindowTextLength(currentHwnd) > 0) {
// *reinterpret_cast<HWND*>(lParam) = currentHwnd;
// }
}
}
return TRUE;
}, reinterpret_cast<LPARAM>(&foundHwnd));
if (!foundHwnd) {
Log(L"FindActualUnityWindow: Unity main window not found.");
}
return foundHwnd;
}
void SimulateKeyEvent(UINT uMsg, WPARAM vkCode, DWORD scanCode, DWORD originalFlags, HWND targetHwnd) {
if (!targetHwnd || !IsWindow(targetHwnd)) {
Log(L"SimulateKeyEvent - Target window invalid or destroyed.");
g_unityMainWindow = NULL; // Mark for re-search
return;
}
INPUT input[1] = {0};
input[0].type = INPUT_KEYBOARD;
input[0].ki.wVk = (WORD)vkCode;
input[0].ki.wScan = (WORD)scanCode;
input[0].ki.dwFlags = 0;
if (uMsg == WM_KEYUP || uMsg == WM_SYSKEYUP) {
input[0].ki.dwFlags |= KEYEVENTF_KEYUP;
}
if (originalFlags & LLKHF_EXTENDED) { // Check KBDLLHOOKSTRUCT's extended flag
input[0].ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
}
// If scan code is present and it's not a virtual key that implies its own scan code (like VK_PACKET)
// you might want to add KEYEVENTF_SCANCODE. For most VKs, this is handled.
// Some games are very particular about wScan vs wVk.
// if (scanCode != 0) {
// input[0].ki.dwFlags |= KEYEVENTF_SCANCODE;
// }
// Log((L"Simulating: uMsg=" + std::to_wstring(uMsg) + L" vk=" + std::to_wstring(vkCode) + L" scan=" + std::to_wstring(scanCode) + L" flags=" + std::to_wstring(originalFlags) + L" -> SendInputFlags=" + std::to_wstring(input[0].ki.dwFlags)).c_str());
UINT sent = SendInput(1, input, sizeof(INPUT));
if (sent == 0) {
Log(L"SendInput failed. Error: " + std::to_wstring(GetLastError()));
}
}
void PipeThreadProc() {
Log(L"PipeThreadProc started.");
hPipe = CreateNamedPipeW(
PIPE_NAME,
PIPE_ACCESS_INBOUND, // DLL only reads
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
1, // Only one host instance expected
sizeof(PipeKeyMessage) * 10, // Buffer size for pipe (e.g., 10 messages)
sizeof(PipeKeyMessage) * 10,
NMPWAIT_USE_DEFAULT_WAIT,
NULL);
if (hPipe == INVALID_HANDLE_VALUE) {
Log(L"CreateNamedPipeW failed. Error: " + std::to_wstring(GetLastError()));
return;
}
Log(L"Named pipe created. Waiting for connection...");
while (WaitForSingleObject(g_hStopEvent, 0) == WAIT_TIMEOUT) { // Loop until stop event is signaled
BOOL connected = ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
if (connected) {
Log(L"Pipe connected to host.");
if (g_unityMainWindow == NULL || !IsWindow(g_unityMainWindow)) { // Check if window is still valid
g_unityMainWindow = FindActualUnityWindow();
}
PipeKeyMessage km;
DWORD bytesRead;
while (WaitForSingleObject(g_hStopEvent, 0) == WAIT_TIMEOUT) {
BOOL success = ReadFile(hPipe, &km, sizeof(PipeKeyMessage), &bytesRead, NULL);
if (!success || bytesRead == 0) {
DWORD error = GetLastError();
if (error == ERROR_BROKEN_PIPE) {
Log(L"Pipe broken (host disconnected).");
} else if (error != ERROR_IO_PENDING) { // ERROR_IO_PENDING is not an error for overlapped, but we are synchronous
Log(L"ReadFile failed. Error: " + std::to_wstring(error));
}
DisconnectNamedPipe(hPipe);
Log(L"Disconnected from host. Waiting for new connection...");
break;
}
if (bytesRead != sizeof(PipeKeyMessage)) {
Log(L"ReadFile: Received incomplete message. Expected " + std::to_wstring(sizeof(PipeKeyMessage)) + L" got " + std::to_wstring(bytesRead));
// This is a more serious error, might indicate pipe corruption or mismatched structures.
// Consider breaking and re-establishing connection.
DisconnectNamedPipe(hPipe);
Log(L"Disconnected due to malformed message. Waiting for new connection...");
break;
}
// Log(L"Received KeyMessage: uMsg=" + std::to_wstring(km.uMsg) + L", vkCode=" + std::to_wstring(km.vkCode) + L", scanCode=" + std::to_wstring(km.scanCode) + L", flags=" + std::to_wstring(km.flags));
if (g_unityMainWindow == NULL || !IsWindow(g_unityMainWindow)) {
Log(L"Attempting to find Unity window again before simulating key...");
g_unityMainWindow = FindActualUnityWindow();
}
if (g_unityMainWindow) {
SimulateKeyEvent(km.uMsg, km.vkCode, km.scanCode, km.flags, g_unityMainWindow);
} else {
Log(L"Unity main window not found, cannot simulate key.");
}
}
} else {
DWORD error = GetLastError();
// ERROR_PIPE_LISTENING is normal if no client has connected yet.
// Don't log spam for this one unless it persists.
// if (error != ERROR_PIPE_LISTENING) {
// Log(L"ConnectNamedPipe failed. Error: " + std::to_wstring(error) + L". Retrying...");
// }
Sleep(100); // Wait before retrying connection
}
}
Log(L"PipeThreadProc stopping.");
if (hPipe != INVALID_HANDLE_VALUE) {
DisconnectNamedPipe(hPipe); // Ensure client is disconnected
CloseHandle(hPipe);
hPipe = INVALID_HANDLE_VALUE;
}
Log(L"PipeThreadProc finished.");
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(hModule);
Log(L"DLL_PROCESS_ATTACH");
g_hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // Manual-reset, initially non-signaled
if (g_hStopEvent == NULL) {
Log(L"Failed to create stop event.");
return FALSE; // Critical failure
}
// It's better to find window after pipe connection, as Unity might not be fully up yet.
// g_unityMainWindow = FindActualUnityWindow();
pipeThread = std::thread(PipeThreadProc);
// pipeThread.detach(); // No, make it joinable for clean shutdown
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
Log(L"DLL_PROCESS_DETACH");
if (g_hStopEvent != NULL) {
Log(L"Signaling pipe thread to stop.");
SetEvent(g_hStopEvent); // Signal thread to stop
}
if (pipeThread.joinable()) {
Log(L"Waiting for pipe thread to join...");
pipeThread.join(); // Wait for thread to finish
Log(L"Pipe thread joined.");
}
if (g_hStopEvent != NULL) {
CloseHandle(g_hStopEvent);
g_hStopEvent = NULL;
}
Log(L"DLL cleanup finished.");
break;
}
return TRUE;
}
最新发布