mirror of
https://github.com/zoriya/astal.git
synced 2026-06-05 19:04:20 +00:00
518 lines
19 KiB
C
518 lines
19 KiB
C
#include <wp/wp.h>
|
|
|
|
#include "audio.h"
|
|
#include "device-private.h"
|
|
#include "endpoint-private.h"
|
|
#include "glib-object.h"
|
|
#include "glib.h"
|
|
#include "video.h"
|
|
#include "wp.h"
|
|
|
|
struct _AstalWpWp {
|
|
GObject parent_instance;
|
|
|
|
AstalWpEndpoint *default_speaker;
|
|
AstalWpEndpoint *default_microphone;
|
|
|
|
AstalWpAudio *audio;
|
|
AstalWpVideo *video;
|
|
|
|
AstalWpScale scale;
|
|
};
|
|
|
|
typedef struct {
|
|
WpCore *core;
|
|
WpObjectManager *obj_manager;
|
|
|
|
WpPlugin *mixer;
|
|
WpPlugin *defaults;
|
|
gint pending_plugins;
|
|
|
|
GHashTable *endpoints;
|
|
GHashTable *devices;
|
|
} AstalWpWpPrivate;
|
|
|
|
G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalWpWp, astal_wp_wp, G_TYPE_OBJECT);
|
|
|
|
G_DEFINE_ENUM_TYPE(AstalWpScale, astal_wp_scale,
|
|
G_DEFINE_ENUM_VALUE(ASTAL_WP_SCALE_LINEAR, "linear"),
|
|
G_DEFINE_ENUM_VALUE(ASTAL_WP_SCALE_CUBIC, "cubic"));
|
|
|
|
typedef enum {
|
|
ASTAL_WP_WP_SIGNAL_ENDPOINT_ADDED,
|
|
ASTAL_WP_WP_SIGNAL_ENDPOINT_REMOVED,
|
|
ASTAL_WP_WP_SIGNAL_DEVICE_ADDED,
|
|
ASTAL_WP_WP_SIGNAL_DEVICE_REMOVED,
|
|
ASTAL_WP_WP_N_SIGNALS
|
|
} AstalWpWpSignals;
|
|
|
|
static guint astal_wp_wp_signals[ASTAL_WP_WP_N_SIGNALS] = {
|
|
0,
|
|
};
|
|
|
|
typedef enum {
|
|
ASTAL_WP_WP_PROP_AUDIO = 1,
|
|
ASTAL_WP_WP_PROP_VIDEO,
|
|
ASTAL_WP_WP_PROP_ENDPOINTS,
|
|
ASTAL_WP_WP_PROP_DEVICES,
|
|
ASTAL_WP_WP_PROP_DEFAULT_SPEAKER,
|
|
ASTAL_WP_WP_PROP_DEFAULT_MICROPHONE,
|
|
ASTAL_WP_WP_PROP_SCALE,
|
|
ASTAL_WP_WP_N_PROPERTIES,
|
|
} AstalWpWpProperties;
|
|
|
|
static GParamSpec *astal_wp_wp_properties[ASTAL_WP_WP_N_PROPERTIES] = {
|
|
NULL,
|
|
};
|
|
|
|
/**
|
|
* AstalWpWp
|
|
*
|
|
* manages the connection to wireplumber. Usually you don't want to use this class directly, but use
|
|
* the [class@AstalWp.Audio] or [class@AstalWp.Video] instead.
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* astal_wp_wp_get_endpoint:
|
|
* @self: the AstalWpWp object
|
|
* @id: the id of the endpoint
|
|
*
|
|
* the endpoint with the given id
|
|
*
|
|
* Returns: (transfer none) (nullable): the endpoint with the given id
|
|
*/
|
|
AstalWpEndpoint *astal_wp_wp_get_endpoint(AstalWpWp *self, guint id) {
|
|
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
|
|
|
|
AstalWpEndpoint *endpoint = g_hash_table_lookup(priv->endpoints, GUINT_TO_POINTER(id));
|
|
return endpoint;
|
|
}
|
|
|
|
/**
|
|
* astal_wp_wp_get_endpoints:
|
|
* @self: the AstalWpWp object
|
|
*
|
|
* a GList containing all endpoints
|
|
*
|
|
* Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the
|
|
* endpoints
|
|
*/
|
|
GList *astal_wp_wp_get_endpoints(AstalWpWp *self) {
|
|
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
|
|
return g_hash_table_get_values(priv->endpoints);
|
|
}
|
|
|
|
/**
|
|
* astal_wp_wp_get_device:
|
|
* @self: the AstalWpWp object
|
|
* @id: the id of the device
|
|
*
|
|
* the device with the given id
|
|
*
|
|
* Returns: (transfer none) (nullable): the device with the given id
|
|
*/
|
|
AstalWpDevice *astal_wp_wp_get_device(AstalWpWp *self, guint id) {
|
|
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
|
|
|
|
AstalWpDevice *device = g_hash_table_lookup(priv->devices, GUINT_TO_POINTER(id));
|
|
return device;
|
|
}
|
|
|
|
/**
|
|
* astal_wp_wp_get_devices:
|
|
* @self: the AstalWpWp object
|
|
*
|
|
* the GList containing the devices
|
|
*
|
|
* Returns: (transfer container) (nullable) (type GList(AstalWpDevice)): a GList containing the
|
|
* devices
|
|
*/
|
|
GList *astal_wp_wp_get_devices(AstalWpWp *self) {
|
|
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
|
|
return g_hash_table_get_values(priv->devices);
|
|
}
|
|
|
|
/**
|
|
* astal_wp_wp_get_audio
|
|
*
|
|
* gets the [class@AstalWp.Audio] object
|
|
*
|
|
* Returns: (nullable) (transfer none): gets the audio object
|
|
*/
|
|
AstalWpAudio *astal_wp_wp_get_audio(AstalWpWp *self) { return self->audio; }
|
|
|
|
/**
|
|
* astal_wp_wp_get_video
|
|
*
|
|
* gets the video object
|
|
*
|
|
* Returns: (nullable) (transfer none): gets the video object
|
|
*/
|
|
AstalWpVideo *astal_wp_wp_get_video(AstalWpWp *self) { return self->video; }
|
|
|
|
/**
|
|
* astal_wp_wp_get_default_speaker
|
|
*
|
|
* gets the default speaker object
|
|
*
|
|
* Returns: (nullable) (transfer none): gets the default speaker object
|
|
*/
|
|
AstalWpEndpoint *astal_wp_wp_get_default_speaker(AstalWpWp *self) { return self->default_speaker; }
|
|
|
|
/**
|
|
* astal_wp_wp_get_default_microphone
|
|
*
|
|
* gets the default microphone object
|
|
*
|
|
* Returns: (nullable) (transfer none): gets the default microphone object
|
|
*/
|
|
AstalWpEndpoint *astal_wp_wp_get_default_microphone(AstalWpWp *self) {
|
|
return self->default_microphone;
|
|
}
|
|
|
|
AstalWpScale astal_wp_wp_get_scale(AstalWpWp *self) { return self->scale; }
|
|
|
|
void astal_wp_wp_set_scale(AstalWpWp *self, AstalWpScale scale) {
|
|
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
|
|
self->scale = scale;
|
|
|
|
if (priv->mixer == NULL) return;
|
|
|
|
g_object_set(priv->mixer, "scale", self->scale, NULL);
|
|
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
|
|
g_hash_table_iter_init(&iter, priv->endpoints);
|
|
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
|
AstalWpEndpoint *ep = ASTAL_WP_ENDPOINT(value);
|
|
astal_wp_endpoint_update_volume(ep);
|
|
}
|
|
|
|
astal_wp_endpoint_update_volume(self->default_speaker);
|
|
astal_wp_endpoint_update_volume(self->default_microphone);
|
|
}
|
|
|
|
static void astal_wp_wp_get_property(GObject *object, guint property_id, GValue *value,
|
|
GParamSpec *pspec) {
|
|
AstalWpWp *self = ASTAL_WP_WP(object);
|
|
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
|
|
|
|
switch (property_id) {
|
|
case ASTAL_WP_WP_PROP_AUDIO:
|
|
g_value_set_object(value, astal_wp_wp_get_audio(self));
|
|
break;
|
|
case ASTAL_WP_WP_PROP_VIDEO:
|
|
g_value_set_object(value, astal_wp_wp_get_video(self));
|
|
break;
|
|
case ASTAL_WP_WP_PROP_ENDPOINTS:
|
|
g_value_set_pointer(value, g_hash_table_get_values(priv->endpoints));
|
|
break;
|
|
case ASTAL_WP_WP_PROP_DEVICES:
|
|
g_value_set_pointer(value, g_hash_table_get_values(priv->devices));
|
|
break;
|
|
case ASTAL_WP_WP_PROP_DEFAULT_SPEAKER:
|
|
g_value_set_object(value, self->default_speaker);
|
|
break;
|
|
case ASTAL_WP_WP_PROP_DEFAULT_MICROPHONE:
|
|
g_value_set_object(value, self->default_microphone);
|
|
break;
|
|
case ASTAL_WP_WP_PROP_SCALE:
|
|
g_value_set_enum(value, self->scale);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void astal_wp_wp_set_property(GObject *object, guint property_id, const GValue *value,
|
|
GParamSpec *pspec) {
|
|
AstalWpWp *self = ASTAL_WP_WP(object);
|
|
|
|
switch (property_id) {
|
|
case ASTAL_WP_WP_PROP_SCALE:
|
|
astal_wp_wp_set_scale(self, g_value_get_enum(value));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void astal_wp_wp_object_added(AstalWpWp *self, gpointer object) {
|
|
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
|
|
|
|
if (WP_IS_NODE(object)) {
|
|
WpNode *node = WP_NODE(object);
|
|
AstalWpEndpoint *endpoint =
|
|
astal_wp_endpoint_create(node, priv->mixer, priv->defaults, self);
|
|
|
|
g_hash_table_insert(priv->endpoints,
|
|
GUINT_TO_POINTER(wp_proxy_get_bound_id(WP_PROXY(node))), endpoint);
|
|
|
|
g_signal_emit_by_name(self, "endpoint-added", endpoint);
|
|
g_object_notify(G_OBJECT(self), "endpoints");
|
|
} else if (WP_IS_DEVICE(object)) {
|
|
WpDevice *node = WP_DEVICE(object);
|
|
AstalWpDevice *device = astal_wp_device_create(node);
|
|
g_hash_table_insert(priv->devices, GUINT_TO_POINTER(wp_proxy_get_bound_id(WP_PROXY(node))),
|
|
device);
|
|
g_signal_emit_by_name(self, "device-added", device);
|
|
g_object_notify(G_OBJECT(self), "devices");
|
|
}
|
|
}
|
|
|
|
static void astal_wp_wp_object_removed(AstalWpWp *self, gpointer object) {
|
|
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
|
|
|
|
if (WP_IS_NODE(object)) {
|
|
guint id = wp_proxy_get_bound_id(WP_PROXY(object));
|
|
AstalWpEndpoint *endpoint =
|
|
g_object_ref(g_hash_table_lookup(priv->endpoints, GUINT_TO_POINTER(id)));
|
|
|
|
g_hash_table_remove(priv->endpoints, GUINT_TO_POINTER(id));
|
|
|
|
g_signal_emit_by_name(self, "endpoint-removed", endpoint);
|
|
g_object_notify(G_OBJECT(self), "endpoints");
|
|
g_object_unref(endpoint);
|
|
} else if (WP_IS_DEVICE(object)) {
|
|
guint id = wp_proxy_get_bound_id(WP_PROXY(object));
|
|
AstalWpDevice *device =
|
|
g_object_ref(g_hash_table_lookup(priv->devices, GUINT_TO_POINTER(id)));
|
|
g_hash_table_remove(priv->devices, GUINT_TO_POINTER(id));
|
|
|
|
g_signal_emit_by_name(self, "device-removed", device);
|
|
g_object_notify(G_OBJECT(self), "devices");
|
|
g_object_unref(device);
|
|
}
|
|
}
|
|
|
|
static void astal_wp_wp_objm_installed(AstalWpWp *self) {
|
|
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
|
|
|
|
astal_wp_endpoint_init_as_default(self->default_speaker, priv->mixer, priv->defaults,
|
|
ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER, self);
|
|
astal_wp_endpoint_init_as_default(self->default_microphone, priv->mixer, priv->defaults,
|
|
ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE, self);
|
|
}
|
|
|
|
static void astal_wp_wp_plugin_activated(WpObject *obj, GAsyncResult *result, AstalWpWp *self) {
|
|
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
|
|
|
|
GError *error = NULL;
|
|
wp_object_activate_finish(obj, result, &error);
|
|
if (error) {
|
|
g_critical("Failed to activate component: %s\n", error->message);
|
|
return;
|
|
}
|
|
|
|
if (--priv->pending_plugins == 0) {
|
|
priv->defaults = wp_plugin_find(priv->core, "default-nodes-api");
|
|
priv->mixer = wp_plugin_find(priv->core, "mixer-api");
|
|
g_object_set(priv->mixer, "scale", self->scale, NULL);
|
|
|
|
g_signal_connect_swapped(priv->obj_manager, "object-added",
|
|
G_CALLBACK(astal_wp_wp_object_added), self);
|
|
g_signal_connect_swapped(priv->obj_manager, "object-removed",
|
|
G_CALLBACK(astal_wp_wp_object_removed), self);
|
|
|
|
wp_core_install_object_manager(priv->core, priv->obj_manager);
|
|
}
|
|
}
|
|
|
|
static void astal_wp_wp_plugin_loaded(WpObject *obj, GAsyncResult *result, AstalWpWp *self) {
|
|
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
|
|
|
|
GError *error = NULL;
|
|
wp_core_load_component_finish(priv->core, result, &error);
|
|
if (error) {
|
|
g_critical("Failed to load component: %s\n", error->message);
|
|
return;
|
|
}
|
|
|
|
wp_object_activate(obj, WP_PLUGIN_FEATURE_ENABLED, NULL,
|
|
(GAsyncReadyCallback)astal_wp_wp_plugin_activated, self);
|
|
}
|
|
|
|
/**
|
|
* astal_wp_wp_get_default
|
|
*
|
|
* gets the default wireplumber object.
|
|
*
|
|
* Returns: (nullable) (transfer none): gets the default wireplumber object.
|
|
*/
|
|
AstalWpWp *astal_wp_wp_get_default() {
|
|
static AstalWpWp *self = NULL;
|
|
|
|
if (self == NULL) self = g_object_new(ASTAL_WP_TYPE_WP, NULL);
|
|
|
|
return self;
|
|
}
|
|
|
|
/**
|
|
* astal_wp_get_default
|
|
*
|
|
* gets the default wireplumber object.
|
|
*
|
|
* Returns: (nullable) (transfer none): gets the default wireplumber object.
|
|
*/
|
|
AstalWpWp *astal_wp_get_default() { return astal_wp_wp_get_default(); }
|
|
|
|
static void astal_wp_wp_dispose(GObject *object) {
|
|
AstalWpWp *self = ASTAL_WP_WP(object);
|
|
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
|
|
|
|
g_clear_object(&self->video);
|
|
g_clear_object(&self->audio);
|
|
|
|
wp_core_disconnect(priv->core);
|
|
g_clear_object(&self->default_speaker);
|
|
g_clear_object(&self->default_microphone);
|
|
g_clear_object(&priv->mixer);
|
|
g_clear_object(&priv->defaults);
|
|
g_clear_object(&priv->obj_manager);
|
|
g_clear_object(&priv->core);
|
|
|
|
if (priv->endpoints != NULL) {
|
|
g_hash_table_destroy(priv->endpoints);
|
|
priv->endpoints = NULL;
|
|
}
|
|
}
|
|
|
|
static void astal_wp_wp_finalize(GObject *object) {
|
|
AstalWpWp *self = ASTAL_WP_WP(object);
|
|
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
|
|
}
|
|
|
|
static void astal_wp_wp_init(AstalWpWp *self) {
|
|
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
|
|
|
|
priv->endpoints = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref);
|
|
priv->devices = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref);
|
|
|
|
wp_init(7);
|
|
priv->core = wp_core_new(NULL, NULL, NULL);
|
|
|
|
if (!wp_core_connect(priv->core)) {
|
|
g_critical("could not connect to PipeWire\n");
|
|
return;
|
|
}
|
|
|
|
priv->obj_manager = wp_object_manager_new();
|
|
wp_object_manager_request_object_features(priv->obj_manager, WP_TYPE_NODE,
|
|
WP_OBJECT_FEATURES_ALL);
|
|
wp_object_manager_request_object_features(priv->obj_manager, WP_TYPE_GLOBAL_PROXY,
|
|
WP_OBJECT_FEATURES_ALL);
|
|
|
|
wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY,
|
|
"media.class", "=s", "Audio/Sink", NULL);
|
|
wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY,
|
|
"media.class", "=s", "Audio/Source", NULL);
|
|
wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY,
|
|
"media.class", "=s", "Stream/Output/Audio", NULL);
|
|
wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY,
|
|
"media.class", "=s", "Stream/Input/Audio", NULL);
|
|
wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_DEVICE,
|
|
WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "media.class", "=s",
|
|
"Audio/Device", NULL);
|
|
|
|
wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY,
|
|
"media.class", "=s", "Video/Sink", NULL);
|
|
wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY,
|
|
"media.class", "=s", "Video/Source", NULL);
|
|
wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY,
|
|
"media.class", "=s", "Stream/Output/Video", NULL);
|
|
wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY,
|
|
"media.class", "=s", "Stream/Input/Video", NULL);
|
|
wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_DEVICE,
|
|
WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "media.class", "=s",
|
|
"Video/Device", NULL);
|
|
// wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_CLIENT, NULL);
|
|
|
|
g_signal_connect_swapped(priv->obj_manager, "installed", (GCallback)astal_wp_wp_objm_installed,
|
|
self);
|
|
|
|
self->default_speaker = g_object_new(ASTAL_WP_TYPE_ENDPOINT, NULL);
|
|
self->default_microphone = g_object_new(ASTAL_WP_TYPE_ENDPOINT, NULL);
|
|
|
|
self->audio = astal_wp_audio_new(self);
|
|
self->video = astal_wp_video_new(self);
|
|
|
|
priv->pending_plugins = 2;
|
|
wp_core_load_component(priv->core, "libwireplumber-module-default-nodes-api", "module", NULL,
|
|
"default-nodes-api", NULL,
|
|
(GAsyncReadyCallback)astal_wp_wp_plugin_loaded, self);
|
|
wp_core_load_component(priv->core, "libwireplumber-module-mixer-api", "module", NULL,
|
|
"mixer-api", NULL, (GAsyncReadyCallback)astal_wp_wp_plugin_loaded, self);
|
|
}
|
|
|
|
static void astal_wp_wp_class_init(AstalWpWpClass *class) {
|
|
GObjectClass *object_class = G_OBJECT_CLASS(class);
|
|
object_class->finalize = astal_wp_wp_finalize;
|
|
object_class->dispose = astal_wp_wp_dispose;
|
|
object_class->get_property = astal_wp_wp_get_property;
|
|
object_class->set_property = astal_wp_wp_set_property;
|
|
|
|
astal_wp_wp_properties[ASTAL_WP_WP_PROP_AUDIO] =
|
|
g_param_spec_object("audio", "audio", "audio", ASTAL_WP_TYPE_AUDIO, G_PARAM_READABLE);
|
|
astal_wp_wp_properties[ASTAL_WP_WP_PROP_VIDEO] =
|
|
g_param_spec_object("video", "video", "video", ASTAL_WP_TYPE_VIDEO, G_PARAM_READABLE);
|
|
/**
|
|
* AstalWpWp:scale: (type AstalWpScale)
|
|
*
|
|
* The scale used for the volume
|
|
*/
|
|
astal_wp_wp_properties[ASTAL_WP_WP_PROP_SCALE] =
|
|
g_param_spec_enum("scale", "scale", "scale", ASTAL_WP_TYPE_SCALE, ASTAL_WP_SCALE_CUBIC,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
|
|
|
|
/**
|
|
* AstalWpWp:endpoints: (type GList(AstalWpEndpoint)) (transfer container)
|
|
*
|
|
* A list of [class@AstalWp.Endpoint] objects
|
|
*/
|
|
astal_wp_wp_properties[ASTAL_WP_WP_PROP_ENDPOINTS] =
|
|
g_param_spec_pointer("endpoints", "endpoints", "endpoints", G_PARAM_READABLE);
|
|
/**
|
|
* AstalWpWp:devices: (type GList(AstalWpDevice)) (transfer container)
|
|
*
|
|
* A list of [class@AstalWp.Device] objects
|
|
*/
|
|
astal_wp_wp_properties[ASTAL_WP_WP_PROP_DEVICES] =
|
|
g_param_spec_pointer("devices", "devices", "devices", G_PARAM_READABLE);
|
|
/**
|
|
* AstalWpWp:default-speaker:
|
|
*
|
|
* The [class@AstalWp.Endpoint] representing the default speaker
|
|
*/
|
|
astal_wp_wp_properties[ASTAL_WP_WP_PROP_DEFAULT_SPEAKER] =
|
|
g_param_spec_object("default-speaker", "default-speaker", "default-speaker",
|
|
ASTAL_WP_TYPE_ENDPOINT, G_PARAM_READABLE);
|
|
/**
|
|
* AstalWpWp:default-microphone:
|
|
*
|
|
* The [class@AstalWp.Endpoint] representing the default speaker
|
|
*/
|
|
astal_wp_wp_properties[ASTAL_WP_WP_PROP_DEFAULT_MICROPHONE] =
|
|
g_param_spec_object("default-microphone", "default-microphone", "default-microphone",
|
|
ASTAL_WP_TYPE_ENDPOINT, G_PARAM_READABLE);
|
|
|
|
g_object_class_install_properties(object_class, ASTAL_WP_WP_N_PROPERTIES,
|
|
astal_wp_wp_properties);
|
|
|
|
astal_wp_wp_signals[ASTAL_WP_WP_SIGNAL_ENDPOINT_ADDED] =
|
|
g_signal_new("endpoint-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
|
|
NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT);
|
|
astal_wp_wp_signals[ASTAL_WP_WP_SIGNAL_ENDPOINT_REMOVED] =
|
|
g_signal_new("endpoint-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL,
|
|
NULL, NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT);
|
|
astal_wp_wp_signals[ASTAL_WP_WP_SIGNAL_DEVICE_ADDED] =
|
|
g_signal_new("device-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
|
|
NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE);
|
|
astal_wp_wp_signals[ASTAL_WP_WP_SIGNAL_DEVICE_REMOVED] =
|
|
g_signal_new("device-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
|
|
NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE);
|
|
}
|