Refactor everything!

After discussing things at the AGL May 2019 F2F meeting
and reflecting on the initial design of WirePlumber,
it became clear that it needed a fresh start.
This commit is contained in:
George Kiagiadakis
2019-05-17 13:08:45 +03:00
parent 43365715f7
commit 447c968846
48 changed files with 1667 additions and 3769 deletions

View File

@@ -1,266 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "core-interfaces.h"
#include "plugin.h"
#include "session.h"
/* WpPipewireObjects */
G_DEFINE_INTERFACE (WpPipewireObjects, wp_pipewire_objects, G_TYPE_OBJECT)
static void
wp_pipewire_objects_default_init (WpPipewireObjectsInterface * iface)
{
}
struct pw_core * wp_pipewire_objects_get_pw_core (WpPipewireObjects * self)
{
WpPipewireObjectsInterface *iface = WP_PIPEWIRE_OBJECTS_GET_IFACE (self);
g_return_val_if_fail (WP_IS_PIPEWIRE_OBJECTS (self), NULL);
g_return_val_if_fail (iface->get_pw_core, NULL);
return iface->get_pw_core (self);
}
struct pw_remote * wp_pipewire_objects_get_pw_remote (WpPipewireObjects * self)
{
WpPipewireObjectsInterface *iface = WP_PIPEWIRE_OBJECTS_GET_IFACE (self);
g_return_val_if_fail (WP_IS_PIPEWIRE_OBJECTS (self), NULL);
g_return_val_if_fail (iface->get_pw_remote, NULL);
return iface->get_pw_remote (self);
}
/* WpPluginRegistry */
G_DEFINE_INTERFACE (WpPluginRegistry, wp_plugin_registry, WP_TYPE_INTERFACE_IMPL)
static void
wp_plugin_registry_default_init (WpPluginRegistryInterface * iface)
{
}
/**
* wp_plugin_registry_register_static: (skip)
* @plugin_type: the #GType of the #WpPlugin subclass
* @metadata: the metadata
* @metadata_size: the sizeof (@metadata), to allow ABI-compatible future
* expansion of the structure
*
* Registers a plugin in the registry.
* This method is used internally by WP_PLUGIN_REGISTER().
* Avoid using it directly.
*/
void
wp_plugin_registry_register_static (WpPluginRegistry * self,
GType plugin_type,
const WpPluginMetadata * metadata,
gsize metadata_size)
{
WpPluginRegistryInterface *iface = WP_PLUGIN_REGISTRY_GET_IFACE (self);
g_return_if_fail (WP_IS_PLUGIN_REGISTRY (self));
g_return_if_fail (iface->register_plugin != NULL);
g_return_if_fail (g_type_is_a (plugin_type, WP_TYPE_PLUGIN));
g_return_if_fail (metadata->name != NULL);
g_return_if_fail (metadata->description != NULL);
g_return_if_fail (metadata->author != NULL);
g_return_if_fail (metadata->license != NULL);
g_return_if_fail (metadata->version != NULL);
g_return_if_fail (metadata->origin != NULL);
WP_PLUGIN_REGISTRY_GET_IFACE (self)->register_plugin (self, plugin_type,
metadata, metadata_size, TRUE);
}
/**
* wp_plugin_registry_register: (method)
* @plugin_type: the #GType of the #WpPlugin subclass
* @rank: the rank of the plugin
* @name: the name of the plugin
* @description: plugin description
* @author: author <email@domain>, author2 <email@domain>
* @license: a SPDX license ID or "Proprietary"
* @version: the version of the plugin
* @origin: URL or short reference of where this plugin came from
*
* Registers a plugin in the registry.
* This method creates a dynamically allocated #WpPluginMetadata and is meant
* to be used by bindings that have no way of representing #WpPluginMetadata.
* In C/C++, you should use WP_PLUGIN_REGISTER()
*/
void
wp_plugin_registry_register (WpPluginRegistry * self,
GType plugin_type,
guint16 rank,
const gchar *name,
const gchar *description,
const gchar *author,
const gchar *license,
const gchar *version,
const gchar *origin)
{
WpPluginRegistryInterface *iface = WP_PLUGIN_REGISTRY_GET_IFACE (self);
WpPluginMetadata metadata = {0};
g_return_if_fail (WP_IS_PLUGIN_REGISTRY (self));
g_return_if_fail (iface->register_plugin != NULL);
g_return_if_fail (g_type_is_a (plugin_type, WP_TYPE_PLUGIN));
g_return_if_fail (name != NULL);
g_return_if_fail (description != NULL);
g_return_if_fail (author != NULL);
g_return_if_fail (license != NULL);
g_return_if_fail (version != NULL);
g_return_if_fail (origin != NULL);
metadata.rank = rank;
metadata.name = name;
metadata.description = description;
metadata.author = author;
metadata.license = license;
metadata.version = version;
metadata.origin = origin;
WP_PLUGIN_REGISTRY_GET_IFACE (self)->register_plugin (self, plugin_type,
&metadata, sizeof (metadata), FALSE);
}
/* WpProxyRegistry */
G_DEFINE_INTERFACE (WpProxyRegistry, wp_proxy_registry, WP_TYPE_INTERFACE_IMPL)
static void
wp_proxy_registry_default_init (WpProxyRegistryInterface * iface)
{
}
/**
* wp_proxy_registry_get_proxy: (method)
* @self: the registry
* @global_id: the ID of the pw_global that is represented by the proxy
*
* Returns: (transfer full): the #WpProxy that represents the global with
* @global_id
*/
WpProxy *
wp_proxy_registry_get_proxy (WpProxyRegistry * self, guint32 global_id)
{
WpProxyRegistryInterface * iface = WP_PROXY_REGISTRY_GET_IFACE (self);
g_return_val_if_fail (WP_IS_PROXY_REGISTRY (self), NULL);
g_return_val_if_fail (iface->get_proxy != NULL, NULL);
return iface->get_proxy (self, global_id);
}
/**
* wp_proxy_registry_get_pw_registry_proxy: (skip)
* @self: the registry
*
* Returns: the underlying `pw_registry_proxy`
*/
struct pw_registry_proxy *
wp_proxy_registry_get_pw_registry_proxy (WpProxyRegistry * self)
{
WpProxyRegistryInterface * iface = WP_PROXY_REGISTRY_GET_IFACE (self);
g_return_val_if_fail (WP_IS_PROXY_REGISTRY (self), NULL);
g_return_val_if_fail (iface->get_pw_registry_proxy != NULL, NULL);
return iface->get_pw_registry_proxy (self);
}
/* WpSessionRegistry */
G_DEFINE_INTERFACE (WpSessionRegistry, wp_session_registry, G_TYPE_OBJECT)
enum {
SIG_SESSION_REGISTERED,
SIG_SESSION_UNREGISTERED,
N_SESSION_REGISTRY_SIGNALS
};
static guint session_registry_signals[N_SESSION_REGISTRY_SIGNALS];
static void
wp_session_registry_default_init (WpSessionRegistryInterface * iface)
{
session_registry_signals[SIG_SESSION_REGISTERED] = g_signal_new (
"session-registered", G_TYPE_FROM_INTERFACE (iface),
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT,
WP_TYPE_SESSION);
session_registry_signals[SIG_SESSION_UNREGISTERED] = g_signal_new (
"session-unregistered", G_TYPE_FROM_INTERFACE (iface),
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
}
guint32
wp_session_registry_register_session (WpSessionRegistry * self,
WpSession * session,
GError ** error)
{
WpSessionRegistryInterface *iface = WP_SESSION_REGISTRY_GET_IFACE (self);
guint32 id;
g_return_val_if_fail (WP_IS_SESSION_REGISTRY (self), -1);
g_return_val_if_fail (session != NULL, -1);
g_return_val_if_fail (iface->register_session, -1);
id = iface->register_session (self, session, error);
if (id != -1) {
g_signal_emit (self, session_registry_signals[SIG_SESSION_REGISTERED], 0,
id, session);
}
return id;
}
gboolean
wp_session_registry_unregister_session (WpSessionRegistry * self,
guint32 session_id)
{
WpSessionRegistryInterface *iface = WP_SESSION_REGISTRY_GET_IFACE (self);
gboolean ret;
g_return_val_if_fail (WP_IS_SESSION_REGISTRY (self), FALSE);
g_return_val_if_fail (iface->unregister_session, FALSE);
ret = iface->unregister_session (self, session_id);
if (ret) {
g_signal_emit (self, session_registry_signals[SIG_SESSION_UNREGISTERED], 0,
session_id);
}
return ret;
}
GArray *
wp_session_registry_list_sessions (WpSessionRegistry * self,
const gchar * media_class)
{
WpSessionRegistryInterface *iface = WP_SESSION_REGISTRY_GET_IFACE (self);
g_return_val_if_fail (WP_IS_SESSION_REGISTRY (self), NULL);
g_return_val_if_fail (iface->list_sessions, NULL);
return iface->list_sessions (self, media_class);
}
WpSession *
wp_session_registry_get_session (WpSessionRegistry * self,
guint32 session_id)
{
WpSessionRegistryInterface *iface = WP_SESSION_REGISTRY_GET_IFACE (self);
g_return_val_if_fail (WP_IS_SESSION_REGISTRY (self), NULL);
g_return_val_if_fail (iface->get_session, NULL);
return iface->get_session (self, session_id);
}

View File

@@ -1,129 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WP_CORE_INTERFACES_H__
#define __WP_CORE_INTERFACES_H__
#include "interface-impl.h"
G_BEGIN_DECLS
/* WpPipewireObjects */
struct pw_core;
struct pw_remote;
#define WP_TYPE_PIPEWIRE_OBJECTS (wp_pipewire_objects_get_type ())
G_DECLARE_INTERFACE (WpPipewireObjects, wp_pipewire_objects, WP, PIPEWIRE_OBJECTS, GObject)
struct _WpPipewireObjectsInterface
{
GTypeInterface parent;
struct pw_core * (*get_pw_core) (WpPipewireObjects * self);
struct pw_remote * (*get_pw_remote) (WpPipewireObjects * self);
};
struct pw_core * wp_pipewire_objects_get_pw_core (WpPipewireObjects * self);
struct pw_remote * wp_pipewire_objects_get_pw_remote (WpPipewireObjects * self);
/* WpPluginRegistry */
#define WP_TYPE_PLUGIN_REGISTRY (wp_plugin_registry_get_type ())
G_DECLARE_INTERFACE (WpPluginRegistry, wp_plugin_registry, WP, PLUGIN_REGISTRY, WpInterfaceImpl)
typedef struct _WpPluginMetadata WpPluginMetadata;
struct _WpPluginRegistryInterface
{
GTypeInterface parent;
void (*register_plugin) (WpPluginRegistry * self,
GType plugin_type,
const WpPluginMetadata * metadata,
gsize metadata_size,
gboolean static_data);
};
void wp_plugin_registry_register_static (WpPluginRegistry * self,
GType plugin_type,
const WpPluginMetadata * metadata,
gsize metadata_size);
void wp_plugin_registry_register (WpPluginRegistry * self,
GType plugin_type,
guint16 rank,
const gchar *name,
const gchar *description,
const gchar *author,
const gchar *license,
const gchar *version,
const gchar *origin);
/* WpProxyRegistry */
struct pw_registry_proxy;
typedef struct _WpProxy WpProxy;
#define WP_TYPE_PROXY_REGISTRY (wp_proxy_registry_get_type ())
G_DECLARE_INTERFACE (WpProxyRegistry, wp_proxy_registry, WP, PROXY_REGISTRY, WpInterfaceImpl)
struct _WpProxyRegistryInterface
{
GTypeInterface parent;
WpProxy * (*get_proxy) (WpProxyRegistry * self, guint32 global_id);
struct pw_registry_proxy * (*get_pw_registry_proxy) (WpProxyRegistry * self);
};
WpProxy * wp_proxy_registry_get_proxy (WpProxyRegistry * self,
guint32 global_id);
struct pw_registry_proxy * wp_proxy_registry_get_pw_registry_proxy (
WpProxyRegistry * self);
/* WpSessionRegistry */
#define WP_TYPE_SESSION_REGISTRY (wp_session_registry_get_type ())
G_DECLARE_INTERFACE (WpSessionRegistry, wp_session_registry, WP, SESSION_REGISTRY, GObject)
typedef struct _WpSession WpSession;
struct _WpSessionRegistryInterface
{
GTypeInterface parent;
guint32 (*register_session) (WpSessionRegistry * self,
WpSession * session,
GError ** error);
gboolean (*unregister_session) (WpSessionRegistry * self, guint32 session_id);
GArray * (*list_sessions) (WpSessionRegistry * self,
const gchar * media_class);
WpSession * (*get_session) (WpSessionRegistry * self,
guint32 session_id);
};
guint32 wp_session_registry_register_session (WpSessionRegistry * self,
WpSession * session_object,
GError ** error);
gboolean wp_session_registry_unregister_session (WpSessionRegistry * self,
guint32 session_id);
GArray * wp_session_registry_list_sessions (WpSessionRegistry * self,
const gchar * media_class);
WpSession * wp_session_registry_get_session (WpSessionRegistry * self,
guint32 session_id);
G_END_DECLS
#endif

107
lib/wp/core.c Normal file
View File

@@ -0,0 +1,107 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "core.h"
struct _WpCore
{
GObject parent;
GData *global_objects;
};
G_DEFINE_TYPE (WpCore, wp_core, G_TYPE_OBJECT)
static void
wp_core_init (WpCore * self)
{
g_datalist_init (&self->global_objects);
}
static void
wp_core_finalize (GObject * obj)
{
WpCore *self = WP_CORE (obj);
g_datalist_clear (&self->global_objects);
G_OBJECT_CLASS (wp_core_parent_class)->finalize (obj);
}
static void
wp_core_class_init (WpCoreClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->finalize = wp_core_finalize;
}
WpCore *
wp_core_new (void)
{
return g_object_new (WP_TYPE_CORE, NULL);
}
/**
* wp_core_get_global: (method)
* @self: the core
* @key: the key of the global
*
* Returns: (type GObject*) (nullable) (transfer none): the global object
* implementing @type
*/
gpointer
wp_core_get_global (WpCore * self, GQuark key)
{
g_return_val_if_fail (WP_IS_CORE (self), NULL);
return g_datalist_id_get_data (&self->global_objects, key);
}
/**
* wp_core_register_global: (method)
* @self: the core
* @key: the key for this global
* @obj: (transfer full): the global object to attach
* @destroy_obj: the destroy function for @obj
*
* Returns: TRUE one success, FALSE if the global already exists
*/
gboolean
wp_core_register_global (WpCore * self, GQuark key, gpointer obj,
GDestroyNotify destroy_obj)
{
gpointer other = NULL;
g_return_val_if_fail (WP_IS_CORE(self), FALSE);
if ((other = g_datalist_id_get_data (&self->global_objects, key)) != NULL) {
g_warning ("cannot register global '%s': it already exists",
g_quark_to_string (key));
return FALSE;
}
g_datalist_id_set_data_full (&self->global_objects, key, obj, destroy_obj);
return TRUE;
}
/**
* wp_core_remove_global: (method)
* @self: the core
* @key: the key for this global
*
* Detaches and unrefs the specified global from this core
*/
void
wp_core_remove_global (WpCore * self, GQuark key)
{
g_return_if_fail (WP_IS_CORE (self));
g_datalist_id_remove_data (&self->global_objects, key);
}
G_DEFINE_QUARK (WP_GLOBAL_PW_CORE, wp_global_pw_core)
G_DEFINE_QUARK (WP_GLIBAL_PW_REMOTE, wp_global_pw_remote)

43
lib/wp/core.h Normal file
View File

@@ -0,0 +1,43 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WIREPLUMBER_CORE_H__
#define __WIREPLUMBER_CORE_H__
#include <glib-object.h>
G_BEGIN_DECLS
#define WP_TYPE_CORE (wp_core_get_type ())
G_DECLARE_FINAL_TYPE (WpCore, wp_core, WP, CORE, GObject)
WpCore * wp_core_new (void);
gpointer wp_core_get_global (WpCore * self, GQuark key);
gboolean wp_core_register_global (WpCore * self, GQuark key, gpointer obj,
GDestroyNotify destroy_obj);
void wp_core_remove_global (WpCore * self, GQuark key);
/**
* WP_GLOBAL_PW_CORE:
* The key to access the pw_core global object
*/
#define WP_GLOBAL_PW_CORE (wp_global_pw_core_quark ())
GQuark wp_global_pw_core_quark (void);
/**
* WP_GLOBAL_PW_REMOTE:
* The key to access the pw_remote global object
*/
#define WP_GLOBAL_PW_REMOTE (wp_global_pw_remote_quark ())
GQuark wp_global_pw_remote_quark (void);
G_END_DECLS
#endif

439
lib/wp/endpoint.c Normal file
View File

@@ -0,0 +1,439 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "endpoint.h"
#include "error.h"
#include "factory.h"
typedef struct _WpEndpointPrivate WpEndpointPrivate;
struct _WpEndpointPrivate
{
gchar *name;
gchar media_class[40];
guint32 active_profile;
GPtrArray *links;
};
enum {
PROP_0,
PROP_NAME,
PROP_MEDIA_CLASS,
};
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (WpEndpoint, wp_endpoint, G_TYPE_OBJECT)
static void
wp_endpoint_init (WpEndpoint * self)
{
WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self);
priv->links = g_ptr_array_new_with_free_func (g_object_unref);
}
static void
wp_endpoint_dispose (GObject * object)
{
WpEndpointPrivate *priv =
wp_endpoint_get_instance_private (WP_ENDPOINT (object));
gint i;
/* wp_endpoint_link_destroy removes elements from the array,
* so traversing in reverse order is faster and less complicated */
for (i = priv->links->len - 1; i >= 0; i--) {
wp_endpoint_link_destroy (g_ptr_array_index (priv->links, i));
}
G_OBJECT_CLASS (wp_endpoint_parent_class)->dispose (object);
}
static void
wp_endpoint_finalize (GObject * object)
{
WpEndpointPrivate *priv =
wp_endpoint_get_instance_private (WP_ENDPOINT (object));
g_ptr_array_unref (priv->links);
G_OBJECT_CLASS (wp_endpoint_parent_class)->finalize (object);
}
static void
wp_endpoint_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpEndpointPrivate *priv =
wp_endpoint_get_instance_private (WP_ENDPOINT (object));
switch (property_id) {
case PROP_NAME:
priv->name = g_value_dup_string (value);
break;
case PROP_MEDIA_CLASS:
strncpy (priv->media_class, g_value_get_string (value),
sizeof (priv->media_class) - 1);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_endpoint_get_property (GObject * object, guint property_id, GValue * value,
GParamSpec * pspec)
{
WpEndpointPrivate *priv =
wp_endpoint_get_instance_private (WP_ENDPOINT (object));
switch (property_id) {
case PROP_NAME:
g_value_set_string (value, priv->name);
break;
case PROP_MEDIA_CLASS:
g_value_set_string (value, priv->media_class);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static guint32
default_get_streams_or_profiles_count (WpEndpoint * self)
{
return 1;
}
static const gchar *
default_get_stream_or_profile_name (WpEndpoint * self, guint32 id)
{
return (id == 0) ? "default" : NULL;
}
static gboolean
default_activate_profile (WpEndpoint * self, guint32 profile_id,
GError ** error)
{
WpEndpointPrivate *priv = wp_endpoint_get_instance_private (self);
if (profile_id >= wp_endpoint_get_profiles_count (self)) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
"profile id %d does not exist", profile_id);
return FALSE;
}
priv->active_profile = profile_id;
return TRUE;
}
static void
wp_endpoint_class_init (WpEndpointClass * klass)
{
GObjectClass * object_class = (GObjectClass *) klass;
object_class->dispose = wp_endpoint_dispose;
object_class->finalize = wp_endpoint_finalize;
object_class->get_property = wp_endpoint_get_property;
object_class->set_property = wp_endpoint_set_property;
klass->get_streams_count = default_get_streams_or_profiles_count;
klass->get_stream_name = default_get_stream_or_profile_name;
klass->get_profiles_count = default_get_streams_or_profiles_count;
klass->get_profile_name = default_get_stream_or_profile_name;
klass->activate_profile = default_activate_profile;
g_object_class_install_property (object_class, PROP_NAME,
g_param_spec_string ("name", "name", "The name of the endpoint", NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_MEDIA_CLASS,
g_param_spec_string ("media-class", "media-class",
"The media class of the endpoint", NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
}
const gchar *
wp_endpoint_get_name (WpEndpoint * self)
{
WpEndpointPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
priv = wp_endpoint_get_instance_private (self);
return priv->name;
}
const gchar *
wp_endpoint_get_media_class (WpEndpoint * self)
{
WpEndpointPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
priv = wp_endpoint_get_instance_private (self);
return priv->media_class;
}
guint32
wp_endpoint_get_streams_count (WpEndpoint * self)
{
g_return_val_if_fail (WP_IS_ENDPOINT (self), 0);
g_return_val_if_fail (WP_ENDPOINT_GET_CLASS (self)->get_streams_count, 0);
return WP_ENDPOINT_GET_CLASS (self)->get_streams_count (self);
}
const gchar *
wp_endpoint_get_stream_name (WpEndpoint * self, guint32 stream_id)
{
g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
g_return_val_if_fail (WP_ENDPOINT_GET_CLASS (self)->get_stream_name, NULL);
return WP_ENDPOINT_GET_CLASS (self)->get_stream_name (self, stream_id);
}
guint32
wp_endpoint_get_profiles_count (WpEndpoint * self)
{
g_return_val_if_fail (WP_IS_ENDPOINT (self), 0);
g_return_val_if_fail (WP_ENDPOINT_GET_CLASS (self)->get_profiles_count, 0);
return WP_ENDPOINT_GET_CLASS (self)->get_profiles_count (self);
}
const gchar *
wp_endpoint_get_profile_name (WpEndpoint * self, guint32 profile_id)
{
g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
g_return_val_if_fail (WP_ENDPOINT_GET_CLASS (self)->get_profile_name, NULL);
return WP_ENDPOINT_GET_CLASS (self)->get_profile_name (self, profile_id);
}
guint32
wp_endpoint_get_active_profile (WpEndpoint * self)
{
WpEndpointPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT (self), 0);
priv = wp_endpoint_get_instance_private (self);
return priv->active_profile;
}
gboolean
wp_endpoint_activate_profile (WpEndpoint * self, guint32 profile_id,
GError ** error)
{
g_return_val_if_fail (WP_IS_ENDPOINT (self), FALSE);
g_return_val_if_fail (WP_ENDPOINT_GET_CLASS (self)->activate_profile, FALSE);
return WP_ENDPOINT_GET_CLASS (self)->activate_profile (self, profile_id,
error);
}
gboolean
wp_endpoint_is_linked (WpEndpoint * self)
{
WpEndpointPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT (self), FALSE);
priv = wp_endpoint_get_instance_private (self);
return (priv->links->len > 0);
}
GPtrArray *
wp_endpoint_get_links (WpEndpoint * self)
{
WpEndpointPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT (self), NULL);
priv = wp_endpoint_get_instance_private (self);
return priv->links;
}
typedef struct _WpEndpointLinkPrivate WpEndpointLinkPrivate;
struct _WpEndpointLinkPrivate
{
WpEndpoint *src;
guint32 src_stream;
WpEndpoint *sink;
guint32 sink_stream;
};
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (WpEndpointLink, wp_endpoint_link, G_TYPE_OBJECT)
static void
wp_endpoint_link_init (WpEndpointLink * self)
{
}
static void
wp_endpoint_link_class_init (WpEndpointLinkClass * klass)
{
}
void
wp_endpoint_link_set_endpoints (WpEndpointLink * self, WpEndpoint * src,
guint32 src_stream, WpEndpoint * sink, guint32 sink_stream)
{
WpEndpointLinkPrivate *priv;
g_return_if_fail (WP_IS_ENDPOINT_LINK (self));
g_return_if_fail (WP_IS_ENDPOINT (src));
g_return_if_fail (WP_IS_ENDPOINT (sink));
priv = wp_endpoint_link_get_instance_private (self);
priv->src = src;
priv->src_stream = src_stream;
priv->sink = sink;
priv->sink_stream = sink_stream;
}
WpEndpoint *
wp_endpoint_link_get_source_endpoint (WpEndpointLink * self)
{
WpEndpointLinkPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT_LINK (self), NULL);
priv = wp_endpoint_link_get_instance_private (self);
return priv->src;
}
guint32
wp_endpoint_link_get_source_stream (WpEndpointLink * self)
{
WpEndpointLinkPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT_LINK (self), 0);
priv = wp_endpoint_link_get_instance_private (self);
return priv->src_stream;
}
WpEndpoint *
wp_endpoint_link_get_sink_endpoint (WpEndpointLink * self)
{
WpEndpointLinkPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT_LINK (self), NULL);
priv = wp_endpoint_link_get_instance_private (self);
return priv->sink;
}
guint32
wp_endpoint_link_get_sink_stream (WpEndpointLink * self)
{
WpEndpointLinkPrivate *priv;
g_return_val_if_fail (WP_IS_ENDPOINT_LINK (self), 0);
priv = wp_endpoint_link_get_instance_private (self);
return priv->sink_stream;
}
WpEndpointLink * wp_endpoint_link_new (WpCore * core, WpEndpoint * src,
guint32 src_stream, WpEndpoint * sink, guint32 sink_stream, GError ** error)
{
g_autoptr (WpEndpointLink) link = NULL;
g_autoptr (GVariant) src_props = NULL;
g_autoptr (GVariant) sink_props = NULL;
const gchar *src_factory = NULL, *sink_factory = NULL;
WpEndpointPrivate *endpoint_priv;
g_return_val_if_fail (WP_IS_ENDPOINT (src), NULL);
g_return_val_if_fail (WP_IS_ENDPOINT (sink), NULL);
g_return_val_if_fail (WP_ENDPOINT_GET_CLASS (src)->prepare_link, NULL);
g_return_val_if_fail (WP_ENDPOINT_GET_CLASS (sink)->prepare_link, NULL);
/* find the factory */
if (WP_ENDPOINT_GET_CLASS (src)->get_endpoint_link_factory)
src_factory = WP_ENDPOINT_GET_CLASS (src)->get_endpoint_link_factory (src);
if (WP_ENDPOINT_GET_CLASS (sink)->get_endpoint_link_factory)
sink_factory = WP_ENDPOINT_GET_CLASS (sink)->get_endpoint_link_factory (sink);
if (src_factory || sink_factory) {
if (src_factory && sink_factory && strcmp (src_factory, sink_factory) != 0) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
"It is not possible to link endpoints that both specify different "
"custom link factories");
return NULL;
} else if (sink_factory)
src_factory = sink_factory;
} else {
src_factory = "pipewire-simple-endpoint-link";
}
/* create link object */
link = wp_core_make_from_factory (core, src_factory, WP_TYPE_ENDPOINT_LINK,
NULL);
if (!link) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"Failed to create link object from factory %s", src_factory);
return NULL;
}
g_return_val_if_fail (WP_ENDPOINT_LINK_GET_CLASS (link)->create, NULL);
/* prepare the link */
wp_endpoint_link_set_endpoints (link, src, src_stream, sink, sink_stream);
if (!WP_ENDPOINT_GET_CLASS (src)->prepare_link (src, src_stream, link,
&src_props, error))
return NULL;
if (!WP_ENDPOINT_GET_CLASS (src)->prepare_link (sink, sink_stream, link,
&sink_props, error))
return NULL;
/* create the link */
if (!WP_ENDPOINT_LINK_GET_CLASS (link)->create (link, src_props, sink_props,
error))
return NULL;
/* register the link on the endpoints */
endpoint_priv = wp_endpoint_get_instance_private (src);
g_ptr_array_add (endpoint_priv->links, g_object_ref (link));
endpoint_priv = wp_endpoint_get_instance_private (sink);
g_ptr_array_add (endpoint_priv->links, g_object_ref (link));
return link;
}
void
wp_endpoint_link_destroy (WpEndpointLink * self)
{
WpEndpointLinkPrivate *priv;
WpEndpointPrivate *endpoint_priv;
g_return_if_fail (WP_IS_ENDPOINT_LINK (self));
g_return_if_fail (WP_ENDPOINT_LINK_GET_CLASS (self)->destroy);
priv = wp_endpoint_link_get_instance_private (self);
WP_ENDPOINT_LINK_GET_CLASS (self)->destroy (self);
if (WP_ENDPOINT_GET_CLASS (priv->src)->release_link)
WP_ENDPOINT_GET_CLASS (priv->src)->release_link (priv->src, self);
if (WP_ENDPOINT_GET_CLASS (priv->sink)->release_link)
WP_ENDPOINT_GET_CLASS (priv->sink)->release_link (priv->sink, self);
endpoint_priv = wp_endpoint_get_instance_private (priv->src);
g_ptr_array_remove_fast (endpoint_priv->links, self);
endpoint_priv = wp_endpoint_get_instance_private (priv->sink);
g_ptr_array_remove_fast (endpoint_priv->links, self);
}

