diff --git a/lib/wp/core.c b/lib/wp/core.c index 2a0e1a21..b6b2d546 100644 --- a/lib/wp/core.c +++ b/lib/wp/core.c @@ -219,3 +219,4 @@ G_DEFINE_QUARK (pw-remote, wp_global_pw_remote) G_DEFINE_QUARK (endpoint, wp_global_endpoint) G_DEFINE_QUARK (factory, wp_global_factory) G_DEFINE_QUARK (module, wp_global_module) +G_DEFINE_QUARK (policy-manager, wp_global_policy_manager) diff --git a/lib/wp/core.h b/lib/wp/core.h index 6e9af37a..e9b331f6 100644 --- a/lib/wp/core.h +++ b/lib/wp/core.h @@ -57,6 +57,9 @@ GQuark wp_global_factory_quark (void); #define WP_GLOBAL_MODULE (wp_global_module_quark ()) GQuark wp_global_module_quark (void); +#define WP_GLOBAL_POLICY_MANAGER (wp_global_policy_manager_quark ()) +GQuark wp_global_policy_manager_quark (void); + G_END_DECLS #endif diff --git a/lib/wp/meson.build b/lib/wp/meson.build index f6d2a88e..beadafac 100644 --- a/lib/wp/meson.build +++ b/lib/wp/meson.build @@ -4,6 +4,7 @@ wp_lib_sources = [ 'error.c', 'factory.c', 'module.c', + 'policy.c', ] wp_lib_headers = [ @@ -12,6 +13,7 @@ wp_lib_headers = [ 'error.h', 'factory.h', 'module.h', + 'policy.h', 'wp.h', ] diff --git a/lib/wp/policy.c b/lib/wp/policy.c new file mode 100644 index 00000000..8045e293 --- /dev/null +++ b/lib/wp/policy.c @@ -0,0 +1,335 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author George Kiagiadakis + * + * SPDX-License-Identifier: MIT + */ + +#include "policy.h" + +/* WpPolicyManager */ + +struct _WpPolicyManager +{ + GObject parent; + GList *policies; +}; + +G_DEFINE_TYPE (WpPolicyManager, wp_policy_manager, G_TYPE_OBJECT) + +static void +wp_policy_manager_init (WpPolicyManager *self) +{ +} + +static void +wp_policy_manager_finalize (GObject *object) +{ + WpPolicyManager *self = WP_POLICY_MANAGER (object); + + g_list_free_full (self->policies, g_object_unref); + + G_OBJECT_CLASS (wp_policy_manager_parent_class)->finalize (object); +} + +static void +wp_policy_manager_class_init (WpPolicyManagerClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + object_class->finalize = wp_policy_manager_finalize; +} + +static void +policy_mgr_endpoint_added (WpCore *core, GQuark key, WpEndpoint *ep, + WpPolicyManager *self) +{ + GList *l; + WpPolicy *p; + gboolean handled = FALSE; + + for (l = g_list_first (self->policies); l; l = g_list_next (l)) { + p = WP_POLICY (l->data); + + if (WP_POLICY_GET_CLASS (p)->endpoint_added) + WP_POLICY_GET_CLASS (p)->endpoint_added (p, ep); + + if (!handled && WP_POLICY_GET_CLASS (p)->handle_endpoint) + handled = WP_POLICY_GET_CLASS (p)->handle_endpoint (p, ep); + } +} + +static void +policy_mgr_endpoint_removed (WpCore *core, GQuark key, WpEndpoint *ep, + WpPolicyManager *self) +{ + GList *l; + WpPolicy *p; + + for (l = g_list_first (self->policies); l; l = g_list_next (l)) { + p = WP_POLICY (l->data); + + if (WP_POLICY_GET_CLASS (p)->endpoint_removed) + WP_POLICY_GET_CLASS (p)->endpoint_removed (p, ep); + } +} + +static WpPolicyManager * +wp_policy_manager_new (WpCore *core) +{ + WpPolicyManager *mgr = g_object_new (WP_TYPE_POLICY_MANAGER, NULL); + + g_signal_connect_object (core, "global-added::endpoint", + (GCallback) policy_mgr_endpoint_added, mgr, 0); + g_signal_connect_object (core, "global-removed::endpoint", + (GCallback) policy_mgr_endpoint_removed, mgr, 0); + + wp_core_register_global (core, WP_GLOBAL_POLICY_MANAGER, mgr, + g_object_unref); + + return mgr; +} + +/* WpPolicy */ + +/** + * WpPolicyClass::endpoint_added: + * @self: the policy + * @ep: the endpoint + * + * Called when a new endpoint has been added. + * This is only informative, to be used for internal bookeeping purposes. + * No action should be taken to do something with this endpoint. + */ + +/** + * WpPolicyClass::endpoint_removed: + * @self: the policy + * @ep: the endpoint + * + * Called when an endpoint has been removed. + * This is only informative, to be used for internal bookeeping purposes. + */ + +/** + * WpPolicyClass::handle_endpoint: + * @self: the policy + * @ep: the endpoint + * + * Called when a new endpoint has been added. + * The policy is meant to decide if this endpoint needs to be linked + * somewhere and if so, create the link. + * This will only be called if no other higher-ranked policy has already + * handled this endpoint. + * + * Returns: TRUE if this policy did handle the endpoint, FALSE to let some + * lower-ranked policy to try + */ + +/** + * WpPolicyClass::find_endpoint: + * @self: the policy + * @props: properties of the lookup + * @stream_id: (out): the relevant stream id of the returned endpoint + * + * Called to locate an endpoint with a specific set of properties, + * which may be used to implement decision making when multiple endpoints + * can match. + * + * The most notorious use case of this function is to locate a target + * device endpoint in order to link a client one. + * + * @props is expected to be a dictionary (a{sv}) GVariant with keys that + * describe the situation. Some of these keys can be: + * * "action" (s): Currently the value can be "link" or "mixer". "link" is + * to find a target for linking a client. "mixer" is to find a target + * to modify mixer controls. + * * "media.role" (s): the role of the media stream, as defined in pipewire + * * "media.class" (s): the media class that the returned endpoint is supposed + * to have (policy is free to ignore this) + * * "target.properties" (a{sv}): the properties of the other endpoint in case + * the action is "link" + * + * @stream_id is to be set to the stream id of the returned endpoint that + * the policy wants to be used for this action. + * + * Returns: (transfer full) (nullable): the found endpoint, or NULL + */ + +typedef struct _WpPolicyPrivate WpPolicyPrivate; +struct _WpPolicyPrivate +{ + guint32 rank; + WpCore *core; +}; + +enum { + PROP_0, + PROP_RANK +}; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (WpPolicy, wp_policy, G_TYPE_OBJECT) + +static void +wp_policy_init (WpPolicy *self) +{ +} + +static void +wp_policy_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + WpPolicyPrivate *priv = wp_policy_get_instance_private (WP_POLICY (object)); + + switch (property_id) { + case PROP_RANK: + priv->rank = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +wp_policy_get_property (GObject * object, guint property_id, GValue * value, + GParamSpec * pspec) +{ + WpPolicyPrivate *priv = wp_policy_get_instance_private (WP_POLICY (object)); + + switch (property_id) { + case PROP_RANK: + g_value_set_uint (value, priv->rank); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +wp_policy_class_init (WpPolicyClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + object_class->set_property = wp_policy_set_property; + object_class->get_property = wp_policy_get_property; + + g_object_class_install_property (object_class, PROP_RANK, + g_param_spec_uint ("rank", "rank", "The rank of the policy", + 0, G_MAXINT32, WP_POLICY_RANK_UPSTREAM, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); +} + +guint32 +wp_policy_get_rank (WpPolicy *self) +{ + WpPolicyPrivate *priv; + + g_return_val_if_fail (WP_IS_POLICY (self), 0); + + priv = wp_policy_get_instance_private (self); + return priv->rank; +} + +WpCore * +wp_policy_get_core (WpPolicy *self) +{ + WpPolicyPrivate *priv; + + g_return_val_if_fail (WP_IS_POLICY (self), NULL); + + priv = wp_policy_get_instance_private (self); + return priv->core ? g_object_ref (priv->core) : NULL; +} + +static gint +compare_ranks (const WpPolicy * a, const WpPolicy * b) +{ + WpPolicyPrivate *a_priv = wp_policy_get_instance_private ((WpPolicy *) a); + WpPolicyPrivate *b_priv = wp_policy_get_instance_private ((WpPolicy *) b); + return (gint) b_priv->rank - (gint) a_priv->rank; +} + +void +wp_policy_register (WpPolicy *self, WpCore *core) +{ + WpPolicyManager *mgr; + WpPolicyPrivate *priv; + + g_return_if_fail (WP_IS_POLICY (self)); + g_return_if_fail (WP_IS_CORE (core)); + + mgr = wp_core_get_global (core, WP_GLOBAL_POLICY_MANAGER); + if (G_UNLIKELY (!mgr)) + mgr = wp_policy_manager_new (core); + + mgr->policies = g_list_insert_sorted (mgr->policies, g_object_ref (self), + (GCompareFunc) compare_ranks); + + priv = wp_policy_get_instance_private (self); + priv->core = core; +} + +void +wp_policy_unregister (WpPolicy *self) +{ + WpPolicyManager *mgr; + WpPolicyPrivate *priv; + + g_return_if_fail (WP_IS_POLICY (self)); + + priv = wp_policy_get_instance_private (self); + + if (priv->core) { + mgr = wp_core_get_global (priv->core, WP_GLOBAL_POLICY_MANAGER); + if (G_UNLIKELY (!mgr)) { + g_critical ("WpPolicy:%p seems registered, but the policy manager " + "is absent", self); + return; + } + + mgr->policies = g_list_remove (mgr->policies, self); + g_object_unref (self); + } +} + +/** + * wp_policy_find_endpoint: + * @core: the #WpCore + * @props: properties of the lookup + * @stream_id: (out): the relevant stream id of the returned endpoint + * + * Calls #WpPolicyClass::find_endpoint on all policies, in order, until + * it finds a suitable endpoint. + * + * Returns: (transfer full) (nullable): the found endpoint, or NULL + */ +WpEndpoint * +wp_policy_find_endpoint (WpCore *core, GVariant *props, + guint32 *stream_id) +{ + WpPolicyManager *mgr; + GList *l; + WpPolicy *p; + WpEndpoint * ret; + + g_return_val_if_fail (WP_IS_CORE (core), NULL); + g_return_val_if_fail (g_variant_is_of_type (props, G_VARIANT_TYPE_VARDICT), NULL); + g_return_val_if_fail (stream_id != NULL, NULL); + + mgr = wp_core_get_global (core, WP_GLOBAL_POLICY_MANAGER); + if (mgr) { + for (l = g_list_first (mgr->policies); l; l = g_list_next (l)) { + p = WP_POLICY (l->data); + + if (WP_POLICY_GET_CLASS (p)->find_endpoint && + (ret = WP_POLICY_GET_CLASS (p)->find_endpoint (p, props, stream_id))) + return ret; + } + } + + return NULL; +} diff --git a/lib/wp/policy.h b/lib/wp/policy.h new file mode 100644 index 00000000..9b58fdd1 --- /dev/null +++ b/lib/wp/policy.h @@ -0,0 +1,66 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author George Kiagiadakis + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __WIREPLUMBER_POLICY_H__ +#define __WIREPLUMBER_POLICY_H__ + +#include "endpoint.h" + +G_BEGIN_DECLS + +/** + * WpPolicyRank: + * @WP_POLICY_RANK_UPSTREAM: should only be used inside WirePlumber + * @WP_POLICY_RANK_PLATFORM: policies provided by the platform + * @WP_POLICY_RANK_VENDOR: policies provided by hardware vendors + * + * The rank of a policy is an unsigned integer that can take an arbitrary + * value from 0 to G_MAXINT32 (0x7fffffff). On invocation, policies ranked + * with a higher number are tried first, which is how one can implement + * overrides. This enum provides default values for certain kinds of policies. + * Feel free to add/substract numbers to these constants in order to make a + * hierarchy, if you are implementing multiple different policies that need to + * be tried in a certain order. + */ +typedef enum { + WP_POLICY_RANK_UPSTREAM = 1, + WP_POLICY_RANK_PLATFORM = 128, + WP_POLICY_RANK_VENDOR = 256, +} WpPolicyRank; + +#define WP_TYPE_POLICY_MANAGER (wp_policy_manager_get_type ()) +G_DECLARE_FINAL_TYPE (WpPolicyManager, wp_policy_manager, WP, POLICY_MANAGER, GObject) + +#define WP_TYPE_POLICY (wp_policy_get_type ()) +G_DECLARE_DERIVABLE_TYPE (WpPolicy, wp_policy, WP, POLICY, GObject) + +struct _WpPolicyClass +{ + GObjectClass parent_class; + + void (*endpoint_added) (WpPolicy *self, WpEndpoint *ep); + void (*endpoint_removed) (WpPolicy *self, WpEndpoint *ep); + + gboolean (*handle_endpoint) (WpPolicy *self, WpEndpoint *ep); + + WpEndpoint * (*find_endpoint) (WpPolicy *self, GVariant *props, + guint32 *stream_id); +}; + +guint32 wp_policy_get_rank (WpPolicy *self); +WpCore *wp_policy_get_core (WpPolicy *self); + +void wp_policy_register (WpPolicy *self, WpCore *core); +void wp_policy_unregister (WpPolicy *self); + +WpEndpoint * wp_policy_find_endpoint (WpCore *core, GVariant *props, + guint32 *stream_id); + +G_END_DECLS + +#endif diff --git a/lib/wp/wp.h b/lib/wp/wp.h index 8463404c..aa8906df 100644 --- a/lib/wp/wp.h +++ b/lib/wp/wp.h @@ -11,3 +11,4 @@ #include "error.h" #include "factory.h" #include "module.h" +#include "policy.h"