2020-05-01 17:51:12 +03:00
|
|
|
/* WirePlumber
|
|
|
|
|
*
|
|
|
|
|
* Copyright © 2020 Collabora Ltd.
|
|
|
|
|
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: MIT
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "object-interest.h"
|
lib: refactor WpProxy
This is an attempt to unclutter the API of WpProxy and
split functionality into smaller pieces, making it easier
to work with.
In this new class layout, we have the following classes:
- WpObject: base class for everything; handles activating
| and deactivating "features"
|- WpProxy: base class for anything that wraps a pw_proxy;
| handles events from pw_proxy and nothing more
|- WpGlobalProxy: handles integration with the registry
All the other classes derive from WpGlobalProxy. The reason
for separating WpGlobalProxy from WpProxy, though, is that
classes such as WpImplNode / WpSpaDevice can also derive from
WpProxy now, without interfacing with the registry.
All objects that come with an "info" structure and have properties
and/or params also implement the WpPipewireObject interface. This
provides the API to query properties and get/set params. Essentially,
this is implemented by all classes except WpMetadata (pw_metadata
does not have info)
This interface is implemented on each object separately, using
a private "mixin", which is a set of vfunc implementations and helper
functions (and macros) to facilitate the implementation of this interface.
A notable difference to the old WpProxy is that now features can be
deactivated, so it is possible to enable something and later disable
it again.
This commit disables modules, tests, tools, etc, to avoid growing the
patch more, while ensuring that the project compiles.
2020-11-10 19:17:02 +02:00
|
|
|
#include "global-proxy.h"
|
2021-03-18 13:21:56 -04:00
|
|
|
#include "session-item.h"
|
lib: refactor WpProxy
This is an attempt to unclutter the API of WpProxy and
split functionality into smaller pieces, making it easier
to work with.
In this new class layout, we have the following classes:
- WpObject: base class for everything; handles activating
| and deactivating "features"
|- WpProxy: base class for anything that wraps a pw_proxy;
| handles events from pw_proxy and nothing more
|- WpGlobalProxy: handles integration with the registry
All the other classes derive from WpGlobalProxy. The reason
for separating WpGlobalProxy from WpProxy, though, is that
classes such as WpImplNode / WpSpaDevice can also derive from
WpProxy now, without interfacing with the registry.
All objects that come with an "info" structure and have properties
and/or params also implement the WpPipewireObject interface. This
provides the API to query properties and get/set params. Essentially,
this is implemented by all classes except WpMetadata (pw_metadata
does not have info)
This interface is implemented on each object separately, using
a private "mixin", which is a set of vfunc implementations and helper
functions (and macros) to facilitate the implementation of this interface.
A notable difference to the old WpProxy is that now features can be
deactivated, so it is possible to enable something and later disable
it again.
This commit disables modules, tests, tools, etc, to avoid growing the
patch more, while ensuring that the project compiles.
2020-11-10 19:17:02 +02:00
|
|
|
#include "proxy-interfaces.h"
|
2022-01-20 21:51:20 +02:00
|
|
|
#include "event-dispatcher.h"
|
2021-05-06 15:45:13 +03:00
|
|
|
#include "log.h"
|
2020-05-01 17:51:12 +03:00
|
|
|
#include "error.h"
|
2020-11-15 20:23:48 +02:00
|
|
|
|
|
|
|
|
#include <pipewire/pipewire.h>
|
2020-05-01 17:51:12 +03:00
|
|
|
|
log: implement a log topics system, like pipewire
The intention is to make checks for enabled log topics faster.
Every topic has its own structure that is statically defined in the file
where the logs are printed from. The structure is initialized transparently
when it is first used and it contains all the log level flags for the levels
that this topic should print messages. It is then checked on the wp_log()
macro before printing the message.
Topics from SPA/PipeWire are also handled natively, so messages are printed
directly without checking if the topic is enabled, since the PipeWire and SPA
macros do the checking themselves.
Messages coming from GLib are checked inside the handler.
An internal WpLogFields object is used to manage the state of each log
message, populating all the fields appropriately from the place they
are coming from (wp_log, spa_log, glib log), formatting the message and
then printing it. For printing to the journald, we still use the glib
message handler, converting all the needed fields to GLogField on demand.
That message handler does not do any checks for the topic or the level, so
we can just call it to send the message.
2023-05-16 11:51:29 +03:00
|
|
|
WP_DEFINE_LOCAL_LOG_TOPIC ("wp-object-interest")
|
|
|
|
|
|
2021-05-21 18:40:43 +03:00
|
|
|
/*! \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.
|
|
|
|
|
*/
|
|
|
|
|
|
2020-05-01 17:51:12 +03:00
|
|
|
struct constraint
|
|
|
|
|
{
|
|
|
|
|
WpConstraintType type;
|
|
|
|
|
WpConstraintVerb verb;
|
|
|
|
|
gchar subject_type; /* a basic GVariantType as a single char */
|
|
|
|
|
gchar *subject;
|
|
|
|
|
GVariant *value;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct _WpObjectInterest
|
|
|
|
|
{
|
2021-01-19 10:52:50 -05:00
|
|
|
grefcount ref;
|
2020-05-01 17:51:12 +03:00
|
|
|
gboolean valid;
|
|
|
|
|
GType gtype;
|
|
|
|
|
struct pw_array constraints;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
G_DEFINE_BOXED_TYPE (WpObjectInterest, wp_object_interest,
|
2021-06-07 17:56:32 +03:00
|
|
|
wp_object_interest_ref, wp_object_interest_unref)
|
2020-05-01 17:51:12 +03:00
|
|
|
|
2021-05-13 17:54:58 +03:00
|
|
|
/*!
|
2021-05-21 18:40:43 +03:00
|
|
|
* \brief Creates a new interest that declares interest in objects of the specified
|
|
|
|
|
* \a gtype, with the constraints specified in the variable arguments.
|
2020-05-01 17:51:12 +03:00
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* The variable arguments should be a list of constraints terminated with NULL,
|
2020-05-01 17:51:12 +03:00
|
|
|
* where each constraint consists of the following arguments:
|
2021-05-21 18:40:43 +03:00
|
|
|
* - a `WpConstraintType`: the constraint type
|
2020-05-01 17:51:12 +03:00
|
|
|
* - 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:
|
2021-05-21 18:40:43 +03:00
|
|
|
* - `=`: 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,
|
2020-05-01 17:51:12 +03:00
|
|
|
* as it would be used in g_variant_new()
|
|
|
|
|
*
|
|
|
|
|
* The rest of this function's arguments up to the start of the next constraint
|
2021-05-21 18:40:43 +03:00
|
|
|
* depend on the GVariant format part of the format string and are used to
|
|
|
|
|
* construct a GVariant for the constraint's value argument.
|
2020-05-01 17:51:12 +03:00
|
|
|
*
|
|
|
|
|
* For further reading on the constraint's arguments, see
|
|
|
|
|
* wp_object_interest_add_constraint()
|
|
|
|
|
*
|
2024-11-07 21:49:33 +00:00
|
|
|
* For example, this interest matches objects that are descendants of WpProxy
|
2020-05-01 17:51:12 +03:00
|
|
|
* 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):
|
2021-05-21 18:40:43 +03:00
|
|
|
* \code
|
2020-05-01 17:51:12 +03:00
|
|
|
* 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);
|
2021-05-21 18:40:43 +03:00
|
|
|
* \endcode
|
2020-05-01 17:51:12 +03:00
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* \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
|
2020-05-01 17:51:12 +03:00
|
|
|
*/
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-13 17:54:58 +03:00
|
|
|
/*!
|
2021-05-21 18:40:43 +03:00
|
|
|
* \brief va_list version of wp_object_interest_new()
|
2020-05-01 17:51:12 +03:00
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* \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
|
2020-05-01 17:51:12 +03:00
|
|
|
*/
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-13 17:54:58 +03:00
|
|
|
/*!
|
2021-05-21 18:40:43 +03:00
|
|
|
* \brief Creates a new interest that declares interest in objects of the
|
|
|
|
|
* specified \a gtype, without any property constraints.
|
2020-05-01 17:51:12 +03:00
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* To add property constraints, you can call wp_object_interest_add_constraint()
|
|
|
|
|
* afterwards.
|
2020-05-01 17:51:12 +03:00
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* \ingroup wpobjectinterest
|
|
|
|
|
* \param gtype the type of the object to declare interest in
|
|
|
|
|
* \returns (transfer full): the new object interest
|
2020-05-01 17:51:12 +03:00
|
|
|
*/
|
|
|
|
|
WpObjectInterest *
|
|
|
|
|
wp_object_interest_new_type (GType gtype)
|
|
|
|
|
{
|
|
|
|
|
WpObjectInterest *self = g_slice_new0 (WpObjectInterest);
|
|
|
|
|
g_return_val_if_fail (self != NULL, NULL);
|
2021-01-19 10:52:50 -05:00
|
|
|
g_ref_count_init (&self->ref);
|
2020-05-01 17:51:12 +03:00
|
|
|
self->gtype = gtype;
|
|
|
|
|
pw_array_init (&self->constraints, sizeof (struct constraint));
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-13 17:54:58 +03:00
|
|
|
/*!
|
2021-05-21 18:40:43 +03:00
|
|
|
* \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.
|
2020-05-01 17:51:12 +03:00
|
|
|
*
|
|
|
|
|
* 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:
|
2021-05-21 18:40:43 +03:00
|
|
|
* \code
|
2020-05-01 17:51:12 +03:00
|
|
|
* wp_object_interest_add_constraint (i,
|
|
|
|
|
* WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id",
|
|
|
|
|
* WP_CONSTRAINT_VERB_EQUALS, g_variant_new_int (10));
|
2021-05-21 18:40:43 +03:00
|
|
|
* \endcode
|
2020-05-01 17:51:12 +03:00
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* 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
|
2020-05-01 17:51:12 +03:00
|
|
|
* value for the constraint to be satisfied
|
2021-05-21 18:40:43 +03:00
|
|
|
* - WP_CONSTRAINT_VERB_IN_LIST: \a value must be a tuple that contains any
|
2020-05-01 17:51:12 +03:00
|
|
|
* number of items of the same type; the items can be string, (u)int32,
|
2021-05-21 18:40:43 +03:00
|
|
|
* (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,
|
2020-05-01 17:51:12 +03:00
|
|
|
* the constraint is satisfied
|
2021-05-21 18:40:43 +03:00
|
|
|
* - WP_CONSTRAINT_VERB_IN_RANGE: \a value must be a tuple that contains exactly
|
2020-05-01 17:51:12 +03:00
|
|
|
* 2 numbers of the same type ((u)int32, (u)int64 or double), meaning the
|
2021-05-21 18:40:43 +03:00
|
|
|
* minimum and maximum (inclusive) of the range. If the \a subject value is a
|
2020-05-01 17:51:12 +03:00
|
|
|
* number within this range, the constraint is satisfied
|
2021-05-21 18:40:43 +03:00
|
|
|
* - WP_CONSTRAINT_VERB_MATCHES: \a value must be a string that defines a
|
|
|
|
|
* pattern usable with GPatternSpec If the \a subject value matches this
|
2020-05-01 17:51:12 +03:00
|
|
|
* pattern, the constraint is satisfied
|
|
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* 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(),
|
2020-05-01 17:51:12 +03:00
|
|
|
* so limitations of this function apply. In the case of PipeWire properties,
|
|
|
|
|
* which are *always* strings, conversion is done as follows:
|
2021-05-21 18:40:43 +03:00
|
|
|
* - to boolean: `"true"` or `"1"` means TRUE, `"false"` or `"0"` means FALSE
|
2020-05-01 17:51:12 +03:00
|
|
|
* - 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.
|
2021-05-21 18:40:43 +03:00
|
|
|
*
|
|
|
|
|
* \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
|
2020-05-01 17:51:12 +03:00
|
|
|
*/
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-13 17:54:58 +03:00
|
|
|
/*!
|
2021-06-01 19:39:13 -04:00
|
|
|
* \brief Increases the reference count of an object interest
|
2021-05-21 18:40:43 +03:00
|
|
|
* \ingroup wpobjectinterest
|
|
|
|
|
* \param self the object interest to ref
|
|
|
|
|
* \returns (transfer full): \a self with an additional reference count on it
|
2020-05-01 17:51:12 +03:00
|
|
|
*/
|
2021-01-19 10:52:50 -05:00
|
|
|
WpObjectInterest *
|
|
|
|
|
wp_object_interest_ref (WpObjectInterest *self)
|
|
|
|
|
{
|
|
|
|
|
g_ref_count_inc (&self->ref);
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2020-05-01 17:51:12 +03:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-13 17:54:58 +03:00
|
|
|
/*!
|
2021-05-21 18:40:43 +03:00
|
|
|
* \brief Decreases the reference count on \a self and frees it when the ref
|
|
|
|
|
* count reaches zero.
|
2021-01-19 10:52:50 -05:00
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* \ingroup wpobjectinterest
|
|
|
|
|
* \param self (transfer full): the object interest to unref
|
2021-01-19 10:52:50 -05:00
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
wp_object_interest_unref (WpObjectInterest * self)
|
|
|
|
|
{
|
|
|
|
|
if (g_ref_count_dec (&self->ref))
|
|
|
|
|
wp_object_interest_free (self);
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-13 17:54:58 +03:00
|
|
|
/*!
|
2021-05-21 18:40:43 +03:00
|
|
|
* \brief Validates the interest, ensuring that the interest GType
|
|
|
|
|
* is a valid object and that all the constraints have been expressed properly.
|
2020-05-01 17:51:12 +03:00
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* \remark This is called internally when \a self is first used to find a match,
|
|
|
|
|
* so it is not necessary to call it explicitly
|
2020-05-01 17:51:12 +03:00
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* \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
|
2020-05-01 17:51:12 +03:00
|
|
|
*/
|
|
|
|
|
gboolean
|
|
|
|
|
wp_object_interest_validate (WpObjectInterest * self, GError ** error)
|
|
|
|
|
{
|
|
|
|
|
struct constraint *c;
|
2021-02-12 12:00:52 +02:00
|
|
|
gboolean is_props;
|
2020-05-01 17:51:12 +03:00
|
|
|
|
|
|
|
|
g_return_val_if_fail (self != NULL, FALSE);
|
|
|
|
|
|
|
|
|
|
/* if already validated, we are done */
|
|
|
|
|
if (self->valid)
|
|
|
|
|
return TRUE;
|
|
|
|
|
|
2021-02-12 12:00:52 +02:00
|
|
|
if (!G_TYPE_IS_OBJECT (self->gtype) && !G_TYPE_IS_INTERFACE (self->gtype) &&
|
2022-01-20 21:51:20 +02:00
|
|
|
!g_type_is_a (self->gtype, WP_TYPE_PROPERTIES) &&
|
|
|
|
|
!g_type_is_a (self->gtype, WP_TYPE_EVENT)) {
|
2020-05-01 17:51:12 +03:00
|
|
|
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
2021-02-12 12:00:52 +02:00
|
|
|
"type '%s' is not a valid interest type", g_type_name (self->gtype));
|
2020-05-01 17:51:12 +03:00
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-12 12:00:52 +02:00
|
|
|
is_props = g_type_is_a (self->gtype, WP_TYPE_PROPERTIES);
|
2020-05-01 17:51:12 +03:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-12 12:00:52 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-01 17:51:12 +03:00
|
|
|
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:
|
2021-02-02 12:50:19 +02:00
|
|
|
case WP_CONSTRAINT_VERB_NOT_EQUALS:
|
2020-05-01 17:51:12 +03:00
|
|
|
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:
|
2021-02-02 12:50:19 +02:00
|
|
|
case WP_CONSTRAINT_VERB_NOT_EQUALS:
|
2020-05-01 17:51:12 +03:00
|
|
|
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,
|
2021-02-02 12:50:19 +02:00
|
|
|
"WP_CONSTRAINT_VERB_{NOT_,}EQUALS requires a basic GVariant type"
|
2020-05-01 17:51:12 +03:00
|
|
|
" (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) {
|
2022-04-06 17:09:01 +03:00
|
|
|
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);
|
|
|
|
|
}
|
2020-05-01 17:51:12 +03:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-13 17:54:58 +03:00
|
|
|
/*!
|
2021-05-21 18:40:43 +03:00
|
|
|
* \brief Checks if the specified \a object matches the type and all the
|
|
|
|
|
* constraints that are described in \a self
|
2020-05-01 17:51:12 +03:00
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* If \a self is configured to match GObject subclasses, this is equivalent to
|
2021-02-12 12:00:52 +02:00
|
|
|
* `wp_object_interest_matches_full (self, G_OBJECT_TYPE (object), object,
|
2021-05-21 18:40:43 +03:00
|
|
|
* NULL, NULL)` and if it is configured to match WpProperties, this is
|
2021-02-12 12:00:52 +02:00
|
|
|
* equivalent to `wp_object_interest_matches_full (self, self->gtype, NULL,
|
|
|
|
|
* (WpProperties *) object, NULL);`
|
2020-05-01 17:51:12 +03:00
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* \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
|
2020-05-01 17:51:12 +03:00
|
|
|
*/
|
|
|
|
|
gboolean
|
|
|
|
|
wp_object_interest_matches (WpObjectInterest * self, gpointer object)
|
|
|
|
|
{
|
2021-02-12 12:00:52 +02:00
|
|
|
if (g_type_is_a (self->gtype, WP_TYPE_PROPERTIES)) {
|
|
|
|
|
g_return_val_if_fail (object != NULL, FALSE);
|
2021-06-07 16:13:43 +03:00
|
|
|
return wp_object_interest_matches_full (self, 0, self->gtype, NULL,
|
|
|
|
|
(WpProperties *) object, NULL) == WP_INTEREST_MATCH_ALL;
|
2021-02-12 12:00:52 +02:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
|
2021-06-07 16:13:43 +03:00
|
|
|
return wp_object_interest_matches_full (self, 0, G_OBJECT_TYPE (object),
|
|
|
|
|
object, NULL, NULL) == WP_INTEREST_MATCH_ALL;
|
2021-02-12 12:00:52 +02:00
|
|
|
}
|
2020-05-01 17:51:12 +03:00
|
|
|
}
|
|
|
|
|
|
2021-05-13 17:54:58 +03:00
|
|
|
/*!
|
2021-05-21 18:40:43 +03:00
|
|
|
* \brief A low-level version of wp_object_interest_matches().
|
2020-05-01 17:51:12 +03:00
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* 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.
|
2020-05-01 17:51:12 +03:00
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* \a pw_props and \a pw_global_props are used to check constraints against
|
2020-05-01 17:51:12 +03:00
|
|
|
* PipeWire object properties and global properties, respectively.
|
|
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* \a object, \a pw_props and \a pw_global_props may be NULL, but in case there
|
2020-05-01 17:51:12 +03:00
|
|
|
* are any constraints that require them, the match will fail.
|
2021-05-21 18:40:43 +03:00
|
|
|
* 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
|
lib: refactor WpProxy
This is an attempt to unclutter the API of WpProxy and
split functionality into smaller pieces, making it easier
to work with.
In this new class layout, we have the following classes:
- WpObject: base class for everything; handles activating
| and deactivating "features"
|- WpProxy: base class for anything that wraps a pw_proxy;
| handles events from pw_proxy and nothing more
|- WpGlobalProxy: handles integration with the registry
All the other classes derive from WpGlobalProxy. The reason
for separating WpGlobalProxy from WpProxy, though, is that
classes such as WpImplNode / WpSpaDevice can also derive from
WpProxy now, without interfacing with the registry.
All objects that come with an "info" structure and have properties
and/or params also implement the WpPipewireObject interface. This
provides the API to query properties and get/set params. Essentially,
this is implemented by all classes except WpMetadata (pw_metadata
does not have info)
This interface is implemented on each object separately, using
a private "mixin", which is a set of vfunc implementations and helper
functions (and macros) to facilitate the implementation of this interface.
A notable difference to the old WpProxy is that now features can be
deactivated, so it is possible to enable something and later disable
it again.
This commit disables modules, tests, tools, etc, to avoid growing the
patch more, while ensuring that the project compiles.
2020-11-10 19:17:02 +02:00
|
|
|
* wp_global_proxy_get_global_properties() respectively.
|
2020-05-01 17:51:12 +03:00
|
|
|
*
|
2021-06-07 16:13:43 +03:00
|
|
|
* 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...)
|
|
|
|
|
*
|
2021-05-21 18:40:43 +03:00
|
|
|
* \ingroup wpobjectinterest
|
|
|
|
|
* \param self the object interest
|
2021-06-07 16:13:43 +03:00
|
|
|
* \param flags flags to alter the behavior of this function
|
2021-05-21 18:40:43 +03:00
|
|
|
* \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
|
2021-06-07 16:13:43 +03:00
|
|
|
* \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
|
2020-05-01 17:51:12 +03:00
|
|
|
*/
|
2021-06-07 16:13:43 +03:00
|
|
|
WpInterestMatch
|
2020-05-01 17:51:12 +03:00
|
|
|
wp_object_interest_matches_full (WpObjectInterest * self,
|
2021-06-07 16:13:43 +03:00
|
|
|
WpInterestMatchFlags flags, GType object_type, gpointer object,
|
|
|
|
|
WpProperties * pw_props, WpProperties * pw_global_props)
|
2020-05-01 17:51:12 +03:00
|
|
|
{
|
2021-06-07 16:13:43 +03:00
|
|
|
WpInterestMatch result = WP_INTEREST_MATCH_ALL;
|
2020-05-01 17:51:12 +03:00
|
|
|
g_autoptr (WpProperties) props = NULL;
|
|
|
|
|
g_autoptr (WpProperties) global_props = NULL;
|
|
|
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
|
struct constraint *c;
|
|
|
|
|
|
2021-06-07 16:13:43 +03:00
|
|
|
g_return_val_if_fail (self != NULL, WP_INTEREST_MATCH_NONE);
|
2020-05-01 17:51:12 +03:00
|
|
|
|
|
|
|
|
if (G_UNLIKELY (!wp_object_interest_validate (self, &error))) {
|
|
|
|
|
wp_critical_boxed (WP_TYPE_OBJECT_INTEREST, self, "validation failed: %s",
|
|
|
|
|
error->message);
|
2021-06-07 16:13:43 +03:00
|
|
|
return WP_INTEREST_MATCH_NONE;
|
2020-05-01 17:51:12 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* check if the GType matches */
|
|
|
|
|
if (!g_type_is_a (object_type, self->gtype))
|
2021-06-07 16:13:43 +03:00
|
|
|
result &= ~WP_INTEREST_MATCH_GTYPE;
|
2020-05-01 17:51:12 +03:00
|
|
|
|
|
|
|
|
/* prepare for constraint lookups on proxy properties */
|
lib: refactor WpProxy
This is an attempt to unclutter the API of WpProxy and
split functionality into smaller pieces, making it easier
to work with.
In this new class layout, we have the following classes:
- WpObject: base class for everything; handles activating
| and deactivating "features"
|- WpProxy: base class for anything that wraps a pw_proxy;
| handles events from pw_proxy and nothing more
|- WpGlobalProxy: handles integration with the registry
All the other classes derive from WpGlobalProxy. The reason
for separating WpGlobalProxy from WpProxy, though, is that
classes such as WpImplNode / WpSpaDevice can also derive from
WpProxy now, without interfacing with the registry.
All objects that come with an "info" structure and have properties
and/or params also implement the WpPipewireObject interface. This
provides the API to query properties and get/set params. Essentially,
this is implemented by all classes except WpMetadata (pw_metadata
does not have info)
This interface is implemented on each object separately, using
a private "mixin", which is a set of vfunc implementations and helper
functions (and macros) to facilitate the implementation of this interface.
A notable difference to the old WpProxy is that now features can be
deactivated, so it is possible to enable something and later disable
it again.
This commit disables modules, tests, tools, etc, to avoid growing the
patch more, while ensuring that the project compiles.
2020-11-10 19:17:02 +02:00
|
|
|
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);
|
|
|
|
|
}
|
2020-05-01 17:51:12 +03:00
|
|
|
|
lib: refactor WpProxy
This is an attempt to unclutter the API of WpProxy and
split functionality into smaller pieces, making it easier
to work with.
In this new class layout, we have the following classes:
- WpObject: base class for everything; handles activating
| and deactivating "features"
|- WpProxy: base class for anything that wraps a pw_proxy;
| handles events from pw_proxy and nothing more
|- WpGlobalProxy: handles integration with the registry
All the other classes derive from WpGlobalProxy. The reason
for separating WpGlobalProxy from WpProxy, though, is that
classes such as WpImplNode / WpSpaDevice can also derive from
WpProxy now, without interfacing with the registry.
All objects that come with an "info" structure and have properties
and/or params also implement the WpPipewireObject interface. This
provides the API to query properties and get/set params. Essentially,
this is implemented by all classes except WpMetadata (pw_metadata
does not have info)
This interface is implemented on each object separately, using
a private "mixin", which is a set of vfunc implementations and helper
functions (and macros) to facilitate the implementation of this interface.
A notable difference to the old WpProxy is that now features can be
deactivated, so it is possible to enable something and later disable
it again.
This commit disables modules, tests, tools, etc, to avoid growing the
patch more, while ensuring that the project compiles.
2020-11-10 19:17:02 +02:00
|
|
|
if (!pw_props && WP_IS_PIPEWIRE_OBJECT (object)) {
|
|
|
|
|
WpObject *oo = (WpObject *) object;
|
|
|
|
|
WpPipewireObject *pwo = (WpPipewireObject *) object;
|
2020-05-01 17:51:12 +03:00
|
|
|
|
2023-11-08 12:23:00 +02:00
|
|
|
if (wp_object_test_active_features (oo, WP_PIPEWIRE_OBJECT_FEATURE_INFO))
|
lib: refactor WpProxy
This is an attempt to unclutter the API of WpProxy and
split functionality into smaller pieces, making it easier
to work with.
In this new class layout, we have the following classes:
- WpObject: base class for everything; handles activating
| and deactivating "features"
|- WpProxy: base class for anything that wraps a pw_proxy;
| handles events from pw_proxy and nothing more
|- WpGlobalProxy: handles integration with the registry
All the other classes derive from WpGlobalProxy. The reason
for separating WpGlobalProxy from WpProxy, though, is that
classes such as WpImplNode / WpSpaDevice can also derive from
WpProxy now, without interfacing with the registry.
All objects that come with an "info" structure and have properties
and/or params also implement the WpPipewireObject interface. This
provides the API to query properties and get/set params. Essentially,
this is implemented by all classes except WpMetadata (pw_metadata
does not have info)
This interface is implemented on each object separately, using
a private "mixin", which is a set of vfunc implementations and helper
functions (and macros) to facilitate the implementation of this interface.
A notable difference to the old WpProxy is that now features can be
deactivated, so it is possible to enable something and later disable
it again.
This commit disables modules, tests, tools, etc, to avoid growing the
patch more, while ensuring that the project compiles.
2020-11-10 19:17:02 +02:00
|
|
|
pw_props = props = wp_pipewire_object_get_properties (pwo);
|
|
|
|
|
}
|
2021-03-18 13:21:56 -04:00
|
|
|
|
|
|
|
|
if (!pw_global_props && WP_IS_SESSION_ITEM (object)) {
|
|
|
|
|
WpSessionItem *si = (WpSessionItem *) object;
|
|
|
|
|
pw_global_props = props = wp_session_item_get_properties (si);
|
2025-09-10 12:43:33 +02:00
|
|
|
if (!pw_props)
|
|
|
|
|
pw_props = props;
|
2021-03-18 13:21:56 -04:00
|
|
|
}
|
2020-05-01 17:51:12 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
|
2021-06-07 16:13:43 +03:00
|
|
|
/* 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;
|
|
|
|
|
|
2020-05-01 17:51:12 +03:00
|
|
|
/* collect, check & convert the subject property */
|
|
|
|
|
switch (c->type) {
|
|
|
|
|
case WP_CONSTRAINT_TYPE_PW_PROPERTY:
|
|
|
|
|
lookup_props = pw_props;
|
2020-12-20 22:14:00 +02:00
|
|
|
SPA_FALLTHROUGH;
|
2020-05-01 17:51:12 +03:00
|
|
|
|
|
|
|
|
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;
|
2020-06-16 19:16:04 +03:00
|
|
|
GParamSpec *pspec = NULL;
|
2020-05-01 17:51:12 +03:00
|
|
|
|
|
|
|
|
if (object)
|
2020-06-16 19:16:04 +03:00
|
|
|
exists = !!(pspec = g_object_class_find_property (
|
|
|
|
|
G_OBJECT_GET_CLASS (object), c->subject));
|
2020-05-01 17:51:12 +03:00
|
|
|
|
|
|
|
|
if (exists && c->subject_type) {
|
2020-06-16 19:16:04 +03:00
|
|
|
g_value_init (&value, pspec->value_type);
|
2020-05-01 17:51:12 +03:00
|
|
|
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);
|
|
|
|
|
}
|
2021-06-07 16:13:43 +03:00
|
|
|
else {
|
|
|
|
|
result &= ~(1 << c->type);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-05-01 17:51:12 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
2021-06-07 16:13:43 +03:00
|
|
|
g_return_val_if_reached (WP_INTEREST_MATCH_NONE);
|
2020-05-01 17:51:12 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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))
|
2021-06-07 16:13:43 +03:00
|
|
|
result &= ~(1 << c->type);
|
2020-05-01 17:51:12 +03:00
|
|
|
break;
|
2021-02-02 12:50:19 +02:00
|
|
|
case WP_CONSTRAINT_VERB_NOT_EQUALS:
|
|
|
|
|
if (exists &&
|
|
|
|
|
constraint_verb_equals (c->subject_type, &value, c->value))
|
2021-06-07 16:13:43 +03:00
|
|
|
result &= ~(1 << c->type);
|
2021-02-02 12:50:19 +02:00
|
|
|
break;
|
2020-05-01 17:51:12 +03:00
|
|
|
case WP_CONSTRAINT_VERB_MATCHES:
|
|
|
|
|
if (!exists ||
|
|
|
|
|
!constraint_verb_matches (c->subject_type, &value, c->value))
|
2021-06-07 16:13:43 +03:00
|
|
|
result &= ~(1 << c->type);
|
2020-05-01 17:51:12 +03:00
|
|
|
break;
|
|
|
|
|
case WP_CONSTRAINT_VERB_IN_LIST:
|
|
|
|
|
if (!exists ||
|
|
|
|
|
!constraint_verb_in_list (c->subject_type, &value, c->value))
|
2021-06-07 16:13:43 +03:00
|
|
|
result &= ~(1 << c->type);
|
2020-05-01 17:51:12 +03:00
|
|
|
break;
|
|
|
|
|
case WP_CONSTRAINT_VERB_IN_RANGE:
|
|
|
|
|
if (!exists ||
|
|
|
|
|
!constraint_verb_in_range (c->subject_type, &value, c->value))
|
2021-06-07 16:13:43 +03:00
|
|
|
result &= ~(1 << c->type);
|
2020-05-01 17:51:12 +03:00
|
|
|
break;
|
|
|
|
|
case WP_CONSTRAINT_VERB_IS_PRESENT:
|
|
|
|
|
if (!exists)
|
2021-06-07 16:13:43 +03:00
|
|
|
result &= ~(1 << c->type);
|
2020-05-01 17:51:12 +03:00
|
|
|
break;
|
|
|
|
|
case WP_CONSTRAINT_VERB_IS_ABSENT:
|
|
|
|
|
if (exists)
|
2021-06-07 16:13:43 +03:00
|
|
|
result &= ~(1 << c->type);
|
2020-05-01 17:51:12 +03:00
|
|
|
break;
|
|
|
|
|
default:
|
2021-06-07 16:13:43 +03:00
|
|
|
g_return_val_if_reached (WP_INTEREST_MATCH_NONE);
|
2020-05-01 17:51:12 +03:00
|
|
|
}
|
|
|
|
|
}
|
2021-06-07 16:13:43 +03:00
|
|
|
return result;
|
2021-05-21 18:40:43 +03:00
|
|
|
}
|
event-dispatcher: Register hooks for defined events in a hash table
Since all the current hooks are defined specifically for a particular event
type, we can register the hooks in a hash table using the event type as key
for faster event hook collection.
Also, hooks that are not specific to a particular event type, like constraints
such as 'event.type=*', will be registered in both the undefined hook list,
and also in all the hash table defined hook lists so they are always evaluated.
Even though 'wp_event_dispatcher_new_hooks_iterator()' can still be used, it is
now marked as deprecated because it is slower. The event hook collection uses
'wp_event_dispatcher_new_hooks_for_event_type_iterator()' now because it is
much faster.
Previously, the more hooks we were registering, the slower WirePlumber would
process events as all hooks needed to be evaluated for all events constantly.
This is not the case anymore with this patch. We can register thousands of
hooks, and if only 1 of those runs for a particular event, only 1 will be
evaluated instead of all of them.
See #824
2025-11-20 16:36:37 -05:00
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \brief Finds all the defined constraint values for a subject in \a self.
|
|
|
|
|
*
|
|
|
|
|
* A defined constraint value is the value of a constraint with the 'equal' or
|
|
|
|
|
* 'in-list' verb, because the full value must be defined with those verbs. This
|
|
|
|
|
* can be useful for cases where we want to enumerate interests that are
|
|
|
|
|
* interested in specific subjects.
|
|
|
|
|
*
|
|
|
|
|
* \ingroup wpobjectinterest
|
|
|
|
|
* \param self the object interest
|
|
|
|
|
* \param type the constraint type
|
|
|
|
|
* \param subject the subject that the constraint applies to
|
|
|
|
|
* \returns (element-type GVariant) (transfer full) (nullable): the defined
|
|
|
|
|
* constraint values for this object interest.
|
|
|
|
|
* \since 0.5.13
|
|
|
|
|
*/
|
|
|
|
|
GPtrArray *
|
|
|
|
|
wp_object_interest_find_defined_constraint_values (WpObjectInterest * self,
|
|
|
|
|
WpConstraintType type, const gchar * subject)
|
|
|
|
|
{
|
|
|
|
|
GPtrArray *res = g_ptr_array_new_with_free_func (
|
|
|
|
|
(GDestroyNotify)g_variant_unref);
|
|
|
|
|
struct constraint *c;
|
|
|
|
|
|
|
|
|
|
pw_array_for_each (c, &self->constraints) {
|
|
|
|
|
if ((c->type == type || WP_CONSTRAINT_TYPE_NONE == type) &&
|
|
|
|
|
g_str_equal (c->subject, subject)) {
|
|
|
|
|
switch (c->verb) {
|
|
|
|
|
case WP_CONSTRAINT_VERB_EQUALS:
|
|
|
|
|
g_ptr_array_add (res, g_variant_ref (c->value));
|
|
|
|
|
break;
|
|
|
|
|
case WP_CONSTRAINT_VERB_IN_LIST: {
|
|
|
|
|
GVariantIter iter;
|
|
|
|
|
GVariant *child;
|
|
|
|
|
g_variant_iter_init (&iter, c->value);
|
|
|
|
|
while ((child = g_variant_iter_next_value (&iter)))
|
|
|
|
|
g_ptr_array_add (res, child);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
}
|