Newer
Older
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
/**
* SECTION: WpSessionItem
* @title: Session Items
#define G_LOG_DOMAIN "wp-si"
#include "core.h"
#include "debug.h"
#include "error.h"
#include "private/impl-endpoint.h"
#include <spa/utils/defs.h>

George Kiagiadakis
committed
struct _WpSiTransition
{
WpTransition parent;
guint (*get_next_step) (WpSessionItem * self, WpTransition * transition,
guint step);
void (*execute_step) (WpSessionItem * self, WpTransition * transition,
guint step);
void (*rollback) (WpSessionItem * self);

George Kiagiadakis
committed
};
G_DECLARE_FINAL_TYPE (WpSiTransition, wp_si_transition,
WP, SI_TRANSITION, WpTransition);
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)
{
WpSiTransition *self = WP_SI_TRANSITION (transition);
WpSessionItem *item = wp_transition_get_source_object (transition);
g_return_val_if_fail (self->get_next_step, WP_TRANSITION_STEP_ERROR);
step = self->get_next_step (item, transition, step);
g_return_val_if_fail (step == WP_TRANSITION_STEP_NONE || self->execute_step,
WP_TRANSITION_STEP_ERROR);
return step;
}
static void
wp_si_transition_execute_step (WpTransition * transition, guint step)
{
WpSiTransition *self = WP_SI_TRANSITION (transition);
WpSessionItem *item = wp_transition_get_source_object (transition);
if (step != WP_TRANSITION_STEP_ERROR)
self->execute_step (item, transition, step);
else if (self->rollback)
self->rollback (item);

George Kiagiadakis
committed
}
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;
}
typedef struct _WpSessionItemPrivate WpSessionItemPrivate;
struct _WpSessionItemPrivate
{
GWeakRef session;
guint32 flags;
union {
WpProxy *impl_proxy;
WpImplEndpoint *impl_endpoint;
WpImplEndpointLink *impl_link;
};
};
enum {
SIGNAL_FLAGS_CHANGED,
N_SIGNALS
};
guint32 signals[N_SIGNALS] = {0};
/**
* WpSessionItem:
*/
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);
g_weak_ref_init (&priv->parent, 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)
{
wp_session_item_unexport (self);
wp_session_item_deactivate (self);
}
static gpointer
wp_session_item_default_get_associated_proxy (WpSessionItem * self,
GType proxy_type)
{
WpSessionItemPrivate *priv;

George Kiagiadakis
committed
gpointer ret = NULL;
if (WP_IS_SI_STREAM (self)) {

Julian Bouzas
committed
g_autoptr (WpSiEndpoint) ep =
wp_si_stream_get_parent_endpoint (WP_SI_STREAM (self));
priv = wp_session_item_get_instance_private (WP_SESSION_ITEM (ep));
} else {
priv = wp_session_item_get_instance_private (self);
}
if (proxy_type == WP_TYPE_SESSION) {

George Kiagiadakis
committed
ret = g_weak_ref_get (&priv->session);
}
else if (proxy_type == WP_TYPE_ENDPOINT) {
if (priv->impl_proxy && WP_IS_ENDPOINT (priv->impl_proxy))

George Kiagiadakis
committed
ret = g_object_ref (priv->impl_proxy);
}
else if (proxy_type == WP_TYPE_ENDPOINT_LINK) {
if (priv->impl_proxy && WP_IS_ENDPOINT_LINK (priv->impl_proxy))

George Kiagiadakis
committed
ret = g_object_ref (priv->impl_proxy);
}
else if (proxy_type == WP_TYPE_ENDPOINT_STREAM) {
gpointer impl_stream = priv->impl_streams ?
g_hash_table_lookup (priv->impl_streams, self) : NULL;

George Kiagiadakis
committed
ret = impl_stream ? g_object_ref (impl_stream) : NULL;

George Kiagiadakis
committed
wp_trace_object (self, "associated %s: " WP_OBJECT_FORMAT,
g_type_name (proxy_type), WP_OBJECT_ARGS (ret));
return ret;
static guint
wp_session_item_default_activate_get_next_step (WpSessionItem * self,
WpTransition * transition, guint step)
{
/* the default implementation just activates instantly,
without taking any action */
return WP_TRANSITION_STEP_NONE;
}
enum {
EXPORT_STEP_ENDPOINT = WP_TRANSITION_STEP_CUSTOM_START,
EXPORT_STEP_STREAMS,

George Kiagiadakis
committed
EXPORT_STEP_ENDPOINT_FT_STREAMS,
EXPORT_STEP_CONNECT_DESTROYED,
wp_session_item_default_export_get_next_step (WpSessionItem * self,
WpTransition * transition, guint step)
{
WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self);
switch (step) {
case WP_TRANSITION_STEP_NONE:
if (WP_IS_SI_ENDPOINT (self))
else if (WP_IS_SI_LINK (self))
return EXPORT_STEP_LINK;
else {
wp_transition_return_error (transition, g_error_new (
WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
"Cannot export WpSessionItem of unknown type " WP_OBJECT_FORMAT,
WP_OBJECT_ARGS (self)));
return WP_TRANSITION_STEP_ERROR;
}
case EXPORT_STEP_ENDPOINT:
g_return_val_if_fail (WP_IS_SI_ENDPOINT (self), WP_TRANSITION_STEP_ERROR);
return EXPORT_STEP_STREAMS;
case EXPORT_STEP_STREAMS:
g_return_val_if_fail (WP_IS_SI_ENDPOINT (self), WP_TRANSITION_STEP_ERROR);
g_return_val_if_fail (priv->impl_streams, WP_TRANSITION_STEP_ERROR);
/* go to next step only when all impl proxies are activated */
if (g_hash_table_size (priv->impl_streams) ==
wp_si_endpoint_get_n_streams (WP_SI_ENDPOINT (self)))

George Kiagiadakis
committed
return EXPORT_STEP_ENDPOINT_FT_STREAMS;

George Kiagiadakis
committed
case EXPORT_STEP_ENDPOINT_FT_STREAMS:
return WP_TRANSITION_STEP_NONE;
case EXPORT_STEP_LINK:
g_return_val_if_fail (WP_IS_SI_LINK (self), WP_TRANSITION_STEP_ERROR);
return EXPORT_STEP_CONNECT_DESTROYED;
case EXPORT_STEP_CONNECT_DESTROYED:
return WP_TRANSITION_STEP_NONE;
default:
return WP_TRANSITION_STEP_ERROR;
}
}
static void
on_export_proxy_activated (WpObject * proxy, GAsyncResult * res, gpointer data)
WpTransition *transition = WP_TRANSITION (data);
WpSessionItem *self = wp_transition_get_source_object (transition);
WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self);
if (!wp_object_activate_finish (proxy, res, &error)) {
wp_transition_return_error (transition, g_steal_pointer (&error));
return;
}
if (WP_IS_IMPL_ENDPOINT_STREAM (proxy)) {
g_autoptr (WpSiStream) si_stream = NULL;
g_object_get (proxy, "item", &si_stream, NULL);
g_return_if_fail (si_stream != NULL);
g_hash_table_insert (priv->impl_streams, si_stream, g_object_ref (proxy));
}
wp_debug_object (self, "export proxy " WP_OBJECT_FORMAT " activated",

George Kiagiadakis
committed
WP_OBJECT_ARGS (proxy));
static gboolean
on_export_proxy_destroyed_deferred (WpSessionItem * self)
{
WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self);
g_return_val_if_fail (priv->impl_proxy, G_SOURCE_REMOVE);
g_return_val_if_fail (WP_SESSION_ITEM_GET_CLASS (self)->export_rollback,
G_SOURCE_REMOVE);
wp_info_object (self, "destroying " WP_OBJECT_FORMAT
" upon request by the server", WP_OBJECT_ARGS (priv->impl_proxy));
WP_SESSION_ITEM_GET_CLASS (self)->export_rollback (self);
priv->flags |= WP_SI_FLAG_EXPORT_ERROR;
g_signal_emit (self, signals[SIGNAL_FLAGS_CHANGED], 0, priv->flags);
return G_SOURCE_REMOVE;
}
static void
on_export_proxy_destroyed (WpObject * proxy, gpointer data)
{
WpSessionItem *self = WP_SESSION_ITEM (data);
g_autoptr (WpCore) core = wp_object_get_core (proxy);
if (core)
wp_core_idle_add_closure (core, NULL, g_cclosure_new_object (
G_CALLBACK (on_export_proxy_destroyed_deferred), G_OBJECT (self)));
}
static void
wp_session_item_default_export_execute_step (WpSessionItem * self,
WpTransition * transition, guint step)
{
WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self);
g_autoptr (WpSession) session = g_weak_ref_get (&priv->session);
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (session));
switch (step) {
case EXPORT_STEP_ENDPOINT:
priv->impl_endpoint = wp_impl_endpoint_new (core, WP_SI_ENDPOINT (self));
wp_object_activate (WP_OBJECT (priv->impl_endpoint),
WP_PIPEWIRE_OBJECT_FEATURES_ALL, NULL,
(GAsyncReadyCallback) on_export_proxy_activated,
transition);
break;
case EXPORT_STEP_STREAMS: {
guint i, n_streams;
priv->impl_streams = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, g_object_unref);
n_streams = wp_si_endpoint_get_n_streams (WP_SI_ENDPOINT (self));
for (i = 0; i < n_streams; i++) {
WpSiStream *stream = wp_si_endpoint_get_stream (WP_SI_ENDPOINT (self), i);
WpImplEndpointStream *impl_stream =
wp_impl_endpoint_stream_new (core, stream);
wp_object_activate (WP_OBJECT (impl_stream),
WP_OBJECT_FEATURES_ALL, NULL,
(GAsyncReadyCallback) on_export_proxy_activated,
transition);
/* the augment task holds a ref; object will be added to
g_object_unref (impl_stream);
}
break;

George Kiagiadakis
committed
case EXPORT_STEP_ENDPOINT_FT_STREAMS:
/* add feature streams only after the streams are exported, otherwise
the endpoint will never be activated in the first place (because it

George Kiagiadakis
committed
internally waits for the streams to be ready) */
wp_object_activate (WP_OBJECT (priv->impl_endpoint),

George Kiagiadakis
committed
WP_ENDPOINT_FEATURE_STREAMS, NULL,
(GAsyncReadyCallback) on_export_proxy_activated,

George Kiagiadakis
committed
transition);
break;
priv->impl_link = wp_impl_endpoint_link_new (core, WP_SI_LINK (self));
wp_object_activate (WP_OBJECT (priv->impl_link),
WP_OBJECT_FEATURES_ALL, NULL,
(GAsyncReadyCallback) on_export_proxy_activated,
case EXPORT_STEP_CONNECT_DESTROYED:
g_signal_connect_object (priv->impl_proxy, "pw-proxy-destroyed",
G_CALLBACK (on_export_proxy_destroyed), self, 0);
wp_transition_advance (transition);
break;
default:
g_return_if_reached ();
}
}
wp_session_item_default_export_rollback (WpSessionItem * self)
{
WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self);
if (priv->impl_proxy)
g_signal_handlers_disconnect_by_data (priv->impl_proxy, self);
g_clear_pointer (&priv->impl_streams, g_hash_table_unref);
g_clear_object (&priv->impl_proxy);
g_weak_ref_set (&priv->session, NULL);
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;
klass->get_associated_proxy = wp_session_item_default_get_associated_proxy;
klass->activate_get_next_step = wp_session_item_default_activate_get_next_step;
klass->export_get_next_step = wp_session_item_default_export_get_next_step;
klass->export_execute_step = wp_session_item_default_export_execute_step;
klass->export_rollback = wp_session_item_default_export_rollback;
/**
* 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_reset: (virtual reset)
* @self: the session item
*
* Resets the state of the item, deactivating it, unexporting it and
* 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);
}
/**
* wp_session_item_get_parent:
* @self: the session item
*
* Gets the item's parent, which is the #WpSessionBin this item has been added
* to, or NULL if the item does not belong to a session bin.
*
* Returns: (nullable) (transfer full): the item's parent.
*/
WpSessionItem *
wp_session_item_get_parent (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->parent);
}
/**
* wp_session_item_set_parent:
* @self: the session item
* @parent: (transfer none): the parent item
* Private API.
* Sets the item's parent; used internally by #WpSessionBin.
*/
void
wp_session_item_set_parent (WpSessionItem *self, WpSessionItem *parent)
{
g_return_if_fail (WP_IS_SESSION_ITEM (self));
WpSessionItemPrivate *priv =
wp_session_item_get_instance_private (self);
g_weak_ref_set (&priv->parent, parent);
}
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
/**
* 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);
}
}
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
/**
* wp_session_item_get_associated_proxy: (virtual get_associated_proxy)
* @self: the session item
* @proxy_type: a #WpProxy subclass #GType
*
* An associated proxy is a #WpProxy subclass instance that is somehow related
* to this item. For example:
* - An exported #WpSiEndpoint should have at least:
* - an associated #WpEndpoint
* - an associated #WpSession
* - An exported #WpSiStream should have at least:
* - an associated #WpEndpointStream
* - an associated #WpEndpoint
* - In cases where the item wraps a single PipeWire node, it should also
* have an associated #WpNode
*
* Returns: (nullable) (transfer full) (type WpProxy): the associated proxy
* of the specified @proxy_type, or %NULL if there is no association to
* such a proxy
*/
gpointer
wp_session_item_get_associated_proxy (WpSessionItem * self, GType proxy_type)
{
g_return_val_if_fail (WP_IS_SESSION_ITEM (self), NULL);
g_return_val_if_fail (WP_SESSION_ITEM_GET_CLASS (self)->get_associated_proxy,
NULL);
g_return_val_if_fail (g_type_is_a (proxy_type, WP_TYPE_PROXY), NULL);
return WP_SESSION_ITEM_GET_CLASS (self)->get_associated_proxy (self, proxy_type);
}
/**
* wp_session_item_get_associated_proxy_id:
* @self: the session item
* @proxy_type: a #WpProxy subclass #GType
*
* Returns: the bound id of the associated proxy of the specified @proxy_type,
* or `SPA_ID_INVALID` if there is no association to such a proxy
*/
guint32
wp_session_item_get_associated_proxy_id (WpSessionItem * self, GType proxy_type)
{
g_autoptr (WpProxy) proxy = wp_session_item_get_associated_proxy (self,
proxy_type);
if (!proxy)
return SPA_ID_INVALID;
return wp_proxy_get_bound_id (proxy);
}
/**
* 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_autoptr (GVariant) args_ref = g_variant_ref_sink (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_VARDICT),
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 floating): 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);
}
/* clear the in progress flag before calling the callback, so that
it's possible to call wp_session_item_export from within the callback */
on_activate_transition_pre_completed (gpointer data, GClosure *closure)
WpTransition *transition = WP_TRANSITION (data);
WpSessionItem *self = wp_transition_get_source_object (transition);
WpSessionItemPrivate *priv =
wp_session_item_get_instance_private (self);
priv->flags &= ~WP_SI_FLAG_ACTIVATING;
}
static void
on_activate_transition_post_completed (gpointer data, GClosure *closure)
{
WpTransition *transition = WP_TRANSITION (data);
WpSessionItem *self = wp_transition_get_source_object (transition);
WpSessionItemPrivate *priv =
wp_session_item_get_instance_private (self);
priv->flags |= wp_transition_had_error (transition) ?
WP_SI_FLAG_ACTIVATE_ERROR : WP_SI_FLAG_ACTIVE;
g_signal_emit (self, signals[SIGNAL_FLAGS_CHANGED], 0, priv->flags);
}
/**
* wp_session_item_activate_closure:
* @closure: (transfer full): the closure to use when activation is completed
* Activates the item asynchronously.
* You can use wp_session_item_activate_finish() in the closure callback to get
* the result of this operation.
* This internally starts a #WpTransition that calls into
* #WpSessionItemClass.activate_get_next_step() and
* #WpSessionItemClass.activate_execute_step() to advance.
* If the transition fails, #WpSessionItemClass.activate_rollback() is called
* to reverse previous actions.
*
* The default implementation of the above virtual functions activates the
* item successfully without doing anything. In order to implement a meaningful
* session item, you should override all 3 of them.
*
* When this method is called, the %WP_SI_FLAG_ACTIVATING flag is set. When
* the operation finishes successfully, that flag is cleared and replaced with
* either %WP_SI_FLAG_ACTIVE or %WP_SI_FLAG_ACTIVATE_ERROR, depending on the
* success outcome of the operation. In order to clear
* %WP_SI_FLAG_ACTIVATE_ERROR, you can either call wp_session_item_deactivate()
* or wp_session_item_activate() to try activating again.
*
* This method cannot be called if another operation (activation or export) is
* in progress (%WP_SI_FLAGS_MASK_OPERATION_IN_PROGRESS) or if the item is
* already activated.
wp_session_item_activate_closure (WpSessionItem * self, GClosure *closure)
{
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_FLAGS_MASK_OPERATION_IN_PROGRESS | WP_SI_FLAG_ACTIVE)));
/* TODO: add a way to cancel the transition if deactivate() is called in the meantime */
WpTransition *transition = wp_transition_new_closure (
wp_si_transition_get_type (), self, NULL, closure);
wp_transition_set_source_tag (transition, wp_session_item_activate);
g_closure_add_marshal_guards (closure,
transition, on_activate_transition_pre_completed,
transition, on_activate_transition_post_completed);

