Skip to content
Snippets Groups Projects
module.c 3.94 KiB
Newer Older
/* WirePlumber
 *
 * Copyright © 2019 Collabora Ltd.
 *    @author George Kiagiadakis <george.kiagiadakis@collabora.com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "module.h"
#include "error.h"
#include <gmodule.h>

#define WP_MODULE_INIT_SYMBOL "wireplumber__module_init"

typedef void (*WpModuleInitFunc) (WpModule *, WpCore *, GVariant *);

struct _WpModule
{
  GObject parent;
  GVariant *properties;
  GDestroyNotify destroy;
  gpointer destroy_data;
};

G_DEFINE_TYPE (WpModule, wp_module, G_TYPE_OBJECT)

static void
wp_module_init (WpModule * self)
{
}

static void
wp_module_finalize (GObject * object)
{
  WpModule *self = WP_MODULE (object);

  g_debug ("WpModule:%p unloading module", self);

  if (self->destroy)
    self->destroy (self->destroy_data);
  g_clear_pointer (&self->properties, g_variant_unref);
  g_weak_ref_clear (&self->core);

  G_OBJECT_CLASS (wp_module_parent_class)->finalize (object);
}

static void
wp_module_class_init (WpModuleClass * klass)
{
  GObjectClass * object_class = (GObjectClass *) klass;
  object_class->finalize = wp_module_finalize;
}

static const gchar *
get_module_dir (void)
{
  static const gchar *module_dir = NULL;
  if (!module_dir) {
    module_dir = g_getenv ("WIREPLUMBER_MODULE_DIR");
    if (!module_dir)
      module_dir = WIREPLUMBER_DEFAULT_MODULE_DIR;
  }
  return module_dir;
}

static gboolean
wp_module_load_c (WpModule * self, WpCore * core,
    const gchar * module_name, GVariant * args, GError ** error)
{
  g_autofree gchar *module_path = NULL;
  GModule *gmodule;
  gpointer module_init;
  GVariantDict properties;

  module_path = g_module_build_path (get_module_dir (), module_name);
  gmodule = g_module_open (module_path, G_MODULE_BIND_LOCAL);
  if (!gmodule) {
    g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
        "Failed to open module %s: %s", module_path, g_module_error ());
    return FALSE;
  }

  if (!g_module_symbol (gmodule, WP_MODULE_INIT_SYMBOL, &module_init)) {
    g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
        "Failed to locate symbol " WP_MODULE_INIT_SYMBOL " in %s",
        module_path);
    g_module_close (gmodule);
    return FALSE;
  }

  g_variant_dict_init (&properties, NULL);
  g_variant_dict_insert (&properties, "module.name", "s", module_name);
  g_variant_dict_insert (&properties, "module.abi", "s", "C");
  g_variant_dict_insert (&properties, "module.path", "s", module_path);
  if (args) {
    g_variant_take_ref (args);
    g_variant_dict_insert_value (&properties, "module.args", args);
  }
  self->properties = g_variant_ref_sink (g_variant_dict_end (&properties));

  ((WpModuleInitFunc) module_init) (self, core, args);

  if (args)
    g_variant_unref (args);

  return TRUE;
}

WpModule *
wp_module_load (WpCore * core, const gchar * abi,
    const gchar * module_name, GVariant * args, GError ** error)
{
  g_autoptr (WpModule) module = NULL;

  module = g_object_new (WP_TYPE_MODULE, NULL);
  g_weak_ref_init (&module->core, core);

  g_info ("WpModule:%p loading module %s (ABI: %s)", module, module_name, abi);

  if (!g_strcmp0 (abi, "C")) {
    if (!wp_module_load_c (module, core, module_name, args, error))
      return NULL;
  } else {
    g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
        "unknown module ABI %s", abi);
    return NULL;
  }

  wp_core_register_global (core, WP_GLOBAL_MODULE, g_object_ref (module),
      g_object_unref);
  return module;
}

GVariant *
wp_module_get_properties (WpModule * self)
{
  return self->properties;
}

/**
 * wp_module_get_core:
 * @self: the module
 *
 * Returns: (transfer full): the core on which this module is registered
 */
WpCore *
wp_module_get_core (WpModule * self)
{
  return g_weak_ref_get (&self->core);
}

void
wp_module_set_destroy_callback (WpModule * self, GDestroyNotify callback,
    gpointer data)
{
  g_return_if_fail (self->destroy == NULL);
  self->destroy = callback;
  self->destroy_data = data;
}