82
lib/wp/endpoint.h Normal file
View File

@@ -0,0 +1,82 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WIREPLUMBER_ENDPOINT_H__
#define __WIREPLUMBER_ENDPOINT_H__
#include "core.h"
G_BEGIN_DECLS
#define WP_TYPE_ENDPOINT (wp_endpoint_get_type ())
G_DECLARE_DERIVABLE_TYPE (WpEndpoint, wp_endpoint, WP, ENDPOINT, GObject)
#define WP_TYPE_ENDPOINT_LINK (wp_endpoint_link_get_type ())
G_DECLARE_DERIVABLE_TYPE (WpEndpointLink, wp_endpoint_link, WP, ENDPOINT_LINK, GObject)
struct _WpEndpointClass
{
GObjectClass parent_class;
guint32 (*get_streams_count) (WpEndpoint * self);
const gchar * (*get_stream_name) (WpEndpoint * self, guint32 stream_id);
guint32 (*get_profiles_count) (WpEndpoint * self);
const gchar * (*get_profile_name) (WpEndpoint * self, guint32 profile_id);
gboolean (*activate_profile) (WpEndpoint * self, guint32 profile_id,
GError ** error);
gboolean (*prepare_link) (WpEndpoint * self, guint32 stream_id,
WpEndpointLink * link, GVariant ** properties, GError ** error);
void (*release_link) (WpEndpoint * self, WpEndpointLink * link);
const gchar * (*get_endpoint_link_factory) (WpEndpoint * self);
};
const gchar * wp_endpoint_get_name (WpEndpoint * self);
const gchar * wp_endpoint_get_media_class (WpEndpoint * self);
guint32 wp_endpoint_get_streams_count (WpEndpoint * self);
const gchar * wp_endpoint_get_stream_name (WpEndpoint * self,
guint32 stream_id);
guint32 wp_endpoint_get_profiles_count (WpEndpoint * self);
const gchar * wp_endpoint_get_profile_name (WpEndpoint * self,
guint32 profile_id);
guint32 wp_endpoint_get_active_profile (WpEndpoint * self);
gboolean wp_endpoint_activate_profile (WpEndpoint * self, guint32 profile_id,
GError ** error);
gboolean wp_endpoint_is_linked (WpEndpoint * self);
GPtrArray * wp_endpoint_get_links (WpEndpoint * self);
struct _WpEndpointLinkClass
{
GObjectClass parent_class;
gboolean (*create) (WpEndpointLink * self, GVariant * src_data,
GVariant * sink_data, GError ** error);
void (*destroy) (WpEndpointLink * self);
};
void wp_endpoint_link_set_endpoints (WpEndpointLink * self, WpEndpoint * src,
guint32 src_stream, WpEndpoint * sink, guint32 sink_stream);
WpEndpoint * wp_endpoint_link_get_source_endpoint (WpEndpointLink * self);
guint32 wp_endpoint_link_get_source_stream (WpEndpointLink * self);
WpEndpoint * wp_endpoint_link_get_sink_endpoint (WpEndpointLink * self);
guint32 wp_endpoint_link_get_sink_stream (WpEndpointLink * self);
WpEndpointLink * wp_endpoint_link_new (WpCore * core, WpEndpoint * src,
guint32 src_stream, WpEndpoint * sink, guint32 sink_stream,
GError ** error);
void wp_endpoint_link_destroy (WpEndpointLink * self);
G_END_DECLS
#endif

View File

@@ -18,6 +18,8 @@ GQuark wp_domain_library_quark (void);
typedef enum { typedef enum {
WP_LIBRARY_ERROR_INVARIANT, WP_LIBRARY_ERROR_INVARIANT,
WP_LIBRARY_ERROR_INVALID_ARGUMENT,
WP_LIBRARY_ERROR_OPERATION_FAILED,
} WpLibraryErrorEnum; } WpLibraryErrorEnum;
G_END_DECLS G_END_DECLS

48
lib/wp/factory.c Normal file
View File

@@ -0,0 +1,48 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "factory.h"
G_DEFINE_TYPE (WpFactory, wp_factory, G_TYPE_OBJECT)
static void
wp_factory_init (WpFactory * self)
{
}
static void
wp_factory_finalize (GObject * obj)
{
WpFactory * self = WP_FACTORY (obj);
g_free (self->name);
G_OBJECT_CLASS (wp_factory_parent_class)->finalize (obj);
}
static void
wp_factory_class_init (WpFactoryClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->finalize = wp_factory_finalize;
}
WpFactory *
wp_factory_new (const gchar * name, WpFactoryFunc func)
{
WpFactory *f;
g_return_val_if_fail (name != NULL && *name != '\0', NULL);
g_return_val_if_fail (func != NULL, NULL);
f = g_object_new (WP_TYPE_FACTORY, NULL);
f->name = g_strdup (name);
f->name_quark = g_quark_from_string (f->name);
f->create_object = func;
return f;
}

69
lib/wp/factory.h Normal file
View File

@@ -0,0 +1,69 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WIREPLUMBER_FACTORY_H__
#define __WIREPLUMBER_FACTORY_H__
#include "core.h"
G_BEGIN_DECLS
#define WP_TYPE_FACTORY (wp_factory_get_type ())
G_DECLARE_FINAL_TYPE (WpFactory, wp_factory, WP, FACTORY, GObject)
typedef gpointer (*WpFactoryFunc) (WpFactory * self, GType type,
GVariant * properties);
struct _WpFactory
{
GObject parent;
gchar *name;
GQuark name_quark;
WpFactoryFunc create_object;
};
WpFactory * wp_factory_new (const gchar * name, WpFactoryFunc func);
static inline const gchar * wp_factory_get_name (WpFactory * factory)
{
return factory->name;
}
static inline gpointer wp_factory_create_object (WpFactory * factory,
GType type, GVariant * properties)
{
return factory->create_object (factory, type, properties);
}
static inline gboolean
wp_core_register_factory (WpCore * core, WpFactory * factory)
{
return wp_core_register_global (core, factory->name_quark, factory,
g_object_unref);
}
static inline WpFactory *
wp_core_find_factory (WpCore * core, const gchar * name)
{
return wp_core_get_global (core, g_quark_from_string (name));
}
static inline gpointer
wp_core_make_from_factory (WpCore * core, const gchar * name, GType type,
GVariant * properties)
{
WpFactory *f = wp_core_find_factory (core, name);
if (!f) return NULL;
return wp_factory_create_object (f, type, properties);
}
G_END_DECLS
#endif

View File

@@ -1,121 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "interface-impl.h"
#include "object.h"
typedef struct {
WpObject *object;
} WpInterfaceImplPrivate;
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (WpInterfaceImpl, wp_interface_impl, G_TYPE_OBJECT);
static void
wp_interface_impl_init (WpInterfaceImpl * self)
{
}
static void
wp_interface_impl_finalize (GObject * obj)
{
WpInterfaceImpl *self = WP_INTERFACE_IMPL (obj);
WpInterfaceImplPrivate *priv = wp_interface_impl_get_instance_private (self);
g_clear_weak_pointer (&priv->object);
G_OBJECT_CLASS (wp_interface_impl_parent_class)->finalize (obj);
}
static void
wp_interface_impl_class_init (WpInterfaceImplClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->finalize = wp_interface_impl_finalize;
}
/**
* wp_interface_impl_set_object: (skip)
* @self: the interface implementation instance
* @object: (nullable) (transfer none): the implementor
*/
void
wp_interface_impl_set_object (WpInterfaceImpl * self, WpObject * object)
{
WpInterfaceImplPrivate *priv = wp_interface_impl_get_instance_private (self);
g_return_if_fail (WP_IS_INTERFACE_IMPL (self));
g_return_if_fail (WP_IS_OBJECT (object));
g_set_weak_pointer (&priv->object, object);
}
/**
* wp_interface_impl_get_object: (method)
* @self: the interface implementation instance
*
* Returns: (nullable) (transfer none): the object implementing this interface
*/
WpObject *
wp_interface_impl_get_object (WpInterfaceImpl * self)
{
WpInterfaceImplPrivate *priv = wp_interface_impl_get_instance_private (self);
g_return_val_if_fail (WP_IS_INTERFACE_IMPL (self), NULL);
return priv->object;
}
/**
* wp_interface_impl_get_sibling: (method)
* @self: the interface implementation instance
* @interface: an interface type
*
* Returns: (type GObject*) (nullable) (transfer none): the object
* implementing @interface
*/
gpointer
wp_interface_impl_get_sibling (WpInterfaceImpl * self, GType interface)
{
WpInterfaceImplPrivate *priv = wp_interface_impl_get_instance_private (self);
gpointer iface = NULL;
g_return_val_if_fail (WP_IS_INTERFACE_IMPL (self), NULL);
g_return_val_if_fail (G_TYPE_IS_INTERFACE (interface), NULL);
if (g_type_is_a (G_TYPE_FROM_INSTANCE (self), interface)) {
iface = self;
} else if (priv->object) {
iface = wp_object_get_interface (priv->object, interface);
}
return iface;
}
/**
* wp_interface_impl_get_prerequisites: (virtual get_prerequisites)
* @self: the interface implementation instance
* @n_prerequisites: (out): the number of elements in the returned array
*
* Returns: (array length=n_prerequisites) (transfer none): the types that are
* required by this interface implementation
*/
GType *
wp_interface_impl_get_prerequisites (WpInterfaceImpl * self,
guint * n_prerequisites)
{
WpInterfaceImplClass * klass = WP_INTERFACE_IMPL_GET_CLASS (self);
g_return_val_if_fail (WP_IS_INTERFACE_IMPL (self), NULL);
g_return_val_if_fail (n_prerequisites != NULL, NULL);
if (klass->get_prerequisites)
return klass->get_prerequisites (self, n_prerequisites);
*n_prerequisites = 0;
return NULL;
}

