-- v15: 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 | 478 +++++++++++++++--- 1 file changed, 396 insertions(+), 82 deletions(-)
diff --git a/dlls/windows.devices.enumeration/tests/devices.c b/dlls/windows.devices.enumeration/tests/devices.c index e4bb1349f6d..0b1ab0806b0 100644 --- a/dlls/windows.devices.enumeration/tests/devices.c +++ b/dlls/windows.devices.enumeration/tests/devices.c @@ -54,97 +54,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 +291,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 +309,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 +367,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 +389,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 +442,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 +462,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 +489,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 +509,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 +523,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 +571,281 @@ 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; + + 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 }, + { L"(((System.Devices.DeviceInstanceId :NOT [])))", S_OK }, +}; +static const struct test_case_filter filters_boolean_op[] = { + { L"NOT System.Devices.DeviceInstanceId := []", S_OK }, + { L"(NOT (NOT (NOT System.Devices.DeviceInstanceId := [])))", S_OK }, + { L"System.Devices.DeviceInstanceId := [] OR System.Devices.DeviceInstanceId :NOT []", S_OK }, + { L"NOT ((System.Devices.DeviceInstanceId :NOT []) AND System.Devices.DeviceInstanceId := [])", S_OK }, + { L"NOT ((NOT System.Devices.InterfaceEnabled := []) AND System.Devices.DeviceInstanceId := [])", 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 }, +}; +static const struct test_case_filter filters_precedence[] = { + /* Gets parsed as ((DeviceInstanceId := [] AND DeviceInstanceId := []) OR DeviceInstanceId :NOT []) */ + { L"System.Devices.DeviceInstanceId := [] System.Devices.DeviceInstanceId := [] OR System.Devices.DeviceInstanceId :NOT []", S_OK }, + /* Gets parsed as ((DeviceInstanceId := [] OR DeviceInstanceId :NOT []) AND DeviceInstanceId :NOT []) */ + { L"System.Devices.DeviceInstanceId := [] OR System.Devices.DeviceInstanceId :NOT [] System.Devices.DeviceInstanceId :NOT []", S_OK } +}; +/* Queries that succeed but don't return any results. */ +static const struct test_case_filter filters_no_results[] = { + /* Gets parsed as ((NOT DeviceInstanceId := []) AND DeviceInstanceId := []) */ + { L"NOT System.Devices.DeviceInstanceId := [] System.Devices.DeviceInstanceId := []", S_OK, TRUE }, + { 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_boolean_op, TRUE, TRUE ); + test_CreateWatcherAqsFilter( statics, filters_boolean_op, FALSE, FALSE, TRUE, TRUE ); + + 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_precedence, TRUE, FALSE ); + test_CreateWatcherAqsFilter( statics, filters_precedence, 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 +914,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 | 491 ++++++++++++++++++ dlls/windows.devices.enumeration/aqs.h | 103 ++++ dlls/windows.devices.enumeration/aqs.y | 240 +++++++++ dlls/windows.devices.enumeration/main.c | 144 ++++- dlls/windows.devices.enumeration/private.h | 1 + .../tests/devices.c | 36 +- 7 files changed, 983 insertions(+), 37 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..f6ce42f9965 --- /dev/null +++ b/dlls/windows.devices.enumeration/aqs.c @@ -0,0 +1,491 @@ +/* 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 (!*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; + + /* Use free instead of free_aqs_expr to reuse the property value buffers in the sub-expressions. */ + free( first ); + free( second ); + return S_OK; +} + +HRESULT get_boolean_binary_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 ); + + 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[1 + 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; +} + +HRESULT get_boolean_not_expr( struct aqs_parser *parser, struct aqs_expr *inner, struct aqs_expr **ret_expr ) +{ + ULONG len = 2 + inner->len; + struct aqs_expr *expr; + + if (!(expr = calloc( 1, offsetof( struct aqs_expr, filters[len] )))) return (parser->error = E_OUTOFMEMORY); + + expr->len = len; + expr->filters[0].Operator = DEVPROP_OPERATOR_NOT_OPEN; + memcpy( &expr->filters[1], inner->filters, sizeof( *expr->filters ) * inner->len ); + expr->filters[len - 1].Operator = DEVPROP_OPERATOR_NOT_CLOSE; + *ret_expr = expr; + + free( inner ); + 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, PROPVARIANT *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; + + assert( prop_name->vt == VT_LPWSTR ); + if (!(expr = calloc( 1, offsetof( struct aqs_expr, filters[1] )))) goto fail; + hr = IPropertySystem_GetPropertyDescriptionByName( parser->propsys, prop_name->pwszVal, &IID_IPropertyDescription, (void **)&prop_desc ); + PropVariantClear( prop_name ); + if (FAILED(hr)) + { + 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..3062914102f --- /dev/null +++ b/dlls/windows.devices.enumeration/aqs.h @@ -0,0 +1,103 @@ +/* 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_binary_expr( struct aqs_parser *parser, DEVPROP_OPERATOR op, struct aqs_expr *first, struct aqs_expr *second, struct aqs_expr **expr ); +extern HRESULT get_boolean_not_expr( struct aqs_parser *parser, struct aqs_expr *inner, struct aqs_expr **expr ); +extern HRESULT get_compare_expr( struct aqs_parser *parser, DEVPROP_OPERATOR op, PROPVARIANT *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..556900ece17 --- /dev/null +++ b/dlls/windows.devices.enumeration/aqs.y @@ -0,0 +1,240 @@ +%{ + +/* 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; +} +%} + +%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_binary_expr( ctx, DEVPROP_OPERATOR_AND_OPEN, $1, $3, &$$ ))) + YYABORT; + } + | expr TK_OR expr + { + if (FAILED(get_boolean_binary_expr( ctx, DEVPROP_OPERATOR_OR_OPEN, $1, $3, &$$ ))) + YYABORT; + } + | TK_NOT expr + { + if (FAILED(get_boolean_not_expr( ctx, $2, &$$ ))) + YYABORT; + } + | expr expr + { + if (FAILED(join_expr( ctx, $1, $2, &$$ ))) + YYABORT; + } + | id TK_COLON propval + { + if (FAILED(get_compare_expr( ctx, DEVPROP_OPERATOR_EQUALS, &$1, &$3, &$$ ))) + YYABORT; + } + | id TK_COLON TK_EQUAL propval + { + if (FAILED(get_compare_expr( ctx, DEVPROP_OPERATOR_EQUALS, &$1, &$4, &$$ ))) + YYABORT; + } + | id TK_COLON TK_NOT propval + { + if (FAILED(get_compare_expr( ctx, DEVPROP_OPERATOR_NOT_EQUALS, &$1, &$4, &$$ ))) + YYABORT; + } + | id TK_COLON TK_NOTEQUAL propval + { + if (FAILED(get_compare_expr( ctx, DEVPROP_OPERATOR_NOT_EQUALS, &$1, &$4, &$$ ))) + YYABORT; + } + | id TK_COLON TK_LT propval + { + if (FAILED(get_compare_expr( ctx, DEVPROP_OPERATOR_LESS_THAN, &$1, &$4, &$$ ))) + YYABORT; + } + | id TK_COLON TK_LTE propval + { + if (FAILED(get_compare_expr( ctx, DEVPROP_OPERATOR_LESS_THAN_EQUALS, &$1, &$4, &$$ ))) + YYABORT; + } + | id TK_COLON TK_GT propval + { + if (FAILED(get_compare_expr( ctx, DEVPROP_OPERATOR_GREATER_THAN, &$1, &$4, &$$ ))) + YYABORT; + } + | id TK_COLON TK_GTE propval + { + if (FAILED(get_compare_expr( ctx, DEVPROP_OPERATOR_GREATER_THAN_EQUALS, &$1, &$4, &$$ ))) + YYABORT; + } + /* String operators */ + | id TK_TILDE TK_LT propval + { + if (FAILED(get_compare_expr( ctx, DEVPROP_OPERATOR_BEGINS_WITH_IGNORE_CASE, &$1, &$4, &$$ ))) + YYABORT; + } + | id TK_TILDE TK_GT propval + { + if (FAILED(get_compare_expr( ctx, DEVPROP_OPERATOR_ENDS_WITH_IGNORE_CASE, &$1, &$4, &$$ ))) + YYABORT; + } + | id TK_TILDE TK_EQUAL propval + { + if (FAILED(get_compare_expr( ctx, DEVPROP_OPERATOR_CONTAINS_IGNORE_CASE, &$1, &$4, &$$ ))) + YYABORT; + } + | id TK_TILDE TK_TILDE propval + { + if (FAILED(get_compare_expr( ctx, DEVPROP_OPERATOR_CONTAINS_IGNORE_CASE, &$1, &$4, &$$ ))) + YYABORT; + } + | id TK_TILDE TK_EXCLAM propval + { + if (FAILED(get_compare_expr( ctx, DEVPROP_OPERATOR_MODIFIER_NOT | DEVPROP_OPERATOR_CONTAINS_IGNORE_CASE, &$1, &$4, &$$ ))) + YYABORT; + } + | id TK_TILDE propval + { + if (FAILED(get_compare_expr( ctx, DEVPROP_OPERATOR_CONTAINS, &$1, &$3, &$$ ))) + YYABORT; + } + | id TK_DOLLAR TK_EQUAL propval + { + if (FAILED(get_compare_expr( ctx, DEVPROP_OPERATOR_EQUALS, &$1, &$4, &$$ ))) + YYABORT; + } + | id TK_DOLLAR TK_DOLLAR propval + { + if (FAILED(get_compare_expr( ctx, DEVPROP_OPERATOR_EQUALS, &$1, &$4, &$$ ))) + YYABORT; + } + | id TK_DOLLAR TK_LT propval + { + if (FAILED(get_compare_expr( ctx, DEVPROP_OPERATOR_BEGINS_WITH_IGNORE_CASE, &$1, &$4, &$$ ))) + YYABORT; + } +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..2125689eeef 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 0b1ab0806b0..433f9c33f2b 100644 --- a/dlls/windows.devices.enumeration/tests/devices.c +++ b/dlls/windows.devices.enumeration/tests/devices.c @@ -815,35 +815,35 @@ 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_boolean_op, TRUE, TRUE ); - test_CreateWatcherAqsFilter( statics, filters_boolean_op, FALSE, FALSE, TRUE, TRUE ); + test_FindAllAsyncAqsFilter( statics, filters_boolean_op, FALSE, TRUE ); + test_CreateWatcherAqsFilter( statics, filters_boolean_op, FALSE, FALSE, FALSE, TRUE );
- 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_precedence, TRUE, FALSE ); - test_CreateWatcherAqsFilter( statics, filters_precedence, FALSE, FALSE, TRUE, TRUE ); + test_FindAllAsyncAqsFilter( statics, filters_precedence, FALSE, TRUE ); + test_CreateWatcherAqsFilter( statics, filters_precedence, FALSE, FALSE, FALSE, TRUE );
- 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 Wed Sep 17 12:53:46 2025 +0000, Rémi Bernon wrote:
Hmm, it still doesn't seem good to have two different ways to express an AND, one supported and one not, when both are supposed to be equivalent. Maybe boolean expressions should be supported first, or maybe we should only support parsing AND (explicit or not) for now, with the joined filters.
Although I guess it's perhaps also how native parses the expressions, though we don't really know, if we assume the filter are a pristine representation of the parsed string. Anyway it's probably fine, we have FIXMEs.
On Wed Sep 17 12:53:46 2025 +0000, Rémi Bernon wrote:
Although I guess it's perhaps also how native parses the expressions, though we don't really know, if we assume the filter are a pristine representation of the parsed string. Anyway it's probably fine, we have FIXMEs.
Sure. Either way, I have added some additional (todo'd ATM) tests for boolean operators in `filters_boolean_op`.
v15:
* Split `get_boolean_expr` into `get_boolean_binary_expr` (for AND/OR) and `get_boolean_not_expr` for clarity. * Add tests for boolean operators in `filters_boolean_op`. * Add tests for left-to-right operator binding rules to `filters_precedence`.
This merge request was approved by Rémi Bernon.