From: "Rémi Bernon" Subject: [PATCH v2 2/4] dinput/tests: Add some RegisterDeviceNotificationA and hotplug tests. Message-Id: <20220126095020.3800590-2-rbernon@codeweavers.com> Date: Wed, 26 Jan 2022 10:50:18 +0100 In-Reply-To: <20220126095020.3800590-1-rbernon@codeweavers.com> References: <20220126095020.3800590-1-rbernon@codeweavers.com> Signed-off-by: Rémi Bernon --- dlls/dinput/tests/dinput_test.h | 1 + dlls/dinput/tests/hid.c | 86 ++++++++++ dlls/dinput/tests/hotplug.c | 283 +++++++++++++++++++++++++++++++- 3 files changed, 369 insertions(+), 1 deletion(-) diff --git a/dlls/dinput/tests/dinput_test.h b/dlls/dinput/tests/dinput_test.h index 0a4c9ba5fa4..97eed949a36 100644 --- a/dlls/dinput/tests/dinput_test.h +++ b/dlls/dinput/tests/dinput_test.h @@ -66,6 +66,7 @@ BOOL dinput_test_init_( const char *file, int line ); void dinput_test_exit(void); HRESULT dinput_test_create_device( DWORD version, DIDEVICEINSTANCEW *devinst, IDirectInputDevice8W **device ); +DWORD WINAPI dinput_test_device_thread( void *stop_event ); #define check_member_( file, line, val, exp, fmt, member ) \ ok_(file, line)( (val).member == (exp).member, "got " #member " " fmt "\n", (val).member ) diff --git a/dlls/dinput/tests/hid.c b/dlls/dinput/tests/hid.c index 0e94ce63aab..7e4ab3dc1f3 100644 --- a/dlls/dinput/tests/hid.c +++ b/dlls/dinput/tests/hid.c @@ -3478,6 +3478,92 @@ HRESULT dinput_test_create_device( DWORD version, DIDEVICEINSTANCEW *devinst, ID return DI_OK; } +DWORD WINAPI dinput_test_device_thread( void *stop_event ) +{ +#include "psh_hid_macros.h" + static const unsigned char gamepad_desc[] = + { + USAGE_PAGE(1, HID_USAGE_PAGE_GENERIC), + USAGE(1, HID_USAGE_GENERIC_GAMEPAD), + COLLECTION(1, Application), + USAGE(1, HID_USAGE_GENERIC_GAMEPAD), + COLLECTION(1, Physical), + USAGE(1, HID_USAGE_GENERIC_X), + USAGE(1, HID_USAGE_GENERIC_Y), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, 127), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(1, 127), + REPORT_SIZE(1, 8), + REPORT_COUNT(1, 2), + INPUT(1, Data|Var|Abs), + + 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 HID_DEVICE_ATTRIBUTES attributes = + { + .Size = sizeof(HID_DEVICE_ATTRIBUTES), + .VendorID = LOWORD(EXPECT_VIDPID), + .ProductID = HIWORD(EXPECT_VIDPID), + .VersionNumber = 0x0100, + }; + static const HIDP_CAPS caps = + { + .InputReportByteLength = 3, + }; + + WCHAR cwd[MAX_PATH], tempdir[MAX_PATH]; + DWORD report_id = 1, polled = 0; + char context[64]; + LSTATUS status; + HKEY hkey; + + GetCurrentDirectoryW( ARRAY_SIZE(cwd), cwd ); + GetTempPathW( ARRAY_SIZE(tempdir), tempdir ); + SetCurrentDirectoryW( tempdir ); + + status = RegCreateKeyExW( HKEY_LOCAL_MACHINE, L"System\\CurrentControlSet\\Services\\winetest", + 0, NULL, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, NULL ); + ok( !status, "RegCreateKeyExW returned %#x\n", status ); + status = RegSetValueExW( hkey, L"ReportID", 0, REG_DWORD, (void *)&report_id, sizeof(report_id) ); + ok( !status, "RegSetValueExW returned %#x\n", status ); + status = RegSetValueExW( hkey, L"PolledMode", 0, REG_DWORD, (void *)&polled, sizeof(polled) ); + ok( !status, "RegSetValueExW returned %#x\n", status ); + status = RegSetValueExW( hkey, L"Descriptor", 0, REG_BINARY, (void *)gamepad_desc, sizeof(gamepad_desc) ); + ok( !status, "RegSetValueExW returned %#x\n", status ); + status = RegSetValueExW( hkey, L"Attributes", 0, REG_BINARY, (void *)&attributes, sizeof(attributes) ); + ok( !status, "RegSetValueExW returned %#x\n", status ); + status = RegSetValueExW( hkey, L"Caps", 0, REG_BINARY, (void *)&caps, sizeof(caps) ); + ok( !status, "RegSetValueExW returned %#x\n", status ); + status = RegSetValueExW( hkey, L"Expect", 0, REG_BINARY, (void *)NULL, 0 ); + ok( !status, "RegSetValueExW returned %#x\n", status ); + status = RegSetValueExW( hkey, L"Input", 0, REG_BINARY, NULL, 0 ); + ok( !status, "RegSetValueExW returned %#x\n", status ); + fill_context( __LINE__, context, ARRAY_SIZE(context) ); + status = RegSetValueExW( hkey, L"Context", 0, REG_BINARY, (void *)context, sizeof(context) ); + ok( !status, "RegSetValueExW returned %#x\n", status ); + + pnp_driver_start( L"driver_hid.dll" ); + WaitForSingleObject( stop_event, INFINITE ); + pnp_driver_stop(); + + SetCurrentDirectoryW( cwd ); + + return 0; +} + START_TEST( hid ) { if (!dinput_test_init()) return; diff --git a/dlls/dinput/tests/hotplug.c b/dlls/dinput/tests/hotplug.c index afe51cf0e97..d271d9bc1db 100644 --- a/dlls/dinput/tests/hotplug.c +++ b/dlls/dinput/tests/hotplug.c @@ -31,7 +31,7 @@ #include "dinput.h" #include "dinputd.h" #include "devguid.h" -#include "mmsystem.h" +#include "dbt.h" #include "wine/hid.h" @@ -154,6 +154,285 @@ done: 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 %u\n", + header->dbch_devicetype ); + + winetest_push_context( "%u", device_change_count ); + + todo_wine_if( IsEqualGUID( &iface->dbcc_classguid, &control_class ) && !device_change_all ) + 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 %u\n", iface->dbcc_size ); + todo_wine + 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) ); + todo_wine + 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 %#x\n", (DWORD)wparam ); + else + ok( wparam == DBT_DEVICEARRIVAL, "got wparam %#x\n", (DWORD)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 %u\n", GetLastError() ); + + SetLastError( 0xdeadbeef ); + devnotify = RegisterDeviceNotificationA( NULL, NULL, 0 ); + todo_wine + ok( !devnotify, "RegisterDeviceNotificationA succeeded\n" ); + todo_wine + ok( GetLastError() == ERROR_INVALID_PARAMETER, "got error %u\n", GetLastError() ); + if (devnotify) UnregisterDeviceNotification( devnotify ); + + SetLastError( 0xdeadbeef ); + devnotify = RegisterDeviceNotificationA( (HWND)0xdeadbeef, NULL, 0 ); + todo_wine + ok( !devnotify, "RegisterDeviceNotificationA succeeded\n" ); + todo_wine + ok( GetLastError() == ERROR_INVALID_PARAMETER, "got error %u\n", GetLastError() ); + if (devnotify) UnregisterDeviceNotification( devnotify ); + + SetLastError( 0xdeadbeef ); + devnotify = RegisterDeviceNotificationA( hwnd, NULL, 2 ); + todo_wine + ok( !devnotify, "RegisterDeviceNotificationA succeeded\n" ); + todo_wine + ok( GetLastError() == ERROR_INVALID_PARAMETER, "got error %u\n", GetLastError() ); + if (devnotify) UnregisterDeviceNotification( devnotify ); + + 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 ); + todo_wine + ok( !devnotify, "RegisterDeviceNotificationA succeeded\n" ); + todo_wine + ok( GetLastError() == ERROR_INVALID_DATA || GetLastError() == ERROR_SERVICE_SPECIFIC_ERROR, + "got error %u\n", GetLastError() ); + if (devnotify) UnregisterDeviceNotification( devnotify ); + + 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 ); + todo_wine + ok( !devnotify, "RegisterDeviceNotificationA succeeded\n" ); + todo_wine + ok( GetLastError() == ERROR_INVALID_DATA || GetLastError() == ERROR_SERVICE_SPECIFIC_ERROR, + "got error %u\n", GetLastError() ); + if (devnotify) UnregisterDeviceNotification( devnotify ); + + 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 ); + todo_wine + ok( !devnotify, "RegisterDeviceNotificationA succeeded\n" ); + todo_wine + ok( GetLastError() == ERROR_INVALID_DATA || GetLastError() == ERROR_SERVICE_SPECIFIC_ERROR, + "got error %u\n", GetLastError() ); + if (devnotify) UnregisterDeviceNotification( devnotify ); + + 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 ); + todo_wine + ok( !devnotify, "RegisterDeviceNotificationA succeeded\n" ); + todo_wine + ok( GetLastError() == ERROR_INVALID_DATA || GetLastError() == ERROR_SERVICE_SPECIFIC_ERROR, + "got error %u\n", GetLastError() ); + if (devnotify) UnregisterDeviceNotification( devnotify ); + + 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 ); + todo_wine + ok( !devnotify, "RegisterDeviceNotificationA succeeded\n" ); + todo_wine + ok( GetLastError() == ERROR_INVALID_DATA || GetLastError() == ERROR_SERVICE_SPECIFIC_ERROR, + "got error %u\n", GetLastError() ); + if (devnotify) UnregisterDeviceNotification( devnotify ); + + devnotify = RegisterDeviceNotificationA( hwnd, &iface_filter_a, DEVICE_NOTIFY_WINDOW_HANDLE ); + ok( !!devnotify, "RegisterDeviceNotificationA failed, error %u\n", GetLastError() ); + while (PeekMessageW( &msg, hwnd, 0, 0, PM_REMOVE )) DispatchMessageW( &msg ); + + device_change_count = 0; + if (!strcmp( winetest_platform, "wine" )) device_change_expect = 4; + else 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 %u\n", GetLastError() ); + thread = CreateThread( NULL, 0, dinput_test_device_thread, stop_event, 0, NULL ); + ok( !!thread, "CreateThread failed, error %u\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 %u\n", GetLastError() ); + while (PeekMessageW( &msg, hwnd, 0, 0, PM_REMOVE )) DispatchMessageW( &msg ); + + device_change_count = 0; + if (!strcmp( winetest_platform, "wine" )) device_change_expect = 4; + else 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 %u\n", GetLastError() ); + thread = CreateThread( NULL, 0, dinput_test_device_thread, stop_event, 0, NULL ); + ok( !!thread, "CreateThread failed, error %u\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 %u\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 %u\n", GetLastError() ); + thread = CreateThread( NULL, 0, dinput_test_device_thread, stop_event, 0, NULL ); + ok( !!thread, "CreateThread failed, error %u\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; @@ -163,6 +442,8 @@ START_TEST( hotplug ) { test_input_lost( 0x700 ); test_input_lost( 0x800 ); + + test_RegisterDeviceNotification(); } CoUninitialize(); -- 2.34.1