2008-08-03 23:23:13 +02:00
|
|
|
/***
|
|
|
|
|
This file is part of PulseAudio.
|
|
|
|
|
|
|
|
|
|
Copyright 2008 Lennart Poettering
|
2009-08-03 19:38:02 +03:00
|
|
|
Copyright 2009 Tanu Kaskinen
|
2008-08-03 23:23:13 +02:00
|
|
|
|
|
|
|
|
PulseAudio is free software; you can redistribute it and/or modify
|
|
|
|
|
it under the terms of the GNU Lesser General Public License as published
|
2009-03-03 20:23:02 +00:00
|
|
|
by the Free Software Foundation; either version 2.1 of the License,
|
2008-08-03 23:23:13 +02:00
|
|
|
or (at your option) any later version.
|
|
|
|
|
|
|
|
|
|
PulseAudio 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
|
|
|
|
|
General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
2014-11-26 14:14:51 +01:00
|
|
|
along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
|
2008-08-03 23:23:13 +02:00
|
|
|
***/
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
|
#include <config.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
2011-06-13 15:04:33 +02:00
|
|
|
#include <pulse/gccmacro.h>
|
2008-08-03 23:23:13 +02:00
|
|
|
#include <pulse/xmalloc.h>
|
|
|
|
|
#include <pulse/volume.h>
|
|
|
|
|
#include <pulse/timeval.h>
|
2009-04-05 02:13:43 +03:00
|
|
|
#include <pulse/rtclock.h>
|
2008-08-03 23:23:13 +02:00
|
|
|
|
|
|
|
|
#include <pulsecore/core-error.h>
|
|
|
|
|
#include <pulsecore/module.h>
|
|
|
|
|
#include <pulsecore/core-util.h>
|
|
|
|
|
#include <pulsecore/modargs.h>
|
|
|
|
|
#include <pulsecore/log.h>
|
|
|
|
|
#include <pulsecore/core-subscribe.h>
|
|
|
|
|
#include <pulsecore/sink-input.h>
|
|
|
|
|
#include <pulsecore/source-output.h>
|
|
|
|
|
#include <pulsecore/namereg.h>
|
2008-08-04 19:01:13 +02:00
|
|
|
#include <pulsecore/protocol-native.h>
|
|
|
|
|
#include <pulsecore/pstream.h>
|
|
|
|
|
#include <pulsecore/pstream-util.h>
|
2009-05-14 01:24:26 +02:00
|
|
|
#include <pulsecore/database.h>
|
2011-06-07 23:21:04 +01:00
|
|
|
#include <pulsecore/tagstruct.h>
|
2011-11-23 16:26:40 +05:30
|
|
|
#include <pulsecore/proplist-util.h>
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2009-08-03 19:38:02 +03:00
|
|
|
#ifdef HAVE_DBUS
|
|
|
|
|
#include <pulsecore/dbus-util.h>
|
|
|
|
|
#include <pulsecore/protocol-dbus.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2008-08-03 23:23:13 +02:00
|
|
|
PA_MODULE_AUTHOR("Lennart Poettering");
|
|
|
|
|
PA_MODULE_DESCRIPTION("Automatically restore the volume/mute/device state of streams");
|
|
|
|
|
PA_MODULE_VERSION(PACKAGE_VERSION);
|
2013-06-27 19:28:09 +02:00
|
|
|
PA_MODULE_LOAD_ONCE(true);
|
2009-01-28 00:19:47 +01:00
|
|
|
PA_MODULE_USAGE(
|
|
|
|
|
"restore_device=<Save/restore sinks/sources?> "
|
|
|
|
|
"restore_volume=<Save/restore volumes?> "
|
2009-06-22 22:35:55 +02:00
|
|
|
"restore_muted=<Save/restore muted states?> "
|
2019-12-07 11:36:25 +08:00
|
|
|
"on_hotplug=<This argument is obsolete, please remove it from configuration> "
|
2019-12-10 16:26:34 +08:00
|
|
|
"on_rescue=<This argument is obsolete, please remove it from configuration> "
|
2011-10-01 12:16:35 +01:00
|
|
|
"fallback_table=<filename>");
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2009-04-05 02:13:43 +03:00
|
|
|
#define SAVE_INTERVAL (10 * PA_USEC_PER_SEC)
|
2009-02-04 22:26:08 +01:00
|
|
|
#define IDENTIFICATION_PROPERTY "module-stream-restore.id"
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2011-10-01 12:16:35 +01:00
|
|
|
#define DEFAULT_FALLBACK_FILE PA_DEFAULT_CONFIG_DIR"/stream-restore.table"
|
|
|
|
|
#define DEFAULT_FALLBACK_FILE_USER "stream-restore.table"
|
|
|
|
|
|
|
|
|
|
#define WHITESPACE "\n\r \t"
|
|
|
|
|
|
2008-08-03 23:23:13 +02:00
|
|
|
static const char* const valid_modargs[] = {
|
|
|
|
|
"restore_device",
|
|
|
|
|
"restore_volume",
|
|
|
|
|
"restore_muted",
|
2009-06-22 22:35:55 +02:00
|
|
|
"on_hotplug",
|
|
|
|
|
"on_rescue",
|
2011-10-01 12:16:35 +01:00
|
|
|
"fallback_table",
|
2008-08-03 23:23:13 +02:00
|
|
|
NULL
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct userdata {
|
|
|
|
|
pa_core *core;
|
2008-08-04 19:00:43 +02:00
|
|
|
pa_module *module;
|
2008-08-03 23:23:13 +02:00
|
|
|
pa_subscription *subscription;
|
|
|
|
|
pa_hook_slot
|
|
|
|
|
*sink_input_new_hook_slot,
|
|
|
|
|
*sink_input_fixate_hook_slot,
|
2008-08-04 19:01:13 +02:00
|
|
|
*source_output_new_hook_slot,
|
2011-08-07 01:08:28 +02:00
|
|
|
*source_output_fixate_hook_slot,
|
2008-08-04 19:01:13 +02:00
|
|
|
*connection_unlink_hook_slot;
|
2008-08-03 23:23:13 +02:00
|
|
|
pa_time_event *save_time_event;
|
2009-05-14 01:24:26 +02:00
|
|
|
pa_database* database;
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2013-06-27 19:28:09 +02:00
|
|
|
bool restore_device:1;
|
|
|
|
|
bool restore_volume:1;
|
|
|
|
|
bool restore_muted:1;
|
2008-08-04 19:01:13 +02:00
|
|
|
|
|
|
|
|
pa_native_protocol *protocol;
|
|
|
|
|
pa_idxset *subscribed;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
#ifdef HAVE_DBUS
|
|
|
|
|
pa_dbus_protocol *dbus_protocol;
|
|
|
|
|
pa_hashmap *dbus_entries;
|
|
|
|
|
uint32_t next_index; /* For generating object paths for entries. */
|
|
|
|
|
#endif
|
2008-08-03 23:23:13 +02:00
|
|
|
};
|
|
|
|
|
|
stream-restore: Forget pre-14.0 stream routing
Prior to commits f899d5f4669dcd536cc142cee99fe359dd8af3d6 and
f62a49b8cf109c011a9818d2358beb6834e6ec25, GNOME's sound settings
overwrote the routing for all entries in the stream-restore database
when selecting a device. Now we prevent that from happening (see the
aforementioned commits), but the old overwritten settings can still be in
the database after updating to PulseAudio 14.0, and they can cause
problems, as documented here:
https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/832
We can't distinguish between devices set by GNOME's sound settings
and devices set by the user, so this patch discards all old device
settings, even though that is going to cause PulseAudio to forget routing
settings for many users. This is less bad than keeping the incorrect
routing settings in the database, because it's difficult for users to
figure out how to fix the situation when e.g. speaker test tones go to
the internal speakers no matter what device is selected as the default,
whereas old manual configuration can be restored restored by doing the
manual configuration again. Also, it's probably more common to have at
some point changed the default device in GNOME's sound settings than it
is to have any manual per-stream routing settings.
This is disabled by default, because this causes data loss, but
distributions that use GNOME are recommended to enable this with
the --enable-stream-restore-clear-old-devices (Autotools) or
-Dstream-restore-clear-old-devices=true (Meson) build option.
Fixes: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/832
2020-05-26 15:04:59 +03:00
|
|
|
#define ENTRY_VERSION 2
|
2009-02-04 18:31:24 +01:00
|
|
|
|
2009-02-13 18:02:47 +01:00
|
|
|
struct entry {
|
2013-06-27 19:28:09 +02:00
|
|
|
bool muted_valid, volume_valid, device_valid, card_valid;
|
|
|
|
|
bool muted;
|
2008-08-04 19:00:43 +02:00
|
|
|
pa_channel_map channel_map;
|
2009-04-13 22:50:24 +02:00
|
|
|
pa_cvolume volume;
|
2011-06-07 23:21:04 +01:00
|
|
|
char* device;
|
|
|
|
|
char* card;
|
|
|
|
|
};
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2008-08-04 19:01:13 +02:00
|
|
|
enum {
|
|
|
|
|
SUBCOMMAND_TEST,
|
|
|
|
|
SUBCOMMAND_READ,
|
|
|
|
|
SUBCOMMAND_WRITE,
|
|
|
|
|
SUBCOMMAND_DELETE,
|
|
|
|
|
SUBCOMMAND_SUBSCRIBE,
|
|
|
|
|
SUBCOMMAND_EVENT
|
|
|
|
|
};
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
static struct entry* entry_new(void);
|
|
|
|
|
static void entry_free(struct entry *e);
|
|
|
|
|
static struct entry *entry_read(struct userdata *u, const char *name);
|
2013-06-27 19:28:09 +02:00
|
|
|
static bool entry_write(struct userdata *u, const char *name, const struct entry *e, bool replace);
|
2011-06-07 23:21:04 +01:00
|
|
|
static struct entry* entry_copy(const struct entry *e);
|
|
|
|
|
static void entry_apply(struct userdata *u, const char *name, struct entry *e);
|
2009-08-03 19:38:02 +03:00
|
|
|
static void trigger_save(struct userdata *u);
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_DBUS
|
|
|
|
|
|
|
|
|
|
#define OBJECT_PATH "/org/pulseaudio/stream_restore1"
|
|
|
|
|
#define ENTRY_OBJECT_NAME "entry"
|
|
|
|
|
#define INTERFACE_STREAM_RESTORE "org.PulseAudio.Ext.StreamRestore1"
|
|
|
|
|
#define INTERFACE_ENTRY INTERFACE_STREAM_RESTORE ".RestoreEntry"
|
|
|
|
|
|
|
|
|
|
#define DBUS_INTERFACE_REVISION 0
|
|
|
|
|
|
|
|
|
|
struct dbus_entry {
|
|
|
|
|
struct userdata *userdata;
|
|
|
|
|
|
|
|
|
|
char *entry_name;
|
|
|
|
|
uint32_t index;
|
|
|
|
|
char *object_path;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata);
|
|
|
|
|
static void handle_get_entries(DBusConnection *conn, DBusMessage *msg, void *userdata);
|
|
|
|
|
|
|
|
|
|
static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
|
|
|
|
|
|
|
|
|
|
static void handle_add_entry(DBusConnection *conn, DBusMessage *msg, void *userdata);
|
|
|
|
|
static void handle_get_entry_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
|
|
|
|
|
|
|
|
|
|
static void handle_entry_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata);
|
|
|
|
|
static void handle_entry_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata);
|
|
|
|
|
static void handle_entry_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata);
|
2009-08-30 19:52:22 +03:00
|
|
|
static void handle_entry_set_device(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
|
2009-08-03 19:38:02 +03:00
|
|
|
static void handle_entry_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata);
|
2009-08-30 19:52:22 +03:00
|
|
|
static void handle_entry_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
|
2009-08-31 17:17:09 +03:00
|
|
|
static void handle_entry_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata);
|
|
|
|
|
static void handle_entry_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
static void handle_entry_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata);
|
|
|
|
|
|
|
|
|
|
static void handle_entry_remove(DBusConnection *conn, DBusMessage *msg, void *userdata);
|
|
|
|
|
|
|
|
|
|
enum property_handler_index {
|
|
|
|
|
PROPERTY_HANDLER_INTERFACE_REVISION,
|
|
|
|
|
PROPERTY_HANDLER_ENTRIES,
|
|
|
|
|
PROPERTY_HANDLER_MAX
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum entry_property_handler_index {
|
|
|
|
|
ENTRY_PROPERTY_HANDLER_INDEX,
|
|
|
|
|
ENTRY_PROPERTY_HANDLER_NAME,
|
|
|
|
|
ENTRY_PROPERTY_HANDLER_DEVICE,
|
|
|
|
|
ENTRY_PROPERTY_HANDLER_VOLUME,
|
2009-08-31 17:17:09 +03:00
|
|
|
ENTRY_PROPERTY_HANDLER_MUTE,
|
2009-08-03 19:38:02 +03:00
|
|
|
ENTRY_PROPERTY_HANDLER_MAX
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static pa_dbus_property_handler property_handlers[PROPERTY_HANDLER_MAX] = {
|
|
|
|
|
[PROPERTY_HANDLER_INTERFACE_REVISION] = { .property_name = "InterfaceRevision", .type = "u", .get_cb = handle_get_interface_revision, .set_cb = NULL },
|
|
|
|
|
[PROPERTY_HANDLER_ENTRIES] = { .property_name = "Entries", .type = "ao", .get_cb = handle_get_entries, .set_cb = NULL }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static pa_dbus_property_handler entry_property_handlers[ENTRY_PROPERTY_HANDLER_MAX] = {
|
|
|
|
|
[ENTRY_PROPERTY_HANDLER_INDEX] = { .property_name = "Index", .type = "u", .get_cb = handle_entry_get_index, .set_cb = NULL },
|
|
|
|
|
[ENTRY_PROPERTY_HANDLER_NAME] = { .property_name = "Name", .type = "s", .get_cb = handle_entry_get_name, .set_cb = NULL },
|
|
|
|
|
[ENTRY_PROPERTY_HANDLER_DEVICE] = { .property_name = "Device", .type = "s", .get_cb = handle_entry_get_device, .set_cb = handle_entry_set_device },
|
|
|
|
|
[ENTRY_PROPERTY_HANDLER_VOLUME] = { .property_name = "Volume", .type = "a(uu)", .get_cb = handle_entry_get_volume, .set_cb = handle_entry_set_volume },
|
2009-08-31 17:17:09 +03:00
|
|
|
[ENTRY_PROPERTY_HANDLER_MUTE] = { .property_name = "Mute", .type = "b", .get_cb = handle_entry_get_mute, .set_cb = handle_entry_set_mute }
|
2009-08-03 19:38:02 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum method_handler_index {
|
|
|
|
|
METHOD_HANDLER_ADD_ENTRY,
|
|
|
|
|
METHOD_HANDLER_GET_ENTRY_BY_NAME,
|
|
|
|
|
METHOD_HANDLER_MAX
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum entry_method_handler_index {
|
|
|
|
|
ENTRY_METHOD_HANDLER_REMOVE,
|
|
|
|
|
ENTRY_METHOD_HANDLER_MAX
|
|
|
|
|
};
|
|
|
|
|
|
2012-11-20 08:30:22 +02:00
|
|
|
static pa_dbus_arg_info add_entry_args[] = { { "name", "s", "in" },
|
|
|
|
|
{ "device", "s", "in" },
|
|
|
|
|
{ "volume", "a(uu)", "in" },
|
|
|
|
|
{ "mute", "b", "in" },
|
|
|
|
|
{ "apply_immediately", "b", "in" },
|
|
|
|
|
{ "entry", "o", "out" } };
|
2009-08-03 19:38:02 +03:00
|
|
|
static pa_dbus_arg_info get_entry_by_name_args[] = { { "name", "s", "in" }, { "entry", "o", "out" } };
|
|
|
|
|
|
|
|
|
|
static pa_dbus_method_handler method_handlers[METHOD_HANDLER_MAX] = {
|
|
|
|
|
[METHOD_HANDLER_ADD_ENTRY] = {
|
|
|
|
|
.method_name = "AddEntry",
|
|
|
|
|
.arguments = add_entry_args,
|
|
|
|
|
.n_arguments = sizeof(add_entry_args) / sizeof(pa_dbus_arg_info),
|
|
|
|
|
.receive_cb = handle_add_entry },
|
|
|
|
|
[METHOD_HANDLER_GET_ENTRY_BY_NAME] = {
|
|
|
|
|
.method_name = "GetEntryByName",
|
|
|
|
|
.arguments = get_entry_by_name_args,
|
|
|
|
|
.n_arguments = sizeof(get_entry_by_name_args) / sizeof(pa_dbus_arg_info),
|
|
|
|
|
.receive_cb = handle_get_entry_by_name }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static pa_dbus_method_handler entry_method_handlers[ENTRY_METHOD_HANDLER_MAX] = {
|
|
|
|
|
[ENTRY_METHOD_HANDLER_REMOVE] = {
|
|
|
|
|
.method_name = "Remove",
|
|
|
|
|
.arguments = NULL,
|
|
|
|
|
.n_arguments = 0,
|
|
|
|
|
.receive_cb = handle_entry_remove }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum signal_index {
|
|
|
|
|
SIGNAL_NEW_ENTRY,
|
|
|
|
|
SIGNAL_ENTRY_REMOVED,
|
|
|
|
|
SIGNAL_MAX
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum entry_signal_index {
|
|
|
|
|
ENTRY_SIGNAL_DEVICE_UPDATED,
|
|
|
|
|
ENTRY_SIGNAL_VOLUME_UPDATED,
|
|
|
|
|
ENTRY_SIGNAL_MUTE_UPDATED,
|
|
|
|
|
ENTRY_SIGNAL_MAX
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static pa_dbus_arg_info new_entry_args[] = { { "entry", "o", NULL } };
|
|
|
|
|
static pa_dbus_arg_info entry_removed_args[] = { { "entry", "o", NULL } };
|
|
|
|
|
|
|
|
|
|
static pa_dbus_arg_info entry_device_updated_args[] = { { "device", "s", NULL } };
|
|
|
|
|
static pa_dbus_arg_info entry_volume_updated_args[] = { { "volume", "a(uu)", NULL } };
|
|
|
|
|
static pa_dbus_arg_info entry_mute_updated_args[] = { { "muted", "b", NULL } };
|
|
|
|
|
|
|
|
|
|
static pa_dbus_signal_info signals[SIGNAL_MAX] = {
|
|
|
|
|
[SIGNAL_NEW_ENTRY] = { .name = "NewEntry", .arguments = new_entry_args, .n_arguments = 1 },
|
|
|
|
|
[SIGNAL_ENTRY_REMOVED] = { .name = "EntryRemoved", .arguments = entry_removed_args, .n_arguments = 1 }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static pa_dbus_signal_info entry_signals[ENTRY_SIGNAL_MAX] = {
|
|
|
|
|
[ENTRY_SIGNAL_DEVICE_UPDATED] = { .name = "DeviceUpdated", .arguments = entry_device_updated_args, .n_arguments = 1 },
|
|
|
|
|
[ENTRY_SIGNAL_VOLUME_UPDATED] = { .name = "VolumeUpdated", .arguments = entry_volume_updated_args, .n_arguments = 1 },
|
|
|
|
|
[ENTRY_SIGNAL_MUTE_UPDATED] = { .name = "MuteUpdated", .arguments = entry_mute_updated_args, .n_arguments = 1 }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static pa_dbus_interface_info stream_restore_interface_info = {
|
|
|
|
|
.name = INTERFACE_STREAM_RESTORE,
|
|
|
|
|
.method_handlers = method_handlers,
|
|
|
|
|
.n_method_handlers = METHOD_HANDLER_MAX,
|
|
|
|
|
.property_handlers = property_handlers,
|
|
|
|
|
.n_property_handlers = PROPERTY_HANDLER_MAX,
|
|
|
|
|
.get_all_properties_cb = handle_get_all,
|
|
|
|
|
.signals = signals,
|
|
|
|
|
.n_signals = SIGNAL_MAX
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static pa_dbus_interface_info entry_interface_info = {
|
|
|
|
|
.name = INTERFACE_ENTRY,
|
|
|
|
|
.method_handlers = entry_method_handlers,
|
|
|
|
|
.n_method_handlers = ENTRY_METHOD_HANDLER_MAX,
|
|
|
|
|
.property_handlers = entry_property_handlers,
|
|
|
|
|
.n_property_handlers = ENTRY_PROPERTY_HANDLER_MAX,
|
|
|
|
|
.get_all_properties_cb = handle_entry_get_all,
|
|
|
|
|
.signals = entry_signals,
|
|
|
|
|
.n_signals = ENTRY_SIGNAL_MAX
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static struct dbus_entry *dbus_entry_new(struct userdata *u, const char *entry_name) {
|
|
|
|
|
struct dbus_entry *de;
|
|
|
|
|
|
|
|
|
|
pa_assert(u);
|
|
|
|
|
pa_assert(entry_name);
|
|
|
|
|
pa_assert(*entry_name);
|
|
|
|
|
|
|
|
|
|
de = pa_xnew(struct dbus_entry, 1);
|
|
|
|
|
de->userdata = u;
|
|
|
|
|
de->entry_name = pa_xstrdup(entry_name);
|
|
|
|
|
de->index = u->next_index++;
|
|
|
|
|
de->object_path = pa_sprintf_malloc("%s/%s%u", OBJECT_PATH, ENTRY_OBJECT_NAME, de->index);
|
|
|
|
|
|
2010-04-01 11:17:52 +03:00
|
|
|
pa_assert_se(pa_dbus_protocol_add_interface(u->dbus_protocol, de->object_path, &entry_interface_info, de) >= 0);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
return de;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void dbus_entry_free(struct dbus_entry *de) {
|
|
|
|
|
pa_assert(de);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(pa_dbus_protocol_remove_interface(de->userdata->dbus_protocol, de->object_path, entry_interface_info.name) >= 0);
|
|
|
|
|
|
|
|
|
|
pa_xfree(de->entry_name);
|
|
|
|
|
pa_xfree(de->object_path);
|
2011-08-12 00:17:39 +02:00
|
|
|
pa_xfree(de);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Reads an array [(UInt32, UInt32)] from the iterator. The struct items are
|
|
|
|
|
* are a channel position and a volume value, respectively. The result is
|
2009-08-30 19:52:22 +03:00
|
|
|
* stored in the map and vol arguments. The iterator must point to a "a(uu)"
|
|
|
|
|
* element. If the data is invalid, an error reply is sent and a negative
|
|
|
|
|
* number is returned. In case of a failure we make no guarantees about the
|
|
|
|
|
* state of map and vol. In case of an empty array the channels field of both
|
|
|
|
|
* map and vol are set to 0. This function calls dbus_message_iter_next(iter)
|
|
|
|
|
* before returning. */
|
2009-08-03 19:38:02 +03:00
|
|
|
static int get_volume_arg(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, pa_channel_map *map, pa_cvolume *vol) {
|
|
|
|
|
DBusMessageIter array_iter;
|
|
|
|
|
DBusMessageIter struct_iter;
|
2018-03-23 17:26:36 +09:00
|
|
|
char *signature;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
pa_assert(conn);
|
|
|
|
|
pa_assert(msg);
|
|
|
|
|
pa_assert(iter);
|
|
|
|
|
pa_assert(map);
|
|
|
|
|
pa_assert(vol);
|
|
|
|
|
|
2018-03-23 17:26:36 +09:00
|
|
|
pa_assert_se(signature = dbus_message_iter_get_signature(iter));
|
|
|
|
|
pa_assert(pa_streq(signature, "a(uu)"));
|
|
|
|
|
|
|
|
|
|
dbus_free(signature);
|
|
|
|
|
|
2009-08-03 19:38:02 +03:00
|
|
|
pa_channel_map_init(map);
|
|
|
|
|
pa_cvolume_init(vol);
|
|
|
|
|
|
|
|
|
|
map->channels = 0;
|
|
|
|
|
vol->channels = 0;
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(iter, &array_iter);
|
|
|
|
|
|
|
|
|
|
while (dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_INVALID) {
|
|
|
|
|
dbus_uint32_t chan_pos;
|
|
|
|
|
dbus_uint32_t chan_vol;
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_recurse(&array_iter, &struct_iter);
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_get_basic(&struct_iter, &chan_pos);
|
|
|
|
|
|
|
|
|
|
if (chan_pos >= PA_CHANNEL_POSITION_MAX) {
|
|
|
|
|
pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid channel position: %u", chan_pos);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2009-08-30 19:52:22 +03:00
|
|
|
pa_assert_se(dbus_message_iter_next(&struct_iter));
|
2009-08-03 19:38:02 +03:00
|
|
|
dbus_message_iter_get_basic(&struct_iter, &chan_vol);
|
|
|
|
|
|
2010-10-19 13:03:48 +05:30
|
|
|
if (!PA_VOLUME_IS_VALID(chan_vol)) {
|
2009-08-03 19:38:02 +03:00
|
|
|
pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Invalid volume: %u", chan_vol);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (map->channels < PA_CHANNELS_MAX) {
|
|
|
|
|
map->map[map->channels] = chan_pos;
|
|
|
|
|
vol->values[map->channels] = chan_vol;
|
|
|
|
|
}
|
|
|
|
|
++map->channels;
|
|
|
|
|
++vol->channels;
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_next(&array_iter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (map->channels > PA_CHANNELS_MAX) {
|
|
|
|
|
pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "Too many channels: %u. The maximum is %u.", map->channels, PA_CHANNELS_MAX);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_next(iter);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void append_volume(DBusMessageIter *iter, struct entry *e) {
|
|
|
|
|
DBusMessageIter array_iter;
|
|
|
|
|
DBusMessageIter struct_iter;
|
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
|
|
pa_assert(iter);
|
|
|
|
|
pa_assert(e);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "(uu)", &array_iter));
|
|
|
|
|
|
|
|
|
|
if (!e->volume_valid) {
|
|
|
|
|
pa_assert_se(dbus_message_iter_close_container(iter, &array_iter));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < e->channel_map.channels; ++i) {
|
|
|
|
|
pa_assert_se(dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter));
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &e->channel_map.map[i]));
|
|
|
|
|
pa_assert_se(dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &e->volume.values[i]));
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_message_iter_close_container(&array_iter, &struct_iter));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_message_iter_close_container(iter, &array_iter));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void append_volume_variant(DBusMessageIter *iter, struct entry *e) {
|
|
|
|
|
DBusMessageIter variant_iter;
|
|
|
|
|
|
|
|
|
|
pa_assert(iter);
|
|
|
|
|
pa_assert(e);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "a(uu)", &variant_iter));
|
|
|
|
|
|
|
|
|
|
append_volume(&variant_iter, e);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_message_iter_close_container(iter, &variant_iter));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void send_new_entry_signal(struct dbus_entry *entry) {
|
2009-11-21 00:30:46 +01:00
|
|
|
DBusMessage *signal_msg;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
pa_assert(entry);
|
|
|
|
|
|
2009-11-21 00:30:46 +01:00
|
|
|
pa_assert_se(signal_msg = dbus_message_new_signal(OBJECT_PATH, INTERFACE_STREAM_RESTORE, signals[SIGNAL_NEW_ENTRY].name));
|
|
|
|
|
pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &entry->object_path, DBUS_TYPE_INVALID));
|
|
|
|
|
pa_dbus_protocol_send_signal(entry->userdata->dbus_protocol, signal_msg);
|
|
|
|
|
dbus_message_unref(signal_msg);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void send_entry_removed_signal(struct dbus_entry *entry) {
|
2009-11-21 00:30:46 +01:00
|
|
|
DBusMessage *signal_msg;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
pa_assert(entry);
|
|
|
|
|
|
2009-11-21 00:30:46 +01:00
|
|
|
pa_assert_se(signal_msg = dbus_message_new_signal(OBJECT_PATH, INTERFACE_STREAM_RESTORE, signals[SIGNAL_ENTRY_REMOVED].name));
|
|
|
|
|
pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_OBJECT_PATH, &entry->object_path, DBUS_TYPE_INVALID));
|
|
|
|
|
pa_dbus_protocol_send_signal(entry->userdata->dbus_protocol, signal_msg);
|
|
|
|
|
dbus_message_unref(signal_msg);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void send_device_updated_signal(struct dbus_entry *de, struct entry *e) {
|
2009-11-21 00:30:46 +01:00
|
|
|
DBusMessage *signal_msg;
|
2009-08-03 19:38:02 +03:00
|
|
|
const char *device;
|
|
|
|
|
|
|
|
|
|
pa_assert(de);
|
|
|
|
|
pa_assert(e);
|
|
|
|
|
|
|
|
|
|
device = e->device_valid ? e->device : "";
|
|
|
|
|
|
2009-11-21 00:30:46 +01:00
|
|
|
pa_assert_se(signal_msg = dbus_message_new_signal(de->object_path, INTERFACE_ENTRY, entry_signals[ENTRY_SIGNAL_DEVICE_UPDATED].name));
|
|
|
|
|
pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_STRING, &device, DBUS_TYPE_INVALID));
|
|
|
|
|
pa_dbus_protocol_send_signal(de->userdata->dbus_protocol, signal_msg);
|
|
|
|
|
dbus_message_unref(signal_msg);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void send_volume_updated_signal(struct dbus_entry *de, struct entry *e) {
|
2009-11-21 00:30:46 +01:00
|
|
|
DBusMessage *signal_msg;
|
2009-08-03 19:38:02 +03:00
|
|
|
DBusMessageIter msg_iter;
|
|
|
|
|
|
|
|
|
|
pa_assert(de);
|
|
|
|
|
pa_assert(e);
|
|
|
|
|
|
2009-11-21 00:30:46 +01:00
|
|
|
pa_assert_se(signal_msg = dbus_message_new_signal(de->object_path, INTERFACE_ENTRY, entry_signals[ENTRY_SIGNAL_VOLUME_UPDATED].name));
|
|
|
|
|
dbus_message_iter_init_append(signal_msg, &msg_iter);
|
2009-08-03 19:38:02 +03:00
|
|
|
append_volume(&msg_iter, e);
|
2009-11-21 00:30:46 +01:00
|
|
|
pa_dbus_protocol_send_signal(de->userdata->dbus_protocol, signal_msg);
|
|
|
|
|
dbus_message_unref(signal_msg);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void send_mute_updated_signal(struct dbus_entry *de, struct entry *e) {
|
2009-11-21 00:30:46 +01:00
|
|
|
DBusMessage *signal_msg;
|
2009-08-03 19:38:02 +03:00
|
|
|
dbus_bool_t muted;
|
|
|
|
|
|
|
|
|
|
pa_assert(de);
|
|
|
|
|
pa_assert(e);
|
|
|
|
|
|
|
|
|
|
pa_assert(e->muted_valid);
|
|
|
|
|
|
|
|
|
|
muted = e->muted;
|
|
|
|
|
|
2009-11-21 00:30:46 +01:00
|
|
|
pa_assert_se(signal_msg = dbus_message_new_signal(de->object_path, INTERFACE_ENTRY, entry_signals[ENTRY_SIGNAL_MUTE_UPDATED].name));
|
|
|
|
|
pa_assert_se(dbus_message_append_args(signal_msg, DBUS_TYPE_BOOLEAN, &muted, DBUS_TYPE_INVALID));
|
|
|
|
|
pa_dbus_protocol_send_signal(de->userdata->dbus_protocol, signal_msg);
|
|
|
|
|
dbus_message_unref(signal_msg);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handle_get_interface_revision(DBusConnection *conn, DBusMessage *msg, void *userdata) {
|
|
|
|
|
dbus_uint32_t interface_revision = DBUS_INTERFACE_REVISION;
|
|
|
|
|
|
|
|
|
|
pa_assert(conn);
|
|
|
|
|
pa_assert(msg);
|
|
|
|
|
|
|
|
|
|
pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &interface_revision);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* The caller frees the array, but not the strings. */
|
|
|
|
|
static const char **get_entries(struct userdata *u, unsigned *n) {
|
|
|
|
|
const char **entries;
|
|
|
|
|
unsigned i = 0;
|
|
|
|
|
void *state = NULL;
|
|
|
|
|
struct dbus_entry *de;
|
|
|
|
|
|
|
|
|
|
pa_assert(u);
|
|
|
|
|
pa_assert(n);
|
|
|
|
|
|
|
|
|
|
*n = pa_hashmap_size(u->dbus_entries);
|
|
|
|
|
|
|
|
|
|
if (*n == 0)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
entries = pa_xnew(const char *, *n);
|
|
|
|
|
|
2009-08-09 09:04:15 +03:00
|
|
|
PA_HASHMAP_FOREACH(de, u->dbus_entries, state)
|
|
|
|
|
entries[i++] = de->object_path;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
return entries;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handle_get_entries(DBusConnection *conn, DBusMessage *msg, void *userdata) {
|
|
|
|
|
struct userdata *u = userdata;
|
|
|
|
|
const char **entries;
|
|
|
|
|
unsigned n;
|
|
|
|
|
|
|
|
|
|
pa_assert(conn);
|
|
|
|
|
pa_assert(msg);
|
|
|
|
|
pa_assert(u);
|
|
|
|
|
|
|
|
|
|
entries = get_entries(u, &n);
|
|
|
|
|
|
|
|
|
|
pa_dbus_send_basic_array_variant_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, entries, n);
|
|
|
|
|
|
|
|
|
|
pa_xfree(entries);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handle_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
|
|
|
|
|
struct userdata *u = userdata;
|
|
|
|
|
DBusMessage *reply = NULL;
|
|
|
|
|
DBusMessageIter msg_iter;
|
|
|
|
|
DBusMessageIter dict_iter;
|
|
|
|
|
dbus_uint32_t interface_revision;
|
|
|
|
|
const char **entries;
|
|
|
|
|
unsigned n_entries;
|
|
|
|
|
|
|
|
|
|
pa_assert(conn);
|
|
|
|
|
pa_assert(msg);
|
|
|
|
|
pa_assert(u);
|
|
|
|
|
|
|
|
|
|
interface_revision = DBUS_INTERFACE_REVISION;
|
|
|
|
|
entries = get_entries(u, &n_entries);
|
|
|
|
|
|
|
|
|
|
pa_assert_se((reply = dbus_message_new_method_return(msg)));
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_init_append(reply, &msg_iter);
|
|
|
|
|
pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
|
|
|
|
|
|
|
|
|
|
pa_dbus_append_basic_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_INTERFACE_REVISION].property_name, DBUS_TYPE_UINT32, &interface_revision);
|
|
|
|
|
pa_dbus_append_basic_array_variant_dict_entry(&dict_iter, property_handlers[PROPERTY_HANDLER_ENTRIES].property_name, DBUS_TYPE_OBJECT_PATH, entries, n_entries);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_connection_send(conn, reply, NULL));
|
|
|
|
|
|
|
|
|
|
dbus_message_unref(reply);
|
|
|
|
|
|
|
|
|
|
pa_xfree(entries);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handle_add_entry(DBusConnection *conn, DBusMessage *msg, void *userdata) {
|
|
|
|
|
struct userdata *u = userdata;
|
|
|
|
|
DBusMessageIter msg_iter;
|
2009-08-30 19:52:22 +03:00
|
|
|
const char *name = NULL;
|
|
|
|
|
const char *device = NULL;
|
2009-08-03 19:38:02 +03:00
|
|
|
pa_channel_map map;
|
|
|
|
|
pa_cvolume vol;
|
2013-06-28 01:08:31 +02:00
|
|
|
dbus_bool_t muted = FALSE;
|
|
|
|
|
dbus_bool_t apply_immediately = FALSE;
|
2009-08-30 19:52:22 +03:00
|
|
|
struct dbus_entry *dbus_entry = NULL;
|
|
|
|
|
struct entry *e = NULL;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
pa_assert(conn);
|
|
|
|
|
pa_assert(msg);
|
|
|
|
|
pa_assert(u);
|
|
|
|
|
|
2009-08-30 19:52:22 +03:00
|
|
|
pa_assert_se(dbus_message_iter_init(msg, &msg_iter));
|
|
|
|
|
dbus_message_iter_get_basic(&msg_iter, &name);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2009-08-30 19:52:22 +03:00
|
|
|
pa_assert_se(dbus_message_iter_next(&msg_iter));
|
|
|
|
|
dbus_message_iter_get_basic(&msg_iter, &device);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2009-08-30 19:52:22 +03:00
|
|
|
pa_assert_se(dbus_message_iter_next(&msg_iter));
|
2009-08-03 19:38:02 +03:00
|
|
|
if (get_volume_arg(conn, msg, &msg_iter, &map, &vol) < 0)
|
|
|
|
|
return;
|
|
|
|
|
|
2009-08-30 19:52:22 +03:00
|
|
|
dbus_message_iter_get_basic(&msg_iter, &muted);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2009-08-30 19:52:22 +03:00
|
|
|
pa_assert_se(dbus_message_iter_next(&msg_iter));
|
|
|
|
|
dbus_message_iter_get_basic(&msg_iter, &apply_immediately);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
if (!*name) {
|
|
|
|
|
pa_dbus_send_error(conn, msg, DBUS_ERROR_INVALID_ARGS, "An empty string was given as the entry name.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((dbus_entry = pa_hashmap_get(u->dbus_entries, name))) {
|
2013-06-27 19:28:09 +02:00
|
|
|
bool mute_updated = false;
|
|
|
|
|
bool volume_updated = false;
|
|
|
|
|
bool device_updated = false;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
pa_assert_se(e = entry_read(u, name));
|
2009-08-03 19:38:02 +03:00
|
|
|
mute_updated = e->muted != muted;
|
|
|
|
|
e->muted = muted;
|
2013-06-27 19:28:09 +02:00
|
|
|
e->muted_valid = true;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
volume_updated = (e->volume_valid != !!map.channels) || !pa_cvolume_equal(&e->volume, &vol);
|
|
|
|
|
e->volume = vol;
|
|
|
|
|
e->channel_map = map;
|
|
|
|
|
e->volume_valid = !!map.channels;
|
|
|
|
|
|
2016-04-26 16:11:44 +03:00
|
|
|
device_updated = (e->device_valid != !!device[0]) || !pa_safe_streq(e->device, device);
|
2011-06-07 23:21:04 +01:00
|
|
|
pa_xfree(e->device);
|
|
|
|
|
e->device = pa_xstrdup(device);
|
2009-08-03 19:38:02 +03:00
|
|
|
e->device_valid = !!device[0];
|
|
|
|
|
|
|
|
|
|
if (mute_updated)
|
|
|
|
|
send_mute_updated_signal(dbus_entry, e);
|
|
|
|
|
if (volume_updated)
|
|
|
|
|
send_volume_updated_signal(dbus_entry, e);
|
|
|
|
|
if (device_updated)
|
|
|
|
|
send_device_updated_signal(dbus_entry, e);
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
dbus_entry = dbus_entry_new(u, name);
|
2009-12-03 15:31:11 +02:00
|
|
|
pa_assert_se(pa_hashmap_put(u->dbus_entries, dbus_entry->entry_name, dbus_entry) == 0);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
e = entry_new();
|
2013-06-27 19:28:09 +02:00
|
|
|
e->muted_valid = true;
|
2009-08-03 19:38:02 +03:00
|
|
|
e->volume_valid = !!map.channels;
|
|
|
|
|
e->device_valid = !!device[0];
|
|
|
|
|
e->muted = muted;
|
|
|
|
|
e->volume = vol;
|
|
|
|
|
e->channel_map = map;
|
2011-06-07 23:21:04 +01:00
|
|
|
e->device = pa_xstrdup(device);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
send_new_entry_signal(dbus_entry);
|
|
|
|
|
}
|
|
|
|
|
|
2013-06-27 19:28:09 +02:00
|
|
|
pa_assert_se(entry_write(u, name, e, true));
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
if (apply_immediately)
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_apply(u, name, e);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
trigger_save(u);
|
|
|
|
|
|
|
|
|
|
pa_dbus_send_empty_reply(conn, msg);
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_free(e);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handle_get_entry_by_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
|
|
|
|
|
struct userdata *u = userdata;
|
|
|
|
|
const char *name;
|
|
|
|
|
struct dbus_entry *de;
|
|
|
|
|
|
|
|
|
|
pa_assert(conn);
|
|
|
|
|
pa_assert(msg);
|
|
|
|
|
pa_assert(u);
|
|
|
|
|
|
2009-08-30 19:52:22 +03:00
|
|
|
pa_assert_se(dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID));
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
if (!(de = pa_hashmap_get(u->dbus_entries, name))) {
|
|
|
|
|
pa_dbus_send_error(conn, msg, PA_DBUS_ERROR_NOT_FOUND, "No such stream restore entry.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_dbus_send_basic_value_reply(conn, msg, DBUS_TYPE_OBJECT_PATH, &de->object_path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handle_entry_get_index(DBusConnection *conn, DBusMessage *msg, void *userdata) {
|
|
|
|
|
struct dbus_entry *de = userdata;
|
|
|
|
|
|
|
|
|
|
pa_assert(conn);
|
|
|
|
|
pa_assert(msg);
|
|
|
|
|
pa_assert(de);
|
|
|
|
|
|
|
|
|
|
pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_UINT32, &de->index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handle_entry_get_name(DBusConnection *conn, DBusMessage *msg, void *userdata) {
|
|
|
|
|
struct dbus_entry *de = userdata;
|
|
|
|
|
|
|
|
|
|
pa_assert(conn);
|
|
|
|
|
pa_assert(msg);
|
|
|
|
|
pa_assert(de);
|
|
|
|
|
|
|
|
|
|
pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &de->entry_name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handle_entry_get_device(DBusConnection *conn, DBusMessage *msg, void *userdata) {
|
|
|
|
|
struct dbus_entry *de = userdata;
|
|
|
|
|
struct entry *e;
|
|
|
|
|
const char *device;
|
|
|
|
|
|
|
|
|
|
pa_assert(conn);
|
|
|
|
|
pa_assert(msg);
|
|
|
|
|
pa_assert(de);
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
pa_assert_se(e = entry_read(de->userdata, de->entry_name));
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
device = e->device_valid ? e->device : "";
|
|
|
|
|
|
|
|
|
|
pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_STRING, &device);
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_free(e);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
|
|
|
|
|
2009-08-30 19:52:22 +03:00
|
|
|
static void handle_entry_set_device(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
|
2009-08-03 19:38:02 +03:00
|
|
|
struct dbus_entry *de = userdata;
|
|
|
|
|
const char *device;
|
|
|
|
|
struct entry *e;
|
2013-06-27 19:28:09 +02:00
|
|
|
bool updated;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
pa_assert(conn);
|
|
|
|
|
pa_assert(msg);
|
2009-08-30 19:52:22 +03:00
|
|
|
pa_assert(iter);
|
2009-08-03 19:38:02 +03:00
|
|
|
pa_assert(de);
|
|
|
|
|
|
2009-08-30 19:52:22 +03:00
|
|
|
dbus_message_iter_get_basic(iter, &device);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
pa_assert_se(e = entry_read(de->userdata, de->entry_name));
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2016-04-26 16:11:44 +03:00
|
|
|
updated = (e->device_valid != !!device[0]) || !pa_safe_streq(e->device, device);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
if (updated) {
|
2011-06-07 23:21:04 +01:00
|
|
|
pa_xfree(e->device);
|
|
|
|
|
e->device = pa_xstrdup(device);
|
2009-08-03 19:38:02 +03:00
|
|
|
e->device_valid = !!device[0];
|
|
|
|
|
|
2013-06-27 19:28:09 +02:00
|
|
|
pa_assert_se(entry_write(de->userdata, de->entry_name, e, true));
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_apply(de->userdata, de->entry_name, e);
|
2009-08-03 19:38:02 +03:00
|
|
|
send_device_updated_signal(de, e);
|
|
|
|
|
trigger_save(de->userdata);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_dbus_send_empty_reply(conn, msg);
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_free(e);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handle_entry_get_volume(DBusConnection *conn, DBusMessage *msg, void *userdata) {
|
|
|
|
|
struct dbus_entry *de = userdata;
|
|
|
|
|
DBusMessage *reply;
|
|
|
|
|
DBusMessageIter msg_iter;
|
|
|
|
|
struct entry *e;
|
|
|
|
|
|
|
|
|
|
pa_assert(conn);
|
|
|
|
|
pa_assert(msg);
|
|
|
|
|
pa_assert(de);
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
pa_assert_se(e = entry_read(de->userdata, de->entry_name));
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
pa_assert_se(reply = dbus_message_new_method_return(msg));
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_init_append(reply, &msg_iter);
|
|
|
|
|
append_volume_variant(&msg_iter, e);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_connection_send(conn, reply, NULL));
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_free(e);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
|
|
|
|
|
2009-08-30 19:52:22 +03:00
|
|
|
static void handle_entry_set_volume(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
|
2009-08-03 19:38:02 +03:00
|
|
|
struct dbus_entry *de = userdata;
|
|
|
|
|
pa_channel_map map;
|
|
|
|
|
pa_cvolume vol;
|
2009-08-30 19:52:22 +03:00
|
|
|
struct entry *e = NULL;
|
2013-06-27 19:28:09 +02:00
|
|
|
bool updated = false;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
pa_assert(conn);
|
|
|
|
|
pa_assert(msg);
|
2009-08-30 19:52:22 +03:00
|
|
|
pa_assert(iter);
|
2009-08-03 19:38:02 +03:00
|
|
|
pa_assert(de);
|
|
|
|
|
|
2009-08-30 19:52:22 +03:00
|
|
|
if (get_volume_arg(conn, msg, iter, &map, &vol) < 0)
|
2009-08-03 19:38:02 +03:00
|
|
|
return;
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
pa_assert_se(e = entry_read(de->userdata, de->entry_name));
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
updated = (e->volume_valid != !!map.channels) || !pa_cvolume_equal(&e->volume, &vol);
|
|
|
|
|
|
|
|
|
|
if (updated) {
|
|
|
|
|
e->volume = vol;
|
|
|
|
|
e->channel_map = map;
|
|
|
|
|
e->volume_valid = !!map.channels;
|
|
|
|
|
|
2013-06-27 19:28:09 +02:00
|
|
|
pa_assert_se(entry_write(de->userdata, de->entry_name, e, true));
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_apply(de->userdata, de->entry_name, e);
|
2009-08-03 19:38:02 +03:00
|
|
|
send_volume_updated_signal(de, e);
|
|
|
|
|
trigger_save(de->userdata);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_dbus_send_empty_reply(conn, msg);
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_free(e);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
|
|
|
|
|
2009-08-31 17:17:09 +03:00
|
|
|
static void handle_entry_get_mute(DBusConnection *conn, DBusMessage *msg, void *userdata) {
|
2009-08-03 19:38:02 +03:00
|
|
|
struct dbus_entry *de = userdata;
|
|
|
|
|
struct entry *e;
|
2009-08-31 17:17:09 +03:00
|
|
|
dbus_bool_t mute;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
pa_assert(conn);
|
|
|
|
|
pa_assert(msg);
|
|
|
|
|
pa_assert(de);
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
pa_assert_se(e = entry_read(de->userdata, de->entry_name));
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2013-06-28 01:08:31 +02:00
|
|
|
mute = e->muted_valid ? e->muted : FALSE;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2009-08-31 17:17:09 +03:00
|
|
|
pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &mute);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_free(e);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
|
|
|
|
|
2009-08-31 17:17:09 +03:00
|
|
|
static void handle_entry_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
|
2009-08-03 19:38:02 +03:00
|
|
|
struct dbus_entry *de = userdata;
|
2009-08-31 17:17:09 +03:00
|
|
|
dbus_bool_t mute;
|
2009-08-03 19:38:02 +03:00
|
|
|
struct entry *e;
|
2013-06-27 19:28:09 +02:00
|
|
|
bool updated;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
pa_assert(conn);
|
|
|
|
|
pa_assert(msg);
|
2009-08-30 19:52:22 +03:00
|
|
|
pa_assert(iter);
|
2009-08-03 19:38:02 +03:00
|
|
|
pa_assert(de);
|
|
|
|
|
|
2009-08-31 17:17:09 +03:00
|
|
|
dbus_message_iter_get_basic(iter, &mute);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
pa_assert_se(e = entry_read(de->userdata, de->entry_name));
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2009-08-31 17:17:09 +03:00
|
|
|
updated = !e->muted_valid || e->muted != mute;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
if (updated) {
|
2009-08-31 17:17:09 +03:00
|
|
|
e->muted = mute;
|
2013-06-27 19:28:09 +02:00
|
|
|
e->muted_valid = true;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2013-06-27 19:28:09 +02:00
|
|
|
pa_assert_se(entry_write(de->userdata, de->entry_name, e, true));
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_apply(de->userdata, de->entry_name, e);
|
2009-08-03 19:38:02 +03:00
|
|
|
send_mute_updated_signal(de, e);
|
|
|
|
|
trigger_save(de->userdata);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_dbus_send_empty_reply(conn, msg);
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_free(e);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handle_entry_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
|
|
|
|
|
struct dbus_entry *de = userdata;
|
|
|
|
|
struct entry *e;
|
|
|
|
|
DBusMessage *reply = NULL;
|
|
|
|
|
DBusMessageIter msg_iter;
|
|
|
|
|
DBusMessageIter dict_iter;
|
|
|
|
|
DBusMessageIter dict_entry_iter;
|
|
|
|
|
const char *device;
|
2009-08-31 17:17:09 +03:00
|
|
|
dbus_bool_t mute;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
pa_assert(conn);
|
|
|
|
|
pa_assert(msg);
|
|
|
|
|
pa_assert(de);
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
pa_assert_se(e = entry_read(de->userdata, de->entry_name));
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
device = e->device_valid ? e->device : "";
|
2013-06-28 01:08:31 +02:00
|
|
|
mute = e->muted_valid ? e->muted : FALSE;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
pa_assert_se((reply = dbus_message_new_method_return(msg)));
|
|
|
|
|
|
|
|
|
|
dbus_message_iter_init_append(reply, &msg_iter);
|
|
|
|
|
pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
|
|
|
|
|
|
|
|
|
|
pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &de->index);
|
|
|
|
|
pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &de->entry_name);
|
|
|
|
|
pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_DEVICE].property_name, DBUS_TYPE_STRING, &device);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter));
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &entry_property_handlers[ENTRY_PROPERTY_HANDLER_VOLUME].property_name));
|
|
|
|
|
append_volume_variant(&dict_entry_iter, e);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter));
|
|
|
|
|
|
2009-08-31 17:17:09 +03:00
|
|
|
pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_MUTE].property_name, DBUS_TYPE_BOOLEAN, &mute);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
|
|
|
|
|
|
|
|
|
|
pa_assert_se(dbus_connection_send(conn, reply, NULL));
|
|
|
|
|
|
|
|
|
|
dbus_message_unref(reply);
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_free(e);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handle_entry_remove(DBusConnection *conn, DBusMessage *msg, void *userdata) {
|
|
|
|
|
struct dbus_entry *de = userdata;
|
|
|
|
|
pa_datum key;
|
|
|
|
|
|
|
|
|
|
pa_assert(conn);
|
|
|
|
|
pa_assert(msg);
|
|
|
|
|
pa_assert(de);
|
|
|
|
|
|
|
|
|
|
key.data = de->entry_name;
|
|
|
|
|
key.size = strlen(de->entry_name);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(pa_database_unset(de->userdata->database, &key) == 0);
|
|
|
|
|
|
|
|
|
|
send_entry_removed_signal(de);
|
|
|
|
|
trigger_save(de->userdata);
|
|
|
|
|
|
2014-03-26 17:39:48 +02:00
|
|
|
pa_assert_se(pa_hashmap_remove_and_free(de->userdata->dbus_entries, de->entry_name) >= 0);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
pa_dbus_send_empty_reply(conn, msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif /* HAVE_DBUS */
|
|
|
|
|
|
2009-04-05 02:13:43 +03:00
|
|
|
static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) {
|
2008-08-03 23:23:13 +02:00
|
|
|
struct userdata *u = userdata;
|
|
|
|
|
|
|
|
|
|
pa_assert(a);
|
|
|
|
|
pa_assert(e);
|
|
|
|
|
pa_assert(u);
|
|
|
|
|
|
|
|
|
|
pa_assert(e == u->save_time_event);
|
|
|
|
|
u->core->mainloop->time_free(u->save_time_event);
|
|
|
|
|
u->save_time_event = NULL;
|
|
|
|
|
|
2009-05-14 01:24:26 +02:00
|
|
|
pa_database_sync(u->database);
|
2008-08-03 23:23:13 +02:00
|
|
|
pa_log_info("Synced.");
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
static struct entry* entry_new(void) {
|
|
|
|
|
struct entry *r = pa_xnew0(struct entry, 1);
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void entry_free(struct entry* e) {
|
|
|
|
|
pa_assert(e);
|
|
|
|
|
|
|
|
|
|
pa_xfree(e->device);
|
|
|
|
|
pa_xfree(e->card);
|
|
|
|
|
pa_xfree(e);
|
|
|
|
|
}
|
|
|
|
|
|
2013-06-27 19:28:09 +02:00
|
|
|
static bool entry_write(struct userdata *u, const char *name, const struct entry *e, bool replace) {
|
2011-06-08 00:47:19 +01:00
|
|
|
pa_tagstruct *t;
|
|
|
|
|
pa_datum key, data;
|
2013-06-27 19:28:09 +02:00
|
|
|
bool r;
|
2011-06-08 00:47:19 +01:00
|
|
|
|
|
|
|
|
pa_assert(u);
|
|
|
|
|
pa_assert(name);
|
|
|
|
|
pa_assert(e);
|
|
|
|
|
|
2014-10-23 16:09:45 +02:00
|
|
|
t = pa_tagstruct_new();
|
2020-05-26 14:45:12 +03:00
|
|
|
pa_tagstruct_putu8(t, ENTRY_VERSION);
|
2011-06-08 00:47:19 +01:00
|
|
|
pa_tagstruct_put_boolean(t, e->volume_valid);
|
|
|
|
|
pa_tagstruct_put_channel_map(t, &e->channel_map);
|
|
|
|
|
pa_tagstruct_put_cvolume(t, &e->volume);
|
|
|
|
|
pa_tagstruct_put_boolean(t, e->muted_valid);
|
|
|
|
|
pa_tagstruct_put_boolean(t, e->muted);
|
|
|
|
|
pa_tagstruct_put_boolean(t, e->device_valid);
|
|
|
|
|
pa_tagstruct_puts(t, e->device);
|
|
|
|
|
pa_tagstruct_put_boolean(t, e->card_valid);
|
|
|
|
|
pa_tagstruct_puts(t, e->card);
|
|
|
|
|
|
|
|
|
|
key.data = (char *) name;
|
|
|
|
|
key.size = strlen(name);
|
|
|
|
|
|
|
|
|
|
data.data = (void*)pa_tagstruct_data(t, &data.size);
|
|
|
|
|
|
|
|
|
|
r = (pa_database_set(u->database, &key, &data, replace) == 0);
|
|
|
|
|
|
|
|
|
|
pa_tagstruct_free(t);
|
|
|
|
|
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
|
|
|
|
|
|
|
|
|
|
#define LEGACY_ENTRY_VERSION 3
|
2012-01-06 16:14:03 +02:00
|
|
|
static struct entry *legacy_entry_read(struct userdata *u, const char *name) {
|
2011-06-08 00:47:19 +01:00
|
|
|
struct legacy_entry {
|
|
|
|
|
uint8_t version;
|
2013-06-27 19:28:09 +02:00
|
|
|
bool muted_valid:1, volume_valid:1, device_valid:1, card_valid:1;
|
|
|
|
|
bool muted:1;
|
2011-06-08 00:47:19 +01:00
|
|
|
pa_channel_map channel_map;
|
|
|
|
|
pa_cvolume volume;
|
|
|
|
|
char device[PA_NAME_MAX];
|
|
|
|
|
char card[PA_NAME_MAX];
|
|
|
|
|
} PA_GCC_PACKED;
|
2012-01-06 16:14:03 +02:00
|
|
|
|
|
|
|
|
pa_datum key;
|
|
|
|
|
pa_datum data;
|
2011-06-08 00:47:19 +01:00
|
|
|
struct legacy_entry *le;
|
|
|
|
|
struct entry *e;
|
2020-07-23 19:42:47 -04:00
|
|
|
pa_channel_map channel_map;
|
|
|
|
|
pa_cvolume volume;
|
2011-06-08 00:47:19 +01:00
|
|
|
|
|
|
|
|
pa_assert(u);
|
2012-01-06 16:14:03 +02:00
|
|
|
pa_assert(name);
|
|
|
|
|
|
|
|
|
|
key.data = (char *) name;
|
|
|
|
|
key.size = strlen(name);
|
|
|
|
|
|
|
|
|
|
pa_zero(data);
|
|
|
|
|
|
|
|
|
|
if (!pa_database_get(u->database, &key, &data))
|
|
|
|
|
goto fail;
|
2011-06-08 00:47:19 +01:00
|
|
|
|
2012-01-06 16:14:03 +02:00
|
|
|
if (data.size != sizeof(struct legacy_entry)) {
|
2011-06-08 00:47:19 +01:00
|
|
|
pa_log_debug("Size does not match.");
|
2012-01-06 16:14:03 +02:00
|
|
|
goto fail;
|
2011-06-08 00:47:19 +01:00
|
|
|
}
|
|
|
|
|
|
2012-01-06 16:14:03 +02:00
|
|
|
le = (struct legacy_entry *) data.data;
|
2011-06-08 00:47:19 +01:00
|
|
|
|
|
|
|
|
if (le->version != LEGACY_ENTRY_VERSION) {
|
|
|
|
|
pa_log_debug("Version mismatch.");
|
2012-01-06 16:14:03 +02:00
|
|
|
goto fail;
|
2011-06-08 00:47:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!memchr(le->device, 0, sizeof(le->device))) {
|
|
|
|
|
pa_log_warn("Device has missing NUL byte.");
|
2012-01-06 16:14:03 +02:00
|
|
|
goto fail;
|
2011-06-08 00:47:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!memchr(le->card, 0, sizeof(le->card))) {
|
|
|
|
|
pa_log_warn("Card has missing NUL byte.");
|
2012-01-06 16:14:03 +02:00
|
|
|
goto fail;
|
2011-06-08 00:47:19 +01:00
|
|
|
}
|
|
|
|
|
|
2011-09-04 12:54:41 +02:00
|
|
|
if (le->device_valid && !pa_namereg_is_valid_name(le->device)) {
|
|
|
|
|
pa_log_warn("Invalid device name stored in database for legacy stream");
|
2012-01-06 16:14:03 +02:00
|
|
|
goto fail;
|
2011-09-04 12:54:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (le->card_valid && !pa_namereg_is_valid_name(le->card)) {
|
|
|
|
|
pa_log_warn("Invalid card name stored in database for legacy stream");
|
2012-01-06 16:14:03 +02:00
|
|
|
goto fail;
|
2011-09-04 12:54:41 +02:00
|
|
|
}
|
|
|
|
|
|
2020-07-23 19:42:47 -04:00
|
|
|
/* Read these out before accessing contents via pointers as struct legacy_entry may not be adequately aligned for these
|
|
|
|
|
* members to be accessed directly */
|
|
|
|
|
channel_map = le->channel_map;
|
|
|
|
|
volume = le->volume;
|
|
|
|
|
|
|
|
|
|
if (le->volume_valid && !pa_channel_map_valid(&channel_map)) {
|
2011-09-04 12:54:41 +02:00
|
|
|
pa_log_warn("Invalid channel map stored in database for legacy stream");
|
2012-01-06 16:14:03 +02:00
|
|
|
goto fail;
|
2011-09-04 12:54:41 +02:00
|
|
|
}
|
|
|
|
|
|
2020-07-23 19:42:47 -04:00
|
|
|
if (le->volume_valid && (!pa_cvolume_valid(&volume) || !pa_cvolume_compatible_with_channel_map(&volume, &channel_map))) {
|
2011-09-04 12:54:41 +02:00
|
|
|
pa_log_warn("Invalid volume stored in database for legacy stream");
|
2012-01-06 16:14:03 +02:00
|
|
|
goto fail;
|
2011-09-04 12:54:41 +02:00
|
|
|
}
|
|
|
|
|
|
2011-06-08 00:47:19 +01:00
|
|
|
e = entry_new();
|
2011-09-04 12:28:07 +02:00
|
|
|
e->muted_valid = le->muted_valid;
|
|
|
|
|
e->muted = le->muted;
|
|
|
|
|
e->volume_valid = le->volume_valid;
|
|
|
|
|
e->channel_map = le->channel_map;
|
|
|
|
|
e->volume = le->volume;
|
|
|
|
|
e->device_valid = le->device_valid;
|
|
|
|
|
e->device = pa_xstrdup(le->device);
|
|
|
|
|
e->card_valid = le->card_valid;
|
2011-06-08 00:47:19 +01:00
|
|
|
e->card = pa_xstrdup(le->card);
|
|
|
|
|
return e;
|
2012-01-06 16:14:03 +02:00
|
|
|
|
|
|
|
|
fail:
|
|
|
|
|
pa_datum_free(&data);
|
|
|
|
|
|
|
|
|
|
return NULL;
|
2011-06-08 00:47:19 +01:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
static struct entry *entry_read(struct userdata *u, const char *name) {
|
2009-05-14 01:24:26 +02:00
|
|
|
pa_datum key, data;
|
2011-06-07 23:21:04 +01:00
|
|
|
struct entry *e = NULL;
|
|
|
|
|
pa_tagstruct *t = NULL;
|
2020-05-26 14:45:12 +03:00
|
|
|
uint8_t version;
|
2011-06-07 23:21:04 +01:00
|
|
|
const char *device, *card;
|
2008-08-03 23:23:13 +02:00
|
|
|
|
|
|
|
|
pa_assert(u);
|
|
|
|
|
pa_assert(name);
|
|
|
|
|
|
2009-05-14 01:24:26 +02:00
|
|
|
key.data = (char*) name;
|
|
|
|
|
key.size = strlen(name);
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2009-05-14 01:24:26 +02:00
|
|
|
pa_zero(data);
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2009-05-14 01:24:26 +02:00
|
|
|
if (!pa_database_get(u->database, &key, &data))
|
2008-08-03 23:23:13 +02:00
|
|
|
goto fail;
|
|
|
|
|
|
2014-10-23 16:09:45 +02:00
|
|
|
t = pa_tagstruct_new_fixed(data.data, data.size);
|
2011-06-07 23:21:04 +01:00
|
|
|
e = entry_new();
|
|
|
|
|
|
2020-05-26 14:45:12 +03:00
|
|
|
if (pa_tagstruct_getu8(t, &version) < 0 ||
|
|
|
|
|
version > ENTRY_VERSION ||
|
2011-06-07 23:21:04 +01:00
|
|
|
pa_tagstruct_get_boolean(t, &e->volume_valid) < 0 ||
|
|
|
|
|
pa_tagstruct_get_channel_map(t, &e->channel_map) < 0 ||
|
|
|
|
|
pa_tagstruct_get_cvolume(t, &e->volume) < 0 ||
|
|
|
|
|
pa_tagstruct_get_boolean(t, &e->muted_valid) < 0 ||
|
|
|
|
|
pa_tagstruct_get_boolean(t, &e->muted) < 0 ||
|
|
|
|
|
pa_tagstruct_get_boolean(t, &e->device_valid) < 0 ||
|
|
|
|
|
pa_tagstruct_gets(t, &device) < 0 ||
|
|
|
|
|
pa_tagstruct_get_boolean(t, &e->card_valid) < 0 ||
|
|
|
|
|
pa_tagstruct_gets(t, &card) < 0) {
|
2008-08-03 23:23:13 +02:00
|
|
|
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
e->device = pa_xstrdup(device);
|
|
|
|
|
e->card = pa_xstrdup(card);
|
2008-08-04 19:00:43 +02:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
if (!pa_tagstruct_eof(t))
|
2009-08-14 23:55:32 +02:00
|
|
|
goto fail;
|
|
|
|
|
|
2020-05-26 14:57:42 +03:00
|
|
|
if (e->device_valid && (!e->device || !pa_namereg_is_valid_name(e->device))) {
|
2009-01-27 23:39:39 +01:00
|
|
|
pa_log_warn("Invalid device name stored in database for stream %s", name);
|
2008-08-04 19:00:43 +02:00
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 14:57:42 +03:00
|
|
|
if (e->card_valid && (!e->card || !pa_namereg_is_valid_name(e->card))) {
|
2009-08-14 23:55:32 +02:00
|
|
|
pa_log_warn("Invalid card name stored in database for stream %s", name);
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
2009-04-13 22:50:24 +02:00
|
|
|
if (e->volume_valid && !pa_channel_map_valid(&e->channel_map)) {
|
2009-02-04 18:31:24 +01:00
|
|
|
pa_log_warn("Invalid channel map stored in database for stream %s", name);
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
2009-04-13 22:50:24 +02:00
|
|
|
if (e->volume_valid && (!pa_cvolume_valid(&e->volume) || !pa_cvolume_compatible_with_channel_map(&e->volume, &e->channel_map))) {
|
2009-01-27 23:39:39 +01:00
|
|
|
pa_log_warn("Invalid volume stored in database for stream %s", name);
|
2008-08-03 23:23:13 +02:00
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
pa_tagstruct_free(t);
|
|
|
|
|
pa_datum_free(&data);
|
|
|
|
|
|
stream-restore: Forget pre-14.0 stream routing
Prior to commits f899d5f4669dcd536cc142cee99fe359dd8af3d6 and
f62a49b8cf109c011a9818d2358beb6834e6ec25, GNOME's sound settings
overwrote the routing for all entries in the stream-restore database
when selecting a device. Now we prevent that from happening (see the
aforementioned commits), but the old overwritten settings can still be in
the database after updating to PulseAudio 14.0, and they can cause
problems, as documented here:
https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/832
We can't distinguish between devices set by GNOME's sound settings
and devices set by the user, so this patch discards all old device
settings, even though that is going to cause PulseAudio to forget routing
settings for many users. This is less bad than keeping the incorrect
routing settings in the database, because it's difficult for users to
figure out how to fix the situation when e.g. speaker test tones go to
the internal speakers no matter what device is selected as the default,
whereas old manual configuration can be restored restored by doing the
manual configuration again. Also, it's probably more common to have at
some point changed the default device in GNOME's sound settings than it
is to have any manual per-stream routing settings.
This is disabled by default, because this causes data loss, but
distributions that use GNOME are recommended to enable this with
the --enable-stream-restore-clear-old-devices (Autotools) or
-Dstream-restore-clear-old-devices=true (Meson) build option.
Fixes: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/832
2020-05-26 15:04:59 +03:00
|
|
|
#ifdef STREAM_RESTORE_CLEAR_OLD_DEVICES
|
|
|
|
|
if (version < ENTRY_VERSION && e->device_valid) {
|
|
|
|
|
/* Prior to PulseAudio 14.0, GNOME's sound settings overwrote the
|
|
|
|
|
* routing for all entries in the stream-restore database when
|
|
|
|
|
* selecting a device. PulseAudio 14.0 prevents that from happening,
|
|
|
|
|
* but the old overwritten settings can still be in the database after
|
|
|
|
|
* updating to PulseAudio 14.0, and they can cause problems, as
|
|
|
|
|
* documented here:
|
|
|
|
|
* https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/832
|
|
|
|
|
*
|
|
|
|
|
* We can't distinguish between devices set by GNOME's sound settings
|
|
|
|
|
* and devices set by the user, so we discard all old device settings,
|
|
|
|
|
* even though that is going to cause PulseAudio to forget routing
|
|
|
|
|
* settings for many users. This is less bad than keeping the incorrect
|
|
|
|
|
* routing settings in the database, because it's difficult for users
|
|
|
|
|
* to figure out how to fix the situation when e.g. speaker test tones
|
|
|
|
|
* go to the internal speakers no matter what device is selected as the
|
|
|
|
|
* default, whereas old manual configuration can be restored restored
|
|
|
|
|
* by doing the manual configuration again. Also, it's probably more
|
|
|
|
|
* common to have at some point changed the default device in GNOME's
|
|
|
|
|
* sound settings than it is to have any manual per-stream routing
|
|
|
|
|
* settings. */
|
|
|
|
|
pa_log_warn("Device set, but it might be incorrect. Clearing the device. If this messes up your manual stream "
|
|
|
|
|
"routing configuration, sorry about that. This is a workaround for this bug: "
|
|
|
|
|
"https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/832");
|
|
|
|
|
pa_log_warn("%s: device: %s -> (unset)", name, e->device);
|
|
|
|
|
pa_xfree(e->device);
|
|
|
|
|
e->device = NULL;
|
|
|
|
|
e->device_valid = false;
|
|
|
|
|
if (e->card_valid) {
|
|
|
|
|
pa_log_warn("%s: card: %s -> (unset)", name, e->card);
|
|
|
|
|
pa_xfree(e->card);
|
|
|
|
|
e->card = NULL;
|
|
|
|
|
e->card_valid = false;
|
|
|
|
|
}
|
|
|
|
|
entry_write(u, name, e, true);
|
|
|
|
|
trigger_save(u);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2008-08-03 23:23:13 +02:00
|
|
|
return e;
|
|
|
|
|
|
|
|
|
|
fail:
|
2011-06-07 23:21:04 +01:00
|
|
|
if (e)
|
|
|
|
|
entry_free(e);
|
|
|
|
|
if (t)
|
|
|
|
|
pa_tagstruct_free(t);
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2011-06-08 00:47:19 +01:00
|
|
|
pa_datum_free(&data);
|
|
|
|
|
return NULL;
|
2011-06-07 23:21:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct entry* entry_copy(const struct entry *e) {
|
|
|
|
|
struct entry* r;
|
|
|
|
|
|
|
|
|
|
pa_assert(e);
|
|
|
|
|
r = entry_new();
|
|
|
|
|
*r = *e;
|
|
|
|
|
r->device = pa_xstrdup(e->device);
|
|
|
|
|
r->card = pa_xstrdup(e->card);
|
|
|
|
|
return r;
|
|
|
|
|
}
|
|
|
|
|
|
2008-08-04 19:01:13 +02:00
|
|
|
static void trigger_save(struct userdata *u) {
|
|
|
|
|
pa_native_connection *c;
|
|
|
|
|
uint32_t idx;
|
|
|
|
|
|
2012-05-29 17:58:11 +05:30
|
|
|
PA_IDXSET_FOREACH(c, u->subscribed, idx) {
|
2008-08-04 19:01:13 +02:00
|
|
|
pa_tagstruct *t;
|
|
|
|
|
|
2014-10-23 16:09:45 +02:00
|
|
|
t = pa_tagstruct_new();
|
2008-08-04 19:01:13 +02:00
|
|
|
pa_tagstruct_putu32(t, PA_COMMAND_EXTENSION);
|
|
|
|
|
pa_tagstruct_putu32(t, 0);
|
|
|
|
|
pa_tagstruct_putu32(t, u->module->index);
|
|
|
|
|
pa_tagstruct_puts(t, u->module->name);
|
|
|
|
|
pa_tagstruct_putu32(t, SUBCOMMAND_EVENT);
|
|
|
|
|
|
|
|
|
|
pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (u->save_time_event)
|
|
|
|
|
return;
|
|
|
|
|
|
2009-04-05 02:13:43 +03:00
|
|
|
u->save_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, save_time_callback, u);
|
2008-08-04 19:01:13 +02:00
|
|
|
}
|
|
|
|
|
|
2013-06-27 19:28:09 +02:00
|
|
|
static bool entries_equal(const struct entry *a, const struct entry *b) {
|
2009-01-27 23:39:39 +01:00
|
|
|
pa_cvolume t;
|
|
|
|
|
|
|
|
|
|
pa_assert(a);
|
|
|
|
|
pa_assert(b);
|
|
|
|
|
|
|
|
|
|
if (a->device_valid != b->device_valid ||
|
2011-06-07 23:21:04 +01:00
|
|
|
(a->device_valid && !pa_streq(a->device, b->device)))
|
2013-06-27 19:28:09 +02:00
|
|
|
return false;
|
2009-01-27 23:39:39 +01:00
|
|
|
|
2009-08-14 23:55:32 +02:00
|
|
|
if (a->card_valid != b->card_valid ||
|
2011-06-07 23:21:04 +01:00
|
|
|
(a->card_valid && !pa_streq(a->card, b->card)))
|
2013-06-27 19:28:09 +02:00
|
|
|
return false;
|
2009-08-14 23:55:32 +02:00
|
|
|
|
2009-01-27 23:39:39 +01:00
|
|
|
if (a->muted_valid != b->muted_valid ||
|
2009-02-04 18:32:15 +01:00
|
|
|
(a->muted_valid && (a->muted != b->muted)))
|
2013-06-27 19:28:09 +02:00
|
|
|
return false;
|
2009-01-27 23:39:39 +01:00
|
|
|
|
2009-04-13 22:50:24 +02:00
|
|
|
t = b->volume;
|
|
|
|
|
if (a->volume_valid != b->volume_valid ||
|
|
|
|
|
(a->volume_valid && !pa_cvolume_equal(pa_cvolume_remap(&t, &b->channel_map, &a->channel_map), &a->volume)))
|
2013-06-27 19:28:09 +02:00
|
|
|
return false;
|
2009-01-27 23:39:39 +01:00
|
|
|
|
2013-06-27 19:28:09 +02:00
|
|
|
return true;
|
2009-01-27 23:39:39 +01:00
|
|
|
}
|
|
|
|
|
|
2008-08-03 23:23:13 +02:00
|
|
|
static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
|
|
|
|
|
struct userdata *u = userdata;
|
2011-06-07 23:21:04 +01:00
|
|
|
struct entry *entry, *old = NULL;
|
2009-06-29 19:55:34 +01:00
|
|
|
char *name = NULL;
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2009-08-03 19:38:02 +03:00
|
|
|
/* These are only used when D-Bus is enabled, but in order to reduce ifdef
|
|
|
|
|
* clutter these are defined here unconditionally. */
|
2013-06-27 19:28:09 +02:00
|
|
|
bool created_new_entry = true;
|
|
|
|
|
bool device_updated = false;
|
|
|
|
|
bool volume_updated = false;
|
|
|
|
|
bool mute_updated = false;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
#ifdef HAVE_DBUS
|
|
|
|
|
struct dbus_entry *de = NULL;
|
|
|
|
|
#endif
|
|
|
|
|
|
2008-08-03 23:23:13 +02:00
|
|
|
pa_assert(c);
|
|
|
|
|
pa_assert(u);
|
|
|
|
|
|
|
|
|
|
if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&
|
|
|
|
|
t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) &&
|
|
|
|
|
t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW) &&
|
|
|
|
|
t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {
|
|
|
|
|
pa_sink_input *sink_input;
|
|
|
|
|
|
|
|
|
|
if (!(sink_input = pa_idxset_get_by_index(c->sink_inputs, idx)))
|
|
|
|
|
return;
|
|
|
|
|
|
2017-05-31 20:54:43 +02:00
|
|
|
/* Ignore this sink input if it is connecting a filter sink to
|
|
|
|
|
* the master */
|
|
|
|
|
if (sink_input->origin_sink)
|
|
|
|
|
return;
|
|
|
|
|
|
2011-11-23 16:26:40 +05:30
|
|
|
if (!(name = pa_proplist_get_stream_group(sink_input->proplist, "sink-input", IDENTIFICATION_PROPERTY)))
|
2008-08-03 23:23:13 +02:00
|
|
|
return;
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
if ((old = entry_read(u, name))) {
|
|
|
|
|
entry = entry_copy(old);
|
2013-06-27 19:28:09 +02:00
|
|
|
created_new_entry = false;
|
2011-06-07 23:21:04 +01:00
|
|
|
} else
|
|
|
|
|
entry = entry_new();
|
2009-03-20 13:51:08 +01:00
|
|
|
|
2011-04-12 13:11:40 +05:30
|
|
|
if (sink_input->save_volume && pa_sink_input_is_volume_readable(sink_input)) {
|
2011-03-27 23:00:26 +03:00
|
|
|
pa_assert(sink_input->volume_writable);
|
2011-02-14 13:41:06 +02:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry->channel_map = sink_input->channel_map;
|
2013-06-27 19:28:09 +02:00
|
|
|
pa_sink_input_get_volume(sink_input, &entry->volume, false);
|
|
|
|
|
entry->volume_valid = true;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
volume_updated = !created_new_entry
|
|
|
|
|
&& (!old->volume_valid
|
2011-06-07 23:21:04 +01:00
|
|
|
|| !pa_channel_map_equal(&entry->channel_map, &old->channel_map)
|
|
|
|
|
|| !pa_cvolume_equal(&entry->volume, &old->volume));
|
2009-04-13 22:50:24 +02:00
|
|
|
}
|
2009-01-27 23:39:39 +01:00
|
|
|
|
2009-04-13 22:50:24 +02:00
|
|
|
if (sink_input->save_muted) {
|
2014-04-15 13:56:11 +03:00
|
|
|
entry->muted = sink_input->muted;
|
2013-06-27 19:28:09 +02:00
|
|
|
entry->muted_valid = true;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
mute_updated = !created_new_entry && (!old->muted_valid || entry->muted != old->muted);
|
2009-04-13 22:50:24 +02:00
|
|
|
}
|
2009-01-27 23:39:39 +01:00
|
|
|
|
2019-01-16 09:07:26 +08:00
|
|
|
if (sink_input->preferred_sink != NULL || !created_new_entry) {
|
|
|
|
|
pa_sink *s = NULL;
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
pa_xfree(entry->device);
|
2019-01-15 12:12:52 +08:00
|
|
|
entry->device = pa_xstrdup(sink_input->preferred_sink);
|
2013-06-27 19:28:09 +02:00
|
|
|
entry->device_valid = true;
|
2019-01-16 09:07:26 +08:00
|
|
|
if (!entry->device)
|
|
|
|
|
entry->device_valid = false;
|
|
|
|
|
|
|
|
|
|
device_updated = !created_new_entry && !pa_safe_streq(entry->device, old->device);
|
|
|
|
|
pa_xfree(entry->card);
|
|
|
|
|
entry->card = NULL;
|
|
|
|
|
entry->card_valid = false;
|
|
|
|
|
if (entry->device_valid && (s = pa_namereg_get(c, entry->device, PA_NAMEREG_SINK)) && s->card) {
|
2019-01-15 12:12:52 +08:00
|
|
|
entry->card = pa_xstrdup(s->card->name);
|
2013-06-27 19:28:09 +02:00
|
|
|
entry->card_valid = true;
|
2009-08-14 23:55:32 +02:00
|
|
|
}
|
2009-04-13 22:50:24 +02:00
|
|
|
}
|
2008-08-03 23:23:13 +02:00
|
|
|
} else {
|
|
|
|
|
pa_source_output *source_output;
|
|
|
|
|
|
|
|
|
|
pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT);
|
|
|
|
|
|
|
|
|
|
if (!(source_output = pa_idxset_get_by_index(c->source_outputs, idx)))
|
|
|
|
|
return;
|
|
|
|
|
|
2017-05-31 20:54:43 +02:00
|
|
|
/* Ignore this source output if it is connecting a filter source to
|
|
|
|
|
* the master */
|
|
|
|
|
if (source_output->destination_source)
|
|
|
|
|
return;
|
|
|
|
|
|
2011-11-23 16:26:40 +05:30
|
|
|
if (!(name = pa_proplist_get_stream_group(source_output->proplist, "source-output", IDENTIFICATION_PROPERTY)))
|
2008-08-03 23:23:13 +02:00
|
|
|
return;
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
if ((old = entry_read(u, name))) {
|
|
|
|
|
entry = entry_copy(old);
|
2013-06-27 19:28:09 +02:00
|
|
|
created_new_entry = false;
|
2011-06-07 23:21:04 +01:00
|
|
|
} else
|
|
|
|
|
entry = entry_new();
|
2009-01-27 23:39:39 +01:00
|
|
|
|
2011-08-07 01:08:28 +02:00
|
|
|
if (source_output->save_volume && pa_source_output_is_volume_readable(source_output)) {
|
|
|
|
|
pa_assert(source_output->volume_writable);
|
|
|
|
|
|
|
|
|
|
entry->channel_map = source_output->channel_map;
|
2013-06-27 19:28:09 +02:00
|
|
|
pa_source_output_get_volume(source_output, &entry->volume, false);
|
|
|
|
|
entry->volume_valid = true;
|
2011-08-07 01:08:28 +02:00
|
|
|
|
|
|
|
|
volume_updated = !created_new_entry
|
|
|
|
|
&& (!old->volume_valid
|
|
|
|
|
|| !pa_channel_map_equal(&entry->channel_map, &old->channel_map)
|
|
|
|
|
|| !pa_cvolume_equal(&entry->volume, &old->volume));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (source_output->save_muted) {
|
2014-04-15 13:56:11 +03:00
|
|
|
entry->muted = source_output->muted;
|
2013-06-27 19:28:09 +02:00
|
|
|
entry->muted_valid = true;
|
2011-08-07 01:08:28 +02:00
|
|
|
|
|
|
|
|
mute_updated = !created_new_entry && (!old->muted_valid || entry->muted != old->muted);
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-07 10:29:14 +08:00
|
|
|
if (source_output->preferred_source != NULL || !created_new_entry) {
|
|
|
|
|
pa_source *s = NULL;
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
pa_xfree(entry->device);
|
2019-12-06 21:51:10 +08:00
|
|
|
entry->device = pa_xstrdup(source_output->preferred_source);
|
2013-06-27 19:28:09 +02:00
|
|
|
entry->device_valid = true;
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2019-12-07 10:29:14 +08:00
|
|
|
if (!entry->device)
|
|
|
|
|
entry->device_valid = false;
|
2009-08-16 21:25:48 +03:00
|
|
|
|
2019-12-07 10:29:14 +08:00
|
|
|
device_updated = !created_new_entry && !pa_safe_streq(entry->device, old->device);
|
|
|
|
|
pa_xfree(entry->card);
|
|
|
|
|
entry->card = NULL;
|
|
|
|
|
entry->card_valid = false;
|
|
|
|
|
if (entry->device_valid && (s = pa_namereg_get(c, entry->device, PA_NAMEREG_SOURCE)) && s->card) {
|
2019-12-06 21:51:10 +08:00
|
|
|
entry->card = pa_xstrdup(s->card->name);
|
2013-06-27 19:28:09 +02:00
|
|
|
entry->card_valid = true;
|
2009-08-14 23:55:32 +02:00
|
|
|
}
|
2009-04-13 22:50:24 +02:00
|
|
|
}
|
2008-08-03 23:23:13 +02:00
|
|
|
}
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
pa_assert(entry);
|
|
|
|
|
|
2009-04-13 22:50:24 +02:00
|
|
|
if (old) {
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
if (entries_equal(old, entry)) {
|
|
|
|
|
entry_free(old);
|
|
|
|
|
entry_free(entry);
|
2008-08-03 23:23:13 +02:00
|
|
|
pa_xfree(name);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_free(old);
|
2008-08-03 23:23:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_log_info("Storing volume/mute/device for stream %s.", name);
|
|
|
|
|
|
2020-10-13 09:50:31 +03:00
|
|
|
if (entry_write(u, name, entry, true)) {
|
2011-06-07 23:21:04 +01:00
|
|
|
trigger_save(u);
|
2020-10-13 09:50:31 +03:00
|
|
|
} else {
|
|
|
|
|
pa_log_error("Could not store volume/mute/device for stream %s.", name);
|
|
|
|
|
}
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2009-08-03 19:38:02 +03:00
|
|
|
#ifdef HAVE_DBUS
|
2020-10-13 09:36:50 +03:00
|
|
|
if (!(de = pa_hashmap_get(u->dbus_entries, name))) {
|
2009-08-03 19:38:02 +03:00
|
|
|
de = dbus_entry_new(u, name);
|
2009-12-03 15:31:11 +02:00
|
|
|
pa_assert_se(pa_hashmap_put(u->dbus_entries, de->entry_name, de) == 0);
|
2009-08-03 19:38:02 +03:00
|
|
|
send_new_entry_signal(de);
|
|
|
|
|
} else {
|
|
|
|
|
if (device_updated)
|
2011-06-07 23:21:04 +01:00
|
|
|
send_device_updated_signal(de, entry);
|
2009-08-03 19:38:02 +03:00
|
|
|
if (volume_updated)
|
2011-06-07 23:21:04 +01:00
|
|
|
send_volume_updated_signal(de, entry);
|
2009-08-03 19:38:02 +03:00
|
|
|
if (mute_updated)
|
2011-06-07 23:21:04 +01:00
|
|
|
send_mute_updated_signal(de, entry);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
2018-10-19 16:28:57 +07:00
|
|
|
#else
|
|
|
|
|
/* Silence compiler warnings */
|
|
|
|
|
(void) device_updated;
|
|
|
|
|
(void) volume_updated;
|
|
|
|
|
(void) mute_updated;
|
2009-08-03 19:38:02 +03:00
|
|
|
#endif
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_free(entry);
|
2008-08-03 23:23:13 +02:00
|
|
|
pa_xfree(name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u) {
|
|
|
|
|
char *name;
|
|
|
|
|
struct entry *e;
|
|
|
|
|
|
2009-06-22 22:33:09 +02:00
|
|
|
pa_assert(c);
|
2008-08-03 23:23:13 +02:00
|
|
|
pa_assert(new_data);
|
2009-06-22 22:33:09 +02:00
|
|
|
pa_assert(u);
|
|
|
|
|
pa_assert(u->restore_device);
|
2009-01-27 23:39:39 +01:00
|
|
|
|
2011-11-23 16:26:40 +05:30
|
|
|
if (!(name = pa_proplist_get_stream_group(new_data->proplist, "sink-input", IDENTIFICATION_PROPERTY)))
|
2008-08-03 23:23:13 +02:00
|
|
|
return PA_HOOK_OK;
|
|
|
|
|
|
2009-08-14 23:55:32 +02:00
|
|
|
if (new_data->sink)
|
2010-02-21 16:22:17 +01:00
|
|
|
pa_log_debug("Not restoring device for stream %s, because already set to '%s'.", name, new_data->sink->name);
|
2017-05-31 20:54:43 +02:00
|
|
|
else if (new_data->origin_sink)
|
|
|
|
|
pa_log_debug("Not restoring device for stream %s, because it connects a filter to the master sink.", name);
|
2011-06-07 23:21:04 +01:00
|
|
|
else if ((e = entry_read(u, name))) {
|
2009-08-14 23:55:32 +02:00
|
|
|
pa_sink *s = NULL;
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2020-04-06 10:19:56 +02:00
|
|
|
if (e->device_valid) {
|
2009-08-14 23:55:32 +02:00
|
|
|
s = pa_namereg_get(c, e->device, PA_NAMEREG_SINK);
|
2020-04-06 10:19:56 +02:00
|
|
|
new_data->preferred_sink = pa_xstrdup(e->device);
|
|
|
|
|
}
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2009-08-14 23:55:32 +02:00
|
|
|
if (!s && e->card_valid) {
|
|
|
|
|
pa_card *card;
|
|
|
|
|
|
|
|
|
|
if ((card = pa_namereg_get(c, e->card, PA_NAMEREG_CARD)))
|
|
|
|
|
s = pa_idxset_first(card->sinks, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* It might happen that a stream and a sink are set up at the
|
|
|
|
|
same time, in which case we want to make sure we don't
|
|
|
|
|
interfere with that */
|
2018-06-26 16:25:58 +03:00
|
|
|
if (s && PA_SINK_IS_LINKED(s->state))
|
stream-restore: Don't restore if the active_port is PA_AVAILABLE_NO
We met two problems recently, one happened on a Lenovo machine with
dual analogue codecs, the other happened on a Dell machine with
a digital mic directly connected to PCH. The two problems are
basically same, there is an internal mic and an external mic, the
internal mic always shows up in the gnome-control-center, the external
mic only shows up when it is plugged. After the external mic is
plugged and users select it from gnome-control-center, the
gnome-control-center will read all saved streams through extension_cb,
and bind the source of external mic to all streams, after that the
apps only record sound via the source of external mic, after the
external mic is unplugged, the internal mic will automatically be
selected since it is the only left input device in the
gnome-control-center, since users don't select it, all streams are
still bond the source of external mic. When users record sound via
apps, they can't record any sound even the default_source is the
source of internal mic and the internal mic is selected in the UI.
It is very common that a machine has internal mic and external mic,
but this problem didn't expose before, that is because both internal
mic and external mic belong to one source, but for those two
machines, the internal mic belongs to one source, while the external
mic belongs to another source (they are in differnt codecs or one is
in the codec and the other is from PCH),
To fix it with a mininal change, we just check if the active_port is
PA_AVAILABLE_NO or not when building a new stream, if it is, don't
restore the device to the new built stream, let pa_source_output_new()
decide the source device for this stream.
And we also do the same change to sink_input.
This change only affects the new built streams, it will not change
the database, so the users' preference is still saved in the database,
after the active_port is not PA_AVAILABLE_NO, the new streams will
still restore to the preferred device.
Signed-off-by: Hui Wang <hui.wang@canonical.com>
2019-05-15 14:39:27 +08:00
|
|
|
if (!s->active_port || s->active_port->available != PA_AVAILABLE_NO) {
|
|
|
|
|
if (pa_sink_input_new_data_set_sink(new_data, s, true, false))
|
|
|
|
|
pa_log_info("Restoring device for stream %s.", name);
|
|
|
|
|
}
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_free(e);
|
2008-08-03 23:23:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_xfree(name);
|
|
|
|
|
|
|
|
|
|
return PA_HOOK_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u) {
|
|
|
|
|
char *name;
|
|
|
|
|
struct entry *e;
|
|
|
|
|
|
2009-06-22 22:33:09 +02:00
|
|
|
pa_assert(c);
|
2008-08-03 23:23:13 +02:00
|
|
|
pa_assert(new_data);
|
2009-06-22 22:33:09 +02:00
|
|
|
pa_assert(u);
|
|
|
|
|
pa_assert(u->restore_volume || u->restore_muted);
|
2009-01-27 23:39:39 +01:00
|
|
|
|
2011-11-23 16:26:40 +05:30
|
|
|
if (!(name = pa_proplist_get_stream_group(new_data->proplist, "sink-input", IDENTIFICATION_PROPERTY)))
|
2008-08-03 23:23:13 +02:00
|
|
|
return PA_HOOK_OK;
|
|
|
|
|
|
2017-05-31 20:54:43 +02:00
|
|
|
if (new_data->origin_sink) {
|
|
|
|
|
pa_log_debug("Not restoring volume for sink input %s, because it connects a filter to the master sink.", name);
|
|
|
|
|
return PA_HOOK_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
if ((e = entry_read(u, name))) {
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2009-04-13 22:50:24 +02:00
|
|
|
if (u->restore_volume && e->volume_valid) {
|
2011-03-27 23:00:26 +03:00
|
|
|
if (!new_data->volume_writable)
|
2011-02-14 13:41:06 +02:00
|
|
|
pa_log_debug("Not restoring volume for sink input %s, because its volume can't be changed.", name);
|
|
|
|
|
else if (new_data->volume_is_set)
|
|
|
|
|
pa_log_debug("Not restoring volume for sink input %s, because already set.", name);
|
|
|
|
|
else {
|
2009-01-27 23:39:39 +01:00
|
|
|
pa_cvolume v;
|
|
|
|
|
|
2009-04-13 22:50:24 +02:00
|
|
|
pa_log_info("Restoring volume for sink input %s.", name);
|
2009-06-18 00:59:33 +02:00
|
|
|
|
2009-04-13 22:50:24 +02:00
|
|
|
v = e->volume;
|
|
|
|
|
pa_cvolume_remap(&v, &e->channel_map, &new_data->channel_map);
|
|
|
|
|
pa_sink_input_new_data_set_volume(new_data, &v);
|
|
|
|
|
|
2013-06-27 19:28:09 +02:00
|
|
|
new_data->volume_is_absolute = false;
|
|
|
|
|
new_data->save_volume = true;
|
2011-02-14 13:41:06 +02:00
|
|
|
}
|
2008-08-03 23:23:13 +02:00
|
|
|
}
|
|
|
|
|
|
2009-01-27 23:39:39 +01:00
|
|
|
if (u->restore_muted && e->muted_valid) {
|
2009-04-13 22:50:24 +02:00
|
|
|
|
2008-08-18 17:49:47 +02:00
|
|
|
if (!new_data->muted_is_set) {
|
|
|
|
|
pa_log_info("Restoring mute state for sink input %s.", name);
|
|
|
|
|
pa_sink_input_new_data_set_muted(new_data, e->muted);
|
2013-06-27 19:28:09 +02:00
|
|
|
new_data->save_muted = true;
|
2008-08-18 17:49:47 +02:00
|
|
|
} else
|
|
|
|
|
pa_log_debug("Not restoring mute state for sink input %s, because already set.", name);
|
2008-08-03 23:23:13 +02:00
|
|
|
}
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_free(e);
|
2008-08-03 23:23:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_xfree(name);
|
|
|
|
|
|
|
|
|
|
return PA_HOOK_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *new_data, struct userdata *u) {
|
|
|
|
|
char *name;
|
|
|
|
|
struct entry *e;
|
|
|
|
|
|
2009-06-22 22:33:09 +02:00
|
|
|
pa_assert(c);
|
2008-08-03 23:23:13 +02:00
|
|
|
pa_assert(new_data);
|
2009-06-22 22:33:09 +02:00
|
|
|
pa_assert(u);
|
|
|
|
|
pa_assert(u->restore_device);
|
2009-01-27 23:39:39 +01:00
|
|
|
|
|
|
|
|
if (new_data->direct_on_input)
|
|
|
|
|
return PA_HOOK_OK;
|
|
|
|
|
|
2011-11-23 16:26:40 +05:30
|
|
|
if (!(name = pa_proplist_get_stream_group(new_data->proplist, "source-output", IDENTIFICATION_PROPERTY)))
|
2008-08-03 23:23:13 +02:00
|
|
|
return PA_HOOK_OK;
|
|
|
|
|
|
2009-08-14 23:55:32 +02:00
|
|
|
if (new_data->source)
|
|
|
|
|
pa_log_debug("Not restoring device for stream %s, because already set", name);
|
2017-05-31 20:54:43 +02:00
|
|
|
else if (new_data->destination_source)
|
|
|
|
|
pa_log_debug("Not restoring device for stream %s, because it connects a filter to the master source.", name);
|
2011-06-07 23:21:04 +01:00
|
|
|
else if ((e = entry_read(u, name))) {
|
2009-08-14 23:55:32 +02:00
|
|
|
pa_source *s = NULL;
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2020-04-06 10:19:56 +02:00
|
|
|
if (e->device_valid) {
|
2009-08-14 23:55:32 +02:00
|
|
|
s = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE);
|
2020-04-06 10:19:56 +02:00
|
|
|
new_data->preferred_source = pa_xstrdup(e->device);
|
|
|
|
|
}
|
2009-08-14 23:55:32 +02:00
|
|
|
|
|
|
|
|
if (!s && e->card_valid) {
|
|
|
|
|
pa_card *card;
|
|
|
|
|
|
|
|
|
|
if ((card = pa_namereg_get(c, e->card, PA_NAMEREG_CARD)))
|
|
|
|
|
s = pa_idxset_first(card->sources, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* It might happen that a stream and a sink are set up at the
|
|
|
|
|
same time, in which case we want to make sure we don't
|
|
|
|
|
interfere with that */
|
2018-06-26 16:25:58 +03:00
|
|
|
if (s && PA_SOURCE_IS_LINKED(s->state)) {
|
stream-restore: Don't restore if the active_port is PA_AVAILABLE_NO
We met two problems recently, one happened on a Lenovo machine with
dual analogue codecs, the other happened on a Dell machine with
a digital mic directly connected to PCH. The two problems are
basically same, there is an internal mic and an external mic, the
internal mic always shows up in the gnome-control-center, the external
mic only shows up when it is plugged. After the external mic is
plugged and users select it from gnome-control-center, the
gnome-control-center will read all saved streams through extension_cb,
and bind the source of external mic to all streams, after that the
apps only record sound via the source of external mic, after the
external mic is unplugged, the internal mic will automatically be
selected since it is the only left input device in the
gnome-control-center, since users don't select it, all streams are
still bond the source of external mic. When users record sound via
apps, they can't record any sound even the default_source is the
source of internal mic and the internal mic is selected in the UI.
It is very common that a machine has internal mic and external mic,
but this problem didn't expose before, that is because both internal
mic and external mic belong to one source, but for those two
machines, the internal mic belongs to one source, while the external
mic belongs to another source (they are in differnt codecs or one is
in the codec and the other is from PCH),
To fix it with a mininal change, we just check if the active_port is
PA_AVAILABLE_NO or not when building a new stream, if it is, don't
restore the device to the new built stream, let pa_source_output_new()
decide the source device for this stream.
And we also do the same change to sink_input.
This change only affects the new built streams, it will not change
the database, so the users' preference is still saved in the database,
after the active_port is not PA_AVAILABLE_NO, the new streams will
still restore to the preferred device.
Signed-off-by: Hui Wang <hui.wang@canonical.com>
2019-05-15 14:39:27 +08:00
|
|
|
if (!s->active_port || s->active_port->available != PA_AVAILABLE_NO) {
|
|
|
|
|
pa_log_info("Restoring device for stream %s.", name);
|
|
|
|
|
pa_source_output_new_data_set_source(new_data, s, true, false);
|
|
|
|
|
}
|
2008-08-03 23:23:13 +02:00
|
|
|
}
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_free(e);
|
2008-08-03 23:23:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_xfree(name);
|
|
|
|
|
|
|
|
|
|
return PA_HOOK_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-07 01:08:28 +02:00
|
|
|
static pa_hook_result_t source_output_fixate_hook_callback(pa_core *c, pa_source_output_new_data *new_data, struct userdata *u) {
|
|
|
|
|
char *name;
|
|
|
|
|
struct entry *e;
|
|
|
|
|
|
|
|
|
|
pa_assert(c);
|
|
|
|
|
pa_assert(new_data);
|
|
|
|
|
pa_assert(u);
|
|
|
|
|
pa_assert(u->restore_volume || u->restore_muted);
|
|
|
|
|
|
2011-11-23 16:26:40 +05:30
|
|
|
if (!(name = pa_proplist_get_stream_group(new_data->proplist, "source-output", IDENTIFICATION_PROPERTY)))
|
2011-08-07 01:08:28 +02:00
|
|
|
return PA_HOOK_OK;
|
|
|
|
|
|
2017-05-31 20:54:43 +02:00
|
|
|
if (new_data->destination_source) {
|
|
|
|
|
pa_log_debug("Not restoring volume for source output %s, because it connects a filter to the master source.", name);
|
|
|
|
|
return PA_HOOK_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2011-08-07 01:08:28 +02:00
|
|
|
if ((e = entry_read(u, name))) {
|
|
|
|
|
|
|
|
|
|
if (u->restore_volume && e->volume_valid) {
|
|
|
|
|
if (!new_data->volume_writable)
|
|
|
|
|
pa_log_debug("Not restoring volume for source output %s, because its volume can't be changed.", name);
|
|
|
|
|
else if (new_data->volume_is_set)
|
|
|
|
|
pa_log_debug("Not restoring volume for source output %s, because already set.", name);
|
|
|
|
|
else {
|
|
|
|
|
pa_cvolume v;
|
|
|
|
|
|
|
|
|
|
pa_log_info("Restoring volume for source output %s.", name);
|
|
|
|
|
|
|
|
|
|
v = e->volume;
|
|
|
|
|
pa_cvolume_remap(&v, &e->channel_map, &new_data->channel_map);
|
|
|
|
|
pa_source_output_new_data_set_volume(new_data, &v);
|
|
|
|
|
|
2013-06-27 19:28:09 +02:00
|
|
|
new_data->volume_is_absolute = false;
|
|
|
|
|
new_data->save_volume = true;
|
2011-08-07 01:08:28 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (u->restore_muted && e->muted_valid) {
|
|
|
|
|
|
|
|
|
|
if (!new_data->muted_is_set) {
|
|
|
|
|
pa_log_info("Restoring mute state for source output %s.", name);
|
|
|
|
|
pa_source_output_new_data_set_muted(new_data, e->muted);
|
2013-06-27 19:28:09 +02:00
|
|
|
new_data->save_muted = true;
|
2011-08-07 01:08:28 +02:00
|
|
|
} else
|
|
|
|
|
pa_log_debug("Not restoring mute state for source output %s, because already set.", name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
entry_free(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_xfree(name);
|
|
|
|
|
|
|
|
|
|
return PA_HOOK_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-01 12:16:35 +01:00
|
|
|
static int fill_db(struct userdata *u, const char *filename) {
|
|
|
|
|
FILE *f;
|
|
|
|
|
int n = 0;
|
|
|
|
|
int ret = -1;
|
|
|
|
|
char *fn = NULL;
|
|
|
|
|
|
|
|
|
|
pa_assert(u);
|
|
|
|
|
|
|
|
|
|
if (filename)
|
|
|
|
|
f = fopen(fn = pa_xstrdup(filename), "r");
|
|
|
|
|
else
|
|
|
|
|
f = pa_open_config_file(DEFAULT_FALLBACK_FILE, DEFAULT_FALLBACK_FILE_USER, NULL, &fn);
|
|
|
|
|
|
|
|
|
|
if (!f) {
|
|
|
|
|
if (filename)
|
|
|
|
|
pa_log("Failed to open %s: %s", filename, pa_cstrerror(errno));
|
|
|
|
|
else
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (!feof(f)) {
|
|
|
|
|
char ln[256];
|
|
|
|
|
char *d, *v;
|
|
|
|
|
double db;
|
|
|
|
|
|
|
|
|
|
if (!fgets(ln, sizeof(ln), f))
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
n++;
|
|
|
|
|
|
|
|
|
|
pa_strip_nl(ln);
|
|
|
|
|
|
|
|
|
|
if (!*ln || ln[0] == '#' || ln[0] == ';')
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
d = ln+strcspn(ln, WHITESPACE);
|
|
|
|
|
v = d+strspn(d, WHITESPACE);
|
|
|
|
|
|
|
|
|
|
if (!*v) {
|
|
|
|
|
pa_log("[%s:%u] failed to parse line - too few words", fn, n);
|
|
|
|
|
goto finish;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*d = 0;
|
|
|
|
|
if (pa_atod(v, &db) >= 0) {
|
|
|
|
|
if (db <= 0.0) {
|
|
|
|
|
struct entry e;
|
|
|
|
|
|
|
|
|
|
pa_zero(e);
|
2013-06-27 19:28:09 +02:00
|
|
|
e.volume_valid = true;
|
2011-10-01 12:16:35 +01:00
|
|
|
pa_cvolume_set(&e.volume, 1, pa_sw_volume_from_dB(db));
|
|
|
|
|
pa_channel_map_init_mono(&e.channel_map);
|
|
|
|
|
|
2014-10-01 20:07:04 -03:00
|
|
|
if (entry_write(u, ln, &e, false))
|
2011-10-01 12:16:35 +01:00
|
|
|
pa_log_debug("Setting %s to %0.2f dB.", ln, db);
|
|
|
|
|
} else
|
|
|
|
|
pa_log_warn("[%s:%u] Positive dB values are not allowed, not setting entry %s.", fn, n, ln);
|
|
|
|
|
} else
|
|
|
|
|
pa_log_warn("[%s:%u] Couldn't parse '%s' as a double, not setting entry %s.", fn, n, v, ln);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trigger_save(u);
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
|
|
finish:
|
|
|
|
|
if (f)
|
|
|
|
|
fclose(f);
|
|
|
|
|
|
|
|
|
|
pa_xfree(fn);
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
2008-08-04 19:01:13 +02:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
static void entry_apply(struct userdata *u, const char *name, struct entry *e) {
|
2008-08-04 19:01:13 +02:00
|
|
|
pa_sink_input *si;
|
|
|
|
|
pa_source_output *so;
|
|
|
|
|
uint32_t idx;
|
|
|
|
|
|
|
|
|
|
pa_assert(u);
|
|
|
|
|
pa_assert(name);
|
|
|
|
|
pa_assert(e);
|
|
|
|
|
|
2009-07-01 21:11:19 +02:00
|
|
|
PA_IDXSET_FOREACH(si, u->core->sink_inputs, idx) {
|
2008-08-04 19:01:13 +02:00
|
|
|
char *n;
|
|
|
|
|
pa_sink *s;
|
|
|
|
|
|
2011-11-23 16:26:40 +05:30
|
|
|
if (!(n = pa_proplist_get_stream_group(si->proplist, "sink-input", IDENTIFICATION_PROPERTY)))
|
2008-08-04 19:01:13 +02:00
|
|
|
continue;
|
|
|
|
|
|
2009-03-20 13:51:08 +01:00
|
|
|
if (!pa_streq(name, n)) {
|
2008-08-04 19:01:13 +02:00
|
|
|
pa_xfree(n);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2009-03-20 13:51:08 +01:00
|
|
|
pa_xfree(n);
|
2008-08-04 19:01:13 +02:00
|
|
|
|
2011-03-27 23:00:26 +03:00
|
|
|
if (u->restore_volume && e->volume_valid && si->volume_writable) {
|
2009-01-27 23:39:39 +01:00
|
|
|
pa_cvolume v;
|
|
|
|
|
|
2009-04-13 22:50:24 +02:00
|
|
|
v = e->volume;
|
|
|
|
|
pa_log_info("Restoring volume for sink input %s.", name);
|
2009-07-01 21:11:19 +02:00
|
|
|
pa_cvolume_remap(&v, &e->channel_map, &si->channel_map);
|
2013-06-27 19:28:09 +02:00
|
|
|
pa_sink_input_set_volume(si, &v, true, false);
|
2008-08-04 19:01:13 +02:00
|
|
|
}
|
|
|
|
|
|
2009-04-13 22:50:24 +02:00
|
|
|
if (u->restore_muted && e->muted_valid) {
|
2008-08-04 19:01:13 +02:00
|
|
|
pa_log_info("Restoring mute state for sink input %s.", name);
|
2013-06-27 19:28:09 +02:00
|
|
|
pa_sink_input_set_mute(si, e->muted, true);
|
2008-08-04 19:01:13 +02:00
|
|
|
}
|
|
|
|
|
|
2010-02-05 00:15:38 +00:00
|
|
|
if (u->restore_device) {
|
|
|
|
|
if (!e->device_valid) {
|
2019-01-15 12:12:52 +08:00
|
|
|
if (si->preferred_sink != NULL) {
|
2010-02-05 00:15:38 +00:00
|
|
|
pa_log_info("Ensuring device is not saved for stream %s.", name);
|
|
|
|
|
/* If the device is not valid we should make sure the
|
2019-01-15 12:12:52 +08:00
|
|
|
preferred_sink is cleared as the user may have specifically
|
2010-02-05 00:15:38 +00:00
|
|
|
removed the sink element from the rule. */
|
2019-01-15 19:28:23 +08:00
|
|
|
pa_sink_input_set_preferred_sink(si, NULL);
|
2010-02-05 00:15:38 +00:00
|
|
|
}
|
|
|
|
|
} else if ((s = pa_namereg_get(u->core, e->device, PA_NAMEREG_SINK))) {
|
|
|
|
|
pa_log_info("Restoring device for stream %s.", name);
|
2019-01-15 19:28:23 +08:00
|
|
|
pa_sink_input_set_preferred_sink(si, s);
|
2010-02-05 00:15:38 +00:00
|
|
|
}
|
2008-08-04 19:01:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2009-07-01 21:11:19 +02:00
|
|
|
PA_IDXSET_FOREACH(so, u->core->source_outputs, idx) {
|
2008-08-04 19:01:13 +02:00
|
|
|
char *n;
|
|
|
|
|
pa_source *s;
|
|
|
|
|
|
2011-11-23 16:26:40 +05:30
|
|
|
if (!(n = pa_proplist_get_stream_group(so->proplist, "source-output", IDENTIFICATION_PROPERTY)))
|
2008-08-04 19:01:13 +02:00
|
|
|
continue;
|
|
|
|
|
|
2009-03-20 13:51:08 +01:00
|
|
|
if (!pa_streq(name, n)) {
|
2008-08-04 19:01:13 +02:00
|
|
|
pa_xfree(n);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2009-03-20 13:51:08 +01:00
|
|
|
pa_xfree(n);
|
2008-08-04 19:01:13 +02:00
|
|
|
|
2011-08-07 01:08:28 +02:00
|
|
|
if (u->restore_volume && e->volume_valid && so->volume_writable) {
|
|
|
|
|
pa_cvolume v;
|
|
|
|
|
|
|
|
|
|
v = e->volume;
|
|
|
|
|
pa_log_info("Restoring volume for source output %s.", name);
|
|
|
|
|
pa_cvolume_remap(&v, &e->channel_map, &so->channel_map);
|
2013-06-27 19:28:09 +02:00
|
|
|
pa_source_output_set_volume(so, &v, true, false);
|
2011-08-07 01:08:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (u->restore_muted && e->muted_valid) {
|
|
|
|
|
pa_log_info("Restoring mute state for source output %s.", name);
|
2013-06-27 19:28:09 +02:00
|
|
|
pa_source_output_set_mute(so, e->muted, true);
|
2011-08-07 01:08:28 +02:00
|
|
|
}
|
|
|
|
|
|
2010-02-05 00:15:38 +00:00
|
|
|
if (u->restore_device) {
|
|
|
|
|
if (!e->device_valid) {
|
2019-12-06 21:51:10 +08:00
|
|
|
if (so->preferred_source != NULL) {
|
2010-02-05 00:15:38 +00:00
|
|
|
pa_log_info("Ensuring device is not saved for stream %s.", name);
|
|
|
|
|
/* If the device is not valid we should make sure the
|
2019-12-06 21:51:10 +08:00
|
|
|
preferred_source is cleared as the user may have specifically
|
2010-02-05 00:15:38 +00:00
|
|
|
removed the source element from the rule. */
|
2019-12-07 10:13:28 +08:00
|
|
|
pa_source_output_set_preferred_source(so, NULL);
|
2010-02-05 00:15:38 +00:00
|
|
|
}
|
|
|
|
|
} else if ((s = pa_namereg_get(u->core, e->device, PA_NAMEREG_SOURCE))) {
|
|
|
|
|
pa_log_info("Restoring device for stream %s.", name);
|
2019-12-07 10:13:28 +08:00
|
|
|
pa_source_output_set_preferred_source(so, s);
|
2010-02-05 00:15:38 +00:00
|
|
|
}
|
2008-08-04 19:01:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-27 13:08:38 +03:00
|
|
|
#ifdef DEBUG_VOLUME
|
|
|
|
|
PA_GCC_UNUSED static void stream_restore_dump_database(struct userdata *u) {
|
2009-05-14 01:24:26 +02:00
|
|
|
pa_datum key;
|
2013-06-27 19:28:09 +02:00
|
|
|
bool done;
|
2008-08-05 19:03:11 +02:00
|
|
|
|
2009-05-14 01:24:26 +02:00
|
|
|
done = !pa_database_first(u->database, &key, NULL);
|
|
|
|
|
|
|
|
|
|
while (!done) {
|
|
|
|
|
pa_datum next_key;
|
2008-08-05 19:03:11 +02:00
|
|
|
struct entry *e;
|
|
|
|
|
char *name;
|
|
|
|
|
|
2009-05-14 01:24:26 +02:00
|
|
|
done = !pa_database_next(u->database, &key, &next_key, NULL);
|
2008-08-05 19:03:11 +02:00
|
|
|
|
2009-05-14 01:24:26 +02:00
|
|
|
name = pa_xstrndup(key.data, key.size);
|
|
|
|
|
pa_datum_free(&key);
|
2008-08-05 19:03:11 +02:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
if ((e = entry_read(u, name))) {
|
2008-08-05 19:03:11 +02:00
|
|
|
char t[256];
|
|
|
|
|
pa_log("name=%s", name);
|
2009-04-13 22:50:24 +02:00
|
|
|
pa_log("device=%s %s", e->device, pa_yes_no(e->device_valid));
|
2008-08-05 19:03:11 +02:00
|
|
|
pa_log("channel_map=%s", pa_channel_map_snprint(t, sizeof(t), &e->channel_map));
|
2013-04-03 16:36:43 +03:00
|
|
|
pa_log("volume=%s %s",
|
|
|
|
|
pa_cvolume_snprint_verbose(t, sizeof(t), &e->volume, &e->channel_map, true),
|
|
|
|
|
pa_yes_no(e->volume_valid));
|
2009-04-13 22:50:24 +02:00
|
|
|
pa_log("mute=%s %s", pa_yes_no(e->muted), pa_yes_no(e->volume_valid));
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_free(e);
|
2008-08-05 19:03:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_xfree(name);
|
|
|
|
|
|
|
|
|
|
key = next_key;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2011-10-01 12:16:35 +01:00
|
|
|
#define EXT_VERSION 1
|
|
|
|
|
|
2008-08-04 19:01:13 +02:00
|
|
|
static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connection *c, uint32_t tag, pa_tagstruct *t) {
|
|
|
|
|
struct userdata *u;
|
|
|
|
|
uint32_t command;
|
2008-08-09 17:04:27 +02:00
|
|
|
pa_tagstruct *reply = NULL;
|
2008-08-04 19:01:13 +02:00
|
|
|
|
|
|
|
|
pa_assert(p);
|
|
|
|
|
pa_assert(m);
|
|
|
|
|
pa_assert(c);
|
|
|
|
|
pa_assert(t);
|
|
|
|
|
|
|
|
|
|
u = m->userdata;
|
|
|
|
|
|
|
|
|
|
if (pa_tagstruct_getu32(t, &command) < 0)
|
|
|
|
|
goto fail;
|
|
|
|
|
|
2014-10-23 16:09:45 +02:00
|
|
|
reply = pa_tagstruct_new();
|
2008-08-04 19:01:13 +02:00
|
|
|
pa_tagstruct_putu32(reply, PA_COMMAND_REPLY);
|
|
|
|
|
pa_tagstruct_putu32(reply, tag);
|
|
|
|
|
|
|
|
|
|
switch (command) {
|
|
|
|
|
case SUBCOMMAND_TEST: {
|
|
|
|
|
if (!pa_tagstruct_eof(t))
|
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
|
|
pa_tagstruct_putu32(reply, EXT_VERSION);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case SUBCOMMAND_READ: {
|
2009-05-14 01:24:26 +02:00
|
|
|
pa_datum key;
|
2013-06-27 19:28:09 +02:00
|
|
|
bool done;
|
2008-08-04 19:01:13 +02:00
|
|
|
|
|
|
|
|
if (!pa_tagstruct_eof(t))
|
|
|
|
|
goto fail;
|
|
|
|
|
|
2009-05-14 01:24:26 +02:00
|
|
|
done = !pa_database_first(u->database, &key, NULL);
|
|
|
|
|
|
|
|
|
|
while (!done) {
|
|
|
|
|
pa_datum next_key;
|
2008-08-04 19:01:13 +02:00
|
|
|
struct entry *e;
|
|
|
|
|
char *name;
|
|
|
|
|
|
2009-05-14 01:24:26 +02:00
|
|
|
done = !pa_database_next(u->database, &key, &next_key, NULL);
|
2008-08-04 19:01:13 +02:00
|
|
|
|
2009-05-14 01:24:26 +02:00
|
|
|
name = pa_xstrndup(key.data, key.size);
|
|
|
|
|
pa_datum_free(&key);
|
2008-08-04 19:01:13 +02:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
if ((e = entry_read(u, name))) {
|
2009-01-27 23:39:39 +01:00
|
|
|
pa_cvolume r;
|
2009-02-04 18:34:08 +01:00
|
|
|
pa_channel_map cm;
|
2009-01-27 23:39:39 +01:00
|
|
|
|
2008-08-04 19:01:13 +02:00
|
|
|
pa_tagstruct_puts(reply, name);
|
2009-04-13 22:50:24 +02:00
|
|
|
pa_tagstruct_put_channel_map(reply, e->volume_valid ? &e->channel_map : pa_channel_map_init(&cm));
|
|
|
|
|
pa_tagstruct_put_cvolume(reply, e->volume_valid ? &e->volume : pa_cvolume_init(&r));
|
2009-01-27 23:39:39 +01:00
|
|
|
pa_tagstruct_puts(reply, e->device_valid ? e->device : NULL);
|
2013-06-27 19:28:09 +02:00
|
|
|
pa_tagstruct_put_boolean(reply, e->muted_valid ? e->muted : false);
|
2008-08-04 19:01:13 +02:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_free(e);
|
2008-08-04 19:01:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_xfree(name);
|
|
|
|
|
|
|
|
|
|
key = next_key;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case SUBCOMMAND_WRITE: {
|
|
|
|
|
uint32_t mode;
|
2013-06-27 19:28:09 +02:00
|
|
|
bool apply_immediately = false;
|
2008-08-04 19:01:13 +02:00
|
|
|
|
|
|
|
|
if (pa_tagstruct_getu32(t, &mode) < 0 ||
|
|
|
|
|
pa_tagstruct_get_boolean(t, &apply_immediately) < 0)
|
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
|
|
if (mode != PA_UPDATE_MERGE &&
|
|
|
|
|
mode != PA_UPDATE_REPLACE &&
|
|
|
|
|
mode != PA_UPDATE_SET)
|
|
|
|
|
goto fail;
|
|
|
|
|
|
2009-08-03 19:38:02 +03:00
|
|
|
if (mode == PA_UPDATE_SET) {
|
|
|
|
|
#ifdef HAVE_DBUS
|
|
|
|
|
struct dbus_entry *de;
|
|
|
|
|
void *state = NULL;
|
|
|
|
|
|
2009-08-09 09:04:15 +03:00
|
|
|
PA_HASHMAP_FOREACH(de, u->dbus_entries, state) {
|
2009-08-03 19:38:02 +03:00
|
|
|
send_entry_removed_signal(de);
|
2014-03-26 17:39:48 +02:00
|
|
|
pa_hashmap_remove_and_free(u->dbus_entries, de->entry_name);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
|
|
|
|
#endif
|
2009-05-14 01:24:26 +02:00
|
|
|
pa_database_clear(u->database);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
2008-08-04 19:01:13 +02:00
|
|
|
|
|
|
|
|
while (!pa_tagstruct_eof(t)) {
|
2019-01-17 11:21:45 +08:00
|
|
|
const char *name, *device, *client_name;
|
2013-06-27 19:28:09 +02:00
|
|
|
bool muted;
|
2011-06-07 23:21:04 +01:00
|
|
|
struct entry *entry;
|
2009-08-03 19:38:02 +03:00
|
|
|
#ifdef HAVE_DBUS
|
|
|
|
|
struct entry *old;
|
|
|
|
|
#endif
|
2008-08-04 19:01:13 +02:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry = entry_new();
|
2008-08-04 19:01:13 +02:00
|
|
|
|
|
|
|
|
if (pa_tagstruct_gets(t, &name) < 0 ||
|
2011-06-07 23:21:04 +01:00
|
|
|
pa_tagstruct_get_channel_map(t, &entry->channel_map) ||
|
|
|
|
|
pa_tagstruct_get_cvolume(t, &entry->volume) < 0 ||
|
2008-08-04 19:01:13 +02:00
|
|
|
pa_tagstruct_gets(t, &device) < 0 ||
|
2013-12-16 15:54:29 +01:00
|
|
|
pa_tagstruct_get_boolean(t, &muted) < 0) {
|
|
|
|
|
entry_free(entry);
|
2008-08-04 19:01:13 +02:00
|
|
|
goto fail;
|
2013-12-16 15:54:29 +01:00
|
|
|
}
|
2008-08-04 19:01:13 +02:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
if (!name || !*name) {
|
|
|
|
|
entry_free(entry);
|
2008-08-04 19:01:13 +02:00
|
|
|
goto fail;
|
2011-06-07 23:21:04 +01:00
|
|
|
}
|
2008-08-04 19:01:13 +02:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry->volume_valid = entry->volume.channels > 0;
|
2009-02-04 18:34:08 +01:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
if (entry->volume_valid)
|
|
|
|
|
if (!pa_cvolume_compatible_with_channel_map(&entry->volume, &entry->channel_map)) {
|
|
|
|
|
entry_free(entry);
|
2009-02-04 18:34:08 +01:00
|
|
|
goto fail;
|
2011-06-07 23:21:04 +01:00
|
|
|
}
|
2009-02-04 18:34:08 +01:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry->muted = muted;
|
2013-06-27 19:28:09 +02:00
|
|
|
entry->muted_valid = true;
|
2009-01-27 23:39:39 +01:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
entry->device = pa_xstrdup(device);
|
|
|
|
|
entry->device_valid = device && !!entry->device[0];
|
2009-01-27 23:39:39 +01:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
if (entry->device_valid && !pa_namereg_is_valid_name(entry->device)) {
|
|
|
|
|
entry_free(entry);
|
2009-01-27 23:39:39 +01:00
|
|
|
goto fail;
|
2011-06-07 23:21:04 +01:00
|
|
|
}
|
2019-01-17 11:21:45 +08:00
|
|
|
/* When users select an output device from gnome-control-center, the gnome-control-center will change all entries
|
|
|
|
|
* in the database to bind the sink of this output device, this is not correct since at this moment, the sink is
|
2019-12-07 13:04:39 +08:00
|
|
|
* default_sink and we shouldn't bind a stream to default_sink via preferred_sink or database. This also applies
|
|
|
|
|
* to source, default_source and preferred_source.
|
2019-01-17 11:21:45 +08:00
|
|
|
* After gnome-control-center fix the issue, let us remove this code */
|
|
|
|
|
client_name = pa_strnull(pa_proplist_gets(pa_native_connection_get_client(c)->proplist, PA_PROP_APPLICATION_PROCESS_BINARY));
|
|
|
|
|
if (pa_safe_streq(client_name, "gnome-control-center")) {
|
2019-12-07 13:04:39 +08:00
|
|
|
if (entry->device_valid && ((m->core->default_sink && pa_safe_streq(device, m->core->default_sink->name)) ||
|
|
|
|
|
(m->core->default_source && pa_safe_streq(device, m->core->default_source->name)))) {
|
2019-01-17 11:21:45 +08:00
|
|
|
entry_free(entry);
|
|
|
|
|
pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), reply);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2009-08-03 19:38:02 +03:00
|
|
|
#ifdef HAVE_DBUS
|
2011-06-07 23:21:04 +01:00
|
|
|
old = entry_read(u, name);
|
2009-08-03 19:38:02 +03:00
|
|
|
#endif
|
|
|
|
|
|
2009-08-27 00:05:54 +02:00
|
|
|
pa_log_debug("Client %s changes entry %s.",
|
|
|
|
|
pa_strnull(pa_proplist_gets(pa_native_connection_get_client(c)->proplist, PA_PROP_APPLICATION_PROCESS_BINARY)),
|
|
|
|
|
name);
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
if (entry_write(u, name, entry, mode == PA_UPDATE_REPLACE)) {
|
2009-08-03 19:38:02 +03:00
|
|
|
#ifdef HAVE_DBUS
|
|
|
|
|
struct dbus_entry *de;
|
|
|
|
|
|
|
|
|
|
if (old) {
|
|
|
|
|
pa_assert_se((de = pa_hashmap_get(u->dbus_entries, name)));
|
|
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
if ((old->device_valid != entry->device_valid)
|
|
|
|
|
|| (entry->device_valid && !pa_streq(entry->device, old->device)))
|
|
|
|
|
send_device_updated_signal(de, entry);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
if ((old->volume_valid != entry->volume_valid)
|
|
|
|
|
|| (entry->volume_valid && (!pa_cvolume_equal(&entry->volume, &old->volume)
|
|
|
|
|
|| !pa_channel_map_equal(&entry->channel_map, &old->channel_map))))
|
|
|
|
|
send_volume_updated_signal(de, entry);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
2011-06-07 23:21:04 +01:00
|
|
|
if (!old->muted_valid || (entry->muted != old->muted))
|
|
|
|
|
send_mute_updated_signal(de, entry);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
de = dbus_entry_new(u, name);
|
2009-12-03 15:31:11 +02:00
|
|
|
pa_assert_se(pa_hashmap_put(u->dbus_entries, de->entry_name, de) == 0);
|
2009-08-03 19:38:02 +03:00
|
|
|
send_new_entry_signal(de);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2008-08-04 19:01:13 +02:00
|
|
|
if (apply_immediately)
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_apply(u, name, entry);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_DBUS
|
|
|
|
|
if (old)
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_free(old);
|
2009-08-03 19:38:02 +03:00
|
|
|
#endif
|
2011-06-07 23:21:04 +01:00
|
|
|
entry_free(entry);
|
2008-08-04 19:01:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trigger_save(u);
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case SUBCOMMAND_DELETE:
|
|
|
|
|
|
|
|
|
|
while (!pa_tagstruct_eof(t)) {
|
|
|
|
|
const char *name;
|
2009-05-14 01:24:26 +02:00
|
|
|
pa_datum key;
|
2009-08-03 19:38:02 +03:00
|
|
|
#ifdef HAVE_DBUS
|
|
|
|
|
struct dbus_entry *de;
|
|
|
|
|
#endif
|
2008-08-04 19:01:13 +02:00
|
|
|
|
|
|
|
|
if (pa_tagstruct_gets(t, &name) < 0)
|
|
|
|
|
goto fail;
|
|
|
|
|
|
2009-08-03 19:38:02 +03:00
|
|
|
#ifdef HAVE_DBUS
|
|
|
|
|
if ((de = pa_hashmap_get(u->dbus_entries, name))) {
|
|
|
|
|
send_entry_removed_signal(de);
|
2014-03-26 17:39:48 +02:00
|
|
|
pa_hashmap_remove_and_free(u->dbus_entries, name);
|
2009-08-03 19:38:02 +03:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2009-05-14 01:24:26 +02:00
|
|
|
key.data = (char*) name;
|
|
|
|
|
key.size = strlen(name);
|
2008-08-04 19:01:13 +02:00
|
|
|
|
2009-05-14 01:24:26 +02:00
|
|
|
pa_database_unset(u->database, &key);
|
2008-08-04 19:01:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trigger_save(u);
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case SUBCOMMAND_SUBSCRIBE: {
|
|
|
|
|
|
2013-06-27 19:28:09 +02:00
|
|
|
bool enabled;
|
2008-08-04 19:01:13 +02:00
|
|
|
|
|
|
|
|
if (pa_tagstruct_get_boolean(t, &enabled) < 0 ||
|
|
|
|
|
!pa_tagstruct_eof(t))
|
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
|
|
if (enabled)
|
|
|
|
|
pa_idxset_put(u->subscribed, c, NULL);
|
|
|
|
|
else
|
|
|
|
|
pa_idxset_remove_by_data(u->subscribed, c, NULL);
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), reply);
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
fail:
|
|
|
|
|
|
|
|
|
|
if (reply)
|
|
|
|
|
pa_tagstruct_free(reply);
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_native_connection *c, struct userdata *u) {
|
|
|
|
|
pa_assert(p);
|
|
|
|
|
pa_assert(c);
|
|
|
|
|
pa_assert(u);
|
|
|
|
|
|
|
|
|
|
pa_idxset_remove_by_data(u->subscribed, c, NULL);
|
|
|
|
|
return PA_HOOK_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-06 16:14:03 +02:00
|
|
|
static void clean_up_db(struct userdata *u) {
|
|
|
|
|
struct clean_up_item {
|
|
|
|
|
PA_LLIST_FIELDS(struct clean_up_item);
|
|
|
|
|
char *entry_name;
|
|
|
|
|
struct entry *entry;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
PA_LLIST_HEAD(struct clean_up_item, to_be_removed);
|
|
|
|
|
#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
|
|
|
|
|
PA_LLIST_HEAD(struct clean_up_item, to_be_converted);
|
|
|
|
|
#endif
|
2013-06-27 19:28:09 +02:00
|
|
|
bool done = false;
|
2012-01-06 16:14:03 +02:00
|
|
|
pa_datum key;
|
|
|
|
|
struct clean_up_item *item = NULL;
|
|
|
|
|
struct clean_up_item *next = NULL;
|
|
|
|
|
|
|
|
|
|
pa_assert(u);
|
|
|
|
|
|
|
|
|
|
/* It would be convenient to remove or replace the entries in the database
|
|
|
|
|
* in the same loop that iterates through the database, but modifying the
|
|
|
|
|
* database is not supported while iterating through it. That's why we
|
|
|
|
|
* collect the entries that need to be removed or replaced to these
|
|
|
|
|
* lists. */
|
|
|
|
|
PA_LLIST_HEAD_INIT(struct clean_up_item, to_be_removed);
|
|
|
|
|
#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
|
|
|
|
|
PA_LLIST_HEAD_INIT(struct clean_up_item, to_be_converted);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
done = !pa_database_first(u->database, &key, NULL);
|
|
|
|
|
while (!done) {
|
|
|
|
|
pa_datum next_key;
|
|
|
|
|
char *entry_name = NULL;
|
|
|
|
|
struct entry *e = NULL;
|
|
|
|
|
|
|
|
|
|
entry_name = pa_xstrndup(key.data, key.size);
|
|
|
|
|
|
|
|
|
|
/* Use entry_read() to check whether this entry is valid. */
|
|
|
|
|
if (!(e = entry_read(u, entry_name))) {
|
|
|
|
|
item = pa_xnew0(struct clean_up_item, 1);
|
|
|
|
|
PA_LLIST_INIT(struct clean_up_item, item);
|
|
|
|
|
item->entry_name = entry_name;
|
|
|
|
|
|
|
|
|
|
#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
|
|
|
|
|
/* entry_read() failed, but what about legacy_entry_read()? */
|
|
|
|
|
if (!(e = legacy_entry_read(u, entry_name)))
|
|
|
|
|
/* Not a legacy entry either, let's remove this. */
|
|
|
|
|
PA_LLIST_PREPEND(struct clean_up_item, to_be_removed, item);
|
|
|
|
|
else {
|
|
|
|
|
/* Yay, it's valid after all! Now let's convert the entry to the current format. */
|
|
|
|
|
item->entry = e;
|
|
|
|
|
PA_LLIST_PREPEND(struct clean_up_item, to_be_converted, item);
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
/* Invalid entry, let's remove this. */
|
|
|
|
|
PA_LLIST_PREPEND(struct clean_up_item, to_be_removed, item);
|
|
|
|
|
#endif
|
|
|
|
|
} else {
|
|
|
|
|
pa_xfree(entry_name);
|
|
|
|
|
entry_free(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
done = !pa_database_next(u->database, &key, &next_key, NULL);
|
|
|
|
|
pa_datum_free(&key);
|
|
|
|
|
key = next_key;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PA_LLIST_FOREACH_SAFE(item, next, to_be_removed) {
|
|
|
|
|
key.data = item->entry_name;
|
|
|
|
|
key.size = strlen(item->entry_name);
|
|
|
|
|
|
|
|
|
|
pa_log_debug("Removing an invalid entry: %s", item->entry_name);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(pa_database_unset(u->database, &key) >= 0);
|
|
|
|
|
trigger_save(u);
|
|
|
|
|
|
|
|
|
|
PA_LLIST_REMOVE(struct clean_up_item, to_be_removed, item);
|
|
|
|
|
pa_xfree(item->entry_name);
|
|
|
|
|
pa_xfree(item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
|
|
|
|
|
PA_LLIST_FOREACH_SAFE(item, next, to_be_converted) {
|
|
|
|
|
pa_log_debug("Upgrading a legacy entry to the current format: %s", item->entry_name);
|
|
|
|
|
|
2016-01-13 23:52:11 +01:00
|
|
|
pa_assert_se(entry_write(u, item->entry_name, item->entry, true));
|
2012-01-06 16:14:03 +02:00
|
|
|
trigger_save(u);
|
|
|
|
|
|
|
|
|
|
PA_LLIST_REMOVE(struct clean_up_item, to_be_converted, item);
|
|
|
|
|
pa_xfree(item->entry_name);
|
|
|
|
|
entry_free(item->entry);
|
|
|
|
|
pa_xfree(item);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2008-08-03 23:23:13 +02:00
|
|
|
int pa__init(pa_module*m) {
|
|
|
|
|
pa_modargs *ma = NULL;
|
|
|
|
|
struct userdata *u;
|
2020-11-29 11:21:29 +03:00
|
|
|
char *state_path;
|
2008-08-03 23:23:13 +02:00
|
|
|
pa_sink_input *si;
|
|
|
|
|
pa_source_output *so;
|
|
|
|
|
uint32_t idx;
|
2019-12-10 16:26:34 +08:00
|
|
|
bool restore_device = true, restore_volume = true, restore_muted = true;
|
|
|
|
|
|
2009-08-03 19:38:02 +03:00
|
|
|
#ifdef HAVE_DBUS
|
|
|
|
|
pa_datum key;
|
2013-06-27 19:28:09 +02:00
|
|
|
bool done;
|
2009-08-03 19:38:02 +03:00
|
|
|
#endif
|
2008-08-03 23:23:13 +02:00
|
|
|
|
|
|
|
|
pa_assert(m);
|
|
|
|
|
|
|
|
|
|
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
|
|
|
|
pa_log("Failed to parse module arguments");
|
|
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pa_modargs_get_value_boolean(ma, "restore_device", &restore_device) < 0 ||
|
|
|
|
|
pa_modargs_get_value_boolean(ma, "restore_volume", &restore_volume) < 0 ||
|
2019-12-10 16:26:34 +08:00
|
|
|
pa_modargs_get_value_boolean(ma, "restore_muted", &restore_muted) < 0) {
|
|
|
|
|
pa_log("restore_device=, restore_volume= and restore_muted= expect boolean arguments");
|
2008-08-03 23:23:13 +02:00
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-10 16:26:34 +08:00
|
|
|
if (pa_modargs_get_value(ma, "on_hotplug", NULL) != NULL ||
|
|
|
|
|
pa_modargs_get_value(ma, "on_rescue", NULL) != NULL)
|
|
|
|
|
pa_log("on_hotplug and on_rescue are obsolete arguments, please remove them from your configuration");
|
2019-12-07 11:36:25 +08:00
|
|
|
|
2008-08-03 23:23:13 +02:00
|
|
|
if (!restore_muted && !restore_volume && !restore_device)
|
|
|
|
|
pa_log_warn("Neither restoring volume, nor restoring muted, nor restoring device enabled!");
|
|
|
|
|
|
2009-06-18 00:59:33 +02:00
|
|
|
m->userdata = u = pa_xnew0(struct userdata, 1);
|
2008-08-03 23:23:13 +02:00
|
|
|
u->core = m->core;
|
2008-08-04 19:00:43 +02:00
|
|
|
u->module = m;
|
2008-08-03 23:23:13 +02:00
|
|
|
u->restore_device = restore_device;
|
|
|
|
|
u->restore_volume = restore_volume;
|
|
|
|
|
u->restore_muted = restore_muted;
|
2008-08-04 19:01:13 +02:00
|
|
|
u->subscribed = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
|
|
|
|
|
|
|
|
|
|
u->protocol = pa_native_protocol_get(m->core);
|
|
|
|
|
pa_native_protocol_install_ext(u->protocol, m, extension_cb);
|
|
|
|
|
|
2015-03-27 11:20:12 +01:00
|
|
|
pa_module_hook_connect(m, &pa_native_protocol_hooks(u->protocol)[PA_NATIVE_HOOK_CONNECTION_UNLINK], PA_HOOK_NORMAL, (pa_hook_cb_t) connection_unlink_hook_cb, u);
|
2008-08-03 23:23:13 +02:00
|
|
|
|
|
|
|
|
u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u);
|
|
|
|
|
|
|
|
|
|
if (restore_device) {
|
2009-06-22 22:35:55 +02:00
|
|
|
/* A little bit earlier than module-intended-roles ... */
|
2015-03-27 11:20:12 +01:00
|
|
|
pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_new_hook_callback, u);
|
|
|
|
|
pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_new_hook_callback, u);
|
2008-08-03 23:23:13 +02:00
|
|
|
}
|
|
|
|
|
|
2011-08-07 01:08:28 +02:00
|
|
|
if (restore_volume || restore_muted) {
|
2015-03-27 11:20:12 +01:00
|
|
|
pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_fixate_hook_callback, u);
|
|
|
|
|
pa_module_hook_connect(m, &m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_fixate_hook_callback, u);
|
2011-08-07 01:08:28 +02:00
|
|
|
}
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2020-11-29 11:21:29 +03:00
|
|
|
if (!(state_path = pa_state_path(NULL, true)))
|
2008-08-03 23:23:13 +02:00
|
|
|
goto fail;
|
|
|
|
|
|
2020-11-29 11:21:29 +03:00
|
|
|
if (!(u->database = pa_database_open(state_path, "stream-volumes", true, true))) {
|
|
|
|
|
pa_xfree(state_path);
|
2008-08-03 23:23:13 +02:00
|
|
|
goto fail;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-29 11:21:29 +03:00
|
|
|
pa_xfree(state_path);
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2012-01-06 16:14:03 +02:00
|
|
|
clean_up_db(u);
|
|
|
|
|
|
2011-10-01 12:16:35 +01:00
|
|
|
if (fill_db(u, pa_modargs_get_value(ma, "fallback_table", NULL)) < 0)
|
|
|
|
|
goto fail;
|
|
|
|
|
|
2009-08-03 19:38:02 +03:00
|
|
|
#ifdef HAVE_DBUS
|
|
|
|
|
u->dbus_protocol = pa_dbus_protocol_get(u->core);
|
2013-09-14 11:50:10 +05:30
|
|
|
u->dbus_entries = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) dbus_entry_free);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
pa_assert_se(pa_dbus_protocol_add_interface(u->dbus_protocol, OBJECT_PATH, &stream_restore_interface_info, u) >= 0);
|
|
|
|
|
pa_assert_se(pa_dbus_protocol_register_extension(u->dbus_protocol, INTERFACE_STREAM_RESTORE) >= 0);
|
|
|
|
|
|
|
|
|
|
/* Create the initial dbus entries. */
|
|
|
|
|
done = !pa_database_first(u->database, &key, NULL);
|
|
|
|
|
while (!done) {
|
|
|
|
|
pa_datum next_key;
|
|
|
|
|
char *name;
|
|
|
|
|
struct dbus_entry *de;
|
|
|
|
|
|
|
|
|
|
name = pa_xstrndup(key.data, key.size);
|
2012-01-06 16:15:58 +02:00
|
|
|
de = dbus_entry_new(u, name);
|
|
|
|
|
pa_assert_se(pa_hashmap_put(u->dbus_entries, de->entry_name, de) == 0);
|
2009-08-03 19:38:02 +03:00
|
|
|
pa_xfree(name);
|
|
|
|
|
|
2012-01-06 16:15:58 +02:00
|
|
|
done = !pa_database_next(u->database, &key, &next_key, NULL);
|
|
|
|
|
pa_datum_free(&key);
|
2009-08-03 19:38:02 +03:00
|
|
|
key = next_key;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2009-06-22 22:33:09 +02:00
|
|
|
PA_IDXSET_FOREACH(si, m->core->sink_inputs, idx)
|
2008-08-03 23:23:13 +02:00
|
|
|
subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, si->index, u);
|
|
|
|
|
|
2009-06-22 22:33:09 +02:00
|
|
|
PA_IDXSET_FOREACH(so, m->core->source_outputs, idx)
|
2008-08-03 23:23:13 +02:00
|
|
|
subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW, so->index, u);
|
|
|
|
|
|
|
|
|
|
pa_modargs_free(ma);
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
fail:
|
|
|
|
|
pa__done(m);
|
|
|
|
|
|
|
|
|
|
if (ma)
|
|
|
|
|
pa_modargs_free(ma);
|
|
|
|
|
|
2011-03-12 19:45:02 +01:00
|
|
|
return -1;
|
2008-08-03 23:23:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pa__done(pa_module*m) {
|
|
|
|
|
struct userdata* u;
|
|
|
|
|
|
|
|
|
|
pa_assert(m);
|
|
|
|
|
|
|
|
|
|
if (!(u = m->userdata))
|
|
|
|
|
return;
|
|
|
|
|
|
2009-08-03 19:38:02 +03:00
|
|
|
#ifdef HAVE_DBUS
|
|
|
|
|
if (u->dbus_protocol) {
|
|
|
|
|
pa_assert(u->dbus_entries);
|
|
|
|
|
|
|
|
|
|
pa_assert_se(pa_dbus_protocol_unregister_extension(u->dbus_protocol, INTERFACE_STREAM_RESTORE) >= 0);
|
|
|
|
|
pa_assert_se(pa_dbus_protocol_remove_interface(u->dbus_protocol, OBJECT_PATH, stream_restore_interface_info.name) >= 0);
|
|
|
|
|
|
2013-09-14 11:50:10 +05:30
|
|
|
pa_hashmap_free(u->dbus_entries);
|
2009-08-03 19:38:02 +03:00
|
|
|
|
|
|
|
|
pa_dbus_protocol_unref(u->dbus_protocol);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2008-08-03 23:23:13 +02:00
|
|
|
if (u->subscription)
|
|
|
|
|
pa_subscription_free(u->subscription);
|
|
|
|
|
|
|
|
|
|
if (u->save_time_event)
|
|
|
|
|
u->core->mainloop->time_free(u->save_time_event);
|
|
|
|
|
|
2009-05-14 01:24:26 +02:00
|
|
|
if (u->database)
|
|
|
|
|
pa_database_close(u->database);
|
2008-08-03 23:23:13 +02:00
|
|
|
|
2008-08-04 19:01:13 +02:00
|
|
|
if (u->protocol) {
|
|
|
|
|
pa_native_protocol_remove_ext(u->protocol, m);
|
|
|
|
|
pa_native_protocol_unref(u->protocol);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (u->subscribed)
|
2013-02-12 21:36:55 +02:00
|
|
|
pa_idxset_free(u->subscribed, NULL);
|
2008-08-04 19:01:13 +02:00
|
|
|
|
2008-08-03 23:23:13 +02:00
|
|
|
pa_xfree(u);
|
|
|
|
|
}
|