
This change also keeps the plugin always activated, regardless of whether the DBus connection is closed or not, which is useful when the dbus service is restarted.
368 lines
11 KiB
C
368 lines
11 KiB
C
/* WirePlumber
|
|
*
|
|
* Copyright © 2021 Collabora Ltd.
|
|
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#include "plugin.h"
|
|
#include "portal-permissionstore-enums.h"
|
|
|
|
#define DBUS_INTERFACE_NAME "org.freedesktop.impl.portal.PermissionStore"
|
|
#define DBUS_OBJECT_PATH "/org/freedesktop/impl/portal/PermissionStore"
|
|
|
|
static void setup_connection (WpPortalPermissionStorePlugin *self);
|
|
|
|
G_DEFINE_TYPE (WpPortalPermissionStorePlugin, wp_portal_permissionstore_plugin,
|
|
WP_TYPE_PLUGIN)
|
|
|
|
enum
|
|
{
|
|
ACTION_LOOKUP,
|
|
ACTION_SET,
|
|
SIGNAL_CHANGED,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_STATE,
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
static GVariant *
|
|
wp_portal_permissionstore_plugin_lookup (WpPortalPermissionStorePlugin *self,
|
|
const gchar *table, const gchar *id)
|
|
{
|
|
g_autoptr (GError) error = NULL;
|
|
g_autoptr (GVariant) res = NULL;
|
|
GVariant *permissions = NULL, *data = NULL;
|
|
|
|
g_return_val_if_fail (self->connection, NULL);
|
|
|
|
/* Lookup */
|
|
res = g_dbus_connection_call_sync (self->connection, DBUS_INTERFACE_NAME,
|
|
DBUS_OBJECT_PATH, DBUS_INTERFACE_NAME, "Lookup",
|
|
g_variant_new ("(ss)", table, id), NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL,
|
|
&error);
|
|
if (error) {
|
|
wp_warning_object (self, "Failed to call Lookup: %s", error->message);
|
|
return NULL;
|
|
}
|
|
|
|
/* Get the permissions */
|
|
g_variant_get (res, "(@a{sas}@v)", &permissions, &data);
|
|
|
|
return permissions ? g_variant_ref (permissions) : NULL;
|
|
}
|
|
|
|
static void
|
|
wp_portal_permissionstore_plugin_set (WpPortalPermissionStorePlugin *self,
|
|
const gchar *table, gboolean create, const gchar *id, GVariant *permissions)
|
|
{
|
|
g_autoptr (GError) error = NULL;
|
|
g_autoptr (GVariant) res = NULL;
|
|
GVariant *data = NULL;
|
|
|
|
g_return_if_fail (self->connection);
|
|
|
|
/* Set */
|
|
res = g_dbus_connection_call_sync (self->connection, DBUS_INTERFACE_NAME,
|
|
DBUS_OBJECT_PATH, DBUS_INTERFACE_NAME, "Set",
|
|
g_variant_new ("(sbs@a{sas}@v)", table, id, permissions, data), NULL,
|
|
G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
|
|
if (error)
|
|
wp_warning_object (self, "Failed to call Set: %s", error->message);
|
|
}
|
|
|
|
static void
|
|
wp_portal_permissionstore_plugin_changed (GDBusConnection *connection,
|
|
const gchar *sender_name, const gchar *object_path,
|
|
const gchar *interface_name, const gchar *signal_name,
|
|
GVariant *parameters, gpointer user_data)
|
|
{
|
|
WpPortalPermissionStorePlugin *self =
|
|
WP_PORTAL_PERMISSIONSTORE_PLUGIN (user_data);
|
|
const char *table = NULL, *id = NULL;
|
|
gboolean deleted = FALSE;
|
|
GVariant *permissions = NULL, *data = NULL;
|
|
|
|
g_return_if_fail (parameters);
|
|
g_variant_get (parameters, "(ssb@v@a{sas})", &table, &id, &deleted, &data,
|
|
&permissions);
|
|
|
|
g_signal_emit (self, signals[SIGNAL_CHANGED], 0, table, id, deleted,
|
|
permissions);
|
|
}
|
|
|
|
static void
|
|
wp_portal_permissionstore_plugin_init (WpPortalPermissionStorePlugin * self)
|
|
{
|
|
self->cancellable = g_cancellable_new ();
|
|
}
|
|
|
|
static void
|
|
wp_portal_permissionstore_plugin_finalize (GObject * object)
|
|
{
|
|
WpPortalPermissionStorePlugin *self =
|
|
WP_PORTAL_PERMISSIONSTORE_PLUGIN (object);
|
|
|
|
g_clear_object (&self->cancellable);
|
|
|
|
G_OBJECT_CLASS (wp_portal_permissionstore_plugin_parent_class)->finalize (
|
|
object);
|
|
}
|
|
|
|
static void
|
|
clear_connection (WpPortalPermissionStorePlugin *self)
|
|
{
|
|
if (self->connection && self->signal_id > 0)
|
|
g_dbus_connection_signal_unsubscribe (self->connection, self->signal_id);
|
|
g_clear_object (&self->connection);
|
|
|
|
if (self->state != WP_DBUS_CONNECTION_STATUS_CLOSED) {
|
|
self->state = WP_DBUS_CONNECTION_STATUS_CLOSED;
|
|
g_object_notify (G_OBJECT (self), "state");
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
do_connect (WpPortalPermissionStorePlugin *self, GAsyncReadyCallback callback,
|
|
gpointer data, GError **error)
|
|
{
|
|
g_autofree gchar *address = NULL;
|
|
|
|
address = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SESSION, NULL, error);
|
|
if (!address) {
|
|
g_prefix_error (error, "Error acquiring session bus address: ");
|
|
return FALSE;
|
|
}
|
|
|
|
wp_debug_object (self, "Connecting to bus: %s", address);
|
|
|
|
self->state = WP_DBUS_CONNECTION_STATUS_CONNECTING;
|
|
g_object_notify (G_OBJECT (self), "state");
|
|
|
|
g_dbus_connection_new_for_address (address,
|
|
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
|
|
G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
|
|
NULL, self->cancellable, callback, data);
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
on_reconnect_got_bus (GObject * obj, GAsyncResult * res, gpointer data)
|
|
{
|
|
WpPortalPermissionStorePlugin *self = WP_PORTAL_PERMISSIONSTORE_PLUGIN (data);
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
self->connection = g_dbus_connection_new_for_address_finish (res, &error);
|
|
if (!self->connection) {
|
|
clear_connection (self);
|
|
wp_info_object (self, "Could not reconnect to session bus: %s",
|
|
error->message);
|
|
return;
|
|
}
|
|
|
|
wp_debug_object (self, "Reconnected to bus");
|
|
setup_connection (self);
|
|
}
|
|
|
|
static gboolean
|
|
idle_connect (WpPortalPermissionStorePlugin * self)
|
|
{
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
if (!do_connect (self, on_reconnect_got_bus, self, &error))
|
|
wp_info_object (self, "Cannot reconnect: %s", error->message);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
on_connection_closed (GDBusConnection *connection,
|
|
gboolean remote_peer_vanished, GError *error, gpointer data)
|
|
{
|
|
WpPortalPermissionStorePlugin *self = WP_PORTAL_PERMISSIONSTORE_PLUGIN (data);
|
|
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
|
|
|
|
wp_info_object (self, "D-Bus connection closed: %s", error->message);
|
|
|
|
clear_connection (self);
|
|
|
|
/* try to reconnect on idle if connection was closed */
|
|
if (core)
|
|
wp_core_idle_add_closure (core, NULL, g_cclosure_new_object (
|
|
G_CALLBACK (idle_connect), G_OBJECT (self)));
|
|
}
|
|
|
|
static void
|
|
setup_connection (WpPortalPermissionStorePlugin *self)
|
|
{
|
|
g_signal_connect_object (self->connection, "closed",
|
|
G_CALLBACK (on_connection_closed), self, 0);
|
|
g_dbus_connection_set_exit_on_close (self->connection, FALSE);
|
|
|
|
self->state = WP_DBUS_CONNECTION_STATUS_CONNECTED;
|
|
g_object_notify (G_OBJECT (self), "state");
|
|
|
|
/* Listen for the changed signal */
|
|
self->signal_id = g_dbus_connection_signal_subscribe (self->connection,
|
|
DBUS_INTERFACE_NAME, DBUS_INTERFACE_NAME, "Changed", NULL, NULL,
|
|
G_DBUS_SIGNAL_FLAGS_NONE, wp_portal_permissionstore_plugin_changed, self,
|
|
NULL);
|
|
|
|
}
|
|
|
|
static void
|
|
on_enable_got_bus (GObject * obj, GAsyncResult * res, gpointer data)
|
|
{
|
|
WpTransition *transition = WP_TRANSITION (data);
|
|
WpPortalPermissionStorePlugin *self =
|
|
wp_transition_get_source_object (transition);
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
self->connection = g_dbus_connection_new_for_address_finish (res, &error);
|
|
if (!self->connection) {
|
|
clear_connection (self);
|
|
g_prefix_error (&error, "Failed to connect to session bus: ");
|
|
wp_transition_return_error (transition, g_steal_pointer (&error));
|
|
return;
|
|
}
|
|
|
|
wp_debug_object (self, "Connected to bus");
|
|
setup_connection (self);
|
|
|
|
wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0);
|
|
}
|
|
|
|
static void
|
|
wp_portal_permissionstore_plugin_enable (WpPlugin * plugin,
|
|
WpTransition * transition)
|
|
{
|
|
WpPortalPermissionStorePlugin *self =
|
|
WP_PORTAL_PERMISSIONSTORE_PLUGIN (plugin);
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
g_return_if_fail (self->state == WP_DBUS_CONNECTION_STATUS_CLOSED);
|
|
|
|
if (!do_connect (self, on_enable_got_bus, transition, &error)) {
|
|
wp_transition_return_error (transition, g_steal_pointer (&error));
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
wp_portal_permissionstore_plugin_disable (WpPlugin * plugin)
|
|
{
|
|
WpPortalPermissionStorePlugin *self =
|
|
WP_PORTAL_PERMISSIONSTORE_PLUGIN (plugin);
|
|
|
|
g_cancellable_cancel (self->cancellable);
|
|
clear_connection (self);
|
|
g_clear_object (&self->cancellable);
|
|
self->cancellable = g_cancellable_new ();
|
|
|
|
wp_object_update_features (WP_OBJECT (self), 0, WP_PLUGIN_FEATURE_ENABLED);
|
|
}
|
|
|
|
static void
|
|
wp_portal_permissionstore_plugin_get_property (GObject * object,
|
|
guint property_id, GValue * value, GParamSpec * pspec)
|
|
{
|
|
WpPortalPermissionStorePlugin *self =
|
|
WP_PORTAL_PERMISSIONSTORE_PLUGIN (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_STATE:
|
|
g_value_set_enum (value, self->state);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
wp_portal_permissionstore_plugin_class_init (
|
|
WpPortalPermissionStorePluginClass * klass)
|
|
{
|
|
GObjectClass *object_class = (GObjectClass *) klass;
|
|
WpPluginClass *plugin_class = (WpPluginClass *) klass;
|
|
|
|
object_class->finalize = wp_portal_permissionstore_plugin_finalize;
|
|
object_class->get_property = wp_portal_permissionstore_plugin_get_property;
|
|
|
|
plugin_class->enable = wp_portal_permissionstore_plugin_enable;
|
|
plugin_class->disable = wp_portal_permissionstore_plugin_disable;
|
|
|
|
g_object_class_install_property (object_class, PROP_STATE,
|
|
g_param_spec_enum ("state", "state", "The state",
|
|
WP_TYPE_DBUS_CONNECTION_STATUS, WP_DBUS_CONNECTION_STATUS_CLOSED,
|
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* WpPortalPermissionStorePlugin::lookup:
|
|
*
|
|
* @brief
|
|
* @em table: the table name
|
|
* @em id: the Id name
|
|
*
|
|
* Returns: (transfer full): the GVariant with permissions
|
|
*/
|
|
signals[ACTION_LOOKUP] = g_signal_new_class_handler (
|
|
"lookup", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
(GCallback) wp_portal_permissionstore_plugin_lookup,
|
|
NULL, NULL, NULL, G_TYPE_VARIANT,
|
|
2, G_TYPE_STRING, G_TYPE_STRING);
|
|
|
|
/**
|
|
* WpPortalPermissionStorePlugin::set:
|
|
*
|
|
* @brief
|
|
* @em table: the table name
|
|
* @em create: whether to create the table if it does not exist
|
|
* @em id: the Id name
|
|
* @em permissions: the permissions
|
|
*
|
|
* Sets the permissions in the permission store
|
|
*/
|
|
signals[ACTION_SET] = g_signal_new_class_handler (
|
|
"set", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
(GCallback) wp_portal_permissionstore_plugin_set,
|
|
NULL, NULL, NULL, G_TYPE_NONE,
|
|
4, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_VARIANT);
|
|
|
|
/**
|
|
* WpPortalPermissionStorePlugin::changed:
|
|
*
|
|
* @brief
|
|
* @em table: the table name
|
|
* @em id: the Id name
|
|
* @em deleted: whether the permission was deleted or not
|
|
* @em permissions: the GVariant with permissions
|
|
*
|
|
* Signaled when the permissions changed
|
|
*/
|
|
signals[SIGNAL_CHANGED] = g_signal_new (
|
|
"changed", G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST, 0,
|
|
NULL, NULL, NULL, G_TYPE_NONE, 4,
|
|
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_VARIANT);
|
|
}
|
|
|
|
WP_PLUGIN_EXPORT gboolean
|
|
wireplumber__module_init (WpCore * core, GVariant * args, GError ** error)
|
|
{
|
|
wp_plugin_register (g_object_new (wp_portal_permissionstore_plugin_get_type(),
|
|
"name", "portal-permissionstore",
|
|
"core", core,
|
|
NULL));
|
|
return TRUE;
|
|
}
|