/* WirePlumber * * Copyright © 2020 Collabora Ltd. * @author George Kiagiadakis * * SPDX-License-Identifier: MIT */ #include "object-interest.h" #include "global-proxy.h" #include "session-item.h" #include "proxy-interfaces.h" #include "event-dispatcher.h" #include "log.h" #include "error.h" #include WP_DEFINE_LOCAL_LOG_TOPIC ("wp-object-interest") /*! \defgroup wpobjectinterest WpObjectInterest */ /*! * \struct WpObjectInterest * * An object interest is a helper that is used in WpObjectManager to * declare interest in certain kinds of objects. * * An interest is defined by a GType and a set of constraints on the object's * properties. An object "matches" the interest if it is of the specified * GType (either the same type or a descendant of it) and all the constraints * are satisfied. */ struct constraint { WpConstraintType type; WpConstraintVerb verb; gchar subject_type; /* a basic GVariantType as a single char */ gchar *subject; GVariant *value; }; struct _WpObjectInterest { grefcount ref; gboolean valid; GType gtype; struct pw_array constraints; }; G_DEFINE_BOXED_TYPE (WpObjectInterest, wp_object_interest, wp_object_interest_ref, wp_object_interest_unref) /*! * \brief Creates a new interest that declares interest in objects of the specified * \a gtype, with the constraints specified in the variable arguments. * * The variable arguments should be a list of constraints terminated with NULL, * where each constraint consists of the following arguments: * - a `WpConstraintType`: the constraint type * - a `const gchar *`: the subject name * - a `const gchar *`: the format string * - 0 or more arguments according to the format string * * The format string is interpreted as follows: * - the first character is the constraint verb: * - `=`: WP_CONSTRAINT_VERB_EQUALS * - `!`: WP_CONSTRAINT_VERB_NOT_EQUALS * - `c`: WP_CONSTRAINT_VERB_IN_LIST * - `~`: WP_CONSTRAINT_VERB_IN_RANGE * - `#`: WP_CONSTRAINT_VERB_MATCHES * - `+`: WP_CONSTRAINT_VERB_IS_PRESENT * - `-`: WP_CONSTRAINT_VERB_IS_ABSENT * - the rest of the characters are interpreted as a GVariant format string, * as it would be used in g_variant_new() * * The rest of this function's arguments up to the start of the next constraint * depend on the GVariant format part of the format string and are used to * construct a GVariant for the constraint's value argument. * * For further reading on the constraint's arguments, see * wp_object_interest_add_constraint() * * For example, this interest matches objects that are descendands of WpProxy * with a "bound-id" between 0 and 100 (inclusive), with a pipewire property * called "format.dsp" that contains the string "audio" somewhere in the value * and with a pipewire property "port.name" being present (with any value): * \code * interest = wp_object_interest_new (WP_TYPE_PROXY, * WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "~(uu)", 0, 100, * WP_CONSTRAINT_TYPE_PW_PROPERTY, "format.dsp", "#s", "*audio*", * WP_CONSTRAINT_TYPE_PW_PROPERTY, "port.name", "+", * NULL); * \endcode * * \ingroup wpobjectinterest * \param gtype the type of the object to declare interest in * \param ... a set of constraints, terminated with NULL * \returns (transfer full): the new object interest */ WpObjectInterest * wp_object_interest_new (GType gtype, ...) { WpObjectInterest *self; va_list args; va_start (args, gtype); self = wp_object_interest_new_valist (gtype, &args); va_end (args); return self; } /*! * \brief va_list version of wp_object_interest_new() * * \ingroup wpobjectinterest * \param gtype the type of the object to declare interest in * \param args pointer to va_list containing the constraints * \returns (transfer full): the new object interest */ WpObjectInterest * wp_object_interest_new_valist (GType gtype, va_list *args) { WpObjectInterest *self = wp_object_interest_new_type (gtype); WpConstraintType type; g_return_val_if_fail (self != NULL, NULL); for (type = va_arg (*args, WpConstraintType); type != WP_CONSTRAINT_TYPE_NONE; type = va_arg (*args, WpConstraintType)) { const gchar *subject, *format; WpConstraintVerb verb = 0; GVariant *value = NULL; subject = va_arg (*args, const gchar *); g_return_val_if_fail (subject != NULL, NULL); format = va_arg (*args, const gchar *); g_return_val_if_fail (format != NULL, NULL); verb = format[0]; if (verb != 0 && format[1] != '\0') value = g_variant_new_va (format + 1, NULL, args); wp_object_interest_add_constraint (self, type, subject, verb, value); } return self; } /*! * \brief Creates a new interest that declares interest in objects of the * specified \a gtype, without any property constraints. * * To add property constraints, you can call wp_object_interest_add_constraint() * afterwards. * * \ingroup wpobjectinterest * \param gtype the type of the object to declare interest in * \returns (transfer full): the new object interest */ WpObjectInterest * wp_object_interest_new_type (GType gtype) { WpObjectInterest *self = g_slice_new0 (WpObjectInterest); g_return_val_if_fail (self != NULL, NULL); g_ref_count_init (&self->ref); self->gtype = gtype; pw_array_init (&self->constraints, sizeof (struct constraint)); return self; } /*! * \brief Adds a constraint to this interest. Constraints consist of a \a type, * a \a subject, a \a verb and, depending on the \a verb, a \a value. * * Constraints are almost like a spoken language sentence that declare a * condition that must be true in order to consider that an object can match * this interest. For instance, a constraint can be "pipewire property * 'object.id' equals 10". This would be translated to: * \code * wp_object_interest_add_constraint (i, * WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", * WP_CONSTRAINT_VERB_EQUALS, g_variant_new_int (10)); * \endcode * * Some verbs require a \a value and some others do not. For those that do, * the \a value must be of a specific type: * - WP_CONSTRAINT_VERB_EQUALS: \a value can be a string, a (u)int32, * a (u)int64, a double or a boolean. The \a subject value must equal this * value for the constraint to be satisfied * - WP_CONSTRAINT_VERB_IN_LIST: \a value must be a tuple that contains any * number of items of the same type; the items can be string, (u)int32, * (u)int64 or double. These items make a list that the \a subject's value * will be checked against. If any of the items equals the \a subject value, * the constraint is satisfied * - WP_CONSTRAINT_VERB_IN_RANGE: \a value must be a tuple that contains exactly * 2 numbers of the same type ((u)int32, (u)int64 or double), meaning the * minimum and maximum (inclusive) of the range. If the \a subject value is a * number within this range, the constraint is satisfied * - WP_CONSTRAINT_VERB_MATCHES: \a value must be a string that defines a * pattern usable with GPatternSpec If the \a subject value matches this * pattern, the constraint is satisfied * * In case the type of the \a subject value is not the same type as the one * requested by the type of the \a value, the \a subject value is converted. * For GObject properties, this conversion is done using g_value_transform(), * so limitations of this function apply. In the case of PipeWire properties, * which are *always* strings, conversion is done as follows: * - to boolean: `"true"` or `"1"` means TRUE, `"false"` or `"0"` means FALSE * - to int / uint / int64 / uint64: One of the `strtol()` family of functions * is used to convert, using base 10 * - to double: `strtod()` is used * * This method does not fail if invalid arguments are given. However, * wp_object_interest_validate() should be called after adding all the * constraints on an interest in order to catch errors. * * \ingroup wpobjectinterest * \param self the object interest * \param type the constraint type * \param subject the subject that the constraint applies to * \param verb the operation that is performed to check the constraint * \param value (transfer floating)(nullable): the value to check for */ void wp_object_interest_add_constraint (WpObjectInterest * self, WpConstraintType type, const gchar * subject, WpConstraintVerb verb, GVariant * value) { struct constraint *c; g_return_if_fail (self != NULL); c = pw_array_add (&self->constraints, sizeof (struct constraint)); g_return_if_fail (c != NULL); c->type = type; c->verb = verb; /* subject_type is filled in by _validate() */ c->subject_type = '\0'; c->subject = g_strdup (subject); c->value = value ? g_variant_ref_sink (value) : NULL; /* mark as invalid to force validation */ self->valid = FALSE; } /*! * \brief Increases the reference count of an object interest * \ingroup wpobjectinterest * \param self the object interest to ref * \returns (transfer full): \a self with an additional reference count on it */ WpObjectInterest * wp_object_interest_ref (WpObjectInterest *self) { g_ref_count_inc (&self->ref); return self; } static void wp_object_interest_free (WpObjectInterest * self) { struct constraint *c; g_return_if_fail (self != NULL); pw_array_for_each (c, &self->constraints) { g_clear_pointer (&c->subject, g_free); g_clear_pointer (&c->value, g_variant_unref); } pw_array_clear (&self->constraints); g_slice_free (WpObjectInterest, self); } /*! * \brief Decreases the reference count on \a self and frees it when the ref * count reaches zero. * * \ingroup wpobjectinterest * \param self (transfer full): the object interest to unref */ void wp_object_interest_unref (WpObjectInterest * self) { if (g_ref_count_dec (&self->ref)) wp_object_interest_free (self); } /*! * \brief Validates the interest, ensuring that the interest GType * is a valid object and that all the constraints have been expressed properly. * * \remark This is called internally when \a self is first used to find a match, * so it is not necessary to call it explicitly * * \ingroup wpobjectinterest * \param self the object interest to validate * \param error (out) (optional): the error, in case validation failed * \returns TRUE if the interest is valid and can be used in a match, * FALSE otherwise */ gboolean wp_object_interest_validate (WpObjectInterest * self, GError ** error) { struct constraint *c; gboolean is_props; g_return_val_if_fail (self != NULL, FALSE); /* if already validated, we are done */ if (self->valid) return TRUE; if (!G_TYPE_IS_OBJECT (self->gtype) && !G_TYPE_IS_INTERFACE (self->gtype) && !g_type_is_a (self->gtype, WP_TYPE_PROPERTIES) && !g_type_is_a (self->gtype, WP_TYPE_EVENT)) { g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "type '%s' is not a valid interest type", g_type_name (self->gtype)); return FALSE; } is_props = g_type_is_a (self->gtype, WP_TYPE_PROPERTIES); pw_array_for_each (c, &self->constraints) { const GVariantType *value_type = NULL; if (c->type <= WP_CONSTRAINT_TYPE_NONE || c->type > WP_CONSTRAINT_TYPE_G_PROPERTY) { g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "invalid constraint type %d", c->type); return FALSE; } if (is_props && c->type == WP_CONSTRAINT_TYPE_G_PROPERTY) { g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "constraint type %d cannot apply to type '%s'", c->type, g_type_name (self->gtype)); return FALSE; } if (!c->subject) { g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "constraint subject cannot be NULL"); return FALSE; } switch (c->verb) { case WP_CONSTRAINT_VERB_EQUALS: case WP_CONSTRAINT_VERB_NOT_EQUALS: case WP_CONSTRAINT_VERB_IN_LIST: case WP_CONSTRAINT_VERB_IN_RANGE: case WP_CONSTRAINT_VERB_MATCHES: if (!c->value) { g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "verb %d (%c) requires a value", c->verb, (gchar) c->verb); return FALSE; } value_type = g_variant_get_type (c->value); break; case WP_CONSTRAINT_VERB_IS_PRESENT: case WP_CONSTRAINT_VERB_IS_ABSENT: if (c->value) { g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "verb %d (%c) should not have a value", c->verb, (gchar) c->verb); return FALSE; } break; default: g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "invalid constraint verb %d (%c)", c->verb, (gchar) c->verb); return FALSE; } switch (c->verb) { case WP_CONSTRAINT_VERB_EQUALS: case WP_CONSTRAINT_VERB_NOT_EQUALS: if (!g_variant_type_equal (value_type, G_VARIANT_TYPE_STRING) && !g_variant_type_equal (value_type, G_VARIANT_TYPE_BOOLEAN) && !g_variant_type_equal (value_type, G_VARIANT_TYPE_INT32) && !g_variant_type_equal (value_type, G_VARIANT_TYPE_UINT32) && !g_variant_type_equal (value_type, G_VARIANT_TYPE_INT64) && !g_variant_type_equal (value_type, G_VARIANT_TYPE_UINT64) && !g_variant_type_equal (value_type, G_VARIANT_TYPE_DOUBLE)) { g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "WP_CONSTRAINT_VERB_{NOT_,}EQUALS requires a basic GVariant type" " (actual type was '%s')", g_variant_get_type_string (c->value)); return FALSE; } break; case WP_CONSTRAINT_VERB_IN_LIST: { const GVariantType *tuple_type; if (!g_variant_type_is_definite (value_type) || !g_variant_type_is_tuple (value_type)) { g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "WP_CONSTRAINT_VERB_IN_LIST requires a tuple GVariant type" " (actual type was '%s')", g_variant_get_type_string (c->value)); return FALSE; } for (tuple_type = value_type = g_variant_type_first (value_type); tuple_type != NULL; tuple_type = g_variant_type_next (tuple_type)) { if (!g_variant_type_equal (tuple_type, value_type)) { g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "tuple must contain children of the same type" " (mismatching type was '%s' at '%.*s')", g_variant_get_type_string (c->value), (int) g_variant_type_get_string_length (tuple_type), g_variant_type_peek_string (tuple_type)); return FALSE; } } if (!g_variant_type_equal (value_type, G_VARIANT_TYPE_STRING) && !g_variant_type_equal (value_type, G_VARIANT_TYPE_INT32) && !g_variant_type_equal (value_type, G_VARIANT_TYPE_UINT32) && !g_variant_type_equal (value_type, G_VARIANT_TYPE_INT64) && !g_variant_type_equal (value_type, G_VARIANT_TYPE_UINT64) && !g_variant_type_equal (value_type, G_VARIANT_TYPE_DOUBLE)) { g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "list tuple must contain string, (u)int32, (u)int64 or double" " (mismatching type was '%s' at '%.*s')", g_variant_get_type_string (c->value), (int) g_variant_type_get_string_length (value_type), g_variant_type_peek_string (value_type)); return FALSE; } break; } case WP_CONSTRAINT_VERB_IN_RANGE: { const GVariantType *tuple_type; if (!g_variant_type_is_definite (value_type) || !g_variant_type_is_tuple (value_type)) { g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "range requires a tuple GVariant type (actual type was '%s')", g_variant_get_type_string (c->value)); return FALSE; } tuple_type = value_type = g_variant_type_first (value_type); if (!tuple_type) { g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "range requires a non-empty tuple (actual type was '%s')", g_variant_get_type_string (c->value)); return FALSE; } if (!g_variant_type_equal (tuple_type, G_VARIANT_TYPE_INT32) && !g_variant_type_equal (tuple_type, G_VARIANT_TYPE_UINT32) && !g_variant_type_equal (tuple_type, G_VARIANT_TYPE_INT64) && !g_variant_type_equal (tuple_type, G_VARIANT_TYPE_UINT64) && !g_variant_type_equal (tuple_type, G_VARIANT_TYPE_DOUBLE)) { g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "range tuple must contain (u)int32, (u)int64 or double" " (mismatching type was '%s' at '%.*s')", g_variant_get_type_string (c->value), (int) g_variant_type_get_string_length (tuple_type), g_variant_type_peek_string (tuple_type)); return FALSE; } tuple_type = g_variant_type_next (tuple_type); if (!tuple_type || !g_variant_type_equal (tuple_type, value_type)) { g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "range tuple must contain 2 children of the same type" " (mismatching type was '%s' at '%.*s')", g_variant_get_type_string (c->value), (int) g_variant_type_get_string_length (tuple_type), g_variant_type_peek_string (tuple_type)); return FALSE; } tuple_type = g_variant_type_next (tuple_type); if (tuple_type) { g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "range tuple must contain exactly 2 children, not more" " (mismatching type was '%s')", g_variant_get_type_string (c->value)); return FALSE; } break; } case WP_CONSTRAINT_VERB_MATCHES: if (!g_variant_type_equal (value_type, G_VARIANT_TYPE_STRING)) { g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, "WP_CONSTRAINT_VERB_MATCHES requires a string GVariant" " (actual type was '%s')", g_variant_get_type_string (c->value)); return FALSE; } break; case WP_CONSTRAINT_VERB_IS_PRESENT: case WP_CONSTRAINT_VERB_IS_ABSENT: break; default: g_return_val_if_reached (FALSE); } /* cache the type that the property must have */ if (value_type) c->subject_type = *g_variant_type_peek_string (value_type); } return (self->valid = TRUE); } G_GNUC_CONST static GType subject_type_to_gtype (gchar type) { switch (type) { case 'b': return G_TYPE_BOOLEAN; case 'i': return G_TYPE_INT; case 'u': return G_TYPE_UINT; case 'x': return G_TYPE_INT64; case 't': return G_TYPE_UINT64; case 'd': return G_TYPE_DOUBLE; case 's': return G_TYPE_STRING; default: g_return_val_if_reached (G_TYPE_INVALID); } } static inline gboolean property_string_to_gvalue (gchar subj_type, const gchar * str, GValue * val) { g_value_init (val, subject_type_to_gtype (subj_type)); switch (subj_type) { case 'b': if (!strcmp (str, "true") || !strcmp (str, "1")) g_value_set_boolean (val, TRUE); else if (!strcmp (str, "false") || !strcmp (str, "0")) g_value_set_boolean (val, FALSE); else { wp_trace ("failed to convert '%s' to boolean", str); return FALSE; } break; case 's': g_value_set_static_string (val, str); break; #define CASE_NUMBER(l, T, convert) \ case l: { \ g##T number; \ errno = 0; \ number = convert; \ if (errno != 0) { \ wp_trace ("failed to convert '%s' to " #T, str); \ return FALSE; \ } \ g_value_set_##T (val, number); \ break; \ } CASE_NUMBER ('i', int, strtol (str, NULL, 10)) CASE_NUMBER ('u', uint, strtoul (str, NULL, 10)) CASE_NUMBER ('x', int64, strtoll (str, NULL, 10)) CASE_NUMBER ('t', uint64, strtoull (str, NULL, 10)) CASE_NUMBER ('d', double, strtod (str, NULL)) #undef CASE_NUMBER default: g_return_val_if_reached (FALSE); } return TRUE; } static inline gboolean constraint_verb_equals (gchar subj_type, const GValue * subj_val, GVariant * check_val) { switch (subj_type) { case 'd': { gdouble a = g_value_get_double (subj_val); gdouble b = g_variant_get_double (check_val); return G_APPROX_VALUE (a, b, FLT_EPSILON); } case 's': return !g_strcmp0 (g_value_get_string (subj_val), g_variant_get_string (check_val, NULL)); #define CASE_BASIC(l, T, R) \ case l: \ return (g_value_get_##T (subj_val) == g_variant_get_##R (check_val)); CASE_BASIC ('b', boolean, boolean) CASE_BASIC ('i', int, int32) CASE_BASIC ('u', uint, uint32) CASE_BASIC ('x', int64, int64) CASE_BASIC ('t', uint64, uint64) #undef CASE_BASIC default: g_return_val_if_reached (FALSE); } } static inline gboolean constraint_verb_matches (gchar subj_type, const GValue * subj_val, GVariant * check_val) { switch (subj_type) { case 's': { const gchar *check_str = g_variant_get_string (check_val, NULL); const gchar *subj_str = g_value_get_string (subj_val); if (!check_str || !subj_str) return FALSE; return g_pattern_match_simple (check_str, subj_str); } default: g_return_val_if_reached (FALSE); } return TRUE; } static inline gboolean constraint_verb_in_list (gchar subj_type, const GValue * subj_val, GVariant * check_val) { GVariantIter iter; g_autoptr (GVariant) child = NULL; g_variant_iter_init (&iter, check_val); while ((child = g_variant_iter_next_value (&iter))) { if (constraint_verb_equals (subj_type, subj_val, child)) return TRUE; g_variant_unref (child); } return FALSE; } static inline gboolean constraint_verb_in_range (gchar subj_type, const GValue * subj_val, GVariant * check_val) { switch (subj_type) { #define CASE_RANGE(l, t, T) \ case l: { \ g##T val, min, max; \ g_variant_get (check_val, "("#t#t")", &min, &max); \ val = g_value_get_##T (subj_val); \ if (val < min || val > max) \ return FALSE; \ break; \ } CASE_RANGE('i', i, int) CASE_RANGE('u', u, uint) CASE_RANGE('x', x, int64) CASE_RANGE('t', t, uint64) CASE_RANGE('d', d, double) #undef CASE_RANGE default: g_return_val_if_reached (FALSE); } return TRUE; } /*! * \brief Checks if the specified \a object matches the type and all the * constraints that are described in \a self * * If \a self is configured to match GObject subclasses, this is equivalent to * `wp_object_interest_matches_full (self, G_OBJECT_TYPE (object), object, * NULL, NULL)` and if it is configured to match WpProperties, this is * equivalent to `wp_object_interest_matches_full (self, self->gtype, NULL, * (WpProperties *) object, NULL);` * * \ingroup wpobjectinterest * \param self the object interest * \param object the target object to check for a match * \returns TRUE if the object matches, FALSE otherwise */ gboolean wp_object_interest_matches (WpObjectInterest * self, gpointer object) { if (g_type_is_a (self->gtype, WP_TYPE_PROPERTIES)) { g_return_val_if_fail (object != NULL, FALSE); return wp_object_interest_matches_full (self, 0, self->gtype, NULL, (WpProperties *) object, NULL) == WP_INTEREST_MATCH_ALL; } else { g_return_val_if_fail (G_IS_OBJECT (object), FALSE); return wp_object_interest_matches_full (self, 0, G_OBJECT_TYPE (object), object, NULL, NULL) == WP_INTEREST_MATCH_ALL; } } /*! * \brief A low-level version of wp_object_interest_matches(). * * In this version, the object's type is directly given in \a object_type and * is not inferred from the \a object. \a object is only used to check for * constraints against GObject properties. * * \a pw_props and \a pw_global_props are used to check constraints against * PipeWire object properties and global properties, respectively. * * \a object, \a pw_props and \a pw_global_props may be NULL, but in case there * are any constraints that require them, the match will fail. * As a special case, if \a object is not NULL and is a subclass of WpProxy, * then \a pw_props and \a pw_global_props, if required, will be internally * retrieved from \a object by calling wp_pipewire_object_get_properties() and * wp_global_proxy_get_global_properties() respectively. * * When \a flags contains WP_INTEREST_MATCH_FLAGS_CHECK_ALL, all the constraints * are checked and the returned value contains accurate information about which * types of constraints have failed to match, if any. When this flag is not * present, this function returns after the first failure has been encountered. * This means that the returned flags set will contain all but one flag, which * will indicate the kind of constraint that failed (more could have failed, * but they are not checked...) * * \ingroup wpobjectinterest * \param self the object interest * \param flags flags to alter the behavior of this function * \param object_type the type to be checked against the interest's type * \param object (type GObject)(transfer none)(nullable): the object to be used for * checking constraints of type WP_CONSTRAINT_TYPE_G_PROPERTY * \param pw_props (transfer none)(nullable): the properties to be used for * checking constraints of type WP_CONSTRAINT_TYPE_PW_PROPERTY * \param pw_global_props (transfer none)(nullable): the properties to be used for * checking constraints of type WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY * \returns flags that indicate which components of the interest match. * WP_INTEREST_MATCH_ALL indicates a fully successful match; any other * combination indicates a failure on the component(s) that do not appear on * the flag set */ WpInterestMatch wp_object_interest_matches_full (WpObjectInterest * self, WpInterestMatchFlags flags, GType object_type, gpointer object, WpProperties * pw_props, WpProperties * pw_global_props) { WpInterestMatch result = WP_INTEREST_MATCH_ALL; g_autoptr (WpProperties) props = NULL; g_autoptr (WpProperties) global_props = NULL; g_autoptr (GError) error = NULL; struct constraint *c; g_return_val_if_fail (self != NULL, WP_INTEREST_MATCH_NONE); if (G_UNLIKELY (!wp_object_interest_validate (self, &error))) { wp_critical_boxed (WP_TYPE_OBJECT_INTEREST, self, "validation failed: %s", error->message); return WP_INTEREST_MATCH_NONE; } /* check if the GType matches */ if (!g_type_is_a (object_type, self->gtype)) result &= ~WP_INTEREST_MATCH_GTYPE; /* prepare for constraint lookups on proxy properties */ if (object) { if (!pw_global_props && WP_IS_GLOBAL_PROXY (object)) { WpGlobalProxy *pwg = (WpGlobalProxy *) object; pw_global_props = global_props = wp_global_proxy_get_global_properties (pwg); } if (!pw_props && WP_IS_PIPEWIRE_OBJECT (object)) { WpObject *oo = (WpObject *) object; WpPipewireObject *pwo = (WpPipewireObject *) object; if (wp_object_test_active_features (oo, WP_PIPEWIRE_OBJECT_FEATURE_INFO)) pw_props = props = wp_pipewire_object_get_properties (pwo); } if (!pw_global_props && WP_IS_SESSION_ITEM (object)) { WpSessionItem *si = (WpSessionItem *) object; pw_global_props = props = wp_session_item_get_properties (si); } } /* check all constraints; if any of them fails at any point, fail the match */ pw_array_for_each (c, &self->constraints) { WpProperties *lookup_props = pw_global_props; g_auto (GValue) value = G_VALUE_INIT; gboolean exists = FALSE; /* return early if the match failed and CHECK_ALL is not specified */ if (!(flags & WP_INTEREST_MATCH_FLAGS_CHECK_ALL) && result != WP_INTEREST_MATCH_ALL) return result; /* collect, check & convert the subject property */ switch (c->type) { case WP_CONSTRAINT_TYPE_PW_PROPERTY: lookup_props = pw_props; SPA_FALLTHROUGH; case WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY: { const gchar *lookup_str = NULL; if (lookup_props) exists = !!(lookup_str = wp_properties_get (lookup_props, c->subject)); if (exists && c->subject_type) property_string_to_gvalue (c->subject_type, lookup_str, &value); break; } case WP_CONSTRAINT_TYPE_G_PROPERTY: { GType value_type; GParamSpec *pspec = NULL; if (object) exists = !!(pspec = g_object_class_find_property ( G_OBJECT_GET_CLASS (object), c->subject)); if (exists && c->subject_type) { g_value_init (&value, pspec->value_type); g_object_get_property (object, c->subject, &value); value_type = G_VALUE_TYPE (&value); /* transform if not compatible */ if (value_type != subject_type_to_gtype (c->subject_type)) { if (g_value_type_transformable (value_type, subject_type_to_gtype (c->subject_type))) { g_auto (GValue) orig = G_VALUE_INIT; g_value_init (&orig, value_type); g_value_copy (&value, &orig); g_value_unset (&value); g_value_init (&value, subject_type_to_gtype (c->subject_type)); g_value_transform (&orig, &value); } else { result &= ~(1 << c->type); continue; } } } break; } default: g_return_val_if_reached (WP_INTEREST_MATCH_NONE); } /* match the subject to the constraint's value, according to the operation defined by the verb */ switch (c->verb) { case WP_CONSTRAINT_VERB_EQUALS: if (!exists || !constraint_verb_equals (c->subject_type, &value, c->value)) result &= ~(1 << c->type); break; case WP_CONSTRAINT_VERB_NOT_EQUALS: if (exists && constraint_verb_equals (c->subject_type, &value, c->value)) result &= ~(1 << c->type); break; case WP_CONSTRAINT_VERB_MATCHES: if (!exists || !constraint_verb_matches (c->subject_type, &value, c->value)) result &= ~(1 << c->type); break; case WP_CONSTRAINT_VERB_IN_LIST: if (!exists || !constraint_verb_in_list (c->subject_type, &value, c->value)) result &= ~(1 << c->type); break; case WP_CONSTRAINT_VERB_IN_RANGE: if (!exists || !constraint_verb_in_range (c->subject_type, &value, c->value)) result &= ~(1 << c->type); break; case WP_CONSTRAINT_VERB_IS_PRESENT: if (!exists) result &= ~(1 << c->type); break; case WP_CONSTRAINT_VERB_IS_ABSENT: if (exists) result &= ~(1 << c->type); break; default: g_return_val_if_reached (WP_INTEREST_MATCH_NONE); } } return result; }