George Kiagiadakis
committed
wp_debug_object (self, "activating item");
priv->flags &= ~WP_SI_FLAG_ACTIVATE_ERROR;
priv->flags |= WP_SI_FLAG_ACTIVATING;
g_signal_emit (self, signals[SIGNAL_FLAGS_CHANGED], 0, priv->flags);

George Kiagiadakis
committed
WP_SI_TRANSITION (transition)->get_next_step =
WP_SESSION_ITEM_GET_CLASS (self)->activate_get_next_step;

George Kiagiadakis
committed
WP_SI_TRANSITION (transition)->execute_step =
WP_SESSION_ITEM_GET_CLASS (self)->activate_execute_step;
WP_SI_TRANSITION (transition)->rollback =
WP_SESSION_ITEM_GET_CLASS (self)->activate_rollback;
wp_transition_advance (transition);
}
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
/**
* 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
*
* @callback and @callback_data version of wp_session_item_activate_closure()
*/
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_FLAGS_MASK_OPERATION_IN_PROGRESS | WP_SI_FLAG_ACTIVE)));
GClosure *closure =
g_cclosure_new (G_CALLBACK (callback), callback_data, NULL);
wp_session_item_activate_closure (self, closure);
}
/**
* wp_session_item_activate_finish:
* @self: the session item
* @res: the async operation result
* @error: (out) (optional): the error of the operation, if any
*
* Returns: %TRUE if the item is now activateed, %FALSE if there was an error
*/
gboolean
wp_session_item_activate_finish (WpSessionItem * self, GAsyncResult * res,
GError ** error)
{
g_return_val_if_fail (WP_IS_SESSION_ITEM (self), FALSE);
g_return_val_if_fail (
g_async_result_is_tagged (res, wp_session_item_activate), FALSE);
return wp_transition_finish (res, error);
}
* wp_session_item_deactivate:
* @self: the session item
*
* De-activates the item and/or cancels any ongoing activation operation.
*
* If the item was not activated, this method does nothing.
wp_session_item_deactivate (WpSessionItem * self)
{
g_return_if_fail (WP_IS_SESSION_ITEM (self));
WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self);
static const guint flags = 0xf; /* all activation flags */
//TODO cancel job if ACTIVATING
if (priv->flags & WP_SI_FLAG_ACTIVE &&
WP_SESSION_ITEM_GET_CLASS (self)->activate_rollback)
WP_SESSION_ITEM_GET_CLASS (self)->activate_rollback (self);
if (priv->flags & flags) {
priv->flags &= ~flags;
g_signal_emit (self, signals[SIGNAL_FLAGS_CHANGED], 0, priv->flags);
}
static void
on_export_transition_pre_completed (gpointer data, GClosure *closure)
{
WpTransition *transition = WP_TRANSITION (data);
WpSessionItem *self = wp_transition_get_source_object (transition);
WpSessionItemPrivate *priv =
wp_session_item_get_instance_private (self);
priv->flags &= ~WP_SI_FLAG_EXPORTING;
}
static void
on_export_transition_post_completed (gpointer data, GClosure *closure)
{
WpTransition *transition = WP_TRANSITION (data);
WpSessionItem *self = wp_transition_get_source_object (transition);
WpSessionItemPrivate *priv =
wp_session_item_get_instance_private (self);
priv->flags |= wp_transition_had_error (transition) ?
WP_SI_FLAG_EXPORT_ERROR : WP_SI_FLAG_EXPORTED;
g_signal_emit (self, signals[SIGNAL_FLAGS_CHANGED], 0, priv->flags);
}
* wp_session_item_export:
* @self: the session item
* @session: the session on which to export this item
* @callback: (scope async): a callback to call when exporting is finished
* @callback_data: (closure): data passed to @callback
*
* Exports this item asynchronously on PipeWire, making it part of the
* specified @session. You can use wp_session_item_export_finish() in the
* @callback to get the result of this operation.
*
* This internally starts a #WpTransition that calls into
* #WpSessionItemClass.export_get_next_step() and
* #WpSessionItemClass.export_execute_step() to advance.
* If the transition fails, #WpSessionItemClass.export_rollback() is called
* to reverse previous actions.
*
* Exporting is internally implemented for endpoints (items that implement
* #WpSiEndpoint) and endpoint links (items that implement #WpSiLink). On other
* items the default implementation will immediately call the @callback,
* reporting error. You can extend this to export custom interfaces by
* overriding the virtual functions mentioned above.
*
* When this method is called, the %WP_SI_FLAG_EXPORTING flag is set. When
* the operation finishes successfully, that flag is cleared and replaced with
* either %WP_SI_FLAG_EXPORTED or %WP_SI_FLAG_EXPORT_ERROR, depending on the
* success outcome of the operation. In order to clear
* %WP_SI_FLAG_EXPORT_ERROR, you can either call wp_session_item_unexport()
* or wp_session_item_export() to try exporting again.
* This method cannot be called if another operation (activation or export) is
* in progress (%WP_SI_FLAGS_MASK_OPERATION_IN_PROGRESS) or if the item is
* already exported.
*/
void
wp_session_item_export (WpSessionItem * self, WpSession * session,
GAsyncReadyCallback callback, gpointer callback_data)
{
g_return_if_fail (WP_IS_SESSION_ITEM (self));
g_return_if_fail (WP_IS_SESSION (session));
WpSessionItemPrivate *priv =
wp_session_item_get_instance_private (self);
g_return_if_fail (!(priv->flags &
(WP_SI_FLAGS_MASK_OPERATION_IN_PROGRESS | WP_SI_FLAG_EXPORTED)));
g_weak_ref_set (&priv->session, session);
GClosure *closure =
g_cclosure_new (G_CALLBACK (callback), callback_data, NULL);
/* TODO: add a way to cancel the transition if unexport() is called in the meantime */
WpTransition *transition = wp_transition_new_closure (
wp_si_transition_get_type (), self, NULL, closure);
wp_transition_set_source_tag (transition, wp_session_item_export);
g_closure_add_marshal_guards (closure,
transition, on_export_transition_pre_completed,
transition, on_export_transition_post_completed);

George Kiagiadakis
committed
wp_debug_object (self, "exporting item on session " WP_OBJECT_FORMAT,
WP_OBJECT_ARGS (session));
priv->flags &= ~WP_SI_FLAG_EXPORT_ERROR;
priv->flags |= WP_SI_FLAG_EXPORTING;
g_signal_emit (self, signals[SIGNAL_FLAGS_CHANGED], 0, priv->flags);
WP_SI_TRANSITION (transition)->get_next_step =
WP_SESSION_ITEM_GET_CLASS (self)->export_get_next_step;
WP_SI_TRANSITION (transition)->execute_step =
WP_SESSION_ITEM_GET_CLASS (self)->export_execute_step;
WP_SI_TRANSITION (transition)->rollback =
WP_SESSION_ITEM_GET_CLASS (self)->export_rollback;
wp_transition_advance (transition);
}
/**
* wp_session_item_export_finish:
* @self: the session item
* @res: the async operation result
* @error: (out) (optional): the error of the operation, if any
*
* Returns: %TRUE if the item is now exported, %FALSE if there was an error
*/
gboolean
wp_session_item_export_finish (WpSessionItem * self, GAsyncResult * res,
GError ** error)
{
g_return_val_if_fail (WP_IS_SESSION_ITEM (self), FALSE);
g_return_val_if_fail (
g_async_result_is_tagged (res, wp_session_item_export), FALSE);
return wp_transition_finish (res, error);
}
/**
* wp_session_item_unexport:
* @self: the session item
*
* Reverses the effects of a previous call to wp_session_item_export().
* This means that after this method is called:
* - The item is no longer exported on PipeWire
* - The item is no longer associated with a session
* - If an export operation was in progress, it is cancelled.
*
* If the item was not exported, this method does nothing.
*/
void
wp_session_item_unexport (WpSessionItem * self)
{
g_return_if_fail (WP_IS_SESSION_ITEM (self));
WpSessionItemPrivate *priv = wp_session_item_get_instance_private (self);
static const guint flags = 0xf0; /* all export flags */
//TODO cancel job if EXPORTING
if (priv->flags & WP_SI_FLAG_EXPORTED &&
WP_SESSION_ITEM_GET_CLASS (self)->export_rollback)
WP_SESSION_ITEM_GET_CLASS (self)->export_rollback (self);
if (priv->flags & flags) {
priv->flags &= ~flags;
g_signal_emit (self, signals[SIGNAL_FLAGS_CHANGED], 0, priv->flags);
}