/* WirePlumber * * Copyright © 2021 Asymptotic * @author Arun Raghavan * * SPDX-License-Identifier: MIT */ #include "module.h" #include "log.h" #include #include #include #include #include WP_DEFINE_LOCAL_LOG_TOPIC ("wp-module") /*! \defgroup wpimplmodule WpImplModule */ /*! * \struct WpImplModule * \since 0.4.2 * * Used to load PipeWire modules within the WirePlumber process. This is * slightly different from other objects in that the module is not exported to * PipeWire, but it may create an export objects itself. */ struct _WpImplModule { GObject parent; GWeakRef core; gchar *name; gchar *args; WpProperties *props; /* only used during module load */ struct pw_impl_module *pw_impl_module; }; G_DEFINE_TYPE (WpImplModule, wp_impl_module, G_TYPE_OBJECT); enum { PROP_0, PROP_CORE, PROP_NAME, PROP_ARGUMENTS, PROP_PROPERTIES, PROP_PW_IMPL_MODULE, }; static void wp_impl_module_init (WpImplModule * self) { g_weak_ref_init (&self->core, NULL); self->name = NULL; self->args = NULL; self->props = NULL; self->pw_impl_module = NULL; } static void wp_impl_module_constructed (GObject * object) { WpImplModule *self = WP_IMPL_MODULE (object); WpCore *core = g_weak_ref_get (&self->core); struct pw_context *context = core ? wp_core_get_pw_context (core) : NULL; struct pw_properties *props = NULL; if (!core || !context) { g_warning ("Tried to load module on unconnected core"); return; } if (!self->name) { g_warning ("Invalid name while loading warnings"); return; } if (self->props) props = wp_properties_to_pw_properties (self->props); self->pw_impl_module = pw_context_load_module (context, self->name, self->args, props); if (self->pw_impl_module && self->props) { /* With the module loaded, properties are just passthrough now */ wp_properties_unref (self->props); self->props = NULL; } G_OBJECT_CLASS (wp_impl_module_parent_class)->constructed (object); } static void wp_impl_module_finalize (GObject * object) { WpImplModule *self = WP_IMPL_MODULE (object); g_weak_ref_clear (&self->core); if (self->pw_impl_module) pw_impl_module_destroy (self->pw_impl_module); g_free (self->name); g_free (self->args); if (self->props) wp_properties_unref (self->props); } static void wp_impl_module_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { WpImplModule *self = WP_IMPL_MODULE (object); switch (prop_id) { case PROP_CORE: g_value_set_pointer (value, g_weak_ref_get (&self->core)); break; case PROP_NAME: g_value_set_string (value, self->name); break; case PROP_ARGUMENTS: g_value_set_string (value, self->args); break; case PROP_PROPERTIES: if (self->pw_impl_module) { const struct pw_properties *props = pw_impl_module_get_properties (self->pw_impl_module); /* Should we just wrap instead of copying? */ if (props) g_value_set_boxed (value, wp_properties_new_copy (props)); else g_value_set_boxed (value, NULL); } else { g_value_set_boxed (value, self->props); } break; case PROP_PW_IMPL_MODULE: g_value_set_pointer (value, self->pw_impl_module); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void wp_impl_module_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { WpImplModule *self = WP_IMPL_MODULE (object); WpProperties *props; switch (prop_id) { case PROP_CORE: g_weak_ref_set (&self->core, g_value_get_pointer (value)); break; case PROP_NAME: g_free (self->name); self->name = g_value_dup_string (value); break; case PROP_ARGUMENTS: g_free (self->args); self->args = g_value_dup_string (value); break; case PROP_PROPERTIES: props = g_value_get_boxed (value); if (props && self->pw_impl_module) { pw_impl_module_update_properties (self->pw_impl_module, wp_properties_peek_dict (props)); } else { if (props) self->props = wp_properties_ref (props); else self->props = NULL; } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void wp_impl_module_class_init (WpImplModuleClass * klass) { GObjectClass *gobject_class = (GObjectClass *) klass; gobject_class->constructed = wp_impl_module_constructed; gobject_class->finalize = wp_impl_module_finalize; gobject_class->get_property = wp_impl_module_get_property; gobject_class->set_property = wp_impl_module_set_property; g_object_class_install_property (gobject_class, PROP_CORE, g_param_spec_pointer ("core", "Core", "The WirePlumber core", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_NAME, g_param_spec_string ("name", "Name", "The name of the PipeWire module", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_ARGUMENTS, g_param_spec_string ("arguments", "Arguments", "The arguments to provide to the module while loading", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_PROPERTIES, g_param_spec_boxed ("properties", "Properties", "Properties of the module", WP_TYPE_PROPERTIES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_PW_IMPL_MODULE, g_param_spec_pointer ("pw-impl-module", "Underlying pw_impl_module", "Pointer to the underlying pw_impl_module structure for the module", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); } /*! * \brief Loads a PipeWire module into the WirePlumber process * * \ingroup wpimplmodule * \since 0.4.2 * \param core (transfer none): The WirePlumber core * \param name (transfer none): the name of the module to load * \param arguments (nullable) (transfer none): arguments to be passed to the module * \param properties (nullable) (transfer none): additional properties to be * provided to the module * \returns (nullable) (transfer full): the WpImplModule for the module that * was loaded on success, %NULL on failure. */ WpImplModule * wp_impl_module_load (WpCore * core, const gchar * name, const gchar * arguments, WpProperties * properties) { WpImplModule *module = WP_IMPL_MODULE ( g_object_new (WP_TYPE_IMPL_MODULE, "core", core, "name", name, "arguments", arguments, "properties", properties, NULL) ); if (!module->pw_impl_module) { /* Module loading failed, free and return */ g_object_unref (module); return NULL; } return module; } /*! * \brief Loads a PipeWire module with arguments from file into the WirePlumber process * * \ingroup wpimplmodule * \since 0.4.15 * \param core (transfer none): The WirePlumber core * \param name (transfer none): the name of the module to load * \param filename (transfer none): filename to be used as arguments * \param properties (nullable) (transfer none): additional properties to be * provided to the module * \returns (nullable) (transfer full): the WpImplModule for the module that * was loaded on success, %NULL on failure. */ WpImplModule * wp_impl_module_load_file (WpCore * core, const gchar * name, const gchar * filename, WpProperties * properties) { char *config = ""; int fd = open(filename, O_RDONLY); if (fd < 0) { g_warning("Failed to open config file %s: %m", filename); return NULL; } struct stat stats; int err = fstat(fd, &stats); if (err < 0) { g_warning("Failed to stat config file %s: %m", filename); close(fd); return NULL; } config = mmap(NULL, stats.st_size, PROT_READ, MAP_SHARED, fd, 0); if (config == MAP_FAILED){ g_warning("Failed to mmap config file %s: %m", filename); close(fd); return NULL; } close(fd); WpImplModule *module = WP_IMPL_MODULE ( g_object_new (WP_TYPE_IMPL_MODULE, "core", core, "name", name, "arguments", config, "properties", properties, NULL) ); munmap(config, stats.st_size); if (!module->pw_impl_module) { /* Module loading failed, free and return */ g_object_unref (module); return NULL; } return module; }