/* * Copyright 2022 RĂ©mi Bernon for CodeWeavers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #define DIRECTINPUT_VERSION 0x0800 #include #include #include #include "ntstatus.h" #define WIN32_NO_STATUS #include "windef.h" #include "winbase.h" #define COBJMACROS #include "dinput.h" #include "dinputd.h" #include "devguid.h" #include "dbt.h" #include "unknwn.h" #include "winstring.h" #include "wine/hid.h" #include "dinput_test.h" #include "initguid.h" #include "roapi.h" #include "weakreference.h" #define WIDL_using_Windows_Foundation #define WIDL_using_Windows_Foundation_Collections #include "windows.foundation.h" #define WIDL_using_Windows_Gaming_Input #define WIDL_using_Windows_Gaming_Input_Custom #include "windows.gaming.input.custom.h" #define MAKE_FUNC(f) static typeof(f) *p ## f MAKE_FUNC( RoGetActivationFactory ); MAKE_FUNC( RoInitialize ); MAKE_FUNC( WindowsCreateString ); MAKE_FUNC( WindowsDeleteString ); #undef MAKE_FUNC static BOOL load_combase_functions(void) { HMODULE combase = GetModuleHandleW( L"combase.dll" ); #define LOAD_FUNC(m, f) if (!(p ## f = (void *)GetProcAddress( m, #f ))) goto failed; LOAD_FUNC( combase, RoGetActivationFactory ); LOAD_FUNC( combase, RoInitialize ); LOAD_FUNC( combase, WindowsCreateString ); LOAD_FUNC( combase, WindowsDeleteString ); #undef LOAD_FUNC return TRUE; failed: win_skip("Failed to load combase.dll functions, skipping tests\n"); return FALSE; } static DWORD wait_for_events( DWORD count, HANDLE *events, DWORD timeout ) { DWORD ret, end = GetTickCount() + timeout; MSG msg; while ((ret = MsgWaitForMultipleObjects( count, events, FALSE, timeout, QS_ALLINPUT )) <= count) { while (PeekMessageW( &msg, 0, 0, 0, PM_REMOVE )) { TranslateMessage( &msg ); DispatchMessageW( &msg ); } if (ret < count) return ret; if (timeout == INFINITE) continue; if (end <= GetTickCount()) timeout = 0; else timeout = end - GetTickCount(); } ok( ret == WAIT_TIMEOUT, "MsgWaitForMultipleObjects returned %#lx\n", ret ); return ret; } #define check_interface( a, b, c ) check_interface_( __LINE__, a, b, c ) static void check_interface_( unsigned int line, void *iface_ptr, REFIID iid, BOOL supported ) { IUnknown *iface = iface_ptr; HRESULT hr, expected; IUnknown *unk; expected = supported ? S_OK : E_NOINTERFACE; hr = IUnknown_QueryInterface( iface, iid, (void **)&unk ); ok_ (__FILE__, line)( hr == expected, "got hr %#lx, expected %#lx.\n", hr, expected ); if (SUCCEEDED(hr)) IUnknown_Release( unk ); } static BOOL test_input_lost( DWORD version ) { #include "psh_hid_macros.h" static const unsigned char report_desc[] = { USAGE_PAGE(1, HID_USAGE_PAGE_GENERIC), USAGE(1, HID_USAGE_GENERIC_JOYSTICK), COLLECTION(1, Application), USAGE(1, HID_USAGE_GENERIC_JOYSTICK), COLLECTION(1, Physical), USAGE_PAGE(1, HID_USAGE_PAGE_BUTTON), USAGE_MINIMUM(1, 1), USAGE_MAXIMUM(1, 6), LOGICAL_MINIMUM(1, 0), LOGICAL_MAXIMUM(1, 1), PHYSICAL_MINIMUM(1, 0), PHYSICAL_MAXIMUM(1, 1), REPORT_SIZE(1, 1), REPORT_COUNT(1, 8), INPUT(1, Data|Var|Abs), END_COLLECTION, END_COLLECTION, }; #include "pop_hid_macros.h" static const HIDP_CAPS hid_caps = { .InputReportByteLength = 1, }; static const DIPROPDWORD buffer_size = { .diph = { .dwHeaderSize = sizeof(DIPROPHEADER), .dwSize = sizeof(DIPROPDWORD), .dwHow = DIPH_DEVICE, .dwObj = 0, }, .dwData = UINT_MAX, }; DIDEVICEINSTANCEW devinst = {.dwSize = sizeof(DIDEVICEINSTANCEW)}; DIDEVICEOBJECTDATA objdata[32] = {{0}}; WCHAR cwd[MAX_PATH], tempdir[MAX_PATH]; IDirectInputDevice8W *device = NULL; ULONG ref, count, size; DIJOYSTATE2 state; HRESULT hr; winetest_push_context( "%#lx", version ); GetCurrentDirectoryW( ARRAY_SIZE(cwd), cwd ); GetTempPathW( ARRAY_SIZE(tempdir), tempdir ); SetCurrentDirectoryW( tempdir ); cleanup_registry_keys(); if (!dinput_driver_start( report_desc, sizeof(report_desc), &hid_caps, NULL, 0 )) goto done; if (FAILED(hr = dinput_test_create_device( version, &devinst, &device ))) goto done; hr = IDirectInputDevice8_SetDataFormat( device, &c_dfDIJoystick2 ); ok( hr == DI_OK, "SetDataFormat returned %#lx\n", hr ); hr = IDirectInputDevice8_SetCooperativeLevel( device, 0, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND ); ok( hr == DI_OK, "SetCooperativeLevel returned %#lx\n", hr ); hr = IDirectInputDevice8_SetProperty( device, DIPROP_BUFFERSIZE, &buffer_size.diph ); ok( hr == DI_OK, "SetProperty returned %#lx\n", hr ); hr = IDirectInputDevice8_Acquire( device ); ok( hr == DI_OK, "Acquire returned %#lx\n", hr ); hr = IDirectInputDevice8_GetDeviceState( device, sizeof(state), &state ); ok( hr == DI_OK, "GetDeviceState returned %#lx\n", hr ); size = version < 0x0800 ? sizeof(DIDEVICEOBJECTDATA_DX3) : sizeof(DIDEVICEOBJECTDATA); count = 1; hr = IDirectInputDevice8_GetDeviceData( device, size, objdata, &count, DIGDD_PEEK ); ok( hr == DI_OK, "GetDeviceData returned %#lx\n", hr ); ok( count == 0, "got %lu expected 0\n", count ); pnp_driver_stop(); hr = IDirectInputDevice8_GetDeviceState( device, sizeof(state), &state ); ok( hr == DIERR_INPUTLOST, "GetDeviceState returned %#lx\n", hr ); hr = IDirectInputDevice8_GetDeviceState( device, sizeof(state), &state ); ok( hr == DIERR_INPUTLOST, "GetDeviceState returned %#lx\n", hr ); hr = IDirectInputDevice8_GetDeviceData( device, size, objdata, &count, DIGDD_PEEK ); ok( hr == DIERR_INPUTLOST, "GetDeviceData returned %#lx\n", hr ); hr = IDirectInputDevice8_Poll( device ); ok( hr == DIERR_INPUTLOST, "Poll returned: %#lx\n", hr ); hr = IDirectInputDevice8_Acquire( device ); ok( hr == DIERR_UNPLUGGED, "Acquire returned %#lx\n", hr ); hr = IDirectInputDevice8_GetDeviceState( device, sizeof(state), &state ); ok( hr == DIERR_NOTACQUIRED, "GetDeviceState returned %#lx\n", hr ); hr = IDirectInputDevice8_GetDeviceData( device, size, objdata, &count, DIGDD_PEEK ); ok( hr == DIERR_NOTACQUIRED, "GetDeviceData returned %#lx\n", hr ); hr = IDirectInputDevice8_Unacquire( device ); ok( hr == DI_NOEFFECT, "Unacquire returned: %#lx\n", hr ); dinput_driver_start( report_desc, sizeof(report_desc), &hid_caps, NULL, 0 ); hr = IDirectInputDevice8_Acquire( device ); todo_wine ok( hr == DIERR_UNPLUGGED, "Acquire returned %#lx\n", hr ); hr = IDirectInputDevice8_GetDeviceState( device, sizeof(state), &state ); todo_wine ok( hr == DIERR_NOTACQUIRED, "GetDeviceState returned %#lx\n", hr ); ref = IDirectInputDevice8_Release( device ); ok( ref == 0, "Release returned %ld\n", ref ); done: pnp_driver_stop(); cleanup_registry_keys(); SetCurrentDirectoryW( cwd ); winetest_pop_context(); return device != NULL; } static int device_change_count; static int device_change_expect; static HWND device_change_hwnd; static BOOL device_change_all; static BOOL all_upper( const WCHAR *str, const WCHAR *end ) { while (str++ != end) if (towupper( str[-1] ) != str[-1]) return FALSE; return TRUE; } static BOOL all_lower( const WCHAR *str, const WCHAR *end ) { while (str++ != end) if (towlower( str[-1] ) != str[-1]) return FALSE; return TRUE; } static LRESULT CALLBACK devnotify_wndproc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) { if (msg == WM_DEVICECHANGE) { DEV_BROADCAST_HDR *header = (DEV_BROADCAST_HDR *)lparam; DEV_BROADCAST_DEVICEINTERFACE_W *iface = (DEV_BROADCAST_DEVICEINTERFACE_W *)lparam; const WCHAR *upper_end, *name_end, *expect_prefix; GUID expect_guid; if (device_change_all && (device_change_count == 0 || device_change_count == 3)) { expect_guid = control_class; expect_prefix = L"\\\\?\\ROOT#"; } else { expect_guid = GUID_DEVINTERFACE_HID; expect_prefix = L"\\\\?\\HID#"; } ok( hwnd == device_change_hwnd, "got hwnd %p\n", hwnd ); ok( header->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE, "got dbch_devicetype %lu\n", header->dbch_devicetype ); winetest_push_context( "%u", device_change_count ); ok( IsEqualGUID( &iface->dbcc_classguid, &expect_guid ), "got dbch_classguid %s\n", debugstr_guid( &iface->dbcc_classguid ) ); ok( iface->dbcc_size >= offsetof( DEV_BROADCAST_DEVICEINTERFACE_W, dbcc_name[wcslen( iface->dbcc_name ) + 1] ), "got dbcc_size %lu\n", iface->dbcc_size ); ok( !wcsncmp( iface->dbcc_name, expect_prefix, wcslen( expect_prefix ) ), "got dbcc_name %s\n", debugstr_w(iface->dbcc_name) ); upper_end = wcschr( iface->dbcc_name + wcslen( expect_prefix ), '#' ); name_end = iface->dbcc_name + wcslen( iface->dbcc_name ) + 1; ok( !!upper_end, "got dbcc_name %s\n", debugstr_w(iface->dbcc_name) ); ok( all_upper( iface->dbcc_name, upper_end ), "got dbcc_name %s\n", debugstr_w(iface->dbcc_name) ); ok( all_lower( upper_end, name_end ), "got dbcc_name %s\n", debugstr_w(iface->dbcc_name) ); if (device_change_count++ >= device_change_expect / 2) ok( wparam == DBT_DEVICEREMOVECOMPLETE, "got wparam %#Ix\n", wparam ); else ok( wparam == DBT_DEVICEARRIVAL, "got wparam %#Ix\n", wparam ); winetest_pop_context(); } return DefWindowProcW( hwnd, msg, wparam, lparam ); } static void test_RegisterDeviceNotification(void) { DEV_BROADCAST_DEVICEINTERFACE_A iface_filter_a = { .dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE_A), .dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE, .dbcc_classguid = GUID_DEVINTERFACE_HID, }; WNDCLASSEXW class = { .cbSize = sizeof(WNDCLASSEXW), .hInstance = GetModuleHandleW( NULL ), .lpszClassName = L"devnotify", .lpfnWndProc = devnotify_wndproc, }; char buffer[1024] = {0}; DEV_BROADCAST_HDR *header = (DEV_BROADCAST_HDR *)buffer; HANDLE hwnd, thread, stop_event; HDEVNOTIFY devnotify; MSG msg; RegisterClassExW( &class ); hwnd = CreateWindowW( class.lpszClassName, NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); SetLastError( 0xdeadbeef ); devnotify = RegisterDeviceNotificationA( NULL, NULL, 0 ); ok( !devnotify, "RegisterDeviceNotificationA succeeded\n" ); ok( GetLastError() == ERROR_INVALID_PARAMETER, "got error %lu\n", GetLastError() ); SetLastError( 0xdeadbeef ); devnotify = RegisterDeviceNotificationA( (HWND)0xdeadbeef, NULL, 0 ); ok( !devnotify, "RegisterDeviceNotificationA succeeded\n" ); ok( GetLastError() == ERROR_INVALID_PARAMETER, "got error %lu\n", GetLastError() ); SetLastError( 0xdeadbeef ); devnotify = RegisterDeviceNotificationA( hwnd, NULL, 2 ); ok( !devnotify, "RegisterDeviceNotificationA succeeded\n" ); ok( GetLastError() == ERROR_INVALID_PARAMETER, "got error %lu\n", GetLastError() ); SetLastError( 0xdeadbeef ); memset( header, 0, sizeof(DEV_BROADCAST_OEM) ); header->dbch_size = sizeof(DEV_BROADCAST_OEM); header->dbch_devicetype = DBT_DEVTYP_OEM; devnotify = RegisterDeviceNotificationA( hwnd, header, 0 ); ok( !devnotify, "RegisterDeviceNotificationA succeeded\n" ); ok( GetLastError() == ERROR_INVALID_DATA || GetLastError() == ERROR_SERVICE_SPECIFIC_ERROR, "got error %lu\n", GetLastError() ); SetLastError( 0xdeadbeef ); memset( header, 0, sizeof(DEV_BROADCAST_DEVNODE) ); header->dbch_size = sizeof(DEV_BROADCAST_DEVNODE); header->dbch_devicetype = DBT_DEVTYP_DEVNODE; devnotify = RegisterDeviceNotificationA( hwnd, header, 0 ); ok( !devnotify, "RegisterDeviceNotificationA succeeded\n" ); ok( GetLastError() == ERROR_INVALID_DATA || GetLastError() == ERROR_SERVICE_SPECIFIC_ERROR, "got error %lu\n", GetLastError() ); SetLastError( 0xdeadbeef ); memset( header, 0, sizeof(DEV_BROADCAST_VOLUME) ); header->dbch_size = sizeof(DEV_BROADCAST_VOLUME); header->dbch_devicetype = DBT_DEVTYP_VOLUME; devnotify = RegisterDeviceNotificationA( hwnd, header, 0 ); ok( !devnotify, "RegisterDeviceNotificationA succeeded\n" ); ok( GetLastError() == ERROR_INVALID_DATA || GetLastError() == ERROR_SERVICE_SPECIFIC_ERROR, "got error %lu\n", GetLastError() ); SetLastError( 0xdeadbeef ); memset( header, 0, sizeof(DEV_BROADCAST_PORT_A) ); header->dbch_size = sizeof(DEV_BROADCAST_PORT_A); header->dbch_devicetype = DBT_DEVTYP_PORT; devnotify = RegisterDeviceNotificationA( hwnd, header, 0 ); ok( !devnotify, "RegisterDeviceNotificationA succeeded\n" ); ok( GetLastError() == ERROR_INVALID_DATA || GetLastError() == ERROR_SERVICE_SPECIFIC_ERROR, "got error %lu\n", GetLastError() ); SetLastError( 0xdeadbeef ); memset( header, 0, sizeof(DEV_BROADCAST_NET) ); header->dbch_size = sizeof(DEV_BROADCAST_NET); header->dbch_devicetype = DBT_DEVTYP_NET; devnotify = RegisterDeviceNotificationA( hwnd, header, 0 ); ok( !devnotify, "RegisterDeviceNotificationA succeeded\n" ); ok( GetLastError() == ERROR_INVALID_DATA || GetLastError() == ERROR_SERVICE_SPECIFIC_ERROR, "got error %lu\n", GetLastError() ); devnotify = RegisterDeviceNotificationA( hwnd, &iface_filter_a, DEVICE_NOTIFY_WINDOW_HANDLE ); ok( !!devnotify, "RegisterDeviceNotificationA failed, error %lu\n", GetLastError() ); while (PeekMessageW( &msg, hwnd, 0, 0, PM_REMOVE )) DispatchMessageW( &msg ); device_change_count = 0; device_change_expect = 2; device_change_hwnd = hwnd; device_change_all = FALSE; stop_event = CreateEventW( NULL, FALSE, FALSE, NULL ); ok( !!stop_event, "CreateEventW failed, error %lu\n", GetLastError() ); thread = CreateThread( NULL, 0, dinput_test_device_thread, stop_event, 0, NULL ); ok( !!thread, "CreateThread failed, error %lu\n", GetLastError() ); while (device_change_count < device_change_expect) { MsgWaitForMultipleObjects( 0, NULL, FALSE, INFINITE, QS_ALLINPUT ); while (PeekMessageW( &msg, hwnd, 0, 0, PM_REMOVE )) { TranslateMessage( &msg ); ok( msg.message != WM_DEVICECHANGE, "got WM_DEVICECHANGE\n" ); DispatchMessageW( &msg ); } if (device_change_count == device_change_expect / 2) SetEvent( stop_event ); } WaitForSingleObject( thread, INFINITE ); CloseHandle( thread ); CloseHandle( stop_event ); UnregisterDeviceNotification( devnotify ); memcpy( buffer, &iface_filter_a, sizeof(iface_filter_a) ); strcpy( ((DEV_BROADCAST_DEVICEINTERFACE_A *)buffer)->dbcc_name, "device name" ); ((DEV_BROADCAST_DEVICEINTERFACE_A *)buffer)->dbcc_size += strlen( "device name" ) + 1; devnotify = RegisterDeviceNotificationA( hwnd, buffer, DEVICE_NOTIFY_WINDOW_HANDLE ); ok( !!devnotify, "RegisterDeviceNotificationA failed, error %lu\n", GetLastError() ); while (PeekMessageW( &msg, hwnd, 0, 0, PM_REMOVE )) DispatchMessageW( &msg ); device_change_count = 0; device_change_expect = 2; device_change_hwnd = hwnd; device_change_all = FALSE; stop_event = CreateEventW( NULL, FALSE, FALSE, NULL ); ok( !!stop_event, "CreateEventW failed, error %lu\n", GetLastError() ); thread = CreateThread( NULL, 0, dinput_test_device_thread, stop_event, 0, NULL ); ok( !!thread, "CreateThread failed, error %lu\n", GetLastError() ); while (device_change_count < device_change_expect) { MsgWaitForMultipleObjects( 0, NULL, FALSE, INFINITE, QS_ALLINPUT ); while (PeekMessageW( &msg, hwnd, 0, 0, PM_REMOVE )) { TranslateMessage( &msg ); ok( msg.message != WM_DEVICECHANGE, "got WM_DEVICECHANGE\n" ); DispatchMessageW( &msg ); } if (device_change_count == device_change_expect / 2) SetEvent( stop_event ); } WaitForSingleObject( thread, INFINITE ); CloseHandle( thread ); CloseHandle( stop_event ); UnregisterDeviceNotification( devnotify ); devnotify = RegisterDeviceNotificationA( hwnd, &iface_filter_a, DEVICE_NOTIFY_ALL_INTERFACE_CLASSES ); ok( !!devnotify, "RegisterDeviceNotificationA failed, error %lu\n", GetLastError() ); while (PeekMessageW( &msg, hwnd, 0, 0, PM_REMOVE )) DispatchMessageW( &msg ); device_change_count = 0; device_change_expect = 4; device_change_hwnd = hwnd; device_change_all = TRUE; stop_event = CreateEventW( NULL, FALSE, FALSE, NULL ); ok( !!stop_event, "CreateEventW failed, error %lu\n", GetLastError() ); thread = CreateThread( NULL, 0, dinput_test_device_thread, stop_event, 0, NULL ); ok( !!thread, "CreateThread failed, error %lu\n", GetLastError() ); while (device_change_count < device_change_expect) { MsgWaitForMultipleObjects( 0, NULL, FALSE, INFINITE, QS_ALLINPUT ); while (PeekMessageW( &msg, hwnd, 0, 0, PM_REMOVE )) { TranslateMessage( &msg ); ok( msg.message != WM_DEVICECHANGE, "got WM_DEVICECHANGE\n" ); DispatchMessageW( &msg ); } if (device_change_count == device_change_expect / 2) SetEvent( stop_event ); } WaitForSingleObject( thread, INFINITE ); CloseHandle( thread ); CloseHandle( stop_event ); UnregisterDeviceNotification( devnotify ); DestroyWindow( hwnd ); UnregisterClassW( class.lpszClassName, class.hInstance ); } struct controller_handler { IEventHandler_RawGameController IEventHandler_RawGameController_iface; HANDLE event; BOOL invoked; }; static inline struct controller_handler *impl_from_IEventHandler_RawGameController( IEventHandler_RawGameController *iface ) { return CONTAINING_RECORD( iface, struct controller_handler, IEventHandler_RawGameController_iface ); } static HRESULT WINAPI controller_handler_QueryInterface( IEventHandler_RawGameController *iface, REFIID iid, void **out ) { if (IsEqualGUID( iid, &IID_IUnknown ) || IsEqualGUID( iid, &IID_IAgileObject ) || IsEqualGUID( iid, &IID_IEventHandler_RawGameController )) { IUnknown_AddRef( iface ); *out = iface; return S_OK; } trace( "%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid( iid ) ); *out = NULL; return E_NOINTERFACE; } static ULONG WINAPI controller_handler_AddRef( IEventHandler_RawGameController *iface ) { return 2; } static ULONG WINAPI controller_handler_Release( IEventHandler_RawGameController *iface ) { return 1; } static HRESULT WINAPI controller_handler_Invoke( IEventHandler_RawGameController *iface, IInspectable *sender, IRawGameController *controller ) { struct controller_handler *impl = impl_from_IEventHandler_RawGameController( iface ); trace( "iface %p, sender %p, controller %p\n", iface, sender, controller ); ok( sender == NULL, "got sender %p\n", sender ); SetEvent( impl->event ); impl->invoked = TRUE; return S_OK; } static const IEventHandler_RawGameControllerVtbl controller_handler_vtbl = { controller_handler_QueryInterface, controller_handler_AddRef, controller_handler_Release, controller_handler_Invoke, }; static struct controller_handler controller_removed = {{&controller_handler_vtbl}}; static struct controller_handler controller_added = {{&controller_handler_vtbl}}; DEFINE_GUID( IID_IGameControllerImpl, 0x06e58977, 0x7684, 0x4dc5, 0xba, 0xd1, 0xcd, 0xa5, 0x2a, 0x4a, 0xa0, 0x6d ); typedef IInspectable IGameControllerImpl; #define DEFINE_IINSPECTABLE_OUTER( pfx, iface_type, impl_type, outer_iface ) \ static inline impl_type *impl_from_##iface_type( iface_type *iface ) \ { \ return CONTAINING_RECORD( iface, impl_type, iface_type##_iface ); \ } \ static HRESULT WINAPI pfx##_QueryInterface( iface_type *iface, REFIID iid, void **out ) \ { \ impl_type *impl = impl_from_##iface_type( iface ); \ return IInspectable_QueryInterface( (IInspectable *)impl->outer_iface, iid, out ); \ } \ static ULONG WINAPI pfx##_AddRef( iface_type *iface ) \ { \ impl_type *impl = impl_from_##iface_type( iface ); \ return IInspectable_AddRef( (IInspectable *)impl->outer_iface ); \ } \ static ULONG WINAPI pfx##_Release( iface_type *iface ) \ { \ impl_type *impl = impl_from_##iface_type( iface ); \ return IInspectable_Release( (IInspectable *)impl->outer_iface ); \ } \ static HRESULT WINAPI pfx##_GetIids( iface_type *iface, ULONG *iid_count, IID **iids ) \ { \ impl_type *impl = impl_from_##iface_type( iface ); \ return IInspectable_GetIids( (IInspectable *)impl->outer_iface, iid_count, iids ); \ } \ static HRESULT WINAPI pfx##_GetRuntimeClassName( iface_type *iface, HSTRING *class_name ) \ { \ impl_type *impl = impl_from_##iface_type( iface ); \ return IInspectable_GetRuntimeClassName( (IInspectable *)impl->outer_iface, class_name ); \ } \ static HRESULT WINAPI pfx##_GetTrustLevel( iface_type *iface, TrustLevel *trust_level ) \ { \ impl_type *impl = impl_from_##iface_type( iface ); \ return IInspectable_GetTrustLevel( (IInspectable *)impl->outer_iface, trust_level ); \ } struct custom_controller { IGameControllerImpl IGameControllerImpl_iface; IGameControllerInputSink IGameControllerInputSink_iface; IHidGameControllerInputSink IHidGameControllerInputSink_iface; IGameController *IGameController_outer; LONG ref; BOOL initialize_called; BOOL on_input_resumed_called; BOOL on_input_suspended_called; BOOL raw_game_controller_queried; }; static inline struct custom_controller *impl_from_IGameControllerImpl( IGameControllerImpl *iface ) { return CONTAINING_RECORD( iface, struct custom_controller, IGameControllerImpl_iface ); } static HRESULT WINAPI controller_QueryInterface( IGameControllerImpl *iface, REFIID iid, void **out ) { struct custom_controller *impl = impl_from_IGameControllerImpl( iface ); if (IsEqualGUID( iid, &IID_IUnknown ) || IsEqualGUID( iid, &IID_IInspectable ) || IsEqualGUID( iid, &IID_IGameControllerImpl )) { IInspectable_AddRef( (*out = &impl->IGameControllerImpl_iface) ); return S_OK; } if (IsEqualGUID( iid, &IID_IGameControllerInputSink )) { IInspectable_AddRef( (*out = &impl->IGameControllerInputSink_iface) ); return S_OK; } if (IsEqualGUID( iid, &IID_IHidGameControllerInputSink )) { IInspectable_AddRef( (*out = &impl->IHidGameControllerInputSink_iface) ); return S_OK; } if (IsEqualGUID( iid, &IID_IRawGameController )) { impl->raw_game_controller_queried = TRUE; *out = NULL; return E_NOINTERFACE; } ok( 0, "%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid( iid ) ); *out = NULL; return E_NOINTERFACE; } static ULONG WINAPI controller_AddRef( IGameControllerImpl *iface ) { struct custom_controller *impl = impl_from_IGameControllerImpl( iface ); return InterlockedIncrement( &impl->ref ); } static ULONG WINAPI controller_Release( IGameControllerImpl *iface ) { struct custom_controller *impl = impl_from_IGameControllerImpl( iface ); return InterlockedDecrement( &impl->ref ); } static HRESULT WINAPI controller_GetIids( IGameControllerImpl *iface, ULONG *iid_count, IID **iids ) { ok( 0, "unexpected call\n" ); return E_NOTIMPL; } static HRESULT WINAPI controller_GetRuntimeClassName( IGameControllerImpl *iface, HSTRING *class_name ) { ok( 0, "unexpected call\n" ); return E_NOTIMPL; } static HRESULT WINAPI controller_GetTrustLevel( IGameControllerImpl *iface, TrustLevel *trust_level ) { ok( 0, "unexpected call\n" ); return E_NOTIMPL; } static HRESULT WINAPI controller_Initialize( IGameControllerImpl *iface, IGameController *outer, IGameControllerProvider *provider ) { struct custom_controller *impl = impl_from_IGameControllerImpl( iface ); ok( !impl->initialize_called, "Initialize already called\n" ); impl->initialize_called = TRUE; check_interface( outer, &IID_IUnknown, TRUE ); check_interface( outer, &IID_IInspectable, TRUE ); check_interface( outer, &IID_IAgileObject, TRUE ); check_interface( outer, &IID_IWeakReferenceSource, TRUE ); check_interface( outer, &IID_IGameController, TRUE ); impl->IGameController_outer = outer; check_interface( provider, &IID_IUnknown, TRUE ); check_interface( provider, &IID_IInspectable, TRUE ); check_interface( provider, &IID_IAgileObject, TRUE ); check_interface( provider, &IID_IWeakReferenceSource, TRUE ); check_interface( provider, &IID_IGameControllerProvider, TRUE ); check_interface( provider, &IID_IHidGameControllerProvider, TRUE ); return S_OK; } static const void *controller_vtbl[] = { controller_QueryInterface, controller_AddRef, controller_Release, /* IInspectable methods */ controller_GetIids, controller_GetRuntimeClassName, controller_GetTrustLevel, /* IGameControllerImpl methods */ controller_Initialize, }; DEFINE_IINSPECTABLE_OUTER( input_sink, IGameControllerInputSink, struct custom_controller, IGameController_outer ) static HRESULT WINAPI input_sink_OnInputResumed( IGameControllerInputSink *iface, UINT64 timestamp ) { struct custom_controller *impl = impl_from_IGameControllerInputSink( iface ); trace( "iface %p, timestamp %I64u\n", iface, timestamp ); ok( !controller_added.invoked, "controller added handler invoked\n" ); ok( !impl->on_input_resumed_called, "OnInputResumed already called\n" ); impl->on_input_resumed_called = TRUE; return S_OK; } static HRESULT WINAPI input_sink_OnInputSuspended( IGameControllerInputSink *iface, UINT64 timestamp ) { struct custom_controller *impl = impl_from_IGameControllerInputSink( iface ); trace( "iface %p, timestamp %I64u\n", iface, timestamp ); ok( !controller_removed.invoked, "controller removed handler invoked\n" ); ok( !impl->on_input_suspended_called, "OnInputSuspended already called\n" ); impl->on_input_suspended_called = TRUE; return S_OK; } static const struct IGameControllerInputSinkVtbl input_sink_vtbl = { input_sink_QueryInterface, input_sink_AddRef, input_sink_Release, /* IInspectable methods */ input_sink_GetIids, input_sink_GetRuntimeClassName, input_sink_GetTrustLevel, /* IGameControllerInputSink methods */ input_sink_OnInputResumed, input_sink_OnInputSuspended, }; DEFINE_IINSPECTABLE_OUTER( hid_sink, IHidGameControllerInputSink, struct custom_controller, IGameController_outer ) static HRESULT WINAPI hid_sink_OnInputReportReceived( IHidGameControllerInputSink *iface, UINT64 timestamp, BYTE id, UINT32 report_len, BYTE *report_buf ) { ok( 0, "unexpected call\n" ); return S_OK; } static const struct IHidGameControllerInputSinkVtbl hid_sink_vtbl = { hid_sink_QueryInterface, hid_sink_AddRef, hid_sink_Release, /* IInspectable methods */ hid_sink_GetIids, hid_sink_GetRuntimeClassName, hid_sink_GetTrustLevel, /* IGameControllerInputSink methods */ hid_sink_OnInputReportReceived, }; static struct custom_controller custom_controller = { {(IInspectableVtbl *)controller_vtbl}, {&input_sink_vtbl}, {&hid_sink_vtbl}, }; struct custom_factory { ICustomGameControllerFactory ICustomGameControllerFactory_iface; BOOL create_controller_called; BOOL create_controller; BOOL on_game_controller_added_called; HANDLE added_event; BOOL on_game_controller_removed_called; HANDLE removed_event; }; static inline struct custom_factory *impl_from_ICustomGameControllerFactory( ICustomGameControllerFactory *iface ) { return CONTAINING_RECORD( iface, struct custom_factory, ICustomGameControllerFactory_iface ); } static HRESULT WINAPI custom_factory_QueryInterface( ICustomGameControllerFactory *iface, REFIID iid, void **out ) { struct custom_factory *impl = impl_from_ICustomGameControllerFactory( iface ); if (IsEqualGUID( iid, &IID_IUnknown ) || IsEqualGUID( iid, &IID_IInspectable ) || IsEqualGUID( iid, &IID_IAgileObject ) || IsEqualGUID( iid, &IID_ICustomGameControllerFactory )) { IInspectable_AddRef( (*out = &impl->ICustomGameControllerFactory_iface) ); return S_OK; } ok( 0, "%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid( iid ) ); *out = NULL; return E_NOINTERFACE; } static ULONG WINAPI custom_factory_AddRef( ICustomGameControllerFactory *iface ) { return 2; } static ULONG WINAPI custom_factory_Release( ICustomGameControllerFactory *iface ) { return 1; } static HRESULT WINAPI custom_factory_GetIids( ICustomGameControllerFactory *iface, ULONG *iid_count, IID **iids ) { ok( 0, "unexpected call\n" ); return E_NOTIMPL; } static HRESULT WINAPI custom_factory_GetRuntimeClassName( ICustomGameControllerFactory *iface, HSTRING *class_name ) { ok( 0, "unexpected call\n" ); return E_NOTIMPL; } static HRESULT WINAPI custom_factory_GetTrustLevel( ICustomGameControllerFactory *iface, TrustLevel *trust_level ) { ok( 0, "unexpected call\n" ); return E_NOTIMPL; } static HRESULT WINAPI custom_factory_CreateGameController( ICustomGameControllerFactory *iface, IGameControllerProvider *provider, IInspectable **value ) { struct custom_factory *impl = impl_from_ICustomGameControllerFactory( iface ); trace( "iface %p, provider %p, value %p\n", iface, provider, value ); ok( !controller_added.invoked, "controller added handler invoked\n" ); ok( !impl->create_controller_called, "unexpected call\n" ); impl->create_controller_called = TRUE; if (!impl->create_controller) return E_NOTIMPL; check_interface( provider, &IID_IUnknown, TRUE ); check_interface( provider, &IID_IInspectable, TRUE ); check_interface( provider, &IID_IAgileObject, TRUE ); check_interface( provider, &IID_IGameControllerProvider, TRUE ); check_interface( provider, &IID_IHidGameControllerProvider, TRUE ); check_interface( provider, &IID_IXusbGameControllerProvider, FALSE ); check_interface( provider, &IID_IGameControllerInputSink, FALSE ); custom_controller.ref = 1; *value = &custom_controller.IGameControllerImpl_iface; return S_OK; } static HRESULT WINAPI custom_factory_OnGameControllerAdded( ICustomGameControllerFactory *iface, IGameController *value ) { struct custom_factory *impl = impl_from_ICustomGameControllerFactory( iface ); trace( "iface %p, value %p\n", iface, value ); ok( controller_added.invoked, "controller added handler not invoked\n" ); ok( impl->create_controller_called, "CreateGameController not called\n" ); ok( impl->create_controller, "unexpected call\n" ); ok( custom_controller.initialize_called, "Initialize not called\n" ); ok( custom_controller.on_input_resumed_called, "OnInputResumed not called\n" ); ok( !custom_controller.on_input_suspended_called, "OnInputSuspended not called\n" ); ok( !impl->on_game_controller_added_called, "OnGameControllerAdded already called\n" ); impl->on_game_controller_added_called = TRUE; SetEvent( impl->added_event ); return S_OK; } static HRESULT WINAPI custom_factory_OnGameControllerRemoved( ICustomGameControllerFactory *iface, IGameController *value ) { struct custom_factory *impl = impl_from_ICustomGameControllerFactory( iface ); trace( "iface %p, value %p\n", iface, value ); ok( controller_removed.invoked, "controller removed handler invoked\n" ); ok( custom_controller.on_input_suspended_called, "OnInputSuspended not called\n" ); ok( impl->create_controller, "unexpected call\n" ); ok( impl->on_game_controller_added_called, "OnGameControllerAdded already called\n" ); ok( !impl->on_game_controller_removed_called, "OnGameControllerRemoved already called\n" ); impl->on_game_controller_removed_called = TRUE; SetEvent( impl->removed_event ); return S_OK; } static const struct ICustomGameControllerFactoryVtbl custom_factory_vtbl = { custom_factory_QueryInterface, custom_factory_AddRef, custom_factory_Release, /* IInspectable methods */ custom_factory_GetIids, custom_factory_GetRuntimeClassName, custom_factory_GetTrustLevel, /* ICustomGameControllerFactory methods */ custom_factory_CreateGameController, custom_factory_OnGameControllerAdded, custom_factory_OnGameControllerRemoved, }; static struct custom_factory custom_factory = {{&custom_factory_vtbl}}; static LRESULT CALLBACK windows_gaming_input_wndproc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) { if (msg == WM_DEVICECHANGE) { winetest_push_context( "%u", device_change_count ); if (device_change_count++ >= device_change_expect / 2) { ok( wparam == DBT_DEVICEREMOVECOMPLETE, "got wparam %#Ix\n", wparam ); ok( controller_added.invoked, "controller added handler not invoked\n" ); ok( controller_removed.invoked, "controller removed handler not invoked\n" ); } else { ok( wparam == DBT_DEVICEARRIVAL, "got wparam %#Ix\n", wparam ); todo_wine /* Wine currently listens to WINEXINPUT device arrival, which is received earlier than HID */ ok( !controller_added.invoked, "controller added handler not invoked\n" ); ok( !controller_removed.invoked, "controller removed handler invoked\n" ); } winetest_pop_context(); } return DefWindowProcW( hwnd, msg, wparam, lparam ); } static void test_windows_gaming_input(void) { static const WCHAR *manager_class_name = RuntimeClass_Windows_Gaming_Input_Custom_GameControllerFactoryManager; static const WCHAR *class_name = RuntimeClass_Windows_Gaming_Input_RawGameController; DEV_BROADCAST_DEVICEINTERFACE_A iface_filter_a = { .dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE_A), .dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE, .dbcc_classguid = GUID_DEVINTERFACE_HID, }; WNDCLASSEXW class = { .cbSize = sizeof(WNDCLASSEXW), .hInstance = GetModuleHandleW( NULL ), .lpszClassName = L"devnotify", .lpfnWndProc = windows_gaming_input_wndproc, }; IGameControllerFactoryManagerStatics2 *manager_statics2; IRawGameController *raw_controller, *tmp_raw_controller; IGameController *game_controller, *tmp_game_controller; IGameControllerFactoryManagerStatics *manager_statics; EventRegistrationToken controller_removed_token; IVectorView_RawGameController *controller_view; EventRegistrationToken controller_added_token; IRawGameControllerStatics *statics; HANDLE hwnd, thread, stop_event; IInspectable *tmp_inspectable; HDEVNOTIFY devnotify; HSTRING str; UINT32 size; HRESULT hr; DWORD ret; MSG msg; if (!load_combase_functions()) return; hr = pRoInitialize( RO_INIT_MULTITHREADED ); ok( hr == RPC_E_CHANGED_MODE, "RoInitialize failed, hr %#lx\n", hr ); hr = pWindowsCreateString( class_name, wcslen( class_name ), &str ); ok( hr == S_OK, "WindowsCreateString failed, hr %#lx\n", hr ); hr = pRoGetActivationFactory( str, &IID_IRawGameControllerStatics, (void **)&statics ); ok( hr == S_OK || broken( hr == REGDB_E_CLASSNOTREG ), "RoGetActivationFactory failed, hr %#lx\n", hr ); pWindowsDeleteString( str ); if (hr == REGDB_E_CLASSNOTREG) { win_skip( "%s runtimeclass not registered, skipping tests.\n", wine_dbgstr_w( class_name ) ); return; } hr = pWindowsCreateString( manager_class_name, wcslen( manager_class_name ), &str ); ok( hr == S_OK, "WindowsCreateString failed, hr %#lx\n", hr ); hr = pRoGetActivationFactory( str, &IID_IGameControllerFactoryManagerStatics, (void **)&manager_statics ); ok( hr == S_OK, "RoGetActivationFactory failed, hr %#lx\n", hr ); hr = pRoGetActivationFactory( str, &IID_IGameControllerFactoryManagerStatics2, (void **)&manager_statics2 ); ok( hr == S_OK, "RoGetActivationFactory failed, hr %#lx\n", hr ); pWindowsDeleteString( str ); controller_added.event = CreateEventW( NULL, FALSE, FALSE, NULL ); ok( !!controller_added.event, "CreateEventW failed, error %lu\n", GetLastError() ); controller_removed.event = CreateEventW( NULL, FALSE, FALSE, NULL ); ok( !!controller_removed.event, "CreateEventW failed, error %lu\n", GetLastError() ); hr = IGameControllerFactoryManagerStatics_RegisterCustomFactoryForHardwareId( manager_statics, &custom_factory.ICustomGameControllerFactory_iface, LOWORD(EXPECT_VIDPID), HIWORD(EXPECT_VIDPID) ); todo_wine ok( hr == S_OK, "RegisterCustomFactoryForHardwareId returned %#lx\n", hr ); hr = IRawGameControllerStatics_add_RawGameControllerAdded( statics, &controller_added.IEventHandler_RawGameController_iface, &controller_added_token ); ok( hr == S_OK, "add_RawGameControllerAdded returned %#lx\n", hr ); ok( controller_added_token.value, "got token %I64u\n", controller_added_token.value ); hr = IRawGameControllerStatics_add_RawGameControllerRemoved( statics, &controller_removed.IEventHandler_RawGameController_iface, &controller_removed_token ); ok( hr == S_OK, "add_RawGameControllerAdded returned %#lx\n", hr ); hr = IRawGameControllerStatics_get_RawGameControllers( statics, &controller_view ); ok( hr == S_OK, "get_RawGameControllers returned %#lx\n", hr ); RegisterClassExW( &class ); hwnd = CreateWindowW( class.lpszClassName, NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); devnotify = RegisterDeviceNotificationA( hwnd, &iface_filter_a, DEVICE_NOTIFY_WINDOW_HANDLE ); ok( !!devnotify, "RegisterDeviceNotificationA failed, error %lu\n", GetLastError() ); while (PeekMessageW( &msg, hwnd, 0, 0, PM_REMOVE )) DispatchMessageW( &msg ); device_change_count = 0; device_change_expect = 2; stop_event = CreateEventW( NULL, FALSE, FALSE, NULL ); ok( !!stop_event, "CreateEventW failed, error %lu\n", GetLastError() ); thread = CreateThread( NULL, 0, dinput_test_device_thread, stop_event, 0, NULL ); ok( !!thread, "CreateThread failed, error %lu\n", GetLastError() ); wait_for_events( 1, &controller_added.event, INFINITE ); ok( controller_added.invoked, "controller added handler not invoked\n" ); ok( !controller_removed.invoked, "controller removed handler invoked\n" ); todo_wine ok( custom_factory.create_controller_called, "CreateGameController not called\n" ); hr = IVectorView_RawGameController_get_Size( controller_view, &size ); ok( hr == S_OK, "get_Size returned %#lx\n", hr ); ok( size == 0, "got size %u\n", size ); IVectorView_RawGameController_Release( controller_view ); hr = IRawGameControllerStatics_get_RawGameControllers( statics, &controller_view ); ok( hr == S_OK, "get_RawGameControllers returned %#lx\n", hr ); hr = IVectorView_RawGameController_get_Size( controller_view, &size ); ok( hr == S_OK, "get_Size returned %#lx\n", hr ); ok( size == 1, "got size %u\n", size ); hr = IVectorView_RawGameController_GetAt( controller_view, 0, &raw_controller ); ok( hr == S_OK, "GetAt returned %#lx\n", hr ); hr = IRawGameController_QueryInterface( raw_controller, &IID_IGameController, (void **)&game_controller ); ok( hr == S_OK, "QueryInterface returned %#lx\n", hr ); hr = IGameControllerFactoryManagerStatics2_TryGetFactoryControllerFromGameController( manager_statics2, &custom_factory.ICustomGameControllerFactory_iface, game_controller, &tmp_game_controller ); todo_wine ok( hr == S_OK, "TryGetFactoryControllerFromGameController returned %#lx\n", hr ); ok( !tmp_game_controller, "got controller %p\n", tmp_game_controller ); hr = IRawGameControllerStatics_FromGameController( statics, game_controller, &tmp_raw_controller ); ok( hr == S_OK, "FromGameController returned %#lx\n", hr ); ok( tmp_raw_controller == raw_controller, "got controller %p\n", tmp_raw_controller ); IRawGameController_Release( tmp_raw_controller ); IGameController_Release( game_controller ); IRawGameController_Release( raw_controller ); SetEvent( stop_event ); wait_for_events( 1, &controller_removed.event, INFINITE ); ok( controller_added.invoked, "controller added handler not invoked\n" ); ok( controller_removed.invoked, "controller removed handler not invoked\n" ); hr = IVectorView_RawGameController_get_Size( controller_view, &size ); ok( hr == S_OK, "get_Size returned %#lx\n", hr ); ok( size == 1, "got size %u\n", size ); IVectorView_RawGameController_Release( controller_view ); hr = IRawGameControllerStatics_get_RawGameControllers( statics, &controller_view ); ok( hr == S_OK, "get_RawGameControllers returned %#lx\n", hr ); hr = IVectorView_RawGameController_get_Size( controller_view, &size ); ok( hr == S_OK, "get_Size returned %#lx\n", hr ); ok( size == 0, "got size %u\n", size ); IVectorView_RawGameController_Release( controller_view ); WaitForSingleObject( thread, INFINITE ); CloseHandle( thread ); while (PeekMessageW( &msg, hwnd, 0, 0, PM_REMOVE )) DispatchMessageW( &msg ); device_change_count = 0; device_change_expect = 2; custom_factory.create_controller = TRUE; custom_factory.create_controller_called = FALSE; ResetEvent( controller_added.event ); controller_added.invoked = FALSE; ResetEvent( controller_removed.event ); controller_removed.invoked = FALSE; ResetEvent( stop_event ); custom_factory.added_event = CreateEventW( NULL, FALSE, FALSE, NULL ); ok( !!custom_factory.added_event, "CreateEventW failed, error %lu\n", GetLastError() ); custom_factory.removed_event = CreateEventW( NULL, FALSE, FALSE, NULL ); ok( !!custom_factory.removed_event, "CreateEventW failed, error %lu\n", GetLastError() ); thread = CreateThread( NULL, 0, dinput_test_device_thread, stop_event, 0, NULL ); ok( !!thread, "CreateThread failed, error %lu\n", GetLastError() ); wait_for_events( 1, &controller_added.event, INFINITE ); ret = wait_for_events( 1, &custom_factory.added_event, 500 ); todo_wine ok( !ret, "wait_for_events returned %#lx\n", ret ); hr = IRawGameControllerStatics_get_RawGameControllers( statics, &controller_view ); ok( hr == S_OK, "get_RawGameControllers returned %#lx\n", hr ); hr = IVectorView_RawGameController_GetAt( controller_view, 0, &raw_controller ); ok( hr == S_OK, "GetAt returned %#lx\n", hr ); hr = IRawGameController_QueryInterface( raw_controller, &IID_IGameController, (void **)&game_controller ); ok( hr == S_OK, "QueryInterface returned %#lx\n", hr ); ok( game_controller != custom_controller.IGameController_outer, "got controller %p\n", game_controller ); hr = IGameControllerFactoryManagerStatics2_TryGetFactoryControllerFromGameController( manager_statics2, &custom_factory.ICustomGameControllerFactory_iface, game_controller, &tmp_game_controller ); todo_wine ok( hr == S_OK, "TryGetFactoryControllerFromGameController returned %#lx\n", hr ); ok( tmp_game_controller == custom_controller.IGameController_outer, "got controller %p\n", tmp_game_controller ); if (hr != S_OK) goto next; hr = IGameController_QueryInterface( tmp_game_controller, &IID_IInspectable, (void **)&tmp_inspectable ); ok( hr == S_OK, "QueryInterface returned %#lx\n", hr ); ok( tmp_inspectable == (void *)tmp_game_controller, "got inspectable %p\n", tmp_inspectable ); check_interface( tmp_inspectable, &IID_IUnknown, TRUE ); check_interface( tmp_inspectable, &IID_IInspectable, TRUE ); check_interface( tmp_inspectable, &IID_IAgileObject, TRUE ); check_interface( tmp_inspectable, &IID_IWeakReferenceSource, TRUE ); check_interface( tmp_inspectable, &IID_IGameController, TRUE ); check_interface( tmp_inspectable, &IID_IGameControllerBatteryInfo, TRUE ); check_interface( tmp_inspectable, &IID_IGameControllerInputSink, TRUE ); check_interface( tmp_inspectable, &IID_IHidGameControllerInputSink, TRUE ); check_interface( tmp_inspectable, &IID_IGameControllerImpl, TRUE ); check_interface( tmp_inspectable, &IID_IRawGameController, FALSE ); check_interface( tmp_inspectable, &IID_IGameControllerProvider, FALSE ); IInspectable_Release( tmp_inspectable ); ok( custom_controller.raw_game_controller_queried, "IRawGameController not queried\n" ); IGameController_Release( tmp_game_controller ); next: hr = IRawGameControllerStatics_FromGameController( statics, custom_controller.IGameController_outer, &tmp_raw_controller ); todo_wine ok( hr == S_OK, "FromGameController returned %#lx\n", hr ); todo_wine ok( tmp_raw_controller == raw_controller, "got controller %p\n", tmp_raw_controller ); if (hr == S_OK) IRawGameController_Release( tmp_raw_controller ); IGameController_Release( game_controller ); IRawGameController_Release( raw_controller ); SetEvent( stop_event ); ret = wait_for_events( 1, &custom_factory.removed_event, 500 ); todo_wine ok( !ret, "wait_for_events returned %#lx\n", ret ); wait_for_events( 1, &controller_removed.event, INFINITE ); hr = IRawGameControllerStatics_remove_RawGameControllerAdded( statics, controller_added_token ); ok( hr == S_OK, "remove_RawGameControllerAdded returned %#lx\n", hr ); hr = IRawGameControllerStatics_remove_RawGameControllerRemoved( statics, controller_removed_token ); ok( hr == S_OK, "remove_RawGameControllerRemoved returned %#lx\n", hr ); hr = IRawGameControllerStatics_remove_RawGameControllerRemoved( statics, controller_removed_token ); ok( hr == S_OK, "remove_RawGameControllerRemoved returned %#lx\n", hr ); IVectorView_RawGameController_Release( controller_view ); IGameControllerFactoryManagerStatics2_Release( manager_statics2 ); IGameControllerFactoryManagerStatics_Release( manager_statics ); IRawGameControllerStatics_Release( statics ); WaitForSingleObject( thread, INFINITE ); CloseHandle( thread ); CloseHandle( stop_event ); UnregisterDeviceNotification( devnotify ); DestroyWindow( hwnd ); UnregisterClassW( class.lpszClassName, class.hInstance ); CloseHandle( custom_factory.added_event ); CloseHandle( custom_factory.removed_event ); CloseHandle( controller_added.event ); CloseHandle( controller_removed.event ); } START_TEST( hotplug ) { if (!dinput_test_init()) return; CoInitialize( NULL ); if (test_input_lost( 0x500 )) { test_input_lost( 0x700 ); test_input_lost( 0x800 ); test_RegisterDeviceNotification(); test_windows_gaming_input(); } CoUninitialize(); dinput_test_exit(); }