diff --git a/modules/meson.build b/modules/meson.build index 5edfb10a011fcc0a32672d74cd05f12048e63c7e..79448d4db76c978220f6d65b196908982e0d610c 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -25,6 +25,17 @@ shared_library( dependencies : [wp_dep], ) +shared_library( + 'wireplumber-module-monitor', + [ + 'module-monitor.c', + ], + c_args : [common_c_args, '-DG_LOG_DOMAIN="m-monitor"'], + install : true, + install_dir : wireplumber_module_dir, + dependencies : [wp_dep, pipewire_dep], +) + shared_library( 'wireplumber-module-pipewire', [ diff --git a/modules/module-monitor.c b/modules/module-monitor.c new file mode 100644 index 0000000000000000000000000000000000000000..d80bcf70b67c7ef538006217b55a38144b00b0e0 --- /dev/null +++ b/modules/module-monitor.c @@ -0,0 +1,250 @@ +/* WirePlumber + * + * Copyright © 2019 Wim Taymans + * Copyright © 2019 Collabora Ltd. + * @author George Kiagiadakis <george.kiagiadakis@collabora.com> + * + * SPDX-License-Identifier: MIT + */ + +#include <wp/wp.h> + +#include <spa/monitor/monitor.h> +#include <pipewire/pipewire.h> +#include <spa/utils/keys.h> + +static void +setup_device_props (WpMonitor *self, WpProperties *p, WpModule *module) +{ + const gchar *s, *d, *api; + + api = wp_properties_get (p, SPA_KEY_DEVICE_API); + + /* set the device name if it's not already set */ + if (!wp_properties_get (p, SPA_KEY_DEVICE_NAME)) { + if ((s = wp_properties_get (p, SPA_KEY_DEVICE_BUS_ID)) == NULL) { + if ((s = wp_properties_get (p, SPA_KEY_DEVICE_BUS_PATH)) == NULL) { + s = wp_properties_get (p, WP_MONITOR_KEY_OBJECT_ID); + } + } + + if (!g_strcmp0 (api, "alsa")) { + /* what we call a "device" in pipewire, in alsa it's a "card", + so make it clear to avoid confusion */ + wp_properties_setf (p, PW_KEY_DEVICE_NAME, "alsa_card.%s", s); + } else { + wp_properties_setf (p, PW_KEY_DEVICE_NAME, "%s_device.%s", api, s); + } + } + + /* set the device description if it's not already set */ + if (!wp_properties_get (p, PW_KEY_DEVICE_DESCRIPTION)) { + d = NULL; + + if (!g_strcmp0 (api, "alsa")) { + if ((s = wp_properties_get (p, PW_KEY_DEVICE_FORM_FACTOR)) + && !g_strcmp0 (s, "internal")) + d = "Built-in Audio"; + else if ((s = wp_properties_get (p, PW_KEY_DEVICE_CLASS)) + && !g_strcmp0 (s, "modem")) + d = "Modem"; + } + + if (!d) + d = wp_properties_get (p, PW_KEY_DEVICE_PRODUCT_NAME); + if (!d) + d = "Unknown device"; + + wp_properties_set (p, PW_KEY_DEVICE_DESCRIPTION, d); + } + + /* set the icon name for ALSA - TODO for other APIs */ + if (!wp_properties_get (p, PW_KEY_DEVICE_ICON_NAME) + && !g_strcmp0 (api, "alsa")) + { + d = NULL; + + if ((s = wp_properties_get (p, PW_KEY_DEVICE_FORM_FACTOR))) { + if (!g_strcmp0 (s, "microphone")) + d = "audio-input-microphone"; + else if (!g_strcmp0 (s, "webcam")) + d = "camera-web"; + else if (!g_strcmp0 (s, "computer")) + d = "computer"; + else if (!g_strcmp0 (s, "handset")) + d = "phone"; + else if (!g_strcmp0 (s, "portable")) + d = "multimedia-player"; + else if (!g_strcmp0 (s, "tv")) + d = "video-display"; + else if (!g_strcmp0 (s, "headset")) + d = "audio-headset"; + else if (!g_strcmp0 (s, "headphone")) + d = "audio-headphones"; + else if (!g_strcmp0 (s, "speaker")) + d = "audio-speakers"; + else if (!g_strcmp0 (s, "hands-free")) + d = "audio-handsfree"; + } + if (!d) + if ((s = wp_properties_get (p, PW_KEY_DEVICE_CLASS)) + && !g_strcmp0 (s, "modem")) + d = "modem"; + + if (!d) + d = "audio-card"; + + s = wp_properties_get (p, PW_KEY_DEVICE_BUS); + + wp_properties_setf (p, PW_KEY_DEVICE_ICON_NAME, + "%s-analog%s%s", d, s ? "-" : "", s); + } +} + +static void +setup_node_props (WpMonitor *self, WpProperties *dev_props, + WpProperties *node_props, WpModule *module) +{ + const gchar *api, *name, *description, *factory; + + /* Make the device properties directly available on the node */ + wp_properties_copy_keys (dev_props, node_props, + SPA_KEY_DEVICE_API, + SPA_KEY_DEVICE_NAME, + SPA_KEY_DEVICE_ALIAS, + SPA_KEY_DEVICE_NICK, + SPA_KEY_DEVICE_DESCRIPTION, + SPA_KEY_DEVICE_ICON, + SPA_KEY_DEVICE_ICON_NAME, + SPA_KEY_DEVICE_PLUGGED_USEC, + SPA_KEY_DEVICE_BUS_ID, + SPA_KEY_DEVICE_BUS_PATH, + SPA_KEY_DEVICE_BUS, + SPA_KEY_DEVICE_SUBSYSTEM, + SPA_KEY_DEVICE_SYSFS_PATH, + SPA_KEY_DEVICE_VENDOR_ID, + SPA_KEY_DEVICE_VENDOR_NAME, + SPA_KEY_DEVICE_PRODUCT_ID, + SPA_KEY_DEVICE_PRODUCT_NAME, + SPA_KEY_DEVICE_SERIAL, + SPA_KEY_DEVICE_CLASS, + SPA_KEY_DEVICE_CAPABILITIES, + SPA_KEY_DEVICE_FORM_FACTOR, + PW_KEY_DEVICE_INTENDED_ROLES, + NULL); + + /* get some strings that we are going to need below */ + api = wp_properties_get (node_props, SPA_KEY_DEVICE_API); + factory = wp_properties_get (node_props, PW_KEY_FACTORY_NAME); + + name = wp_properties_get (node_props, SPA_KEY_DEVICE_NAME); + if (G_UNLIKELY (!name)) + name = wp_properties_get (node_props, SPA_KEY_DEVICE_NICK); + if (G_UNLIKELY (!name)) + name = wp_properties_get (node_props, SPA_KEY_DEVICE_ALIAS); + if (G_UNLIKELY (!name)) + name = "unknown-device"; + + description = wp_properties_get (node_props, SPA_KEY_DEVICE_DESCRIPTION); + if (!description) + description = name; + + /* set ALSA specific properties */ + if (!g_strcmp0 (api, "alsa")) { + const gchar *str; + + /* compose the node name */ + str = wp_properties_get (node_props, SPA_KEY_API_ALSA_PCM_ID); + wp_properties_setf (node_props, PW_KEY_NODE_NAME, "%s/%s/%s", + factory, name, str); + + /* compose the node description */ + str = wp_properties_get (node_props, SPA_KEY_API_ALSA_PCM_NAME); + wp_properties_setf (node_props, PW_KEY_NODE_DESCRIPTION, "%s: %s", + description, str); + + wp_properties_copy_keys (dev_props, node_props, + SPA_KEY_API_ALSA_CARD, + SPA_KEY_API_ALSA_CARD_ID, + SPA_KEY_API_ALSA_CARD_COMPONENTS, + SPA_KEY_API_ALSA_CARD_DRIVER, + SPA_KEY_API_ALSA_CARD_NAME, + SPA_KEY_API_ALSA_CARD_LONGNAME, + SPA_KEY_API_ALSA_CARD_MIXERNAME, + NULL); + + /* set BLUEZ 5 specific properties */ + } else if (!g_strcmp0 (api, "bluez5")) { + const gchar *profile = + wp_properties_get (node_props, SPA_KEY_API_BLUEZ5_PROFILE); + + /* compose the node name */ + wp_properties_setf (node_props, PW_KEY_NODE_NAME, "%s/%s/%s", + factory, name, profile); + + /* compose the node description */ + wp_properties_setf (node_props, PW_KEY_NODE_DESCRIPTION, "%s (%s)", + description, profile); + + wp_properties_copy_keys (dev_props, node_props, + SPA_KEY_API_BLUEZ5_PATH, + SPA_KEY_API_BLUEZ5_ADDRESS, + NULL); + + /* set node properties for other APIs */ + } else { + wp_properties_setf (node_props, PW_KEY_NODE_NAME, "%s/%s", + factory, name); + wp_properties_set (node_props, PW_KEY_NODE_DESCRIPTION, description); + } +} + +static void +start_monitor (WpMonitor *monitor) +{ + g_autoptr (GError) error = NULL; + + if (!wp_monitor_start (monitor, &error)) { + g_message ("Failed to start monitor: %s", error->message); + } +} + +void +wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args) +{ + WpMonitor *monitor; + const gchar *factory = NULL; + WpMonitorFlags flags = 0; + GVariantIter *iter; + + if (!g_variant_lookup (args, "factory", "&s", &factory)) { + g_message ("Failed to load monitor: no 'factory' key specified"); + return; + } + + if (g_variant_lookup (args, "flags", "as", &iter)) { + gchar *flag_str = NULL; + GFlagsValue *flag_val = NULL; + GFlagsClass *flag_class = g_type_class_ref (WP_TYPE_MONITOR_FLAGS); + + while (g_variant_iter_loop (iter, "s", &flag_str)) { + flag_val = g_flags_get_value_by_nick (flag_class, flag_str); + if (flag_val) + flags |= flag_val->value; + } + g_variant_iter_free (iter); + } + + monitor = wp_monitor_new (core, factory, flags); + + g_signal_connect (monitor, "setup-device-props", + (GCallback) setup_device_props, module); + g_signal_connect (monitor, "setup-node-props", + (GCallback) setup_node_props, module); + + wp_module_set_destroy_callback (module, g_object_unref, monitor); + + /* Start the monitor when the connected callback is triggered */ + g_signal_connect_object (core, "remote-state-changed::connected", + (GCallback) start_monitor, monitor, G_CONNECT_SWAPPED); +}