modules: implement a new reserve-device module

This one offers API to interract on a lower level with
the D-Bus reservation API and uses GDBus high level bindings only.

Also, this one implements the full Acquire procedure, calling
RequestRelease() on the peer and requesting the name again with
REPLACE_EXISTING
This commit is contained in:
George Kiagiadakis 2021-01-26 13:38:09 +02:00
parent bf71b73ae2
commit ef908439c4
11 changed files with 1588 additions and 20 deletions

View file

@ -14,12 +14,6 @@ shared_library(
dependencies : [wp_dep, pipewire_dep],
)
reserve_device_interface_src = gnome.gdbus_codegen('reserve-device-interface',
sources: 'module-dbus-reservation/org.freedesktop.ReserveDevice1.xml',
interface_prefix : 'org.freedesktop.ReserveDevice1.',
namespace : 'Wp'
)
shared_library(
'wireplumber-module-metadata',
[
@ -42,20 +36,6 @@ shared_library(
dependencies : [wp_dep, pipewire_dep],
)
shared_library(
'wireplumber-module-dbus-reservation',
[
'module-dbus-reservation.c',
'module-dbus-reservation/reserve-device.c',
'module-dbus-reservation/dbus-device-reservation.c',
reserve_device_interface_src,
],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-dbus-reservation"'],
install : true,
install_dir : wireplumber_module_dir,
dependencies : [wp_dep, pipewire_dep, giounix_dep],
)
shared_library(
'wireplumber-module-default-profile',
[
@ -143,6 +123,23 @@ shared_library(
dependencies : [wp_dep, wptoml_dep, pipewire_dep],
)
subdir('module-reserve-device')
shared_library(
'wireplumber-module-reserve-device',
[
'module-reserve-device/plugin.c',
'module-reserve-device/reserve-device.c',
'module-reserve-device/transitions.c',
reserve_device_interface_src,
reserve_device_enums,
],
c_args : [common_c_args, '-DG_LOG_DOMAIN="m-reserve-device"'],
include_directories: reserve_device_includes,
install : true,
install_dir : wireplumber_module_dir,
dependencies : [wp_dep, giounix_dep],
)
shared_library(
'wireplumber-module-si-adapter',
[

View file

@ -0,0 +1,11 @@
reserve_device_interface_src = gnome.gdbus_codegen('reserve-device-interface',
sources: 'org.freedesktop.ReserveDevice1.xml',
interface_prefix : 'org.freedesktop.ReserveDevice1.',
namespace : 'Wp'
)
reserve_device_enums = gnome.mkenums_simple('reserve-device-enums',
sources: [ 'plugin.h', 'reserve-device.h' ],
)
reserve_device_includes = include_directories('.')

View file

@ -0,0 +1,11 @@
<node>
<interface name="org.freedesktop.ReserveDevice1">
<method name="RequestRelease">
<arg name="priority" type="i" direction="in"/>
<arg name="result" type="b" direction="out"/>
</method>
<property name="Priority" type="i" access="read"/>
<property name="ApplicationName" type="s" access="read"/>
<property name="ApplicationDeviceName" type="s" access="read"/>
</interface>
</node>

View file

@ -0,0 +1,277 @@
/* WirePlumber
*
* Copyright © 2021 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "plugin.h"
#include "reserve-device.h"
#include "reserve-device-enums.h"
G_DEFINE_TYPE (WpReserveDevicePlugin, wp_reserve_device_plugin, WP_TYPE_PLUGIN)
enum
{
ACTION_CREATE_RESERVATION,
ACTION_DESTROY_RESERVATION,
ACTION_GET_RESERVATION,
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_STATE,
};
static guint signals[LAST_SIGNAL] = { 0 };
static void
rd_unref (gpointer data)
{
WpReserveDevice *rd = data;
g_signal_emit_by_name (rd, "release");
g_object_unref (rd);
}
static void
wp_reserve_device_plugin_init (WpReserveDevicePlugin * self)
{
self->cancellable = g_cancellable_new ();
self->reserve_devices = g_hash_table_new_full (g_str_hash, g_str_equal,
NULL, rd_unref);
}
static void
wp_reserve_device_plugin_finalize (GObject * object)
{
WpReserveDevicePlugin *self = WP_RESERVE_DEVICE_PLUGIN (object);
g_clear_pointer (&self->reserve_devices, g_hash_table_unref);
g_clear_object (&self->cancellable);
G_OBJECT_CLASS (wp_reserve_device_plugin_parent_class)->finalize (object);
}
static void
wp_reserve_device_plugin_disable (WpReserveDevicePlugin *self)
{
g_hash_table_remove_all (self->reserve_devices);
g_clear_object (&self->manager);
g_clear_object (&self->connection);
if (self->state != WP_DBUS_CONNECTION_STATE_CLOSED) {
self->state = WP_DBUS_CONNECTION_STATE_CLOSED;
g_object_notify (G_OBJECT (self), "state");
}
}
static void
on_connection_closed (GDBusConnection *connection,
gboolean remote_peer_vanished, GError *error, gpointer data)
{
WpReserveDevicePlugin *self = WP_RESERVE_DEVICE_PLUGIN (data);
wp_info_object (self, "D-Bus connection closed: %s", error->message);
wp_reserve_device_plugin_disable (self);
}
static void
got_bus (GObject * obj, GAsyncResult * res, gpointer data)
{
WpReserveDevicePlugin *self = WP_RESERVE_DEVICE_PLUGIN (data);
g_autoptr (GError) error = NULL;
self->connection = g_dbus_connection_new_for_address_finish (res, &error);
if (!self->connection) {
wp_message_object (self, "Failed to connect to session bus: %s",
error->message);
wp_reserve_device_plugin_disable (self);
return;
}
wp_debug_object (self, "Connected to bus");
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->manager = g_dbus_object_manager_server_new (FDO_RESERVE_DEVICE1_PATH);
g_dbus_object_manager_server_set_connection (self->manager, self->connection);
self->state = WP_DBUS_CONNECTION_STATE_CONNECTED;
g_object_notify (G_OBJECT (self), "state");
}
static void
wp_reserve_device_plugin_activate (WpPlugin * plugin)
{
WpReserveDevicePlugin *self = WP_RESERVE_DEVICE_PLUGIN (plugin);
g_autoptr (GError) error = NULL;
g_autofree gchar *address = NULL;
g_return_if_fail (self->state == WP_DBUS_CONNECTION_STATE_CLOSED);
address = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SESSION, NULL, &error);
if (!address) {
wp_message_object (self, "Error acquiring session bus address: %s",
error->message);
return;
}
wp_debug_object (self, "Connecting to bus: %s", address);
self->state = WP_DBUS_CONNECTION_STATE_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, got_bus, self);
}
static void
wp_reserve_device_plugin_deactivate (WpPlugin * plugin)
{
WpReserveDevicePlugin *self = WP_RESERVE_DEVICE_PLUGIN (plugin);
g_cancellable_cancel (self->cancellable);
wp_reserve_device_plugin_disable (self);
g_clear_object (&self->cancellable);
self->cancellable = g_cancellable_new ();
}
static gpointer
wp_reserve_device_plugin_create_reservation (WpReserveDevicePlugin *self,
const gchar *name, const gchar *app_name, const gchar *app_dev_name,
gint priority)
{
if (self->state != WP_DBUS_CONNECTION_STATE_CONNECTED) {
wp_message_object (self, "not connected to D-Bus");
return NULL;
}
WpReserveDevice *rd = g_object_new (wp_reserve_device_get_type (),
"plugin", self,
"name", name,
"application-name", app_name,
"application-device-name", app_dev_name,
"priority", priority,
NULL);
/* use rd->name to avoid copying @name again */
g_hash_table_insert (self->reserve_devices, rd->name, rd);
return g_object_ref (rd);
}
static void
wp_reserve_device_plugin_destroy_reservation (WpReserveDevicePlugin *self,
const gchar *name)
{
if (self->state != WP_DBUS_CONNECTION_STATE_CONNECTED) {
wp_message_object (self, "not connected to D-Bus");
return;
}
g_hash_table_remove (self->reserve_devices, name);
}
static gpointer
wp_reserve_device_plugin_get_reservation (WpReserveDevicePlugin *self,
const gchar *name)
{
if (self->state != WP_DBUS_CONNECTION_STATE_CONNECTED) {
wp_message_object (self, "not connected to D-Bus");
return NULL;
}
WpReserveDevice *rd = g_hash_table_lookup (self->reserve_devices, name);
return rd ? g_object_ref (rd) : NULL;
}
static void
wp_reserve_device_plugin_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpReserveDevicePlugin *self = WP_RESERVE_DEVICE_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_reserve_device_plugin_class_init (WpReserveDevicePluginClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpPluginClass *plugin_class = (WpPluginClass *) klass;
object_class->finalize = wp_reserve_device_plugin_finalize;
object_class->get_property = wp_reserve_device_plugin_get_property;
plugin_class->activate = wp_reserve_device_plugin_activate;
plugin_class->deactivate = wp_reserve_device_plugin_deactivate;
g_object_class_install_property (object_class, PROP_STATE,
g_param_spec_enum ("state", "state", "The state",
WP_TYPE_DBUS_CONNECTION_STATE, WP_DBUS_CONNECTION_STATE_CLOSED,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
/**
* WpReserveDevicePlugin::create-reservation:
* @name:
* @app_name:
* @app_dev_name:
* @priority:
*
* Returns: (transfer full): the reservation object
*/
signals[ACTION_CREATE_RESERVATION] = g_signal_new_class_handler (
"create-reservation", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
(GCallback) wp_reserve_device_plugin_create_reservation,
NULL, NULL, NULL,
G_TYPE_OBJECT, 4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT);
/**
* WpReserveDevicePlugin::destroy-reservation:
* @name:
*
*/
signals[ACTION_DESTROY_RESERVATION] = g_signal_new_class_handler (
"destroy-reservation", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
(GCallback) wp_reserve_device_plugin_destroy_reservation,
NULL, NULL, NULL,
G_TYPE_NONE, 1, G_TYPE_STRING);
/**
* WpReserveDevicePlugin::get-reservation:
* @name:
*
* Returns: (transfer full): the reservation object
*/
signals[ACTION_GET_RESERVATION] = g_signal_new_class_handler (
"get-reservation", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
(GCallback) wp_reserve_device_plugin_get_reservation,
NULL, NULL, NULL,
G_TYPE_OBJECT, 1, G_TYPE_STRING);
}
WP_PLUGIN_EXPORT void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
{
wp_plugin_register (g_object_new (wp_reserve_device_plugin_get_type (),
"name", "reserve-device",
"module", module,
NULL));
}

View file

@ -0,0 +1,42 @@
/* WirePlumber
*
* Copyright © 2021 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __WIREPLUMBER_RESERVE_DEVICE_PLUGIN_H__
#define __WIREPLUMBER_RESERVE_DEVICE_PLUGIN_H__
#include <wp/wp.h>
G_BEGIN_DECLS
#define FDO_RESERVE_DEVICE1_SERVICE "org.freedesktop.ReserveDevice1"
#define FDO_RESERVE_DEVICE1_PATH "/org/freedesktop/ReserveDevice1"
typedef enum {
WP_DBUS_CONNECTION_STATE_CLOSED = 0,
WP_DBUS_CONNECTION_STATE_CONNECTING,
WP_DBUS_CONNECTION_STATE_CONNECTED,
} WpDBusConnectionState;
G_DECLARE_FINAL_TYPE (WpReserveDevicePlugin, wp_reserve_device_plugin,
WP, RESERVE_DEVICE_PLUGIN, WpPlugin)
struct _WpReserveDevicePlugin
{
WpPlugin parent;
WpDBusConnectionState state;
GHashTable *reserve_devices;
GCancellable *cancellable;
GDBusConnection *connection;
GDBusObjectManagerServer *manager;
};
G_END_DECLS
#endif

View file

@ -0,0 +1,499 @@
/* WirePlumber
*
* Copyright © 2021 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "reserve-device.h"
#include "plugin.h"
#include "transitions.h"
#include "reserve-device-interface.h"
#include "reserve-device-enums.h"
G_DEFINE_TYPE (WpReserveDevice, wp_reserve_device, G_TYPE_OBJECT)
enum
{
ACTION_ACQUIRE,
ACTION_RELEASE,
ACTION_DENY_RELEASE,
SIGNAL_RELEASE_REQUESTED,
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_PLUGIN,
PROP_NAME,
PROP_APP_NAME,
PROP_APP_DEV_NAME,
PROP_PRIORITY,
PROP_STATE,
PROP_OWNER_APP_NAME,
};
static guint signals[LAST_SIGNAL] = { 0 };
static void
wp_reserve_device_init (WpReserveDevice * self)
{
g_weak_ref_init (&self->plugin, NULL);
}
static void
on_got_proxy (GObject * src, GAsyncResult * res, WpReserveDevice *self)
{
g_autoptr (GError) error = NULL;
g_autoptr (WpOrgFreedesktopReserveDevice1) proxy =
wp_org_freedesktop_reserve_device1_proxy_new_finish (res, &error);
if (!proxy) {
wp_info_object (self, "%s: Could not get proxy of remote reservation: %s",
self->name, error->message);
return;
}
wp_debug_object (self, "%s owned by: %s", self->name,
wp_org_freedesktop_reserve_device1_get_application_name (proxy));
/* ensure that we are still busy and there is no owner_app_name */
if (self->state == WP_RESERVE_DEVICE_STATE_BUSY && !self->owner_app_name) {
self->owner_app_name =
wp_org_freedesktop_reserve_device1_dup_application_name (proxy);
g_object_notify (G_OBJECT (self), "owner-application-name");
}
}
static void
update_owner_app_name (WpReserveDevice *self)
{
if (self->state == WP_RESERVE_DEVICE_STATE_BUSY && !self->owner_app_name) {
/* create proxy */
g_autoptr (WpReserveDevicePlugin) plugin = g_weak_ref_get (&self->plugin);
wp_org_freedesktop_reserve_device1_proxy_new (plugin->connection,
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
self->service_name, self->object_path, NULL,
(GAsyncReadyCallback) on_got_proxy, self);
}
else if (self->state != WP_RESERVE_DEVICE_STATE_BUSY && self->owner_app_name) {
g_clear_pointer (&self->owner_app_name, g_free);
g_object_notify (G_OBJECT (self), "owner-application-name");
}
}
static void
on_name_appeared (GDBusConnection *connection, const gchar *name,
const gchar *owner, gpointer user_data)
{
WpReserveDevice *self = WP_RESERVE_DEVICE (user_data);
if (!self->transition) {
self->state = WP_RESERVE_DEVICE_STATE_BUSY;
wp_info_object (self, "%s busy (by %s)", name, owner);
g_object_notify (G_OBJECT (self), "state");
update_owner_app_name (self);
}
}
static void
on_name_vanished (GDBusConnection *connection, const gchar *name,
gpointer user_data)
{
WpReserveDevice *self = WP_RESERVE_DEVICE (user_data);
if (!self->transition) {
self->state = WP_RESERVE_DEVICE_STATE_AVAILABLE;
wp_info_object (self, "%s released", name);
g_object_notify (G_OBJECT (self), "state");
update_owner_app_name (self);
}
}
static void
wp_reserve_device_constructed (GObject * object)
{
WpReserveDevice *self = WP_RESERVE_DEVICE (object);
g_autoptr (WpReserveDevicePlugin) plugin = g_weak_ref_get (&self->plugin);
self->service_name =
g_strdup_printf (FDO_RESERVE_DEVICE1_SERVICE ".%s", self->name);
self->object_path =
g_strdup_printf (FDO_RESERVE_DEVICE1_PATH "/%s", self->name);
/* Watch for the name */
self->watcher_id = g_bus_watch_name_on_connection (plugin->connection,
self->service_name, G_BUS_NAME_WATCHER_FLAGS_NONE,
on_name_appeared, on_name_vanished, self, NULL);
G_OBJECT_CLASS (wp_reserve_device_parent_class)->constructed (object);
}
static void
wp_reserve_device_finalize (GObject * object)
{
WpReserveDevice *self = WP_RESERVE_DEVICE (object);
if (self->watcher_id > 0)
g_bus_unwatch_name (self->watcher_id);
if (self->owner_id > 0)
g_bus_unown_name (self->owner_id);
g_weak_ref_clear (&self->plugin);
g_clear_pointer (&self->name, g_free);
g_clear_pointer (&self->app_name, g_free);
g_clear_pointer (&self->app_dev_name, g_free);
g_clear_pointer (&self->service_name, g_free);
g_clear_pointer (&self->object_path, g_free);
G_OBJECT_CLASS (wp_reserve_device_parent_class)->finalize (object);
}
static void
on_acquire_transition_done (GObject *rd, GAsyncResult *res, gpointer data)
{
WpReserveDevice *self = WP_RESERVE_DEVICE (data);
g_autoptr (GError) error = NULL;
gboolean acquired = wp_reserve_device_acquire_transition_finish (res, &error);
if (error) {
wp_message_object (self, "%s: Acquire error: %s", self->name,
error->message);
}
self->state = acquired ?
WP_RESERVE_DEVICE_STATE_ACQUIRED : WP_RESERVE_DEVICE_STATE_BUSY;
self->transition = NULL;
g_object_notify (G_OBJECT (self), "state");
update_owner_app_name (self);
}
static void
wp_reserve_device_acquire (WpReserveDevice * self)
{
if (self->state == WP_RESERVE_DEVICE_STATE_ACQUIRED ||
self->transition != NULL) {
wp_debug_object (self, "%s: already acquired or operation in progress",
self->name);
return;
}
g_autoptr (WpReserveDevicePlugin) plugin = g_weak_ref_get (&self->plugin);
self->transition = wp_reserve_device_acquire_transition_new (self,
plugin->cancellable, on_acquire_transition_done, self);
wp_transition_advance (self->transition);
}
static void
wp_reserve_device_release (WpReserveDevice * self)
{
if (self->state != WP_RESERVE_DEVICE_STATE_ACQUIRED) {
wp_debug_object (self, "%s: not acquired", self->name);
return;
}
/* set state to AVAILABLE to ensure that on_name_lost()
does not emit SIGNAL_REQUEST_RELEASE */
/* on_name_vanished() will emit the state change */
self->state = WP_RESERVE_DEVICE_STATE_AVAILABLE;
wp_reserve_device_unown_name (self);
if (self->req_rel_invocation) {
wp_org_freedesktop_reserve_device1_complete_request_release (NULL,
self->req_rel_invocation, TRUE);
self->req_rel_invocation = NULL;
}
}
static void
wp_reserve_device_deny_release (WpReserveDevice * self, gboolean success)
{
if (self->req_rel_invocation) {
wp_org_freedesktop_reserve_device1_complete_request_release (NULL,
self->req_rel_invocation, FALSE);
self->req_rel_invocation = NULL;
}
}
static gboolean
wp_reserve_device_handle_request_release (WpOrgFreedesktopReserveDevice1 *iface,
GDBusMethodInvocation *invocation, gint priority, gpointer user_data)
{
WpReserveDevice *self = WP_RESERVE_DEVICE (user_data);
/* deny release if the priority is lower than ours */
if (priority < self->priority) {
wp_org_freedesktop_reserve_device1_complete_request_release (iface,
g_object_ref (invocation), FALSE);
return TRUE;
}
/* else, request the release of the device from the implementation;
if signal handlers are connected, assume the functionality is implemented,
otherwise return FALSE to let the iface return UnknownMethod */
if (g_signal_has_handler_pending (self,
signals[SIGNAL_RELEASE_REQUESTED], 0, FALSE)) {
self->req_rel_invocation = g_object_ref (invocation);
g_signal_emit (self, signals[SIGNAL_RELEASE_REQUESTED], 0, FALSE);
return TRUE;
}
return FALSE;
}
static void
wp_reserve_device_get_property (GObject * object, guint property_id,
GValue * value, GParamSpec * pspec)
{
WpReserveDevice *self = WP_RESERVE_DEVICE (object);
switch (property_id) {
case PROP_NAME:
g_value_set_string (value, self->name);
break;
case PROP_APP_NAME:
g_value_set_string (value, self->app_name);
break;
case PROP_APP_DEV_NAME:
g_value_set_string (value, self->app_dev_name);
break;
case PROP_PRIORITY:
g_value_set_int (value, self->priority);
break;
case PROP_STATE:
g_value_set_enum (value, self->state);
break;
case PROP_OWNER_APP_NAME:
switch (self->state) {
case WP_RESERVE_DEVICE_STATE_ACQUIRED:
g_value_set_string (value, self->app_name);
break;
default:
g_value_set_string (value, self->owner_app_name);
break;
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_reserve_device_set_property (GObject * object,
guint property_id, const GValue * value, GParamSpec * pspec)
{
WpReserveDevice *self = WP_RESERVE_DEVICE (object);
switch (property_id) {
case PROP_PLUGIN:
g_weak_ref_set (&self->plugin, g_value_get_object (value));
break;
case PROP_NAME:
g_clear_pointer (&self->name, g_free);
self->name = g_value_dup_string (value);
break;
case PROP_APP_NAME:
g_clear_pointer (&self->app_name, g_free);
self->app_name = g_value_dup_string (value);
break;
case PROP_APP_DEV_NAME:
g_clear_pointer (&self->app_dev_name, g_free);
self->app_dev_name = g_value_dup_string (value);
break;
case PROP_PRIORITY:
self->priority = g_value_get_int(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
wp_reserve_device_class_init (WpReserveDeviceClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
object_class->constructed = wp_reserve_device_constructed;
object_class->finalize = wp_reserve_device_finalize;
object_class->get_property = wp_reserve_device_get_property;
object_class->set_property = wp_reserve_device_set_property;
g_object_class_install_property (object_class, PROP_PLUGIN,
g_param_spec_object ("plugin", "plugin",
"The parent plugin instance", wp_reserve_device_plugin_get_type (),
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_NAME,
g_param_spec_string ("name", "name",
"The reservation name", NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_APP_NAME,
g_param_spec_string ("application-name", "application-name",
"The application name", NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_APP_DEV_NAME,
g_param_spec_string ("application-device-name", "application-device-name",
"The application device name", NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_PRIORITY,
g_param_spec_int ("priority", "priority",
"The priority", G_MININT, G_MAXINT, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_STATE,
g_param_spec_enum ("state", "state", "The state",
WP_TYPE_RESERVE_DEVICE_STATE, WP_RESERVE_DEVICE_STATE_UNKNOWN,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_OWNER_APP_NAME,
g_param_spec_string ("owner-application-name", "owner-application-name",
"The owner application name", NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
/**
* WpReserveDevice::acquire:
*
*/
signals[ACTION_ACQUIRE] = g_signal_new_class_handler (
"acquire", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
(GCallback) wp_reserve_device_acquire,
NULL, NULL, NULL, G_TYPE_NONE, 0);
/**
* WpReserveDevice::release:
*
*/
signals[ACTION_RELEASE] = g_signal_new_class_handler (
"release", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
(GCallback) wp_reserve_device_release,
NULL, NULL, NULL, G_TYPE_NONE, 0);
/**
* WpReserveDevice::deny-release:
*
*/
signals[ACTION_DENY_RELEASE] = g_signal_new_class_handler (
"deny-release", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
(GCallback) wp_reserve_device_deny_release,
NULL, NULL, NULL, G_TYPE_NONE, 0);
/**
* WpReserveDevice::release-requested:
* @forced: %TRUE if the name was forcibly taken from us,
* %FALSE if the `RequestRelease()` d-bus method was called
*
* Signaled when the device needs to be released. If @forced is %FALSE,
* call #WpReserveDevice::release to release or #WpReserveDevice::deny-release
* to refuse and return %FALSE from the `RequestRelease()` d-bus method
*/
signals[SIGNAL_RELEASE_REQUESTED] = g_signal_new (
"release-requested", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
}
void
wp_reserve_device_export_object (WpReserveDevice *self)
{
g_autoptr (WpReserveDevicePlugin) plugin = g_weak_ref_get (&self->plugin);
if (plugin) {
g_autoptr (GDBusObjectSkeleton) skeleton =
g_dbus_object_skeleton_new (self->object_path);
g_autoptr (WpOrgFreedesktopReserveDevice1) iface =
wp_org_freedesktop_reserve_device1_skeleton_new ();
g_object_set (iface,
"priority", self->priority,
"application-name", self->app_name,
"application-device-name", self->app_dev_name,
NULL);
g_signal_connect_object (iface, "handle-request-release",
(GCallback) wp_reserve_device_handle_request_release, self, 0);
g_dbus_object_skeleton_add_interface (skeleton,
G_DBUS_INTERFACE_SKELETON (iface));
g_dbus_object_manager_server_export (plugin->manager, skeleton);
}
}
void
wp_reserve_device_unexport_object (WpReserveDevice *self)
{
g_autoptr (WpReserveDevicePlugin) plugin = g_weak_ref_get (&self->plugin);
if (plugin) {
wp_debug_object (self, "unexport %s", self->object_path);
g_dbus_object_manager_server_unexport (plugin->manager, self->object_path);
}
}
static void
on_name_acquired (GDBusConnection *connection, const gchar *name,
gpointer user_data)
{
WpReserveDevice *self = WP_RESERVE_DEVICE (user_data);
wp_debug_object (self, "%s acquired", name);
if (WP_IS_RESERVE_DEVICE_ACQUIRE_TRANSITION (self->transition)) {
wp_reserve_device_acquire_transition_name_acquired (self->transition);
}
}
static void
on_name_lost (GDBusConnection *connection, const gchar *name,
gpointer user_data)
{
WpReserveDevice *self = WP_RESERVE_DEVICE (user_data);
wp_debug_object (self, "%s lost", name);
if (WP_IS_RESERVE_DEVICE_ACQUIRE_TRANSITION (self->transition)) {
wp_reserve_device_acquire_transition_name_lost (self->transition);
return;
}
if (self->state == WP_RESERVE_DEVICE_STATE_ACQUIRED) {
/* Emit release signal with forced set to TRUE */
g_signal_emit (self, signals[SIGNAL_RELEASE_REQUESTED], 0, TRUE);
wp_reserve_device_unown_name (self);
}
wp_reserve_device_unexport_object (self);
}
void
wp_reserve_device_own_name (WpReserveDevice * self, gboolean force)
{
g_return_if_fail (self->owner_id == 0);
g_autoptr (WpReserveDevicePlugin) plugin = g_weak_ref_get (&self->plugin);
if (plugin) {
GBusNameOwnerFlags flags = G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE;
if (self->priority != G_MAXINT32)
flags |= G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT;
if (force)
flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE;
wp_debug_object (self, "request ownership of %s", self->service_name);
self->owner_id = g_bus_own_name_on_connection (plugin->connection,
self->service_name, flags, on_name_acquired, on_name_lost, self, NULL);
}
}
void
wp_reserve_device_unown_name (WpReserveDevice * self)
{
if (self->owner_id) {
wp_debug_object (self, "drop ownership of %s", self->service_name);
g_bus_unown_name (self->owner_id);
self->owner_id = 0;
}
}

View file

@ -0,0 +1,55 @@
/* WirePlumber
*
* Copyright © 2021 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __WIREPLUMBER_RESERVE_DEVICE_H__
#define __WIREPLUMBER_RESERVE_DEVICE_H__
#include <wp/wp.h>
G_BEGIN_DECLS
typedef enum {
WP_RESERVE_DEVICE_STATE_UNKNOWN = 0,
WP_RESERVE_DEVICE_STATE_BUSY,
WP_RESERVE_DEVICE_STATE_AVAILABLE,
WP_RESERVE_DEVICE_STATE_ACQUIRED,
} WpReserveDeviceState;
G_DECLARE_FINAL_TYPE (WpReserveDevice, wp_reserve_device,
WP, RESERVE_DEVICE, GObject)
struct _WpReserveDevice
{
GObject parent;
GWeakRef plugin;
gchar *name;
gchar *app_name;
gchar *app_dev_name;
gint priority;
gchar *owner_app_name;
gchar *service_name;
gchar *object_path;
WpTransition *transition;
GDBusMethodInvocation *req_rel_invocation;
WpReserveDeviceState state;
guint watcher_id;
guint owner_id;
};
void wp_reserve_device_export_object (WpReserveDevice *self);
void wp_reserve_device_unexport_object (WpReserveDevice *self);
void wp_reserve_device_own_name (WpReserveDevice * self, gboolean force);
void wp_reserve_device_unown_name (WpReserveDevice * self);
G_END_DECLS
#endif

View file

@ -0,0 +1,266 @@
/* WirePlumber
*
* Copyright © 2021 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "transitions.h"
#include "plugin.h"
#include "reserve-device.h"
#include "reserve-device-interface.h"
struct _WpReserveDeviceAcquireTransition
{
WpTransition parent;
gint owner_state;
WpOrgFreedesktopReserveDevice1 *proxy;
};
enum {
OWNER_STATE_NONE = 0,
OWNER_STATE_ACQUIRED,
OWNER_STATE_LOST,
};
enum {
STEP_EXPORT_OBJECT = WP_TRANSITION_STEP_CUSTOM_START,
STEP_ACQUIRE_NO_FORCE,
STEP_GET_PROXY,
STEP_REQUEST_RELEASE,
STEP_ACQUIRE_WITH_FORCE,
STEP_UNEXPORT_OBJECT,
};
G_DEFINE_TYPE (WpReserveDeviceAcquireTransition,
wp_reserve_device_acquire_transition, WP_TYPE_TRANSITION)
static void
wp_reserve_device_acquire_transition_init (
WpReserveDeviceAcquireTransition * self)
{
}
static void
wp_reserve_device_acquire_transition_finalize (GObject * object)
{
WpReserveDeviceAcquireTransition *self =
WP_RESERVE_DEVICE_ACQUIRE_TRANSITION (object);
g_clear_object (&self->proxy);
G_OBJECT_CLASS (wp_reserve_device_acquire_transition_parent_class)->
finalize (object);
}
static guint
wp_reserve_device_acquire_transition_get_next_step (
WpTransition * transition, guint step)
{
WpReserveDeviceAcquireTransition *self =
WP_RESERVE_DEVICE_ACQUIRE_TRANSITION (transition);
switch (step) {
case WP_TRANSITION_STEP_NONE:
return STEP_EXPORT_OBJECT;
case STEP_EXPORT_OBJECT:
return STEP_ACQUIRE_NO_FORCE;
case STEP_ACQUIRE_NO_FORCE:
switch (self->owner_state) {
case OWNER_STATE_ACQUIRED:
return WP_TRANSITION_STEP_NONE;
case OWNER_STATE_LOST:
return STEP_GET_PROXY;
default:
return WP_TRANSITION_STEP_ERROR;
}
case STEP_GET_PROXY:
if (self->proxy)
return STEP_REQUEST_RELEASE;
else
return STEP_ACQUIRE_WITH_FORCE;
case STEP_REQUEST_RELEASE:
switch (self->owner_state) {
case OWNER_STATE_ACQUIRED:
return STEP_ACQUIRE_WITH_FORCE;
case OWNER_STATE_LOST:
return STEP_UNEXPORT_OBJECT;
default:
return WP_TRANSITION_STEP_ERROR;
}
case STEP_ACQUIRE_WITH_FORCE:
return WP_TRANSITION_STEP_NONE;
case STEP_UNEXPORT_OBJECT:
return WP_TRANSITION_STEP_NONE;
default:
return WP_TRANSITION_STEP_ERROR;
}
}
static void
on_got_proxy (GObject * src, GAsyncResult * res, WpTransition * transition)
{
WpReserveDeviceAcquireTransition *self =
WP_RESERVE_DEVICE_ACQUIRE_TRANSITION (transition);
g_autoptr (GError) error = NULL;
self->proxy =
wp_org_freedesktop_reserve_device1_proxy_new_finish (res, &error);
if (!self->proxy) {
WpReserveDevice *rd = wp_transition_get_source_object (transition);
wp_info_object (rd, "%s: Could not get proxy of remote reservation: %s",
rd->name, error->message);
}
wp_transition_advance (transition);
}
static void
on_request_release_done (GObject * src, GAsyncResult * res,
WpTransition * transition)
{
WpReserveDeviceAcquireTransition *self =
WP_RESERVE_DEVICE_ACQUIRE_TRANSITION (transition);
g_autoptr (GError) error = NULL;
gboolean released = FALSE;
if (!wp_org_freedesktop_reserve_device1_call_request_release_finish (
self->proxy, &released, res, &error)) {
WpReserveDevice *rd = wp_transition_get_source_object (transition);
wp_info_object (rd, "%s: Could not call RequestRelease: %s",
rd->name, error->message);
}
self->owner_state = released ? OWNER_STATE_ACQUIRED : OWNER_STATE_LOST;
wp_transition_advance (transition);
}
static void
wp_reserve_device_acquire_transition_execute_step (
WpTransition * transition, guint step)
{
WpReserveDeviceAcquireTransition *self =
WP_RESERVE_DEVICE_ACQUIRE_TRANSITION (transition);
WpReserveDevice *rd = wp_transition_get_source_object (transition);
g_autoptr (WpReserveDevicePlugin) plugin = g_weak_ref_get (&rd->plugin);
if (G_UNLIKELY (!plugin && step != WP_TRANSITION_STEP_ERROR)) {
wp_transition_return_error (transition, g_error_new (
WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"plugin destroyed while Acquire was in progress"));
return;
}
switch (step) {
case STEP_EXPORT_OBJECT:
wp_reserve_device_export_object (rd);
wp_transition_advance (transition);
break;
case STEP_ACQUIRE_NO_FORCE:
wp_reserve_device_own_name (rd, FALSE);
break;
case STEP_GET_PROXY:
wp_org_freedesktop_reserve_device1_proxy_new (plugin->connection,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
rd->service_name, rd->object_path, NULL,
(GAsyncReadyCallback) on_got_proxy, self);
break;
case STEP_REQUEST_RELEASE:
self->owner_state = OWNER_STATE_NONE;
wp_org_freedesktop_reserve_device1_call_request_release (
self->proxy, rd->priority, NULL,
(GAsyncReadyCallback) on_request_release_done, self);
break;
case STEP_ACQUIRE_WITH_FORCE:
wp_reserve_device_unown_name (rd);
self->owner_state = OWNER_STATE_NONE;
wp_reserve_device_own_name (rd, TRUE);
break;
case STEP_UNEXPORT_OBJECT:
wp_reserve_device_unown_name (rd);
wp_reserve_device_unexport_object (rd);
wp_transition_advance (transition);
break;
case WP_TRANSITION_STEP_ERROR:
wp_reserve_device_unown_name (rd);
break;
default:
g_return_if_reached ();
}
}
static void
wp_reserve_device_acquire_transition_class_init (
WpReserveDeviceAcquireTransitionClass * klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpTransitionClass *transition_class = (WpTransitionClass *) klass;
object_class->finalize =
wp_reserve_device_acquire_transition_finalize;
transition_class->get_next_step =
wp_reserve_device_acquire_transition_get_next_step;
transition_class->execute_step =
wp_reserve_device_acquire_transition_execute_step;
}
WpTransition *
wp_reserve_device_acquire_transition_new (WpReserveDevice *rd,
GCancellable * cancellable, GAsyncReadyCallback callback,
gpointer callback_data)
{
return wp_transition_new (wp_reserve_device_acquire_transition_get_type (),
rd, cancellable, callback, callback_data);
}
void
wp_reserve_device_acquire_transition_name_acquired (WpTransition * tr)
{
WpReserveDeviceAcquireTransition *self =
WP_RESERVE_DEVICE_ACQUIRE_TRANSITION (tr);
self->owner_state = OWNER_STATE_ACQUIRED;
wp_transition_advance (tr);
}
void
wp_reserve_device_acquire_transition_name_lost (WpTransition * tr)
{
WpReserveDeviceAcquireTransition *self =
WP_RESERVE_DEVICE_ACQUIRE_TRANSITION (tr);
self->owner_state = OWNER_STATE_LOST;
wp_transition_advance (tr);
}
gboolean
wp_reserve_device_acquire_transition_finish (GAsyncResult * res,
GError ** error)
{
if (!wp_transition_finish (res, error))
return FALSE;
WpReserveDeviceAcquireTransition *self =
WP_RESERVE_DEVICE_ACQUIRE_TRANSITION (res);
return (self->owner_state == OWNER_STATE_ACQUIRED) ? TRUE : FALSE;
}

View file

@ -0,0 +1,32 @@
/* WirePlumber
*
* Copyright © 2021 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __WIREPLUMBER_RESERVE_DEVICE_TRANSITIONS_H__
#define __WIREPLUMBER_RESERVE_DEVICE_TRANSITIONS_H__
#include "reserve-device.h"
G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (WpReserveDeviceAcquireTransition,
wp_reserve_device_acquire_transition,
WP, RESERVE_DEVICE_ACQUIRE_TRANSITION, WpTransition)
WpTransition * wp_reserve_device_acquire_transition_new (WpReserveDevice *rd,
GCancellable * cancellable, GAsyncReadyCallback callback,
gpointer callback_data);
void wp_reserve_device_acquire_transition_name_acquired (WpTransition * tr);
void wp_reserve_device_acquire_transition_name_lost (WpTransition * tr);
gboolean wp_reserve_device_acquire_transition_finish (GAsyncResult * res,
GError ** error);
G_END_DECLS
#endif

View file

@ -63,6 +63,13 @@ test(
workdir : meson.current_source_dir(),
)
test(
'test-reserve-device',
executable('test-reserve-device', 'reserve-device.c',
dependencies: common_deps, c_args: common_args),
env: common_env,
)
test(
'test-si-simple-node-endpoint',
executable('test-si-simple-node-endpoint', 'si-simple-node-endpoint.c',

View file

@ -0,0 +1,371 @@
/* WirePlumber
*
* Copyright © 2021 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "../common/base-test-fixture.h"
typedef struct {
WpBaseTestFixture base;
GTestDBus *test_dbus;
WpPlugin *rd_plugin_1;
WpPlugin *rd_plugin_2;
gint expected_rd1_state;
gint expected_rd2_state;
} RdTestFixture;
static void
test_rd_setup (RdTestFixture *f, gconstpointer data)
{
wp_base_test_fixture_setup (&f->base,
WP_BASE_TEST_FLAG_CLIENT_CORE | WP_BASE_TEST_FLAG_DONT_CONNECT);
f->test_dbus = g_test_dbus_new (G_TEST_DBUS_NONE);
g_test_dbus_up (f->test_dbus);
{
g_autoptr (GError) error = NULL;
WpModule *module = wp_module_load (f->base.core, "C",
"libwireplumber-module-reserve-device", NULL, &error);
g_assert_no_error (error);
g_assert_nonnull (module);
}
{
g_autoptr (GError) error = NULL;
WpModule *module = wp_module_load (f->base.client_core, "C",
"libwireplumber-module-reserve-device", NULL, &error);
g_assert_no_error (error);
g_assert_nonnull (module);
}
f->rd_plugin_1 = wp_plugin_find (f->base.core, "reserve-device");
g_assert_nonnull (f->rd_plugin_1);
f->rd_plugin_2 = wp_plugin_find (f->base.client_core, "reserve-device");
g_assert_nonnull (f->rd_plugin_2);
}
static void
test_rd_teardown (RdTestFixture *f, gconstpointer data)
{
g_clear_object (&f->rd_plugin_1);
g_clear_object (&f->rd_plugin_2);
g_test_dbus_down (f->test_dbus);
g_clear_object (&f->test_dbus);
wp_base_test_fixture_teardown (&f->base);
}
static void
ensure_plugins_stable_state (GObject * obj, GParamSpec * spec, RdTestFixture *f)
{
gint state1 = 0, state2 = 0;
g_object_get (f->rd_plugin_1, "state", &state1, NULL);
g_object_get (f->rd_plugin_2, "state", &state2, NULL);
if (state1 != 1 && state2 != 1 && state1 == state2)
g_main_loop_quit (f->base.loop);
}
static void
test_rd_plugin (RdTestFixture *f, gconstpointer data)
{
GObject *rd1 = NULL, *rd2 = NULL, *rd_video = NULL, *tmp = NULL;
gint state = 0xffff;
gchar *str;
g_object_get (f->rd_plugin_1, "state", &state, NULL);
g_assert_cmpint (state, ==, 0);
g_object_get (f->rd_plugin_2, "state", &state, NULL);
g_assert_cmpint (state, ==, 0);
wp_plugin_activate (f->rd_plugin_1);
wp_plugin_activate (f->rd_plugin_2);
g_object_get (f->rd_plugin_1, "state", &state, NULL);
g_assert_cmpint (state, ==, 1);
g_object_get (f->rd_plugin_2, "state", &state, NULL);
g_assert_cmpint (state, ==, 1);
g_signal_connect (f->rd_plugin_1, "notify::state",
G_CALLBACK (ensure_plugins_stable_state), f);
g_signal_connect (f->rd_plugin_2, "notify::state",
G_CALLBACK (ensure_plugins_stable_state), f);
g_main_loop_run (f->base.loop);
g_object_get (f->rd_plugin_1, "state", &state, NULL);
g_assert_cmpint (state, ==, 2);
g_object_get (f->rd_plugin_2, "state", &state, NULL);
g_assert_cmpint (state, ==, 2);
g_signal_emit_by_name (f->rd_plugin_1, "create-reservation",
"Audio0", "WirePlumber", "hw:0,0", 10, &rd1);
g_assert_nonnull (rd1);
g_signal_emit_by_name (f->rd_plugin_2, "create-reservation",
"Audio0", "Other Server", "hw:0,0", 15, &rd2);
g_assert_nonnull (rd2);
g_signal_emit_by_name (f->rd_plugin_1, "create-reservation",
"Video0", "WirePlumber", "/dev/video0", 10, &rd_video);
g_assert_nonnull (rd_video);
g_signal_emit_by_name (f->rd_plugin_1, "get-reservation", "Video1", &tmp);
g_assert_null (tmp);
g_signal_emit_by_name (f->rd_plugin_2, "get-reservation", "Video0", &tmp);
g_assert_null (tmp);
g_signal_emit_by_name (f->rd_plugin_1, "get-reservation", "Audio0", &tmp);
g_assert_nonnull (tmp);
g_assert_true (tmp == rd1);
g_clear_object (&tmp);
g_object_get (rd1, "name", &str, NULL);
g_assert_cmpstr (str, ==, "Audio0");
g_free (str);
g_object_get (rd2, "name", &str, NULL);
g_assert_cmpstr (str, ==, "Audio0");
g_free (str);
g_object_get (rd_video, "name", &str, NULL);
g_assert_cmpstr (str, ==, "Video0");
g_free (str);
g_object_get (rd1, "application-name", &str, NULL);
g_assert_cmpstr (str, ==, "WirePlumber");
g_free (str);
g_object_get (rd1, "application-device-name", &str, NULL);
g_assert_cmpstr (str, ==, "hw:0,0");
g_free (str);
g_object_get (rd1, "priority", &state, NULL);
g_assert_cmpint (state, ==, 10);
g_object_get (rd2, "priority", &state, NULL);
g_assert_cmpint (state, ==, 15);
g_signal_emit_by_name (f->rd_plugin_1, "destroy-reservation", "Audio0");
g_signal_emit_by_name (f->rd_plugin_1, "get-reservation", "Audio0", &tmp);
g_assert_null (tmp);
g_signal_emit_by_name (f->rd_plugin_2, "get-reservation", "Audio0", &tmp);
g_assert_nonnull (tmp);
g_assert_true (tmp == rd2);
g_clear_object (&tmp);
g_clear_object (&rd2);
g_clear_object (&rd1);
g_clear_object (&rd_video);
wp_plugin_deactivate (f->rd_plugin_1);
wp_plugin_deactivate (f->rd_plugin_2);
g_object_get (f->rd_plugin_1, "state", &state, NULL);
g_assert_cmpint (state, ==, 0);
g_object_get (f->rd_plugin_2, "state", &state, NULL);
g_assert_cmpint (state, ==, 0);
}
static void
test_rd_conn_closed (RdTestFixture *f, gconstpointer data)
{
GObject *rd1 = NULL;
gint state = 0xffff;
g_object_get (f->rd_plugin_1, "state", &state, NULL);
g_assert_cmpint (state, ==, 0);
g_object_get (f->rd_plugin_2, "state", &state, NULL);
g_assert_cmpint (state, ==, 0);
wp_plugin_activate (f->rd_plugin_1);
wp_plugin_activate (f->rd_plugin_2);
g_object_get (f->rd_plugin_1, "state", &state, NULL);
g_assert_cmpint (state, ==, 1);
g_object_get (f->rd_plugin_2, "state", &state, NULL);
g_assert_cmpint (state, ==, 1);
g_signal_connect (f->rd_plugin_1, "notify::state",
G_CALLBACK (ensure_plugins_stable_state), f);
g_signal_connect (f->rd_plugin_2, "notify::state",
G_CALLBACK (ensure_plugins_stable_state), f);
g_main_loop_run (f->base.loop);
g_object_get (f->rd_plugin_1, "state", &state, NULL);
g_assert_cmpint (state, ==, 2);
g_object_get (f->rd_plugin_2, "state", &state, NULL);
g_assert_cmpint (state, ==, 2);
g_signal_emit_by_name (f->rd_plugin_1, "create-reservation",
"Audio0", "WirePlumber", "hw:0,0", 10, &rd1);
g_assert_nonnull (rd1);
g_clear_object (&rd1);
/* stop the bus, expect the connections to close
and state to go back to CLOSED */
g_test_dbus_stop (f->test_dbus);
g_main_loop_run (f->base.loop);
g_object_get (f->rd_plugin_1, "state", &state, NULL);
g_assert_cmpint (state, ==, 0);
g_object_get (f->rd_plugin_2, "state", &state, NULL);
g_assert_cmpint (state, ==, 0);
g_signal_emit_by_name (f->rd_plugin_1, "get-reservation", "Audio0", &rd1);
g_assert_null (rd1);
}
static void
expect_rd1_state (GObject * rd, GParamSpec * spec, RdTestFixture *f)
{
gint state;
g_object_get (rd, "state", &state, NULL);
if (state == f->expected_rd1_state)
g_main_loop_quit (f->base.loop);
}
static void
expect_rd2_state (GObject * rd, GParamSpec * spec, RdTestFixture *f)
{
gint state;
g_object_get (rd, "state", &state, NULL);
if (state == f->expected_rd2_state)
g_main_loop_quit (f->base.loop);
}
static void
handle_release_requested (GObject * rd, gboolean forced, RdTestFixture *f)
{
gint state = 0xffff;
g_signal_emit_by_name (rd, "release");
g_object_get (rd, "state", &state, NULL);
g_assert_cmpint (state, ==, 2);
g_main_loop_quit (f->base.loop);
}
static void
test_rd_acquire_release (RdTestFixture *f, gconstpointer data)
{
GObject *rd1 = NULL, *rd2 = NULL;
gint state = 0xffff;
gchar *str = NULL;
g_object_get (f->rd_plugin_1, "state", &state, NULL);
g_assert_cmpint (state, ==, 0);
g_object_get (f->rd_plugin_2, "state", &state, NULL);
g_assert_cmpint (state, ==, 0);
wp_plugin_activate (f->rd_plugin_1);
wp_plugin_activate (f->rd_plugin_2);
g_object_get (f->rd_plugin_1, "state", &state, NULL);
g_assert_cmpint (state, ==, 1);
g_object_get (f->rd_plugin_2, "state", &state, NULL);
g_assert_cmpint (state, ==, 1);
g_signal_connect (f->rd_plugin_1, "notify::state",
G_CALLBACK (ensure_plugins_stable_state), f);
g_signal_connect (f->rd_plugin_2, "notify::state",
G_CALLBACK (ensure_plugins_stable_state), f);
g_main_loop_run (f->base.loop);
g_object_get (f->rd_plugin_1, "state", &state, NULL);
g_assert_cmpint (state, ==, 2);
g_object_get (f->rd_plugin_2, "state", &state, NULL);
g_assert_cmpint (state, ==, 2);
g_signal_emit_by_name (f->rd_plugin_1, "create-reservation",
"Audio0", "WirePlumber", "hw:0,0", 10, &rd1);
g_assert_nonnull (rd1);
g_signal_emit_by_name (f->rd_plugin_2, "create-reservation",
"Audio0", "Other Server", "hw:0,0", 15, &rd2);
g_assert_nonnull (rd2);
g_signal_connect (rd1, "notify::state", G_CALLBACK (expect_rd1_state), f);
g_signal_connect (rd2, "notify::state", G_CALLBACK (expect_rd2_state), f);
/* acquire */
wp_info ("rd1 acquire");
f->expected_rd1_state = 3;
g_signal_emit_by_name (rd1, "acquire");
g_main_loop_run (f->base.loop);
g_object_get (rd1, "state", &state, NULL);
g_assert_cmpint (state, ==, 3);
g_object_get (rd1, "owner-application-name", &str, NULL);
g_assert_cmpstr (str, ==, "WirePlumber");
g_free (str);
g_signal_connect (rd1, "release-requested",
G_CALLBACK (handle_release_requested), f);
/* acquire with higher priority */
wp_info ("rd2 acquire, higher prio");
g_signal_emit_by_name (rd2, "acquire");
/* rd1 is now released */
g_main_loop_run (f->base.loop);
g_object_get (rd1, "state", &state, NULL);
g_assert_cmpint (state, ==, 2);
/* rd2 acquired */
f->expected_rd2_state = 3;
g_main_loop_run (f->base.loop);
g_object_get (rd2, "state", &state, NULL);
g_assert_cmpint (state, ==, 3);
/* rd1 busy */
g_signal_connect_swapped (rd1, "notify::owner-application-name",
G_CALLBACK (g_main_loop_quit), f->base.loop);
g_main_loop_run (f->base.loop);
g_object_get (rd1, "state", &state, NULL);
g_assert_cmpint (state, ==, 1);
g_object_get (rd1, "owner-application-name", &str, NULL);
g_assert_cmpstr (str, ==, "Other Server");
g_free (str);
g_signal_handlers_disconnect_by_func (rd1, G_CALLBACK (g_main_loop_quit),
f->base.loop);
/* try to acquire back with lower priority */
wp_info ("rd1 acquire, lower prio");
g_signal_emit_by_name (rd1, "acquire");
/* ... expect this to fail */
f->expected_rd1_state = 1;
g_main_loop_run (f->base.loop);
g_object_get (rd1, "state", &state, NULL);
g_assert_cmpint (state, ==, 1);
g_object_get (rd1, "owner-application-name", &str, NULL);
g_assert_cmpstr (str, ==, "Other Server");
g_free (str);
/* release */
wp_info ("rd2 release");
g_signal_emit_by_name (rd2, "release");
g_object_get (rd2, "state", &state, NULL);
g_assert_cmpint (state, ==, 2);
f->expected_rd1_state = 2;
g_main_loop_run (f->base.loop);
g_object_get (rd1, "state", &state, NULL);
g_assert_cmpint (state, ==, 2);
g_object_get (rd1, "owner-application-name", &str, NULL);
g_assert_null (str);
g_clear_object (&rd1);
g_clear_object (&rd2);
wp_plugin_deactivate (f->rd_plugin_1);
wp_plugin_deactivate (f->rd_plugin_2);
}
gint
main (gint argc, gchar *argv[])
{
g_test_init (&argc, &argv, NULL);
wp_init (WP_INIT_ALL);
g_test_add ("/modules/rd/plugin", RdTestFixture, NULL,
test_rd_setup, test_rd_plugin, test_rd_teardown);
g_test_add ("/modules/rd/conn_closed", RdTestFixture, NULL,
test_rd_setup, test_rd_conn_closed, test_rd_teardown);
g_test_add ("/modules/rd/acquire_release", RdTestFixture, NULL,
test_rd_setup, test_rd_acquire_release, test_rd_teardown);
return g_test_run ();
}