mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-11-03 09:01:54 -05:00 
			
		
		
		
	Make enum_params and set_param to configure properties, format and other parameters. This allows us to remove some duplicate code and make the properties and parameters much more extensible. Use the object id to mark the id of the parameter. Remove the spa_format and spa_props. We can now make the client-node easier by merging the various format methods into the params. Make the stream API more powerful now that we can pass params around.
		
			
				
	
	
		
			814 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			814 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* PipeWire
 | 
						|
 * Copyright (C) 2016 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 <string.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <errno.h>
 | 
						|
#include <sys/types.h>
 | 
						|
#include <sys/stat.h>
 | 
						|
#include <fcntl.h>
 | 
						|
#include <unistd.h>
 | 
						|
 | 
						|
#include "config.h"
 | 
						|
 | 
						|
#include <dbus/dbus.h>
 | 
						|
 | 
						|
#include "pipewire/core.h"
 | 
						|
#include "pipewire/interfaces.h"
 | 
						|
#include "pipewire/link.h"
 | 
						|
#include "pipewire/log.h"
 | 
						|
#include "pipewire/module.h"
 | 
						|
#include "pipewire/utils.h"
 | 
						|
 | 
						|
struct impl {
 | 
						|
	struct pw_core *core;
 | 
						|
	struct pw_type *type;
 | 
						|
	struct pw_properties *properties;
 | 
						|
 | 
						|
	DBusConnection *bus;
 | 
						|
 | 
						|
	struct spa_hook core_listener;
 | 
						|
	struct spa_hook module_listener;
 | 
						|
 | 
						|
	struct spa_list client_list;
 | 
						|
 | 
						|
	struct spa_source *dispatch_event;
 | 
						|
};
 | 
						|
 | 
						|
struct resource;
 | 
						|
 | 
						|
struct client_info {
 | 
						|
	struct spa_list link;
 | 
						|
	struct impl *impl;
 | 
						|
	struct pw_client *client;
 | 
						|
	struct spa_hook client_listener;
 | 
						|
	bool is_sandboxed;
 | 
						|
        struct spa_list resources;
 | 
						|
	struct resource *core_resource;
 | 
						|
	struct spa_list async_pending;
 | 
						|
};
 | 
						|
 | 
						|
struct resource {
 | 
						|
        struct spa_list link;
 | 
						|
	struct client_info *cinfo;
 | 
						|
	struct pw_resource *resource;
 | 
						|
	struct spa_hook override;
 | 
						|
};
 | 
						|
 | 
						|
struct async_pending {
 | 
						|
	struct spa_list link;
 | 
						|
	struct resource *resource;
 | 
						|
	bool handled;
 | 
						|
	char *handle;
 | 
						|
	char *factory_name;
 | 
						|
	uint32_t type;
 | 
						|
	uint32_t version;
 | 
						|
	struct pw_properties *properties;
 | 
						|
	uint32_t new_id;
 | 
						|
};
 | 
						|
 | 
						|
static struct client_info *find_client_info(struct impl *impl, struct pw_client *client)
 | 
						|
{
 | 
						|
	struct client_info *info;
 | 
						|
 | 
						|
	spa_list_for_each(info, &impl->client_list, link) {
 | 
						|
		if (info->client == client)
 | 
						|
			return info;
 | 
						|
	}
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static void close_request(struct async_pending *p)
 | 
						|
{
 | 
						|
	DBusMessage *m = NULL;
 | 
						|
	struct impl *impl = p->resource->cinfo->impl;
 | 
						|
 | 
						|
	pw_log_debug("pending %p: handle %s", p, p->handle);
 | 
						|
 | 
						|
	if (!(m = dbus_message_new_method_call("org.freedesktop.portal.Request",
 | 
						|
					       p->handle,
 | 
						|
					       "org.freedesktop.portal.Request", "Close"))) {
 | 
						|
		pw_log_error("Failed to create message");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!dbus_connection_send(impl->bus, m, NULL))
 | 
						|
		pw_log_error("Failed to send message");
 | 
						|
 | 
						|
	dbus_message_unref(m);
 | 
						|
}
 | 
						|
 | 
						|
static struct async_pending *find_pending(struct client_info *cinfo, const char *handle)
 | 
						|
{
 | 
						|
	struct async_pending *p;
 | 
						|
 | 
						|
	spa_list_for_each(p, &cinfo->async_pending, link) {
 | 
						|
		if (strcmp(p->handle, handle) == 0)
 | 
						|
			return p;
 | 
						|
	}
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static void free_pending(struct async_pending *p)
 | 
						|
{
 | 
						|
	if (!p->handled)
 | 
						|
		close_request(p);
 | 
						|
 | 
						|
	pw_log_debug("pending %p: handle %s", p, p->handle);
 | 
						|
	spa_list_remove(&p->link);
 | 
						|
	free(p->handle);
 | 
						|
	free(p->factory_name);
 | 
						|
	if (p->properties)
 | 
						|
		pw_properties_free(p->properties);
 | 
						|
	free(p);
 | 
						|
}
 | 
						|
 | 
						|
static void free_resource(struct resource *r)
 | 
						|
{
 | 
						|
	spa_list_remove(&r->link);
 | 
						|
	free(r);
 | 
						|
}
 | 
						|
 | 
						|
static void client_info_free(struct client_info *cinfo)
 | 
						|
{
 | 
						|
	struct async_pending *p, *tp;
 | 
						|
	struct resource *r, *tr;
 | 
						|
 | 
						|
	spa_list_for_each_safe(p, tp, &cinfo->async_pending, link)
 | 
						|
		free_pending(p);
 | 
						|
	spa_list_for_each_safe(r, tr, &cinfo->resources, link)
 | 
						|
		free_resource(r);
 | 
						|
 | 
						|
	spa_hook_remove(&cinfo->client_listener);
 | 
						|
	spa_list_remove(&cinfo->link);
 | 
						|
	free(cinfo);
 | 
						|
}
 | 
						|
 | 
						|
static bool check_sandboxed(struct client_info *cinfo, char **error)
 | 
						|
{
 | 
						|
	char root_path[2048];
 | 
						|
	int root_fd, info_fd;
 | 
						|
	const struct ucred *ucred;
 | 
						|
	struct stat stat_buf;
 | 
						|
 | 
						|
	ucred = pw_client_get_ucred(cinfo->client);
 | 
						|
 | 
						|
	cinfo->is_sandboxed = true;
 | 
						|
 | 
						|
	if (ucred) {
 | 
						|
		pw_log_info("client has trusted pid %d", ucred->pid);
 | 
						|
	} else {
 | 
						|
		cinfo->is_sandboxed = false;
 | 
						|
		pw_log_info("no trusted pid found, assuming not sandboxed\n");
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	sprintf(root_path, "/proc/%u/root", ucred->pid);
 | 
						|
	root_fd = openat (AT_FDCWD, root_path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY);
 | 
						|
	if (root_fd == -1) {
 | 
						|
		/* Not able to open the root dir shouldn't happen. Probably the app died and
 | 
						|
		 * we're failing due to /proc/$pid not existing. In that case fail instead
 | 
						|
		 * of treating this as privileged. */
 | 
						|
		asprintf(error, "failed to open \"%s\": %m", root_path);
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	info_fd = openat (root_fd, ".flatpak-info", O_RDONLY | O_CLOEXEC | O_NOCTTY);
 | 
						|
	close (root_fd);
 | 
						|
	if (info_fd == -1) {
 | 
						|
		if (errno == ENOENT) {
 | 
						|
			pw_log_debug("no .flatpak-info, client on the host");
 | 
						|
			cinfo->is_sandboxed = false;
 | 
						|
			/* No file => on the host */
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
		asprintf(error, "error opening .flatpak-info: %m");
 | 
						|
		return false;
 | 
						|
        }
 | 
						|
	if (fstat (info_fd, &stat_buf) != 0 || !S_ISREG (stat_buf.st_mode)) {
 | 
						|
		/* Some weird fd => failure */
 | 
						|
		close(info_fd);
 | 
						|
		asprintf(error, "error fstat .flatpak-info: %m");
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
static bool
 | 
						|
check_global_owner(struct pw_core *core, struct pw_client *client, struct pw_global *global)
 | 
						|
{
 | 
						|
	struct pw_client *owner;
 | 
						|
	const struct ucred *owner_ucred, *client_ucred;
 | 
						|
 | 
						|
	if (global == NULL)
 | 
						|
		return false;
 | 
						|
 | 
						|
	owner = pw_global_get_owner(global);
 | 
						|
	if (owner == NULL)
 | 
						|
		return true;
 | 
						|
 | 
						|
	owner_ucred = pw_client_get_ucred(owner);
 | 
						|
	client_ucred = pw_client_get_ucred(client);
 | 
						|
 | 
						|
	if (owner_ucred == NULL || client_ucred == NULL)
 | 
						|
		return true;
 | 
						|
 | 
						|
	return owner_ucred->uid == client_ucred->uid;
 | 
						|
}
 | 
						|
 | 
						|
static uint32_t
 | 
						|
do_permission(struct pw_global *global, struct pw_client *client, void *data)
 | 
						|
{
 | 
						|
	struct impl *impl = data;
 | 
						|
 | 
						|
	if (pw_global_get_type(global) == impl->type->link) {
 | 
						|
		struct pw_link *link = pw_global_get_object(global);
 | 
						|
		struct pw_port *port;
 | 
						|
		struct pw_node *node;
 | 
						|
 | 
						|
		port = pw_link_get_output(link);
 | 
						|
		node = pw_port_get_node(port);
 | 
						|
		/* we must be able to see both nodes */
 | 
						|
		if (port && node && !check_global_owner(impl->core, client, pw_node_get_global(node)))
 | 
						|
			 return 0;
 | 
						|
 | 
						|
		port = pw_link_get_input(link);
 | 
						|
		node = pw_port_get_node(port);
 | 
						|
		if (port && node && !check_global_owner(impl->core, client, pw_node_get_global(node)))
 | 
						|
			 return 0;
 | 
						|
	}
 | 
						|
	else if (!check_global_owner(impl->core, client, global))
 | 
						|
		return 0;
 | 
						|
 | 
						|
	return PW_PERM_RWX;
 | 
						|
}
 | 
						|
 | 
						|
static DBusHandlerResult
 | 
						|
portal_response(DBusConnection *connection, DBusMessage *msg, void *user_data)
 | 
						|
{
 | 
						|
	struct client_info *cinfo = user_data;
 | 
						|
 | 
						|
	if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) {
 | 
						|
		uint32_t response = 2;
 | 
						|
		DBusError error;
 | 
						|
		struct async_pending *p;
 | 
						|
 | 
						|
		dbus_error_init(&error);
 | 
						|
 | 
						|
		dbus_connection_remove_filter(connection, portal_response, cinfo);
 | 
						|
 | 
						|
		if (!dbus_message_get_args
 | 
						|
		    (msg, &error, DBUS_TYPE_UINT32, &response, DBUS_TYPE_INVALID)) {
 | 
						|
			pw_log_error("failed to parse Response: %s", error.message);
 | 
						|
			dbus_error_free(&error);
 | 
						|
		}
 | 
						|
 | 
						|
		p = find_pending(cinfo, dbus_message_get_path(msg));
 | 
						|
		if (p == NULL)
 | 
						|
			return DBUS_HANDLER_RESULT_HANDLED;
 | 
						|
 | 
						|
		p->handled = true;
 | 
						|
 | 
						|
		pw_log_debug("portal check result: %d", response);
 | 
						|
 | 
						|
		if (response == 0) {
 | 
						|
			pw_resource_do_parent(p->resource->resource,
 | 
						|
					      &p->resource->override,
 | 
						|
					      struct pw_core_proxy_methods,
 | 
						|
					      create_object,
 | 
						|
					      p->factory_name,
 | 
						|
					      p->type,
 | 
						|
					      p->version,
 | 
						|
					      &p->properties->dict,
 | 
						|
					      p->new_id);
 | 
						|
		} else {
 | 
						|
			pw_resource_error(p->resource->resource, SPA_RESULT_NO_PERMISSION, "not allowed");
 | 
						|
 | 
						|
		}
 | 
						|
		free_pending(p);
 | 
						|
		pw_client_set_busy(cinfo->client, false);
 | 
						|
 | 
						|
		return DBUS_HANDLER_RESULT_HANDLED;
 | 
						|
	}
 | 
						|
	return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void do_create_object(void *data,
 | 
						|
			     const char *factory_name,
 | 
						|
			     uint32_t type,
 | 
						|
			     uint32_t version,
 | 
						|
			     const struct spa_dict *props,
 | 
						|
			     uint32_t new_id)
 | 
						|
{
 | 
						|
	struct resource *resource = data;
 | 
						|
	struct client_info *cinfo = resource->cinfo;
 | 
						|
	struct impl *impl = cinfo->impl;
 | 
						|
	struct pw_client *client = cinfo->client;
 | 
						|
	DBusMessage *m = NULL, *r = NULL;
 | 
						|
	DBusError error;
 | 
						|
	pid_t pid;
 | 
						|
	DBusMessageIter msg_iter;
 | 
						|
	DBusMessageIter dict_iter;
 | 
						|
	const char *handle;
 | 
						|
	const char *device;
 | 
						|
	struct async_pending *p;
 | 
						|
 | 
						|
	if (!cinfo->is_sandboxed) {
 | 
						|
		pw_resource_do_parent(resource->resource,
 | 
						|
				      &resource->override,
 | 
						|
				      struct pw_core_proxy_methods,
 | 
						|
				      create_object,
 | 
						|
				      factory_name,
 | 
						|
				      type,
 | 
						|
				      version,
 | 
						|
				      props,
 | 
						|
				      new_id);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if (strcmp(factory_name, "client-node") != 0) {
 | 
						|
		pw_log_error("can only allow client-node");
 | 
						|
		goto not_allowed;
 | 
						|
	}
 | 
						|
 | 
						|
	pw_log_info("ask portal for client %p", cinfo->client);
 | 
						|
	pw_client_set_busy(client, true);
 | 
						|
 | 
						|
	dbus_error_init(&error);
 | 
						|
 | 
						|
	if (!(m = dbus_message_new_method_call("org.freedesktop.portal.Desktop",
 | 
						|
					       "/org/freedesktop/portal/desktop",
 | 
						|
					       "org.freedesktop.portal.Device", "AccessDevice")))
 | 
						|
		goto no_method_call;
 | 
						|
 | 
						|
	device = "camera";
 | 
						|
 | 
						|
	pid = pw_client_get_ucred(cinfo->client)->pid;
 | 
						|
	if (!dbus_message_append_args(m, DBUS_TYPE_UINT32, &pid, DBUS_TYPE_INVALID))
 | 
						|
		goto message_failed;
 | 
						|
 | 
						|
	dbus_message_iter_init_append(m, &msg_iter);
 | 
						|
	dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "s", &dict_iter);
 | 
						|
	dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &device);
 | 
						|
	dbus_message_iter_close_container(&msg_iter, &dict_iter);
 | 
						|
 | 
						|
	dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter);
 | 
						|
	dbus_message_iter_close_container(&msg_iter, &dict_iter);
 | 
						|
 | 
						|
	if (!(r = dbus_connection_send_with_reply_and_block(impl->bus, m, -1, &error)))
 | 
						|
		goto send_failed;
 | 
						|
 | 
						|
	dbus_message_unref(m);
 | 
						|
 | 
						|
	if (!dbus_message_get_args(r, &error, DBUS_TYPE_OBJECT_PATH, &handle, DBUS_TYPE_INVALID))
 | 
						|
		goto parse_failed;
 | 
						|
 | 
						|
	dbus_message_unref(r);
 | 
						|
 | 
						|
	dbus_bus_add_match(impl->bus,
 | 
						|
			   "type='signal',interface='org.freedesktop.portal.Request'", &error);
 | 
						|
	dbus_connection_flush(impl->bus);
 | 
						|
	if (dbus_error_is_set(&error))
 | 
						|
		goto subscribe_failed;
 | 
						|
 | 
						|
	dbus_connection_add_filter(impl->bus, portal_response, cinfo, NULL);
 | 
						|
 | 
						|
	p = calloc(1, sizeof(struct async_pending));
 | 
						|
	p->resource = resource;
 | 
						|
	p->handle = strdup(handle);
 | 
						|
	p->handled = false;
 | 
						|
	p->factory_name = strdup(factory_name);
 | 
						|
	p->type = type;
 | 
						|
	p->version = version;
 | 
						|
	p->properties = props ? pw_properties_new_dict(props) : NULL;
 | 
						|
	p->new_id = new_id;
 | 
						|
 | 
						|
	pw_log_debug("pending %p: handle %s", p, handle);
 | 
						|
	spa_list_append(&cinfo->async_pending, &p->link);
 | 
						|
 | 
						|
	return;
 | 
						|
 | 
						|
      no_method_call:
 | 
						|
	pw_log_error("Failed to create message");
 | 
						|
	goto not_allowed;
 | 
						|
      message_failed:
 | 
						|
	dbus_message_unref(m);
 | 
						|
	goto not_allowed;
 | 
						|
      send_failed:
 | 
						|
	pw_log_error("Failed to call portal: %s", error.message);
 | 
						|
	dbus_error_free(&error);
 | 
						|
	dbus_message_unref(m);
 | 
						|
	goto not_allowed;
 | 
						|
      parse_failed:
 | 
						|
	pw_log_error("Failed to parse AccessDevice result: %s", error.message);
 | 
						|
	dbus_error_free(&error);
 | 
						|
	dbus_message_unref(r);
 | 
						|
	goto not_allowed;
 | 
						|
      subscribe_failed:
 | 
						|
	pw_log_error("Failed to subscribe to Request signal: %s", error.message);
 | 
						|
	dbus_error_free(&error);
 | 
						|
	goto not_allowed;
 | 
						|
      not_allowed:
 | 
						|
	pw_resource_error(cinfo->core_resource->resource, SPA_RESULT_NO_PERMISSION, "not allowed");
 | 
						|
	return;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
do_create_link(void *data,
 | 
						|
	       uint32_t output_node_id,
 | 
						|
	       uint32_t output_port_id,
 | 
						|
	       uint32_t input_node_id,
 | 
						|
	       uint32_t input_port_id,
 | 
						|
	       const struct spa_pod_object *filter,
 | 
						|
	       const struct spa_dict *props,
 | 
						|
	       uint32_t new_id)
 | 
						|
{
 | 
						|
	struct resource *resource = data;
 | 
						|
	struct client_info *cinfo = resource->cinfo;
 | 
						|
 | 
						|
	if (cinfo->is_sandboxed) {
 | 
						|
		pw_resource_error(resource->resource, SPA_RESULT_NO_PERMISSION, "not allowed");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	pw_resource_do_parent(resource->resource,
 | 
						|
			      &resource->override,
 | 
						|
			      struct pw_core_proxy_methods,
 | 
						|
			      create_link,
 | 
						|
			      output_node_id,
 | 
						|
			      output_port_id,
 | 
						|
			      input_node_id,
 | 
						|
			      input_port_id,
 | 
						|
			      filter,
 | 
						|
			      props,
 | 
						|
			      new_id);
 | 
						|
}
 | 
						|
 | 
						|
static const struct pw_core_proxy_methods core_override = {
 | 
						|
	PW_VERSION_CORE_PROXY_METHODS,
 | 
						|
	.create_object = do_create_object,
 | 
						|
	.create_link = do_create_link,
 | 
						|
};
 | 
						|
 | 
						|
static void client_resource_impl(void *data, struct pw_resource *resource)
 | 
						|
{
 | 
						|
	struct client_info *cinfo = data;
 | 
						|
	struct impl *impl = cinfo->impl;
 | 
						|
 | 
						|
	if (pw_resource_get_type(resource) == impl->type->core) {
 | 
						|
		struct resource *r;
 | 
						|
 | 
						|
		r = calloc(1, sizeof(struct resource));
 | 
						|
		r->cinfo = cinfo;
 | 
						|
		r->resource = resource;
 | 
						|
		spa_list_append(&cinfo->resources, &r->link);
 | 
						|
 | 
						|
		if (pw_resource_get_id(resource) == 0)
 | 
						|
			cinfo->core_resource = r;
 | 
						|
 | 
						|
		pw_log_debug("module %p: add core override", impl);
 | 
						|
		pw_resource_add_override(resource,
 | 
						|
					 &r->override,
 | 
						|
					 &core_override,
 | 
						|
					 r);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
const struct pw_client_events client_events = {
 | 
						|
	PW_VERSION_CLIENT_EVENTS,
 | 
						|
	.resource_impl = client_resource_impl,
 | 
						|
};
 | 
						|
 | 
						|
static void
 | 
						|
core_global_added(void *data, struct pw_global *global)
 | 
						|
{
 | 
						|
	struct impl *impl = data;
 | 
						|
 | 
						|
	if (pw_global_get_type(global) == impl->type->client) {
 | 
						|
		struct pw_client *client = pw_global_get_object(global);
 | 
						|
		struct client_info *cinfo;
 | 
						|
		char *error;
 | 
						|
 | 
						|
		cinfo = calloc(1, sizeof(struct client_info));
 | 
						|
		cinfo->impl = impl;
 | 
						|
		cinfo->client = client;
 | 
						|
		if (!check_sandboxed(cinfo, &error)) {
 | 
						|
			pw_log_warn("module %p: client %p sandbox check failed: %s", impl, client, error);
 | 
						|
			free(error);
 | 
						|
		}
 | 
						|
		spa_list_init(&cinfo->async_pending);
 | 
						|
		spa_list_init(&cinfo->resources);
 | 
						|
 | 
						|
		pw_client_add_listener(client, &cinfo->client_listener, &client_events, cinfo);
 | 
						|
 | 
						|
		spa_list_append(&impl->client_list, &cinfo->link);
 | 
						|
 | 
						|
		pw_log_debug("module %p: client %p added", impl, client);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
core_global_removed(void *data, struct pw_global *global)
 | 
						|
{
 | 
						|
	struct impl *impl = data;
 | 
						|
 | 
						|
	if (pw_global_get_type(global) == impl->type->client) {
 | 
						|
		struct pw_client *client = pw_global_get_object(global);
 | 
						|
		struct client_info *cinfo;
 | 
						|
 | 
						|
		if ((cinfo = find_client_info(impl, client)))
 | 
						|
			client_info_free(cinfo);
 | 
						|
 | 
						|
		pw_log_debug("module %p: client %p removed", impl, client);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
const struct pw_core_events core_events = {
 | 
						|
	PW_VERSION_CORE_EVENTS,
 | 
						|
	.global_added = core_global_added,
 | 
						|
	.global_removed = core_global_removed,
 | 
						|
};
 | 
						|
 | 
						|
static void dispatch_cb(void *userdata)
 | 
						|
{
 | 
						|
	struct impl *impl = userdata;
 | 
						|
 | 
						|
	if (dbus_connection_dispatch(impl->bus) == DBUS_DISPATCH_COMPLETE)
 | 
						|
		pw_loop_enable_idle(pw_core_get_main_loop(impl->core), impl->dispatch_event, false);
 | 
						|
}
 | 
						|
 | 
						|
static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, void *userdata)
 | 
						|
{
 | 
						|
	struct impl *impl = userdata;
 | 
						|
 | 
						|
	pw_loop_enable_idle(pw_core_get_main_loop(impl->core),
 | 
						|
			    impl->dispatch_event, status == DBUS_DISPATCH_COMPLETE ? false : true);
 | 
						|
}
 | 
						|
 | 
						|
static inline enum spa_io dbus_to_io(DBusWatch *watch)
 | 
						|
{
 | 
						|
	enum spa_io mask;
 | 
						|
	unsigned int flags;
 | 
						|
 | 
						|
	/* no watch flags for disabled watches */
 | 
						|
	if (!dbus_watch_get_enabled(watch))
 | 
						|
		return 0;
 | 
						|
 | 
						|
	flags = dbus_watch_get_flags(watch);
 | 
						|
	mask = SPA_IO_HUP | SPA_IO_ERR;
 | 
						|
 | 
						|
	if (flags & DBUS_WATCH_READABLE)
 | 
						|
		mask |= SPA_IO_IN;
 | 
						|
	if (flags & DBUS_WATCH_WRITABLE)
 | 
						|
		mask |= SPA_IO_OUT;
 | 
						|
 | 
						|
	return mask;
 | 
						|
}
 | 
						|
 | 
						|
static inline unsigned int io_to_dbus(enum spa_io mask)
 | 
						|
{
 | 
						|
	unsigned int flags = 0;
 | 
						|
 | 
						|
	if (mask & SPA_IO_IN)
 | 
						|
		flags |= DBUS_WATCH_READABLE;
 | 
						|
	if (mask & SPA_IO_OUT)
 | 
						|
		flags |= DBUS_WATCH_WRITABLE;
 | 
						|
	if (mask & SPA_IO_HUP)
 | 
						|
		flags |= DBUS_WATCH_HANGUP;
 | 
						|
	if (mask & SPA_IO_ERR)
 | 
						|
		flags |= DBUS_WATCH_ERROR;
 | 
						|
	return flags;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
handle_io_event(void *userdata, int fd, enum spa_io mask)
 | 
						|
{
 | 
						|
	DBusWatch *watch = userdata;
 | 
						|
 | 
						|
	if (!dbus_watch_get_enabled(watch)) {
 | 
						|
		pw_log_warn("Asked to handle disabled watch: %p %i", (void *) watch, fd);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	dbus_watch_handle(watch, io_to_dbus(mask));
 | 
						|
}
 | 
						|
 | 
						|
static dbus_bool_t add_watch(DBusWatch *watch, void *userdata)
 | 
						|
{
 | 
						|
	struct impl *impl = userdata;
 | 
						|
	struct spa_source *source;
 | 
						|
 | 
						|
	pw_log_debug("add watch %p %d", watch, dbus_watch_get_unix_fd(watch));
 | 
						|
 | 
						|
	/* we dup because dbus tends to add the same fd multiple times and our epoll
 | 
						|
	 * implementation does not like that */
 | 
						|
	source = pw_loop_add_io(pw_core_get_main_loop(impl->core),
 | 
						|
				dup(dbus_watch_get_unix_fd(watch)),
 | 
						|
				dbus_to_io(watch), true, handle_io_event, watch);
 | 
						|
 | 
						|
	dbus_watch_set_data(watch, source, NULL);
 | 
						|
	return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
static void remove_watch(DBusWatch *watch, void *userdata)
 | 
						|
{
 | 
						|
	struct impl *impl = userdata;
 | 
						|
	struct spa_source *source;
 | 
						|
 | 
						|
	if ((source = dbus_watch_get_data(watch)))
 | 
						|
		pw_loop_destroy_source(pw_core_get_main_loop(impl->core), source);
 | 
						|
}
 | 
						|
 | 
						|
static void toggle_watch(DBusWatch *watch, void *userdata)
 | 
						|
{
 | 
						|
	struct impl *impl = userdata;
 | 
						|
	struct spa_source *source;
 | 
						|
 | 
						|
	source = dbus_watch_get_data(watch);
 | 
						|
 | 
						|
	pw_loop_update_io(pw_core_get_main_loop(impl->core), source, dbus_to_io(watch));
 | 
						|
}
 | 
						|
struct timeout_data {
 | 
						|
	struct spa_source *source;
 | 
						|
	struct impl *impl;
 | 
						|
};
 | 
						|
 | 
						|
static void
 | 
						|
handle_timer_event(void *userdata, uint64_t expirations)
 | 
						|
{
 | 
						|
	DBusTimeout *timeout = userdata;
 | 
						|
	uint64_t t;
 | 
						|
	struct timespec ts;
 | 
						|
	struct timeout_data *data = dbus_timeout_get_data(timeout);
 | 
						|
	struct impl *impl = data->impl;
 | 
						|
 | 
						|
	if (dbus_timeout_get_enabled(timeout)) {
 | 
						|
		t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC;
 | 
						|
		ts.tv_sec = t / SPA_NSEC_PER_SEC;
 | 
						|
		ts.tv_nsec = t % SPA_NSEC_PER_SEC;
 | 
						|
		pw_loop_update_timer(pw_core_get_main_loop(impl->core),
 | 
						|
				     data->source, &ts, NULL, false);
 | 
						|
		dbus_timeout_handle(timeout);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static dbus_bool_t add_timeout(DBusTimeout *timeout, void *userdata)
 | 
						|
{
 | 
						|
	struct impl *impl = userdata;
 | 
						|
	struct timespec ts;
 | 
						|
	struct timeout_data *data;
 | 
						|
	uint64_t t;
 | 
						|
 | 
						|
	if (!dbus_timeout_get_enabled(timeout))
 | 
						|
		return FALSE;
 | 
						|
 | 
						|
	data = calloc(1, sizeof(struct timeout_data));
 | 
						|
	data->impl = impl;
 | 
						|
	data->source = pw_loop_add_timer(pw_core_get_main_loop(impl->core), handle_timer_event, timeout);
 | 
						|
	dbus_timeout_set_data(timeout, data, NULL);
 | 
						|
 | 
						|
	t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC;
 | 
						|
	ts.tv_sec = t / SPA_NSEC_PER_SEC;
 | 
						|
	ts.tv_nsec = t % SPA_NSEC_PER_SEC;
 | 
						|
	pw_loop_update_timer(pw_core_get_main_loop(impl->core), data->source, &ts, NULL, false);
 | 
						|
 | 
						|
	return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
static void remove_timeout(DBusTimeout *timeout, void *userdata)
 | 
						|
{
 | 
						|
	struct impl *impl = userdata;
 | 
						|
	struct timeout_data *data;
 | 
						|
 | 
						|
	if ((data = dbus_timeout_get_data(timeout))) {
 | 
						|
		pw_loop_destroy_source(pw_core_get_main_loop(impl->core), data->source);
 | 
						|
		free(data);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void toggle_timeout(DBusTimeout *timeout, void *userdata)
 | 
						|
{
 | 
						|
	struct impl *impl = userdata;
 | 
						|
	struct timeout_data *data;
 | 
						|
	struct timespec ts, *tsp;
 | 
						|
 | 
						|
	data = dbus_timeout_get_data(timeout);
 | 
						|
 | 
						|
	if (dbus_timeout_get_enabled(timeout)) {
 | 
						|
		uint64_t t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC;
 | 
						|
		ts.tv_sec = t / SPA_NSEC_PER_SEC;
 | 
						|
		ts.tv_nsec = t % SPA_NSEC_PER_SEC;
 | 
						|
		tsp = &ts;
 | 
						|
	} else {
 | 
						|
		tsp = NULL;
 | 
						|
	}
 | 
						|
	pw_loop_update_timer(pw_core_get_main_loop(impl->core), data->source, tsp, NULL, false);
 | 
						|
}
 | 
						|
 | 
						|
static void wakeup_main(void *userdata)
 | 
						|
{
 | 
						|
	struct impl *impl = userdata;
 | 
						|
 | 
						|
	pw_loop_enable_idle(pw_core_get_main_loop(impl->core), impl->dispatch_event, true);
 | 
						|
}
 | 
						|
 | 
						|
static void module_destroy(void *data)
 | 
						|
{
 | 
						|
	struct impl *impl = data;
 | 
						|
	struct client_info *info, *t;
 | 
						|
 | 
						|
	spa_hook_remove(&impl->core_listener);
 | 
						|
	spa_hook_remove(&impl->module_listener);
 | 
						|
 | 
						|
	dbus_connection_close(impl->bus);
 | 
						|
	dbus_connection_unref(impl->bus);
 | 
						|
 | 
						|
	spa_list_for_each_safe(info, t, &impl->client_list, link)
 | 
						|
		client_info_free(info);
 | 
						|
 | 
						|
	pw_loop_destroy_source(pw_core_get_main_loop(impl->core), impl->dispatch_event);
 | 
						|
 | 
						|
	if (impl->properties)
 | 
						|
		pw_properties_free(impl->properties);
 | 
						|
 | 
						|
	free(impl);
 | 
						|
}
 | 
						|
 | 
						|
const struct pw_module_events module_events = {
 | 
						|
	PW_VERSION_MODULE_EVENTS,
 | 
						|
	.destroy = module_destroy,
 | 
						|
};
 | 
						|
 | 
						|
static bool module_init(struct pw_module *module, struct pw_properties *properties)
 | 
						|
{
 | 
						|
	struct pw_core *core = pw_module_get_core(module);
 | 
						|
	struct impl *impl;
 | 
						|
	DBusError error;
 | 
						|
 | 
						|
	dbus_error_init(&error);
 | 
						|
 | 
						|
	impl = calloc(1, sizeof(struct impl));
 | 
						|
	pw_log_debug("module %p: new", impl);
 | 
						|
 | 
						|
	impl->core = core;
 | 
						|
	impl->type = pw_core_get_type(core);
 | 
						|
	impl->properties = properties;
 | 
						|
 | 
						|
	impl->bus = dbus_bus_get_private(DBUS_BUS_SESSION, &error);
 | 
						|
	if (impl->bus == NULL)
 | 
						|
		goto error;
 | 
						|
 | 
						|
	impl->dispatch_event = pw_loop_add_idle(pw_core_get_main_loop(core), false, dispatch_cb, impl);
 | 
						|
 | 
						|
	dbus_connection_set_exit_on_disconnect(impl->bus, false);
 | 
						|
	dbus_connection_set_dispatch_status_function(impl->bus, dispatch_status, impl, NULL);
 | 
						|
	dbus_connection_set_watch_functions(impl->bus, add_watch, remove_watch, toggle_watch, impl,
 | 
						|
					    NULL);
 | 
						|
	dbus_connection_set_timeout_functions(impl->bus, add_timeout, remove_timeout,
 | 
						|
					      toggle_timeout, impl, NULL);
 | 
						|
	dbus_connection_set_wakeup_main_function(impl->bus, wakeup_main, impl, NULL);
 | 
						|
 | 
						|
	spa_list_init(&impl->client_list);
 | 
						|
 | 
						|
	pw_core_add_listener(core, &impl->core_listener, &core_events, impl);
 | 
						|
	pw_module_add_listener(module, &impl->module_listener, &module_events, impl);
 | 
						|
 | 
						|
	pw_core_set_permission_callback(core, do_permission, impl);
 | 
						|
 | 
						|
	return true;
 | 
						|
 | 
						|
      error:
 | 
						|
	free(impl);
 | 
						|
	pw_log_error("Failed to connect to system bus: %s", error.message);
 | 
						|
	dbus_error_free(&error);
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
#if 0
 | 
						|
static void module_destroy(struct impl *impl)
 | 
						|
{
 | 
						|
	pw_log_debug("module %p: destroy", impl);
 | 
						|
 | 
						|
	dbus_connection_close(impl->bus);
 | 
						|
	dbus_connection_unref(impl->bus);
 | 
						|
	free(impl);
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
bool pipewire__module_init(struct pw_module *module, const char *args)
 | 
						|
{
 | 
						|
	return module_init(module, NULL);
 | 
						|
}
 |