[PATCH 9/9] dinput: Read HID reports from the internal thread.

Rémi Bernon rbernon at codeweavers.com
Fri Aug 27 04:10:05 CDT 2021


Signed-off-by: Rémi Bernon <rbernon at codeweavers.com>
---

This series makes the new dinput(8) HID joystick usable for input, (at
least from the joy.cpl perspective).

It's a bit long but hopefully most patches are trivial, and it may be
interesting to have it for today's release.

 dlls/dinput/device.c         |   2 +
 dlls/dinput/device_private.h |   6 +
 dlls/dinput/dinput_main.c    |  36 ++++-
 dlls/dinput/joystick_hid.c   | 245 ++++++++++++++++++++++++++++++++++-
 4 files changed, 285 insertions(+), 4 deletions(-)

diff --git a/dlls/dinput/device.c b/dlls/dinput/device.c
index 25084f82a9c..6d4c630fb23 100644
--- a/dlls/dinput/device.c
+++ b/dlls/dinput/device.c
@@ -1725,6 +1725,8 @@ HRESULT direct_input_device_alloc( SIZE_T size, const IDirectInputDevice8WVtbl *
     InitializeCriticalSection( &This->crit );
     This->dinput = dinput;
     IDirectInput_AddRef( &dinput->IDirectInput7A_iface );
+    This->read_event = NULL;
+    This->read_callback = NULL;
 
     *out = This;
     return DI_OK;
diff --git a/dlls/dinput/device_private.h b/dlls/dinput/device_private.h
index 1811e0cc5a6..0e72ee77bf1 100644
--- a/dlls/dinput/device_private.h
+++ b/dlls/dinput/device_private.h
@@ -53,6 +53,8 @@ typedef struct
     UINT_PTR uAppData;
 } ActionMap;
 
+typedef HRESULT dinput_device_read_state( IDirectInputDevice8W *iface );
+
 /* Device implementation */
 typedef struct IDirectInputDeviceImpl IDirectInputDeviceImpl;
 struct IDirectInputDeviceImpl
@@ -84,6 +86,10 @@ struct IDirectInputDeviceImpl
     /* Action mapping */
     int                         num_actions; /* number of actions mapped */
     ActionMap                  *action_map;  /* array of mappings */
+
+    /* internal device file reading */
+    HANDLE                    read_event;
+    dinput_device_read_state *read_callback;
 };
 
 extern HRESULT direct_input_device_alloc( SIZE_T size, const IDirectInputDevice8WVtbl *vtbl, const GUID *guid,
diff --git a/dlls/dinput/dinput_main.c b/dlls/dinput/dinput_main.c
index 88ee1855675..cbedc9f0880 100644
--- a/dlls/dinput/dinput_main.c
+++ b/dlls/dinput/dinput_main.c
@@ -1289,6 +1289,10 @@ static LRESULT CALLBACK callwndproc_proc( int code, WPARAM wparam, LPARAM lparam
 static DWORD WINAPI hook_thread_proc(void *param)
 {
     static HHOOK kbd_hook, mouse_hook;
+    IDirectInputDeviceImpl *dev;
+    SIZE_T events_count = 0;
+    HANDLE events[128];
+    DWORD ret;
     MSG msg;
 
     di_em_win = CreateWindowW( di_em_win_w, di_em_win_w, 0, 0, 0, 0, 0,
@@ -1298,10 +1302,33 @@ static DWORD WINAPI hook_thread_proc(void *param)
     PeekMessageW( &msg, 0, 0, 0, PM_NOREMOVE );
     SetEvent(param);
 
-    while (GetMessageW( &msg, 0, 0, 0 ))
+    while ((ret = MsgWaitForMultipleObjectsEx( events_count, events, INFINITE, QS_ALLINPUT, 0 )) <= events_count)
     {
+        IDirectInputDevice8W *iface = NULL;
         UINT kbd_cnt = 0, mice_cnt = 0;
 
+        if (ret < events_count)
+        {
+            EnterCriticalSection( &dinput_hook_crit );
+            LIST_FOR_EACH_ENTRY( dev, &acquired_device_list, IDirectInputDeviceImpl, entry )
+            {
+                if (dev->read_event == events[ret])
+                {
+                    iface = &dev->IDirectInputDevice8W_iface;
+                    IDirectInputDevice8_AddRef( iface );
+                    break;
+                }
+            }
+            LeaveCriticalSection( &dinput_hook_crit );
+
+            if (iface)
+            {
+                dev->read_callback( iface );
+                IDirectInputDevice8_Release( iface );
+            }
+        }
+
+        if (!PeekMessageW( &msg, 0, 0, 0, PM_REMOVE )) continue;
         if (msg.message == WM_USER+0x10)
         {
             HANDLE finished_event = (HANDLE)msg.lParam;
@@ -1316,9 +1343,16 @@ static DWORD WINAPI hook_thread_proc(void *param)
                 break;
             }
 
+            events_count = 0;
             EnterCriticalSection( &dinput_hook_crit );
             kbd_cnt = list_count( &acquired_keyboard_list );
             mice_cnt = list_count( &acquired_mouse_list );
+            LIST_FOR_EACH_ENTRY( dev, &acquired_device_list, IDirectInputDeviceImpl, entry )
+            {
+                if (!dev->read_event || !dev->read_callback) continue;
+                if (events_count >= ARRAY_SIZE(events)) break;
+                events[events_count++] = dev->read_event;
+            }
             LeaveCriticalSection( &dinput_hook_crit );
 
             if (kbd_cnt && !kbd_hook)
diff --git a/dlls/dinput/joystick_hid.c b/dlls/dinput/joystick_hid.c
index ea0ab2b8f0d..82458570c82 100644
--- a/dlls/dinput/joystick_hid.c
+++ b/dlls/dinput/joystick_hid.c
@@ -141,6 +141,7 @@ struct hid_joystick
     DIJOYSTATE2 state;
 
     HANDLE device;
+    OVERLAPPED read_ovl;
     PHIDP_PREPARSED_DATA preparsed;
 
     DIDEVICEINSTANCEW instance;
@@ -152,6 +153,12 @@ struct hid_joystick
     HIDP_LINK_COLLECTION_NODE *collection_nodes;
     HIDP_BUTTON_CAPS *input_button_caps;
     HIDP_VALUE_CAPS *input_value_caps;
+
+    char *input_report_buf;
+    USAGE_AND_PAGE *usages_buf;
+    ULONG usages_count;
+
+    BYTE device_state_report_id;
 };
 
 static inline struct hid_joystick *impl_from_IDirectInputDevice8W( IDirectInputDevice8W *iface )
@@ -404,10 +411,14 @@ static ULONG WINAPI hid_joystick_Release( IDirectInputDevice8W *iface )
 
     if (!(ref = IDirectInputDevice2WImpl_Release( iface )))
     {
+        HeapFree( GetProcessHeap(), 0, tmp.usages_buf );
+        HeapFree( GetProcessHeap(), 0, tmp.input_report_buf );
         HeapFree( GetProcessHeap(), 0, tmp.input_value_caps );
         HeapFree( GetProcessHeap(), 0, tmp.input_button_caps );
         HeapFree( GetProcessHeap(), 0, tmp.collection_nodes );
         HidD_FreePreparsedData( tmp.preparsed );
+        CancelIoEx( tmp.device, &tmp.read_ovl );
+        CloseHandle( tmp.base.read_event );
         CloseHandle( tmp.device );
     }
 
@@ -568,6 +579,40 @@ static HRESULT WINAPI hid_joystick_SetProperty( IDirectInputDevice8W *iface, con
     return DI_OK;
 }
 
+static HRESULT WINAPI hid_joystick_Acquire( IDirectInputDevice8W *iface )
+{
+    struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
+    ULONG report_len = impl->caps.InputReportByteLength;
+    HRESULT hr;
+
+    TRACE( "iface %p.\n", iface );
+
+    if ((hr = IDirectInputDevice2WImpl_Acquire( iface )) != DI_OK) return hr;
+
+    memset( &impl->read_ovl, 0, sizeof(impl->read_ovl) );
+    impl->read_ovl.hEvent = impl->base.read_event;
+    if (ReadFile( impl->device, impl->input_report_buf, report_len, NULL, &impl->read_ovl ))
+        impl->base.read_callback( iface );
+
+    return DI_OK;
+}
+
+static HRESULT WINAPI hid_joystick_Unacquire( IDirectInputDevice8W *iface )
+{
+    struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
+    HRESULT hr;
+    BOOL ret;
+
+    TRACE( "iface %p.\n", iface );
+
+    if ((hr = IDirectInputDevice2WImpl_Unacquire( iface )) != DI_OK) return hr;
+
+    ret = CancelIoEx( impl->device, &impl->read_ovl );
+    if (!ret) WARN( "CancelIoEx failed, last error %u\n", GetLastError() );
+
+    return DI_OK;
+}
+
 static HRESULT WINAPI hid_joystick_GetDeviceState( IDirectInputDevice8W *iface, DWORD len, void *ptr )
 {
     struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
@@ -665,8 +710,8 @@ static const IDirectInputDevice8WVtbl hid_joystick_vtbl =
     hid_joystick_EnumObjects,
     hid_joystick_GetProperty,
     hid_joystick_SetProperty,
-    IDirectInputDevice2WImpl_Acquire,
-    IDirectInputDevice2WImpl_Unacquire,
+    hid_joystick_Acquire,
+    hid_joystick_Unacquire,
     hid_joystick_GetDeviceState,
     IDirectInputDevice2WImpl_GetDeviceData,
     IDirectInputDevice2WImpl_SetDataFormat,
@@ -695,6 +740,183 @@ static const IDirectInputDevice8WVtbl hid_joystick_vtbl =
     IDirectInputDevice8WImpl_GetImageInfo,
 };
 
+struct parse_device_state_params
+{
+    DIJOYSTATE2 old_state;
+    DWORD time;
+    DWORD seq;
+};
+
+static BOOL check_device_state_button( struct hid_joystick *impl, struct hid_caps *caps,
+                                       DIDEVICEOBJECTINSTANCEW *instance, void *data )
+{
+    IDirectInputDevice8W *iface = &impl->base.IDirectInputDevice8W_iface;
+    struct parse_device_state_params *params = data;
+    DWORD i = DIDFT_GETINSTANCE( instance->dwType );
+
+    if (!(instance->dwType & DIDFT_BUTTON))
+        FIXME( "unexpected object type %#x, expected DIDFT_BUTTON\n", instance->dwType );
+    else if (params->old_state.rgbButtons[i] != impl->state.rgbButtons[i])
+        queue_event( iface, instance->dwType, impl->state.rgbButtons[i], params->time, params->seq );
+
+    return DIENUM_CONTINUE;
+}
+
+static LONG sign_extend( ULONG value, const HIDP_VALUE_CAPS *caps )
+{
+    UINT sign = 1 << (caps->BitSize - 1);
+    if (sign <= 1 || caps->LogicalMin >= 0) return value;
+    return value - ((value & sign) << 1);
+}
+
+static LONG scale_value( ULONG value, const HIDP_VALUE_CAPS *caps, LONG min, LONG max )
+{
+    ULONG bit_max = (1 << caps->BitSize) - 1;
+    LONG tmp = sign_extend( value, caps );
+
+    /* xinput HID gamepad have bogus logical value range, let's use the bit range instead */
+    if (caps->LogicalMin == 0 && caps->LogicalMax == -1) return min + MulDiv( tmp, max - min, bit_max );
+    if (caps->LogicalMin > tmp || caps->LogicalMax < tmp) return -1; /* invalid / null value */
+    return min + MulDiv( tmp - caps->LogicalMin, max - min, caps->LogicalMax - caps->LogicalMin );
+}
+
+static BOOL read_device_state_value( struct hid_joystick *impl, struct hid_caps *caps,
+                                     DIDEVICEOBJECTINSTANCEW *instance, void *data )
+{
+    IDirectInputDevice8W *iface = &impl->base.IDirectInputDevice8W_iface;
+    ULONG logical_value, report_len = impl->caps.InputReportByteLength;
+    struct parse_device_state_params *params = data;
+    char *report_buf = impl->input_report_buf;
+    HIDP_VALUE_CAPS *value_caps = caps->value;
+    NTSTATUS status;
+    LONG value;
+
+    if (!(instance->dwType & (DIDFT_POV | DIDFT_AXIS)))
+        FIXME( "unexpected object type %#x, expected DIDFT_POV | DIDFT_AXIS\n", instance->dwType );
+    else
+    {
+        status = HidP_GetUsageValue( HidP_Input, instance->wUsagePage, 0, instance->wUsage,
+                                     &logical_value, impl->preparsed, report_buf, report_len );
+        if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_GetUsageValue %04x:%04x returned %#x\n",
+                                                 instance->wUsagePage, instance->wUsage, status );
+        value = scale_value( logical_value, value_caps, value_caps->PhysicalMin, value_caps->PhysicalMax );
+
+        switch (instance->dwOfs)
+        {
+        case DIJOFS_X:
+            if (impl->state.lX == value) break;
+            impl->state.lX = value;
+            queue_event( iface, instance->dwType, value, params->time, params->seq );
+            break;
+        case DIJOFS_Y:
+            if (impl->state.lY == value) break;
+            impl->state.lY = value;
+            queue_event( iface, instance->dwType, value, params->time, params->seq );
+            break;
+        case DIJOFS_Z:
+            if (impl->state.lZ == value) break;
+            impl->state.lZ = value;
+            queue_event( iface, instance->dwType, value, params->time, params->seq );
+            break;
+        case DIJOFS_RX:
+            if (impl->state.lRx == value) break;
+            impl->state.lRx = value;
+            queue_event( iface, instance->dwType, value, params->time, params->seq );
+            break;
+        case DIJOFS_RY:
+            if (impl->state.lRy == value) break;
+            impl->state.lRy = value;
+            queue_event( iface, instance->dwType, value, params->time, params->seq );
+            break;
+        case DIJOFS_RZ:
+            if (impl->state.lRz == value) break;
+            impl->state.lRz = value;
+            queue_event( iface, instance->dwType, value, params->time, params->seq );
+            break;
+        case DIJOFS_POV( 0 ):
+            if (impl->state.rgdwPOV[0] == value) break;
+            impl->state.rgdwPOV[0] = value;
+            queue_event( iface, instance->dwType, value, params->time, params->seq );
+            break;
+        default:
+            FIXME( "unimplemented offset %#x.\n", instance->dwOfs );
+            break;
+        }
+    }
+
+    return DIENUM_CONTINUE;
+}
+
+static HRESULT hid_joystick_read_state( IDirectInputDevice8W *iface )
+{
+    static const DIPROPHEADER filter =
+    {
+        .dwSize = sizeof(filter),
+        .dwHeaderSize = sizeof(filter),
+        .dwHow = DIPH_DEVICE,
+    };
+    struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
+    ULONG i, count, report_len = impl->caps.InputReportByteLength;
+    struct parse_device_state_params params = {0};
+    char *report_buf = impl->input_report_buf;
+    USAGE_AND_PAGE *usages;
+    NTSTATUS status;
+    BOOL ret;
+
+    ret = GetOverlappedResult( impl->device, &impl->read_ovl, &count, FALSE );
+    if (!ret) WARN( "ReadFile failed, error %u\n", GetLastError() );
+    else if (TRACE_ON(dinput))
+    {
+        TRACE( "read size %u report:\n", count );
+        for (i = 0; i < report_len;)
+        {
+            char buffer[256], *buf = buffer;
+            buf += sprintf(buf, "%08x ", i);
+            do
+            {
+                buf += sprintf(buf, " %02x", (BYTE)report_buf[i] );
+            } while (++i % 16 && i < report_len);
+            TRACE("%s\n", buffer);
+        }
+    }
+
+    do
+    {
+        count = impl->usages_count;
+        memset( impl->usages_buf, 0, count * sizeof(*impl->usages_buf) );
+        status = HidP_GetUsagesEx( HidP_Input, 0, impl->usages_buf, &count,
+                                   impl->preparsed, report_buf, report_len );
+        if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_GetUsagesEx returned %#x\n", status );
+
+        if (report_buf[0] == impl->device_state_report_id)
+        {
+            params.old_state = impl->state;
+            params.time = GetCurrentTime();
+            params.seq = impl->base.dinput->evsequence++;
+
+            memset( impl->state.rgbButtons, 0, sizeof(impl->state.rgbButtons) );
+            while (count--)
+            {
+                usages = impl->usages_buf + count;
+                if (usages->UsagePage != HID_USAGE_PAGE_BUTTON)
+                    FIXME( "unimplemented usage page %x.\n", usages->UsagePage );
+                else if (usages->Usage >= 128)
+                    FIXME( "ignoring extraneous button %d.\n", usages->Usage );
+                else
+                    impl->state.rgbButtons[usages->Usage - 1] = 0x80;
+            }
+
+            enum_value_objects( impl, &filter, DIDFT_ALL, read_device_state_value, &params );
+            enum_button_objects( impl, &filter, DIDFT_ALL, check_device_state_button, &params );
+        }
+
+        memset( &impl->read_ovl, 0, sizeof(impl->read_ovl) );
+        impl->read_ovl.hEvent = impl->base.read_event;
+    } while (ReadFile( impl->device, report_buf, report_len, &count, &impl->read_ovl ));
+
+    return DI_OK;
+}
+
 static BOOL hid_joystick_device_try_open( UINT32 handle, const WCHAR *path, HANDLE *device,
                                           PHIDP_PREPARSED_DATA *preparsed, HIDD_ATTRIBUTES *attrs,
                                           HIDP_CAPS *caps, DIDEVICEINSTANCEW *instance, DWORD version )
@@ -703,7 +925,7 @@ static BOOL hid_joystick_device_try_open( UINT32 handle, const WCHAR *path, HAND
     HANDLE device_file;
 
     device_file = CreateFileW( path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
-                               NULL, OPEN_EXISTING, 0, 0 );
+                               NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 0 );
     if (device_file == INVALID_HANDLE_VALUE) return FALSE;
 
     if (!HidD_GetPreparsedData( device_file, &preparsed_data )) goto failed;
@@ -841,6 +1063,11 @@ static BOOL init_objects( struct hid_joystick *impl, struct hid_caps *caps,
     if (instance->dwType & DIDFT_AXIS) impl->dev_caps.dwAxes++;
     if (instance->dwType & DIDFT_POV) impl->dev_caps.dwPOVs++;
 
+    if (!impl->device_state_report_id)
+        impl->device_state_report_id = instance->wReportId;
+    else if (impl->device_state_report_id != instance->wReportId)
+        FIXME( "multiple device state reports found!\n" );
+
     return DIENUM_CONTINUE;
 }
 
@@ -890,8 +1117,10 @@ static HRESULT hid_joystick_create_device( IDirectInputImpl *dinput, const GUID
     DIDATAFORMAT *format = NULL;
     HIDP_BUTTON_CAPS *buttons;
     HIDP_VALUE_CAPS *values;
+    USAGE_AND_PAGE *usages;
     DWORD size, index;
     NTSTATUS status;
+    char *buffer;
     HRESULT hr;
 
     TRACE( "dinput %p, guid %s, out %p\n", dinput, debugstr_guid( guid ), out );
@@ -911,6 +1140,8 @@ static HRESULT hid_joystick_create_device( IDirectInputImpl *dinput, const GUID
     if (FAILED(hr)) return hr;
     impl->base.crit.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": hid_joystick.base.crit");
     impl->base.dwCoopLevel = DISCL_NONEXCLUSIVE | DISCL_BACKGROUND;
+    impl->base.read_event = CreateEventA( NULL, FALSE, FALSE, NULL );
+    impl->base.read_callback = hid_joystick_read_state;
 
     hr = hid_joystick_device_open( -1, &instance, impl->device_path, &impl->device, &impl->preparsed,
                                    &attrs, &impl->caps, dinput->dwVersion );
@@ -932,6 +1163,14 @@ static HRESULT hid_joystick_create_device( IDirectInputImpl *dinput, const GUID
     if (!(values = HeapAlloc( GetProcessHeap(), 0, size ))) goto failed;
     impl->input_value_caps = values;
 
+    size = impl->caps.InputReportByteLength;
+    if (!(buffer = HeapAlloc( GetProcessHeap(), 0, size ))) goto failed;
+    impl->input_report_buf = buffer;
+    impl->usages_count = HidP_MaxUsageListLength( HidP_Input, 0, impl->preparsed );
+    size = impl->usages_count * sizeof(USAGE_AND_PAGE);
+    if (!(usages = HeapAlloc( GetProcessHeap(), 0, size ))) goto failed;
+    impl->usages_buf = usages;
+
     size = impl->caps.NumberLinkCollectionNodes;
     status = HidP_GetLinkCollectionNodes( nodes, &size, impl->preparsed );
     if (status != HIDP_STATUS_SUCCESS) goto failed;
-- 
2.33.0




More information about the wine-devel mailing list