diff --git a/src/calls-plugin.c b/src/calls-plugin.c new file mode 100644 index 0000000..e8859bb --- /dev/null +++ b/src/calls-plugin.c @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2023 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Evangelos Ribeiro Tzaras + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "calls-plugin.h" + +/** + * SECTION:calls-plugin + * @short_description: A plugin for calls + * @Title: CallsPlugin + * + * Holds information about plugins and allows loading/unloading. + */ + +struct _CallsPlugin { + GObject parent_instance; + + PeasPluginInfo *info; + CallsProvider *provider; + + gboolean is_loaded; +}; + +G_DEFINE_TYPE (CallsPlugin, calls_plugin, G_TYPE_OBJECT) + +enum { + PROP_0, + PROP_PLUGIN_INFO, + PROP_NAME, + PROP_DESCRIPTION, + PROP_AUTHORS, + PROP_COPYRIGHT, + PROP_VERSION, + PROP_LOADED, + PROP_PROVIDER, + PROP_LAST_PROP +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +static void +calls_plugin_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsPlugin *self = CALLS_PLUGIN (object); + + switch (prop_id) { + case PROP_PLUGIN_INFO: + self->info = g_value_get_boxed (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + + +static void +calls_plugin_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CallsPlugin *self = CALLS_PLUGIN (object); + + switch (prop_id) { + case PROP_NAME: + g_value_set_string (value, calls_plugin_get_name (self)); + break; + + case PROP_DESCRIPTION: + g_value_set_string (value, calls_plugin_get_description (self)); + break; + + case PROP_AUTHORS: + g_value_set_boxed (value, calls_plugin_get_authors (self)); + break; + + case PROP_COPYRIGHT: + g_value_set_string (value, calls_plugin_get_copyright (self)); + break; + + case PROP_VERSION: + g_value_set_string (value, calls_plugin_get_version (self)); + break; + + case PROP_LOADED: + g_value_set_boolean (value, calls_plugin_is_loaded (self)); + break; + + case PROP_PROVIDER: + g_value_set_object (value, calls_plugin_get_provider (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + + +static void +calls_plugin_dispose (GObject *object) +{ + CallsPlugin *self = CALLS_PLUGIN (object); + + g_clear_object (&self->info); + g_clear_object (&self->provider); + + G_OBJECT_CLASS (calls_plugin_parent_class)->dispose (object); +} + + +static void +calls_plugin_class_init (CallsPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = calls_plugin_dispose; + object_class->get_property = calls_plugin_get_property; + object_class->set_property = calls_plugin_set_property; + + /** + * CallsPlugin:plugin-info: + * + * The #PeasPluginInfo containing information about the plugin + */ + props[PROP_PLUGIN_INFO] = + g_param_spec_boxed ("plugin-info", + "", + "", + PEAS_TYPE_PLUGIN_INFO, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + /** + * CallsPlugin:name: + * + * The name of the plugin + */ + props[PROP_NAME] = + g_param_spec_string ("name", + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * CallsPlugin:description: + * + * A description of the plugin + */ + props[PROP_DESCRIPTION] = + g_param_spec_string ("description", + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * CallsPlugin:authors: + * + * The name of the plugin + */ + props[PROP_AUTHORS] = + g_param_spec_boxed ("authors", + "", + "", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * CallsPlugin:description: + * + * The copyright holder of the plugin + */ + props[PROP_COPYRIGHT] = + g_param_spec_string ("copyright", + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * CallsPlugin:version: + * + * The version of the plugin + */ + props[PROP_VERSION] = + g_param_spec_string ("version", + "", + "", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * CallsPlugin:loaded: + * + * Whether the plugin is loaded. This property is set after the plugin was loaded + * and unset before the plugin was unloaded. This means at notification time + * the e.g. provider property still points to a valid object. + */ + props[PROP_LOADED] = + g_param_spec_boolean ("loaded", + "", + "", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + /** + * CallsPlugin:provider: + * + * The #CallsProvider provided by this plugin, if any + */ + props[PROP_PROVIDER] = + g_param_spec_object ("provider", + "", + "", + CALLS_TYPE_PROVIDER, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} + + +static void +calls_plugin_init (CallsPlugin *self) +{ +} + + +CallsPlugin * +calls_plugin_new (PeasPluginInfo *info) +{ + g_return_val_if_fail (info, NULL); + + return g_object_new (CALLS_TYPE_PLUGIN, + "plugin-info", info, + NULL); +} + + +gboolean +calls_plugin_load (CallsPlugin *self, + GError **error) +{ + PeasEngine *peas = peas_engine_get_default (); + PeasExtension *extension; + + g_return_val_if_fail (CALLS_IS_PLUGIN (self), FALSE); + + if (calls_plugin_is_loaded (self)) + return TRUE; + + if (!peas_engine_load_plugin (peas, self->info)) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Plugin '%s' could not be loaded", + peas_plugin_info_get_module_name (self->info)); + return FALSE; + } + + g_assert (peas_plugin_info_is_loaded (self->info)); + + if (!peas_engine_provides_extension (peas, self->info, CALLS_TYPE_PROVIDER)) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Plugin '%s' does not provide a CallsProvider", + peas_plugin_info_get_module_name (self->info)); + peas_engine_unload_plugin (peas, self->info); + return FALSE; + } + + g_debug ("Successfully loaded plugin '%s'", + peas_plugin_info_get_module_name (self->info)); + + extension = peas_engine_create_extension (peas, self->info, CALLS_TYPE_PROVIDER, NULL); + if (!extension) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not create CallsProvider from plugin '%s'", + peas_plugin_info_get_module_name (self->info)); + peas_engine_unload_plugin (peas, self->info); + return FALSE; + } + + self->provider = CALLS_PROVIDER (extension); + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_PROVIDER]); + + self->is_loaded = TRUE; + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LOADED]); + + return TRUE; +} + + +gboolean +calls_plugin_unload (CallsPlugin *self, + GError **error) +{ + PeasEngine *peas = peas_engine_get_default (); + + g_return_val_if_fail (CALLS_IS_PLUGIN (self), FALSE); + + if (!calls_plugin_is_loaded (self)) + return TRUE; + + if (!peas_engine_unload_plugin (peas, self->info)) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Could not unload plugin '%s'", + peas_plugin_info_get_module_name (self->info)); + return FALSE; + } + + self->is_loaded = FALSE; + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LOADED]); + g_clear_object (&self->provider); + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_PROVIDER]); + + return TRUE; +} + + +gboolean +calls_plugin_is_loaded (CallsPlugin *self) +{ + g_return_val_if_fail (CALLS_IS_PLUGIN (self), FALSE); + + return self->is_loaded; +} + + +CallsProvider * +calls_plugin_get_provider (CallsPlugin *self) +{ + g_return_val_if_fail (CALLS_IS_PLUGIN (self), NULL); + + return self->provider; +} + + +const char * +calls_plugin_get_module_name (CallsPlugin *self) +{ + g_return_val_if_fail (CALLS_IS_PLUGIN (self), NULL); + g_return_val_if_fail (self->info, NULL); + + return peas_plugin_info_get_module_name (self->info); +} + +const char * +calls_plugin_get_name (CallsPlugin *self) +{ + g_return_val_if_fail (CALLS_IS_PLUGIN (self), NULL); + g_return_val_if_fail (self->info, NULL); + + return peas_plugin_info_get_name (self->info); +} + + +const char * +calls_plugin_get_description (CallsPlugin *self) +{ + g_return_val_if_fail (CALLS_IS_PLUGIN (self), NULL); + g_return_val_if_fail (self->info, NULL); + + return peas_plugin_info_get_name (self->info); +} + + +const char ** +calls_plugin_get_authors (CallsPlugin *self) +{ + g_return_val_if_fail (CALLS_IS_PLUGIN (self), NULL); + g_return_val_if_fail (self->info, NULL); + + return peas_plugin_info_get_authors (self->info); +} + + +const char * +calls_plugin_get_copyright (CallsPlugin *self) +{ + g_return_val_if_fail (CALLS_IS_PLUGIN (self), NULL); + g_return_val_if_fail (self->info, NULL); + + return peas_plugin_info_get_copyright (self->info); +} + +const char * +calls_plugin_get_version (CallsPlugin *self) +{ + g_return_val_if_fail (CALLS_IS_PLUGIN (self), NULL); + g_return_val_if_fail (self->info, NULL); + + return peas_plugin_info_get_version (self->info); +} diff --git a/src/calls-plugin.h b/src/calls-plugin.h new file mode 100644 index 0000000..01bb661 --- /dev/null +++ b/src/calls-plugin.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 Purism SPC + * + * This file is part of Calls. + * + * Calls is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calls is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calls. If not, see . + * + * Author: Evangelos Ribeiro Tzaras + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "calls-provider.h" + +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_PLUGIN (calls_plugin_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsPlugin, calls_plugin, CALLS, PLUGIN, GObject) + +CallsPlugin *calls_plugin_new (PeasPluginInfo *info); +gboolean calls_plugin_load (CallsPlugin *self, + GError **error); +gboolean calls_plugin_unload (CallsPlugin *self, + GError **error); +gboolean calls_plugin_is_loaded (CallsPlugin *self); +gboolean calls_plugin_has_provider (CallsPlugin *self); +CallsProvider *calls_plugin_get_provider (CallsPlugin *self); +const char *calls_plugin_get_module_name (CallsPlugin *self); +const char *calls_plugin_get_name (CallsPlugin *self); +const char *calls_plugin_get_description (CallsPlugin *self); +const char **calls_plugin_get_authors (CallsPlugin *self); +const char *calls_plugin_get_copyright (CallsPlugin *self); +const char *calls_plugin_get_version (CallsPlugin *self); + +G_END_DECLS diff --git a/src/meson.build b/src/meson.build index 571c40a..e1d747f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -116,6 +116,7 @@ calls_sources = files([ 'calls-new-call-box.c', 'calls-new-call-box.h', 'calls-notifier.c', 'calls-notifier.h', 'calls-origin.c', 'calls-origin.h', + 'calls-plugin.c', 'calls-plugin.h', 'calls-provider.c', 'calls-provider.h', 'calls-record-store.c', 'calls-record-store.h', 'calls-ringer.c', 'calls-ringer.h',