所以我有一个程序使用多个键盘作为使用原始输入的输入,我想弄乱每个键盘的大写锁定、滚动锁定和数字锁定的指示灯。
所以我的方法是使用
RegisterRawInputDevices
标志对键盘进行 RIDEV_DEVNOTIFY
。然后当我收到带有 WM_INPUT_DEVICE_CHANGE
wParam 的 GIDC_ARRIVAL
消息时。我会像 HANDLE hDevice = (HANDLE)Message.lParam;
一样保存 lParam,然后当我得到 WM_INPUT
时,我可以使用 DeviceIoControl
和来自 RAWINPUT
的 GetRawInputData
结构中的句柄对按键进行操作,以扰乱指示灯。 l参数。然而 DeviceIoControl
不喜欢 header.hDevice
结构中的 RAWINPUT
。我如何在每个键盘上使用 DeviceIoControl
和原始输入手柄?
这是一个程序,演示了上述尝试使每个键盘的大写锁定指示灯亮起
#ifndef NOMINMAX
#define NOMINMAX
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
//windows
#include <windows.h>
#include <dbt.h>
#include <Ntddkbd.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
typedef uint8_t u8;
typedef uint32_t u32;
typedef int64_t s64;
typedef float f32;
#define DEFAULT_SCREEN_WIDTH 1280
#define DEFAULT_SCREEN_HEIGHT 720
#define MINIMUM_SCREEN_WIDTH 300
#define MINIMUM_SCREEN_HEIGHT 300
typedef struct GameState
{
u8 hwIsRunning;
} GameState;
typedef struct Renderer
{
//ScreenGraphics State
u8 hwIsMinimized;
//Win32 Screen Variables
u32 dwScreenWidth = DEFAULT_SCREEN_WIDTH;
u32 dwScreenHeight = DEFAULT_SCREEN_HEIGHT;
HWND MainWindowHandle;
const char *szWindowName = "FPS Camera Basic";
} Renderer;
Renderer sRENDERER;
GameState sGAMESTATE;
u32 max( u32 a, u32 b )
{
return a > b ? a : b;
}
int logWindowsError(const char* msg)
{
LPVOID lpMsgBuf;
DWORD dw = GetLastError();
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, dw, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), (LPTSTR)&lpMsgBuf, 0, NULL );
OutputDebugStringA( msg );
OutputDebugStringA( (LPCTSTR)lpMsgBuf );
LocalFree( lpMsgBuf );
return -1;
}
inline
void CloseProgram()
{
sGAMESTATE.hwIsRunning = 0;
}
LRESULT CALLBACK
Win32MainWindowCallback(
HWND Window,
UINT Message,
WPARAM WParam,
LPARAM LParam)
{
LRESULT Result = 0;
switch (Message)
{
case WM_SYSCHAR:
break;
case WM_SIZE:
{
u32 dwTempScreenWidth = LOWORD( LParam );
u32 dwTempScreenHeight = HIWORD( LParam );
if( WParam == SIZE_MINIMIZED || dwTempScreenWidth == 0 || dwTempScreenHeight == 0 )
{
sRENDERER.hwIsMinimized = 1;
}
else
{
sRENDERER.hwIsMinimized = 0;
}
sRENDERER.dwScreenWidth = max( 1, dwTempScreenWidth );
sRENDERER.dwScreenHeight = max( 1, dwTempScreenHeight );
}break;
case WM_GETMINMAXINFO:
{
LPMINMAXINFO lpMMI = (LPMINMAXINFO)LParam;
lpMMI->ptMinTrackSize.x = MINIMUM_SCREEN_WIDTH;
lpMMI->ptMinTrackSize.y = MINIMUM_SCREEN_HEIGHT;
}break;
case WM_CLOSE: //when user clicks on the X button on the window
{
CloseProgram();
} break;
case WM_ACTIVATE:
{
switch(WParam)
{
//WM_MOUSEACTIVATE
case WA_ACTIVE:
case WA_CLICKACTIVE:
case WA_INACTIVE:
default:
{
break;
}
}
} break;
default:
Result = DefWindowProc( Window, Message, WParam, LParam ); //call windows to handle default behavior of things we don't handle
}
return Result;
}
inline
void InitRawInput( HWND WindowHandle )
{
RAWINPUTDEVICE Rid[2];
Rid[0].usUsagePage = (USHORT) 0x01;
Rid[0].usUsage = (USHORT) 0x02;
Rid[0].dwFlags = RIDEV_INPUTSINK|RIDEV_DEVNOTIFY; //RIDEV_INPUTSINK: If set, this enables the caller to receive the input even when the caller is not in the foreground
Rid[0].hwndTarget = WindowHandle;
Rid[1].usUsagePage = (USHORT) 0x01;
Rid[1].usUsage = (USHORT) 0x06;
Rid[1].dwFlags = RIDEV_INPUTSINK | RIDEV_DEVNOTIFY | RIDEV_NOLEGACY; //RIDEV_INPUTSINK: If set, this enables the caller to receive the input even when the caller is not in the foreground
Rid[1].hwndTarget = WindowHandle;
RegisterRawInputDevices( Rid, 2, sizeof( Rid[0] ) );
}
inline
void InitWin32Window()
{
WNDCLASSEX WindowClass;
WindowClass.cbSize = sizeof( WNDCLASSEX );
WindowClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW; //https://devblogs.microsoft.com/oldnewthing/20060601-06/?p=31003
WindowClass.lpfnWndProc = Win32MainWindowCallback;
WindowClass.cbClsExtra = 0;
WindowClass.cbWndExtra = 0;
WindowClass.hInstance = GetModuleHandle( NULL );
WindowClass.hIcon = LoadIcon( 0, IDI_APPLICATION ); //IDI_APPLICATION: Default application icon, 0 means use a default Icon
WindowClass.hCursor = LoadCursor( 0, IDC_ARROW ); //IDC_ARROW: Standard arrow, 0 means used a predefined Cursor
WindowClass.hbrBackground = NULL;
WindowClass.lpszMenuName = NULL; // No menu
WindowClass.lpszClassName = "WindowTestClass"; //name our class
WindowClass.hIconSm = NULL; //can also do default Icon here? will NULL be default automatically?
if ( !RegisterClassEx( &WindowClass ) )
{
logWindowsError( "Failed to Register Window Class:\n" );
sRENDERER.MainWindowHandle = 0;
return;
}
HWND WindowHandle = CreateWindowEx( 0, WindowClass.lpszClassName, sRENDERER.szWindowName,
WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, sRENDERER.dwScreenWidth, sRENDERER.dwScreenHeight, //if fullscreen get monitor width and height
0, 0, WindowClass.hInstance, NULL );
if ( !WindowHandle )
{
logWindowsError( "Failed to Instantiate Window Class:\n" );
sRENDERER.MainWindowHandle = 0;
return;
}
sRENDERER.MainWindowHandle = WindowHandle;
InitRawInput( sRENDERER.MainWindowHandle );
}
inline
void InitStartingGameState()
{
sGAMESTATE.hwIsRunning = 1;
}
u32 dwNumHandles = 0;
typedef struct DoubleHandle
{
HANDLE hHandle0;
HANDLE hHandle1;
} DoubleHandle;
DoubleHandle handles[32];
//Use subsystem console when compiling
int main()
{
InitStartingGameState();
InitWin32Window();
if( !sRENDERER.MainWindowHandle )
{
return -1;
}
while( sGAMESTATE.hwIsRunning )
{
MSG Message;
while( PeekMessage( &Message, 0, 0, 0, PM_REMOVE ) )
{
switch( Message.message )
{
case WM_QUIT:
{
CloseProgram();
break;
}
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_KEYDOWN:
case WM_KEYUP:
{
break;
}
case WM_INPUT_DEVICE_CHANGE:
{
HANDLE hDevice = (HANDLE)Message.lParam;
RID_DEVICE_INFO deviceInfo;
deviceInfo.cbSize = sizeof(RID_DEVICE_INFO);
u32 dwSize = sizeof(RID_DEVICE_INFO);
GetRawInputDeviceInfo(hDevice,RIDI_DEVICEINFO,&deviceInfo,&dwSize);
u32 dwNameSize = 0;
u32 res = GetRawInputDeviceInfo(hDevice, RIDI_DEVICENAME, nullptr, &dwNameSize);
char *pName = new char[dwNameSize+1];
res = GetRawInputDeviceInfo(hDevice, RIDI_DEVICENAME, pName, &dwNameSize);
pName[dwNameSize] = 0;
switch(Message.wParam)
{
case GIDC_ARRIVAL:
{
switch(deviceInfo.dwType)
{
case RIM_TYPEKEYBOARD:
{
u32 dwKeyBoard = dwNumHandles++;
handles[dwKeyBoard].hHandle0 = hDevice;
} break;
default:
{
} break;
}
printf("GIDC_ARRIVAL %p %d %s\n",hDevice,deviceInfo.dwType,pName);
} break;
case GIDC_REMOVAL:
{
switch(deviceInfo.dwType)
{
case RIM_TYPEKEYBOARD:
{
} break;
default:
{
} break;
}
printf("GIDC_REMOVAL %p %d %s\n",hDevice,deviceInfo.dwType,pName);
} break;
default:
{
} break;
}
delete [] pName;
} break;
case WM_INPUT:
{
UINT dwSize = sizeof( RAWINPUT );
static BYTE lpb[sizeof( RAWINPUT )];
GetRawInputData( (HRAWINPUT)Message.lParam, RID_INPUT, lpb, &dwSize, sizeof( RAWINPUTHEADER ) );
RAWINPUT* raw = (RAWINPUT*)lpb;
if(raw->header.dwType == RIM_TYPEKEYBOARD )
{
bool bIsUp = (raw->data.keyboard.Flags & RI_KEY_BREAK) != 0;
u32 dwScanCode = raw->data.keyboard.MakeCode;
//capslock
if( 0x3A == dwScanCode)
{
for( u32 dwKeyBoard = 0; dwKeyBoard < dwNumHandles; ++dwKeyBoard )
{
if( hDevice != handles[dwKeyBoard].hHandle0)
{
KEYBOARD_INDICATOR_PARAMETERS InputBuffer; // Input buffer for DeviceIoControl
KEYBOARD_INDICATOR_PARAMETERS OutputBuffer; // Output buffer for DeviceIoControl
UINT LedFlagsMask;
BOOL Toggle;
ULONG DataLength = sizeof(KEYBOARD_INDICATOR_PARAMETERS);
ULONG ReturnedLength; // Number of bytes returned in output buffer
InputBuffer.UnitId = 0;
OutputBuffer.UnitId = 0;
UINT LedFlag = KEYBOARD_CAPS_LOCK_ON;
if (DeviceIoControl(handles[dwKeyBoard].hHandle0, IOCTL_KEYBOARD_QUERY_INDICATORS,
&InputBuffer, DataLength,
&OutputBuffer, DataLength,
&ReturnedLength, NULL))
{
LedFlagsMask = (OutputBuffer.LedFlags & (~LedFlag));
Toggle = (OutputBuffer.LedFlags & LedFlag);
Toggle ^= 1;
InputBuffer.LedFlags = (LedFlagsMask | (LedFlag * Toggle));
DeviceIoControl(handles[dwKeyBoard].hHandle0, IOCTL_KEYBOARD_SET_INDICATORS,
&InputBuffer, DataLength,
NULL, 0, &ReturnedLength, NULL);
}
else
{
logWindowsError("failed to get indicators\n");
}
}
}
}
}
else
{
TranslateMessage( &Message );
DispatchMessage( &Message );
}
break;
}
default:
{
TranslateMessage( &Message );
DispatchMessage( &Message );
break;
}
}
}
}
return 0;
}
RAWINPUT.header.hDevice
不能直接与DeviceIoControl
一起使用。
您必须使用
GetRawInputDeviceInfo
调用 RIDI_DEVICENAME
才能获取设备接口路径。之后需要调用CreateFile
打开该接口并获取其句柄。此手柄可与DeviceIoControl
一起使用。
这是来自我的测试仓库的一些代码:
inline ScopedHandle OpenDeviceInterface(const std::string& deviceInterface, bool readOnly = false)
{
DWORD desired_access = readOnly ? 0 : (GENERIC_WRITE | GENERIC_READ);
DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE;
HANDLE handle = ::CreateFileW(utf8::widen(deviceInterface).c_str(), desired_access, share_mode, 0, OPEN_EXISTING, 0, 0);
return ScopedHandle(handle);
}
bool RawInputDevice::QueryRawInputDeviceInfo()
{
DCHECK(IsValidHandle(m_Handle));
UINT size = 0;
UINT result = ::GetRawInputDeviceInfoW(m_Handle, RIDI_DEVICENAME, nullptr, &size);
if (result == static_cast<UINT>(-1))
{
//PLOG(ERROR) << "GetRawInputDeviceInfo() failed";
return false;
}
DCHECK_EQ(0u, result);
std::wstring buffer(size, 0);
result = ::GetRawInputDeviceInfoW(m_Handle, RIDI_DEVICENAME, buffer.data(), &size);
if (result == static_cast<UINT>(-1))
{
//PLOG(ERROR) << "GetRawInputDeviceInfo() failed";
return false;
}
DCHECK_EQ(size, result);
m_InterfacePath = utf8::narrow(buffer);
m_InterfaceHandle = OpenDeviceInterface(m_InterfacePath);
if (!IsValidHandle(m_InterfaceHandle.get()))
{
/* System devices, such as keyboards and mice, cannot be opened in
read-write mode, because the system takes exclusive control over
them. This is to prevent keyloggers. However, feature reports
can still be sent and received. Retry opening the device, but
without read/write access. */
m_InterfaceHandle = OpenDeviceInterface(m_InterfacePath, true);
if (IsValidHandle(m_InterfaceHandle.get()))
m_IsReadOnlyInterface = true;
}
return !m_InterfacePath.empty();
}
bool RawInputDeviceKeyboard::ExtendedKeyboardInfo::QueryInfo(const ScopedHandle& interfaceHandle)
{
// https://docs.microsoft.com/windows/win32/api/ntddkbd/ns-ntddkbd-keyboard_extended_attributes
KEYBOARD_EXTENDED_ATTRIBUTES extended_attributes{ KEYBOARD_EXTENDED_ATTRIBUTES_STRUCT_VERSION_1 };
DWORD len = 0;
if (!DeviceIoControl(interfaceHandle.get(), IOCTL_KEYBOARD_QUERY_EXTENDED_ATTRIBUTES, nullptr, 0, &extended_attributes, sizeof(extended_attributes), &len, nullptr))
return false;
DCHECK_EQ(len, sizeof(extended_attributes));
FormFactor = extended_attributes.FormFactor;
KeyType = extended_attributes.IETFLanguageTagIndex;
PhysicalLayout = extended_attributes.PhysicalLayout;
VendorSpecificPhysicalLayout = extended_attributes.VendorSpecificPhysicalLayout;
IETFLanguageTagIndex = extended_attributes.IETFLanguageTagIndex;
ImplementedInputAssistControls = extended_attributes.ImplementedInputAssistControls;
return true;
}
我想弄乱每个键盘的大写锁定、滚动锁定和数字锁定的指示灯
如果没有
此处描述的某种技巧,您无法打开键盘手柄进行书写并从用户模式执行
DeviceIoControl(..., IOCTL_KEYBOARD_SET_INDICATORS, ...)
。它没有记录,可能随时损坏。
额外的闲聊:Windows 将设备组织为虚拟文件,几乎就像 Linux 在
/dev/
文件系统路径下所做的那样。这些虚拟文件通过通常的 API 是不可见的。但是有WinObj 工具存在,它使用未记录的 API,并且可以显示内部工作原理。它可以帮助您了解 Windows 的底层工作原理。