View File

@@ -1,45 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WP_INTERFACE_IMPL_H__
#define __WP_INTERFACE_IMPL_H__
#include <glib-object.h>
G_BEGIN_DECLS
typedef struct _WpObject WpObject;
#define WP_TYPE_INTERFACE_IMPL (wp_interface_impl_get_type ())
G_DECLARE_DERIVABLE_TYPE (WpInterfaceImpl, wp_interface_impl, WP, INTERFACE_IMPL, GObject)
struct _WpInterfaceImplClass
{
GObjectClass parent_class;
/**
* get_prerequisites:
* @self: the interface implementation instance
* @n_prerequisites: (out): the number of elements in the returned array
*
* Returns: (array length=n_prerequisites) (transfer none): the types that are
* required by this interface implementation
*/
GType *(*get_prerequisites) (WpInterfaceImpl * self, guint * n_prerequisites);
};
void wp_interface_impl_set_object (WpInterfaceImpl * self, WpObject * object);
WpObject * wp_interface_impl_get_object (WpInterfaceImpl * self);
gpointer wp_interface_impl_get_sibling (WpInterfaceImpl * self,
GType interface);
GType * wp_interface_impl_get_prerequisites (WpInterfaceImpl * self,
guint * n_prerequisites);
G_END_DECLS
#endif

View File

@@ -1,21 +1,19 @@
wp_lib_sources = [ wp_lib_sources = [
'core-interfaces.c', 'core.c',
'endpoint.c',
'error.c', 'error.c',
'interface-impl.c', 'factory.c',
'object.c', 'module.c',
'plugin.c', 'session-manager.c',
'proxy.c',
'session.c',
] ]
wp_lib_headers = [ wp_lib_headers = [
'core-interfaces.h', 'core.h',
'endpoint.h',
'error.h', 'error.h',
'interface-impl.h', 'factory.h',
'object.h', 'module.h',
'plugin.h', 'session-manager.h',
'proxy.h',
'session.h',
] ]
enums = gnome.mkenums_simple('wpenums', sources: wp_lib_headers) enums = gnome.mkenums_simple('wpenums', sources: wp_lib_headers)
@@ -28,14 +26,15 @@ wp_lib = library('wireplumber-' + wireplumber_api_version,
'-DG_LOG_DOMAIN="libwireplumber"' '-DG_LOG_DOMAIN="libwireplumber"'
], ],
install: true, install: true,
dependencies : [gobject_dep, pipewire_dep, gio_dep], include_directories: wp_lib_include_dir,
dependencies : [gobject_dep, gmodule_dep],
) )
gnome.generate_gir(wp_lib, gnome.generate_gir(wp_lib,
namespace: 'Wp', namespace: 'Wp',
nsversion: wireplumber_api_version, nsversion: wireplumber_api_version,
sources: [wp_lib_sources, wp_lib_headers], sources: [wp_lib_sources, wp_lib_headers],
includes: ['GLib-2.0', 'GObject-2.0', 'Gio-2.0'], includes: ['GLib-2.0', 'GObject-2.0'],
install: true, install: true,
) )

137
lib/wp/module.c Normal file
View File

@@ -0,0 +1,137 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "module.h"
#include "error.h"
#include <gmodule.h>
#define WP_MODULE_INIT_SYMBOL "wireplumber__module_init"
typedef void (*WpModuleInitFunc) (WpModule *, WpCore *, GVariant *);
struct _WpModule
{
GObject parent;
GVariant *properties;
GDestroyNotify destroy;
gpointer destroy_data;
};
G_DEFINE_TYPE (WpModule, wp_module, G_TYPE_OBJECT)
static void
wp_module_init (WpModule * self)
{
}
static void
wp_module_finalize (GObject * object)
{
WpModule *self = WP_MODULE (object);
if (self->destroy)
self->destroy (self->destroy_data);
g_clear_pointer (&self->properties, g_variant_unref);
G_OBJECT_CLASS (wp_module_parent_class)->finalize (object);
}
static void
wp_module_class_init (WpModuleClass * klass)
{
GObjectClass * object_class = (GObjectClass *) klass;
object_class->finalize = wp_module_finalize;
}
static const gchar *
get_module_dir (void)
{
const gchar *module_dir = NULL;
if (!module_dir)
module_dir = g_getenv ("WIREPLUMBER_MODULE_DIR");
return module_dir;
}
static gboolean
wp_module_load_c (WpModule * self, WpCore * core,
const gchar * module_name, GVariant * args, GError ** error)
{
g_autofree gchar *module_path = NULL;
GModule *gmodule;
gpointer module_init;
GVariantDict properties;
module_path = g_module_build_path (get_module_dir (), module_name);
gmodule = g_module_open (module_path, G_MODULE_BIND_LOCAL);
if (!gmodule) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"Failed to open module %s: %s", module_path, g_module_error ());
return FALSE;
}
if (!g_module_symbol (gmodule, WP_MODULE_INIT_SYMBOL, &module_init)) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"Failed to locate symbol " WP_MODULE_INIT_SYMBOL " in %s",
module_path);
g_module_close (gmodule);
return FALSE;
}
g_variant_dict_init (&properties, NULL);
g_variant_dict_insert (&properties, "module.name", "s", module_name);
g_variant_dict_insert (&properties, "module.abi", "s", "C");
g_variant_dict_insert (&properties, "module.path", "s", module_path);
if (args) {
g_variant_take_ref (args);
g_variant_dict_insert_value (&properties, "module.args", args);
}
self->properties = g_variant_ref_sink (g_variant_dict_end (&properties));
((WpModuleInitFunc) module_init) (self, core, args);
if (args)
g_variant_unref (args);
return TRUE;
}
WpModule *
wp_module_load (WpCore * core, const gchar * abi,
const gchar * module_name, GVariant * args, GError ** error)
{
g_autoptr (WpModule) module = NULL;
if (!g_strcmp0 (abi, "C")) {
module = g_object_new (WP_TYPE_MODULE, NULL);
if (!wp_module_load_c (module, core, module_name, args, error))
return NULL;
} else {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
"unknown module ABI %s", abi);
return NULL;
}
wp_core_register_global (core, g_quark_from_string (module_name),
g_object_ref (module), g_object_unref);
return module;
}
GVariant *
wp_module_get_properties (WpModule * self)
{
return self->properties;
}
void
wp_module_set_destroy_callback (WpModule * self, GDestroyNotify callback,
gpointer data)
{
g_return_if_fail (self->destroy == NULL);
self->destroy = callback;
self->destroy_data = data;
}

29
lib/wp/module.h Normal file
View File

@@ -0,0 +1,29 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WIREPLUMBER_MODULE_H__
#define __WIREPLUMBER_MODULE_H__
#include "core.h"
G_BEGIN_DECLS
#define WP_TYPE_MODULE (wp_module_get_type ())
G_DECLARE_FINAL_TYPE (WpModule, wp_module, WP, MODULE, GObject)
WpModule * wp_module_load (WpCore * core, const gchar * abi,
const gchar * module_name, GVariant * args, GError ** error);
GVariant * wp_module_get_properties (WpModule * module);
void wp_module_set_destroy_callback (WpModule * module, GDestroyNotify callback,
gpointer data);
G_END_DECLS
#endif

View File

@@ -1,225 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "object.h"
#include "interface-impl.h"
#include "error.h"
typedef struct {
GArray *iface_objects;
GArray *iface_types;
} WpObjectPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (WpObject, wp_object, G_TYPE_OBJECT);
static void
wp_object_init (WpObject * self)
{
WpObjectPrivate *priv = wp_object_get_instance_private (self);
priv->iface_objects = g_array_new (FALSE, FALSE, sizeof (gpointer));
priv->iface_types = g_array_new (FALSE, FALSE, sizeof (GType));
}
static void
wp_object_finalize (GObject * obj)
{
WpObject *self = WP_OBJECT (obj);
WpObjectPrivate *priv = wp_object_get_instance_private (self);
guint i;
for (i = 0; i < priv->iface_objects->len; i++) {
GObject *obj = g_array_index (priv->iface_objects, GObject*, i);
wp_interface_impl_set_object (WP_INTERFACE_IMPL (obj), NULL);
g_object_unref (obj);
}
g_array_free (priv->iface_objects, TRUE);
g_array_free (priv->iface_types, TRUE);
G_OBJECT_CLASS (wp_object_parent_class)->finalize (obj);
}
static void
wp_object_class_init (WpObjectClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->finalize = wp_object_finalize;
}
/**
* wp_object_implements_interface: (method)
* @self: the object
* @interface: an interface type
*
* Returns: whether the interface is implemented in this object or not
*/
gboolean
wp_object_implements_interface (WpObject * self, GType interface)
{
WpObjectPrivate *priv = wp_object_get_instance_private (self);
guint i;
g_return_val_if_fail (WP_IS_OBJECT (self), FALSE);
for (i = 0; i < priv->iface_types->len; i++) {
GType t = g_array_index (priv->iface_types, GType, i);
if (t == interface)
return TRUE;
}
return FALSE;
}
/**
* wp_object_get_interface: (method)
* @self: the object
* @interface: an interface type
*
* Returns: (type GObject*) (nullable) (transfer none): the object
* implementing @interface
*/
gpointer
wp_object_get_interface (WpObject * self, GType interface)
{
WpObjectPrivate *priv = wp_object_get_instance_private (self);
guint i;
g_return_val_if_fail (WP_IS_OBJECT (self), FALSE);
for (i = 0; i < priv->iface_objects->len; i++) {
GObject *obj = g_array_index (priv->iface_objects, GObject*, i);
if (g_type_is_a (G_TYPE_FROM_INSTANCE (obj), interface))
return obj;
}
return NULL;
}
/**
* wp_object_list_interfaces: (method)
* @self: the object
* @n_interfaces: (out): the number of elements in the returned array
*
* Returns: (array length=n_interfaces) (transfer none): the interface types
* that are implemented in this object
*/
GType *
wp_object_list_interfaces (WpObject * self, guint * n_interfaces)
{
WpObjectPrivate *priv = wp_object_get_instance_private (self);
g_return_val_if_fail (WP_IS_OBJECT (self), NULL);
*n_interfaces = priv->iface_types->len;
return (GType *) priv->iface_types->data;
}
/**
* wp_object_attach_interface_impl: (method)
* @self: the object
* @impl: (type WpInterfaceImpl*) (transfer none): the interface implementation
* @error: (out): a GError to return on failure
*
* Returns: TRUE one success, FALSE on error
*/
gboolean
wp_object_attach_interface_impl (WpObject * self, gpointer impl,
GError ** error)
{
WpObjectPrivate *priv = wp_object_get_instance_private (self);
GType *new_ifaces;
GType *prerequisites;
guint n_new_ifaces;
guint n_prerequisites, n_satisfied = 0;
guint i, j;
g_return_val_if_fail (WP_IS_OBJECT (self), FALSE);
g_return_val_if_fail (WP_IS_INTERFACE_IMPL (impl), FALSE);
new_ifaces = g_type_interfaces (G_TYPE_FROM_INSTANCE (impl),
&n_new_ifaces);
prerequisites = wp_interface_impl_get_prerequisites (impl, &n_prerequisites);
for (i = 0; i < priv->iface_types->len; i++) {
GType t = g_array_index (priv->iface_types, GType, i);
for (j = 0; j < n_prerequisites; j++) {
if (prerequisites[j] == t)
n_satisfied++;
}
for (j = 0; j < n_new_ifaces; j++) {
if (new_ifaces[j] == t) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
"Interface %s is already provided on object %p",
g_type_name (t), self);
return FALSE;
}
}
}
if (n_satisfied != n_prerequisites) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
"Interface implementation %p has unsatisfied requirements", impl);
return FALSE;
}
g_object_ref (impl);
g_array_append_val (priv->iface_objects, impl);
g_array_append_vals (priv->iface_types, new_ifaces, n_new_ifaces);
wp_interface_impl_set_object (WP_INTERFACE_IMPL (impl), self);
return TRUE;
}
/* WpPipewireProperties */
G_DEFINE_INTERFACE (WpPipewireProperties, wp_pipewire_properties, G_TYPE_OBJECT)
static void
wp_pipewire_properties_default_init (WpPipewirePropertiesInterface * iface)
{
}
/**
* wp_pipewire_properties_get: (virtual get)
* @self: the interface
* @key: the name of the property to lookup
*
* Return: (transfer none): The value of the underlying PipeWire object's
* property with this @key, or %NULL.
*/
const gchar *
wp_pipewire_properties_get (WpPipewireProperties * self, const gchar * key)
{
WpPipewirePropertiesInterface *iface = WP_PIPEWIRE_PROPERTIES_GET_IFACE (self);
g_return_val_if_fail (WP_IS_PIPEWIRE_PROPERTIES (self), NULL);
g_return_val_if_fail (key != NULL, NULL);
g_return_val_if_fail (iface->get, NULL);
return iface->get (self, key);
}
/**
* wp_pipewire_properties_get_as_spa_dict: (virtual get_as_spa_dict)
* @self: the interface
*
* Return: (transfer none): The underlying `struct spa_dict` that holds
* the properties
*/
const struct spa_dict *
wp_pipewire_properties_get_as_spa_dict (WpPipewireProperties * self)
{
WpPipewirePropertiesInterface *iface = WP_PIPEWIRE_PROPERTIES_GET_IFACE (self);
g_return_val_if_fail (WP_IS_PIPEWIRE_PROPERTIES (self), NULL);
g_return_val_if_fail (iface->get_as_spa_dict, NULL);
return iface->get_as_spa_dict (self);
}

View File

@@ -1,54 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WP_OBJECT_H__
#define __WP_OBJECT_H__
#include <glib-object.h>
G_BEGIN_DECLS
#define WP_TYPE_OBJECT (wp_object_get_type ())
G_DECLARE_DERIVABLE_TYPE (WpObject, wp_object, WP, OBJECT, GObject)
struct _WpObjectClass
{
GObjectClass parent_class;
};
gboolean wp_object_implements_interface (WpObject * self, GType interface);
gpointer wp_object_get_interface (WpObject * self, GType interface);
GType * wp_object_list_interfaces (WpObject * self, guint * n_interfaces);
gboolean wp_object_attach_interface_impl (WpObject * self, gpointer impl,
GError ** error);
/* WpPipewireProperties */
#define WP_TYPE_PIPEWIRE_PROPERTIES (wp_pipewire_properties_get_type ())
G_DECLARE_INTERFACE (WpPipewireProperties, wp_pipewire_properties,
WP, PIPEWIRE_PROPERTIES, GObject)
struct spa_dict;
struct _WpPipewirePropertiesInterface
{
GTypeInterface parent;
const gchar * (*get) (WpPipewireProperties * self, const gchar * key);
const struct spa_dict * (*get_as_spa_dict) (WpPipewireProperties * self);
};
const gchar * wp_pipewire_properties_get (WpPipewireProperties * self,
const gchar * key);
const struct spa_dict * wp_pipewire_properties_get_as_spa_dict (
WpPipewireProperties * self);
G_END_DECLS
#endif

View File

