如何在原始输入设备句柄上使用DeviceIoControl

问题描述 投票:0回答:1

所以我有一个程序使用多个键盘作为使用原始输入的输入,我想弄乱每个键盘的大写锁定、滚动锁定和数字锁定的指示灯。

所以我的方法是使用

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;
}
c winapi raw-input deviceiocontrol
1个回答
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 的底层工作原理。

© www.soinside.com 2019 - 2024. All rights reserved.