mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-11-03 09:01:54 -05:00 
			
		
		
		
	pulse-server: add gsettings module
Uses a thread to monitor gsettings data. Loads and unload modules based on gsettings. This makes paprefs work.
This commit is contained in:
		
							parent
							
								
									89d4cafec4
								
							
						
					
					
						commit
						37439d2b73
					
				
					 4 changed files with 313 additions and 0 deletions
				
			
		
							
								
								
									
										298
									
								
								src/modules/module-protocol-pulse/modules/module-gsettings.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										298
									
								
								src/modules/module-protocol-pulse/modules/module-gsettings.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,298 @@
 | 
			
		|||
/* PipeWire
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com>
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a
 | 
			
		||||
 * copy of this software and associated documentation files (the "Software"),
 | 
			
		||||
 * to deal in the Software without restriction, including without limitation
 | 
			
		||||
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 | 
			
		||||
 * and/or sell copies of the Software, and to permit persons to whom the
 | 
			
		||||
 * Software is furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice (including the next
 | 
			
		||||
 * paragraph) shall be included in all copies or substantial portions of the
 | 
			
		||||
 * Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 | 
			
		||||
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 | 
			
		||||
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 | 
			
		||||
 * DEALINGS IN THE SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <gio/gio.h>
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
 | 
			
		||||
#include <spa/debug/mem.h>
 | 
			
		||||
#include <pipewire/pipewire.h>
 | 
			
		||||
#include <pipewire/thread.h>
 | 
			
		||||
 | 
			
		||||
#include "../module.h"
 | 
			
		||||
 | 
			
		||||
#define NAME "gsettings"
 | 
			
		||||
 | 
			
		||||
PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
 | 
			
		||||
#define PW_LOG_TOPIC_DEFAULT mod_topic
 | 
			
		||||
 | 
			
		||||
#define PA_GSETTINGS_MODULE_GROUP_SCHEMA "org.freedesktop.pulseaudio.module-group"
 | 
			
		||||
#define PA_GSETTINGS_MODULE_GROUPS_SCHEMA "org.freedesktop.pulseaudio.module-groups"
 | 
			
		||||
#define PA_GSETTINGS_MODULE_GROUPS_PATH "/org/freedesktop/pulseaudio/module-groups/"
 | 
			
		||||
 | 
			
		||||
#define MAX_MODULES	10
 | 
			
		||||
 | 
			
		||||
struct module_gsettings_data {
 | 
			
		||||
	struct module *module;
 | 
			
		||||
 | 
			
		||||
	GMainContext *context;
 | 
			
		||||
	GMainLoop *loop;
 | 
			
		||||
	struct spa_thread *thr;
 | 
			
		||||
 | 
			
		||||
	GSettings *settings;
 | 
			
		||||
	gchar **group_names;
 | 
			
		||||
 | 
			
		||||
	struct spa_list groups;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct group {
 | 
			
		||||
	struct spa_list link;
 | 
			
		||||
	char *name;
 | 
			
		||||
	struct module *module;
 | 
			
		||||
	struct spa_hook module_listener;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct info {
 | 
			
		||||
	bool enabled;
 | 
			
		||||
	char *name;
 | 
			
		||||
	char *module[MAX_MODULES];
 | 
			
		||||
	char *args[MAX_MODULES];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void clean_info(const struct info *info)
 | 
			
		||||
{
 | 
			
		||||
	int i;
 | 
			
		||||
	for (i = 0; i < MAX_MODULES; i++) {
 | 
			
		||||
		g_free(info->module[i]);
 | 
			
		||||
		g_free(info->args[i]);
 | 
			
		||||
	}
 | 
			
		||||
	g_free(info->name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void unload_module(struct module_gsettings_data *d, struct group *g)
 | 
			
		||||
{
 | 
			
		||||
	spa_list_remove(&g->link);
 | 
			
		||||
	g_free(g->name);
 | 
			
		||||
	if (g->module)
 | 
			
		||||
		module_unload(g->module);
 | 
			
		||||
	free(g);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void unload_group(struct module_gsettings_data *d, const char *name)
 | 
			
		||||
{
 | 
			
		||||
	struct group *g, *t;
 | 
			
		||||
	spa_list_for_each_safe(g, t, &d->groups, link) {
 | 
			
		||||
		if (spa_streq(g->name, name))
 | 
			
		||||
			unload_module(d, g);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
static void module_destroy(void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct group *g = data;
 | 
			
		||||
	if (g->module) {
 | 
			
		||||
		spa_hook_remove(&g->module_listener);
 | 
			
		||||
		g->module = NULL;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const struct module_events module_gsettings_events = {
 | 
			
		||||
	VERSION_MODULE_EVENTS,
 | 
			
		||||
	.destroy = module_destroy
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int load_group(struct module_gsettings_data *d, const struct info *info)
 | 
			
		||||
{
 | 
			
		||||
	struct group *g;
 | 
			
		||||
	int i, res;
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < MAX_MODULES; i++) {
 | 
			
		||||
		if (info->module[i] == NULL || strlen(info->module[i]) <= 0)
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		g = calloc(1, sizeof(struct group));
 | 
			
		||||
		if (g == NULL)
 | 
			
		||||
			return -errno;
 | 
			
		||||
 | 
			
		||||
		g->name = strdup(info->name);
 | 
			
		||||
		g->module = module_create(d->module->impl, info->module[i], info->args[i]);
 | 
			
		||||
		if (g->module == NULL) {
 | 
			
		||||
			pw_log_info("can't create module:%s args:%s: %m",
 | 
			
		||||
					info->module[i], info->args[i]);
 | 
			
		||||
		} else {
 | 
			
		||||
			module_add_listener(g->module, &g->module_listener,
 | 
			
		||||
					&module_gsettings_events, g);
 | 
			
		||||
			if ((res = module_load(g->module)) < 0) {
 | 
			
		||||
				pw_log_warn("can't load module:%s args:%s: %s",
 | 
			
		||||
						info->module[i], info->args[i],
 | 
			
		||||
						spa_strerror(res));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		spa_list_append(&d->groups, &g->link);
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
do_handle_info(struct spa_loop *loop,
 | 
			
		||||
		bool async, uint32_t seq, const void *data, size_t size, void *user_data)
 | 
			
		||||
{
 | 
			
		||||
	struct module_gsettings_data *d = user_data;
 | 
			
		||||
	const struct info *info = data;
 | 
			
		||||
 | 
			
		||||
	unload_group(d, info->name);
 | 
			
		||||
	if (info->enabled)
 | 
			
		||||
		load_group(d, info);
 | 
			
		||||
 | 
			
		||||
	clean_info(info);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void handle_module_group(struct module_gsettings_data *d, gchar *name)
 | 
			
		||||
{
 | 
			
		||||
	struct impl *impl = d->module->impl;
 | 
			
		||||
	GSettings *settings;
 | 
			
		||||
	gchar p[1024];
 | 
			
		||||
	struct info info;
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	snprintf(p, sizeof(p), PA_GSETTINGS_MODULE_GROUPS_PATH"%s/", name);
 | 
			
		||||
 | 
			
		||||
	settings = g_settings_new_with_path(PA_GSETTINGS_MODULE_GROUP_SCHEMA, p);
 | 
			
		||||
	if (settings == NULL)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	spa_zero(info);
 | 
			
		||||
	info.name = strdup(p);
 | 
			
		||||
	info.enabled = g_settings_get_boolean(settings, "enabled");
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < MAX_MODULES; i++) {
 | 
			
		||||
		snprintf(p, sizeof(p), "name%d", i);
 | 
			
		||||
		info.module[i] = g_settings_get_string(settings, p);
 | 
			
		||||
 | 
			
		||||
		snprintf(p, sizeof(p), "args%i", i);
 | 
			
		||||
		info.args[i] = g_settings_get_string(settings, p);
 | 
			
		||||
	}
 | 
			
		||||
	pw_loop_invoke(impl->loop, do_handle_info, 0,
 | 
			
		||||
			&info, sizeof(info), false, d);
 | 
			
		||||
 | 
			
		||||
	g_object_unref(G_OBJECT(settings));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void module_group_callback(GSettings *settings, gchar *key, gpointer user_data)
 | 
			
		||||
{
 | 
			
		||||
	struct module_gsettings_data *d = g_object_get_data(G_OBJECT(settings), "module-data");
 | 
			
		||||
	handle_module_group(d, user_data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void *do_loop(void *user_data)
 | 
			
		||||
{
 | 
			
		||||
	struct module_gsettings_data *d = user_data;
 | 
			
		||||
 | 
			
		||||
	pw_log_info("enter");
 | 
			
		||||
	g_main_context_push_thread_default(d->context);
 | 
			
		||||
 | 
			
		||||
	d->loop = g_main_loop_new(d->context, FALSE);
 | 
			
		||||
 | 
			
		||||
	g_main_loop_run(d->loop);
 | 
			
		||||
 | 
			
		||||
	g_main_context_pop_thread_default(d->context);
 | 
			
		||||
	g_main_loop_unref (d->loop);
 | 
			
		||||
	d->loop = NULL;
 | 
			
		||||
	pw_log_info("leave");
 | 
			
		||||
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int module_gsettings_load(struct module *module)
 | 
			
		||||
{
 | 
			
		||||
	struct module_gsettings_data *data = module->user_data;
 | 
			
		||||
	gchar **name;
 | 
			
		||||
 | 
			
		||||
	data->context = g_main_context_new();
 | 
			
		||||
	g_main_context_push_thread_default(data->context);
 | 
			
		||||
 | 
			
		||||
	data->settings = g_settings_new(PA_GSETTINGS_MODULE_GROUPS_SCHEMA);
 | 
			
		||||
	if (data->settings == NULL)
 | 
			
		||||
		return -EIO;
 | 
			
		||||
 | 
			
		||||
	data->group_names = g_settings_list_children(data->settings);
 | 
			
		||||
 | 
			
		||||
	for (name = data->group_names; *name; name++) {
 | 
			
		||||
		GSettings *child = g_settings_get_child(data->settings, *name);
 | 
			
		||||
		/* The child may have been removed between the
 | 
			
		||||
		 * g_settings_list_children() and g_settings_get_child() calls. */
 | 
			
		||||
		if (child == NULL)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		g_object_set_data(G_OBJECT(child), "module-data", data);
 | 
			
		||||
		g_signal_connect(child, "changed", (GCallback) module_group_callback, *name);
 | 
			
		||||
		handle_module_group(data, *name);
 | 
			
		||||
	}
 | 
			
		||||
	g_main_context_pop_thread_default(data->context);
 | 
			
		||||
 | 
			
		||||
	data->thr = pw_thread_utils_create(NULL, do_loop, data);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static gboolean
 | 
			
		||||
