From 32dd4856490b1cd70500e8ef6ec3a9e47661d017 Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Thu, 12 Mar 2020 18:00:01 +0200 Subject: [PATCH] lib: implement WpSessionItem base class --- lib/wp/meson.build | 2 + lib/wp/session-item.c | 354 ++++++++++++++++++++++++++++++++++++++++++ lib/wp/session-item.h | 132 ++++++++++++++++ lib/wp/wp.h | 1 + 4 files changed, 489 insertions(+) create mode 100644 lib/wp/session-item.c create mode 100644 lib/wp/session-item.h diff --git a/lib/wp/meson.build b/lib/wp/meson.build index cf17d9f3..6021b8bc 100644 --- a/lib/wp/meson.build +++ b/lib/wp/meson.build @@ -16,6 +16,7 @@ wp_lib_sources = files( 'properties.c', 'proxy.c', 'session.c', + 'session-item.c', 'spa-props.c', 'transition.c', ) @@ -39,6 +40,7 @@ wp_lib_headers = files( 'properties.h', 'proxy.h', 'session.h', + 'session-item.h', 'transition.h', 'wp.h', ) diff --git a/lib/wp/session-item.c b/lib/wp/session-item.c new file mode 100644 index 00000000..d8849d66 --- /dev/null +++ b/lib/wp/session-item.c @@ -0,0 +1,354 @@ +/* WirePlumber + * + * Copyright © 2020 Collabora Ltd. + * @author George Kiagiadakis + * + * SPDX-License-Identifier: MIT + */ + +/** + * SECTION: WpSessionItem + */ + +#include "session-item.h" +#include "wpenums.h" + +typedef struct _WpSessionItemPrivate WpSessionItemPrivate; +struct _WpSessionItemPrivate +{ + GWeakRef session; + guint32 flags; +}; + +enum { + SIGNAL_FLAGS_CHANGED, + N_SIGNALS +}; + +guint32 signals[N_SIGNALS] = {0}; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (WpSessionItem, wp_session_item, G_TYPE_OBJECT) + +static void +wp_session_item_init (WpSessionItem * self) +{ + WpSessionItemPrivate *priv = + wp_session_item_get_instance_private (self); + + g_weak_ref_init (&priv->session, NULL); +} + +static void +wp_session_item_dispose (GObject * object) +{ + WpSessionItem * self = WP_SESSION_ITEM (object); + + wp_session_item_reset (self); + + G_OBJECT_CLASS (wp_session_item_parent_class)->dispose (object); +} + +static void +wp_session_item_finalize (GObject * object) +{ + WpSessionItem * self = WP_SESSION_ITEM (object); + WpSessionItemPrivate *priv = + wp_session_item_get_instance_private (self); + + g_weak_ref_clear (&priv->session); + + G_OBJECT_CLASS (wp_session_item_parent_class)->finalize (object); +} + +static void +wp_session_item_default_reset (WpSessionItem * self) +{ + WpSessionItemPrivate *priv = + wp_session_item_get_instance_private (self); + + priv->flags &= ~(WP_SI_FLAG_ACTIVE | WP_SI_FLAG_IN_ERROR); + g_signal_emit (self, signals[SIGNAL_FLAGS_CHANGED], 0, priv->flags); +} + +static void +wp_session_item_class_init (WpSessionItemClass * klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + object_class->dispose = wp_session_item_dispose; + object_class->finalize = wp_session_item_finalize; + + klass->reset = wp_session_item_default_reset; + + /** + * WpSessionItem::flags-changed: + * @self: the session item + * @flags: the current flags + */ + signals[SIGNAL_FLAGS_CHANGED] = g_signal_new ( + "flags-changed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, WP_TYPE_SI_FLAGS); +} + +/** + * wp_session_item_get_session: + * @self: the session item + * + * Returns: (nullable) (transfer full): the session that owns this item, or + * %NULL if this item is not part of a session + */ +WpSession * +wp_session_item_get_session (WpSessionItem * self) +{ + g_return_val_if_fail (WP_IS_SESSION_ITEM (self), NULL); + + WpSessionItemPrivate *priv = + wp_session_item_get_instance_private (self); + return g_weak_ref_get (&priv->session); +} + +/** + * wp_session_item_get_flags: + * @self: the session item + * + * Returns: the item's flags + */ +WpSiFlags +wp_session_item_get_flags (WpSessionItem * self) +{ + g_return_val_if_fail (WP_IS_SESSION_ITEM (self), 0); + + WpSessionItemPrivate *priv = + wp_session_item_get_instance_private (self); + return priv->flags; +} + +/** + * wp_session_item_set_flag: + * @self: the session item + * @flag: the flag to set + * + * Sets the specified @flag on this item. + * + * Note that bits 1-8 cannot be set using this function, they can only + * be changed internally. + */ +void +wp_session_item_set_flag (WpSessionItem * self, WpSiFlags flag) +{ + g_return_if_fail (WP_IS_SESSION_ITEM (self)); + + WpSessionItemPrivate *priv = + wp_session_item_get_instance_private (self); + + /* mask to make sure we are not changing an immutable flag */ + flag &= ~((1<<8) - 1); + if (flag != 0) { + priv->flags |= flag; + g_signal_emit (self, signals[SIGNAL_FLAGS_CHANGED], 0, priv->flags); + } +} + +/** + * wp_session_item_clear_flag: + * @self: the session item + * @flag: the flag to clear + * + * Clears the specified @flag from this item. + * + * Note that bits 1-8 cannot be cleared using this function, they can only + * be changed internally. + */ +void +wp_session_item_clear_flag (WpSessionItem * self, WpSiFlags flag) +{ + g_return_if_fail (WP_IS_SESSION_ITEM (self)); + + WpSessionItemPrivate *priv = + wp_session_item_get_instance_private (self); + + /* mask to make sure we are not changing an immutable flag */ + flag &= ~((1<<8) - 1); + if (flag != 0) { + priv->flags &= ~flag; + g_signal_emit (self, signals[SIGNAL_FLAGS_CHANGED], 0, priv->flags); + } +} + +/** + * wp_session_item_get_config_spec: (virtual get_config_spec) + * @self: the session item + * + * Constructs and returns a description of all the configuration options + * that this item has. Configuration options are a way for items to accept + * input from external sources that affects their behavior, or to provide + * output for other items to consume as their configuration. + * + * The returned GVariant has the a(ssymv) type. This is an array of tuples, + * where each tuple has the following values, in order: + * * s (string): the name of the option + * * s (string): a GVariant type string, describing the type of the data + * * y (byte): a combination of #WpSiConfigOptionFlags + * * mv (optional variant): optionally, an additional variant + * This is provided to allow extensions. + * + * Returns: (transfer full): the configuration description + */ +GVariant * +wp_session_item_get_config_spec (WpSessionItem * self) +{ + g_return_val_if_fail (WP_IS_SESSION_ITEM (self), NULL); + g_return_val_if_fail (WP_SESSION_ITEM_GET_CLASS (self)->get_config_spec, + NULL); + + return WP_SESSION_ITEM_GET_CLASS (self)->get_config_spec (self); +} + +/** + * wp_session_item_configure: (virtual configure) + * @self: the session item + * @args: (transfer floating): the configuration options to set + * (`a{sv}` dictionary, mapping option names to values) + * + * Returns: %TRUE on success, %FALSE if the options could not be set + */ +gboolean +wp_session_item_configure (WpSessionItem * self, GVariant * args) +{ + g_return_val_if_fail (WP_IS_SESSION_ITEM (self), FALSE); + g_return_val_if_fail (WP_SESSION_ITEM_GET_CLASS (self)->configure, + FALSE); + g_return_val_if_fail (g_variant_is_of_type (args, G_VARIANT_TYPE ("a{sv}")), + FALSE); + + return WP_SESSION_ITEM_GET_CLASS (self)->configure (self, args); +} + +/** + * wp_session_item_get_configuration: (virtual get_configuration) + * @self: the session item + * + * Returns: (transfer full): the active configuration, as a `a{sv}` dictionary + */ +GVariant * +wp_session_item_get_configuration (WpSessionItem * self) +{ + g_return_val_if_fail (WP_IS_SESSION_ITEM (self), NULL); + g_return_val_if_fail (WP_SESSION_ITEM_GET_CLASS (self)->get_configuration, + NULL); + + return WP_SESSION_ITEM_GET_CLASS (self)->get_configuration (self); +} + +typedef WpTransition WpSiTransition; +typedef WpTransitionClass WpSiTransitionClass; + +G_DEFINE_TYPE (WpSiTransition, wp_si_transition, WP_TYPE_TRANSITION) + +static void +wp_si_transition_init (WpSiTransition * transition) {} + +static guint +wp_si_transition_get_next_step (WpTransition * transition, guint step) +{ + WpSessionItem *item = wp_transition_get_source_object (transition); + g_return_val_if_fail ( + WP_SESSION_ITEM_GET_CLASS (item)->get_next_step, + WP_TRANSITION_STEP_ERROR); + g_return_val_if_fail ( + WP_SESSION_ITEM_GET_CLASS (item)->execute_step, + WP_TRANSITION_STEP_ERROR); + + return WP_SESSION_ITEM_GET_CLASS (item)->get_next_step (item, + transition, step); +} + +static void +wp_si_transition_execute_step (WpTransition * transition, guint step) +{ + WpSessionItem *item = wp_transition_get_source_object (transition); + WP_SESSION_ITEM_GET_CLASS (item)->execute_step (item, transition, step); +} + +static void +wp_si_transition_class_init (WpSiTransitionClass * klass) +{ + WpTransitionClass *transition_class = (WpTransitionClass *) klass; + + transition_class->get_next_step = wp_si_transition_get_next_step; + transition_class->execute_step = wp_si_transition_execute_step; +} + +static void +on_transition_completed (WpTransition * transition, GParamSpec * pspec, + WpSessionItem * self) +{ + WpSessionItemPrivate *priv = + wp_session_item_get_instance_private (self); + + if (wp_transition_had_error (transition)) + priv->flags |= WP_SI_FLAG_IN_ERROR; + else + priv->flags |= WP_SI_FLAG_ACTIVE; + + priv->flags &= ~WP_SI_FLAG_ACTIVATING; + g_signal_emit (self, signals[SIGNAL_FLAGS_CHANGED], 0, priv->flags); +} + +/** + * wp_session_item_activate: + * @self: the session item + * @callback: (scope async): a callback to call when activation is finished + * @callback_data: (closure): data passed to @callback + * + * Activates the item asynchronously. This internally starts a #WpTransition + * that calls into #WpSessionItemClass.get_next_step() and + * #WpSessionItemClass.execute_step() to advance. + * + * You can use wp_transition_finish() in the @callback to figure out the + * result of this operation. + * + * Normally this function is called internally by the session; there is no need + * to activate an item externally, except for unit testing purposes. + */ +void +wp_session_item_activate (WpSessionItem * self, + GAsyncReadyCallback callback, + gpointer callback_data) +{ + g_return_if_fail (WP_IS_SESSION_ITEM (self)); + + WpSessionItemPrivate *priv = + wp_session_item_get_instance_private (self); + + g_return_if_fail (!(priv->flags & (WP_SI_FLAG_ACTIVATING | WP_SI_FLAG_ACTIVE))); + + /* TODO: add a way to cancel the transition if reset() is called in the meantime */ + WpTransition *transition = wp_transition_new (wp_si_transition_get_type (), + self, NULL, callback, callback_data); + wp_transition_set_source_tag (transition, wp_session_item_activate); + g_signal_connect (transition, "notify::completed", + (GCallback) on_transition_completed, self); + + priv->flags |= WP_SI_FLAG_ACTIVATING; + g_signal_emit (self, signals[SIGNAL_FLAGS_CHANGED], 0, priv->flags); + + wp_transition_advance (transition); +} + +/** + * wp_session_item_reset: (virtual reset) + * @self: the session item + * + * Resets the state of the item, deactivating it, and possibly + * resetting configuration options as well. + */ +void +wp_session_item_reset (WpSessionItem * self) +{ + g_return_if_fail (WP_IS_SESSION_ITEM (self)); + g_return_if_fail (WP_SESSION_ITEM_GET_CLASS (self)->reset); + + WP_SESSION_ITEM_GET_CLASS (self)->reset (self); +} diff --git a/lib/wp/session-item.h b/lib/wp/session-item.h new file mode 100644 index 00000000..793a4d99 --- /dev/null +++ b/lib/wp/session-item.h @@ -0,0 +1,132 @@ +/* WirePlumber + * + * Copyright © 2020 Collabora Ltd. + * @author George Kiagiadakis + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __WIREPLUMBER_SESSION_ITEM_H__ +#define __WIREPLUMBER_SESSION_ITEM_H__ + +#include "transition.h" + +G_BEGIN_DECLS + +typedef struct _WpSession WpSession; + +/** + * WP_TYPE_SESSION_ITEM: + * + * The #WpSessionItem #GType + */ +#define WP_TYPE_SESSION_ITEM (wp_session_item_get_type ()) +WP_API +G_DECLARE_DERIVABLE_TYPE (WpSessionItem, wp_session_item, + WP, SESSION_ITEM, GObject) + +/** + * WpSiFlags: + * @WP_SI_FLAG_ACTIVATING: set when an activation transition is in progress + * @WP_SI_FLAG_ACTIVE: set when an activation transition completes successfully + * @WP_SI_FLAG_EXPORTED: set when the item has exported all necessary objects + * to PipeWire + * @WP_SI_FLAG_IN_ERROR: set when there was an error in the activation process; + * to recover, the handler must call wp_session_item_reset() before anything + * else + * @WP_SI_FLAG_CONFIGURED: must be set by subclasses when all the required + * (%WP_SI_CONFIG_OPTION_REQUIRED) configuration options have been set + */ +typedef enum { + /* immutable flags, set internally */ + WP_SI_FLAG_ACTIVATING = (1<<0), + WP_SI_FLAG_ACTIVE = (1<<1), + WP_SI_FLAG_EXPORTED = (1<<2), + WP_SI_FLAG_IN_ERROR = (1<<3), + + /* flags that can be changed by subclasses */ + WP_SI_FLAG_CONFIGURED = (1<<8), + + /* implementation-specific flags */ + WP_SI_FLAG_CUSTOM_START = (1<<16), +} WpSiFlags; + +/** + * WpSiConfigOptionFlags: + * @WP_SI_CONFIG_OPTION_WRITEABLE: the option can be set externally + * @WP_SI_CONFIG_OPTION_REQUIRED: the option is required to complete activation + * @WP_SI_CONFIG_OPTION_PROVIDED: the value of this option can be provided + * by the implementation if it is not set externally; this can be used to + * have a "default fallback" value or to report immutable configuration + * that is discovered from an underlying layer (ex. hardware properties) + */ +typedef enum { + WP_SI_CONFIG_OPTION_WRITEABLE = (1<<0), + WP_SI_CONFIG_OPTION_REQUIRED = (1<<1), + WP_SI_CONFIG_OPTION_PROVIDED = (1<<2), +} WpSiConfigOptionFlags; + +/** + * WpSessionItemClass: + * @get_config_spec: See wp_session_item_get_config_spec() + * @configure: See wp_session_item_configure() + * @get_configuration: See wp_session_item_get_configuration() + * @get_next_step: Implements #WpTransitionClass.get_next_step() for the + * transition of wp_session_item_activate() + * @execute_step: Implements #WpTransitionClass.execute_step() for the + * transition of wp_session_item_activate() + * @reset: See wp_session_item_reset() + */ +struct _WpSessionItemClass +{ + GObjectClass parent_class; + + GVariant * (*get_config_spec) (WpSessionItem * self); + gboolean (*configure) (WpSessionItem * self, GVariant * args); + GVariant * (*get_configuration) (WpSessionItem * self); + + guint (*get_next_step) (WpSessionItem * self, WpTransition * transition, + guint step); + void (*execute_step) (WpSessionItem * self, WpTransition * transition, + guint step); + + void (*reset) (WpSessionItem * self); +}; + +/* properties */ + +WP_API +WpSession * wp_session_item_get_session (WpSessionItem * self); + +WP_API +WpSiFlags wp_session_item_get_flags (WpSessionItem * self); + +WP_API +void wp_session_item_set_flag (WpSessionItem * self, WpSiFlags flag); + +WP_API +void wp_session_item_clear_flag (WpSessionItem * self, WpSiFlags flag); + +/* configuration */ + +WP_API +GVariant * wp_session_item_get_config_spec (WpSessionItem * self); + +WP_API +gboolean wp_session_item_configure (WpSessionItem * self, GVariant * args); + +WP_API +GVariant * wp_session_item_get_configuration (WpSessionItem * self); + +/* state management */ + +WP_API +void wp_session_item_activate (WpSessionItem * self, + GAsyncReadyCallback callback, gpointer callback_data); + +WP_API +void wp_session_item_reset (WpSessionItem * self); + +G_END_DECLS + +#endif diff --git a/lib/wp/wp.h b/lib/wp/wp.h index 531873db..c11fb72e 100644 --- a/lib/wp/wp.h +++ b/lib/wp/wp.h @@ -23,5 +23,6 @@ #include "properties.h" #include "proxy.h" #include "session.h" +#include "session-item.h" #include "transition.h" #include "wpenums.h"