mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-11-03 09:01:54 -05:00 
			
		
		
		
	Make sure we chain up to the same resource that we override, for this keep a per resource info that we pass around.
		
			
				
	
	
		
			829 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			829 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;
 | 
						|
	char *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);
 | 
						|
	free(p->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 client_is_sandboxed(struct pw_client *cl)
 | 
						|
{
 | 
						|
	char data[2048], *ptr;
 | 
						|
	size_t n, size;
 | 
						|
	const char *state = NULL;
 | 
						|
	const char *current;
 | 
						|
	bool result;
 | 
						|
	int fd;
 | 
						|
	pid_t pid;
 | 
						|
	const struct ucred *ucred;
 | 
						|
 | 
						|
	ucred = pw_client_get_ucred(cl);
 | 
						|
 | 
						|
	if (ucred) {
 | 
						|
		pw_log_info("client has trusted pid %d", ucred->pid);
 | 
						|
	} else {
 | 
						|
		pw_log_info("no trusted pid found, assuming not sandboxed\n");
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	pid = ucred->pid;
 | 
						|
 | 
						|
	sprintf(data, "/proc/%u/cgroup", pid);
 | 
						|
	fd = open(data, O_RDONLY | O_CLOEXEC, 0);
 | 
						|
	if (fd == -1)
 | 
						|
		return false;
 | 
						|
 | 
						|
	spa_zero(data);
 | 
						|
	size = sizeof(data);
 | 
						|
	ptr = data;
 | 
						|
 | 
						|
	while (size > 0) {
 | 
						|
		int r;
 | 
						|
 | 
						|
		if ((r = read(fd, data, size)) < 0) {
 | 
						|
			if (errno == EINTR)
 | 
						|
				continue;
 | 
						|
			else
 | 
						|
				break;
 | 
						|
		}
 | 
						|
		if (r == 0)
 | 
						|
			break;
 | 
						|
 | 
						|
		ptr += r;
 | 
						|
		size -= r;
 | 
						|
	}
 | 
						|
	close(fd);
 | 
						|
 | 
						|
	result = false;
 | 
						|
	while ((current = pw_split_walk(data, "\n", &n, &state)) != NULL) {
 | 
						|
		if (strncmp(current, "1:name=systemd:", strlen("1:name=systemd:")) == 0) {
 | 
						|
			const char *p = strstr(current, "flatpak-");
 | 
						|
			if (p && p - current < n) {
 | 
						|
				pw_log_info("found a flatpak cgroup, assuming sandboxed\n");
 | 
						|
				result = true;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
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_node,
 | 
						|
					      p->factory_name,
 | 
						|
					      p->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_node(void *data,
 | 
						|
			   const char *factory_name,
 | 
						|
			   const char *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_node,
 | 
						|
				      factory_name,
 | 
						|
				      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->name = strdup(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_format *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_node = do_create_node,
 | 
						|
	.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;
 | 
						|
 | 
						|
		cinfo = calloc(1, sizeof(struct client_info));
 | 
						|
		cinfo->impl = impl;
 | 
						|
		cinfo->client = client;
 | 
						|
		cinfo->is_sandboxed = client_is_sandboxed(client);
 | 
						|
		spa_list_init(&cinfo->async_pending);
 | 
						|
		spa_list_init(&cinfo->resources);
 | 
						|
 | 
						|
		pw_client_add_listener(client, &cinfo->client_listener, &client_events, cinfo);
 | 
						|
 | 
						|
		spa_list_insert(impl->client_list.prev, &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);
 | 
						|
}
 |