do_stop(gpointer data)
 | 
			
		||||
{
 | 
			
		||||
	struct module_gsettings_data *d = data;
 | 
			
		||||
	if (d->loop)
 | 
			
		||||
		g_main_loop_quit(d->loop);
 | 
			
		||||
	return FALSE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int module_gsettings_unload(struct module *module)
 | 
			
		||||
{
 | 
			
		||||
	struct module_gsettings_data *d = module->user_data;
 | 
			
		||||
	struct group *g;
 | 
			
		||||
 | 
			
		||||
	g_main_context_invoke(d->context, do_stop, d);
 | 
			
		||||
	pw_thread_utils_join(d->thr, NULL);
 | 
			
		||||
	g_main_context_unref(d->context);
 | 
			
		||||
 | 
			
		||||
	spa_list_consume(g, &d->groups, link)
 | 
			
		||||
		unload_module(d, g);
 | 
			
		||||
 | 
			
		||||
	g_strfreev(d->group_names);
 | 
			
		||||
	g_object_unref(G_OBJECT(d->settings));
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int module_gsettings_prepare(struct module * const module)
 | 
			
		||||
{
 | 
			
		||||
	PW_LOG_TOPIC_INIT(mod_topic);
 | 
			
		||||
 | 
			
		||||
	struct module_gsettings_data * const data = module->user_data;
 | 
			
		||||
	spa_list_init(&data->groups);
 | 
			
		||||
	data->module = module;
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const struct spa_dict_item module_gsettings_info[] = {
 | 
			
		||||
	{ PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
 | 
			
		||||
	{ PW_KEY_MODULE_DESCRIPTION, "GSettings Adapter" },
 | 
			
		||||
	{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
DEFINE_MODULE_INFO(module_gsettings) = {
 | 
			
		||||
	.name = "module-gsettings",
 | 
			
		||||
	.load_once = true,
 | 
			
		||||
	.prepare = module_gsettings_prepare,
 | 
			
		||||
	.load = module_gsettings_load,
 | 
			
		||||
	.unload = module_gsettings_unload,
 | 
			
		||||
	.properties = &SPA_DICT_INIT_ARRAY(module_gsettings_info),
 | 
			
		||||
	.data_size = sizeof(struct module_gsettings_data),
 | 
			
		||||
};
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue