pipewire/pipewire/client/context.c
2017-06-16 15:57:03 +02:00

920 lines
24 KiB
C

/* PipeWire
* Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include "pipewire/client/pipewire.h"
#include "pipewire/client/context.h"
#include "pipewire/client/introspect.h"
#include "pipewire/client/interfaces.h"
#include "pipewire/client/protocol-native.h"
#include "pipewire/client/connection.h"
#include "pipewire/client/subscribe.h"
#include <spa/lib/debug.h>
/** \cond */
struct context {
struct pw_context this;
struct spa_support support[3];
bool no_proxy;
int fd;
struct pw_connection *connection;
struct spa_source *source;
bool disconnecting;
struct pw_listener need_flush;
struct spa_source *flush_event;
};
struct proxy_data {
void *info;
};
/** \endcond */
const char *pw_context_state_as_string(enum pw_context_state state)
{
switch (state) {
case PW_CONTEXT_STATE_ERROR:
return "error";
case PW_CONTEXT_STATE_UNCONNECTED:
return "unconnected";
case PW_CONTEXT_STATE_CONNECTING:
return "connecting";
case PW_CONTEXT_STATE_CONNECTED:
return "connected";
}
return "invalid-state";
}
static void
context_set_state(struct pw_context *context, enum pw_context_state state, const char *fmt, ...)
{
if (context->state != state) {
if (context->error)
free(context->error);
if (fmt) {
va_list varargs;
va_start(varargs, fmt);
vasprintf(&context->error, fmt, varargs);
va_end(varargs);
} else {
context->error = NULL;
}
pw_log_debug("context %p: update state from %s -> %s (%s)", context,
pw_context_state_as_string(context->state),
pw_context_state_as_string(state), context->error);
context->state = state;
pw_signal_emit(&context->state_changed, context);
}
}
static void core_event_info(void *object, struct pw_core_info *info)
{
struct pw_proxy *proxy = object;
struct pw_context *this = proxy->context;
enum pw_subscription_event event;
struct proxy_data *data = proxy->user_data;
pw_log_debug("got core info");
if (data->info == NULL)
event = PW_SUBSCRIPTION_EVENT_NEW;
else
event = PW_SUBSCRIPTION_EVENT_CHANGE;
data->info = pw_core_info_update(data->info, info);
pw_signal_emit(&this->subscription, this, event, proxy->type, proxy->id);
}
static void core_event_done(void *object, uint32_t seq)
{
struct pw_proxy *proxy = object;
struct pw_context *this = proxy->context;
if (seq == 0) {
pw_core_do_sync(this->core_proxy, 1);
} else if (seq == 1) {
context_set_state(this, PW_CONTEXT_STATE_CONNECTED, NULL);
}
}
static void core_event_error(void *object, uint32_t id, int res, const char *error, ...)
{
struct pw_proxy *proxy = object;
struct pw_context *this = proxy->context;
context_set_state(this, PW_CONTEXT_STATE_ERROR, error);
}
static void core_event_remove_id(void *object, uint32_t id)
{
struct pw_proxy *core_proxy = object;
struct pw_context *this = core_proxy->context;
struct pw_proxy *proxy;
proxy = pw_map_lookup(&this->objects, id);
if (proxy) {
pw_log_debug("context %p: object remove %u", this, id);
pw_proxy_destroy(proxy);
}
}
static void
core_event_update_types(void *object, uint32_t first_id, uint32_t n_types, const char **types)
{
struct pw_proxy *proxy = object;
struct pw_context *this = proxy->context;
int i;
for (i = 0; i < n_types; i++, first_id++) {
uint32_t this_id = spa_type_map_get_id(this->type.map, types[i]);
if (!pw_map_insert_at(&this->types, first_id, PW_MAP_ID_TO_PTR(this_id)))
pw_log_error("can't add type for client");
}
}
static const struct pw_core_events core_events = {
&core_event_update_types,
&core_event_done,
&core_event_error,
&core_event_remove_id,
&core_event_info,
};
static void module_event_info(void *object, struct pw_module_info *info)
{
struct pw_proxy *proxy = object;
struct pw_context *this = proxy->context;
enum pw_subscription_event event;
struct proxy_data *data = proxy->user_data;
pw_log_debug("got module info");
if (data->info == NULL)
event = PW_SUBSCRIPTION_EVENT_NEW;
else
event = PW_SUBSCRIPTION_EVENT_CHANGE;
data->info = pw_module_info_update(data->info, info);
pw_signal_emit(&this->subscription, this, event, proxy->type, proxy->id);
}
static const struct pw_module_events module_events = {
&module_event_info,
};
static void node_event_info(void *object, struct pw_node_info *info)
{
struct pw_proxy *proxy = object;
struct pw_context *this = proxy->context;
enum pw_subscription_event event;
struct proxy_data *data = proxy->user_data;
pw_log_debug("got node info");
if (data->info == NULL)
event = PW_SUBSCRIPTION_EVENT_NEW;
else
event = PW_SUBSCRIPTION_EVENT_CHANGE;
data->info = pw_node_info_update(data->info, info);
pw_signal_emit(&this->subscription, this, event, proxy->type, proxy->id);
}
static const struct pw_node_events node_events = {
&node_event_info
};
static void client_event_info(void *object, struct pw_client_info *info)
{
struct pw_proxy *proxy = object;
struct pw_context *this = proxy->context;
enum pw_subscription_event event;
struct proxy_data *data = proxy->user_data;
pw_log_debug("got client info");
if (data->info == NULL)
event = PW_SUBSCRIPTION_EVENT_NEW;
else
event = PW_SUBSCRIPTION_EVENT_CHANGE;
data->info = pw_client_info_update(data->info, info);
pw_signal_emit(&this->subscription, this, event, proxy->type, proxy->id);
}
static const struct pw_client_events client_events = {
&client_event_info
};
static void link_event_info(void *object, struct pw_link_info *info)
{
struct pw_proxy *proxy = object;
struct pw_context *this = proxy->context;
enum pw_subscription_event event;
struct proxy_data *data = proxy->user_data;
pw_log_debug("got link info");
if (data->info == NULL)
event = PW_SUBSCRIPTION_EVENT_NEW;
else
event = PW_SUBSCRIPTION_EVENT_CHANGE;
data->info = pw_link_info_update(data->info, info);
pw_signal_emit(&this->subscription, this, event, proxy->type, proxy->id);
}
static const struct pw_link_events link_events = {
&link_event_info
};
static void
destroy_proxy (void *data)
{
struct pw_proxy *proxy = data;
struct proxy_data *user_data = proxy->user_data;
if (user_data->info == NULL)
return;
if (proxy->type == proxy->context->type.core) {
pw_core_info_free (user_data->info);
} else if (proxy->type == proxy->context->type.node) {
pw_node_info_free (user_data->info);
} else if (proxy->type == proxy->context->type.module) {
pw_module_info_free (user_data->info);
} else if (proxy->type == proxy->context->type.client) {
pw_client_info_free (user_data->info);
} else if (proxy->type == proxy->context->type.link) {
pw_link_info_free (user_data->info);
}
user_data->info = NULL;
}
static void registry_event_global(void *object, uint32_t id, const char *type, uint32_t version)
{
struct pw_proxy *registry_proxy = object;
struct pw_context *this = registry_proxy->context;
struct context *impl = SPA_CONTAINER_OF(this, struct context, this);
struct pw_proxy *proxy = NULL;
uint32_t proxy_type, client_version;
const void *implementation;
if (impl->no_proxy)
return;
pw_log_debug("got global %u %s %u", id, type, version);
if (!strcmp(type, PIPEWIRE_TYPE__Node)) {
proxy_type = this->type.node;
implementation = &node_events;
client_version = PW_VERSION_NODE;
} else if (!strcmp(type, PIPEWIRE_TYPE__Module)) {
proxy_type = this->type.module;
implementation = &module_events;
client_version = PW_VERSION_MODULE;
} else if (!strcmp(type, PIPEWIRE_TYPE__Client)) {
proxy_type = this->type.client;
implementation = &client_events;
client_version = PW_VERSION_CLIENT;
} else if (!strcmp(type, PIPEWIRE_TYPE__Link)) {
proxy_type = this->type.link;
implementation = &link_events;
client_version = PW_VERSION_LINK;
} else
return;
proxy = pw_proxy_new(this, SPA_ID_INVALID, proxy_type, sizeof(struct proxy_data));
if (proxy == NULL)
goto no_mem;
pw_proxy_set_implementation(proxy, this, client_version, implementation, destroy_proxy);
pw_registry_do_bind(registry_proxy, id, version, proxy->id);
return;
no_mem:
pw_log_error("context %p: failed to create proxy", this);
return;
}
static void registry_event_global_remove(void *object, uint32_t id)
{
struct pw_proxy *proxy = object;
struct pw_context *this = proxy->context;
pw_log_debug("got global remove %u", id);
pw_signal_emit(&this->subscription, this, PW_SUBSCRIPTION_EVENT_REMOVE, SPA_ID_INVALID, id);
}
static const struct pw_registry_events registry_events = {
&registry_event_global,
&registry_event_global_remove
};
typedef bool(*demarshal_func_t) (void *object, void *data, size_t size);
static void do_flush_event(struct spa_loop_utils *utils, struct spa_source *source, void *data)
{
struct context *impl = data;
if (impl->connection)
if (!pw_connection_flush(impl->connection))
pw_context_disconnect(&impl->this);
}
static void on_need_flush(struct pw_listener *listener, struct pw_connection *connection)
{
struct context *impl = SPA_CONTAINER_OF(listener, struct context, need_flush);
struct pw_context *this = &impl->this;
pw_loop_signal_event(this->loop, impl->flush_event);
}
static void
on_context_data(struct spa_loop_utils *utils,
struct spa_source *source, int fd, enum spa_io mask, void *data)
{
struct context *impl = data;
struct pw_context *this = &impl->this;
struct pw_connection *conn = impl->connection;
if (mask & (SPA_IO_ERR | SPA_IO_HUP)) {
context_set_state(this, PW_CONTEXT_STATE_ERROR, "connection closed");
return;
}
if (mask & SPA_IO_IN) {
uint8_t opcode;
uint32_t id;
uint32_t size;
void *message;
while (!impl->disconnecting
&& pw_connection_get_next(conn, &opcode, &id, &message, &size)) {
struct pw_proxy *proxy;
const demarshal_func_t *demarshal;
pw_log_trace("context %p: got message %d from %u", this, opcode, id);
proxy = pw_map_lookup(&this->objects, id);
if (proxy == NULL) {
pw_log_error("context %p: could not find proxy %u", this, id);
continue;
}
if (opcode >= proxy->iface->n_events) {
pw_log_error("context %p: invalid method %u for %u", this, opcode,
id);
continue;
}
demarshal = proxy->iface->events;
if (demarshal[opcode]) {
if (!demarshal[opcode] (proxy, message, size))
pw_log_error
("context %p: invalid message received %u for %u", this,
opcode, id);
} else
pw_log_error("context %p: function %d not implemented on %u", this,
opcode, id);
}
}
}
/** Create a new unconnected context
*
* \param loop a \ref pw_loop to use as event loop
* \param name an application name
* \param properties optional properties, ownership of the properties is
* taken.
* \return a new unconnected context
*
* \memberof pw_context
*/
struct pw_context *pw_context_new(struct pw_loop *loop,
const char *name, struct pw_properties *properties)
{
struct context *impl;
struct pw_context *this;
impl = calloc(1, sizeof(struct context));
if (impl == NULL)
return NULL;
impl->fd = -1;
this = &impl->this;
pw_log_debug("context %p: new", impl);
this->name = strdup(name);
if (properties == NULL)
properties = pw_properties_new("application.name", name, NULL);
if (properties == NULL)
goto no_mem;
pw_fill_context_properties(properties);
this->properties = properties;
this->loop = loop;
this->protocol = pw_protocol_native_client_init();
pw_type_init(&this->type);
spa_debug_set_type_map(this->type.map);
impl->support[0] = SPA_SUPPORT_INIT (SPA_TYPE__TypeMap, this->type.map);
impl->support[1] = SPA_SUPPORT_INIT (SPA_TYPE_LOOP__MainLoop, this->loop->loop);
impl->support[2] = SPA_SUPPORT_INIT (SPA_TYPE__Log, pw_log_get());
this->support = impl->support;
this->n_support = 3;
impl->flush_event = pw_loop_add_event(loop, do_flush_event, impl);
this->state = PW_CONTEXT_STATE_UNCONNECTED;
pw_map_init(&this->objects, 64, 32);
pw_map_init(&this->types, 64, 32);
spa_list_init(&this->stream_list);
spa_list_init(&this->proxy_list);
pw_signal_init(&this->state_changed);
pw_signal_init(&this->subscription);
pw_signal_init(&this->destroy_signal);
return this;
no_mem:
free(this->name);
free(impl);
return NULL;
}
/** Destroy a context
*
* \param context a \ref pw_context to destroy to destroy
*
* \memberof pw_context
*/
void pw_context_destroy(struct pw_context *context)
{
struct context *impl = SPA_CONTAINER_OF(context, struct context, this);
struct pw_stream *stream, *t1;
struct pw_proxy *proxy, *t2;
pw_log_debug("context %p: destroy", context);
pw_signal_emit(&context->destroy_signal, context);
pw_loop_destroy_source(impl->this.loop, impl->flush_event);
if (context->state != PW_CONTEXT_STATE_UNCONNECTED)
pw_context_disconnect(context);
spa_list_for_each_safe(stream, t1, &context->stream_list, link)
pw_stream_destroy(stream);
spa_list_for_each_safe(proxy, t2, &context->proxy_list, link)
pw_proxy_destroy(proxy);
pw_map_clear(&context->objects);
pw_map_clear(&context->types);
free(context->name);
if (context->properties)
pw_properties_free(context->properties);
free(context->error);
free(impl);
}
/** Connect to the PipeWire daemon
*
* \param context a \ref pw_context
* \param flags flags to use
* \return true on success.
*
* \memberof pw_context
*/
bool pw_context_connect(struct pw_context *context, enum pw_context_flags flags)
{
struct sockaddr_un addr;
socklen_t size;
const char *runtime_dir, *name = NULL;
int name_size, fd;
if ((runtime_dir = getenv("XDG_RUNTIME_DIR")) == NULL) {
context_set_state(context,
PW_CONTEXT_STATE_ERROR,
"connect failed: XDG_RUNTIME_DIR not set in the environment");
return false;
}
if (name == NULL)
name = getenv("PIPEWIRE_CORE");
if (name == NULL)
name = "pipewire-0";
if ((fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0)
return false;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_LOCAL;
name_size = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s", runtime_dir, name) + 1;
if (name_size > (int) sizeof addr.sun_path) {
pw_log_error("socket path \"%s/%s\" plus null terminator exceeds 108 bytes",
runtime_dir, name);
goto error_close;
};
size = offsetof(struct sockaddr_un, sun_path) + name_size;
if (connect(fd, (struct sockaddr *) &addr, size) < 0) {
context_set_state(context,
PW_CONTEXT_STATE_ERROR, "connect failed: %s", strerror(errno));
goto error_close;
}
return pw_context_connect_fd(context, flags, fd);
error_close:
close(fd);
return false;
}
/** Connect to the PipeWire daemon on the given socket
*
* \param context a \ref pw_context
* \param flags flags to use
* \param fd the connected socket to use
* \return true on success.
*
* \memberof pw_context
*/
bool pw_context_connect_fd(struct pw_context *context, enum pw_context_flags flags, int fd)
{
struct context *impl = SPA_CONTAINER_OF(context, struct context, this);
context_set_state(context, PW_CONTEXT_STATE_CONNECTING, NULL);
impl->connection = pw_connection_new(fd);
if (impl->connection == NULL)
goto error_close;
context->protocol_private = impl->connection;
pw_signal_add(&impl->connection->need_flush, &impl->need_flush, on_need_flush);
impl->fd = fd;
impl->source = pw_loop_add_io(context->loop,
fd,
SPA_IO_IN | SPA_IO_HUP | SPA_IO_ERR,
false, on_context_data, impl);
context->core_proxy = pw_proxy_new(context, 0, context->type.core, sizeof(struct proxy_data));
if (context->core_proxy == NULL)
goto no_proxy;
pw_proxy_set_implementation(context->core_proxy, context, PW_VERSION_CORE,
&core_events, destroy_proxy);
pw_core_do_client_update(context->core_proxy, &context->properties->dict);
if (!(flags & PW_CONTEXT_FLAG_NO_REGISTRY)) {
context->registry_proxy = pw_proxy_new(context,
SPA_ID_INVALID, context->type.registry, 0);
if (context->registry_proxy == NULL)
goto no_registry;
pw_proxy_set_implementation(context->registry_proxy, context, PW_VERSION_REGISTRY,
&registry_events, NULL);
pw_core_do_get_registry(context->core_proxy, context->registry_proxy->id);
}
impl->no_proxy = ! !(flags & PW_CONTEXT_FLAG_NO_PROXY);
pw_core_do_sync(context->core_proxy, 0);
return true;
no_registry:
pw_proxy_destroy(context->core_proxy);
no_proxy:
pw_loop_destroy_source(context->loop, impl->source);
pw_connection_destroy(impl->connection);
error_close:
close(fd);
return false;
}
/** Disconnect from the daemon.
*
* \param context a \ref pw_context
* \return true on success.
*
* \memberof pw_context
*/
bool pw_context_disconnect(struct pw_context *context)
{
struct context *impl = SPA_CONTAINER_OF(context, struct context, this);
impl->disconnecting = true;
if (impl->source)
pw_loop_destroy_source(context->loop, impl->source);
impl->source = NULL;
if (context->registry_proxy)
pw_proxy_destroy(context->registry_proxy);
context->registry_proxy = NULL;
if (context->core_proxy)
pw_proxy_destroy(context->core_proxy);
context->core_proxy = NULL;
if (impl->connection)
pw_connection_destroy(impl->connection);
impl->connection = NULL;
context->protocol_private = NULL;
if (impl->fd != -1)
close(impl->fd);
impl->fd = -1;
context_set_state(context, PW_CONTEXT_STATE_UNCONNECTED, NULL);
return true;
}
/** Get core information
*
* \param context A \ref pw_context
* \param cb the callback to call with the result
* \param user_data user data passed to \a cb
*
* \memberof pw_introspect
*/
void pw_context_get_core_info(struct pw_context *context, pw_core_info_cb_t cb, void *user_data)
{
struct pw_proxy *proxy;
proxy = pw_map_lookup(&context->objects, 0);
if (proxy == NULL) {
cb(context, SPA_RESULT_INVALID_OBJECT_ID, NULL, user_data);
} else if (proxy->type == context->type.core) {
struct proxy_data *data = proxy->user_data;
struct pw_core_info *info = data->info;
if (info) {
cb(context, SPA_RESULT_OK, info, user_data);
info->change_mask = 0;
}
}
cb(context, SPA_RESULT_ENUM_END, NULL, user_data);
}
typedef void (*list_func_t) (struct pw_context *, int, void *, void *);
static void do_list(struct pw_context *context, uint32_t type, list_func_t cb, void *user_data)
{
union pw_map_item *item;
pw_array_for_each(item, &context->objects.items) {
struct pw_proxy *proxy;
struct proxy_data *data;
if (pw_map_item_is_free(item))
continue;
proxy = item->data;
if (proxy->type != type)
continue;
data = proxy->user_data;
if (data->info)
cb(context, SPA_RESULT_OK, data->info, user_data);
}
cb(context, SPA_RESULT_ENUM_END, NULL, user_data);
}
/** Get all module information
*
* \param context A \ref pw_context
* \param cb the callback to call with the results
* \param user_data user data passed to \a cb
*
* \a cb is called for each module
*
* \memberof pw_introspect
*/
void
pw_context_list_module_info(struct pw_context *context, pw_module_info_cb_t cb, void *user_data)
{
do_list(context, context->type.module, (list_func_t) cb, user_data);
}
/** Get module information
*
* \param context A \ref pw_context
* \param id the client side id of the module to query
* \param cb the callback to call with the results
* \param user_data user data passed to \a cb
*
* \a cb is called for the module with \a id
*
* \memberof pw_introspect
*/
void
pw_context_get_module_info_by_id(struct pw_context *context,
uint32_t id, pw_module_info_cb_t cb, void *user_data)
{
struct pw_proxy *proxy;
proxy = pw_map_lookup(&context->objects, id);
if (proxy == NULL) {
cb(context, SPA_RESULT_INVALID_OBJECT_ID, NULL, user_data);
} else if (proxy->type == context->type.module) {
struct proxy_data *data = proxy->user_data;
struct pw_module_info *info = data->info;
if (info) {
cb(context, SPA_RESULT_OK, info, user_data);
info->change_mask = 0;
}
}
cb(context, SPA_RESULT_ENUM_END, NULL, user_data);
}
/** Get all client information
*
* \param context A \ref pw_context
* \param cb the callback to call with the results
* \param user_data user data passed to \a cb
*
* \a cb is called for each client
*
* \memberof pw_introspect
*/
void
pw_context_list_client_info(struct pw_context *context, pw_client_info_cb_t cb, void *user_data)
{
do_list(context, context->type.client, (list_func_t) cb, user_data);
}
/** Get client information
*
* \param context A \ref pw_context
* \param id the client side id of the client to query
* \param cb the callback to call with the results
* \param user_data user data passed to \a cb
*
* \a cb is called for the client with \a id
*
* \memberof pw_introspect
*/
void
pw_context_get_client_info_by_id(struct pw_context *context,
uint32_t id, pw_client_info_cb_t cb, void *user_data)
{
struct pw_proxy *proxy;
proxy = pw_map_lookup(&context->objects, id);
if (proxy == NULL) {
cb(context, SPA_RESULT_INVALID_OBJECT_ID, NULL, user_data);
} else if (proxy->type == context->type.client) {
struct proxy_data *data = proxy->user_data;
struct pw_client_info *info = data->info;
if (info) {
cb(context, SPA_RESULT_OK, info, user_data);
info->change_mask = 0;
}
}
cb(context, SPA_RESULT_ENUM_END, NULL, user_data);
}
/** Get all node information
*
* \param context A \ref pw_context
* \param cb the callback to call with the results
* \param user_data user data passed to \a cb
*
* \a cb is called for each node
*
* \memberof pw_introspect
*/
void pw_context_list_node_info(struct pw_context *context, pw_node_info_cb_t cb, void *user_data)
{
do_list(context, context->type.node, (list_func_t) cb, user_data);
}
/** Get node information
*
* \param context A \ref pw_context
* \param id the client side id of the node to query
* \param cb the callback to call with the results
* \param user_data user data passed to \a cb
*
* \a cb is called for the node with \a id
*
* \memberof pw_introspect
*/
void
pw_context_get_node_info_by_id(struct pw_context *context,
uint32_t id, pw_node_info_cb_t cb, void *user_data)
{
struct pw_proxy *proxy;
proxy = pw_map_lookup(&context->objects, id);
if (proxy == NULL) {
cb(context, SPA_RESULT_INVALID_OBJECT_ID, NULL, user_data);
} else if (proxy->type == context->type.node) {
struct proxy_data *data = proxy->user_data;
struct pw_node_info *info = data->info;
if (info) {
cb(context, SPA_RESULT_OK, info, user_data);
info->change_mask = 0;
}
}
cb(context, SPA_RESULT_ENUM_END, NULL, user_data);
}
/** Get all link information
*
* \param context A \ref pw_context
* \param cb the callback to call with the results
* \param user_data user data passed to \a cb
*
* \a cb is called for each link
*
* \memberof pw_introspect
*/
void pw_context_list_link_info(struct pw_context *context, pw_link_info_cb_t cb, void *user_data)
{
do_list(context, context->type.link, (list_func_t) cb, user_data);
}
/** Get link information
*
* \param context A \ref pw_context
* \param id the client side id of the link to query
* \param cb the callback to call with the results
* \param user_data user data passed to \a cb
*
* \a cb is called for the link with \a id
*
* \memberof pw_introspect
*/
void
pw_context_get_link_info_by_id(struct pw_context *context,
uint32_t id, pw_link_info_cb_t cb, void *user_data)
{
struct pw_proxy *proxy;
proxy = pw_map_lookup(&context->objects, id);
if (proxy == NULL) {
cb(context, SPA_RESULT_INVALID_OBJECT_ID, NULL, user_data);
} else if (proxy->type == context->type.link) {
struct proxy_data *data = proxy->user_data;
struct pw_link_info *info = data->info;
if (info) {
cb(context, SPA_RESULT_OK, info, user_data);
info->change_mask = 0;
}
}
cb(context, SPA_RESULT_ENUM_END, NULL, user_data);
}