/* * 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 "wine/hid.h" #include "dinput_test.h" 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) { 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) { 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) { 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 ); } 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(); } CoUninitialize(); dinput_test_exit(); }