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 ); }