@@ -1,277 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "plugin.h"
#include <pipewire/pipewire.h>
enum {
PROP_0,
PROP_RANK,
PROP_NAME,
PROP_DESCRIPTION,
PROP_AUTHOR,
PROP_LICENSE,
PROP_VERSION,
PROP_ORIGIN,
PROP_CORE,
PROP_METADATA,
};
typedef struct {
WpObject *core;
const WpPluginMetadata *metadata;
} WpPluginPrivate;
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (WpPlugin, wp_plugin, G_TYPE_OBJECT);
static void
wp_plugin_init (WpPlugin * self)
{
}
static void
wp_plugin_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpPlugin *plugin = WP_PLUGIN (object);
WpPluginPrivate *priv = wp_plugin_get_instance_private (plugin);
switch (property_id) {
case PROP_CORE:
priv->core = g_value_get_object (value);
break;
case PROP_METADATA:
priv->metadata = g_value_get_pointer (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_plugin_get_property (GObject * object, guint property_id, GValue * value,
GParamSpec * pspec)
{
WpPlugin *plugin = WP_PLUGIN (object);
WpPluginPrivate *priv = wp_plugin_get_instance_private (plugin);
switch (property_id) {
case PROP_RANK:
g_value_set_uint (value, priv->metadata->rank);
break;
case PROP_NAME:
g_value_set_string (value, priv->metadata->name);
break;
case PROP_DESCRIPTION:
g_value_set_string (value, priv->metadata->description);
break;
case PROP_AUTHOR:
g_value_set_string (value, priv->metadata->author);
break;
case PROP_LICENSE:
g_value_set_string (value, priv->metadata->license);
break;
case PROP_VERSION:
g_value_set_string (value, priv->metadata->version);
break;
case PROP_ORIGIN:
g_value_set_string (value, priv->metadata->origin);
break;
case PROP_CORE:
g_value_set_object (value, priv->core);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static gboolean
default_handle_pw_proxy (WpPlugin * self, WpProxy * proxy)
{
WpPluginPrivate *priv = wp_plugin_get_instance_private (self);
switch (wp_proxy_get_spa_type (proxy)) {
case PW_TYPE_INTERFACE_Device:
return wp_plugin_handle_pw_device (self, proxy);
case PW_TYPE_INTERFACE_Client:
return wp_plugin_handle_pw_client (self, proxy);
case PW_TYPE_INTERFACE_Node:
{
WpProxyRegistry *reg;
g_autoptr (WpProxy) parent;
reg = wp_object_get_interface (priv->core, WP_TYPE_PROXY_REGISTRY);
parent = wp_proxy_registry_get_proxy (reg, wp_proxy_get_parent_id (proxy));
switch (wp_proxy_get_spa_type (parent)) {
case PW_TYPE_INTERFACE_Device:
return wp_plugin_handle_pw_device_node (self, proxy);
case PW_TYPE_INTERFACE_Client:
return wp_plugin_handle_pw_client_node (self, proxy);
}
}
default:
return FALSE;
}
}
static void
wp_plugin_class_init (WpPluginClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
klass->handle_pw_proxy = default_handle_pw_proxy;
object_class->get_property = wp_plugin_get_property;
object_class->set_property = wp_plugin_set_property;
g_object_class_install_property (object_class, PROP_RANK,
g_param_spec_uint ("rank", "Rank", "The plugin rank", 0, G_MAXUINT, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_NAME,
g_param_spec_string ("name", "Name", "The plugin's name", NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_DESCRIPTION,
g_param_spec_string ("description", "Description",
"The plugin's description", NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_AUTHOR,
g_param_spec_string ("author", "Author", "The plugin's author", NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_LICENSE,
g_param_spec_string ("license", "License", "The plugin's license", NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_VERSION,
g_param_spec_string ("version", "Version", "The plugin's version", NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_ORIGIN,
g_param_spec_string ("origin", "Origin", "The plugin's origin", NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_CORE,
g_param_spec_object ("core", "Core", "The WpCore that owns this plugin",
WP_TYPE_OBJECT,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_METADATA,
g_param_spec_pointer ("metadata", "metadata", "metadata",
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS |
G_PARAM_PRIVATE));
}
/**
* wp_plugin_handle_pw_proxy: (virtual handle_pw_proxy)
*/
gboolean
wp_plugin_handle_pw_proxy (WpPlugin * self, WpProxy * proxy)
{
if (WP_PLUGIN_GET_CLASS (self)->handle_pw_proxy)
return WP_PLUGIN_GET_CLASS (self)->handle_pw_proxy (self, proxy);
else
return FALSE;
}
/**
* wp_plugin_handle_pw_device: (virtual handle_pw_device)
*/
gboolean
wp_plugin_handle_pw_device (WpPlugin * self, WpProxy * proxy)
{
if (WP_PLUGIN_GET_CLASS (self)->handle_pw_device)
return WP_PLUGIN_GET_CLASS (self)->handle_pw_device (self, proxy);
else
return FALSE;
}
/**
* wp_plugin_handle_pw_device_node: (virtual handle_pw_device_node)
*/
gboolean
wp_plugin_handle_pw_device_node (WpPlugin * self, WpProxy * proxy)
{
if (WP_PLUGIN_GET_CLASS (self)->handle_pw_device_node)
return WP_PLUGIN_GET_CLASS (self)->handle_pw_device_node (self, proxy);
else
return FALSE;
}
/**
* wp_plugin_handle_pw_client: (virtual handle_pw_client)
*/
gboolean
wp_plugin_handle_pw_client (WpPlugin * self, WpProxy * proxy)
{
if (WP_PLUGIN_GET_CLASS (self)->handle_pw_client)
return WP_PLUGIN_GET_CLASS (self)->handle_pw_client (self, proxy);
else
return FALSE;
}
/**
* wp_plugin_handle_pw_client_node: (virtual handle_pw_client_node)
*/
gboolean
wp_plugin_handle_pw_client_node (WpPlugin * self, WpProxy * proxy)
{
if (WP_PLUGIN_GET_CLASS (self)->handle_pw_client_node)
return WP_PLUGIN_GET_CLASS (self)->handle_pw_client_node (self, proxy);
else
return FALSE;
}
/**
* wp_plugin_provide_interfaces: (virtual provide_interfaces)
*/
gboolean
wp_plugin_provide_interfaces (WpPlugin * self, WpObject * object)
{
if (WP_PLUGIN_GET_CLASS (self)->provide_interfaces)
return WP_PLUGIN_GET_CLASS (self)->provide_interfaces (self, object);
else
return FALSE;
}
/**
* wp_plugin_get_core: (method)
* @self: the plugin
*
* Returns: (transfer full): the core where this plugin is registered
*/
WpObject *
wp_plugin_get_core (WpPlugin * self)
{
WpPluginPrivate *priv = wp_plugin_get_instance_private (self);
return g_object_ref (priv->core);
}
/**
* wp_plugin_get_metadata: (skip)
* @self: the plugin
*
* This is intended for C/C++ only. Use the #WpPlugin properties in bindings.
*
* Returns: the metadata structure associated with this plugin
*/
const WpPluginMetadata *
wp_plugin_get_metadata (WpPlugin * self)
{
WpPluginPrivate *priv = wp_plugin_get_instance_private (self);
return priv->metadata;
}

View File

@@ -1,190 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WP_PLUGIN_H__
#define __WP_PLUGIN_H__
#include "object.h"
#include "proxy.h"
#include "core-interfaces.h"
G_BEGIN_DECLS
/**
* WpPluginRank:
* @WP_PLUGIN_RANK_UPSTREAM: should only be used inside WirePlumber
* @WP_PLUGIN_RANK_PLATFORM_OVERRIDE: plugins provided by the platform,
* possibly to provide a platform-specific policy
* @WP_PLUGIN_RANK_VENDOR_OVERRIDE: plugins provided by hardware vendors
* to provide hardware-specific device handling and/or policies
*
* The rank of a plugin is an unsigned integer that can take an arbitrary
* value. On invocation, plugins ranked with a higher number are tried first,
* which is how one can implement overrides. This enum provides default
* values for certain kinds of plugins. Feel free to add/substract numbers
* to these constants in order to make a hierarchy, if you are implementing
* multiple different plugins that need to be tried in a certain order.
*/
typedef enum {
WP_PLUGIN_RANK_UPSTREAM = 0,
WP_PLUGIN_RANK_PLATFORM_OVERRIDE = 128,
WP_PLUGIN_RANK_VENDOR_OVERRIDE = 256,
} WpPluginRank;
/**
* WpPluginMetadata: (skip)
* @rank: the rank of the plugin
* @name: the name of the plugin
* @description: plugin description
* @author: author <email@domain>, author2 <email@domain>
* @license: a SPDX license ID or "Proprietary"
* @version: the version of the plugin
* @origin: URL or short reference of where this plugin came from
*
* Metadata for registering a plugin (for the C API).
* You should normally never need to use this directly.
* Use WP_PLUGIN_DEFINE() instead.
*/
struct _WpPluginMetadata
{
union {
guint rank;
gpointer _unused_for_alignment[2];
};
const gchar *name;
const gchar *description;
const gchar *author;
const gchar *license;
const gchar *version;
const gchar *origin;
};
#define WP_TYPE_PLUGIN (wp_plugin_get_type ())
G_DECLARE_DERIVABLE_TYPE (WpPlugin, wp_plugin, WP, PLUGIN, GObject)
struct _WpPluginClass
{
GObjectClass parent_class;
/**
* handle_pw_proxy:
* @self: the plugin
* @proxy: (transfer none): the proxy
*
* This method is called for every new proxy that appears in PipeWire.
* The default implementation will inspect the proxy type and will dispatch
* the call to one of the specialized methods available below.
* Override only for very special cases.
*/
gboolean (*handle_pw_proxy) (WpPlugin * self, WpProxy * proxy);
/**
* handle_pw_device:
* @self: the plugin
* @proxy: (transfer none): the device proxy
*
* This method is called for every new PipeWire proxy of type
* `PipeWire:Interface:Device`. The implementation is expected to create
* a new #WpDevice and register it with the #WpDeviceManager.
*
* The default implementation returns FALSE.
* Override if you are implementing custom device management.
*
* Returns: TRUE if the device was handled, FALSE otherwise.
*/
gboolean (*handle_pw_device) (WpPlugin * self, WpProxy * proxy);
/**
* handle_pw_device_node:
* @self: the plugin
* @proxy: (transfer none): the node proxy
*
* This method is called for every new PipeWire proxy of type
* `PipeWire:Interface:Node` whose parent proxy is a
* `PipeWire:Interface:Device`.
*
* The default implementation returns FALSE.
* Override if you are implementing custom device management.
*
* Returns: TRUE if the node was handled, FALSE otherwise.
*/
gboolean (*handle_pw_device_node) (WpPlugin * self, WpProxy * proxy);
/**
* handle_pw_client:
* @self: the plugin
* @proxy: (transfer none): the client proxy
*
* This method is called for every new PipeWire proxy of type
* `PipeWire:Interface:Client`. The implementation is expected to update
* the client's permissions, if necessary.
*
* The default implementation returns FALSE.
* Override if you are implementing custom policy management.
*
* Returns: TRUE if the client was handled, FALSE otherwise.
*/
gboolean (*handle_pw_client) (WpPlugin * self, WpProxy * proxy);
/**
* handle_pw_client_node:
* @self: the plugin
* @proxy: (transfer none): the node proxy
*
* This method is called for every new PipeWire proxy of type
* `PipeWire:Interface:Node` whose parent proxy is a
* `PipeWire:Interface:Client`. The implementation is expected to create
* a new #WpStream in some #WpSession.
*
* The default implementation returns FALSE.
* Override if you are implementing custom policy management.
*
* Returns: TRUE if the node was handled, FALSE otherwise.
*/
gboolean (*handle_pw_client_node) (WpPlugin * self, WpProxy * proxy);
/**
* provide_interfaces:
* @self: the plugin
* @object: (transfer none): a #WpObject
*
* This method is called for every new #WpObject created in WirePlumber.
* The implementation is expected to attach any interface implementations
* that it can provide for this kind of object, if necessary, only if
* these interfaces have not already been attached on the @object.
*
* The default implementation returns FALSE.
* Override if you are providing custom interface implementations for objects.
*
* Returns: TRUE if the node was handled, FALSE otherwise.
*/
gboolean (*provide_interfaces) (WpPlugin * self, WpObject * object);
};
gboolean wp_plugin_handle_pw_proxy (WpPlugin * self, WpProxy * proxy);
gboolean wp_plugin_handle_pw_device (WpPlugin * self, WpProxy * proxy);
gboolean wp_plugin_handle_pw_device_node (WpPlugin * self, WpProxy * proxy);
gboolean wp_plugin_handle_pw_client (WpPlugin * self, WpProxy * proxy);
gboolean wp_plugin_handle_pw_client_node (WpPlugin * self, WpProxy * proxy);
gboolean wp_plugin_provide_interfaces (WpPlugin * self, WpObject * object);
WpObject * wp_plugin_get_core (WpPlugin * self);
const WpPluginMetadata * wp_plugin_get_metadata (WpPlugin * self);
/**
* WP_MODULE_INIT_SYMBOL: (skip)
*
* The linker symbol that serves as an entry point in modules
*/
#define WP_MODULE_INIT_SYMBOL wireplumber__module_init
G_END_DECLS
#endif

View File

@@ -1,611 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "proxy.h"
#include "error.h"
#include <pipewire/pipewire.h>
#include <spa/debug/types.h>
#include <spa/pod/builder.h>
struct _WpProxy
{
GObject parent;
WpObject *core;
struct pw_proxy *proxy;
guint32 id;
guint32 parent_id;
guint32 type;
const gchar *type_string;
struct spa_hook proxy_listener;
struct spa_hook proxy_proxy_listener;
union {
const struct spa_dict *initial_properties;
struct pw_properties *properties;
};
union {
gpointer info;
struct pw_node_info *node_info;
struct pw_port_info *port_info;
struct pw_factory_info *factory_info;
struct pw_link_info *link_info;
struct pw_client_info *client_info;
struct pw_module_info *module_info;
struct pw_device_info *device_info;
};
GList *tasks;
};
enum {
PROP_0,
PROP_ID,
PROP_PARENT_ID,
PROP_SPA_TYPE,
PROP_SPA_TYPE_STRING,
PROP_INITIAL_PROPERTIES,
PROP_CORE,
PROP_PROXY,
};
enum {
SIGNAL_DESTROYED,
SIGNAL_CHANGED,
N_SIGNALS
};
static guint signals[N_SIGNALS];
static int global_seq = 0;
static void wp_proxy_pw_properties_init (WpPipewirePropertiesInterface * iface);
G_DEFINE_TYPE_WITH_CODE (WpProxy, wp_proxy, WP_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_PROPERTIES, wp_proxy_pw_properties_init);
)
struct task_data {
guint32 seq;
GPtrArray *result;
};
/* Updates the info structure while copying the properties into self->properties
* and avoiding making a second copy of the properties dict in info->props */
#define PROXY_INFO_FUNC(TYPE, type) \
static void \
type##_event_info (void *object, const struct pw_##type##_info *info) \
{ \
WpProxy *self = WP_PROXY (object); \
\
if (info->change_mask & PW_##TYPE##_CHANGE_MASK_PROPS) { \
g_clear_pointer (&self->properties, pw_properties_free); \
self->properties = pw_properties_new_dict (info->props); \
} \
\
{ \
uint64_t change_mask = info->change_mask; \
((struct pw_##type##_info *)info)->change_mask &= ~PW_##TYPE##_CHANGE_MASK_PROPS; \
self->type##_info = pw_##type##_info_update (self->type##_info, info); \
self->type##_info->props = &self->properties->dict; \
((struct pw_##type##_info *)info)->change_mask = change_mask; \
} \
\
g_signal_emit (self, signals[SIGNAL_CHANGED], 0); \
}
#define PROXY_PARAM_FUNC(type) \
static void \
type##_event_param(void *object, int seq, uint32_t id, uint32_t index, \
uint32_t next, const struct spa_pod *param) \
{ \
WpProxy *self = WP_PROXY (object); \
GList *l = self->tasks; \
struct task_data *data = NULL; \
\
/* FIXME: seq is not propagated properly \
while (l) { \
data = g_task_get_task_data (G_TASK (l->data)); \
if (data && data->seq == seq) \
break; \
l = g_list_next (l); \
} \
*/ \
g_return_if_fail (l != NULL); \
data = g_task_get_task_data (G_TASK (l->data)); \
g_ptr_array_add (data->result, spa_pod_copy (param)); \
}
PROXY_INFO_FUNC (NODE, node)
PROXY_PARAM_FUNC(node)
static const struct pw_node_proxy_events node_events = {
PW_VERSION_NODE_PROXY_EVENTS,
.info = node_event_info,
.param = node_event_param,
};
PROXY_INFO_FUNC (PORT, port)
PROXY_PARAM_FUNC(port)
static const struct pw_port_proxy_events port_events = {
PW_VERSION_PORT_PROXY_EVENTS,
.info = port_event_info,
.param = port_event_param,
};
PROXY_INFO_FUNC (FACTORY, factory)
static const struct pw_factory_proxy_events factory_events = {
PW_VERSION_FACTORY_PROXY_EVENTS,
.info = factory_event_info
};
PROXY_INFO_FUNC (LINK, link)
static const struct pw_link_proxy_events link_events = {
PW_VERSION_LINK_PROXY_EVENTS,
.info = link_event_info
};
PROXY_INFO_FUNC (CLIENT, client)
static const struct pw_client_proxy_events client_events = {
PW_VERSION_CLIENT_PROXY_EVENTS,
.info = client_event_info
};
PROXY_INFO_FUNC (MODULE, module)
static const struct pw_module_proxy_events module_events = {
PW_VERSION_MODULE_PROXY_EVENTS,
.info = module_event_info
};
PROXY_INFO_FUNC (DEVICE, device)
PROXY_PARAM_FUNC (device)
static const struct pw_device_proxy_events device_events = {
PW_VERSION_DEVICE_PROXY_EVENTS,
.info = device_event_info,
.param = device_event_param,
};
static void
proxy_event_destroy (void *object)
{
WpProxy *self = WP_PROXY (object);
g_debug ("proxy %u destroyed", self->id);
self->proxy = NULL;
g_signal_emit (self, signals[SIGNAL_DESTROYED], 0);
g_object_unref (self);
}
static void
proxy_event_done (void *object, int seq)
{
WpProxy *self = WP_PROXY (object);
GList *l = self->tasks;
struct task_data *data = NULL;
/* FIXME: seq is not propagated properly */
#if 0
while (l) {
data = g_task_get_task_data (G_TASK (l->data));
if (data && data->seq == seq)
break;
l = g_list_next (l);
}
#endif
g_return_if_fail (l != NULL);
data = g_task_get_task_data (G_TASK (l->data));
g_task_return_pointer (G_TASK (l->data), g_ptr_array_ref (data->result),
(GDestroyNotify) g_ptr_array_unref);
g_object_unref (l->data);
self->tasks = g_list_remove_link (self->tasks, l);
}
static const struct pw_proxy_events proxy_events = {
PW_VERSION_PROXY_EVENTS,
.destroy = proxy_event_destroy,
.done = proxy_event_done,
};
static void
wp_proxy_init (WpProxy * self)
{
}
static void
wp_proxy_constructed (GObject * object)
{
WpProxy *self = WP_PROXY (object);
WpProxyRegistry *pr = NULL;
struct pw_registry_proxy *reg_proxy;
const void *events = NULL;
uint32_t ver = 0;
self->type_string = spa_debug_type_find_name (pw_type_info (), self->type);
g_debug ("added id %u, parent %u, type %s", self->id, self->parent_id,
self->type_string);
switch (self->type) {
case PW_TYPE_INTERFACE_Node:
events = &node_events;
ver = PW_VERSION_NODE;
break;
case PW_TYPE_INTERFACE_Port:
events = &port_events;
ver = PW_VERSION_PORT;
break;
case PW_TYPE_INTERFACE_Factory:
events = &factory_events;
ver = PW_VERSION_FACTORY;
break;
case PW_TYPE_INTERFACE_Link:
events = &link_events;
ver = PW_VERSION_LINK;
break;
case PW_TYPE_INTERFACE_Client:
events = &client_events;
ver = PW_VERSION_CLIENT;
break;
case PW_TYPE_INTERFACE_Module:
events = &module_events;
ver = PW_VERSION_MODULE;
break;
case PW_TYPE_INTERFACE_Device:
events = &device_events;
ver = PW_VERSION_DEVICE;
break;
default:
break;
}
pr = wp_object_get_interface (self->core, WP_TYPE_PROXY_REGISTRY);
reg_proxy = wp_proxy_registry_get_pw_registry_proxy (pr);
g_warn_if_fail (reg_proxy != NULL);
self->proxy = pw_registry_proxy_bind (reg_proxy, self->id, self->type, ver, 0);
pw_proxy_add_listener (self->proxy, &self->proxy_listener, &proxy_events,
self);
/* this reference is held by the pw_proxy, released in proxy_event_destroy() */
g_object_ref (self);
if (events)
pw_proxy_add_proxy_listener(self->proxy, &self->proxy_proxy_listener,
events, self);
/*
* initial_properties is a stack-allocated const spa_dict *
* that is not safe to access beyond the scope of the g_object_new()
* call, so we replace it with a pw_properties
*/
if (self->initial_properties)
self->properties = pw_properties_new_dict (self->initial_properties);
else
self->properties = pw_properties_new (NULL);
G_OBJECT_CLASS (wp_proxy_parent_class)->constructed (object);
}
static void
wp_proxy_finalize (GObject * object)
{
WpProxy *self = WP_PROXY (object);
g_clear_pointer (&self->properties, pw_properties_free);
G_OBJECT_CLASS (wp_proxy_parent_class)->finalize (object);
}
static void
wp_proxy_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpProxy *self = WP_PROXY (object);
switch (property_id) {
case PROP_ID:
self->id = g_value_get_uint (value);
break;
case PROP_PARENT_ID:
self->parent_id = g_value_get_uint (value);
break;
case PROP_SPA_TYPE:
self->type = g_value_get_uint (value);
break;
case PROP_INITIAL_PROPERTIES:
self->initial_properties = g_value_get_pointer (value);
break;
case PROP_CORE:
self->core = g_value_get_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_proxy_get_property (GObject * object, guint property_id, GValue * value,
GParamSpec * pspec)
{
WpProxy *self = WP_PROXY (object);
switch (property_id) {
case PROP_ID:
g_value_set_uint (value, self->id);
break;
case PROP_PARENT_ID:
g_value_set_uint (value, self->parent_id);
break;
case PROP_SPA_TYPE:
g_value_set_uint (value, self->type);
break;
case PROP_SPA_TYPE_STRING:
g_value_set_string (value, self->type_string);
break;
case PROP_CORE:
g_value_set_object (value, self->core);
break;
case PROP_PROXY:
g_value_set_pointer (value, self->proxy);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_proxy_class_init (WpProxyClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->constructed = wp_proxy_constructed;
object_class->finalize = wp_proxy_finalize;
object_class->get_property = wp_proxy_get_property;
object_class->set_property = wp_proxy_set_property;
g_object_class_install_property (object_class, PROP_ID,
g_param_spec_uint ("id", "id",
"The global ID of the object", 0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_PARENT_ID,
g_param_spec_uint ("parent-id", "parent-id",
"The global ID of the parent object", 0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_SPA_TYPE,
g_param_spec_uint ("spa-type", "spa-type",
"The SPA type of the object", 0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_SPA_TYPE_STRING,
g_param_spec_string ("spa-type-string", "spa-type-string",
"The string representation of the SPA type of the object", NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_INITIAL_PROPERTIES,
g_param_spec_pointer ("initial-properties", "initial-properties",
"The initial set of properties of the proxy",
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_CORE,
g_param_spec_object ("core", "core", "The core that owns this proxy",
WP_TYPE_OBJECT,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_PROXY,
g_param_spec_pointer ("proxy", "proxy",
"The underlying struct pw_proxy *",
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
signals[SIGNAL_DESTROYED] = g_signal_new ("destroyed",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 0);
signals[SIGNAL_CHANGED] = g_signal_new ("changed",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 0);
}
static const gchar *
wp_proxy_pw_properties_get (WpPipewireProperties * p, const gchar * property)
{
WpProxy * self = WP_PROXY (p);
return pw_properties_get (self->properties, property);
}
static const struct spa_dict *
wp_proxy_pw_properties_get_as_spa_dict (WpPipewireProperties * p)
{
WpProxy * self = WP_PROXY (p);
return &self->properties->dict;
}
static void
wp_proxy_pw_properties_init (WpPipewirePropertiesInterface * iface)
{
iface->get = wp_proxy_pw_properties_get;
iface->get_as_spa_dict = wp_proxy_pw_properties_get_as_spa_dict;
}
/**
* wp_proxy_get_id: (method)
* @self: the proxy
*
* Returns: the global ID of the remote object
*/
guint32
wp_proxy_get_id (WpProxy * self)
{
g_return_val_if_fail (WP_IS_PROXY (self), -1);
return self->id;
}
/**
* wp_proxy_get_parent_id: (method)
* @self: the proxy
*
* Returns: the global ID of the parent remote object
*/
guint32
wp_proxy_get_parent_id (WpProxy * self)
{
g_return_val_if_fail (WP_IS_PROXY (self), -1);
return self->parent_id;
}
/**
* wp_proxy_get_spa_type: (method)
* @self: the proxy
*
* Returns: the SPA type of the remote object
*/
guint32
wp_proxy_get_spa_type (WpProxy * self)
{
g_return_val_if_fail (WP_IS_PROXY (self), -1);
return self->type;
}
/**
* wp_proxy_get_spa_type_string: (method)
* @self: the proxy
*
* Returns: (transfer none): the string that describes the SPA type of
* the remote object
*/
const gchar *
wp_proxy_get_spa_type_string (WpProxy * self)
{
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
return self->type_string;
}
/**
* wp_proxy_get_core: (method)
* @self: the proxy
*
* Returns: (transfer full): the core #WpObject
*/
WpObject *
wp_proxy_get_core (WpProxy *self)
{
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
return g_object_ref (self->core);
}
/**
* wp_proxy_is_destroyed: (method)
* @self: the proxy
*
* Returns: TRUE if the proxy has been destroyed
*/
gboolean
wp_proxy_is_destroyed (WpProxy * self)
{
g_return_val_if_fail (WP_IS_PROXY (self), TRUE);
return (self->proxy == NULL);
}
/**
* wp_proxy_get_pw_proxy: (skip)
* @self: the proxy
*
* Returns: the pw_proxy pointer or %NULL if the proxy is destroyed
*/
struct pw_proxy *
wp_proxy_get_pw_proxy (WpProxy * self)
{
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
return self->proxy;
}
gconstpointer
wp_proxy_get_info_native (WpProxy * self)
{
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
return self->info;
}
static void
task_data_free (struct task_data * data)
{
g_ptr_array_unref (data->result);
g_slice_free (struct task_data, data);
}
void
wp_proxy_enum_params (WpProxy * self, guint32 id,
GAsyncReadyCallback callback, gpointer user_data)
{
g_autoptr (GTask) task = NULL;
int seq;
struct task_data *data;
g_return_if_fail (WP_IS_PROXY (self));
g_return_if_fail (callback != NULL);
task = g_task_new (self, NULL, callback, user_data);
g_task_set_source_tag (task, wp_proxy_enum_params);
data = g_slice_new0 (struct task_data);
data->seq = seq = global_seq++;
data->result = g_ptr_array_new_with_free_func (free);
g_task_set_task_data (task, data, (GDestroyNotify) task_data_free);
self->tasks = g_list_append (self->tasks, g_object_ref (task));
switch (self->type) {
case PW_TYPE_INTERFACE_Node:
pw_node_proxy_enum_params ((struct pw_node_proxy *) self->proxy,
seq, id, 0, -1, NULL);
pw_proxy_sync (self->proxy, seq);
break;
case PW_TYPE_INTERFACE_Port:
pw_port_proxy_enum_params ((struct pw_port_proxy *) self->proxy,
seq, id, 0, -1, NULL);
pw_proxy_sync (self->proxy, seq);
break;
case PW_TYPE_INTERFACE_Device:
pw_device_proxy_enum_params ((struct pw_device_proxy *) self->proxy,
seq, id, 0, -1, NULL);
pw_proxy_sync (self->proxy, seq);
break;
default:
g_task_return_new_error (task, WP_DOMAIN_LIBRARY,
WP_LIBRARY_ERROR_INVARIANT,
"Proxy interface does not have an enum_params method");
break;
}
}
/**
* wp_proxy_enum_params_finish:
*
* Returns: (transfer full) (element-type spa_pod*): the params
*/
GPtrArray *
wp_proxy_enum_params_finish (WpProxy * self,
GAsyncResult * res, GError ** err)
{
g_return_val_if_fail (g_task_is_valid (res, self), NULL);
g_return_val_if_fail (g_async_result_is_tagged (res, wp_proxy_enum_params),
NULL);
return g_task_propagate_pointer (G_TASK (res), err);
}

View File

@@ -1,43 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WP_PROXY_H__
#define __WP_PROXY_H__
#include "object.h"
#include "core-interfaces.h"
#include <gio/gio.h>
G_BEGIN_DECLS
struct pw_proxy;
#define WP_TYPE_PROXY (wp_proxy_get_type ())
G_DECLARE_FINAL_TYPE (WpProxy, wp_proxy, WP, PROXY, WpObject)
guint32 wp_proxy_get_id (WpProxy * self);
guint32 wp_proxy_get_parent_id (WpProxy * self);
guint32 wp_proxy_get_spa_type (WpProxy * self);
const gchar * wp_proxy_get_spa_type_string (WpProxy * self);
WpObject * wp_proxy_get_core (WpProxy *self);
gboolean wp_proxy_is_destroyed (WpProxy * self);
struct pw_proxy * wp_proxy_get_pw_proxy (WpProxy * self);
gconstpointer wp_proxy_get_info_native (WpProxy * self);
void wp_proxy_enum_params (WpProxy * self, guint32 id,
GAsyncReadyCallback callback, gpointer data);
GPtrArray * wp_proxy_enum_params_finish (WpProxy * self,
GAsyncResult * res, GError ** err);
G_END_DECLS
#endif

120
lib/wp/session-manager.c Normal file
View File

@@ -0,0 +1,120 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "session-manager.h"
struct _WpSessionManager
{
GObject parent;
GPtrArray *endpoints;
};
G_DEFINE_TYPE (WpSessionManager, wp_session_manager, G_TYPE_OBJECT)
G_DEFINE_QUARK (WP_GLOBAL_SESSION_MANAGER, wp_global_session_manager)
static void
wp_session_manager_init (WpSessionManager * self)
{
self->endpoints = g_ptr_array_new_with_free_func (g_object_unref);
}
static void
wp_session_manager_finalize (GObject * obj)
{
WpSessionManager * self = WP_SESSION_MANAGER (obj);
g_ptr_array_unref (self->endpoints);
G_OBJECT_CLASS (wp_session_manager_parent_class)->finalize (obj);
}
static void
wp_session_manager_class_init (WpSessionManagerClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->finalize = wp_session_manager_finalize;
}
WpSessionManager *
wp_session_manager_new (void)
{
return g_object_new (WP_TYPE_SESSION_MANAGER, NULL);
}
void
wp_session_manager_register_endpoint (WpSessionManager * self,
WpEndpoint * ep)
{
g_ptr_array_add (self->endpoints, g_object_ref (ep));
}
void
wp_session_manager_remove_endpoint (WpSessionManager * self,
WpEndpoint * ep)
{
g_ptr_array_remove_fast (self->endpoints, ep);
}
struct endpoints_foreach_data
{
GPtrArray *result;
const gchar *lookup;
};
static inline gboolean
media_class_matches (const gchar * media_class, const gchar * lookup)
{
const gchar *c1 = media_class, *c2 = lookup;
/* empty lookup matches all classes */
if (!lookup)
return TRUE;
/* compare until we reach the end of the lookup string */
for (; *c2 != '\0'; c1++, c2++) {
if (*c1 != *c2)
return FALSE;
}
/* the lookup may not end in a slash, however it must match up
* to the end of a submedia_class. i.e.:
* match: media_class: Audio/Source/Virtual
* lookup: Audio/Source
*
* NO match: media_class: Audio/Source/Virtual
* lookup: Audio/Sou
*
* if *c1 is not /, also check the previous char, because the lookup
* may actually end in a slash:
*
* match: media_class: Audio/Source/Virtual
* lookup: Audio/Source/
*/
if (!(*c1 == '/' || *c1 == '\0' || *(c1 - 1) == '/'))
return FALSE;
return TRUE;
}
static void
find_endpoints (WpEndpoint * endpoint, struct endpoints_foreach_data * data)
{
if (media_class_matches (wp_endpoint_get_media_class (endpoint), data->lookup))
g_ptr_array_add (data->result, g_object_ref (endpoint));
}
GPtrArray *
wp_session_manager_find_endpoints (WpSessionManager * self,
const gchar * media_class_lookup)
{
struct endpoints_foreach_data data;
data.result = g_ptr_array_new_with_free_func (g_object_unref);
data.lookup = media_class_lookup;
g_ptr_array_foreach (self->endpoints, (GFunc) find_endpoints, &data);
return data.result;
}

34
lib/wp/session-manager.h Normal file
View File

@@ -0,0 +1,34 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WIREPLUMBER_SESSION_MANAGER_H__
#define __WIREPLUMBER_SESSION_MANAGER_H__
#include "endpoint.h"
G_BEGIN_DECLS
#define WP_TYPE_SESSION_MANAGER (wp_session_manager_get_type ())
G_DECLARE_FINAL_TYPE (WpSessionManager, wp_session_manager, WP, SESSION_MANAGER, GObject)
#define WP_GLOBAL_SESSION_MANAGER (wp_global_session_manager_quark ())
GQuark wp_global_session_manager_quark (void);
WpSessionManager * wp_session_manager_new (void);
void wp_session_manager_register_endpoint (WpSessionManager * self,
WpEndpoint * ep);
void wp_session_manager_remove_endpoint (WpSessionManager * self,
WpEndpoint * ep);
GPtrArray * wp_session_manager_find_endpoints (WpSessionManager * self,
const gchar * media_class_lookup);
G_END_DECLS
#endif

View File

@@ -1,101 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "session.h"
#include "wpenums.h"
enum {
PROP_0,
PROP_DIRECTION,
PROP_MEDIA_CLASS,
};
typedef struct
{
WpSessionDirection direction;
gchar media_class[41];
} WpSessionPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (WpSession, wp_session, WP_TYPE_OBJECT)
static void
wp_session_init (WpSession * self)
{
}
static void
wp_session_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpSessionPrivate *priv = wp_session_get_instance_private (WP_SESSION (object));
switch (property_id) {
case PROP_DIRECTION:
priv->direction = g_value_get_enum (value);
break;
case PROP_MEDIA_CLASS:
strncpy (priv->media_class, g_value_get_string (value), 40);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_session_get_property (GObject * object, guint property_id, GValue * value,
GParamSpec * pspec)
{
WpSessionPrivate *priv = wp_session_get_instance_private (WP_SESSION (object));
switch (property_id) {
case PROP_DIRECTION:
g_value_set_enum (value, priv->direction);
break;
case PROP_MEDIA_CLASS:
g_value_set_string (value, priv->media_class);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_session_class_init (WpSessionClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->set_property = wp_session_set_property;
object_class->get_property = wp_session_get_property;
g_object_class_install_property (object_class, PROP_DIRECTION,
g_param_spec_enum ("direction", "direction",
"The media flow direction of the session",
WP_TYPE_SESSION_DIRECTION, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_MEDIA_CLASS,
g_param_spec_string ("media-class", "media-class",
"The media class of the session", NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
WpSessionDirection
wp_session_get_direction (WpSession * self)
{
WpSessionPrivate *priv = wp_session_get_instance_private (self);
return priv->direction;
}
const gchar *
wp_session_get_media_class (WpSession * self)
{
WpSessionPrivate *priv = wp_session_get_instance_private (self);
return priv->media_class;
}

View File

@@ -1,34 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WP_SESSION_H__
#define __WP_SESSION_H__
#include "object.h"
G_BEGIN_DECLS
#define WP_TYPE_SESSION (wp_session_get_type ())
G_DECLARE_DERIVABLE_TYPE (WpSession, wp_session, WP, SESSION, WpObject)
typedef enum {
WP_SESSION_DIRECTION_INPUT,
WP_SESSION_DIRECTION_OUTPUT
} WpSessionDirection;
struct _WpSessionClass
{
WpObjectClass parent_class;
};
WpSessionDirection wp_session_get_direction (WpSession * session);
const gchar *wp_session_get_media_class (WpSession * session);
G_END_DECLS
#endif

View File

@@ -6,6 +6,9 @@
* SPDX-License-Identifier: LGPL-2.1-or-later * SPDX-License-Identifier: LGPL-2.1-or-later
*/ */
#include "utils.h" #include "core.h"
#include "endpoint.h"
G_DEFINE_QUARK (wireplumber-core, wp_domain_core); #include "error.h"
#include "factory.h"
#include "module.h"
#include "session-manager.h"

View File

@@ -4,9 +4,36 @@ common_c_args = [
] ]
shared_library( shared_library(
'wireplumber-module-default-session', 'wireplumber-module-pipewire',
['module-default-session.c'], [
c_args : [common_c_args, '-DG_LOG_DOMAIN="wireplumber-module-default-session"'], 'module-pipewire.c',
'module-pipewire/loop-source.c',
'module-pipewire/simple-endpoint-link.c',
'module-pipewire/simple-endpoint.c',
],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-pipewire"'],
install : true,
#install_dir : modules_install_dir,
dependencies : [wp_dep, pipewire_dep],
)
shared_library(
'wireplumber-module-pw-alsa-udev',
[
'module-pw-alsa-udev.c',
],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-pw-alsa-udev"'],
install : true,
#install_dir : modules_install_dir,
dependencies : [wp_dep, pipewire_dep],
)
shared_library(
'wireplumber-module-pw-audio-softdsp-endpoint',
[
'module-pw-audio-softdsp-endpoint.c',
],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-pw-audio-softdsp-endpoint"'],
install : true, install : true,
#install_dir : modules_install_dir, #install_dir : modules_install_dir,
dependencies : [wp_dep, pipewire_dep], dependencies : [wp_dep, pipewire_dep],

View File

@@ -1,338 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <wp/plugin.h>
#include <wp/session.h>
#include <pipewire/pipewire.h>
#include <spa/param/audio/format-utils.h>
#define MIN_QUANTUM_SIZE 64
#define MAX_QUANTUM_SIZE 1024
G_DECLARE_FINAL_TYPE (DefaultSession, session, DEFAULT, SESSION, WpSession)
G_DECLARE_FINAL_TYPE (DefaultSessionPlugin, plugin, DEFAULT, SESSION_PLUGIN, WpPlugin)
/* DefaultSession */
struct _DefaultSession
{
WpSession parent;
WpProxy *device_node;
struct pw_node_proxy *dsp_proxy;
struct spa_audio_info_raw format;
guint32 media_type;
guint32 session_id;
};
G_DEFINE_TYPE (DefaultSession, session, WP_TYPE_SESSION)
static void
session_init (DefaultSession * self)
{
}
static void
session_finalize (GObject * obj)
{
DefaultSession *self = DEFAULT_SESSION (obj);
G_OBJECT_CLASS (session_parent_class)->finalize (obj);
}
static void
session_class_init (DefaultSessionClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->finalize = session_finalize;
}
static DefaultSession *
session_new (WpProxy * device_node, guint32 type, WpSessionDirection dir,
const gchar * media_class)
{
DefaultSession *sess = g_object_new (session_get_type (),
"direction", dir,
"media-class", media_class,
NULL);
sess->device_node = device_node;
sess->media_type = type;
return sess;
}
/* DefaultSessionPlugin */
struct _DefaultSessionPlugin
{
WpPlugin parent;
};
G_DEFINE_TYPE (DefaultSessionPlugin, plugin, WP_TYPE_PLUGIN)
static void
device_node_destroyed (WpProxy * device_node, DefaultSession * session)
{
g_autoptr (WpObject) core = NULL;
WpSessionRegistry *sr = NULL;
g_info ("Proxy %u destroyed - unregistering session %u",
wp_proxy_get_id (device_node), session->session_id);
core = wp_proxy_get_core (device_node);
sr = wp_object_get_interface (core, WP_TYPE_SESSION_REGISTRY);
g_return_if_fail (sr != NULL);
wp_session_registry_unregister_session (sr, session->session_id);
}
static gboolean
handle_node (WpPlugin * self, WpProxy * proxy)
{
g_autoptr (WpObject) core = NULL;
g_autoptr (DefaultSession) session = NULL;
g_autoptr (GError) error = NULL;
WpSessionRegistry *sr = NULL;
const gchar *media_class, *ptr;
WpSessionDirection direction;
guint32 media_type;
guint32 sess_id;
ptr = media_class = wp_pipewire_properties_get (
WP_PIPEWIRE_PROPERTIES (proxy), "media.class");
if (g_str_has_prefix (ptr, "Audio/")) {
ptr += strlen ("Audio/");
media_type = SPA_MEDIA_TYPE_audio;
} else if (g_str_has_prefix (ptr, "Video/")) {
ptr += strlen ("Video/");
media_type = SPA_MEDIA_TYPE_video;
} else {
goto out;
}
if (strcmp (ptr, "Sink") == 0)
direction = WP_SESSION_DIRECTION_OUTPUT;
else if (strcmp (ptr, "Source") == 0)
direction = WP_SESSION_DIRECTION_INPUT;
else
goto out;
g_info ("Creating session for node %u (%s), media.class = '%s'",
wp_proxy_get_id (proxy), wp_proxy_get_spa_type_string (proxy),
media_class);
session = session_new (proxy, media_type, direction, media_class);
core = wp_plugin_get_core (self);
sr = wp_object_get_interface (core, WP_TYPE_SESSION_REGISTRY);
g_return_val_if_fail (sr != NULL, FALSE);
if ((sess_id = wp_session_registry_register_session (sr,
WP_SESSION (session), &error)) == -1) {
g_warning ("Error registering session: %s", error->message);
return FALSE;
}
session->session_id = sess_id;
g_object_set_data_full (G_OBJECT (proxy), "module-default-session.session",
g_object_ref (session), g_object_unref);
g_signal_connect_object (proxy, "destroyed",
(GCallback) device_node_destroyed, session, 0);
return TRUE;
out:
g_message ("Unrecognized media.class '%s' - not handling proxy %u (%s)",
media_class, wp_proxy_get_id (proxy),
wp_proxy_get_spa_type_string (proxy));
return FALSE;
}
static gboolean
plug_dsp (WpProxy * node)
{
DefaultSession *session;
g_autoptr (WpObject) core = NULL;
WpPipewireObjects *pw_objects = NULL;
struct pw_core_proxy *core_proxy;
WpPipewireProperties *pw_props = NULL;
struct pw_properties *props;
const char *name;
enum pw_direction reverse_direction;
uint8_t buf[1024];
struct spa_pod_builder b = { 0, };
struct spa_pod *param;
session = g_object_get_data (G_OBJECT (node), "module-default-session.session");
g_return_val_if_fail (session->media_type == SPA_MEDIA_TYPE_audio,
G_SOURCE_REMOVE);
g_info ("making audio dsp for session %u", session->session_id);
core = wp_proxy_get_core (node);
pw_objects = WP_PIPEWIRE_OBJECTS (core);
core_proxy = pw_remote_get_core_proxy (wp_pipewire_objects_get_pw_remote (pw_objects));
pw_props = WP_PIPEWIRE_PROPERTIES (node);
props = pw_properties_new_dict (
wp_pipewire_properties_get_as_spa_dict (pw_props));
if ((name = pw_properties_get (props, "device.nick")) == NULL)
name = "unnamed";
pw_properties_set (props, "audio-dsp.name", name);
pw_properties_setf (props, "audio-dsp.direction", "%d",
wp_session_get_direction (WP_SESSION (session)));
pw_properties_setf (props, "audio-dsp.maxbuffer", "%ld",
MAX_QUANTUM_SIZE * sizeof (float));
session->dsp_proxy = pw_core_proxy_create_object (core_proxy,
"audio-dsp",
PW_TYPE_INTERFACE_Node,
PW_VERSION_NODE,
&props->dict,
0);
pw_properties_free (props);
reverse_direction =
(wp_session_get_direction (WP_SESSION (session)) == WP_SESSION_DIRECTION_INPUT) ?
PW_DIRECTION_OUTPUT : PW_DIRECTION_INPUT;
spa_pod_builder_init (&b, buf, sizeof (buf));
param = spa_format_audio_raw_build (&b, SPA_PARAM_Format, &session->format);
param = spa_pod_builder_add_object (&b,
SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile,
SPA_PARAM_PROFILE_direction, SPA_POD_Id (reverse_direction),
SPA_PARAM_PROFILE_format, SPA_POD_Pod (param));
pw_node_proxy_set_param ((struct pw_node_proxy *) session->dsp_proxy,
SPA_PARAM_Profile, 0, param);
return G_SOURCE_REMOVE;
}
static void
audio_port_enum_params_done (GObject * port, GAsyncResult * res, gpointer data)
{
g_autoptr (GError) error = NULL;
g_autoptr (GPtrArray) params = NULL;
WpProxy *node;
DefaultSession *session;
struct spa_audio_info_raw info = { 0, };
guint32 media_type, media_subtype;
guint i;
g_debug ("done enumerating port %u params",
wp_proxy_get_id (WP_PROXY (port)));
params = wp_proxy_enum_params_finish (WP_PROXY (port), res, &error);
if (!params) {
g_warning ("%s", error->message);
return;
}
node = WP_PROXY (data);
session = g_object_get_data (G_OBJECT (node), "module-default-session.session");
for (i = 0; i < params->len; i++) {
struct spa_pod *param = g_ptr_array_index (params, i);
if (spa_format_parse(param, &media_type, &media_subtype) < 0)
return;
if (media_type != SPA_MEDIA_TYPE_audio ||
media_subtype != SPA_MEDIA_SUBTYPE_raw)
return;
spa_pod_fixate (param);
if (spa_format_audio_raw_parse (param, &info) < 0)
return;
if (info.channels > session->format.channels)
session->format = info;
}
g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, (GSourceFunc) plug_dsp,
g_object_ref (node), g_object_unref);
}
static gboolean
handle_audio_port (WpPlugin * self, WpProxy * port, WpProxy * node)
{
wp_proxy_enum_params (port, SPA_PARAM_EnumFormat,
audio_port_enum_params_done, node);
return TRUE;
}
static gboolean
handle_pw_proxy (WpPlugin * self, WpProxy * proxy)
{
g_autoptr (WpObject) core = NULL;
g_autoptr (WpProxy) parent = NULL;
WpProxyRegistry *reg = NULL;
DefaultSession *session;
if (wp_proxy_get_spa_type (proxy) != PW_TYPE_INTERFACE_Port &&
wp_proxy_get_spa_type (proxy) != PW_TYPE_INTERFACE_Node)
return FALSE;
core = wp_plugin_get_core (self);
reg = wp_object_get_interface (core, WP_TYPE_PROXY_REGISTRY);
parent = wp_proxy_registry_get_proxy (reg, wp_proxy_get_parent_id (proxy));
if (wp_proxy_get_spa_type (parent) == PW_TYPE_INTERFACE_Device &&
wp_proxy_get_spa_type (proxy) == PW_TYPE_INTERFACE_Node)
{
g_debug ("handling node %u (parent device %u)", wp_proxy_get_id (proxy),
wp_proxy_get_id (parent));
return handle_node (self, proxy);
}
else if (wp_proxy_get_spa_type (parent) == PW_TYPE_INTERFACE_Node &&
wp_proxy_get_spa_type (proxy) == PW_TYPE_INTERFACE_Port &&
(session = g_object_get_data (G_OBJECT (parent), "module-default-session.session")) &&
session->media_type == SPA_MEDIA_TYPE_audio)
{
g_debug ("handling audio port %u (parent node %u)", wp_proxy_get_id (proxy),
wp_proxy_get_id (parent));
return handle_audio_port (self, proxy, parent);
}
return FALSE;
}
static void
plugin_init (DefaultSessionPlugin * self)
{
}
static void
plugin_class_init (DefaultSessionPluginClass * klass)
{
WpPluginClass *plugin_class = WP_PLUGIN_CLASS (klass);
plugin_class->handle_pw_proxy = handle_pw_proxy;
}
static const WpPluginMetadata plugin_metadata = {
.rank = WP_PLUGIN_RANK_UPSTREAM,
.name = "default-session",
.description = "Provides the default WpSession implementation",
.author = "George Kiagiadakis <george.kiagiadakis@collabora.com>",
.license = "LGPL-2.1-or-later",
.version = "0.1",
.origin = "https://gitlab.freedesktop.org/gkiagia/wireplumber"
};
void
WP_MODULE_INIT_SYMBOL (WpPluginRegistry * registry)
{
wp_plugin_registry_register_static (registry, plugin_get_type (),
&plugin_metadata, sizeof (plugin_metadata));
}

68
modules/module-pipewire.c Normal file
View File

@@ -0,0 +1,68 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
/**
* module-pipewire provides basic integration between wireplumber and pipewire.
* It provides the pipewire core and remote, connects to pipewire and provides
* the most primitive implementations of WpEndpoint and WpEndpointLink
*/
#include <wp/wp.h>
#include <pipewire/pipewire.h>
#include "module-pipewire/loop-source.h"
gpointer simple_endpoint_factory (WpFactory * factory, GType type,
GVariant * properties);
gpointer simple_endpoint_link_factory (WpFactory * factory, GType type,
GVariant * properties);
static gboolean
connect_in_idle (struct pw_remote *remote)
{
pw_remote_connect (remote);
return G_SOURCE_REMOVE;
}
static void
module_destroy (gpointer r)
{
struct pw_remote *remote = r;
struct pw_core *core = pw_remote_get_core (remote);
pw_remote_destroy (remote);
pw_core_destroy (core);
}
void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
{
GSource *source;
struct pw_core *pw_core;
struct pw_remote *pw_remote;
pw_init (NULL, NULL);
source = wp_loop_source_new ();
g_source_attach (source, NULL);
pw_core = pw_core_new (WP_LOOP_SOURCE (source)->loop, NULL, 0);
wp_core_register_global (core, WP_GLOBAL_PW_CORE, pw_core, NULL);
pw_remote = pw_remote_new (pw_core, NULL, 0);
wp_core_register_global (core, WP_GLOBAL_PW_REMOTE, pw_remote, NULL);
wp_module_set_destroy_callback (module, module_destroy, pw_remote);
wp_core_register_factory (core, wp_factory_new (
"pipewire-simple-endpoint", simple_endpoint_factory));
wp_core_register_factory (core, wp_factory_new (
"pipewire-simple-endpoint-link", simple_endpoint_link_factory));
g_idle_add ((GSourceFunc) connect_in_idle, pw_remote);
}

View File

@@ -6,18 +6,12 @@
* SPDX-License-Identifier: LGPL-2.1-or-later * SPDX-License-Identifier: LGPL-2.1-or-later
*/ */
/**
* Integration between the PipeWire main loop and GMainLoop
*/
#include "loop-source.h" #include "loop-source.h"
#define WP_LOOP_SOURCE(x) ((WpLoopSource *) x)
typedef struct _WpLoopSource WpLoopSource;
struct _WpLoopSource
{
GSource parent;
struct pw_loop *loop;
};
static gboolean static gboolean
wp_loop_source_dispatch (GSource * s, GSourceFunc callback, gpointer user_data) wp_loop_source_dispatch (GSource * s, GSourceFunc callback, gpointer user_data)
{ {
@@ -58,9 +52,3 @@ wp_loop_source_new (void)
return (GSource *) s; return (GSource *) s;
} }
struct pw_loop *
wp_loop_source_get_loop (GSource *s)
{
return WP_LOOP_SOURCE(s)->loop;
}

View File

@@ -0,0 +1,21 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <wp/wp.h>
#include <pipewire/pipewire.h>
#define WP_LOOP_SOURCE(x) ((WpLoopSource *) x)
typedef struct _WpLoopSource WpLoopSource;
struct _WpLoopSource
{
GSource parent;
struct pw_loop *loop;
};
GSource * wp_loop_source_new (void);

View File

@@ -0,0 +1,71 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
/**
* The simple endpoint link is an implementation of WpEndpointLink that
* expects the two linked endpoints to have nodes in the pipewire graph.
* When asked to create a link, it creates pw_link objects that will link
* the ports of the source node to the ports of the sink node.
*
* The GVariant data that is passed in create must be of type (uau),
* which means a tuple with the following fields:
* - u: a uint32 that is the ID of a node
* - au: an array of uint32 that are the IDs of the ports on this node
*
* Linking endpoints with multiple nodes is not supported by this implementation.
*/
#include <wp/wp.h>
#include <pipewire/pipewire.h>
struct _WpPipewireSimpleEndpointLink
{
WpEndpointLink parent;
};
G_DECLARE_FINAL_TYPE (WpPipewireSimpleEndpointLink,
simple_endpoint_link, WP_PIPEWIRE, SIMPLE_ENDPOINT_LINK, WpEndpointLink)
G_DEFINE_TYPE (WpPipewireSimpleEndpointLink,
simple_endpoint_link, WP_TYPE_ENDPOINT_LINK)
static void
simple_endpoint_link_init (WpPipewireSimpleEndpointLink * self)
{
}
static gboolean
simple_endpoint_link_create (WpEndpointLink * self, GVariant * src_data,
GVariant * sink_data, GError ** error)
{
/* TODO create pw_links based on the nodes & ports described in src/sink_data */
}
static void
simple_endpoint_link_destroy (WpEndpointLink * self)
{
/* TODO destroy pw_links */
}
static void
simple_endpoint_link_class_init (WpPipewireSimpleEndpointLinkClass * klass)
{
WpEndpointLinkClass *link_class = (WpEndpointLinkClass *) klass;
link_class->create = simple_endpoint_link_create;
link_class->destroy = simple_endpoint_link_destroy;
}
gpointer
simple_endpoint_link_factory (WpFactory * factory, GType type,
GVariant * properties)
{
if (type != WP_TYPE_ENDPOINT_LINK)
return NULL;
return g_object_new (simple_endpoint_link_get_type (), NULL);
}

View File

@@ -0,0 +1,63 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
/**
* The simple endpoint is a WpEndpoint implementation that represents
* all ports of a single direction of a single pipewire node.
* It can be used to create an Endpoint for a client node or for any
* other arbitrary node that does not need any kind of internal management.
*/
#include <wp/wp.h>
#include <pipewire/pipewire.h>
struct _WpPipewireSimpleEndpoint
{
WpEndpoint parent;
};
G_DECLARE_FINAL_TYPE (WpPipewireSimpleEndpoint,
simple_endpoint, WP_PIPEWIRE, SIMPLE_ENDPOINT, WpEndpoint)
G_DEFINE_TYPE (WpPipewireSimpleEndpoint, simple_endpoint, WP_TYPE_ENDPOINT)
static void
simple_endpoint_init (WpPipewireSimpleEndpoint * self)
{
}
static gboolean
simple_endpoint_prepare_link (WpEndpoint * self, guint32 stream_id,
WpEndpointLink * link, GVariant ** properties, GError ** error)
{
/* TODO: verify that the remote end supports the same media type */
/* TODO: fill @properties with (node id, array(port ids)) */
}
static void
simple_endpoint_class_init (WpPipewireSimpleEndpointClass * klass)
{
WpEndpointClass *endpoint_class = (WpEndpointClass *) klass;
endpoint_class->prepare_link = simple_endpoint_prepare_link;
}
gpointer
simple_endpoint_factory (WpFactory * factory, GType type,
GVariant * properties)
{
if (type != WP_TYPE_ENDPOINT)
return NULL;
/* TODO: retrieve pw_node* from @properties and keep it
* TODO: populate media_class and name on the endpoint
* TODO: potentially choose between subclasses of SimpleEndpoint
* in order to add interfaces (volume, color balance, etc)
*/
return g_object_new (simple_endpoint_get_type (), NULL);
}

View File

@@ -0,0 +1,20 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
/**
* module-pw-alsa-udev provides alsa device detection through pipewire
* and automatically creates endpoints for all alsa device nodes that appear
*/
#include <wp/wp.h>
#include <pipewire/pipewire.h>
void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
{
}

View File

@@ -0,0 +1,65 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
/**
* module-pw-audio-softdsp-endpoint provides a WpEndpoint implementation
* that wraps an audio device node in pipewire and plugs a DSP node, as well
* as optional merger+volume nodes that are used as entry points for the
* various streams that this endpoint may have
*/
#include <wp/wp.h>
#include <pipewire/pipewire.h>
struct _WpPwAudioSoftdspEndpoint
{
WpEndpoint parent;
};
G_DECLARE_FINAL_TYPE (WpPwAudioSoftdspEndpoint, endpoint,
WP_PW, AUDIO_SOFTDSP_ENDPOINT, WpEndpoint)
G_DEFINE_TYPE (WpPwAudioSoftdspEndpoint, endpoint, WP_TYPE_ENDPOINT)
static void
endpoint_init (WpPwAudioSoftdspEndpoint * self)
{
}
static gboolean
endpoint_prepare_link (WpEndpoint * self, guint32 stream_id,
WpEndpointLink * link, GVariant ** properties, GError ** error)
{
}
static void
endpoint_class_init (WpPwAudioSoftdspEndpointClass * klass)
{
WpEndpointClass *endpoint_class = (WpEndpointClass *) klass;
endpoint_class->prepare_link = endpoint_prepare_link;
}
static gpointer
endpoint_factory (WpFactory * factory, GType type, GVariant * properties)
{
if (type != WP_TYPE_ENDPOINT)
return NULL;
/* TODO: retrieve pw_node* from @properties and keep it
* TODO: populate media_class and name on the endpoint
*/
return g_object_new (endpoint_get_type (), NULL);
}
void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
{
wp_core_register_factory (core, wp_factory_new (
"pw-audio-softdsp-endpoint", endpoint_factory));
}

View File

@@ -1,358 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "core.h"
#include "loop-source.h"
#include "module-loader.h"
#include "plugin-registry-impl.h"
#include "proxy-registry-impl.h"
#include "session-registry-impl.h"
#include "utils.h"
#include <wp/plugin.h>
#include <pipewire/pipewire.h>
#include <glib-unix.h>
#include <gio/gio.h>
#define WIREPLUMBER_DEFAULT_CONFIG_FILE "wireplumber.conf"
struct _WpCore
{
GObject parent;
GMainLoop *loop;
GSource *source;
struct pw_core *core;
struct pw_remote *remote;
struct spa_hook remote_listener;
WpModuleLoader *module_loader;
GError *exit_error;
};
static void wp_core_pw_objects_init (WpPipewireObjectsInterface * iface);
G_DEFINE_TYPE_WITH_CODE (WpCore, wp_core, WP_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (WP_TYPE_PIPEWIRE_OBJECTS, wp_core_pw_objects_init);
)
static gboolean
signal_handler (gpointer data)
{
WpCore *self = WP_CORE (data);
wp_core_exit (self, WP_DOMAIN_CORE, WP_CODE_INTERRUPTED,
"interrupted by signal");
return G_SOURCE_CONTINUE;
}
static void
remote_state_changed (void * data, enum pw_remote_state old_state,
enum pw_remote_state new_state, const char * error)
{
WpCore *self = WP_CORE (data);
g_debug ("remote state changed, old:%s new:%s",
pw_remote_state_as_string (old_state),
pw_remote_state_as_string (new_state));
switch (new_state) {
case PW_REMOTE_STATE_UNCONNECTED:
wp_core_exit (self, WP_DOMAIN_CORE, WP_CODE_DISCONNECTED, "disconnected");
break;
case PW_REMOTE_STATE_ERROR:
wp_core_exit (self, WP_DOMAIN_CORE, WP_CODE_REMOTE_ERROR,
"pipewire remote error: %s", error);
break;
default:
break;
}
}
static const struct pw_remote_events remote_events = {
PW_VERSION_REMOTE_EVENTS,
.state_changed = remote_state_changed,
};
static gboolean
wp_core_parse_commands_file (WpCore * self, GInputStream * stream,
GError ** error)
{
WpPluginRegistry *plugin_registry = NULL;
gchar buffer[4096];
gssize bytes_read;
gchar *cur, *linestart, *saveptr;
gchar *cmd, *abi, *module;
gint lineno = 1;
gboolean eof = FALSE;
plugin_registry = wp_object_get_interface (WP_OBJECT (self),
WP_TYPE_PLUGIN_REGISTRY);
linestart = cur = buffer;
do {
bytes_read = g_input_stream_read (stream, cur, sizeof (buffer), NULL, error);
if (bytes_read < 0)
return FALSE;
else if (bytes_read == 0) {
eof = TRUE;
/* terminate the remaining data, so that we consume it all */
if (cur != linestart) {
*cur = '\n';
}
}
bytes_read += (cur - linestart);
while (cur - buffer < bytes_read) {
while (cur - buffer < bytes_read && *cur != '\n')
cur++;
if (*cur == '\n') {
/* found the end of a line */
*cur = '\0';
/* tokenize and execute */
cmd = strtok_r (linestart, " ", &saveptr);
if (!g_strcmp0 (cmd, "load-module")) {
abi = strtok_r (NULL, " ", &saveptr);
module = strtok_r (NULL, " ", &saveptr);
if (!abi || !module) {
g_set_error (error, WP_DOMAIN_CORE, WP_CODE_INVALID_ARGUMENT,
"expected ABI and MODULE at line %i", lineno);
return FALSE;
} else if (!wp_module_loader_load (self->module_loader,
plugin_registry, abi, module, error)) {
return FALSE;
}
} else {
g_set_error (error, WP_DOMAIN_CORE, WP_CODE_INVALID_ARGUMENT,
"unknown command '%s' at line %i", cmd, lineno);
return FALSE;
}
/* continue with the next line */
linestart = ++cur;
lineno++;
}
}
/* reached the end of the data that was read */
if (cur - linestart >= sizeof (buffer)) {
g_set_error (error, WP_DOMAIN_CORE, WP_CODE_OPERATION_FAILED,
"line %i exceeds the maximum allowed line size (%d bytes)",
lineno, (gint) sizeof (buffer));
return FALSE;
} else if (cur - linestart > 0) {
/* we have unparsed data, move it to the
* beginning of the buffer and continue */
strncpy (buffer, linestart, cur - linestart);
linestart = buffer;
cur = buffer + (cur - linestart);
}
} while (!eof);
return TRUE;
}
static gboolean
wp_core_load_commands_file (WpCore * self)
{
g_autoptr (GFile) file = NULL;
g_autoptr (GError) error = NULL;
g_autoptr (GFileInputStream) istream = NULL;
const gchar *filename;
filename = g_getenv ("WIREPLUMBER_CONFIG_FILE");
if (!filename)
filename = WIREPLUMBER_DEFAULT_CONFIG_FILE;
file = g_file_new_for_path (filename);
istream = g_file_read (file, NULL, &error);
if (!istream) {
g_propagate_error (&self->exit_error, error);
error = NULL;
g_main_loop_quit (self->loop);
return FALSE;
}
if (!wp_core_parse_commands_file (self, G_INPUT_STREAM (istream), &error)) {
g_propagate_prefixed_error (&self->exit_error, error, "Failed to read %s: ",
filename);
error = NULL;
g_main_loop_quit (self->loop);
return FALSE;
}
return TRUE;
}
static void
wp_core_handle_proxy (WpProxyRegistry *pr, WpProxy * proxy, WpCore * self)
{
WpPluginRegistry *plugin_registry = wp_object_get_interface (WP_OBJECT (self),
WP_TYPE_PLUGIN_REGISTRY);
g_return_if_fail (plugin_registry != NULL);
wp_plugin_registry_impl_invoke (plugin_registry, wp_plugin_handle_pw_proxy,
proxy);
}
static void
wp_core_init (WpCore * self)
{
WpPluginRegistryImpl *plugin_registry;
WpProxyRegistryImpl *proxy_registry;
WpSessionRegistryImpl *session_registry;
self->loop = g_main_loop_new (NULL, FALSE);
self->source = wp_loop_source_new ();
g_source_attach (self->source, NULL);
self->core = pw_core_new (wp_loop_source_get_loop (self->source), NULL, 0);
self->remote = pw_remote_new (self->core, NULL, 0);
pw_remote_add_listener (self->remote, &self->remote_listener, &remote_events,
self);
self->module_loader = wp_module_loader_new ();
proxy_registry = wp_proxy_registry_impl_new (self->remote);
wp_object_attach_interface_impl (WP_OBJECT (self), proxy_registry, NULL);
plugin_registry = wp_plugin_registry_impl_new ();
wp_object_attach_interface_impl (WP_OBJECT (self), plugin_registry, NULL);
session_registry = wp_session_registry_impl_new ();
wp_object_attach_interface_impl (WP_OBJECT (self), session_registry, NULL);
g_signal_connect (proxy_registry, "new-proxy-available",
(GCallback) wp_core_handle_proxy, self);
}
static void
wp_core_dispose (GObject * obj)
{
WpCore *self = WP_CORE (obj);
WpPluginRegistry *plugin_registry = NULL;
WpProxyRegistry *proxy_registry = NULL;
/* ensure all proxies and plugins are unrefed,
* so that the registries can be disposed */
plugin_registry = wp_object_get_interface (WP_OBJECT (self),
WP_TYPE_PLUGIN_REGISTRY);
wp_plugin_registry_impl_unload (WP_PLUGIN_REGISTRY_IMPL (plugin_registry));
proxy_registry = wp_object_get_interface (WP_OBJECT (self),
WP_TYPE_PROXY_REGISTRY);
wp_proxy_registry_impl_unload (WP_PROXY_REGISTRY_IMPL (proxy_registry));
}
static void
wp_core_finalize (GObject * obj)
{
WpCore *self = WP_CORE (obj);
g_clear_object (&self->module_loader);
spa_hook_remove (&self->remote_listener);
pw_remote_destroy (self->remote);
pw_core_destroy (self->core);
g_source_destroy (self->source);
g_source_unref (self->source);
g_main_loop_unref (self->loop);
g_warn_if_fail (self->exit_error == NULL);
g_clear_error (&self->exit_error);
}
static void
wp_core_class_init (WpCoreClass * klass)
{
GObjectClass * object_class = (GObjectClass *) klass;
object_class->dispose = wp_core_dispose;
object_class->finalize = wp_core_finalize;
}
static struct pw_core *
wp_core_get_pw_core (WpPipewireObjects *pwobj)
{
return WP_CORE (pwobj)->core;
}
static struct pw_remote *
wp_core_get_pw_remote (WpPipewireObjects *pwobj)
{
return WP_CORE (pwobj)->remote;
}
static void
wp_core_pw_objects_init (WpPipewireObjectsInterface * iface)
{
iface->get_pw_core = wp_core_get_pw_core;
iface->get_pw_remote = wp_core_get_pw_remote;
}
WpCore *
wp_core_get_instance (void)
{
static WpCore *instance = NULL;
if (G_UNLIKELY (!instance)) {
instance = g_object_new (wp_core_get_type (), NULL);
}
return instance;
}
static gboolean
wp_core_run_in_idle (WpCore * self)
{
if (!wp_core_load_commands_file (self)) goto out;
if (pw_remote_connect (self->remote) < 0) goto out;
out:
return G_SOURCE_REMOVE;
}
void
wp_core_run (WpCore * self, GError ** error)
{
g_unix_signal_add (SIGINT, signal_handler, self);
g_unix_signal_add (SIGTERM, signal_handler, self);
g_unix_signal_add (SIGHUP, signal_handler, self);
g_idle_add ((GSourceFunc) wp_core_run_in_idle, self);
g_main_loop_run (self->loop);
if (self->exit_error) {
g_propagate_error (error, self->exit_error);
self->exit_error = NULL;
}
}
void
wp_core_exit (WpCore * self, GQuark domain, gint code,
const gchar *format, ...)
{
va_list args;
va_start (args, format);
self->exit_error = g_error_new_valist (domain, code, format, args);
va_end (args);
g_main_loop_quit (self->loop);
}

View File

@@ -1,26 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WIREPLUMBER_CORE_H__
#define __WIREPLUMBER_CORE_H__
#include <wp/object.h>
G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (WpCore, wp_core, WP, CORE, WpObject);
WpCore * wp_core_get_instance (void);
void wp_core_run (WpCore * self, GError ** error);
void wp_core_exit (WpCore * self, GQuark domain, gint code,
const gchar *format, ...);
G_END_DECLS
#endif

View File

@@ -1,28 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WIREPLUMBER_LOOP_SOURCE_H__
#define __WIREPLUMBER_LOOP_SOURCE_H__
#include <glib.h>
#include <pipewire/pipewire.h>
G_BEGIN_DECLS
/*
* A GSource that integrates a pw_loop with GMainLoop.
* Use g_source_ref/unref to manage lifetime.
* The pw_loop is owned by the GSource.
*/
GSource * wp_loop_source_new (void);
struct pw_loop * wp_loop_source_get_loop (GSource * s);
G_END_DECLS
#endif

View File

@@ -6,40 +6,221 @@
* SPDX-License-Identifier: LGPL-2.1-or-later * SPDX-License-Identifier: LGPL-2.1-or-later
*/ */
#include "core.h" #include <wp/wp.h>
#include "utils.h" #include <gio/gio.h>
#include <glib-unix.h>
#include <pipewire/pipewire.h> #define WIREPLUMBER_DEFAULT_CONFIG_FILE "wireplumber.conf"
static GOptionEntry entries[] = static GOptionEntry entries[] =
{ {
{ NULL } { NULL }
}; };
#define WP_DOMAIN_DAEMON (wp_domain_daemon_quark ())
static G_DEFINE_QUARK (wireplumber-daemon, wp_domain_daemon);
enum WpExitCode
{
WP_CODE_DISCONNECTED = 0,
WP_CODE_INTERRUPTED,
WP_CODE_OPERATION_FAILED,
WP_CODE_INVALID_ARGUMENT,
};
struct WpDaemonData
{
WpCore *core;
GMainLoop *loop;
gint exit_code;
gchar *exit_message;
GDestroyNotify free_message;
};
static void
daemon_exit (struct WpDaemonData * d, gint code, const gchar *format, ...)
{
va_list args;
va_start (args, format);
d->exit_code = code;
d->exit_message = g_strdup_vprintf (format, args);
d->free_message = g_free;
va_end (args);
g_main_loop_quit (d->loop);
}
static void
daemon_exit_static_str (struct WpDaemonData * d, gint code, const gchar *str)
{
d->exit_code = code;
d->exit_message = (gchar *) str;
d->free_message = NULL;
g_main_loop_quit (d->loop);
}
static gboolean
signal_handler (gpointer data)
{
struct WpDaemonData *d = data;
daemon_exit_static_str (d, WP_CODE_INTERRUPTED, "interrupted by signal");
return G_SOURCE_CONTINUE;
}
static gboolean
parse_commands_file (struct WpDaemonData *d, GInputStream * stream,
GError ** error)
{
gchar buffer[4096];
gssize bytes_read;
gchar *cur, *linestart, *saveptr;
gchar *cmd, *abi, *module;
gint lineno = 1;
gboolean eof = FALSE;
linestart = cur = buffer;
do {
bytes_read = g_input_stream_read (stream, cur, sizeof (buffer), NULL, error);
if (bytes_read < 0)
return FALSE;
else if (bytes_read == 0) {
eof = TRUE;
/* terminate the remaining data, so that we consume it all */
if (cur != linestart) {
*cur = '\n';
}
}
bytes_read += (cur - linestart);
while (cur - buffer < bytes_read) {
while (cur - buffer < bytes_read && *cur != '\n')
cur++;
if (*cur == '\n') {
/* found the end of a line */
*cur = '\0';
/* tokenize and execute */
cmd = strtok_r (linestart, " ", &saveptr);
if (!g_strcmp0 (cmd, "load-module")) {
abi = strtok_r (NULL, " ", &saveptr);
module = strtok_r (NULL, " ", &saveptr);
if (!abi || !module) {
g_set_error (error, WP_DOMAIN_DAEMON, WP_CODE_INVALID_ARGUMENT,
"expected ABI and MODULE at line %i", lineno);
return FALSE;
}
if (!wp_module_load (d->core, abi, module, NULL, error)) {
return FALSE;
}
} else {
g_set_error (error, WP_DOMAIN_DAEMON, WP_CODE_INVALID_ARGUMENT,
"unknown command '%s' at line %i", cmd, lineno);
return FALSE;
}
/* continue with the next line */
linestart = ++cur;
lineno++;
}
}
/* reached the end of the data that was read */
if (cur - linestart >= sizeof (buffer)) {
g_set_error (error, WP_DOMAIN_DAEMON, WP_CODE_OPERATION_FAILED,
"line %i exceeds the maximum allowed line size (%d bytes)",
lineno, (gint) sizeof (buffer));
return FALSE;
} else if (cur - linestart > 0) {
/* we have unparsed data, move it to the
* beginning of the buffer and continue */
strncpy (buffer, linestart, cur - linestart);
linestart = buffer;
cur = buffer + (cur - linestart);
}
} while (!eof);
return TRUE;
}
static gboolean
load_commands_file (struct WpDaemonData *d)
{
g_autoptr (GFile) file = NULL;
g_autoptr (GError) error = NULL;
g_autoptr (GFileInputStream) istream = NULL;
const gchar *filename;
filename = g_getenv ("WIREPLUMBER_CONFIG_FILE");
if (!filename)
filename = WIREPLUMBER_DEFAULT_CONFIG_FILE;
file = g_file_new_for_path (filename);
istream = g_file_read (file, NULL, &error);
if (!istream) {
daemon_exit (d, WP_CODE_INVALID_ARGUMENT, "%s", error->message);
return G_SOURCE_REMOVE;
}
if (!parse_commands_file (d, G_INPUT_STREAM (istream), &error)) {
daemon_exit (d, error->code, "Failed to read '%s': %s", filename,
error->message);
return G_SOURCE_REMOVE;
}
return G_SOURCE_REMOVE;
}
gint gint
main (gint argc, gchar **argv) main (gint argc, gchar **argv)
{ {
struct WpDaemonData data = {0};
g_autoptr (GOptionContext) context = NULL; g_autoptr (GOptionContext) context = NULL;
g_autoptr (GError) error = NULL; g_autoptr (GError) error = NULL;
g_autoptr (WpCore) core = NULL; g_autoptr (WpCore) core = NULL;
gint ret = 0; g_autoptr (GMainLoop) loop = NULL;
context = g_option_context_new ("- PipeWire Session/Policy Manager"); context = g_option_context_new ("- PipeWire Session/Policy Manager");
g_option_context_add_main_entries (context, entries, NULL); g_option_context_add_main_entries (context, entries, NULL);
if (!g_option_context_parse (context, &argc, &argv, &error)) if (!g_option_context_parse (context, &argc, &argv, &error)) {
data.exit_message = error->message;
data.exit_code = WP_CODE_INVALID_ARGUMENT;
goto out; goto out;
}
pw_init (NULL, NULL); /* init wireplumber */
core = wp_core_get_instance (); data.core = core = wp_core_new ();
wp_core_run (core, &error);
wp_core_register_global (core, WP_GLOBAL_SESSION_MANAGER,
wp_session_manager_new (), g_object_unref);
/* init main loop */
data.loop = loop = g_main_loop_new (NULL, FALSE);
/* watch for exit signals */
g_unix_signal_add (SIGINT, signal_handler, &data);
g_unix_signal_add (SIGTERM, signal_handler, &data);
g_unix_signal_add (SIGHUP, signal_handler, &data);
/* run */
g_idle_add ((GSourceFunc) load_commands_file, &data);
g_main_loop_run (data.loop);
out: out:
if (error) { if (data.exit_message) {
ret = error->code; g_message ("%s", data.exit_message);
if (error->domain != WP_DOMAIN_CORE) if (data.free_message)
ret += 100; data.free_message (data.exit_message);
g_message ("exit code %d; %s", ret, error->message);
} }
return ret; return data.exit_code;
} }

View File

@@ -1,12 +1,5 @@
wp_sources = [ wp_sources = [
'core.c',
'loop-source.c',
'main.c', 'main.c',
'module-loader.c',
'plugin-registry-impl.c',
'proxy-registry-impl.c',
'session-registry-impl.c',
'utils.c',
] ]
executable('wireplumber', executable('wireplumber',
@@ -17,5 +10,5 @@ executable('wireplumber',
'-DG_LOG_DOMAIN="wireplumber"' '-DG_LOG_DOMAIN="wireplumber"'
], ],
install: true, install: true,
dependencies : [gobject_dep, gmodule_dep, gio_dep, pipewire_dep, wp_dep], dependencies : [gobject_dep, gio_dep, wp_dep],
) )

View File

@@ -1,81 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "module-loader.h"
#include "utils.h"
#include <wp/plugin.h>
#include <gmodule.h>
struct _WpModuleLoader
{
GObject parent;
const gchar *module_dir;
};
G_DEFINE_TYPE (WpModuleLoader, wp_module_loader, G_TYPE_OBJECT);
static void
wp_module_loader_init (WpModuleLoader * self)
{
self->module_dir = g_getenv ("WIREPLUMBER_MODULE_DIR");
}
static void
wp_module_loader_class_init (WpModuleLoaderClass * klass)
{
}
WpModuleLoader *
wp_module_loader_new (void)
{
return g_object_new (wp_module_loader_get_type (), NULL);
}
static gboolean
wp_module_loader_load_c (WpModuleLoader * self, WpPluginRegistry * registry,
const gchar * module_name, GError ** error)
{
g_autofree gchar *module_path = NULL;
GModule *module;
gpointer module_init;
typedef void (*WpModuleInitFunc)(WpPluginRegistry *);
module_path = g_module_build_path (self->module_dir, module_name);
module = g_module_open (module_path, G_MODULE_BIND_LOCAL);
if (!module) {
g_set_error (error, WP_DOMAIN_CORE, WP_CODE_OPERATION_FAILED,
"Failed to open module %s: %s", module_path, g_module_error ());
return FALSE;
}
if (!g_module_symbol (module, G_STRINGIFY (WP_MODULE_INIT_SYMBOL),
&module_init)) {
g_set_error (error, WP_DOMAIN_CORE, WP_CODE_OPERATION_FAILED,
"Failed to locate symbol " G_STRINGIFY (WP_MODULE_INIT_SYMBOL) " in %s",
module_path);
g_module_close (module);
return FALSE;
}
((WpModuleInitFunc) module_init) (registry);
return TRUE;
}
gboolean
wp_module_loader_load (WpModuleLoader * self, WpPluginRegistry * registry,
const gchar * abi, const gchar * module_name, GError ** error)
{
if (!g_strcmp0 (abi, "C")) {
return wp_module_loader_load_c (self, registry, module_name, error);
} else {
g_set_error (error, WP_DOMAIN_CORE, WP_CODE_INVALID_ARGUMENT,
"unknown module ABI %s", abi);
return FALSE;
}
}

View File

@@ -1,27 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WIREPLUMBER_MODULE_LOADER_H__
#define __WIREPLUMBER_MODULE_LOADER_H__
#include <glib-object.h>
#include <wp/core-interfaces.h>
G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (WpModuleLoader, wp_module_loader, WP, MODULE_LOADER, GObject)
WpModuleLoader * wp_module_loader_new (void);
gboolean wp_module_loader_load (WpModuleLoader * self,
WpPluginRegistry * registry, const gchar * abi, const gchar * module_name,
GError ** error);
G_END_DECLS
#endif

View File

@@ -1,164 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "plugin-registry-impl.h"
#include <wp/plugin.h>
typedef struct {
gsize block_size;
GType gtype;
const WpPluginMetadata *metadata;
WpPlugin *instance;
} PluginData;
struct _WpPluginRegistryImpl
{
WpInterfaceImpl parent;
GList *plugins;
GStringChunk *metadata_strings;
};
static void wp_plugin_registry_impl_iface_init (WpPluginRegistryInterface * iface);
G_DEFINE_TYPE_WITH_CODE (WpPluginRegistryImpl, wp_plugin_registry_impl, WP_TYPE_INTERFACE_IMPL,
G_IMPLEMENT_INTERFACE (WP_TYPE_PLUGIN_REGISTRY, wp_plugin_registry_impl_iface_init);)
static void
wp_plugin_registry_impl_init (WpPluginRegistryImpl * self)
{
self->metadata_strings = g_string_chunk_new (200);
}
static void
plugin_data_free (PluginData *data)
{
g_slice_free1 (data->block_size, data);
}
static void
wp_plugin_registry_impl_finalize (GObject * object)
{
WpPluginRegistryImpl *self = WP_PLUGIN_REGISTRY_IMPL (object);
g_list_free_full (self->plugins, (GDestroyNotify) plugin_data_free);
g_string_chunk_free (self->metadata_strings);
G_OBJECT_CLASS (wp_plugin_registry_impl_parent_class)->finalize (object);
}
static void
wp_plugin_registry_impl_class_init (WpPluginRegistryImplClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->finalize = wp_plugin_registry_impl_finalize;
}
static gint
compare_ranks (const WpPluginMetadata * a, const WpPluginMetadata * b)
{
return (gint) b->rank - (gint) a->rank;
}
static void
wp_plugin_registry_impl_register_plugin (WpPluginRegistry * r,
GType plugin_type,
const WpPluginMetadata * metadata,
gsize metadata_size,
gboolean static_data)
{
WpPluginRegistryImpl *self = WP_PLUGIN_REGISTRY_IMPL (r);
PluginData *data;
g_return_if_fail (metadata_size == sizeof (WpPluginMetadata));
if (static_data) {
data = g_slice_alloc (sizeof (PluginData));
data->block_size = sizeof (PluginData);
} else {
data = g_slice_alloc (sizeof (PluginData) + sizeof (WpPluginMetadata));
data->block_size = sizeof (PluginData) + sizeof (WpPluginMetadata);
}
data->gtype = plugin_type;
data->instance = NULL;
if (!static_data) {
WpPluginMetadata *m;
m = (WpPluginMetadata *) ((guint8 *) data) + sizeof (PluginData);
m->rank = metadata->rank;
m->name = g_string_chunk_insert (self->metadata_strings, metadata->name);
m->description = g_string_chunk_insert (self->metadata_strings,
metadata->description);
m->author = g_string_chunk_insert (self->metadata_strings,
metadata->author);
m->license = g_string_chunk_insert (self->metadata_strings,
metadata->license);
m->version = g_string_chunk_insert (self->metadata_strings,
metadata->version);
m->origin = g_string_chunk_insert (self->metadata_strings,
metadata->origin);
data->metadata = m;
} else {
data->metadata = metadata;
}
self->plugins = g_list_insert_sorted (self->plugins, data,
(GCompareFunc) compare_ranks);
}
static void
wp_plugin_registry_impl_iface_init (WpPluginRegistryInterface * iface)
{
iface->register_plugin = wp_plugin_registry_impl_register_plugin;
}
WpPluginRegistryImpl *
wp_plugin_registry_impl_new (void)
{
return g_object_new (wp_plugin_registry_impl_get_type (), NULL);
}
void
wp_plugin_registry_impl_unload (WpPluginRegistryImpl * self)
{
GList *list;
PluginData *plugin_data;
for (list = self->plugins; list != NULL; list = g_list_next (list)) {
plugin_data = list->data;
g_clear_object (&plugin_data->instance);
}
}
static inline void
make_plugin (WpPluginRegistryImpl * self, PluginData * plugin_data)
{
WpObject *core = wp_interface_impl_get_object (WP_INTERFACE_IMPL (self));
plugin_data->instance = g_object_new (plugin_data->gtype,
"core", core, "metadata", plugin_data->metadata, NULL);
}
gboolean
wp_plugin_registry_impl_invoke_internal (WpPluginRegistryImpl * self,
WpPluginFunc func, gpointer data)
{
GList *list;
PluginData *plugin_data;
for (list = self->plugins; list != NULL; list = g_list_next (list)) {
plugin_data = list->data;
if (!plugin_data->instance)
make_plugin (self, plugin_data);
if (func (plugin_data->instance, data))
return TRUE;
}
return FALSE;
}

View File

@@ -1,37 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WP_PLUGIN_REGISTRY_IMPL_H__
#define __WP_PLUGIN_REGISTRY_IMPL_H__
#include <wp/core-interfaces.h>
G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (WpPluginRegistryImpl, wp_plugin_registry_impl,
WP, PLUGIN_REGISTRY_IMPL, WpInterfaceImpl)
WpPluginRegistryImpl * wp_plugin_registry_impl_new (void);
void wp_plugin_registry_impl_unload (WpPluginRegistryImpl * self);
typedef gboolean (*WpPluginFunc) (gpointer plugin, gpointer data);
gboolean wp_plugin_registry_impl_invoke_internal (WpPluginRegistryImpl * self,
WpPluginFunc func, gpointer data);
#define wp_plugin_registry_impl_invoke(r, func, data) \
G_STMT_START { \
(0 ? func ((WpPlugin *) NULL, data) : \
wp_plugin_registry_impl_invoke_internal ( \
WP_PLUGIN_REGISTRY_IMPL (r), (WpPluginFunc) func, \
(gpointer) data)); \
} G_STMT_END
G_END_DECLS
#endif

View File

@@ -1,281 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "proxy-registry-impl.h"
#include "plugin-registry-impl.h"
#include <wp/proxy.h>
#include <wp/plugin.h>
#include <pipewire/pipewire.h>
#include <pipewire/map.h>
struct _WpProxyRegistryImpl
{
WpInterfaceImpl parent;
struct pw_remote *remote;
struct spa_hook remote_listener;
struct pw_registry_proxy *reg_proxy;
struct spa_hook reg_proxy_listener;
struct pw_map globals;
GArray *new_globals;
};
enum {
PROP_0,
PROP_REMOTE,
};
enum {
SIGNAL_NEW_PROXY_AVAILABLE,
N_SIGNALS
};
static guint signals[N_SIGNALS];
static void wp_proxy_registry_impl_iface_init (WpProxyRegistryInterface * iface);
G_DEFINE_TYPE_WITH_CODE (WpProxyRegistryImpl, wp_proxy_registry_impl, WP_TYPE_INTERFACE_IMPL,
G_IMPLEMENT_INTERFACE (WP_TYPE_PROXY_REGISTRY, wp_proxy_registry_impl_iface_init);)
static gint
guint32_compare (const guint32 *a, const guint32 *b)
{
return (gint) ((gint64)*a - (gint64)*b);
}
static gboolean
idle_notify_new_globals (gpointer data)
{
WpProxyRegistryImpl *self = WP_PROXY_REGISTRY_IMPL (data);
guint i;
guint32 id;
// TODO verify these globals still exist
g_array_sort (self->new_globals, (GCompareFunc) guint32_compare);
for (i = 0; i < self->new_globals->len; i++) {
id = g_array_index (self->new_globals, guint32, i);
g_signal_emit (self, signals[SIGNAL_NEW_PROXY_AVAILABLE], 0,
pw_map_lookup (&self->globals, id));
}
g_array_remove_range (self->new_globals, 0, self->new_globals->len);
return G_SOURCE_REMOVE;
}
static inline void
map_insert (struct pw_map *map, guint32 id, gpointer obj)
{
size_t size = pw_map_get_size (map);
while (id > size)
pw_map_insert_at (map, size++, NULL);
pw_map_insert_at (map, id, obj);
}
static void
registry_global (void * data, uint32_t id, uint32_t parent_id,
uint32_t permissions, uint32_t type, uint32_t version,
const struct spa_dict * props)
{
WpProxyRegistryImpl *self = WP_PROXY_REGISTRY_IMPL (data);
WpProxy *proxy;
WpPluginRegistry *plugin_registry = NULL;
WpObject *core = wp_interface_impl_get_object (WP_INTERFACE_IMPL (self));
proxy = g_object_new (WP_TYPE_PROXY,
"id", id,
"parent-id", parent_id,
"spa-type", type,
"initial-properties", props,
"core", core,
NULL);
map_insert (&self->globals, id, proxy);
plugin_registry = wp_interface_impl_get_sibling (WP_INTERFACE_IMPL (self),
WP_TYPE_PLUGIN_REGISTRY);
wp_plugin_registry_impl_invoke (plugin_registry,
wp_plugin_provide_interfaces, WP_OBJECT (proxy));
/*
* defer notifications until we return to the main loop;
* this allows the pipewire event loop to finish emitting
* all new available globals before we use them
*/
if (self->new_globals->len == 0)
g_idle_add (idle_notify_new_globals, self);
g_array_append_val (self->new_globals, id);
}
static void
registry_global_remove (void * data, uint32_t id)
{
WpProxyRegistryImpl *self = WP_PROXY_REGISTRY_IMPL (data);
GObject *p = pw_map_lookup (&self->globals, id);
g_object_unref (p);
pw_map_insert_at (&self->globals, id, NULL);
}
static const struct pw_registry_proxy_events registry_events = {
PW_VERSION_REGISTRY_PROXY_EVENTS,
.global = registry_global,
.global_remove = registry_global_remove,
};
static void
remote_state_changed (void * data, enum pw_remote_state old_state,
enum pw_remote_state new_state, const char * error)
{
WpProxyRegistryImpl *self = WP_PROXY_REGISTRY_IMPL (data);
switch (new_state) {
case PW_REMOTE_STATE_CONNECTED:
self->reg_proxy = pw_core_proxy_get_registry (
pw_remote_get_core_proxy (self->remote),
PW_TYPE_INTERFACE_Registry, PW_VERSION_REGISTRY, 0);
pw_registry_proxy_add_listener (self->reg_proxy,
&self->reg_proxy_listener, &registry_events, self);
break;
case PW_REMOTE_STATE_UNCONNECTED:
self->reg_proxy = NULL;
break;
default:
break;
}
}
static const struct pw_remote_events remote_events = {
PW_VERSION_REMOTE_EVENTS,
.state_changed = remote_state_changed,
};
static void
wp_proxy_registry_impl_init (WpProxyRegistryImpl * self)
{
pw_map_init (&self->globals, 64, 64);
self->new_globals = g_array_sized_new (FALSE, FALSE, sizeof (guint32), 64);
}
static void
wp_proxy_registry_impl_constructed (GObject * obj)
{
WpProxyRegistryImpl *self = WP_PROXY_REGISTRY_IMPL (obj);
pw_remote_add_listener (self->remote, &self->remote_listener, &remote_events,
self);
G_OBJECT_CLASS (wp_proxy_registry_impl_parent_class)->constructed (obj);
}
static void
wp_proxy_registry_impl_finalize (GObject * obj)
{
WpProxyRegistryImpl *self = WP_PROXY_REGISTRY_IMPL (obj);
pw_map_clear (&self->globals);
g_array_unref (self->new_globals);
G_OBJECT_CLASS (wp_proxy_registry_impl_parent_class)->finalize (obj);
}
static void
wp_proxy_registry_impl_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpProxyRegistryImpl *self = WP_PROXY_REGISTRY_IMPL (object);
switch (property_id) {
case PROP_REMOTE:
self->remote = g_value_get_pointer (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_proxy_registry_impl_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpProxyRegistryImpl *self = WP_PROXY_REGISTRY_IMPL (object);
switch (property_id) {
case PROP_REMOTE:
g_value_set_pointer (value, self->remote);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_proxy_registry_impl_class_init (WpProxyRegistryImplClass * klass)
{
GObjectClass * object_class = (GObjectClass *) klass;
object_class->constructed = wp_proxy_registry_impl_constructed;
object_class->finalize = wp_proxy_registry_impl_finalize;
object_class->get_property = wp_proxy_registry_impl_get_property;
object_class->set_property = wp_proxy_registry_impl_set_property;
g_object_class_install_property (object_class, PROP_REMOTE,
g_param_spec_pointer ("remote", "remote",
"The underlying struct pw_remote *",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
signals[SIGNAL_NEW_PROXY_AVAILABLE] = g_signal_new ("new-proxy-available",
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 1, WP_TYPE_PROXY);
}
static WpProxy *
wp_proxy_registry_impl_get_proxy (WpProxyRegistry * r, guint32 global_id)
{
WpProxyRegistryImpl *self = WP_PROXY_REGISTRY_IMPL (r);
WpProxy *p = pw_map_lookup (&self->globals, global_id);
if (p)
g_object_ref (p);
return p;
}
static struct pw_registry_proxy *
wp_proxy_registry_impl_get_pw_registry_proxy (WpProxyRegistry * r)
{
WpProxyRegistryImpl *self = WP_PROXY_REGISTRY_IMPL (r);
return self->reg_proxy;
}
static void
wp_proxy_registry_impl_iface_init (WpProxyRegistryInterface * iface)
{
iface->get_proxy = wp_proxy_registry_impl_get_proxy;
iface->get_pw_registry_proxy = wp_proxy_registry_impl_get_pw_registry_proxy;
}
WpProxyRegistryImpl *
wp_proxy_registry_impl_new (struct pw_remote * remote)
{
return g_object_new (wp_proxy_registry_impl_get_type (), "remote", remote,
NULL);
}
void
wp_proxy_registry_impl_unload (WpProxyRegistryImpl * self)
{
size_t i, size = pw_map_get_size (&self->globals);
for (i = 0; i < size; i++) {
g_clear_object (&pw_map_get_item (&self->globals, i)->data);
}
}

View File

@@ -1,27 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WP_PROXY_REGISTRY_IMPL_H__
#define __WP_PROXY_REGISTRY_IMPL_H__
#include <wp/core-interfaces.h>
G_BEGIN_DECLS
struct pw_remote;
G_DECLARE_FINAL_TYPE (WpProxyRegistryImpl, wp_proxy_registry_impl,
WP, PROXY_REGISTRY_IMPL, WpInterfaceImpl)
WpProxyRegistryImpl * wp_proxy_registry_impl_new (struct pw_remote * remote);
void wp_proxy_registry_impl_unload (WpProxyRegistryImpl * self);
G_END_DECLS
#endif

View File

@@ -1,196 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "session-registry-impl.h"
#include "plugin-registry-impl.h"
#include "utils.h"
#include <wp/session.h>
#include <wp/plugin.h>
typedef struct
{
guint32 id;
gchar *media_class;
WpSession *session;
} SessionData;
struct _WpSessionRegistryImpl
{
WpInterfaceImpl parent;
guint32 next_id;
GArray *sessions;
};
static void wp_session_registry_impl_iface_init (WpSessionRegistryInterface * iface);
G_DEFINE_TYPE_WITH_CODE (WpSessionRegistryImpl, wp_session_registry_impl, WP_TYPE_INTERFACE_IMPL,
G_IMPLEMENT_INTERFACE (WP_TYPE_SESSION_REGISTRY, wp_session_registry_impl_iface_init);)
static void
wp_session_registry_impl_init (WpSessionRegistryImpl * self)
{
self->sessions = g_array_new (FALSE, FALSE, sizeof (SessionData));
}
static void
wp_session_registry_impl_finalize (GObject * obj)
{
WpSessionRegistryImpl * self = WP_SESSION_REGISTRY_IMPL (obj);
g_array_unref (self->sessions);
G_OBJECT_CLASS (wp_session_registry_impl_parent_class)->finalize (obj);
}
static void
wp_session_registry_impl_class_init (WpSessionRegistryImplClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->finalize = wp_session_registry_impl_finalize;
}
static gboolean
media_class_matches (const gchar * media_class, const gchar * lookup)
{
const gchar *c1 = media_class, *c2 = lookup;
/* empty lookup matches all classes */
if (!lookup)
return TRUE;
/* compare until we reach the end of the lookup string */
for (; *c2 != '\0'; c1++, c2++) {
if (*c1 != *c2)
return FALSE;
}
/* the lookup may not end in a slash, however it must match up
* to the end of a submedia_class. i.e.:
* OK: media_class: Audio/Source/Virtual/
* lookup: Audio/Source
*
* Not OK: media_class: Audio/Source/Virtual/
* lookup: Audio/Sou
*
* if *c1 is not /, also check the previous char, because the lookup
* may actually end in a slash.
*/
if (*c1 != '/' && *(c1 - 1) != '/')
return FALSE;
return TRUE;
}
static gchar *
sanitize_media_class (const gchar *media_class)
{
gsize len = strlen (media_class);
if (media_class[len-1] != '/')
return g_strdup_printf ("%s/", media_class);
else
return g_strdup (media_class);
}
static guint32
register_session (WpSessionRegistry * sr,
WpSession * session,
GError ** error)
{
WpSessionRegistryImpl * self = WP_SESSION_REGISTRY_IMPL (sr);
WpPluginRegistry *plugin_registry = NULL;
const gchar *media_class = NULL;
SessionData data;
plugin_registry = wp_interface_impl_get_sibling (WP_INTERFACE_IMPL (self),
WP_TYPE_PLUGIN_REGISTRY);
wp_plugin_registry_impl_invoke (plugin_registry,
wp_plugin_provide_interfaces, WP_OBJECT (session));
media_class = wp_session_get_media_class (session);
if (!media_class) {
g_set_error (error, WP_DOMAIN_CORE, WP_CODE_INVALID_ARGUMENT,
"session media_class is NULL");
return -1;
}
data.id = self->next_id++;
data.media_class = sanitize_media_class (media_class);
data.session = g_object_ref (session);
g_array_append_val (self->sessions, data);
return data.id;
}
static gboolean
unregister_session (WpSessionRegistry * sr, guint32 session_id)
{
WpSessionRegistryImpl * self = WP_SESSION_REGISTRY_IMPL (sr);
guint i;
for (i = 0; i < self->sessions->len; i++) {
SessionData *d = &g_array_index (self->sessions, SessionData, i);
if (session_id == d->id) {
g_free (d->media_class);
g_object_unref (d->session);
g_array_remove_index_fast (self->sessions, i);
return TRUE;
}
}
return FALSE;
}
static WpSession *
get_session (WpSessionRegistry * sr, guint32 session_id)
{
WpSessionRegistryImpl * self = WP_SESSION_REGISTRY_IMPL (sr);
guint i;
for (i = 0; i < self->sessions->len; i++) {
SessionData *d = &g_array_index (self->sessions, SessionData, i);
if (session_id == d->id)
return g_object_ref (d->session);
}
return NULL;
}
static GArray *
list_sessions (WpSessionRegistry * sr, const gchar * media_class)
{
WpSessionRegistryImpl * self = WP_SESSION_REGISTRY_IMPL (sr);
guint i;
GArray *ret;
ret = g_array_new (FALSE, FALSE, sizeof (guint32));
for (i = 0; i < self->sessions->len; i++) {
SessionData *d = &g_array_index (self->sessions, SessionData, i);
if (media_class_matches (d->media_class, media_class))
g_array_append_val (ret, d->id);
}
return ret;
}
static void
wp_session_registry_impl_iface_init (WpSessionRegistryInterface * iface)
{
iface->register_session = register_session;
iface->unregister_session = unregister_session;
iface->get_session = get_session;
iface->list_sessions = list_sessions;
}
WpSessionRegistryImpl *
wp_session_registry_impl_new (void)
{
return g_object_new (wp_session_registry_impl_get_type (), NULL);
}

View File

@@ -1,23 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WP_SESSION_REGISTRY_IMPL_H__
#define __WP_SESSION_REGISTRY_IMPL_H__
#include <wp/core-interfaces.h>
G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (WpSessionRegistryImpl, wp_session_registry_impl,
WP, SESSION_REGISTRY_IMPL, WpInterfaceImpl)
WpSessionRegistryImpl * wp_session_registry_impl_new (void);
G_END_DECLS
#endif

View File

@@ -1,29 +0,0 @@
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __WIREPLUMBER_UTILS_H__
#define __WIREPLUMBER_UTILS_H__
#include <glib.h>
G_BEGIN_DECLS
GQuark wp_domain_core_quark (void);
#define WP_DOMAIN_CORE (wp_domain_core_quark ())
enum WpCoreCode {
WP_CODE_DISCONNECTED = 0,
WP_CODE_INTERRUPTED,
WP_CODE_OPERATION_FAILED,
WP_CODE_INVALID_ARGUMENT,
WP_CODE_REMOTE_ERROR,
};
G_END_DECLS
#endif