proxy: refactor the proxy class to hide pipewire API and make things easier
This commit is contained in:
@@ -6,46 +6,29 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "error.h"
|
|
||||||
#include "proxy-link.h"
|
#include "proxy-link.h"
|
||||||
|
|
||||||
#include <pipewire/pipewire.h>
|
#include <pipewire/pipewire.h>
|
||||||
|
|
||||||
struct _WpProxyLink
|
struct _WpProxyLink
|
||||||
{
|
{
|
||||||
WpProxy parent;
|
WpProxy parent;
|
||||||
|
|
||||||
/* The task to signal the proxy is initialized */
|
|
||||||
GTask *init_task;
|
|
||||||
|
|
||||||
/* The link proxy listener */
|
/* The link proxy listener */
|
||||||
struct spa_hook listener;
|
struct spa_hook listener;
|
||||||
|
|
||||||
/* The link info */
|
|
||||||
struct pw_link_info *info;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void wp_proxy_link_async_initable_init (gpointer iface,
|
G_DEFINE_TYPE (WpProxyLink, wp_proxy_link, WP_TYPE_PROXY)
|
||||||
gpointer iface_data);
|
|
||||||
|
|
||||||
G_DEFINE_TYPE_WITH_CODE (WpProxyLink, wp_proxy_link, WP_TYPE_PROXY,
|
|
||||||
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
|
|
||||||
wp_proxy_link_async_initable_init))
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
link_event_info(void *data, const struct pw_link_info *info)
|
link_event_info(void *data, const struct pw_link_info *info)
|
||||||
{
|
{
|
||||||
WpProxyLink *self = data;
|
WpProxy *proxy = WP_PROXY (data);
|
||||||
|
|
||||||
/* Make sure the task is valid */
|
wp_proxy_update_native_info (proxy, info,
|
||||||
if (!self->init_task)
|
(WpProxyNativeInfoUpdate) pw_link_info_update,
|
||||||
return;
|
(GDestroyNotify) pw_link_info_free);
|
||||||
|
wp_proxy_set_feature_ready (proxy, WP_PROXY_FEATURE_INFO);
|
||||||
/* Update the link info */
|
|
||||||
self->info = pw_link_info_update(self->info, info);
|
|
||||||
|
|
||||||
/* Finish the creation of the proxy */
|
|
||||||
g_task_return_boolean (self->init_task, TRUE);
|
|
||||||
g_clear_object (&self->init_task);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct pw_link_proxy_events link_events = {
|
static const struct pw_link_proxy_events link_events = {
|
||||||
@@ -53,102 +36,23 @@ static const struct pw_link_proxy_events link_events = {
|
|||||||
.info = link_event_info,
|
.info = link_event_info,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
|
||||||
wp_proxy_link_finalize (GObject * object)
|
|
||||||
{
|
|
||||||
WpProxyLink *self = WP_PROXY_LINK(object);
|
|
||||||
|
|
||||||
/* Destroy the init task */
|
|
||||||
g_clear_object (&self->init_task);
|
|
||||||
|
|
||||||
/* Clear the info */
|
|
||||||
if (self->info) {
|
|
||||||
pw_link_info_free(self->info);
|
|
||||||
self->info = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
G_OBJECT_CLASS (wp_proxy_link_parent_class)->finalize (object);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
wp_proxy_link_destroy (WpProxy * proxy)
|
|
||||||
{
|
|
||||||
WpProxyLink *self = WP_PROXY_LINK(proxy);
|
|
||||||
GError *error = NULL;
|
|
||||||
|
|
||||||
/* Return error if the pipewire destruction happened while the async creation
|
|
||||||
* of this proxy link object has not finished */
|
|
||||||
if (self->init_task) {
|
|
||||||
g_set_error (&error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
|
||||||
"pipewire link proxy destroyed before finishing");
|
|
||||||
g_task_return_error (self->init_task, error);
|
|
||||||
g_clear_object (&self->init_task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
wp_proxy_link_init_async (GAsyncInitable *initable, int io_priority,
|
|
||||||
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
|
|
||||||
{
|
|
||||||
WpProxyLink *self = WP_PROXY_LINK(initable);
|
|
||||||
WpProxy *wp_proxy = WP_PROXY(initable);
|
|
||||||
struct pw_link_proxy *proxy = NULL;
|
|
||||||
|
|
||||||
/* Create the async task */
|
|
||||||
self->init_task = g_task_new (initable, cancellable, callback, data);
|
|
||||||
|
|
||||||
/* Get the proxy from the base class */
|
|
||||||
proxy = wp_proxy_get_pw_proxy(wp_proxy);
|
|
||||||
|
|
||||||
/* Add the link proxy listener */
|
|
||||||
pw_link_proxy_add_listener(proxy, &self->listener, &link_events, self);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
wp_proxy_link_async_initable_init (gpointer iface, gpointer iface_data)
|
|
||||||
{
|
|
||||||
GAsyncInitableIface *ai_iface = iface;
|
|
||||||
|
|
||||||
/* Only set the init_async */
|
|
||||||
ai_iface->init_async = wp_proxy_link_init_async;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
wp_proxy_link_init (WpProxyLink * self)
|
wp_proxy_link_init (WpProxyLink * self)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
wp_proxy_link_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy)
|
||||||
|
{
|
||||||
|
WpProxyLink *self = WP_PROXY_LINK (proxy);
|
||||||
|
pw_link_proxy_add_listener ((struct pw_link_proxy *) pw_proxy,
|
||||||
|
&self->listener, &link_events, self);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
wp_proxy_link_class_init (WpProxyLinkClass * klass)
|
wp_proxy_link_class_init (WpProxyLinkClass * klass)
|
||||||
{
|
{
|
||||||
GObjectClass *object_class = (GObjectClass *) klass;
|
|
||||||
WpProxyClass *proxy_class = (WpProxyClass *) klass;
|
WpProxyClass *proxy_class = (WpProxyClass *) klass;
|
||||||
|
|
||||||
object_class->finalize = wp_proxy_link_finalize;
|
proxy_class->pw_proxy_created = wp_proxy_link_pw_proxy_created;
|
||||||
|
|
||||||
proxy_class->destroy = wp_proxy_link_destroy;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
wp_proxy_link_new (guint global_id, gpointer proxy,
|
|
||||||
GAsyncReadyCallback callback, gpointer user_data)
|
|
||||||
{
|
|
||||||
g_async_initable_new_async (
|
|
||||||
WP_TYPE_PROXY_LINK, G_PRIORITY_DEFAULT, NULL, callback, user_data,
|
|
||||||
"global-id", global_id,
|
|
||||||
"pw-proxy", proxy,
|
|
||||||
NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
WpProxyLink *
|
|
||||||
wp_proxy_link_new_finish(GObject *initable, GAsyncResult *res, GError **error)
|
|
||||||
{
|
|
||||||
GAsyncInitable *ai = G_ASYNC_INITABLE(initable);
|
|
||||||
return WP_PROXY_LINK(g_async_initable_new_finish(ai, res, error));
|
|
||||||
}
|
|
||||||
|
|
||||||
const struct pw_link_info *
|
|
||||||
wp_proxy_link_get_info (WpProxyLink * self)
|
|
||||||
{
|
|
||||||
return self->info;
|
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,6 @@
|
|||||||
#ifndef __WIREPLUMBER_PROXY_LINK_H__
|
#ifndef __WIREPLUMBER_PROXY_LINK_H__
|
||||||
#define __WIREPLUMBER_PROXY_LINK_H__
|
#define __WIREPLUMBER_PROXY_LINK_H__
|
||||||
|
|
||||||
#include "core.h"
|
|
||||||
#include "proxy.h"
|
#include "proxy.h"
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
@@ -17,12 +16,12 @@ G_BEGIN_DECLS
|
|||||||
#define WP_TYPE_PROXY_LINK (wp_proxy_link_get_type ())
|
#define WP_TYPE_PROXY_LINK (wp_proxy_link_get_type ())
|
||||||
G_DECLARE_FINAL_TYPE (WpProxyLink, wp_proxy_link, WP, PROXY_LINK, WpProxy)
|
G_DECLARE_FINAL_TYPE (WpProxyLink, wp_proxy_link, WP, PROXY_LINK, WpProxy)
|
||||||
|
|
||||||
void wp_proxy_link_new (guint global_id, gpointer proxy,
|
static inline const struct pw_link_info *
|
||||||
GAsyncReadyCallback callback, gpointer user_data);
|
wp_proxy_link_get_info (WpProxyLink * self)
|
||||||
WpProxyLink *wp_proxy_link_new_finish(GObject *initable, GAsyncResult *res,
|
{
|
||||||
GError **error);
|
return (const struct pw_link_info *)
|
||||||
|
wp_proxy_get_native_info (WP_PROXY (self));
|
||||||
const struct pw_link_info *wp_proxy_link_get_info (WpProxyLink * self);
|
}
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
|
@@ -6,46 +6,29 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "error.h"
|
|
||||||
#include "proxy-node.h"
|
#include "proxy-node.h"
|
||||||
|
|
||||||
#include <pipewire/pipewire.h>
|
#include <pipewire/pipewire.h>
|
||||||
|
|
||||||
struct _WpProxyNode
|
struct _WpProxyNode
|
||||||
{
|
{
|
||||||
WpProxy parent;
|
WpProxy parent;
|
||||||
|
|
||||||
/* The task to signal the proxy is initialized */
|
|
||||||
GTask *init_task;
|
|
||||||
|
|
||||||
/* The node proxy listener */
|
/* The node proxy listener */
|
||||||
struct spa_hook listener;
|
struct spa_hook listener;
|
||||||
|
|
||||||
/* The node info */
|
|
||||||
struct pw_node_info *info;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void wp_proxy_node_async_initable_init (gpointer iface,
|
G_DEFINE_TYPE (WpProxyNode, wp_proxy_node, WP_TYPE_PROXY)
|
||||||
gpointer iface_data);
|
|
||||||
|
|
||||||
G_DEFINE_TYPE_WITH_CODE (WpProxyNode, wp_proxy_node, WP_TYPE_PROXY,
|
|
||||||
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
|
|
||||||
wp_proxy_node_async_initable_init))
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
node_event_info(void *data, const struct pw_node_info *info)
|
node_event_info(void *data, const struct pw_node_info *info)
|
||||||
{
|
{
|
||||||
WpProxyNode *self = data;
|
WpProxy *proxy = WP_PROXY (data);
|
||||||
|
|
||||||
/* Make sure the task is valid */
|
wp_proxy_update_native_info (proxy, info,
|
||||||
if (!self->init_task)
|
(WpProxyNativeInfoUpdate) pw_node_info_update,
|
||||||
return;
|
(GDestroyNotify) pw_node_info_free);
|
||||||
|
wp_proxy_set_feature_ready (proxy, WP_PROXY_FEATURE_INFO);
|
||||||
/* Update the node info */
|
|
||||||
self->info = pw_node_info_update(self->info, info);
|
|
||||||
|
|
||||||
/* Finish the creation of the proxy */
|
|
||||||
g_task_return_boolean (self->init_task, TRUE);
|
|
||||||
g_clear_object (&self->init_task);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct pw_node_proxy_events node_events = {
|
static const struct pw_node_proxy_events node_events = {
|
||||||
@@ -53,102 +36,23 @@ static const struct pw_node_proxy_events node_events = {
|
|||||||
.info = node_event_info,
|
.info = node_event_info,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
|
||||||
wp_proxy_node_finalize (GObject * object)
|
|
||||||
{
|
|
||||||
WpProxyNode *self = WP_PROXY_NODE(object);
|
|
||||||
|
|
||||||
/* Destroy the init task */
|
|
||||||
g_clear_object (&self->init_task);
|
|
||||||
|
|
||||||
/* Clear the info */
|
|
||||||
if (self->info) {
|
|
||||||
pw_node_info_free(self->info);
|
|
||||||
self->info = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
G_OBJECT_CLASS (wp_proxy_node_parent_class)->finalize (object);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
wp_proxy_node_destroy (WpProxy * proxy)
|
|
||||||
{
|
|
||||||
WpProxyNode *self = WP_PROXY_NODE(proxy);
|
|
||||||
GError *error = NULL;
|
|
||||||
|
|
||||||
/* Return error if the pipewire destruction happened while the async creation
|
|
||||||
* of this proxy node object has not finished */
|
|
||||||
if (self->init_task) {
|
|
||||||
g_set_error (&error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
|
||||||
"pipewire node proxy destroyed before finishing");
|
|
||||||
g_task_return_error (self->init_task, error);
|
|
||||||
g_clear_object (&self->init_task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
wp_proxy_node_init_async (GAsyncInitable *initable, int io_priority,
|
|
||||||
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
|
|
||||||
{
|
|
||||||
WpProxyNode *self = WP_PROXY_NODE(initable);
|
|
||||||
WpProxy *wp_proxy = WP_PROXY(initable);
|
|
||||||
struct pw_node_proxy *proxy = NULL;
|
|
||||||
|
|
||||||
/* Create the async task */
|
|
||||||
self->init_task = g_task_new (initable, cancellable, callback, data);
|
|
||||||
|
|
||||||
/* Get the proxy from the base class */
|
|
||||||
proxy = wp_proxy_get_pw_proxy(wp_proxy);
|
|
||||||
|
|
||||||
/* Add the node proxy listener */
|
|
||||||
pw_node_proxy_add_listener(proxy, &self->listener, &node_events, self);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
wp_proxy_node_async_initable_init (gpointer iface, gpointer iface_data)
|
|
||||||
{
|
|
||||||
GAsyncInitableIface *ai_iface = iface;
|
|
||||||
|
|
||||||
/* Only set the init_async */
|
|
||||||
ai_iface->init_async = wp_proxy_node_init_async;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
wp_proxy_node_init (WpProxyNode * self)
|
wp_proxy_node_init (WpProxyNode * self)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
wp_proxy_node_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy)
|
||||||
|
{
|
||||||
|
WpProxyNode *self = WP_PROXY_NODE (proxy);
|
||||||
|
pw_node_proxy_add_listener ((struct pw_node_proxy *) pw_proxy,
|
||||||
|
&self->listener, &node_events, self);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
wp_proxy_node_class_init (WpProxyNodeClass * klass)
|
wp_proxy_node_class_init (WpProxyNodeClass * klass)
|
||||||
{
|
{
|
||||||
GObjectClass *object_class = (GObjectClass *) klass;
|
|
||||||
WpProxyClass *proxy_class = (WpProxyClass *) klass;
|
WpProxyClass *proxy_class = (WpProxyClass *) klass;
|
||||||
|
|
||||||
object_class->finalize = wp_proxy_node_finalize;
|
proxy_class->pw_proxy_created = wp_proxy_node_pw_proxy_created;
|
||||||
|
|
||||||
proxy_class->destroy = wp_proxy_node_destroy;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
wp_proxy_node_new (guint global_id, gpointer proxy,
|
|
||||||
GAsyncReadyCallback callback, gpointer user_data)
|
|
||||||
{
|
|
||||||
g_async_initable_new_async (
|
|
||||||
WP_TYPE_PROXY_NODE, G_PRIORITY_DEFAULT, NULL, callback, user_data,
|
|
||||||
"global-id", global_id,
|
|
||||||
"pw-proxy", proxy,
|
|
||||||
NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
WpProxyNode *
|
|
||||||
wp_proxy_node_new_finish(GObject *initable, GAsyncResult *res, GError **error)
|
|
||||||
{
|
|
||||||
GAsyncInitable *ai = G_ASYNC_INITABLE(initable);
|
|
||||||
return WP_PROXY_NODE(g_async_initable_new_finish(ai, res, error));
|
|
||||||
}
|
|
||||||
|
|
||||||
const struct pw_node_info *
|
|
||||||
wp_proxy_node_get_info (WpProxyNode * self)
|
|
||||||
{
|
|
||||||
return self->info;
|
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,6 @@
|
|||||||
#ifndef __WIREPLUMBER_PROXY_NODE_H__
|
#ifndef __WIREPLUMBER_PROXY_NODE_H__
|
||||||
#define __WIREPLUMBER_PROXY_NODE_H__
|
#define __WIREPLUMBER_PROXY_NODE_H__
|
||||||
|
|
||||||
#include "core.h"
|
|
||||||
#include "proxy.h"
|
#include "proxy.h"
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
@@ -17,12 +16,12 @@ G_BEGIN_DECLS
|
|||||||
#define WP_TYPE_PROXY_NODE (wp_proxy_node_get_type ())
|
#define WP_TYPE_PROXY_NODE (wp_proxy_node_get_type ())
|
||||||
G_DECLARE_FINAL_TYPE (WpProxyNode, wp_proxy_node, WP, PROXY_NODE, WpProxy)
|
G_DECLARE_FINAL_TYPE (WpProxyNode, wp_proxy_node, WP, PROXY_NODE, WpProxy)
|
||||||
|
|
||||||
void wp_proxy_node_new (guint global_id, gpointer proxy,
|
static inline const struct pw_node_info *
|
||||||
GAsyncReadyCallback callback, gpointer user_data);
|
wp_proxy_node_get_info (WpProxyNode * self)
|
||||||
WpProxyNode *wp_proxy_node_new_finish(GObject *initable, GAsyncResult *res,
|
{
|
||||||
GError **error);
|
return (const struct pw_node_info *)
|
||||||
|
wp_proxy_get_native_info (WP_PROXY (self));
|
||||||
const struct pw_node_info *wp_proxy_node_get_info (WpProxyNode * self);
|
}
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
|
@@ -6,8 +6,8 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "error.h"
|
|
||||||
#include "proxy-port.h"
|
#include "proxy-port.h"
|
||||||
|
|
||||||
#include <pipewire/pipewire.h>
|
#include <pipewire/pipewire.h>
|
||||||
#include <spa/param/audio/format-utils.h>
|
#include <spa/param/audio/format-utils.h>
|
||||||
|
|
||||||
@@ -15,46 +15,33 @@ struct _WpProxyPort
|
|||||||
{
|
{
|
||||||
WpProxy parent;
|
WpProxy parent;
|
||||||
|
|
||||||
/* The task to signal the proxy is initialized */
|
|
||||||
GTask *init_task;
|
|
||||||
|
|
||||||
/* The port proxy listener */
|
/* The port proxy listener */
|
||||||
struct spa_hook listener;
|
struct spa_hook listener;
|
||||||
|
|
||||||
/* The port info */
|
|
||||||
struct pw_port_info *info;
|
|
||||||
|
|
||||||
/* The port format */
|
/* The port format */
|
||||||
uint32_t media_type;
|
uint32_t media_type;
|
||||||
uint32_t media_subtype;
|
uint32_t media_subtype;
|
||||||
struct spa_audio_info_raw format;
|
struct spa_audio_info_raw format;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void wp_proxy_port_async_initable_init (gpointer iface,
|
G_DEFINE_TYPE (WpProxyPort, wp_proxy_port, WP_TYPE_PROXY)
|
||||||
gpointer iface_data);
|
|
||||||
|
|
||||||
G_DEFINE_TYPE_WITH_CODE (WpProxyPort, wp_proxy_port, WP_TYPE_PROXY,
|
|
||||||
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
|
|
||||||
wp_proxy_port_async_initable_init))
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
port_event_info(void *data, const struct pw_port_info *info)
|
port_event_info(void *data, const struct pw_port_info *info)
|
||||||
{
|
{
|
||||||
WpProxyPort *self = data;
|
WpProxy *proxy = WP_PROXY (data);
|
||||||
|
|
||||||
/* Update the port info */
|
wp_proxy_update_native_info (proxy, info,
|
||||||
self->info = pw_port_info_update(self->info, info);
|
(WpProxyNativeInfoUpdate) pw_port_info_update,
|
||||||
|
(GDestroyNotify) pw_port_info_free);
|
||||||
|
wp_proxy_set_feature_ready (proxy, WP_PROXY_FEATURE_INFO);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
port_event_param(void *data, int seq, uint32_t id, uint32_t index,
|
port_event_param(void *data, int seq, uint32_t id, uint32_t index,
|
||||||
uint32_t next, const struct spa_pod *param)
|
uint32_t next, const struct spa_pod *param)
|
||||||
{
|
{
|
||||||
WpProxyPort *self = data;
|
WpProxyPort *self = WP_PROXY_PORT (data);
|
||||||
|
|
||||||
/* Make sure the task is valid */
|
|
||||||
if (!self->init_task)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* Only handle EnumFormat */
|
/* Only handle EnumFormat */
|
||||||
if (id != SPA_PARAM_EnumFormat)
|
if (id != SPA_PARAM_EnumFormat)
|
||||||
@@ -64,17 +51,14 @@ port_event_param(void *data, int seq, uint32_t id, uint32_t index,
|
|||||||
spa_format_parse(param, &self->media_type, &self->media_subtype);
|
spa_format_parse(param, &self->media_type, &self->media_subtype);
|
||||||
|
|
||||||
/* Only handle raw audio formats for now */
|
/* Only handle raw audio formats for now */
|
||||||
if (self->media_type != SPA_MEDIA_TYPE_audio ||
|
if (self->media_type == SPA_MEDIA_TYPE_audio &&
|
||||||
self->media_subtype != SPA_MEDIA_SUBTYPE_raw)
|
self->media_subtype == SPA_MEDIA_SUBTYPE_raw) {
|
||||||
return;
|
|
||||||
|
|
||||||
/* Parse the raw audio format */
|
/* Parse the raw audio format */
|
||||||
spa_pod_fixate ((struct spa_pod *) param);
|
spa_pod_fixate ((struct spa_pod *) param);
|
||||||
spa_format_audio_raw_parse (param, &self->format);
|
spa_format_audio_raw_parse (param, &self->format);
|
||||||
|
}
|
||||||
|
|
||||||
/* Finish the creation of the proxy */
|
wp_proxy_set_feature_ready (WP_PROXY (self), WP_PROXY_PORT_FEATURE_FORMAT);
|
||||||
g_task_return_boolean (self->init_task, TRUE);
|
|
||||||
g_clear_object (&self->init_task);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct pw_port_proxy_events port_events = {
|
static const struct pw_port_proxy_events port_events = {
|
||||||
@@ -83,108 +67,41 @@ static const struct pw_port_proxy_events port_events = {
|
|||||||
.param = port_event_param,
|
.param = port_event_param,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
|
||||||
wp_proxy_port_finalize (GObject * object)
|
|
||||||
{
|
|
||||||
WpProxyPort *self = WP_PROXY_PORT(object);
|
|
||||||
|
|
||||||
/* Destroy the init task */
|
|
||||||
g_clear_object (&self->init_task);
|
|
||||||
|
|
||||||
/* Clear the indo */
|
|
||||||
if (self->info) {
|
|
||||||
pw_port_info_free(self->info);
|
|
||||||
self->info = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
G_OBJECT_CLASS (wp_proxy_port_parent_class)->finalize (object);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
wp_proxy_port_destroy (WpProxy * proxy)
|
|
||||||
{
|
|
||||||
WpProxyPort *self = WP_PROXY_PORT(proxy);
|
|
||||||
GError *error = NULL;
|
|
||||||
|
|
||||||
/* Return error if the pipewire destruction happened while the async creation
|
|
||||||
* of this proxy port object has not finished */
|
|
||||||
if (self->init_task) {
|
|
||||||
g_set_error (&error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
|
||||||
"pipewire port proxy destroyed before finishing");
|
|
||||||
g_task_return_error (self->init_task, error);
|
|
||||||
g_clear_object (&self->init_task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
wp_proxy_port_init_async (GAsyncInitable *initable, int io_priority,
|
|
||||||
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
|
|
||||||
{
|
|
||||||
WpProxyPort *self = WP_PROXY_PORT(initable);
|
|
||||||
WpProxy *wp_proxy = WP_PROXY(initable);
|
|
||||||
struct pw_port_proxy *proxy = NULL;
|
|
||||||
|
|
||||||
/* Create the async task */
|
|
||||||
self->init_task = g_task_new (initable, cancellable, callback, data);
|
|
||||||
|
|
||||||
/* Get the proxy from the base class */
|
|
||||||
proxy = wp_proxy_get_pw_proxy(wp_proxy);
|
|
||||||
|
|
||||||
/* Add the port proxy listener */
|
|
||||||
pw_port_proxy_add_listener(proxy, &self->listener, &port_events, self);
|
|
||||||
|
|
||||||
/* Emit the EnumFormat param */
|
|
||||||
pw_port_proxy_enum_params((struct pw_port_proxy*)proxy, 0,
|
|
||||||
SPA_PARAM_EnumFormat, 0, -1, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
wp_proxy_port_async_initable_init (gpointer iface, gpointer iface_data)
|
|
||||||
{
|
|
||||||
GAsyncInitableIface *ai_iface = iface;
|
|
||||||
|
|
||||||
/* Only set the init_async */
|
|
||||||
ai_iface->init_async = wp_proxy_port_init_async;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
wp_proxy_port_init (WpProxyPort * self)
|
wp_proxy_port_init (WpProxyPort * self)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
wp_proxy_port_augment (WpProxy * proxy, WpProxyFeatures features)
|
||||||
|
{
|
||||||
|
/* call the default implementation to ensure we have a proxy, if necessary */
|
||||||
|
WP_PROXY_CLASS (wp_proxy_port_parent_class)->augment (proxy, features);
|
||||||
|
|
||||||
|
if (features & WP_PROXY_PORT_FEATURE_FORMAT) {
|
||||||
|
struct pw_proxy *pwp = wp_proxy_get_pw_proxy (proxy);
|
||||||
|
g_return_if_fail (pwp != NULL);
|
||||||
|
|
||||||
|
pw_port_proxy_enum_params ((struct pw_port_proxy *) pwp, 0,
|
||||||
|
SPA_PARAM_EnumFormat, 0, -1, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
wp_proxy_port_pw_proxy_created (WpProxy * proxy, struct pw_proxy * pw_proxy)
|
||||||
|
{
|
||||||
|
WpProxyPort *self = WP_PROXY_PORT (proxy);
|
||||||
|
pw_port_proxy_add_listener ((struct pw_port_proxy *) pw_proxy,
|
||||||
|
&self->listener, &port_events, self);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
wp_proxy_port_class_init (WpProxyPortClass * klass)
|
wp_proxy_port_class_init (WpProxyPortClass * klass)
|
||||||
{
|
{
|
||||||
GObjectClass *object_class = (GObjectClass *) klass;
|
|
||||||
WpProxyClass *proxy_class = (WpProxyClass *) klass;
|
WpProxyClass *proxy_class = (WpProxyClass *) klass;
|
||||||
|
|
||||||
object_class->finalize = wp_proxy_port_finalize;
|
proxy_class->augment = wp_proxy_port_augment;
|
||||||
|
proxy_class->pw_proxy_created = wp_proxy_port_pw_proxy_created;
|
||||||
proxy_class->destroy = wp_proxy_port_destroy;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
wp_proxy_port_new (guint global_id, gpointer proxy,
|
|
||||||
GAsyncReadyCallback callback, gpointer user_data)
|
|
||||||
{
|
|
||||||
g_async_initable_new_async (
|
|
||||||
WP_TYPE_PROXY_PORT, G_PRIORITY_DEFAULT, NULL, callback, user_data,
|
|
||||||
"global-id", global_id,
|
|
||||||
"pw-proxy", proxy,
|
|
||||||
NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
WpProxyPort *
|
|
||||||
wp_proxy_port_new_finish(GObject *initable, GAsyncResult *res, GError **error)
|
|
||||||
{
|
|
||||||
GAsyncInitable *ai = G_ASYNC_INITABLE(initable);
|
|
||||||
return WP_PROXY_PORT(g_async_initable_new_finish(ai, res, error));
|
|
||||||
}
|
|
||||||
|
|
||||||
const struct pw_port_info *
|
|
||||||
wp_proxy_port_get_info (WpProxyPort * self)
|
|
||||||
{
|
|
||||||
return self->info;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct spa_audio_info_raw *
|
const struct spa_audio_info_raw *
|
||||||
|
@@ -9,20 +9,24 @@
|
|||||||
#ifndef __WIREPLUMBER_PROXY_PORT_H__
|
#ifndef __WIREPLUMBER_PROXY_PORT_H__
|
||||||
#define __WIREPLUMBER_PROXY_PORT_H__
|
#define __WIREPLUMBER_PROXY_PORT_H__
|
||||||
|
|
||||||
#include "core.h"
|
|
||||||
#include "proxy.h"
|
#include "proxy.h"
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
typedef enum { /*< flags >*/
|
||||||
|
WP_PROXY_PORT_FEATURE_FORMAT = (WP_PROXY_FEATURE_LAST << 0),
|
||||||
|
} WpProxyPortFeatures;
|
||||||
|
|
||||||
#define WP_TYPE_PROXY_PORT (wp_proxy_port_get_type ())
|
#define WP_TYPE_PROXY_PORT (wp_proxy_port_get_type ())
|
||||||
G_DECLARE_FINAL_TYPE (WpProxyPort, wp_proxy_port, WP, PROXY_PORT, WpProxy)
|
G_DECLARE_FINAL_TYPE (WpProxyPort, wp_proxy_port, WP, PROXY_PORT, WpProxy)
|
||||||
|
|
||||||
void wp_proxy_port_new (guint global_id, gpointer proxy,
|
static inline const struct pw_port_info *
|
||||||
GAsyncReadyCallback callback, gpointer user_data);
|
wp_proxy_port_get_info (WpProxyPort * self)
|
||||||
WpProxyPort *wp_proxy_port_new_finish(GObject *initable, GAsyncResult *res,
|
{
|
||||||
GError **error);
|
return (const struct pw_port_info *)
|
||||||
|
wp_proxy_get_native_info (WP_PROXY (self));
|
||||||
|
}
|
||||||
|
|
||||||
const struct pw_port_info *wp_proxy_port_get_info (WpProxyPort * self);
|
|
||||||
const struct spa_audio_info_raw *wp_proxy_port_get_format (WpProxyPort * self);
|
const struct spa_audio_info_raw *wp_proxy_port_get_format (WpProxyPort * self);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
612
lib/wp/proxy.c
612
lib/wp/proxy.c
@@ -2,46 +2,130 @@
|
|||||||
*
|
*
|
||||||
* Copyright © 2019 Collabora Ltd.
|
* Copyright © 2019 Collabora Ltd.
|
||||||
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||||
|
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <pipewire/pipewire.h>
|
|
||||||
|
|
||||||
#include "proxy.h"
|
#include "proxy.h"
|
||||||
|
#include "error.h"
|
||||||
|
#include "remote-pipewire.h"
|
||||||
|
#include "wpenums.h"
|
||||||
|
|
||||||
|
#include "proxy-link.h"
|
||||||
|
#include "proxy-node.h"
|
||||||
|
#include "proxy-port.h"
|
||||||
|
|
||||||
|
#include <pipewire/pipewire.h>
|
||||||
|
#include <spa/debug/types.h>
|
||||||
|
|
||||||
typedef struct _WpProxyPrivate WpProxyPrivate;
|
typedef struct _WpProxyPrivate WpProxyPrivate;
|
||||||
struct _WpProxyPrivate
|
struct _WpProxyPrivate
|
||||||
{
|
{
|
||||||
/* The global id */
|
/* properties */
|
||||||
guint global_id;
|
GWeakRef remote;
|
||||||
|
|
||||||
/* The proxy */
|
guint32 global_id;
|
||||||
struct pw_proxy *proxy;
|
guint32 global_perm;
|
||||||
|
WpProperties *global_props;
|
||||||
|
|
||||||
|
guint32 iface_type;
|
||||||
|
guint32 iface_version;
|
||||||
|
|
||||||
|
struct pw_proxy *pw_proxy;
|
||||||
|
gpointer native_info;
|
||||||
|
GDestroyNotify native_info_destroy;
|
||||||
|
|
||||||
/* The proxy listener */
|
/* The proxy listener */
|
||||||
struct spa_hook listener;
|
struct spa_hook listener;
|
||||||
|
|
||||||
|
/* augment state */
|
||||||
|
WpProxyFeatures ft_ready;
|
||||||
|
WpProxyFeatures ft_wanted;
|
||||||
|
GTask *task;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
PROP_0,
|
PROP_0,
|
||||||
|
PROP_REMOTE,
|
||||||
PROP_GLOBAL_ID,
|
PROP_GLOBAL_ID,
|
||||||
PROP_PROXY,
|
PROP_GLOBAL_PERMISSIONS,
|
||||||
|
PROP_GLOBAL_PROPERTIES,
|
||||||
|
PROP_INTERFACE_TYPE,
|
||||||
|
PROP_INTERFACE_NAME,
|
||||||
|
PROP_INTERFACE_QUARK,
|
||||||
|
PROP_INTERFACE_VERSION,
|
||||||
|
PROP_PW_PROXY,
|
||||||
|
PROP_NATIVE_INFO,
|
||||||
|
PROP_FEATURES,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
SIGNAL_DONE,
|
SIGNAL_PW_PROXY_CREATED,
|
||||||
|
SIGNAL_PW_PROXY_DESTROYED,
|
||||||
LAST_SIGNAL,
|
LAST_SIGNAL,
|
||||||
};
|
};
|
||||||
|
|
||||||
static guint wp_proxy_signals[LAST_SIGNAL] = { 0 };
|
static guint wp_proxy_signals[LAST_SIGNAL] = { 0 };
|
||||||
|
|
||||||
static void wp_proxy_async_initable_init (gpointer iface, gpointer iface_data);
|
G_DEFINE_TYPE_WITH_PRIVATE (WpProxy, wp_proxy, G_TYPE_OBJECT)
|
||||||
|
|
||||||
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (WpProxy, wp_proxy, G_TYPE_OBJECT,
|
G_DEFINE_QUARK (core, wp_proxy_core)
|
||||||
G_ADD_PRIVATE (WpProxy)
|
G_DEFINE_QUARK (registry, wp_proxy_registry)
|
||||||
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, wp_proxy_async_initable_init))
|
G_DEFINE_QUARK (node, wp_proxy_node)
|
||||||
|
G_DEFINE_QUARK (port, wp_proxy_port)
|
||||||
|
G_DEFINE_QUARK (factory, wp_proxy_factory)
|
||||||
|
G_DEFINE_QUARK (link, wp_proxy_link)
|
||||||
|
G_DEFINE_QUARK (client, wp_proxy_client)
|
||||||
|
G_DEFINE_QUARK (module, wp_proxy_module)
|
||||||
|
G_DEFINE_QUARK (device, wp_proxy_device)
|
||||||
|
G_DEFINE_QUARK (client-node, wp_proxy_client_node)
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
/* the pipewire interface type */
|
||||||
|
guint32 pw_type;
|
||||||
|
/* the minimum interface version that the remote object must support */
|
||||||
|
guint32 req_version;
|
||||||
|
/* the _get_type() function of the subclass */
|
||||||
|
GType (*get_type) (void);
|
||||||
|
/* a function returning a quark that identifies the interface */
|
||||||
|
GQuark (*get_quark) (void);
|
||||||
|
} types_assoc[] = {
|
||||||
|
{ PW_TYPE_INTERFACE_Core, 0, wp_proxy_get_type, wp_proxy_core_quark },
|
||||||
|
{ PW_TYPE_INTERFACE_Registry, 0, wp_proxy_get_type, wp_proxy_registry_quark },
|
||||||
|
{ PW_TYPE_INTERFACE_Node, 0, wp_proxy_node_get_type, wp_proxy_node_quark },
|
||||||
|
{ PW_TYPE_INTERFACE_Port, 0, wp_proxy_port_get_type, wp_proxy_port_quark },
|
||||||
|
{ PW_TYPE_INTERFACE_Factory, 0, wp_proxy_get_type, wp_proxy_factory_quark },
|
||||||
|
{ PW_TYPE_INTERFACE_Link, 0, wp_proxy_link_get_type, wp_proxy_link_quark },
|
||||||
|
{ PW_TYPE_INTERFACE_Client, 0, wp_proxy_get_type, wp_proxy_client_quark },
|
||||||
|
{ PW_TYPE_INTERFACE_Module, 0, wp_proxy_get_type, wp_proxy_module_quark },
|
||||||
|
{ PW_TYPE_INTERFACE_Device, 0, wp_proxy_get_type, wp_proxy_device_quark },
|
||||||
|
{ PW_TYPE_INTERFACE_ClientNode, 0, wp_proxy_get_type, wp_proxy_client_node_quark },
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline GType
|
||||||
|
wp_proxy_find_instance_type (guint32 type, guint32 version)
|
||||||
|
{
|
||||||
|
for (gint i = 0; i < SPA_N_ELEMENTS (types_assoc); i++) {
|
||||||
|
if (types_assoc[i].pw_type == type &&
|
||||||
|
types_assoc[i].req_version <= version)
|
||||||
|
return types_assoc[i].get_type ();
|
||||||
|
}
|
||||||
|
|
||||||
|
return WP_TYPE_PROXY;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline GQuark
|
||||||
|
wp_proxy_find_quark_for_type (guint32 type)
|
||||||
|
{
|
||||||
|
for (gint i = 0; i < SPA_N_ELEMENTS (types_assoc); i++) {
|
||||||
|
if (types_assoc[i].pw_type == type)
|
||||||
|
return types_assoc[i].get_quark ();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
proxy_event_destroy (void *data)
|
proxy_event_destroy (void *data)
|
||||||
@@ -49,34 +133,46 @@ proxy_event_destroy (void *data)
|
|||||||
WpProxy *self = WP_PROXY (data);
|
WpProxy *self = WP_PROXY (data);
|
||||||
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
|
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
|
||||||
|
|
||||||
/* Set the proxy to NULL */
|
priv->pw_proxy = NULL;
|
||||||
priv->proxy = NULL;
|
|
||||||
|
|
||||||
/* Call the destroy method */
|
g_signal_emit (self, wp_proxy_signals[SIGNAL_PW_PROXY_DESTROYED], 0);
|
||||||
if (WP_PROXY_GET_CLASS (self)->destroy)
|
|
||||||
WP_PROXY_GET_CLASS (self)->destroy (self);
|
/* Return error if the pw_proxy destruction happened while the async
|
||||||
|
* init or augment of this proxy object was in progress */
|
||||||
|
if (priv->task) {
|
||||||
|
g_task_return_new_error (priv->task, WP_DOMAIN_LIBRARY,
|
||||||
|
WP_LIBRARY_ERROR_OPERATION_FAILED,
|
||||||
|
"pipewire node proxy destroyed before finishing");
|
||||||
|
g_clear_object (&priv->task);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
proxy_event_done (void *data, int seq)
|
|
||||||
{
|
|
||||||
/* Emit the done signal */
|
|
||||||
g_signal_emit (data, wp_proxy_signals[SIGNAL_DONE], 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct pw_proxy_events proxy_events = {
|
static const struct pw_proxy_events proxy_events = {
|
||||||
PW_VERSION_PROXY_EVENTS,
|
PW_VERSION_PROXY_EVENTS,
|
||||||
.destroy = proxy_event_destroy,
|
.destroy = proxy_event_destroy,
|
||||||
.done = proxy_event_done,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
wp_proxy_init (WpProxy * self)
|
||||||
|
{
|
||||||
|
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
|
||||||
|
g_weak_ref_init (&priv->remote, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
wp_proxy_constructed (GObject * object)
|
wp_proxy_constructed (GObject * object)
|
||||||
{
|
{
|
||||||
WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY(object));
|
WpProxy *self = WP_PROXY (object);
|
||||||
|
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
|
||||||
|
|
||||||
/* Add the event listener */
|
/* native proxy was passed in the constructor, declare it as ready */
|
||||||
pw_proxy_add_listener (priv->proxy, &priv->listener, &proxy_events, object);
|
if (priv->pw_proxy) {
|
||||||
|
priv->ft_ready |= WP_PROXY_FEATURE_PW_PROXY;
|
||||||
|
|
||||||
|
/* and inform the subclasses */
|
||||||
|
g_signal_emit (self, wp_proxy_signals[SIGNAL_PW_PROXY_CREATED], 0,
|
||||||
|
priv->pw_proxy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -84,14 +180,14 @@ wp_proxy_finalize (GObject * object)
|
|||||||
{
|
{
|
||||||
WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY(object));
|
WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY(object));
|
||||||
|
|
||||||
g_debug ("%s:%p destroyed (pw proxy %p)", G_OBJECT_TYPE_NAME (object),
|
g_debug ("%s:%p destroyed (global %u; pw_proxy %p)", G_OBJECT_TYPE_NAME (object),
|
||||||
object, priv->proxy);
|
object, priv->global_id, priv->pw_proxy);
|
||||||
|
|
||||||
/* Destroy the proxy */
|
g_clear_object (&priv->task);
|
||||||
if (priv->proxy) {
|
g_clear_pointer (&priv->global_props, wp_properties_unref);
|
||||||
pw_proxy_destroy (priv->proxy);
|
g_clear_pointer (&priv->native_info, priv->native_info_destroy);
|
||||||
priv->proxy = NULL;
|
g_clear_pointer (&priv->pw_proxy, pw_proxy_destroy);
|
||||||
}
|
g_weak_ref_clear (&priv->remote);
|
||||||
|
|
||||||
G_OBJECT_CLASS (wp_proxy_parent_class)->finalize (object);
|
G_OBJECT_CLASS (wp_proxy_parent_class)->finalize (object);
|
||||||
}
|
}
|
||||||
@@ -103,11 +199,29 @@ wp_proxy_set_property (GObject * object, guint property_id,
|
|||||||
WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY(object));
|
WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY(object));
|
||||||
|
|
||||||
switch (property_id) {
|
switch (property_id) {
|
||||||
|
case PROP_REMOTE:
|
||||||
|
g_weak_ref_set (&priv->remote, g_value_get_object (value));
|
||||||
|
break;
|
||||||
case PROP_GLOBAL_ID:
|
case PROP_GLOBAL_ID:
|
||||||
priv->global_id = g_value_get_uint (value);
|
priv->global_id = g_value_get_uint (value);
|
||||||
break;
|
break;
|
||||||
case PROP_PROXY:
|
case PROP_GLOBAL_PERMISSIONS:
|
||||||
priv->proxy = g_value_get_pointer (value);
|
priv->global_perm = g_value_get_uint (value);
|
||||||
|
break;
|
||||||
|
case PROP_GLOBAL_PROPERTIES:
|
||||||
|
priv->global_props = g_value_dup_boxed (value);
|
||||||
|
break;
|
||||||
|
case PROP_INTERFACE_TYPE:
|
||||||
|
priv->iface_type = g_value_get_uint (value);
|
||||||
|
break;
|
||||||
|
case PROP_INTERFACE_VERSION:
|
||||||
|
priv->iface_version = g_value_get_uint (value);
|
||||||
|
break;
|
||||||
|
case PROP_PW_PROXY:
|
||||||
|
priv->pw_proxy = g_value_get_pointer (value);
|
||||||
|
break;
|
||||||
|
case PROP_NATIVE_INFO:
|
||||||
|
priv->native_info = g_value_get_pointer (value);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
@@ -122,11 +236,39 @@ wp_proxy_get_property (GObject * object, guint property_id, GValue * value,
|
|||||||
WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY(object));
|
WpProxyPrivate *priv = wp_proxy_get_instance_private (WP_PROXY(object));
|
||||||
|
|
||||||
switch (property_id) {
|
switch (property_id) {
|
||||||
|
case PROP_REMOTE:
|
||||||
|
g_value_take_object (value, g_weak_ref_get (&priv->remote));
|
||||||
|
break;
|
||||||
case PROP_GLOBAL_ID:
|
case PROP_GLOBAL_ID:
|
||||||
g_value_set_uint (value, priv->global_id);
|
g_value_set_uint (value, priv->global_id);
|
||||||
break;
|
break;
|
||||||
case PROP_PROXY:
|
case PROP_GLOBAL_PERMISSIONS:
|
||||||
g_value_set_pointer (value, priv->proxy);
|
g_value_set_uint (value, priv->global_perm);
|
||||||
|
break;
|
||||||
|
case PROP_GLOBAL_PROPERTIES:
|
||||||
|
g_value_set_boxed (value, priv->global_props);
|
||||||
|
break;
|
||||||
|
case PROP_INTERFACE_TYPE:
|
||||||
|
g_value_set_uint (value, priv->iface_type);
|
||||||
|
break;
|
||||||
|
case PROP_INTERFACE_NAME:
|
||||||
|
g_value_set_static_string (value,
|
||||||
|
spa_debug_type_find_name (pw_type_info(), priv->iface_type));
|
||||||
|
break;
|
||||||
|
case PROP_INTERFACE_QUARK:
|
||||||
|
g_value_set_uint (value, wp_proxy_find_quark_for_type (priv->iface_type));
|
||||||
|
break;
|
||||||
|
case PROP_INTERFACE_VERSION:
|
||||||
|
g_value_set_uint (value, priv->iface_version);
|
||||||
|
break;
|
||||||
|
case PROP_PW_PROXY:
|
||||||
|
g_value_set_pointer (value, priv->pw_proxy);
|
||||||
|
break;
|
||||||
|
case PROP_NATIVE_INFO:
|
||||||
|
g_value_set_pointer (value, priv->native_info);
|
||||||
|
break;
|
||||||
|
case PROP_FEATURES:
|
||||||
|
g_value_set_flags (value, priv->ft_ready);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||||
@@ -134,27 +276,29 @@ wp_proxy_get_property (GObject * object, guint property_id, GValue * value,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static void
|
||||||
wp_proxy_init_finish (GAsyncInitable *initable, GAsyncResult *result,
|
wp_proxy_default_augment (WpProxy * self, WpProxyFeatures features)
|
||||||
GError **error)
|
|
||||||
{
|
{
|
||||||
g_return_val_if_fail (g_task_is_valid (result, initable), FALSE);
|
WpProxyPrivate *priv = wp_proxy_get_instance_private (self);
|
||||||
|
|
||||||
return g_task_propagate_boolean (G_TASK (result), error);
|
/* ensure we have a pw_proxy, as we can't have
|
||||||
|
* any other feature without first having that */
|
||||||
|
if (!priv->pw_proxy && features != 0)
|
||||||
|
features |= WP_PROXY_FEATURE_PW_PROXY;
|
||||||
|
|
||||||
|
/* if we don't have a pw_proxy, we have to assume that this WpProxy
|
||||||
|
* represents a global object from the registry; we have no other way
|
||||||
|
* to get a pw_proxy */
|
||||||
|
if (features & WP_PROXY_FEATURE_PW_PROXY) {
|
||||||
|
if (!wp_proxy_is_global (self)) {
|
||||||
|
wp_proxy_augment_error (self, g_error_new (WP_DOMAIN_LIBRARY,
|
||||||
|
WP_LIBRARY_ERROR_INVALID_ARGUMENT,
|
||||||
|
"No global id specified; cannot bind pw_proxy"));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
wp_proxy_bind_global (self);
|
||||||
wp_proxy_async_initable_init (gpointer iface, gpointer iface_data)
|
|
||||||
{
|
|
||||||
GAsyncInitableIface *ai_iface = iface;
|
|
||||||
|
|
||||||
/* The init_async must be implemented in the derived classes */
|
|
||||||
ai_iface->init_finish = wp_proxy_init_finish;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
wp_proxy_init (WpProxy * self)
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -167,22 +311,263 @@ wp_proxy_class_init (WpProxyClass * klass)
|
|||||||
object_class->get_property = wp_proxy_get_property;
|
object_class->get_property = wp_proxy_get_property;
|
||||||
object_class->set_property = wp_proxy_set_property;
|
object_class->set_property = wp_proxy_set_property;
|
||||||
|
|
||||||
|
klass->augment = wp_proxy_default_augment;
|
||||||
|
|
||||||
/* Install the properties */
|
/* Install the properties */
|
||||||
|
|
||||||
|
g_object_class_install_property (object_class, PROP_REMOTE,
|
||||||
|
g_param_spec_object ("remote", "remote",
|
||||||
|
"The pipewire connection WpRemote", WP_TYPE_REMOTE,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
g_object_class_install_property (object_class, PROP_GLOBAL_ID,
|
g_object_class_install_property (object_class, PROP_GLOBAL_ID,
|
||||||
g_param_spec_uint ("global-id", "global-id", "The pipewire global id",
|
g_param_spec_uint ("global-id", "global-id",
|
||||||
0, G_MAXUINT, 0,
|
"The pipewire global id", 0, G_MAXUINT, 0,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
g_object_class_install_property (object_class, PROP_GLOBAL_PERMISSIONS,
|
||||||
|
g_param_spec_uint ("global-permissions", "global-permissions",
|
||||||
|
"The pipewire global permissions", 0, G_MAXUINT, 0,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
g_object_class_install_property (object_class, PROP_GLOBAL_PROPERTIES,
|
||||||
|
g_param_spec_boxed ("global-properties", "global-properties",
|
||||||
|
"The pipewire global properties", WP_TYPE_PROPERTIES,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
g_object_class_install_property (object_class, PROP_INTERFACE_TYPE,
|
||||||
|
g_param_spec_uint ("interface-type", "interface-type",
|
||||||
|
"The pipewire interface type", 0, G_MAXUINT, 0,
|
||||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||||
g_object_class_install_property (object_class, PROP_PROXY,
|
|
||||||
g_param_spec_pointer ("pw-proxy", "pw-proxy", "The pipewire proxy",
|
g_object_class_install_property (object_class, PROP_INTERFACE_NAME,
|
||||||
|
g_param_spec_string ("interface-name", "interface-name",
|
||||||
|
"The name of the pipewire interface", NULL,
|
||||||
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
g_object_class_install_property (object_class, PROP_INTERFACE_QUARK,
|
||||||
|
g_param_spec_uint ("interface-quark", "interface-quark",
|
||||||
|
"A quark identifying the pipewire interface", 0, G_MAXUINT, 0,
|
||||||
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
g_object_class_install_property (object_class, PROP_INTERFACE_VERSION,
|
||||||
|
g_param_spec_uint ("interface-version", "interface-version",
|
||||||
|
"The pipewire interface version", 0, G_MAXUINT, 0,
|
||||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
g_object_class_install_property (object_class, PROP_PW_PROXY,
|
||||||
|
g_param_spec_pointer ("pw-proxy", "pw-proxy", "The struct pw_proxy *",
|
||||||
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
g_object_class_install_property (object_class, PROP_NATIVE_INFO,
|
||||||
|
g_param_spec_pointer ("native-info", "native-info",
|
||||||
|
"The struct pw_XXXX_info *",
|
||||||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
g_object_class_install_property (object_class, PROP_FEATURES,
|
||||||
|
g_param_spec_flags ("features", "features",
|
||||||
|
"The ready WpProxyFeatures on this proxy", WP_TYPE_PROXY_FEATURES, 0,
|
||||||
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
/* Signals */
|
/* Signals */
|
||||||
wp_proxy_signals[SIGNAL_DONE] =
|
wp_proxy_signals[SIGNAL_PW_PROXY_CREATED] = g_signal_new (
|
||||||
g_signal_new ("done", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
|
"pw-proxy-created", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
|
||||||
G_STRUCT_OFFSET (WpProxyClass, done), NULL, NULL, NULL, G_TYPE_NONE, 0);
|
G_STRUCT_OFFSET (WpProxyClass, pw_proxy_created), NULL, NULL, NULL,
|
||||||
|
G_TYPE_NONE, 1, G_TYPE_POINTER);
|
||||||
|
|
||||||
|
wp_proxy_signals[SIGNAL_PW_PROXY_DESTROYED] = g_signal_new (
|
||||||
|
"pw-proxy-destroyed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST,
|
||||||
|
G_STRUCT_OFFSET (WpProxyClass, pw_proxy_destroyed), NULL, NULL, NULL,
|
||||||
|
G_TYPE_NONE, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
guint
|
WpProxy *
|
||||||
|
wp_proxy_new_global (WpRemote * remote,
|
||||||
|
guint32 id, guint32 permissions, WpProperties * properties,
|
||||||
|
guint32 type, guint32 version)
|
||||||
|
{
|
||||||
|
GType gtype = wp_proxy_find_instance_type (type, version);
|
||||||
|
return g_object_new (gtype,
|
||||||
|
"remote", remote,
|
||||||
|
"global-id", id,
|
||||||
|
"global-permissions", permissions,
|
||||||
|
"global-properties", properties,
|
||||||
|
"interface-type", type,
|
||||||
|
"interface-version", version,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
WpProxy *
|
||||||
|
wp_proxy_new_wrap (WpRemote * remote,
|
||||||
|
struct pw_proxy * proxy, guint32 type, guint32 version)
|
||||||
|
{
|
||||||
|
GType gtype = wp_proxy_find_instance_type (type, version);
|
||||||
|
return g_object_new (gtype,
|
||||||
|
"remote", remote,
|
||||||
|
"pw-proxy", proxy,
|
||||||
|
"interface-type", type,
|
||||||
|
"interface-version", version,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
wp_proxy_augment (WpProxy * self,
|
||||||
|
WpProxyFeatures ft_wanted, GCancellable * cancellable,
|
||||||
|
GAsyncReadyCallback callback, gpointer user_data)
|
||||||
|
{
|
||||||
|
WpProxyPrivate *priv;
|
||||||
|
WpProxyFeatures missing = 0;
|
||||||
|
|
||||||
|
g_return_if_fail (WP_IS_PROXY (self));
|
||||||
|
g_return_if_fail (WP_PROXY_GET_CLASS (self)->augment);
|
||||||
|
|
||||||
|
priv = wp_proxy_get_instance_private (self);
|
||||||
|
priv->task = g_task_new (self, cancellable, callback, user_data);
|
||||||
|
|
||||||
|
/* we don't simply assign here, we keep all the previous wanted features;
|
||||||
|
* it is not allowed to remove features */
|
||||||
|
priv->ft_wanted |= ft_wanted;
|
||||||
|
|
||||||
|
/* find which features are wanted but missing from the "ready" set */
|
||||||
|
missing = (priv->ft_ready ^ priv->ft_wanted) & priv->ft_wanted;
|
||||||
|
|
||||||
|
/* if the features are not ready, call augment(),
|
||||||
|
* otherwise signal the callback directly */
|
||||||
|
if (missing != 0) {
|
||||||
|
WP_PROXY_GET_CLASS (self)->augment (self, missing);
|
||||||
|
} else {
|
||||||
|
g_task_return_boolean (priv->task, TRUE);
|
||||||
|
g_clear_object (&priv->task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
wp_proxy_augment_finish (WpProxy * self, GAsyncResult * res,
|
||||||
|
GError ** error)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (WP_IS_PROXY (self), FALSE);
|
||||||
|
g_return_val_if_fail (g_task_is_valid (res, self), FALSE);
|
||||||
|
|
||||||
|
return g_task_propagate_boolean (G_TASK (res), error);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
wp_proxy_set_feature_ready (WpProxy * self, WpProxyFeatures feature)
|
||||||
|
{
|
||||||
|
WpProxyPrivate *priv;
|
||||||
|
|
||||||
|
g_return_if_fail (WP_IS_PROXY (self));
|
||||||
|
|
||||||
|
priv = wp_proxy_get_instance_private (self);
|
||||||
|
|
||||||
|
/* feature already marked as ready */
|
||||||
|
if (priv->ft_ready & feature)
|
||||||
|
return;
|
||||||
|
|
||||||
|
priv->ft_ready |= feature;
|
||||||
|
|
||||||
|
g_object_notify (G_OBJECT (self), "features");
|
||||||
|
|
||||||
|
/* return from the task if all the wanted features are now ready */
|
||||||
|
if (priv->task &&
|
||||||
|
(priv->ft_ready & priv->ft_wanted) == priv->ft_wanted) {
|
||||||
|
g_task_return_boolean (priv->task, TRUE);
|
||||||
|
g_clear_object (&priv->task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wp_proxy_augment_error:
|
||||||
|
* @self: the proxy
|
||||||
|
* @error: (transfer full): the error
|
||||||
|
*
|
||||||
|
* Reports an error that occured during the augment process
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
wp_proxy_augment_error (WpProxy * self, GError * error)
|
||||||
|
{
|
||||||
|
WpProxyPrivate *priv;
|
||||||
|
|
||||||
|
g_return_if_fail (WP_IS_PROXY (self));
|
||||||
|
|
||||||
|
priv = wp_proxy_get_instance_private (self);
|
||||||
|
|
||||||
|
if (priv->task)
|
||||||
|
g_task_return_error (priv->task, error);
|
||||||
|
else
|
||||||
|
g_error_free (error);
|
||||||
|
|
||||||
|
g_clear_object (&priv->task);
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
wp_proxy_bind_global (WpProxy * self)
|
||||||
|
{
|
||||||
|
WpProxyPrivate *priv;
|
||||||
|
g_autoptr (WpRemote) remote = NULL;
|
||||||
|
|
||||||
|
g_return_val_if_fail (wp_proxy_is_global (self), FALSE);
|
||||||
|
|
||||||
|
priv = wp_proxy_get_instance_private (self);
|
||||||
|
|
||||||
|
if (priv->pw_proxy)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
remote = g_weak_ref_get (&priv->remote);
|
||||||
|
g_return_val_if_fail (remote != NULL, FALSE);
|
||||||
|
|
||||||
|
/* bind */
|
||||||
|
priv->pw_proxy = wp_remote_pipewire_proxy_bind (
|
||||||
|
WP_REMOTE_PIPEWIRE (remote),
|
||||||
|
priv->global_id, priv->iface_type);
|
||||||
|
pw_proxy_add_listener (priv->pw_proxy, &priv->listener, &proxy_events,
|
||||||
|
self);
|
||||||
|
|
||||||
|
/* inform subclasses and listeners */
|
||||||
|
g_signal_emit (self, wp_proxy_signals[SIGNAL_PW_PROXY_CREATED], 0,
|
||||||
|
priv->pw_proxy);
|
||||||
|
|
||||||
|
/* declare the feature as ready */
|
||||||
|
wp_proxy_set_feature_ready (self, WP_PROXY_FEATURE_PW_PROXY);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
WpProxyFeatures
|
||||||
|
wp_proxy_get_features (WpProxy * self)
|
||||||
|
{
|
||||||
|
WpProxyPrivate *priv;
|
||||||
|
|
||||||
|
g_return_val_if_fail (WP_IS_PROXY (self), 0);
|
||||||
|
|
||||||
|
priv = wp_proxy_get_instance_private (self);
|
||||||
|
return priv->ft_ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wp_proxy_get_remote:
|
||||||
|
* @self: the proxy
|
||||||
|
*
|
||||||
|
* Returns: (transfer full): the remote that created this proxy
|
||||||
|
*/
|
||||||
|
WpRemote *
|
||||||
|
wp_proxy_get_remote (WpProxy * self)
|
||||||
|
{
|
||||||
|
WpProxyPrivate *priv;
|
||||||
|
|
||||||
|
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
|
||||||
|
|
||||||
|
priv = wp_proxy_get_instance_private (self);
|
||||||
|
return g_weak_ref_get (&priv->remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
wp_proxy_is_global (WpProxy * self)
|
||||||
|
{
|
||||||
|
return wp_proxy_get_global_id (self) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
guint32
|
||||||
wp_proxy_get_global_id (WpProxy * self)
|
wp_proxy_get_global_id (WpProxy * self)
|
||||||
{
|
{
|
||||||
WpProxyPrivate *priv;
|
WpProxyPrivate *priv;
|
||||||
@@ -193,7 +578,78 @@ wp_proxy_get_global_id (WpProxy * self)
|
|||||||
return priv->global_id;
|
return priv->global_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
gpointer
|
guint32
|
||||||
|
wp_proxy_get_global_permissions (WpProxy * self)
|
||||||
|
{
|
||||||
|
WpProxyPrivate *priv;
|
||||||
|
|
||||||
|
g_return_val_if_fail (WP_IS_PROXY (self), 0);
|
||||||
|
|
||||||
|
priv = wp_proxy_get_instance_private (self);
|
||||||
|
return priv->global_perm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wp_proxy_get_global_properties:
|
||||||
|
*
|
||||||
|
* Returns: (transfer full): the global properties of the proxy
|
||||||
|
*/
|
||||||
|
WpProperties *
|
||||||
|
wp_proxy_get_global_properties (WpProxy * self)
|
||||||
|
{
|
||||||
|
WpProxyPrivate *priv;
|
||||||
|
|
||||||
|
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
|
||||||
|
|
||||||
|
priv = wp_proxy_get_instance_private (self);
|
||||||
|
return priv->global_props ? wp_properties_ref (priv->global_props) : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
guint32
|
||||||
|
wp_proxy_get_interface_type (WpProxy * self)
|
||||||
|
{
|
||||||
|
WpProxyPrivate *priv;
|
||||||
|
|
||||||
|
g_return_val_if_fail (WP_IS_PROXY (self), 0);
|
||||||
|
|
||||||
|
priv = wp_proxy_get_instance_private (self);
|
||||||
|
return priv->iface_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gchar *
|
||||||
|
wp_proxy_get_interface_name (WpProxy * self)
|
||||||
|
{
|
||||||
|
const gchar *name = NULL;
|
||||||
|
|
||||||
|
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
|
||||||
|
|
||||||
|
g_object_get (self, "interface-name", &name, NULL);
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
GQuark
|
||||||
|
wp_proxy_get_interface_quark (WpProxy * self)
|
||||||
|
{
|
||||||
|
GQuark q = 0;
|
||||||
|
|
||||||
|
g_return_val_if_fail (WP_IS_PROXY (self), 0);
|
||||||
|
|
||||||
|
g_object_get (self, "interface-quark", &q, NULL);
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
guint32
|
||||||
|
wp_proxy_get_interface_version (WpProxy * self)
|
||||||
|
{
|
||||||
|
WpProxyPrivate *priv;
|
||||||
|
|
||||||
|
g_return_val_if_fail (WP_IS_PROXY (self), 0);
|
||||||
|
|
||||||
|
priv = wp_proxy_get_instance_private (self);
|
||||||
|
return priv->iface_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct pw_proxy *
|
||||||
wp_proxy_get_pw_proxy (WpProxy * self)
|
wp_proxy_get_pw_proxy (WpProxy * self)
|
||||||
{
|
{
|
||||||
WpProxyPrivate *priv;
|
WpProxyPrivate *priv;
|
||||||
@@ -201,17 +657,39 @@ wp_proxy_get_pw_proxy (WpProxy * self)
|
|||||||
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
|
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
|
||||||
|
|
||||||
priv = wp_proxy_get_instance_private (self);
|
priv = wp_proxy_get_instance_private (self);
|
||||||
return priv->proxy;
|
return priv->pw_proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
void wp_proxy_sync (WpProxy * self)
|
gconstpointer
|
||||||
|
wp_proxy_get_native_info (WpProxy * self)
|
||||||
|
{
|
||||||
|
WpProxyPrivate *priv;
|
||||||
|
|
||||||
|
g_return_val_if_fail (WP_IS_PROXY (self), NULL);
|
||||||
|
|
||||||
|
priv = wp_proxy_get_instance_private (self);
|
||||||
|
return priv->native_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
wp_proxy_update_native_info (WpProxy * self, gconstpointer info,
|
||||||
|
WpProxyNativeInfoUpdate update, GDestroyNotify destroy)
|
||||||
{
|
{
|
||||||
WpProxyPrivate *priv;
|
WpProxyPrivate *priv;
|
||||||
|
|
||||||
g_return_if_fail (WP_IS_PROXY (self));
|
g_return_if_fail (WP_IS_PROXY (self));
|
||||||
|
g_return_if_fail (destroy != NULL);
|
||||||
|
|
||||||
priv = wp_proxy_get_instance_private (self);
|
priv = wp_proxy_get_instance_private (self);
|
||||||
|
|
||||||
/* Trigger the done callback */
|
if (update) {
|
||||||
pw_proxy_sync(priv->proxy, 0);
|
priv->native_info = update (priv->native_info, info);
|
||||||
|
} else {
|
||||||
|
g_clear_pointer (&priv->native_info, priv->native_info_destroy);
|
||||||
|
priv->native_info = (gpointer) info;
|
||||||
|
}
|
||||||
|
|
||||||
|
priv->native_info_destroy = destroy;
|
||||||
|
|
||||||
|
g_object_notify (G_OBJECT (self), "native-info");
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
*
|
*
|
||||||
* Copyright © 2019 Collabora Ltd.
|
* Copyright © 2019 Collabora Ltd.
|
||||||
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
||||||
|
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
@@ -11,10 +12,20 @@
|
|||||||
|
|
||||||
#include <gio/gio.h>
|
#include <gio/gio.h>
|
||||||
|
|
||||||
#include "core.h"
|
#include "remote.h"
|
||||||
|
#include "properties.h"
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
struct pw_proxy;
|
||||||
|
|
||||||
|
typedef enum { /*< flags >*/
|
||||||
|
WP_PROXY_FEATURE_PW_PROXY = (1 << 0),
|
||||||
|
WP_PROXY_FEATURE_INFO = (1 << 1),
|
||||||
|
|
||||||
|
WP_PROXY_FEATURE_LAST = (1 << 5), /*< skip >*/
|
||||||
|
} WpProxyFeatures;
|
||||||
|
|
||||||
#define WP_TYPE_PROXY (wp_proxy_get_type ())
|
#define WP_TYPE_PROXY (wp_proxy_get_type ())
|
||||||
G_DECLARE_DERIVABLE_TYPE (WpProxy, wp_proxy, WP, PROXY, GObject)
|
G_DECLARE_DERIVABLE_TYPE (WpProxy, wp_proxy, WP, PROXY, GObject)
|
||||||
|
|
||||||
@@ -23,16 +34,53 @@ struct _WpProxyClass
|
|||||||
{
|
{
|
||||||
GObjectClass parent_class;
|
GObjectClass parent_class;
|
||||||
|
|
||||||
/* Methods */
|
void (*augment) (WpProxy *self, WpProxyFeatures features);
|
||||||
void (*destroy) (WpProxy * self);
|
|
||||||
|
|
||||||
/* Signals */
|
void (*pw_proxy_created) (WpProxy * self, struct pw_proxy * proxy);
|
||||||
void (*done)(WpProxy *wp_proxy, gpointer data);
|
void (*pw_proxy_destroyed) (WpProxy * self);
|
||||||
};
|
};
|
||||||
|
|
||||||
guint wp_proxy_get_global_id (WpProxy * self);
|
WpProxy * wp_proxy_new_global (WpRemote * remote,
|
||||||
gpointer wp_proxy_get_pw_proxy (WpProxy * self);
|
guint32 id, guint32 permissions, WpProperties * properties,
|
||||||
void wp_proxy_sync (WpProxy * self);
|
guint32 type, guint32 version);
|
||||||
|
WpProxy * wp_proxy_new_wrap (WpRemote * remote,
|
||||||
|
struct pw_proxy * proxy, guint32 type, guint32 version);
|
||||||
|
|
||||||
|
void wp_proxy_augment (WpProxy *self,
|
||||||
|
WpProxyFeatures wanted_features, GCancellable * cancellable,
|
||||||
|
GAsyncReadyCallback callback, gpointer user_data);
|
||||||
|
gboolean wp_proxy_augment_finish (WpProxy * self, GAsyncResult * res,
|
||||||
|
GError ** error);
|
||||||
|
|
||||||
|
WpProxyFeatures wp_proxy_get_features (WpProxy * self);
|
||||||
|
|
||||||
|
WpRemote * wp_proxy_get_remote (WpProxy * self);
|
||||||
|
|
||||||
|
gboolean wp_proxy_is_global (WpProxy * self);
|
||||||
|
guint32 wp_proxy_get_global_id (WpProxy * self);
|
||||||
|
guint32 wp_proxy_get_global_permissions (WpProxy * self);
|
||||||
|
WpProperties * wp_proxy_get_global_properties (WpProxy * self);
|
||||||
|
|
||||||
|
guint32 wp_proxy_get_interface_type (WpProxy * self);
|
||||||
|
const gchar * wp_proxy_get_interface_name (WpProxy * self);
|
||||||
|
GQuark wp_proxy_get_interface_quark (WpProxy * self);
|
||||||
|
guint32 wp_proxy_get_interface_version (WpProxy * self);
|
||||||
|
|
||||||
|
struct pw_proxy * wp_proxy_get_pw_proxy (WpProxy * self);
|
||||||
|
gconstpointer wp_proxy_get_native_info (WpProxy * self);
|
||||||
|
|
||||||
|
/* for subclasses only */
|
||||||
|
|
||||||
|
typedef gpointer (*WpProxyNativeInfoUpdate) (gpointer old_info,
|
||||||
|
gconstpointer new_info);
|
||||||
|
|
||||||
|
void wp_proxy_update_native_info (WpProxy * self, gconstpointer info,
|
||||||
|
WpProxyNativeInfoUpdate update, GDestroyNotify destroy);
|
||||||
|
|
||||||
|
void wp_proxy_set_feature_ready (WpProxy * self, WpProxyFeatures feature);
|
||||||
|
void wp_proxy_augment_error (WpProxy * self, GError * error);
|
||||||
|
|
||||||
|
gboolean wp_proxy_bind_global (WpProxy * self);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
|
@@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "remote-pipewire.h"
|
#include "remote-pipewire.h"
|
||||||
|
|
||||||
#include <pipewire/pipewire.h>
|
#include <pipewire/pipewire.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -15,15 +16,6 @@
|
|||||||
|
|
||||||
#define WP_LOOP_SOURCE(x) ((WpLoopSource *) x)
|
#define WP_LOOP_SOURCE(x) ((WpLoopSource *) x)
|
||||||
|
|
||||||
G_DEFINE_QUARK (node, signal_detail_node)
|
|
||||||
G_DEFINE_QUARK (port, signal_detail_port)
|
|
||||||
G_DEFINE_QUARK (factory, signal_detail_factory)
|
|
||||||
G_DEFINE_QUARK (link, signal_detail_link)
|
|
||||||
G_DEFINE_QUARK (client, signal_detail_client)
|
|
||||||
G_DEFINE_QUARK (module, signal_detail_module)
|
|
||||||
G_DEFINE_QUARK (device, signal_detail_device)
|
|
||||||
G_DEFINE_QUARK (endpoint, signal_detail_endpoint)
|
|
||||||
|
|
||||||
typedef struct _WpLoopSource WpLoopSource;
|
typedef struct _WpLoopSource WpLoopSource;
|
||||||
struct _WpLoopSource
|
struct _WpLoopSource
|
||||||
{
|
{
|
||||||
@@ -90,6 +82,10 @@ struct _WpRemotePipewire
|
|||||||
struct spa_hook registry_listener;
|
struct spa_hook registry_listener;
|
||||||
|
|
||||||
GMainContext *context;
|
GMainContext *context;
|
||||||
|
|
||||||
|
GHashTable *proxies;
|
||||||
|
GHashTable *default_features;
|
||||||
|
GQueue created_obj_proxies;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@@ -113,45 +109,53 @@ static guint signals[LAST_SIGNAL] = { 0 };
|
|||||||
G_DEFINE_TYPE (WpRemotePipewire, wp_remote_pipewire, WP_TYPE_REMOTE)
|
G_DEFINE_TYPE (WpRemotePipewire, wp_remote_pipewire, WP_TYPE_REMOTE)
|
||||||
|
|
||||||
static void
|
static void
|
||||||
registry_global(void *data, uint32_t id,
|
on_proxy_ready (GObject * obj, GAsyncResult * res, gpointer data)
|
||||||
uint32_t permissions, uint32_t type, uint32_t version,
|
|
||||||
const struct spa_dict *props)
|
|
||||||
{
|
{
|
||||||
GQuark detail = 0;
|
WpRemotePipewire *self = WP_REMOTE_PIPEWIRE (data);
|
||||||
|
WpProxy *proxy = WP_PROXY (obj);
|
||||||
|
g_autoptr (GError) error = NULL;
|
||||||
|
|
||||||
switch (type) {
|
if (!wp_proxy_augment_finish (proxy, res, &error)) {
|
||||||
case PW_TYPE_INTERFACE_Node:
|
g_warning ("Failed to augment WpProxy (%p): %s", obj, error->message);
|
||||||
detail = signal_detail_node_quark ();
|
|
||||||
break;
|
|
||||||
case PW_TYPE_INTERFACE_Port:
|
|
||||||
detail = signal_detail_port_quark ();
|
|
||||||
break;
|
|
||||||
case PW_TYPE_INTERFACE_Factory:
|
|
||||||
detail = signal_detail_factory_quark ();
|
|
||||||
break;
|
|
||||||
case PW_TYPE_INTERFACE_Link:
|
|
||||||
detail = signal_detail_link_quark ();
|
|
||||||
break;
|
|
||||||
case PW_TYPE_INTERFACE_Client:
|
|
||||||
detail = signal_detail_client_quark ();
|
|
||||||
break;
|
|
||||||
case PW_TYPE_INTERFACE_Module:
|
|
||||||
detail = signal_detail_module_quark ();
|
|
||||||
break;
|
|
||||||
case PW_TYPE_INTERFACE_Device:
|
|
||||||
detail = signal_detail_device_quark ();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g_signal_emit (data, signals[SIGNAL_GLOBAL_ADDED], detail, id, props);
|
g_signal_emit (self, signals[SIGNAL_GLOBAL_ADDED],
|
||||||
|
wp_proxy_get_interface_quark (proxy), proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
registry_global(void *data, uint32_t id, uint32_t permissions,
|
||||||
|
uint32_t type, uint32_t version, const struct spa_dict *props)
|
||||||
|
{
|
||||||
|
WpRemotePipewire *self = WP_REMOTE_PIPEWIRE (data);
|
||||||
|
WpProxy *proxy;
|
||||||
|
WpProxyFeatures features;
|
||||||
|
g_autoptr (WpProperties) properties = wp_properties_new_copy_dict (props);
|
||||||
|
|
||||||
|
/* construct & store WpProxy */
|
||||||
|
proxy = wp_proxy_new_global (WP_REMOTE (self), id, permissions, properties,
|
||||||
|
type, version);
|
||||||
|
g_hash_table_insert (self->proxies, GUINT_TO_POINTER (id), proxy);
|
||||||
|
|
||||||
|
g_debug ("registry global:%u perm:0x%x type:%u/%u -> WpProxy:%p",
|
||||||
|
id, permissions, type, version, proxy);
|
||||||
|
|
||||||
|
/* augment */
|
||||||
|
features = GPOINTER_TO_UINT (g_hash_table_lookup (self->default_features,
|
||||||
|
GUINT_TO_POINTER (G_TYPE_FROM_INSTANCE (proxy))));
|
||||||
|
wp_proxy_augment (proxy, features, NULL, on_proxy_ready, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
registry_global_remove (void *data, uint32_t id)
|
registry_global_remove (void *data, uint32_t id)
|
||||||
{
|
{
|
||||||
g_signal_emit (data, signals[SIGNAL_GLOBAL_REMOVED], 0, id);
|
WpRemotePipewire *self = WP_REMOTE_PIPEWIRE (data);
|
||||||
|
g_autoptr (WpProxy) proxy = NULL;
|
||||||
|
|
||||||
|
if (g_hash_table_steal_extended (self->proxies, GUINT_TO_POINTER (id), NULL,
|
||||||
|
(gpointer *) &proxy))
|
||||||
|
g_signal_emit (data, signals[SIGNAL_GLOBAL_REMOVED],
|
||||||
|
wp_proxy_get_interface_quark (proxy), proxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct pw_registry_proxy_events registry_proxy_events = {
|
static const struct pw_registry_proxy_events registry_proxy_events = {
|
||||||
@@ -198,6 +202,9 @@ static const struct pw_remote_events remote_events = {
|
|||||||
static void
|
static void
|
||||||
wp_remote_pipewire_init (WpRemotePipewire *self)
|
wp_remote_pipewire_init (WpRemotePipewire *self)
|
||||||
{
|
{
|
||||||
|
self->proxies = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
|
||||||
|
g_object_unref);
|
||||||
|
self->default_features = g_hash_table_new (g_direct_hash, g_direct_equal);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -223,6 +230,8 @@ wp_remote_pipewire_finalize (GObject *object)
|
|||||||
{
|
{
|
||||||
WpRemotePipewire *self = WP_REMOTE_PIPEWIRE (object);
|
WpRemotePipewire *self = WP_REMOTE_PIPEWIRE (object);
|
||||||
|
|
||||||
|
g_clear_pointer (&self->proxies, g_hash_table_unref);
|
||||||
|
g_clear_pointer (&self->default_features, g_hash_table_unref);
|
||||||
pw_remote_destroy (self->remote);
|
pw_remote_destroy (self->remote);
|
||||||
pw_core_destroy (self->core);
|
pw_core_destroy (self->core);
|
||||||
g_clear_pointer (&self->context, g_main_context_unref);
|
g_clear_pointer (&self->context, g_main_context_unref);
|
||||||
@@ -336,10 +345,10 @@ wp_remote_pipewire_class_init (WpRemotePipewireClass *klass)
|
|||||||
/* Signals */
|
/* Signals */
|
||||||
signals[SIGNAL_GLOBAL_ADDED] = g_signal_new ("global-added",
|
signals[SIGNAL_GLOBAL_ADDED] = g_signal_new ("global-added",
|
||||||
G_TYPE_FROM_CLASS (klass), G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST,
|
G_TYPE_FROM_CLASS (klass), G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST,
|
||||||
0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_POINTER);
|
0, NULL, NULL, NULL, G_TYPE_NONE, 1, WP_TYPE_PROXY);
|
||||||
signals[SIGNAL_GLOBAL_REMOVED] = g_signal_new ("global-removed",
|
signals[SIGNAL_GLOBAL_REMOVED] = g_signal_new ("global-removed",
|
||||||
G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
|
G_TYPE_FROM_CLASS (klass), G_SIGNAL_DETAILED | G_SIGNAL_RUN_LAST,
|
||||||
0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT);
|
0, NULL, NULL, NULL, G_TYPE_NONE, 1, WP_TYPE_PROXY);
|
||||||
}
|
}
|
||||||
|
|
||||||
WpRemote *
|
WpRemote *
|
||||||
@@ -359,6 +368,33 @@ wp_remote_pipewire_new (WpCore *core, GMainContext *context)
|
|||||||
return remote;
|
return remote;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
wp_remote_pipewire_set_default_features (WpRemotePipewire * self,
|
||||||
|
GType proxy_type, WpProxyFeatures features)
|
||||||
|
{
|
||||||
|
g_return_if_fail (WP_IS_REMOTE_PIPEWIRE (self));
|
||||||
|
|
||||||
|
g_hash_table_insert (self->default_features, GUINT_TO_POINTER (proxy_type),
|
||||||
|
GUINT_TO_POINTER (features));
|
||||||
|
}
|
||||||
|
|
||||||
|
WpProxy *
|
||||||
|
wp_remote_pipewire_create_object (WpRemotePipewire *self,
|
||||||
|
const gchar *factory_name, guint32 interface_type,
|
||||||
|
guint32 interface_version, WpProperties * properties)
|
||||||
|
{
|
||||||
|
struct pw_proxy *pw_proxy;
|
||||||
|
|
||||||
|
g_return_val_if_fail (WP_IS_REMOTE_PIPEWIRE (self), NULL);
|
||||||
|
g_return_val_if_fail (self->core_proxy, NULL);
|
||||||
|
|
||||||
|
pw_proxy = pw_core_proxy_create_object (self->core_proxy, factory_name,
|
||||||
|
interface_type, interface_version, wp_properties_peek_dict (properties),
|
||||||
|
0);
|
||||||
|
return wp_proxy_new_wrap (WP_REMOTE (self), pw_proxy, interface_type,
|
||||||
|
interface_version);
|
||||||
|
}
|
||||||
|
|
||||||
gpointer
|
gpointer
|
||||||
wp_remote_pipewire_proxy_bind (WpRemotePipewire *self, guint global_id,
|
wp_remote_pipewire_proxy_bind (WpRemotePipewire *self, guint global_id,
|
||||||
guint global_type)
|
guint global_type)
|
||||||
@@ -380,17 +416,6 @@ wp_remote_pipewire_find_factory (WpRemotePipewire *self,
|
|||||||
return pw_core_find_factory(self->core, factory_name);
|
return pw_core_find_factory(self->core, factory_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
gpointer
|
|
||||||
wp_remote_pipewire_create_object (WpRemotePipewire *self,
|
|
||||||
const char *factory_name, guint global_type, gconstpointer props)
|
|
||||||
{
|
|
||||||
g_return_val_if_fail (WP_IS_REMOTE_PIPEWIRE(self), NULL);
|
|
||||||
g_return_val_if_fail (self->core_proxy, NULL);
|
|
||||||
|
|
||||||
return pw_core_proxy_create_object (self->core_proxy, factory_name,
|
|
||||||
global_type, 0, props, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
wp_remote_pipewire_add_spa_lib (WpRemotePipewire *self,
|
wp_remote_pipewire_add_spa_lib (WpRemotePipewire *self,
|
||||||
const char *factory_regexp, const char *lib)
|
const char *factory_regexp, const char *lib)
|
||||||
|
@@ -10,6 +10,7 @@
|
|||||||
#define __WIREPLUMBER_REMOTE_PIPEWIRE_H__
|
#define __WIREPLUMBER_REMOTE_PIPEWIRE_H__
|
||||||
|
|
||||||
#include "remote.h"
|
#include "remote.h"
|
||||||
|
#include "proxy.h"
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
@@ -19,12 +20,17 @@ G_DECLARE_FINAL_TYPE (WpRemotePipewire, wp_remote_pipewire,
|
|||||||
|
|
||||||
WpRemote *wp_remote_pipewire_new (WpCore *core, GMainContext *context);
|
WpRemote *wp_remote_pipewire_new (WpCore *core, GMainContext *context);
|
||||||
|
|
||||||
|
void wp_remote_pipewire_set_default_features (
|
||||||
|
WpRemotePipewire * self, GType proxy_type, WpProxyFeatures features);
|
||||||
|
|
||||||
|
WpProxy * wp_remote_pipewire_create_object (WpRemotePipewire *self,
|
||||||
|
const gchar *factory_name, guint32 interface_type,
|
||||||
|
guint32 interface_version, WpProperties * properties);
|
||||||
|
|
||||||
gpointer wp_remote_pipewire_proxy_bind (WpRemotePipewire *self, guint global_id,
|
gpointer wp_remote_pipewire_proxy_bind (WpRemotePipewire *self, guint global_id,
|
||||||
guint global_type);
|
guint global_type);
|
||||||
gpointer wp_remote_pipewire_find_factory (WpRemotePipewire *self,
|
gpointer wp_remote_pipewire_find_factory (WpRemotePipewire *self,
|
||||||
const char *factory_name);
|
const char *factory_name);
|
||||||
gpointer wp_remote_pipewire_create_object (WpRemotePipewire *self,
|
|
||||||
const char *factory_name, guint global_type, gconstpointer props);
|
|
||||||
void wp_remote_pipewire_add_spa_lib (WpRemotePipewire *self,
|
void wp_remote_pipewire_add_spa_lib (WpRemotePipewire *self,
|
||||||
const char *factory_regexp, const char *lib);
|
const char *factory_regexp, const char *lib);
|
||||||
gpointer wp_remote_pipewire_load_spa_handle(WpRemotePipewire *self,
|
gpointer wp_remote_pipewire_load_spa_handle(WpRemotePipewire *self,
|
||||||
|
Reference in New Issue
Block a user