-- v12: maintainers: Add a section for Windows.Devices.Enumeration. windows.devices.enumeration: Support parsing AQS filters in IDeviceInformationStatics::{FindAllAsyncAqsFilter, CreateWatcherAqsFilter}. windows.devices.enumeration/tests: Add tests for IDeviceInformationStatics::{FindAllAsyncAqsFilter, CreateWatcherAqsFilter}.
From: Vibhav Pant vibhavp@gmail.com
--- dlls/cfgmgr32/main.c | 4 +--- dlls/cfgmgr32/tests/cfgmgr32.c | 13 +++++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/dlls/cfgmgr32/main.c b/dlls/cfgmgr32/main.c index 35b78f8f8af..69004ea4b5c 100644 --- a/dlls/cfgmgr32/main.c +++ b/dlls/cfgmgr32/main.c @@ -418,10 +418,8 @@ static HRESULT devprop_filter_eval_compare( const DEV_OBJECT *obj, const DEVPROP cmp = memcmp( prop->Buffer, cmp_prop->Buffer, prop->BufferSize ); break; } - if (op == DEVPROP_OPERATOR_EQUALS) + if (op & DEVPROP_OPERATOR_EQUALS) ret = !cmp; - else if (op & DEVPROP_OPERATOR_EQUALS && !cmp) - ret = TRUE; else ret = (op & DEVPROP_OPERATOR_LESS_THAN) ? cmp < 0 : cmp > 0; } diff --git a/dlls/cfgmgr32/tests/cfgmgr32.c b/dlls/cfgmgr32/tests/cfgmgr32.c index 2652fac3abb..5841a1a8d4b 100644 --- a/dlls/cfgmgr32/tests/cfgmgr32.c +++ b/dlls/cfgmgr32/tests/cfgmgr32.c @@ -1145,6 +1145,19 @@ static void test_DevGetObjects( void ) } }
+ memset( filters, 0, sizeof( filters ) ); + filters[0] = valid_filter; + filters[0].Operator = DEVPROP_OPERATOR_NOT_EQUALS; + bool_val = FALSE; + len = 0; + objects = NULL; + hr = pDevGetObjects( DevObjectTypeDeviceInterface, DevQueryFlagNone, 0, NULL, 1, filters, &len, &objects ); + ok( hr == S_OK, "got hr %#lx\n", hr ); + ok( len > 0, "got len %lu\n", len ); + ok( !!objects, "got objects %p\n", objects ); + pDevFreeObjects( len, objects ); + bool_val = TRUE; + for (i = 0; i < ARRAY_SIZE( test_cases ); i++) { const DEV_OBJECT *objects = NULL;
From: Vibhav Pant vibhavp@gmail.com
--- dlls/propsys/propsys_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dlls/propsys/propsys_main.c b/dlls/propsys/propsys_main.c index 25d3eea4798..011c05eca96 100644 --- a/dlls/propsys/propsys_main.c +++ b/dlls/propsys/propsys_main.c @@ -829,7 +829,7 @@ static struct system_property_description system_properties[] = { {L"System.Devices.ContainerId", &PKEY_Devices_ContainerId, VT_CLSID}, {L"System.Devices.InterfaceClassGuid", &PKEY_Devices_InterfaceClassGuid, VT_CLSID}, - {L"System.Devices.DeviceInstanceId", &PKEY_Devices_DeviceInstanceId, VT_CLSID}, + {L"System.Devices.DeviceInstanceId", &PKEY_Devices_DeviceInstanceId, VT_LPWSTR}, {L"System.Devices.InterfaceEnabled", &PKEY_Devices_InterfaceEnabled, VT_BOOL}, {L"System.Devices.ClassGuid", &PKEY_Devices_ClassGuid, VT_CLSID}, {L"System.Devices.CompatibleIds", &PKEY_Devices_CompatibleIds, VT_VECTOR | VT_LPWSTR},
From: Vibhav Pant vibhavp@gmail.com
--- .../tests/devices.c | 459 ++++++++++++++---- 1 file changed, 377 insertions(+), 82 deletions(-)
diff --git a/dlls/windows.devices.enumeration/tests/devices.c b/dlls/windows.devices.enumeration/tests/devices.c index e4bb1349f6d..b25f656ab75 100644 --- a/dlls/windows.devices.enumeration/tests/devices.c +++ b/dlls/windows.devices.enumeration/tests/devices.c @@ -17,6 +17,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#include "minwindef.h" #include <stdarg.h>
#define COBJMACROS @@ -54,97 +55,133 @@ static void check_interface_(unsigned int line, void *obj, const IID *iid, BOOL IUnknown_Release(unk); }
-struct device_watcher_handler +struct inspectable_event_handler { - ITypedEventHandler_DeviceWatcher_IInspectable ITypedEventHandler_DeviceWatcher_IInspectable_iface; + ITypedEventHandler_IInspectable_IInspectable iface; + const GUID *iid; + void (*callback)( IInspectable *, IInspectable *, void * ); + void *data; LONG ref; - - unsigned int test_deviceinformation : 1; - LONG devices_added; - HANDLE event; - BOOL invoked; - IInspectable *args; };
-static inline struct device_watcher_handler *impl_from_ITypedEventHandler_DeviceWatcher_IInspectable( - ITypedEventHandler_DeviceWatcher_IInspectable *iface ) +static inline struct inspectable_event_handler *impl_from_ITypedEventHandler_IInspectable_IInspectable( ITypedEventHandler_IInspectable_IInspectable *iface ) { - return CONTAINING_RECORD( iface, struct device_watcher_handler, ITypedEventHandler_DeviceWatcher_IInspectable_iface ); + return CONTAINING_RECORD( iface, struct inspectable_event_handler, iface ); }
-static HRESULT WINAPI device_watcher_handler_QueryInterface( - ITypedEventHandler_DeviceWatcher_IInspectable *iface, REFIID iid, void **out ) +static HRESULT WINAPI inspectable_event_handler_QueryInterface( ITypedEventHandler_IInspectable_IInspectable *iface, REFIID iid, void **out ) { - struct device_watcher_handler *impl = impl_from_ITypedEventHandler_DeviceWatcher_IInspectable( iface ); + struct inspectable_event_handler *impl = impl_from_ITypedEventHandler_IInspectable_IInspectable( iface );
- if (IsEqualGUID( iid, &IID_IUnknown ) || - IsEqualGUID( iid, &IID_ITypedEventHandler_DeviceWatcher_IInspectable ) || - (impl->test_deviceinformation && IsEqualGUID( iid, &IID_ITypedEventHandler_DeviceWatcher_DeviceInformation ))) + if (winetest_debug > 1) trace( "(%p, %s, %p)\n", iface, debugstr_guid( iid ), out ); + if (IsEqualGUID( iid, &IID_IUnknown ) || IsEqualGUID( iid, &IID_IAgileObject) || IsEqualGUID( iid, impl->iid )) { - IUnknown_AddRef( &impl->ITypedEventHandler_DeviceWatcher_IInspectable_iface ); - *out = &impl->ITypedEventHandler_DeviceWatcher_IInspectable_iface; + ITypedEventHandler_IInspectable_IInspectable_AddRef((*out = &impl->iface.lpVtbl)); return S_OK; }
- trace( "%s not implemented, returning E_NO_INTERFACE.\n", debugstr_guid( iid ) ); *out = NULL; + if (winetest_debug > 1) trace( "%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid( iid ) ); return E_NOINTERFACE; }
-static ULONG WINAPI device_watcher_handler_AddRef( ITypedEventHandler_DeviceWatcher_IInspectable *iface ) +static ULONG WINAPI inspectable_event_handler_AddRef( ITypedEventHandler_IInspectable_IInspectable *iface ) { - struct device_watcher_handler *impl = impl_from_ITypedEventHandler_DeviceWatcher_IInspectable( iface ); - ULONG ref = InterlockedIncrement( &impl->ref ); - return ref; + struct inspectable_event_handler *impl = impl_from_ITypedEventHandler_IInspectable_IInspectable( iface ); + return InterlockedIncrement( &impl->ref ); }
-static ULONG WINAPI device_watcher_handler_Release( ITypedEventHandler_DeviceWatcher_IInspectable *iface ) +static ULONG WINAPI inspectable_event_handler_Release( ITypedEventHandler_IInspectable_IInspectable *iface ) { - struct device_watcher_handler *impl = impl_from_ITypedEventHandler_DeviceWatcher_IInspectable( iface ); + struct inspectable_event_handler *impl = impl_from_ITypedEventHandler_IInspectable_IInspectable( iface ); ULONG ref = InterlockedDecrement( &impl->ref ); + + if (!ref) free( impl ); return ref; }
-static void test_DeviceInformation_obj( int line, IDeviceInformation *info ); -static HRESULT WINAPI device_watcher_handler_Invoke( ITypedEventHandler_DeviceWatcher_IInspectable *iface, - IDeviceWatcher *sender, IInspectable *args ) +static HRESULT WINAPI inspectable_event_handler_Invoke( ITypedEventHandler_IInspectable_IInspectable *iface, IInspectable *arg1, IInspectable *arg2 ) { - struct device_watcher_handler *impl = impl_from_ITypedEventHandler_DeviceWatcher_IInspectable( iface ); + struct inspectable_event_handler *impl = impl_from_ITypedEventHandler_IInspectable_IInspectable( iface );
- impl->invoked = TRUE; - impl->args = args; + if (winetest_debug > 1) trace( "(%p, %p, %p)\n", iface, arg1, arg2 ); + impl->callback( arg1, arg2, impl->data ); + return S_OK; +}
- if (impl->test_deviceinformation) - { - IDeviceInformation *info; - HRESULT hr; +static const ITypedEventHandler_IInspectable_IInspectableVtbl inspectable_event_handler_vtbl = { + /* IUnknown */ + inspectable_event_handler_QueryInterface, + inspectable_event_handler_AddRef, + inspectable_event_handler_Release, + /* ITypedEventHandler<IInspectable *, IInspectable *> */ + inspectable_event_handler_Invoke +};
- hr = IInspectable_QueryInterface( args, &IID_IDeviceInformation, (void *)&info ); - ok( hr == S_OK, "got hr %#lx\n", hr ); - test_DeviceInformation_obj( __LINE__, info ); - InterlockedIncrement( &impl->devices_added ); - IDeviceInformation_Release( info ); - } +static ITypedEventHandler_IInspectable_IInspectable *inspectable_event_handler_create( REFIID iid, void (*callback)( IInspectable *, IInspectable *, void * ), + void *data ) +{ + struct inspectable_event_handler *handler; + + if (!(handler = calloc( 1, sizeof( *handler )))) return NULL; + handler->iface.lpVtbl = &inspectable_event_handler_vtbl; + handler->iid = iid; + handler->callback = callback; + handler->data = data; + handler->ref = 1; + return &handler->iface; +}
- SetEvent( impl->event ); +struct device_watcher_added_handler_data +{ + LONG devices_added; +};
- return S_OK; +static void test_DeviceInformation_obj( int line, IDeviceInformation *info ); +static void device_watcher_added_callback( IInspectable *arg1, IInspectable *arg2, void *param ) +{ + struct device_watcher_added_handler_data *data = param; + IDeviceInformation *device_info; + HRESULT hr; + + check_interface( arg1, &IID_IDeviceWatcher, TRUE ); + hr = IInspectable_QueryInterface( arg2, &IID_IDeviceInformation, (void **)&device_info ); + ok( hr == S_OK, "got hr %#lx\n", hr ); + + InterlockedIncrement( &data->devices_added ); + test_DeviceInformation_obj( __LINE__, device_info ); + IDeviceInformation_Release( device_info ); +} + +static ITypedEventHandler_DeviceWatcher_DeviceInformation *device_watcher_added_handler_create( struct device_watcher_added_handler_data *data ) +{ + return (ITypedEventHandler_DeviceWatcher_DeviceInformation *)inspectable_event_handler_create( &IID_ITypedEventHandler_DeviceWatcher_DeviceInformation, + device_watcher_added_callback, data ); }
-static const ITypedEventHandler_DeviceWatcher_IInspectableVtbl device_watcher_handler_vtbl = +struct device_watcher_once_handler_data { - device_watcher_handler_QueryInterface, - device_watcher_handler_AddRef, - device_watcher_handler_Release, - /* ITypedEventHandler<DeviceWatcher*,IInspectable*> methods */ - device_watcher_handler_Invoke, + HANDLE event; + LONG invoked; };
-static void device_watcher_handler_create( struct device_watcher_handler *impl ) +static void device_watcher_once_callback( IInspectable *arg1, IInspectable *arg2, void *param ) { - impl->ITypedEventHandler_DeviceWatcher_IInspectable_iface.lpVtbl = &device_watcher_handler_vtbl; - impl->invoked = FALSE; - impl->ref = 1; + struct device_watcher_once_handler_data *data = param; + BOOL ret; + + check_interface( arg1, &IID_IDeviceWatcher, TRUE ); + ok( !arg2, "got arg2 %p\n", arg2 ); + + ok( InterlockedIncrement( &data->invoked ), "event handler invoked more than once\n" ); + ret = SetEvent( data->event ); + ok( ret, "SetEvent failed, last error %lu \n", GetLastError() ); +} + +static ITypedEventHandler_DeviceWatcher_IInspectable *device_watcher_once_handler_create( struct device_watcher_once_handler_data *data ) +{ + return (ITypedEventHandler_DeviceWatcher_IInspectable *)inspectable_event_handler_create( &IID_ITypedEventHandler_DeviceWatcher_IInspectable, + device_watcher_once_callback, data ); }
struct device_information_collection_async_handler @@ -255,9 +292,10 @@ static void await_device_information_collection_( int line, IAsyncOperation_Devi ok_(__FILE__, line)( ret, "CloseHandle failed, error %lu\n", GetLastError() ); }
-#define check_device_information_collection_async( a, b, c, d, e ) check_device_information_collection_async_( __LINE__, a, b, c, d, e ) +#define check_device_information_collection_async( a, b, c, d, e ) check_device_information_collection_async_( __LINE__, a, TRUE, b, c, d, e ) +#define check_device_information_collection_async_no_id( a, b, c, d ) check_device_information_collection_async_( __LINE__, a, FALSE, 0, b, c, d ) static void check_device_information_collection_async_( int line, IAsyncOperation_DeviceInformationCollection *async, - UINT32 expect_id, AsyncStatus expect_status, + BOOL test_id, UINT32 expect_id, AsyncStatus expect_status, HRESULT expect_hr, IVectorView_DeviceInformation **result ) { AsyncStatus async_status; @@ -272,7 +310,8 @@ static void check_device_information_collection_async_( int line, IAsyncOperatio hr = IAsyncInfo_get_Id( async_info, &async_id ); if (expect_status < 4) ok_(__FILE__, line)( hr == S_OK, "get_Id returned %#lx\n", hr ); else ok_(__FILE__, line)( hr == E_ILLEGAL_METHOD_CALL, "get_Id returned %#lx\n", hr ); - ok_(__FILE__, line)( async_id == expect_id, "got id %u\n", async_id ); + if (test_id) + ok_(__FILE__, line)( async_id == expect_id, "got id %u\n", async_id );
async_status = 0xdeadbeef; hr = IAsyncInfo_get_Status( async_info, &async_status ); @@ -329,7 +368,10 @@ static void test_DeviceInformation( void ) { static const WCHAR *device_info_name = L"Windows.Devices.Enumeration.DeviceInformation";
- static struct device_watcher_handler stopped_handler, added_handler, enumerated_handler; + ITypedEventHandler_DeviceWatcher_IInspectable *stopped_handler, *enumerated_handler; + struct device_watcher_once_handler_data enumerated_data = {0}, stopped_data = {0}; + ITypedEventHandler_DeviceWatcher_DeviceInformation *device_added_handler; + struct device_watcher_added_handler_data device_added_data = {0}; EventRegistrationToken stopped_token, added_token, enumerated_token; IInspectable *inspectable, *inspectable2; IActivationFactory *factory; @@ -348,14 +390,14 @@ static void test_DeviceInformation( void ) HRESULT hr; ULONG ref;
- device_watcher_handler_create( &added_handler ); - device_watcher_handler_create( &stopped_handler ); - device_watcher_handler_create( &enumerated_handler ); + enumerated_data.event = CreateEventW( NULL, FALSE, FALSE, NULL ); + ok( !!enumerated_data.event, "failed to create event, got error %lu\n", GetLastError() ); + stopped_data.event = CreateEventW( NULL, FALSE, FALSE, NULL ); + ok( !!stopped_data.event, "failed to create event, got error %lu\n", GetLastError() );
- stopped_handler.event = CreateEventW( NULL, FALSE, FALSE, NULL ); - ok( !!stopped_handler.event, "failed to create event, got error %lu\n", GetLastError() ); - enumerated_handler.event = CreateEventW( NULL, FALSE, FALSE, NULL ); - ok( !!enumerated_handler.event, "failed to create event, got error %lu\n", GetLastError() ); + device_added_handler = device_watcher_added_handler_create( &device_added_data ); + stopped_handler = device_watcher_once_handler_create( &stopped_data ); + enumerated_handler = device_watcher_once_handler_create( &enumerated_data );
hr = WindowsCreateString( device_info_name, wcslen( device_info_name ), &str ); ok( hr == S_OK, "got hr %#lx\n", hr ); @@ -401,9 +443,10 @@ static void test_DeviceInformation( void ) ref = IDeviceWatcher_Release( watcher ); ok( ref == 1, "got ref %lu\n", ref );
- hr = IDeviceWatcher_add_Added( device_watcher, (void *)&added_handler.ITypedEventHandler_DeviceWatcher_IInspectable_iface, &added_token ); + device_added_data.devices_added = 0; + hr = IDeviceWatcher_add_Added( device_watcher, device_added_handler, &added_token ); ok( hr == S_OK, "got hr %#lx\n", hr ); - hr = IDeviceWatcher_add_Stopped( device_watcher, &stopped_handler.ITypedEventHandler_DeviceWatcher_IInspectable_iface, &stopped_token ); + hr = IDeviceWatcher_add_Stopped( device_watcher, stopped_handler, &stopped_token ); ok( hr == S_OK, "got hr %#lx\n", hr );
hr = IDeviceWatcher_get_Status( device_watcher, &status ); @@ -420,13 +463,12 @@ static void test_DeviceInformation( void ) ok( ref == 2, "got ref %lu\n", ref ); hr = IDeviceWatcher_Stop( device_watcher ); ok( hr == S_OK, "got hr %#lx\n", hr ); - ok( !WaitForSingleObject( stopped_handler.event, 1000 ), "wait for stopped_handler.event failed\n" ); + ok( !WaitForSingleObject( stopped_data.event, 1000 ), "wait for stopped_handler.event failed\n" );
hr = IDeviceWatcher_get_Status( device_watcher, &status ); ok( hr == S_OK, "got hr %#lx\n", hr ); ok( status == DeviceWatcherStatus_Stopped, "got status %u\n", status ); - ok( stopped_handler.invoked, "stopped_handler not invoked\n" ); - ok( stopped_handler.args == NULL, "stopped_handler not invoked\n" ); + ok( stopped_data.invoked, "stopped_handler not invoked\n" );
IDeviceWatcher_Release( device_watcher ); IInspectable_Release( inspectable2 ); @@ -448,15 +490,16 @@ static void test_DeviceInformation( void ) check_interface( device_watcher, &IID_IAgileObject, TRUE ); check_interface( device_watcher, &IID_IDeviceWatcher, TRUE );
- hr = IDeviceWatcher_add_Added( device_watcher, (void *)&added_handler.ITypedEventHandler_DeviceWatcher_IInspectable_iface, &added_token ); + hr = IDeviceWatcher_add_Added( device_watcher, device_added_handler, &added_token ); ok( hr == S_OK, "got hr %#lx\n", hr ); - hr = IDeviceWatcher_add_Stopped( device_watcher, &stopped_handler.ITypedEventHandler_DeviceWatcher_IInspectable_iface, &stopped_token ); + hr = IDeviceWatcher_add_Stopped( device_watcher, stopped_handler, &stopped_token ); ok( hr == S_OK, "got hr %#lx\n", hr );
hr = IDeviceWatcher_get_Status( device_watcher, &status ); ok( hr == S_OK, "got hr %#lx\n", hr ); ok( status == DeviceWatcherStatus_Created, "got status %u\n", status );
+ stopped_data.invoked = 0; hr = IDeviceWatcher_Start( device_watcher ); ok( hr == S_OK, "got hr %#lx\n", hr ); hr = IDeviceWatcher_get_Status( device_watcher, &status ); @@ -467,13 +510,12 @@ static void test_DeviceInformation( void ) ok( ref == 2, "got ref %lu\n", ref ); hr = IDeviceWatcher_Stop( device_watcher ); ok( hr == S_OK, "got hr %#lx\n", hr ); - ok( !WaitForSingleObject( stopped_handler.event, 1000 ), "wait for stopped_handler.event failed\n" ); + ok( !WaitForSingleObject( stopped_data.event, 1000 ), "wait for stopped_handler.event failed\n" );
hr = IDeviceWatcher_get_Status( device_watcher, &status ); ok( hr == S_OK, "got hr %#lx\n", hr ); ok( status == DeviceWatcherStatus_Stopped, "got status %u\n", status ); - ok( stopped_handler.invoked, "stopped_handler not invoked\n" ); - ok( stopped_handler.args == NULL, "stopped_handler not invoked\n" ); + ok( stopped_data.invoked, "stopped_handler not invoked\n" );
IDeviceWatcher_Release( device_watcher );
@@ -482,16 +524,15 @@ static void test_DeviceInformation( void )
if (device_watcher) { - added_handler.test_deviceinformation = 1; - hr = IDeviceWatcher_add_Added( device_watcher, (void *)&added_handler.ITypedEventHandler_DeviceWatcher_IInspectable_iface, &added_token ); + hr = IDeviceWatcher_add_Added( device_watcher, device_added_handler, &added_token ); ok( hr == S_OK, "got hr %#lx\n", hr ); - hr = IDeviceWatcher_add_EnumerationCompleted( device_watcher, (void *)&enumerated_handler.ITypedEventHandler_DeviceWatcher_IInspectable_iface, &enumerated_token ); + hr = IDeviceWatcher_add_EnumerationCompleted( device_watcher, enumerated_handler, &enumerated_token ); ok( hr == S_OK, "got hr %#lx\n", hr );
hr = IDeviceWatcher_Start( device_watcher ); ok( hr == S_OK, "got hr %#lx\n", hr ); - ok( !WaitForSingleObject( enumerated_handler.event, 5000 ), "wait for enumerated_handler.event failed\n" ); - ok( added_handler.devices_added > 0, "devices_added should be greater than 0\n" ); + ok( !WaitForSingleObject( enumerated_data.event, 5000 ), "wait for enumerated_handler.event failed\n" ); + ok( device_added_data.devices_added > 0, "devices_added should be greater than 0\n" ); hr = IDeviceWatcher_get_Status( device_watcher, &status ); ok( hr == S_OK, "got hr %#lx\n", hr ); ok( status == DeviceWatcherStatus_EnumerationCompleted, "got status %u\n", status ); @@ -531,8 +572,261 @@ skip_device_statics:
done: WindowsDeleteString( str ); - CloseHandle( stopped_handler.event ); - CloseHandle( enumerated_handler.event ); + ITypedEventHandler_DeviceWatcher_DeviceInformation_Release( device_added_handler ); + ITypedEventHandler_DeviceWatcher_IInspectable_Release( stopped_handler ); + ITypedEventHandler_DeviceWatcher_IInspectable_Release( enumerated_handler ); + CloseHandle( stopped_data.event ); + CloseHandle( enumerated_data.event ); +} + +struct test_case_filter +{ + const WCHAR *filter; + HRESULT hr; + BOOL no_results; + HRESULT start_hr; /* Code returned by IDeviceWatcher::Start */ +}; + +#define test_FindAllAsyncAqsFilter( statics, test_cases, todo_hr, todo_results ) \ + test_FindAllAsyncAqsFilter_( statics, #test_cases, test_cases, ARRAY_SIZE( test_cases ), todo_hr, todo_results ) + +static void test_FindAllAsyncAqsFilter_( IDeviceInformationStatics *statics, const char *name, const struct test_case_filter *test_cases, SIZE_T len, + BOOL todo_hr, BOOL todo_results ) +{ + SIZE_T i; + + return; + + for (i = 0; i < len; i++) + { + IAsyncOperation_DeviceInformationCollection *devices_async; + const struct test_case_filter *test_case = &test_cases[i]; + IVectorView_DeviceInformation *devices; + UINT32 size; + HSTRING str; + HRESULT hr; + + winetest_push_context("%s[%Iu]", name, i ); + + hr = WindowsCreateString( test_case->filter, wcslen( test_case->filter ), &str ); + ok( hr == S_OK, "got hr %#lx\n", hr ); + hr = IDeviceInformationStatics_FindAllAsyncAqsFilter( statics, str, &devices_async ); + todo_wine_if( todo_hr ) ok( hr == test_case->hr, "got hr %#lx != %#lx\n", hr, test_case->hr ); + WindowsDeleteString( str ); + if (FAILED( hr ) || FAILED( test_case->hr )) + { + if (SUCCEEDED( hr )) IAsyncOperation_DeviceInformationCollection_Release( devices_async ); + winetest_pop_context(); + continue; + } + await_device_information_collection( devices_async ); + check_device_information_collection_async_no_id( devices_async, Completed, S_OK, &devices ); + + hr = IVectorView_DeviceInformation_get_Size( devices, &size ); + ok( hr == S_OK, "got hr %#lx\n", hr ); + todo_wine_if( todo_results ) ok( test_case->no_results == !size, "got size %I32u\n", size ); + IAsyncOperation_DeviceInformationCollection_Release( devices_async ); + IVectorView_DeviceInformation_Release( devices ); + + winetest_pop_context(); + } +} + +#define test_CreateWatcherAqsFilter( statics, test_cases, todo_hr, todo_start_hr, todo_wait, todo_results ) \ + test_CreateWatcherAqsFilter_( statics, #test_cases, test_cases, ARRAY_SIZE( test_cases ), todo_hr, todo_start_hr, todo_wait, todo_results ) + +static void test_CreateWatcherAqsFilter_( IDeviceInformationStatics *statics, const char *name, const struct test_case_filter *test_cases, SIZE_T len, + BOOL todo_hr, BOOL todo_start_hr, BOOL todo_wait, BOOL todo_results ) +{ + ITypedEventHandler_DeviceWatcher_IInspectable *enumerated_handler, *stopped_handler; + struct device_watcher_once_handler_data enumerated_data = {0}, stopped_data = {0}; + SIZE_T i; + + enumerated_data.event = CreateEventW( NULL, FALSE, FALSE, NULL ); + ok( !!enumerated_data.event, "CreateEventW failed, last error %lu\n", GetLastError() ); + enumerated_handler = device_watcher_once_handler_create( &enumerated_data ); + + stopped_data.event = CreateEventW( NULL, FALSE, FALSE, NULL ); + ok( !!stopped_data.event, "CreateEventW failed, last error %lu\n", GetLastError() ); + stopped_handler = device_watcher_once_handler_create( &stopped_data ); + + for (i = 0; i < len; i++) + { + EventRegistrationToken added_token, enumerated_token, stopped_token; + ITypedEventHandler_DeviceWatcher_DeviceInformation *added_handler; + struct device_watcher_added_handler_data added_data = {0}; + const struct test_case_filter *test_case = &test_cases[i]; + IDeviceWatcher *watcher; + HSTRING str; + HRESULT hr; + DWORD ret; + + winetest_push_context("%s[%Iu]", name, i ); + + hr = WindowsCreateString( test_case->filter, wcslen( test_case->filter ), &str ); + ok( hr == S_OK, "got hr %#lx\n", hr ); + hr = IDeviceInformationStatics_CreateWatcherAqsFilter( statics, str, &watcher ); + todo_wine_if( todo_hr ) ok( hr == test_case->hr, "got hr %#lx != %#lx\n", hr, test_case->hr ); + WindowsDeleteString( str ); + if (FAILED( hr ) || FAILED( test_case->hr )) + { + if (SUCCEEDED( hr )) IDeviceWatcher_Release( watcher ); + winetest_pop_context(); + continue; + } + + added_handler = device_watcher_added_handler_create( &added_data ); + hr = IDeviceWatcher_add_Added( watcher, added_handler, &added_token ); + ok( hr == S_OK, "got hr %#lx\n", hr ); + ITypedEventHandler_DeviceWatcher_DeviceInformation_Release( added_handler ); + hr = IDeviceWatcher_add_EnumerationCompleted( watcher, enumerated_handler, &enumerated_token ); + ok( hr == S_OK, "got hr %#lx\n", hr ); + hr = IDeviceWatcher_add_Stopped( watcher, stopped_handler, &stopped_token ); + ok( hr == S_OK, "got hr %#lx\n", hr ); + hr = IDeviceWatcher_Start( watcher ); + todo_wine_if( todo_start_hr ) ok( hr == test_case->start_hr, "got hr %#lx\n", hr ); + if (FAILED( hr ) || FAILED( test_case->start_hr )) goto next; + + ret = WaitForSingleObject( enumerated_data.event, 500 ); + todo_wine_if( todo_wait ) ok( !ret, "WaitForSingleObject returned %lu\n", ret ); + + hr = IDeviceWatcher_Stop( watcher ); + ret = WaitForSingleObject( stopped_data.event, 500 ); + todo_wine_if( todo_wait ) ok( !ret, "WaitForSingleObject returned %lu\n", ret ); + todo_wine_if( todo_results ) ok( test_case->no_results == !added_data.devices_added, "got devices_added %lu\n", added_data.devices_added ); + + next: + hr = IDeviceWatcher_remove_Added( watcher, added_token ); + ok( hr == S_OK, "got hr %#lx\n", hr ); + hr = IDeviceWatcher_remove_Stopped( watcher, stopped_token ); + ok( hr == S_OK, "got hr %#lx\n", hr ); + hr = IDeviceWatcher_remove_EnumerationCompleted( watcher, enumerated_token ); + ok( hr == S_OK, "got hr %#lx\n", hr ); + IDeviceWatcher_Release( watcher ); + + winetest_pop_context(); + + enumerated_data.invoked = 0; + stopped_data.invoked = 0; + } + + ITypedEventHandler_DeviceWatcher_IInspectable_Release( enumerated_handler ); + ITypedEventHandler_DeviceWatcher_IInspectable_Release( stopped_handler ); + CloseHandle( enumerated_data.event ); + CloseHandle( stopped_data.event ); +} + +static const struct test_case_filter filters_empty[] = { { L"", S_OK } }; +static const struct test_case_filter filters_simple[] = { + { L"System.Devices.InterfaceEnabled := System.StructuredQueryType.Boolean#True", S_OK }, + { L"System.Devices.InterfaceEnabled : System.StructuredQueryType.Boolean#True", S_OK }, + { L"\t\nSystem.Devices.InterfaceEnabled\n\t\n:=\r\n\tSystem.StructuredQueryType.Boolean#True ", S_OK }, + { L"System.Devices.InterfaceEnabled :NOT System.StructuredQueryType.Boolean#False", S_OK }, + { L"System.Devices.InterfaceEnabled :<> System.StructuredQueryType.Boolean#False", S_OK }, + { L"System.Devices.InterfaceEnabled :- System.StructuredQueryType.Boolean#False", S_OK }, + { L"System.Devices.InterfaceEnabled :\u2260 System.StructuredQueryType.Boolean#False", S_OK }, + { L"System.Devices.InterfaceClassGuid :\u2260 {deadbeef-dead-beef-dead-deadbeefdead}", S_OK }, + { L"System.Devices.InterfaceEnabled :NOT []", S_OK }, + { L"System.Devices.InterfaceEnabled := System.StructuredQueryType.Boolean#True " + L"System.Devices.DeviceInstanceId :NOT []", S_OK }, +}; +/* Propsys canonical property names are case-sensitive, but not in AQS. */ +static const struct test_case_filter filters_case_insensitive[] = { + { L"SYSTEM.DEVICES.InterfaceEnabled := System.StructuredQueryType.Boolean#True", S_OK }, + { L"system.devices.interfaceenabled : SYSTEM.STRUCTUREDQUERYTYPE.BOOLEAN#true", S_OK }, + { L"SYSTEM.DEVICES.INTERFACEENABLED :- SYSTEM.STRUCTUREDQUERYTYPE.BOOLEAN#FALSE", S_OK }, + { L"system.devices.interfaceenabled :NOT system.structuredquerytype.boolean#false", S_OK }, + { L"SYSTEM.DEVICES.INTERFACECLASSGUID :\u2260 {DEADBEEF-DEAD-BEEF-DEAD-DEADBEEFDEAD}", S_OK }, +}; +/* Queries that succeed but don't return any results. */ +static const struct test_case_filter filters_no_results[] = { + { L"System.Devices.InterfaceClassGuid := {deadbeef-dead-beef-dead-deadbeefdead}", S_OK, TRUE }, + { L"System.Devices.DeviceInstanceId := "invalid\device\id"", S_OK, TRUE }, + { L"System.Devices.InterfaceEnabled := []", S_OK, TRUE }, + { L"System.Devices.DeviceInstanceId := []", S_OK, TRUE }, + { L"System.Devices.InterfaceEnabled := System.StructuredQueryType.Boolean#True " + L"System.Devices.InterfaceEnabled := System.StructuredQueryType.Boolean#False", S_OK, TRUE }, + { L"System.Devices.InterfaceClassGuid :< {deadbeef-dead-beef-dead-deadbeefdead} OR " + L"System.Devices.InterfaceClassGuid :> {deadbeef-dead-beef-dead-deadbeefdead}", S_OK, TRUE } +}; +static const struct test_case_filter filters_invalid_comparand_type[] = { + { L"System.Devices.InterfaceEnabled := "foo"", E_INVALIDARG }, + { L"System.Devices.InterfaceEnabled := {deadbeef-dead-beef-dead-deadbeefdead}", E_INVALIDARG }, + { L"System.Devices.InterfaceClassGuid := System.StructuredQueryType.Boolean#True", E_INVALIDARG }, + { L"System.Devices.InterfaceClassGuid :- System.StructuredQueryType.Boolean#True", E_INVALIDARG }, + { L"System.Devices.InterfaceEnabled := System.Devices.InterfaceEnabled", E_INVALIDARG }, /* RHS is parsed as a string. */ +}; +static const struct test_case_filter filters_invalid_empty[] = { + { L" ", E_INVALIDARG }, + { L"\t", E_INVALIDARG }, + { L"\n", E_INVALIDARG }, +}; +/* CreateWatcher* accepts whitespace-only strings, the error will be returned by IDeviceWatcher_Start instead. */ +static const struct test_case_filter filters_empty_watcher[] = { + { L" ", S_OK, FALSE, E_INVALIDARG }, + { L"\t", S_OK, FALSE, E_INVALIDARG }, + { L"\n", S_OK, FALSE, E_INVALIDARG }, +}; +static const struct test_case_filter filters_invalid_operator[] = { + { L"System.Devices.InterfaceEnabled = System.StructuredQueryType.Boolean#True", E_INVALIDARG }, /* Missing colon */ + { L"System.Devices.InterfacesEnabled :not System.StructuredQueryType.Boolean#True", E_INVALIDARG }, /* Named operators are case-sensitive */ + { L"System.Devices.InterfacesEnabled System.StructuredQueryType.Boolean#True", E_INVALIDARG }, + { L"System.Devices.InterfacesEnabled := := System.StructuredQueryType.Boolean#True", E_INVALIDARG }, + { L"System.Devices.InterfacesEnabled :!= System.StructuredQueryType.Boolean#True", E_INVALIDARG }, + { L"System.Devices.InterfacesEnabled :\U0001F377 System.StructuredQueryType.Boolean#True", E_INVALIDARG }, + { L"System.Devices.InterfacesEnabled", E_INVALIDARG }, + { L"System.StructuredQueryType.Boolean#True", E_INVALIDARG }, +}; +static const struct test_case_filter filters_invalid_operand[] = { + { L"System.Devices.InterfaceEnabled := ", E_INVALIDARG }, + { L":= System.StructuredQueryType.Boolean#True", E_INVALIDARG }, + { L":=", E_INVALIDARG }, + { L" System.StructuredQueryType.Boolean#True := System.StructuredQueryType.Boolean#True", E_INVALIDARG }, +}; + +static void test_aqs_filters( void ) +{ + static const WCHAR *class_name = RuntimeClass_Windows_Devices_Enumeration_DeviceInformation; + IDeviceInformationStatics *statics; + HSTRING str; + HRESULT hr; + + hr = WindowsCreateString( class_name, wcslen( class_name ), &str ); + ok(hr == S_OK, "got hr %#lx\n", hr ); + hr = RoGetActivationFactory( str, &IID_IDeviceInformationStatics, (void **)&statics ); + ok( hr == S_OK || broken( hr == REGDB_E_CLASSNOTREG ), "got hr %#lx\n", hr ); + WindowsDeleteString( str ); + if (hr == REGDB_E_CLASSNOTREG) + { + win_skip( "%s runtimeclass, not registered.\n", wine_dbgstr_w( class_name ) ); + return; + } + + test_FindAllAsyncAqsFilter( statics, filters_empty, TRUE, FALSE ); + test_CreateWatcherAqsFilter( statics, filters_empty, FALSE, FALSE, FALSE, FALSE ); + + test_FindAllAsyncAqsFilter( statics, filters_simple, TRUE, FALSE ); + test_CreateWatcherAqsFilter( statics, filters_simple, FALSE, FALSE, TRUE, TRUE ); + + test_FindAllAsyncAqsFilter( statics, filters_case_insensitive, TRUE, FALSE ); + test_CreateWatcherAqsFilter( statics, filters_case_insensitive, FALSE, FALSE, TRUE, TRUE ); + + test_FindAllAsyncAqsFilter( statics, filters_no_results, TRUE, FALSE ); + test_CreateWatcherAqsFilter( statics, filters_no_results, FALSE, FALSE, TRUE, FALSE ); + + test_FindAllAsyncAqsFilter( statics, filters_invalid_comparand_type, TRUE, FALSE ); + test_CreateWatcherAqsFilter( statics, filters_invalid_comparand_type, TRUE, FALSE, FALSE, FALSE ); + + test_FindAllAsyncAqsFilter( statics, filters_invalid_empty, TRUE, FALSE ); + test_CreateWatcherAqsFilter( statics, filters_empty_watcher, FALSE, TRUE, FALSE, FALSE ); + + test_FindAllAsyncAqsFilter( statics, filters_invalid_operator, TRUE, FALSE ); + test_CreateWatcherAqsFilter( statics, filters_invalid_operator, TRUE, FALSE, FALSE, FALSE ); + + test_FindAllAsyncAqsFilter( statics, filters_invalid_operand, TRUE, FALSE ); + test_CreateWatcherAqsFilter( statics, filters_invalid_operand, TRUE, FALSE, FALSE, FALSE ); + + IDeviceInformationStatics_Release( statics ); }
static void test_DeviceAccessInformation( void ) @@ -601,6 +895,7 @@ START_TEST( devices )
test_DeviceInformation(); test_DeviceAccessInformation(); + test_aqs_filters();
RoUninitialize(); }
From: Vibhav Pant vibhavp@gmail.com
--- dlls/windows.devices.enumeration/Makefile.in | 5 +- dlls/windows.devices.enumeration/aqs.c | 472 ++++++++++++++++++ dlls/windows.devices.enumeration/aqs.h | 104 ++++ dlls/windows.devices.enumeration/aqs.y | 175 +++++++ dlls/windows.devices.enumeration/main.c | 144 +++++- dlls/windows.devices.enumeration/private.h | 1 + .../tests/devices.c | 28 +- 7 files changed, 896 insertions(+), 33 deletions(-) create mode 100644 dlls/windows.devices.enumeration/aqs.c create mode 100644 dlls/windows.devices.enumeration/aqs.h create mode 100644 dlls/windows.devices.enumeration/aqs.y
diff --git a/dlls/windows.devices.enumeration/Makefile.in b/dlls/windows.devices.enumeration/Makefile.in index 0a204835ae1..eb227b4dfc4 100644 --- a/dlls/windows.devices.enumeration/Makefile.in +++ b/dlls/windows.devices.enumeration/Makefile.in @@ -1,10 +1,11 @@ MODULE = windows.devices.enumeration.dll -IMPORTS = cfgmgr32 combase uuid - +IMPORTS = cfgmgr32 combase propsys uuid SOURCES = \ access.c \ async.c \ async_private.idl \ + aqs.c \ + aqs.y \ classes.idl \ event_handlers.c \ information.c \ diff --git a/dlls/windows.devices.enumeration/aqs.c b/dlls/windows.devices.enumeration/aqs.c new file mode 100644 index 00000000000..d1cca72c4fe --- /dev/null +++ b/dlls/windows.devices.enumeration/aqs.c @@ -0,0 +1,472 @@ +/* Advanced Query Syntax parser + * + * Copyright 2025 Vibhav Pant + * + * 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 + */ + +#include <assert.h> +#include <stdarg.h> + +#include <windef.h> +#include <devpropdef.h> +#include <devfiltertypes.h> +#include <propvarutil.h> + +#include <wine/debug.h> + +#include "aqs.h" +#include "aqs.tab.h" + +WINE_DEFAULT_DEBUG_CHANNEL( aqs ); + +static BOOL is_idchar( WCHAR chr ) +{ + static const WCHAR legal[] = { 5, '#', '-', '.', '_', '{', '}' }; + int i; + + if (iswdigit(chr) || iswalpha(chr) || (chr >= 125 && chr <= 255)) return TRUE; + for (i = 0; i < ARRAY_SIZE( legal ); i++) if (chr == legal[i]) return TRUE; + return FALSE; +} + +static int keyword_type( const WCHAR *str, unsigned int len ) +{ + if (!wcsncmp( str, L"AND", len )) + return TK_AND; + if (!wcsncmp( str, L"NOT", len )) + return TK_NOT; + if (!wcsncmp( str, L"OR", len )) + return TK_OR; + if (!wcsnicmp( str, L"System.StructuredQueryType.Boolean#False", len )) + return TK_FALSE; + if (!wcsnicmp( str, L"System.StructuredQueryType.Boolean#True", len )) + return TK_TRUE; + + return TK_ID; +} + +int get_token( const WCHAR *str, aqs_token_kind_t *token ) +{ + int i; + + switch (str[0]) + { + case '\0': + *token = AQS_EOF; + return 0; + case ' ': + case '\t': + case '\r': + case '\n': + for (i = 1; iswspace( str[i] ); i++) /*nothing */; + *token = TK_WHITESPACE; + return i; + case '(': + *token = TK_LEFTPAREN; + return 1; + case ')': + *token = TK_RIGHTPAREN; + return 1; + case '[': + if (str[1] != ']') break; /* illegal */ + *token = TK_NULL; + return 2; + case ':': + *token = TK_COLON; + return 1; + case '=': + *token = TK_EQUAL; + return 1; + case L'\u2260': /* ≠, NOT EQUALS TO */ + *token = TK_NOTEQUAL; + return 1; + case '-': + if (iswdigit( str[1] )) + { + *token = TK_MINUS; + return 1; + } + *token = TK_NOTEQUAL; + /* Both -- and - are used as not-equals operators. */ + return str[1] == '-' ? 2 : 1; + case '<': + switch (str[1]) + { + case '=': + *token = TK_LTE; + return 2; + case '>': + *token = TK_NOTEQUAL; + return 2; + default: + *token = TK_LT; + return 1; + } + case L'\u2264': /* ≤, LESS-THAN OR EQUAL TO */ + *token = TK_LTE; + return 1; + case '>': + if (str[1] == '=') + { + *token = TK_GTE; + return 2; + } + *token = TK_GT; + return 1; + case L'\u2265': /* ≥, GREATER-THAN OR EQUAL TO */ + *token = TK_GTE; + return 1; + case '~': + *token = TK_TILDE; + return 1; + case '!': + *token = TK_EXCLAM; + return 1; + case '$': + *token = TK_DOLLAR; + return 1; + case '"': + /* lookup for end double quote, skipping any "" escaped double quotes */ + for (i = 1; str[i]; i++) if (str[i] == '"' && str[++i] != '"') break; + if (i == 1 || str[i - 1] != '"') break; /* illegal */ + *token = TK_STRING; + return i; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + *token = TK_INTEGER; + for (i = 1; iswdigit( str[i] ); i++) /* nothing */; + return i; + default: + if (!is_idchar( str[0] )) break; + for (i = 1; is_idchar( str[i] ); i++) /* nothing */; + *token = keyword_type( str, i ); + return i; + } + *token = AQS_UNDEF; + return 0; +} + +UINT aqs_lex( void *p, struct aqs_parser *parser ) +{ + aqs_token_kind_t token = -1; + struct string *str = p; + + do + { + parser->idx += parser->len; + if ((parser->len = get_token( &parser->query[parser->idx], &token ))) + { + str->data = &parser->query[parser->idx]; + str->len = parser->len; + } + parser->all_whitespace &= ((token == TK_WHITESPACE) || (token == AQS_EOF)); + } while (token == TK_WHITESPACE); + return token; +} + +HRESULT aqs_parse_query( const WCHAR *str, struct aqs_expr **expr, BOOL *all_whitespace ) +{ + struct aqs_parser parser = {0}; + HRESULT hr; + int ret; + + *expr = NULL; + if (!wcslen( str )) return S_OK; + + parser.all_whitespace = TRUE; + parser.query = str; + if (FAILED(hr = CoCreateInstance( &CLSID_PropertySystem, NULL, CLSCTX_INPROC_SERVER, &IID_IPropertySystem, (void **)&parser.propsys ))) + return hr; + aqs_debug = TRACE_ON( aqs ); + ret = aqs_parse( &parser ); + if (!ret && parser.expr) + *expr = parser.expr; + else + hr = FAILED(parser.error) ? parser.error : E_INVALIDARG; + + if (all_whitespace) *all_whitespace = parser.all_whitespace; + IPropertySystem_Release( parser.propsys ); + return hr; +} + +HRESULT get_integer( struct aqs_parser *parser, PROPVARIANT *val ) +{ + const WCHAR *str = &parser->query[parser->idx]; + int i, num = 0; + + for (i = 0; i < parser->len; i++) + num = (str[i] - '0') + num * 10; + val->vt = VT_UI4; + val->lVal = num; + return S_OK; +} + +HRESULT get_string( struct aqs_parser *parser, const struct string *p, BOOL id, PROPVARIANT *val ) +{ + const WCHAR *str = p->data; + SIZE_T len = p->len; + WCHAR *buf; + + if (!id) + { + str++; + len -= 2; + } + if (!(buf = CoTaskMemAlloc((len + 1) * sizeof( WCHAR )))) return (parser->error = E_OUTOFMEMORY); + memcpy( buf, str, len * sizeof( WCHAR ) ); + buf[len] = 0; + val->vt = VT_LPWSTR; + val->pwszVal = buf; + return S_OK; +} + +void get_boolean( struct aqs_parser *parser, BOOL b, PROPVARIANT *val ) +{ + val->vt = VT_BOOL; + val->boolVal = b ? VARIANT_TRUE : VARIANT_FALSE; +} + +HRESULT join_expr( struct aqs_parser *parser, struct aqs_expr *first, struct aqs_expr *second, struct aqs_expr **ret_expr ) +{ + ULONG len = first->len + second->len; + struct aqs_expr *expr; + + if (!(expr = calloc( 1, offsetof( struct aqs_expr, filters[len] )))) return (parser->error = E_OUTOFMEMORY); + + expr->len = len; + memcpy( &expr->filters[0], first->filters, sizeof( *expr->filters ) * first->len ); + memcpy( &expr->filters[first->len], second->filters, sizeof( *expr->filters ) * second->len ); + *ret_expr = expr; + + free( first ); + free( second ); + return S_OK; +} + +HRESULT get_boolean_expr( struct aqs_parser *parser, DEVPROP_OPERATOR op, struct aqs_expr *first, + struct aqs_expr *second, struct aqs_expr **ret_expr ) +{ + ULONG len = 2 + first->len + second->len; + struct aqs_expr *expr; + + assert( op == DEVPROP_OPERATOR_AND_OPEN || op == DEVPROP_OPERATOR_OR_OPEN || op == DEVPROP_OPERATOR_NOT_OPEN ); + + if (!(expr = calloc( 1, offsetof( struct aqs_expr, filters[len] ) ))) return (parser->error = E_OUTOFMEMORY); + + expr->len = len; + expr->filters[0].Operator = op; + memcpy( &expr->filters[1], first->filters, sizeof( *expr->filters ) * first->len ); + memcpy( &expr->filters[first->len], second->filters, sizeof( *expr->filters ) * second->len ); + expr->filters[len - 1].Operator = op + (DEVPROP_OPERATOR_AND_CLOSE - DEVPROP_OPERATOR_AND_OPEN); + *ret_expr = expr; + + free( first ); + free( second ); + return S_OK; +} + +static HRESULT propval_to_devprop( const PROPVARIANT *comparand_val, const DEVPROPKEY *prop_key, VARTYPE prop_vt, DEVPROPERTY *devprop ) +{ + union + { + BYTE byte; + UINT16 int16; + UINT32 int32; + UINT64 int64; + GUID guid; + DEVPROP_BOOLEAN boolean; + } devprop_basic_val = {0}; + PROPVARIANT tmp = {0}; + HRESULT hr; + + devprop->CompKey.Key = *prop_key; + if (comparand_val->vt != VT_EMPTY) + { + if (FAILED(hr = PropVariantChangeType( &tmp, comparand_val, 0, prop_vt ))) + return (hr == E_FAIL || hr == E_NOTIMPL) ? E_INVALIDARG : hr; + switch (prop_vt) + { + case VT_CLSID: + devprop->Type = DEVPROP_TYPE_GUID; + devprop_basic_val.guid = *tmp.puuid; + devprop->BufferSize = sizeof( devprop_basic_val.guid ); + break; + case VT_I1: + case VT_UI1: + devprop->Type = prop_vt == VT_I1 ? DEVPROP_TYPE_SBYTE : DEVPROP_TYPE_BYTE; + devprop_basic_val.byte = tmp.bVal; + devprop->BufferSize = sizeof( devprop_basic_val.byte ); + break; + case VT_BOOL: + devprop->Type = DEVPROP_TYPE_BOOLEAN; + devprop_basic_val.boolean = tmp.boolVal ? DEVPROP_TRUE : DEVPROP_FALSE; + devprop->BufferSize = sizeof( devprop_basic_val.boolean ); + break; + case VT_I2: + case VT_UI2: + devprop->Type = prop_vt == VT_I2 ? DEVPROP_TYPE_INT16 : DEVPROP_TYPE_UINT16; + devprop_basic_val.int16 = tmp.uiVal; + devprop->BufferSize = sizeof( devprop_basic_val.int16 ); + break; + case VT_I4: + case VT_UI4: + devprop->Type = prop_vt == VT_I4 ? DEVPROP_TYPE_INT32 : DEVPROP_TYPE_UINT32; + devprop_basic_val.int32 = tmp.ulVal; + devprop->BufferSize = sizeof( devprop_basic_val.int32 ); + break; + case VT_I8: + case VT_UI8: + devprop->Type = prop_vt == VT_I8 ? DEVPROP_TYPE_INT64 : DEVPROP_TYPE_UINT64; + devprop_basic_val.int64 = tmp.uhVal.QuadPart; + devprop->BufferSize = sizeof( devprop_basic_val.int64 ); + break; + case VT_LPWSTR: + devprop->Type = DEVPROP_TYPE_STRING; + devprop->BufferSize = (wcslen( tmp.pwszVal ) + 1) * sizeof( WCHAR ); + break; + default: + FIXME( "Unsupported property VARTYPE %d, treating comparand as string.\n", prop_vt ); + PropVariantClear( &tmp ); + if (FAILED(hr = PropVariantChangeType( &tmp, comparand_val, 0, VT_LPWSTR ))) + return (hr == E_FAIL || hr == E_NOTIMPL) ? E_INVALIDARG : hr; + devprop->Type = DEVPROP_TYPE_STRING; + devprop->BufferSize = (wcslen( tmp.pwszVal ) + 1) * sizeof( WCHAR ); + break; + } + } + else + { + devprop->Type = DEVPROP_TYPE_EMPTY; + devprop->BufferSize = 0; + } + + devprop->CompKey.Store = DEVPROP_STORE_SYSTEM; + devprop->CompKey.LocaleName = NULL; + devprop->Buffer = NULL; + if (devprop->BufferSize && !(devprop->Buffer = calloc( 1, devprop->BufferSize ))) + { + PropVariantClear( &tmp ); + return E_OUTOFMEMORY; + } + switch (devprop->Type) + { + case DEVPROP_TYPE_STRING: + wcscpy( devprop->Buffer, tmp.pwszVal ); + break; + case DEVPROP_TYPE_EMPTY: + break; + default: + memcpy( devprop->Buffer, &devprop_basic_val, devprop->BufferSize ); + break; + } + PropVariantClear( &tmp ); + return S_OK; +} + +HRESULT get_compare_expr( struct aqs_parser *parser, DEVPROP_OPERATOR op, const WCHAR *prop_name, + PROPVARIANT *val, struct aqs_expr **ret_expr ) +{ + IPropertyDescription *prop_desc = NULL; + DEVPROP_FILTER_EXPRESSION *filter; + struct aqs_expr *expr; + VARTYPE type; + HRESULT hr; + + if (!(expr = calloc( 1, offsetof( struct aqs_expr, filters[1] )))) goto fail; + if (FAILED(hr = IPropertySystem_GetPropertyDescriptionByName( parser->propsys, prop_name, &IID_IPropertyDescription, (void **)&prop_desc ))) + { + parser->error = hr == TYPE_E_ELEMENTNOTFOUND ? E_INVALIDARG : hr; + goto fail; + } + expr->len = 1; + filter = &expr->filters[0]; + if (FAILED(parser->error = IPropertyDescription_GetPropertyKey( prop_desc, (PROPERTYKEY *)&filter->Property.CompKey.Key ))) goto fail; + if (FAILED(parser->error = IPropertyDescription_GetPropertyType( prop_desc, &type ))) goto fail; + if (FAILED(parser->error = propval_to_devprop( val, &filter->Property.CompKey.Key, type, &filter->Property ))) goto fail; + + if ((op & DEVPROP_OPERATOR_MASK_EVAL) == DEVPROP_OPERATOR_CONTAINS) FIXME( "Wildcard matching is not supported yet, will compare verbatim.\n" ); + if ((op == DEVPROP_OPERATOR_EQUALS || op == DEVPROP_OPERATOR_NOT_EQUALS) && val->vt == VT_EMPTY) + filter->Operator = (op == DEVPROP_OPERATOR_EQUALS) ? DEVPROP_OPERATOR_NOT_EXISTS : DEVPROP_OPERATOR_EXISTS; + else + filter->Operator = op; + + IPropertyDescription_Release( prop_desc ); + PropVariantClear( val ); + *ret_expr = expr; + + return S_OK; + +fail: + PropVariantClear( val ); + if (prop_desc) IPropertyDescription_Release( prop_desc ); + free( expr ); + return parser->error; +} + +static const char *debugstr_DEVPROPKEY( const DEVPROPKEY *key ) +{ + if (!key) return "(null)"; + return wine_dbg_sprintf( "{%s,%04lx}", debugstr_guid( &key->fmtid ), key->pid ); +} + +static const char *debugstr_DEVPROPCOMPKEY( const DEVPROPCOMPKEY *key ) +{ + if (!key) return "(null)"; + return wine_dbg_sprintf( "{%s}", debugstr_DEVPROPKEY( &key->Key ) ); +} + +static const char *debugstr_DEVPROP_FILTER_EXPRESSION( const DEVPROP_FILTER_EXPRESSION *filter ) +{ + if (!filter) return "(null)"; + return wine_dbg_sprintf( "{%#x,{%s,%#lx,%lu,%p}}", filter->Operator, debugstr_DEVPROPCOMPKEY( &filter->Property.CompKey ), filter->Property.Type, + filter->Property.BufferSize, filter->Property.Buffer ); +} + +const char *debugstr_expr( const struct aqs_expr *expr ) +{ + char buf[200]; + int written = 1; + ULONG i; + + if (!expr) return "(null)"; + + buf[0] = '{'; + for (i = 0; i < expr->len; i++) + { + size_t size = sizeof( buf ) - written - 1; + int len; + + len = snprintf( &buf[written], size, "%s, ", debugstr_DEVPROP_FILTER_EXPRESSION( &expr->filters[i] ) ); + if (len >= size) return wine_dbg_sprintf( "{%lu, %p}", expr->len, expr->filters ); + written += len; + } + /* Overwrites the last comma */ + buf[written - 2] = '}'; + buf[written - 1] = '\0'; + return wine_dbg_sprintf( "%s", buf ); +} + +void free_aqs_expr( struct aqs_expr *expr ) +{ + ULONG i; + + if (!expr) return; + + for (i = 0; i < expr->len; i++) + free( expr->filters[i].Property.Buffer ); + + free( expr ); +} diff --git a/dlls/windows.devices.enumeration/aqs.h b/dlls/windows.devices.enumeration/aqs.h new file mode 100644 index 00000000000..53710f9f655 --- /dev/null +++ b/dlls/windows.devices.enumeration/aqs.h @@ -0,0 +1,104 @@ +/* Advanced Query Syntax parser + * + * Copyright 2025 Vibhav Pant + * + * 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 + */ + +#include <devpropdef.h> +#include <devfiltertypes.h> +#include <wine/list.h> +#include <wine/debug.h> + +#include "private.h" + +struct string +{ + const WCHAR *data; + int len; +}; + +struct aqs_parser +{ + const WCHAR *query; + int idx; + int len; + HRESULT error; + /* If the parsed query is only whitespaces. Needed for CreateWatcher. */ + BOOL all_whitespace; + + IPropertySystem *propsys; + struct aqs_expr *expr; +}; + +struct aqs_expr +{ + ULONG len; + DEVPROP_FILTER_EXPRESSION filters[1]; +}; + +extern HRESULT aqs_parse_query( const WCHAR *str, struct aqs_expr **expr, BOOL *all_whitespace ); + +extern UINT aqs_lex( void *val, struct aqs_parser *parser ); + +extern HRESULT get_integer( struct aqs_parser *parser, PROPVARIANT *val ); +extern HRESULT get_string( struct aqs_parser *parser, const struct string *str, BOOL id, PROPVARIANT *val ); +extern void get_boolean( struct aqs_parser *parser, BOOL b, PROPVARIANT *val ); + +extern HRESULT join_expr( struct aqs_parser *parser, struct aqs_expr *first, struct aqs_expr *second, struct aqs_expr **expr ); +extern HRESULT get_boolean_expr( struct aqs_parser *parser, DEVPROP_OPERATOR op, struct aqs_expr *first, + struct aqs_expr *second, struct aqs_expr **expr ); +extern HRESULT get_compare_expr( struct aqs_parser *parser, DEVPROP_OPERATOR op, const WCHAR *prop_name, + PROPVARIANT *val, struct aqs_expr **expr ); + +extern void free_aqs_expr( struct aqs_expr *expr ); +extern void free_devprop_filters( DEVPROP_FILTER_EXPRESSION *filters, ULONG filters_len ); + +extern const char *debugstr_expr( const struct aqs_expr *expr ); +static inline const char *debugstr_propvar(const PROPVARIANT *v) +{ + if (!v) + return "(null)"; + + switch (v->vt) + { + case VT_EMPTY: + return wine_dbg_sprintf("%p {VT_EMPTY}", v); + case VT_NULL: + return wine_dbg_sprintf("%p {VT_NULL}", v); + case VT_BOOL: + return wine_dbg_sprintf("%p {VT_BOOL %d}", v, !!v->boolVal); + case VT_UI4: + return wine_dbg_sprintf("%p {VT_UI4: %ld}", v, v->ulVal); + case VT_UI8: + return wine_dbg_sprintf("%p {VT_UI8: %s}", v, wine_dbgstr_longlong(v->uhVal.QuadPart)); + case VT_I8: + return wine_dbg_sprintf("%p {VT_I8: %s}", v, wine_dbgstr_longlong(v->hVal.QuadPart)); + case VT_R4: + return wine_dbg_sprintf("%p {VT_R4: %.8e}", v, v->fltVal); + case VT_R8: + return wine_dbg_sprintf("%p {VT_R8: %lf}", v, v->dblVal); + case VT_CLSID: + return wine_dbg_sprintf("%p {VT_CLSID: %s}", v, wine_dbgstr_guid(v->puuid)); + case VT_LPWSTR: + return wine_dbg_sprintf("%p {VT_LPWSTR: %s}", v, wine_dbgstr_w(v->pwszVal)); + case VT_VECTOR | VT_UI1: + return wine_dbg_sprintf("%p {VT_VECTOR|VT_UI1: %p}", v, v->caub.pElems); + case VT_UNKNOWN: + return wine_dbg_sprintf("%p {VT_UNKNOWN: %p}", v, v->punkVal); + default: + return wine_dbg_sprintf("%p {vt %#x}", v, v->vt); + } +} diff --git a/dlls/windows.devices.enumeration/aqs.y b/dlls/windows.devices.enumeration/aqs.y new file mode 100644 index 00000000000..517769f6201 --- /dev/null +++ b/dlls/windows.devices.enumeration/aqs.y @@ -0,0 +1,175 @@ +%{ + +/* Advanced Query Syntax parser + * + * Copyright 2025 Vibhav Pant + * + * 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 + */ + +#include <stdarg.h> +#include <stdlib.h> + +#include <windef.h> +#include <winbase.h> + +#include <wine/debug.h> + +#include "aqs.h" + +WINE_DEFAULT_DEBUG_CHANNEL( aqs ); + +#define YYFPRINTF(file, ...) TRACE(__VA_ARGS__) + +static const PROPVARIANT propval_empty = { VT_EMPTY }; + +static int aqs_error( struct aqs_parser *parser, const char *str ) +{ + if (TRACE_ON( aqs )) ERR( "%s\n", str ); + return 0; +} + +#define GET_COMPARE_EXPR( ctx, op, prop_vt, val_vt, out ) \ + if (FAILED(get_compare_expr( ctx, op, (prop_vt)->pwszVal, val_vt, out ))) YYABORT +%} + +%lex-param { struct aqs_parser *ctx } +%parse-param { struct aqs_parser *ctx } +%define parse.error verbose +%define api.prefix {aqs_} +%define api.pure + +%union +{ + struct string str; + PROPVARIANT propval; + struct aqs_expr *expr; +} + +%token TK_LEFTPAREN TK_RIGHTPAREN TK_NULL +%token TK_INTEGER TK_WHITESPACE TK_ILLEGAL TK_MINUS +%token TK_TRUE TK_FALSE +%token <str> TK_STRING TK_ID + +%type <propval> id string number boolean null +%type <propval> propval +%type <expr> expr query + +%left TK_AND TK_OR TK_NOT TK_COLON TK_EQUAL TK_NOTEQUAL TK_LT TK_LTE TK_GT TK_GTE TK_TILDE TK_EXCLAM TK_DOLLAR + +%destructor { PropVariantClear( &$$ ); } propval +%destructor { free_aqs_expr( $$ ); } expr + +%debug + +%printer { TRACE( "%s", debugstr_wn( $$.data, $$.len ) ); } TK_STRING TK_ID +%printer { TRACE( "%s", debugstr_propvar( &$$ ) ); } id string +%printer { TRACE( "%s", debugstr_propvar( &$$ ) ); } number +%printer { TRACE( "%s", debugstr_propvar( &$$ ) ); } propval +%printer { TRACE( "%s", debugstr_expr( $$ ) ); } expr + +%% + +query: expr { ctx->expr = $1; } + ; +expr: + TK_LEFTPAREN expr TK_RIGHTPAREN + { + $$ = $2; +#if YYBISON >= 30704 + (void)yysymbol_name; /* avoid unused function warning */ +#endif + (void)yynerrs; /* avoid unused variable warning */ + } + | expr TK_AND expr + { + if (FAILED(get_boolean_expr( ctx, DEVPROP_OPERATOR_AND_OPEN, $1, $3, &$$ ))) + YYABORT; + } + | expr TK_OR expr + { + if (FAILED(get_boolean_expr( ctx, DEVPROP_OPERATOR_OR_OPEN, $1, $3, &$$ ))) + YYABORT; + } + | TK_NOT expr + { + if (FAILED(get_boolean_expr( ctx, DEVPROP_OPERATOR_NOT_OPEN, $2, NULL, &$$ ))) + YYABORT; + } + | expr expr + { + if (FAILED(join_expr( ctx, $1, $2, &$$ ))) + YYABORT; + } + | id TK_COLON propval { GET_COMPARE_EXPR( ctx, DEVPROP_OPERATOR_EQUALS, &$1, &$3, &$$ ); } + | id TK_COLON TK_EQUAL propval { GET_COMPARE_EXPR( ctx, DEVPROP_OPERATOR_EQUALS, &$1, &$4, &$$ ); } + | id TK_COLON TK_NOT propval { GET_COMPARE_EXPR( ctx, DEVPROP_OPERATOR_NOT_EQUALS, &$1, &$4, &$$ ); } + | id TK_COLON TK_NOTEQUAL propval { GET_COMPARE_EXPR( ctx, DEVPROP_OPERATOR_NOT_EQUALS, &$1, &$4, &$$ ); } + | id TK_COLON TK_LT propval { GET_COMPARE_EXPR( ctx, DEVPROP_OPERATOR_LESS_THAN, &$1, &$4, &$$ ); } + | id TK_COLON TK_LTE propval { GET_COMPARE_EXPR( ctx, DEVPROP_OPERATOR_LESS_THAN_EQUALS, &$1, &$4, &$$ ); } + | id TK_COLON TK_GT propval { GET_COMPARE_EXPR( ctx, DEVPROP_OPERATOR_GREATER_THAN, &$1, &$4, &$$ ); } + | id TK_COLON TK_GTE propval { GET_COMPARE_EXPR( ctx, DEVPROP_OPERATOR_GREATER_THAN_EQUALS, &$1, &$4, &$$ ); } + /* String operators */ + | id TK_TILDE TK_LT propval { GET_COMPARE_EXPR( ctx, DEVPROP_OPERATOR_BEGINS_WITH_IGNORE_CASE, &$1, &$4, &$$ ); } + | id TK_TILDE TK_GT propval { GET_COMPARE_EXPR( ctx, DEVPROP_OPERATOR_ENDS_WITH_IGNORE_CASE, &$1, &$4, &$$ ); } + | id TK_TILDE TK_EQUAL propval { GET_COMPARE_EXPR( ctx, DEVPROP_OPERATOR_CONTAINS_IGNORE_CASE, &$1, &$4, &$$ ); } + | id TK_TILDE TK_TILDE propval { GET_COMPARE_EXPR( ctx, DEVPROP_OPERATOR_CONTAINS_IGNORE_CASE, &$1, &$4, &$$ ); } + | id TK_TILDE TK_EXCLAM propval { GET_COMPARE_EXPR( ctx, DEVPROP_OPERATOR_MODIFIER_NOT | DEVPROP_OPERATOR_CONTAINS_IGNORE_CASE, &$1, &$4, &$$ ); } + | id TK_TILDE propval { GET_COMPARE_EXPR( ctx, DEVPROP_OPERATOR_CONTAINS, &$1, &$3, &$$ ); } + | id TK_DOLLAR TK_EQUAL propval { GET_COMPARE_EXPR( ctx, DEVPROP_OPERATOR_EQUALS, &$1, &$4, &$$ ); } + | id TK_DOLLAR TK_DOLLAR propval { GET_COMPARE_EXPR( ctx, DEVPROP_OPERATOR_EQUALS, &$1, &$4, &$$ ); } + | id TK_DOLLAR TK_LT propval { GET_COMPARE_EXPR( ctx, DEVPROP_OPERATOR_BEGINS_WITH_IGNORE_CASE, &$1, &$4, &$$ ); } +propval: + id | string | number | boolean | null + ; +id: + TK_ID + { + if (FAILED(get_string( ctx, &$1, TRUE, &$$ ))) + YYABORT; + } + ; +string: + TK_STRING + { + if (FAILED(get_string( ctx, &$1, FALSE, &$$ ))) + YYABORT; + } + ; +number: + TK_INTEGER + { + if (FAILED(get_integer( ctx, &$$ ))) + YYABORT; + } + ; +boolean: + TK_TRUE + { + get_boolean( ctx, TRUE, &$$ ); + } + ; + | TK_FALSE + { + get_boolean( ctx, FALSE, &$$ ); + } + ; +null: + TK_NULL + { + $$ = propval_empty; + } + ; +%% diff --git a/dlls/windows.devices.enumeration/main.c b/dlls/windows.devices.enumeration/main.c index f8462c8faed..c74e5942841 100644 --- a/dlls/windows.devices.enumeration/main.c +++ b/dlls/windows.devices.enumeration/main.c @@ -19,6 +19,7 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#define COBJMACROS #include <assert.h>
#include "initguid.h" @@ -26,11 +27,85 @@ #include "devpropdef.h" #include "devfiltertypes.h" #include "devquery.h" +#include "aqs.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(enumeration);
+struct devquery_params +{ + IUnknown IUnknown_iface; + struct aqs_expr *expr; + LONG ref; +}; + +static inline struct devquery_params *impl_from_IUnknown( IUnknown *iface ) +{ + return CONTAINING_RECORD( iface, struct devquery_params, IUnknown_iface ); +} + +static HRESULT WINAPI devquery_params_QueryInterface( IUnknown *iface, REFIID iid, void **out ) +{ + TRACE( "iface %p, iid %s, out %p\n", iface, debugstr_guid( iid ), out ); + + if (IsEqualGUID( iid, &IID_IUnknown )) + { + IUnknown_AddRef(( iface )); + *out = iface; + return S_OK; + } + + *out = NULL; + FIXME( "%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid( iid ) ); + return S_OK; +} + +static ULONG WINAPI devquery_params_AddRef( IUnknown *iface ) +{ + struct devquery_params *impl = impl_from_IUnknown( iface ); + + TRACE( "iface %p\n", iface ); + return InterlockedIncrement( &impl->ref ); +} + +static ULONG WINAPI devquery_params_Release( IUnknown *iface ) +{ + struct devquery_params *impl = impl_from_IUnknown( iface ); + ULONG ref = InterlockedDecrement( &impl->ref ); + + TRACE( "iface %p\n", iface ); + + if (!ref) + { + free_aqs_expr( impl->expr ); + free( impl ); + } + return ref; +} + +static const IUnknownVtbl devquery_params_vtbl = +{ + /* IUnknown */ + devquery_params_QueryInterface, + devquery_params_AddRef, + devquery_params_Release, +}; + +static HRESULT devquery_params_create( struct aqs_expr *expr, IUnknown **out ) +{ + struct devquery_params *impl; + + *out = NULL; + if (!(impl = calloc( 1, sizeof( *impl ) ))) return E_OUTOFMEMORY; + + impl->IUnknown_iface.lpVtbl = &devquery_params_vtbl; + impl->ref = 1; + impl->expr = expr; + *out = &impl->IUnknown_iface; + return S_OK; +} + struct device_watcher { IDeviceWatcher IDeviceWatcher_iface; @@ -39,7 +114,8 @@ struct device_watcher struct list added_handlers; struct list enumerated_handlers; struct list stopped_handlers; - HSTRING filter; + IUnknown *query_params; + BOOL aqs_all_whitespace;
CRITICAL_SECTION cs; DeviceWatcherStatus status; @@ -97,7 +173,7 @@ static ULONG WINAPI device_watcher_Release( IDeviceWatcher *iface ) typed_event_handlers_clear( &impl->added_handlers ); typed_event_handlers_clear( &impl->enumerated_handlers ); typed_event_handlers_clear( &impl->stopped_handlers ); - WindowsDeleteString( impl->filter ); + IUnknown_Release( impl->query_params ); impl->cs.DebugInfo->Spare[0] = 0; DeleteCriticalSection( &impl->cs ); if (impl->query) DevCloseObjectQuery( impl->query ); @@ -290,13 +366,9 @@ static HRESULT WINAPI device_watcher_Start( IDeviceWatcher *iface ) struct device_watcher *impl = impl_from_IDeviceWatcher( iface ); HRESULT hr = S_OK;
- FIXME( "iface %p: semi-stub!\n", iface ); + TRACE( "iface %p\n", iface );
- if (!WindowsIsStringEmpty( impl->filter )) - { - FIXME( "Unsupported filter: %s\n", debugstr_hstring( impl->filter ) ); - return S_OK; - } + if (impl->aqs_all_whitespace) return E_INVALIDARG;
EnterCriticalSection( &impl->cs ); switch (impl->status) @@ -309,12 +381,21 @@ static HRESULT WINAPI device_watcher_Start( IDeviceWatcher *iface ) case DeviceWatcherStatus_Created: case DeviceWatcherStatus_Stopped: { + const struct devquery_params *query_params = impl_from_IUnknown( impl->query_params ); + const DEVPROP_FILTER_EXPRESSION *filters = NULL; + ULONG filters_len = 0; IWeakReference *weak; HRESULT hr;
+ if (query_params->expr) + { + filters = query_params->expr->filters; + filters_len = query_params->expr->len; + } + IWeakReferenceSource_GetWeakReference( &impl->weak_reference_source.IWeakReferenceSource_iface, &weak ); - hr = DevCreateObjectQuery( DevObjectTypeDeviceInterfaceDisplay, DevQueryFlagAsyncClose, 0, NULL, 0, NULL, device_object_query_callback, weak, - &impl->query ); + hr = DevCreateObjectQuery( DevObjectTypeDeviceInterfaceDisplay, DevQueryFlagAsyncClose, 0, NULL, filters_len, filters, device_object_query_callback, + weak, &impl->query ); if (FAILED(hr)) { ERR( "Failed to create device query: %#lx\n", hr ); @@ -385,6 +466,7 @@ static const struct IDeviceWatcherVtbl device_watcher_vtbl = static HRESULT device_watcher_create( HSTRING filter, IDeviceWatcher **out ) { struct device_watcher *impl; + struct aqs_expr *expr; HRESULT hr;
if (!(impl = calloc( 1, sizeof(*impl) ))) return E_OUTOFMEMORY; @@ -395,12 +477,20 @@ static HRESULT device_watcher_create( HSTRING filter, IDeviceWatcher **out ) free( impl ); return hr; } - if (FAILED(hr = WindowsDuplicateString( filter, &impl->filter ))) + /* If the filter string is all whitespaces, we return E_INVALIDARG in IDeviceWatcher_Start, not here. */ + if (FAILED(hr = aqs_parse_query( WindowsGetStringRawBuffer( filter, NULL ), &expr, &impl->aqs_all_whitespace )) && !impl->aqs_all_whitespace) { weak_reference_strong_release( &impl->weak_reference_source ); free( impl ); return hr; } + if (FAILED(hr = devquery_params_create( expr, &impl->query_params ))) + { + free_aqs_expr( expr ); + weak_reference_strong_release( &impl->weak_reference_source ); + free( impl ); + return hr; + }
list_init( &impl->added_handlers ); list_init( &impl->enumerated_handlers ); @@ -538,18 +628,27 @@ static HRESULT find_all_async( IUnknown *invoker, IUnknown *param, PROPVARIANT * .iterable = &IID_IIterable_DeviceInformation, .iterator = &IID_IIterator_DeviceInformation, }; + const DEVPROP_FILTER_EXPRESSION *filters = NULL; IVectorView_DeviceInformation *view; + struct devquery_params *params; IVector_IInspectable *vector; + ULONG filters_len = 0, len, i; const DEV_OBJECT *objects; - ULONG len, i; HRESULT hr;
TRACE( "invoker %p, param %p, result %p\n", invoker, param, result );
+ params = impl_from_IUnknown( param ); + if (params->expr) + { + filters = params->expr->filters; + filters_len = params->expr->len; + } if (FAILED(hr = vector_create( &iids, (void *)&vector ))) return hr; - if (FAILED(hr = DevGetObjects( DevObjectTypeDeviceInterfaceDisplay, DevQueryFlagNone, 0, NULL, 0, NULL, &len, &objects ))) + if (FAILED(hr = DevGetObjects( DevObjectTypeDeviceInterfaceDisplay, DevQueryFlagNone, 0, NULL, filters_len, filters, &len, &objects ))) { IVector_IInspectable_Release( vector ); + ERR("DevGetObjects failed, hr %#lx\n", hr); return hr; } for (i = 0; i < len && SUCCEEDED(hr); i++) @@ -575,8 +674,7 @@ static HRESULT WINAPI device_statics_FindAllAsync( IDeviceInformationStatics *if IAsyncOperation_DeviceInformationCollection **op ) { TRACE( "iface %p, op %p\n", iface, op ); - return async_operation_inspectable_create( &IID_IAsyncOperation_DeviceInformationCollection, (IUnknown *)iface, NULL, - find_all_async, (IAsyncOperation_IInspectable **)op ); + return IDeviceInformationStatics_FindAllAsyncAqsFilter( iface, NULL, op ); }
static HRESULT WINAPI device_statics_FindAllAsyncDeviceClass( IDeviceInformationStatics *iface, DeviceClass class, @@ -589,8 +687,20 @@ static HRESULT WINAPI device_statics_FindAllAsyncDeviceClass( IDeviceInformation static HRESULT WINAPI device_statics_FindAllAsyncAqsFilter( IDeviceInformationStatics *iface, HSTRING filter, IAsyncOperation_DeviceInformationCollection **op ) { - FIXME( "iface %p, aqs %p, op %p stub!\n", iface, debugstr_hstring(filter), op ); - return E_NOTIMPL; + struct aqs_expr *expr; + IUnknown *params; + HRESULT hr; + + TRACE( "iface %p, aqs %p, op %p\n", iface, debugstr_hstring(filter), op ); + + if (FAILED(hr = aqs_parse_query(WindowsGetStringRawBuffer( filter, NULL ), &expr, NULL ))) return hr; + if (FAILED(hr = devquery_params_create( expr, ¶ms ))) + { + free_aqs_expr( expr ); + return hr; + } + return async_operation_inspectable_create( &IID_IAsyncOperation_DeviceInformationCollection, (IUnknown *)iface, (IUnknown *)params, + find_all_async, (IAsyncOperation_IInspectable **)op ); }
static HRESULT WINAPI device_statics_FindAllAsyncAqsFilterAndAdditionalProperties( IDeviceInformationStatics *iface, HSTRING filter, diff --git a/dlls/windows.devices.enumeration/private.h b/dlls/windows.devices.enumeration/private.h index ad15b7916ca..0ce5624b21c 100644 --- a/dlls/windows.devices.enumeration/private.h +++ b/dlls/windows.devices.enumeration/private.h @@ -28,6 +28,7 @@ #include "winbase.h" #include "winstring.h" #include "objbase.h" +#include "propsys.h"
#include "activation.h"
diff --git a/dlls/windows.devices.enumeration/tests/devices.c b/dlls/windows.devices.enumeration/tests/devices.c index b25f656ab75..46f6280441f 100644 --- a/dlls/windows.devices.enumeration/tests/devices.c +++ b/dlls/windows.devices.enumeration/tests/devices.c @@ -802,29 +802,29 @@ static void test_aqs_filters( void ) return; }
- test_FindAllAsyncAqsFilter( statics, filters_empty, TRUE, FALSE ); + test_FindAllAsyncAqsFilter( statics, filters_empty, FALSE, FALSE ); test_CreateWatcherAqsFilter( statics, filters_empty, FALSE, FALSE, FALSE, FALSE );
- test_FindAllAsyncAqsFilter( statics, filters_simple, TRUE, FALSE ); - test_CreateWatcherAqsFilter( statics, filters_simple, FALSE, FALSE, TRUE, TRUE ); + test_FindAllAsyncAqsFilter( statics, filters_simple, FALSE, FALSE ); + test_CreateWatcherAqsFilter( statics, filters_simple, FALSE, FALSE, FALSE, TRUE );
test_FindAllAsyncAqsFilter( statics, filters_case_insensitive, TRUE, FALSE ); - test_CreateWatcherAqsFilter( statics, filters_case_insensitive, FALSE, FALSE, TRUE, TRUE ); + test_CreateWatcherAqsFilter( statics, filters_case_insensitive, TRUE, FALSE, FALSE, FALSE );
- test_FindAllAsyncAqsFilter( statics, filters_no_results, TRUE, FALSE ); - test_CreateWatcherAqsFilter( statics, filters_no_results, FALSE, FALSE, TRUE, FALSE ); + test_FindAllAsyncAqsFilter( statics, filters_no_results, FALSE, FALSE ); + test_CreateWatcherAqsFilter( statics, filters_no_results, FALSE, FALSE, FALSE, FALSE );
- test_FindAllAsyncAqsFilter( statics, filters_invalid_comparand_type, TRUE, FALSE ); - test_CreateWatcherAqsFilter( statics, filters_invalid_comparand_type, TRUE, FALSE, FALSE, FALSE ); + test_FindAllAsyncAqsFilter( statics, filters_invalid_comparand_type, FALSE, FALSE ); + test_CreateWatcherAqsFilter( statics, filters_invalid_comparand_type, FALSE, FALSE, FALSE, FALSE );
- test_FindAllAsyncAqsFilter( statics, filters_invalid_empty, TRUE, FALSE ); - test_CreateWatcherAqsFilter( statics, filters_empty_watcher, FALSE, TRUE, FALSE, FALSE ); + test_FindAllAsyncAqsFilter( statics, filters_invalid_empty, FALSE, FALSE ); + test_CreateWatcherAqsFilter( statics, filters_empty_watcher, FALSE, FALSE, FALSE, FALSE );
- test_FindAllAsyncAqsFilter( statics, filters_invalid_operator, TRUE, FALSE ); - test_CreateWatcherAqsFilter( statics, filters_invalid_operator, TRUE, FALSE, FALSE, FALSE ); + test_FindAllAsyncAqsFilter( statics, filters_invalid_operator, FALSE, FALSE ); + test_CreateWatcherAqsFilter( statics, filters_invalid_operator, FALSE, FALSE, FALSE, FALSE );
- test_FindAllAsyncAqsFilter( statics, filters_invalid_operand, TRUE, FALSE ); - test_CreateWatcherAqsFilter( statics, filters_invalid_operand, TRUE, FALSE, FALSE, FALSE ); + test_FindAllAsyncAqsFilter( statics, filters_invalid_operand, FALSE, FALSE ); + test_CreateWatcherAqsFilter( statics, filters_invalid_operand, FALSE, FALSE, FALSE, FALSE );
IDeviceInformationStatics_Release( statics ); }
From: Vibhav Pant vibhavp@gmail.com
--- MAINTAINERS | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS index ad646df417f..7bbdac35021 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -391,6 +391,10 @@ WinINet P: Jacek Caban jacek@codeweavers.com F: dlls/wininet/
+WinRT Device Enumeration +P: Vibhav Pant vibhavp@gmail.com +F: dlls/windows.devices.enumeration/ + X11 Driver M: Alexandre Julliard julliard@winehq.org P: Rémi Bernon rbernon@codeweavers.com
On Mon Sep 15 12:22:29 2025 +0000, Rémi Bernon wrote:
Looking a bit deeper into the details I'm thinking you should probably generate the DEVPROP_FILTER_EXPRESSION directly while parsing, instead of using a separate IR. You could have:
struct aqs_expr { DEVPROP_FILTER_EXPRESSION *filters; UINT filters_len; };
And get rid of pretty much all the accessory stuff. `get_boolean_expr` would allocate a 1 filter expression, `get_compare_expr` would reallocate lhs filters to grow and append rhs, with the open/close ops around.
Yes, that seems better. I've pushed this in the latest revision, thanks.
v12:
* Add tests + partial implementation for parsing AQS selectors while creating `DeviceWatcher` as well. * Rewrite the testing `device_watcher_handler` delegate implementation to use a more generic `inspectable_event_handler`. * Create `DEVPROP_FILTER_EXPRESSION` filters directly in `aqs_parse_query` without first generating an IR.