diff --git a/src/calls-application.c b/src/calls-application.c index 2ba5e2d..6cef3a5 100644 --- a/src/calls-application.c +++ b/src/calls-application.c @@ -34,12 +34,12 @@ #include "calls-contacts.h" #include "calls-call-window.h" #include "calls-main-window.h" +#include "calls-manager.h" #include "calls-application.h" #define HANDY_USE_UNSTABLE_API #include -#include #include #include @@ -57,8 +57,7 @@ struct _CallsApplication GtkApplication parent_instance; gboolean daemon; - GString *provider_name; - CallsProvider *provider; + CallsManager *manager; CallsRinger *ringer; CallsRecordStore *record_store; CallsContacts *contacts; @@ -95,6 +94,12 @@ handle_local_options (GApplication *application, "set-provider-name", g_variant_new_string (arg)); } + else + { + g_action_group_activate_action (G_ACTION_GROUP (application), + "set-provider-name", + g_variant_new_string (DEFAULT_PROVIDER_PLUGIN)); + } ok = g_variant_dict_contains (options, "daemon"); if (ok) @@ -121,13 +126,14 @@ set_provider_name_action (GSimpleAction *action, GVariant *parameter, gpointer user_data) { - CallsApplication *self = CALLS_APPLICATION (user_data); const gchar *name; name = g_variant_get_string (parameter, NULL); g_return_if_fail (name != NULL); - if (self->provider) + /* FIXME: allow to set a new provider, we need to make sure that the + provider is unloaded correctly from the CallsManager */ + if (calls_manager_get_provider (calls_manager_get_default ()) != NULL) { g_warning ("Cannot set provider name to `%s'" " because provider is already created", @@ -135,10 +141,8 @@ set_provider_name_action (GSimpleAction *action, return; } - g_string_assign (self->provider_name, name); - - g_debug ("Provider name set to `%s'", - self->provider_name->str); + g_debug ("Start loading provider `%s'", name); + calls_manager_set_provider (calls_manager_get_default (), name); } @@ -315,82 +319,11 @@ startup (GApplication *application) } -static void -load_provider_plugin (CallsApplication *self) -{ - const gchar * const name = self->provider_name->str; - PeasEngine *plugins; - PeasPluginInfo *info; - PeasExtension *extension; - - g_assert (self->provider == NULL); - - // Add Calls search path and rescan - plugins = peas_engine_get_default (); - peas_engine_add_search_path (plugins, PLUGIN_LIBDIR, PLUGIN_LIBDIR); - g_debug ("Scanning for plugins in `%s'", PLUGIN_LIBDIR); - - // Find the plugin - info = peas_engine_get_plugin_info (plugins, name); - if (!info) - { - g_critical ("Could not find plugin `%s'", name); - return; - } - - // Possibly load the plugin - if (!peas_plugin_info_is_loaded (info)) - { - g_autoptr(GError) error = NULL; - - peas_engine_load_plugin (plugins, info); - - if (!peas_plugin_info_is_available (info, &error)) - { - if (error) - { - g_critical ("Error loading plugin `%s': %s", - name, error->message); - } - else - { - g_critical ("Could not load plugin `%s'", name); - } - - return; - } - - g_debug ("Loaded plugin `%s'", name); - } - - // Check the plugin provides CallsProvider - if (!peas_engine_provides_extension - (plugins, info, CALLS_TYPE_PROVIDER)) - { - g_critical ("Plugin `%s' does not have a provider extension", - name); - return; - } - - // Get the extension - extension = peas_engine_create_extensionv - (plugins, info, CALLS_TYPE_PROVIDER, 0, NULL); - if (!extension) - { - g_critical ("Could not create provider from plugin `%s'", - name); - return; - } - - g_debug ("Created provider from plugin `%s'", name); - self->provider = CALLS_PROVIDER (extension); -} - - static gboolean start_proper (CallsApplication *self) { GtkApplication *gtk_app; + CallsProvider *provider; if (self->main_window) { @@ -399,24 +332,13 @@ start_proper (CallsApplication *self) gtk_app = GTK_APPLICATION (self); - // Later we will make provider loading/unloaded a dynamic - // process but that will have far-reaching consequences and is - // of no use immediately so for now, we just load one provider - // at startup. We can't put this in the actual startup() method - // though, because we need to be able to set the provider name - // from the command line and we use actions to do that, which - // depend on the application already being started up. - load_provider_plugin (self); - if (!self->provider) - { - g_application_quit (G_APPLICATION (self)); - return FALSE; - } + provider = calls_manager_get_real_provider (calls_manager_get_default ()); + g_assert (provider != NULL); - self->ringer = calls_ringer_new (self->provider); + self->ringer = calls_ringer_new (provider); g_assert (self->ringer != NULL); - self->record_store = calls_record_store_new (self->provider); + self->record_store = calls_record_store_new (provider); g_assert (self->record_store != NULL); self->contacts = calls_contacts_new (); @@ -424,13 +346,13 @@ start_proper (CallsApplication *self) self->main_window = calls_main_window_new (gtk_app, - self->provider, + provider, G_LIST_MODEL (self->record_store), self->contacts); g_assert (self->main_window != NULL); self->call_window = calls_call_window_new - (gtk_app, self->provider); + (gtk_app, provider); g_assert (self->call_window != NULL); return TRUE; @@ -549,7 +471,7 @@ constructed (GObject *object) static void -dispose (GObject *object) +finalize (GObject *object) { CallsApplication *self = (CallsApplication *)object; @@ -557,23 +479,11 @@ dispose (GObject *object) g_clear_object (&self->main_window); g_clear_object (&self->record_store); g_clear_object (&self->ringer); - g_clear_object (&self->provider); G_OBJECT_CLASS (calls_application_parent_class)->dispose (object); } -static void -finalize (GObject *object) -{ - CallsApplication *self = (CallsApplication *)object; - - g_string_free (self->provider_name, TRUE); - - G_OBJECT_CLASS (calls_application_parent_class)->finalize (object); -} - - static void calls_application_class_init (CallsApplicationClass *klass) { @@ -581,7 +491,6 @@ calls_application_class_init (CallsApplicationClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = constructed; - object_class->dispose = dispose; object_class->finalize = finalize; application_class->handle_local_options = handle_local_options; @@ -628,8 +537,6 @@ calls_application_init (CallsApplication *self) }; g_application_add_main_option_entries (G_APPLICATION (self), options); - - self->provider_name = g_string_new (DEFAULT_PROVIDER_PLUGIN); } diff --git a/src/calls-manager.c b/src/calls-manager.c new file mode 100644 index 0000000..269dd5d --- /dev/null +++ b/src/calls-manager.c @@ -0,0 +1,520 @@ +/* + * Copyright (C) 2020 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: Julian Sparber + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "config.h" +#include "calls-manager.h" +#include "enum-types.h" + +#include +#include + +struct _CallsManager +{ + GObject parent_instance; + + CallsProvider *provider; + gchar *provider_name; + CallsOrigin *default_origin; + CallsManagerState state; +}; + +G_DEFINE_TYPE (CallsManager, calls_manager, G_TYPE_OBJECT); + +enum { + PROP_0, + PROP_PROVIDER, + PROP_DEFAULT_ORIGIN, + PROP_STATE, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +enum { + SIGNAL_ORIGIN_ADD, + SIGNAL_ORIGIN_REMOVE, + SIGNAL_CALL_ADD, + SIGNAL_CALL_REMOVE, + /* TODO: currently this event isn't emitted since the plugins don't give use + * a usable error or error message. */ + SIGNAL_ERROR, + SIGNAL_LAST_SIGNAL, +}; +static guint signals [SIGNAL_LAST_SIGNAL]; + +static void +set_state (CallsManager *self, CallsManagerState state) +{ + if (self->state == state) + return; + + self->state = state; + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STATE]); +} + +static CallsProvider * +load_provider (const gchar* name) +{ + g_autoptr (GError) error = NULL; + PeasEngine *plugins; + PeasPluginInfo *info; + PeasExtension *extension; + + // Add Calls search path and rescan + plugins = peas_engine_get_default (); + peas_engine_add_search_path (plugins, PLUGIN_LIBDIR, NULL); + g_debug ("Scanning for plugins in `%s'", PLUGIN_LIBDIR); + + // Find the plugin + info = peas_engine_get_plugin_info (plugins, name); + if (!info) + { + g_debug ("Could not find plugin `%s'", name); + return NULL; + } + + // Possibly load the plugin + if (!peas_plugin_info_is_loaded (info)) + { + peas_engine_load_plugin (plugins, info); + + if (!peas_plugin_info_is_available (info, &error)) + { + g_debug ("Error loading plugin `%s': %s", name, error->message); + return NULL; + } + + g_debug ("Loaded plugin `%s'", name); + } + + // Check the plugin provides CallsProvider + if (!peas_engine_provides_extension (plugins, info, CALLS_TYPE_PROVIDER)) + { + g_debug ("Plugin `%s' does not have a provider extension", name); + return NULL; + } + + // Get the extension + extension = peas_engine_create_extensionv (plugins, info, CALLS_TYPE_PROVIDER, 0, NULL); + if (!extension) + { + g_debug ("Could not create provider from plugin `%s'", name); + return NULL; + } + + g_debug ("Created provider from plugin `%s'", name); + return CALLS_PROVIDER (extension); +} + +static void +add_call (CallsManager *self, CallsCall *call, CallsOrigin *origin) +{ + g_return_if_fail (CALLS_IS_MANAGER (self)); + g_return_if_fail (CALLS_IS_ORIGIN (origin)); + g_return_if_fail (CALLS_IS_CALL (call)); + + g_signal_emit (self, signals[SIGNAL_CALL_ADD], 0, call, origin); +} + +static void +remove_call (CallsManager *self, CallsCall *call, gchar *reason, CallsOrigin *origin) +{ + g_return_if_fail (CALLS_IS_MANAGER (self)); + g_return_if_fail (CALLS_IS_ORIGIN (origin)); + g_return_if_fail (CALLS_IS_CALL (call)); + + /* We ignore the reason for now, because it doesn't give any usefull information */ + g_signal_emit (self, signals[SIGNAL_CALL_REMOVE], 0, call, origin); +} + +static void +add_origin (CallsManager *self, CallsOrigin *origin, CallsProvider *provider) +{ + g_autoptr (GList) calls = NULL; + GList *c; + g_return_if_fail (CALLS_IS_ORIGIN (origin)); + + calls = calls_origin_get_calls (origin); + + g_signal_connect_swapped (origin, "call-added", G_CALLBACK (add_call), self); + g_signal_connect_swapped (origin, "call-removed", G_CALLBACK (remove_call), self); + + for (c = calls; c != NULL; c = c->next) + { + add_call (self, c->data, origin); + } + + set_state (self, CALLS_MANAGER_STATE_READY); + g_signal_emit (self, signals[SIGNAL_ORIGIN_ADD], 0, origin); +} + +static void +remove_origin (CallsManager *self, CallsOrigin *origin, CallsProvider *provider) +{ + g_autoptr (GList) origins = NULL; + g_autoptr (GList) calls = NULL; + GList *c; + g_return_if_fail (CALLS_IS_ORIGIN (origin)); + + g_signal_handlers_disconnect_by_data (origin, self); + + calls = calls_origin_get_calls (origin); + for (c = calls; c != NULL; c = c->next) + { + remove_call (self, c->data, NULL, origin); + } + + if (self->default_origin == origin) + calls_manager_set_default_origin (self, NULL); + + origins = calls_manager_get_origins (self); + if (origins == NULL) + set_state (self, CALLS_MANAGER_STATE_NO_ORIGIN); + + g_signal_emit (self, signals[SIGNAL_ORIGIN_REMOVE], 0, origin); +} + +static void +remove_provider (CallsManager *self) +{ + PeasEngine *engine = peas_engine_get_default (); + PeasPluginInfo *plugin = peas_engine_get_plugin_info (engine, self->provider_name); + g_autoptr (GList) origins = NULL; + GList *o; + + g_debug ("Remove provider: %s", calls_provider_get_name (self->provider)); + g_signal_handlers_disconnect_by_data (self->provider, self); + + origins = calls_provider_get_origins (self->provider); + + for (o = origins; o != NULL; o = o->next) + { + remove_origin (self, o->data, self->provider); + } + + g_clear_pointer (&self->provider_name, g_free); + peas_engine_unload_plugin (engine, plugin); + set_state (self, CALLS_MANAGER_STATE_NO_PROVIDER); +} + +static void +add_provider (CallsManager *self, const gchar *name) +{ + g_autoptr (GList) origins = NULL; + GList *o; + + /* We could eventually enable more then one provider, but for now let's use + only one */ + if (self->provider != NULL) + remove_provider (self); + + if (name == NULL) + return; + + self->provider = load_provider (name); + + if (self->provider == NULL) { + set_state (self, CALLS_MANAGER_STATE_NO_PLUGIN); + return; + } + + set_state (self, CALLS_MANAGER_STATE_NO_ORIGIN); + + origins = calls_provider_get_origins (self->provider); + + g_signal_connect_swapped (self->provider, "origin-added", G_CALLBACK (add_origin), self); + g_signal_connect_swapped (self->provider, "origin-removed", G_CALLBACK (remove_origin), self); + + for (o = origins; o != NULL; o = o->next) + { + add_origin (self, o->data, self->provider); + } + + self->provider_name = g_strdup (name); +} + +static void +calls_manager_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + CallsManager *self = CALLS_MANAGER (object); + + switch (property_id) { + case PROP_PROVIDER: + g_value_set_string (value, calls_manager_get_provider (self)); + break; + + case PROP_DEFAULT_ORIGIN: + g_value_set_object (value, calls_manager_get_default_origin (self)); + break; + + case PROP_STATE: + g_value_set_enum (value, calls_manager_get_state (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +calls_manager_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsManager *self = CALLS_MANAGER (object); + + switch (property_id) { + case PROP_PROVIDER: + calls_manager_set_provider (self, g_value_get_string (value)); + break; + + case PROP_DEFAULT_ORIGIN: + calls_manager_set_default_origin (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +calls_manager_finalize (GObject *object) +{ + CallsManager *self = CALLS_MANAGER (object); + + g_clear_object (&self->provider); + g_clear_pointer (&self->provider_name, g_free); + + G_OBJECT_CLASS (calls_manager_parent_class)->finalize (object); +} + + +static void +calls_manager_class_init (CallsManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = calls_manager_get_property; + object_class->set_property = calls_manager_set_property; + object_class->finalize = calls_manager_finalize; + + signals[SIGNAL_ORIGIN_ADD] = + g_signal_new ("origin-add", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, + 1, + CALLS_TYPE_ORIGIN); + + signals[SIGNAL_ORIGIN_REMOVE] = + g_signal_new ("origin-remove", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, + 1, + CALLS_TYPE_ORIGIN); + + signals[SIGNAL_CALL_ADD] = + g_signal_new ("call-add", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, + 2, + CALLS_TYPE_CALL, + CALLS_TYPE_ORIGIN); + + signals[SIGNAL_CALL_REMOVE] = + g_signal_new ("call-remove", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, + 2, + CALLS_TYPE_CALL, + CALLS_TYPE_ORIGIN); + + signals[SIGNAL_ERROR] = + g_signal_new ("error", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, + 1, + G_TYPE_STRING); + + props[PROP_PROVIDER] = g_param_spec_string ("provider", + "provider", + "The name of the currently loaded provider", + NULL, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + props[PROP_STATE] = g_param_spec_enum ("state", + "state", + "The state of the Manager", + CALLS_TYPE_MANAGER_STATE, + CALLS_MANAGER_STATE_NO_PROVIDER, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY); + + props[PROP_DEFAULT_ORIGIN] = g_param_spec_object ("default-origin", + "default origin", + "The default origin, if any", + CALLS_TYPE_ORIGIN, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} + + +static void +calls_manager_init (CallsManager *self) +{ + self->state = CALLS_MANAGER_STATE_NO_PROVIDER; + self->provider_name = NULL; +} + + +CallsManager * +calls_manager_new (void) +{ + return g_object_new (CALLS_TYPE_MANAGER, NULL); +} + +CallsManager * +calls_manager_get_default (void) +{ + static CallsManager *instance; + + if (instance == NULL) { + instance = calls_manager_new (); + g_object_add_weak_pointer (G_OBJECT (instance), (gpointer *)&instance); + } + return instance; +} + +const gchar * +calls_manager_get_provider (CallsManager *self) +{ + g_return_val_if_fail (CALLS_IS_MANAGER (self), NULL); + + return self->provider_name; +} + +void +calls_manager_set_provider (CallsManager *self, const gchar *name) +{ + g_return_if_fail (CALLS_IS_MANAGER (self)); + + if (self->provider != NULL && g_strcmp0 (calls_provider_get_name (self->provider), name) == 0) + return; + + add_provider (self, name); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_PROVIDER]); +} + +/* FIXME: This function should be removed since we don't want to hand out the + provider */ +CallsProvider * +calls_manager_get_real_provider (CallsManager *self) +{ + g_return_val_if_fail (CALLS_IS_MANAGER (self), NULL); + + return self->provider; +} + +CallsManagerState +calls_manager_get_state (CallsManager *self) +{ + g_return_val_if_fail (CALLS_IS_MANAGER (self), CALLS_MANAGER_STATE_UNKNOWN); + + return self->state; +} + +GList * +calls_manager_get_origins (CallsManager *self) +{ + g_return_val_if_fail (CALLS_IS_MANAGER (self), NULL); + + if (self->provider == NULL) + return NULL; + + return calls_provider_get_origins (self->provider); +} + +GList * +calls_manager_get_calls (CallsManager *self) +{ + g_autoptr (GList) origins = NULL; + g_autoptr (GList) calls = NULL; + GList *o; + + g_return_val_if_fail (CALLS_IS_MANAGER (self), NULL); + + origins = calls_manager_get_origins (self); + + for (o = origins; o != NULL; o = o->next) + calls = g_list_concat (calls, calls_origin_get_calls (o->data)); + + return g_steal_pointer (&calls); +} + +CallsOrigin * +calls_manager_get_default_origin (CallsManager *self) +{ + g_return_val_if_fail (CALLS_IS_MANAGER (self), NULL); + + return self->default_origin; +} + +void +calls_manager_set_default_origin (CallsManager *self, + CallsOrigin *origin) +{ + g_return_if_fail (CALLS_IS_MANAGER (self)); + + if (self->default_origin == origin) + return; + + g_clear_object (&self->default_origin); + + if (origin) + self->default_origin = g_object_ref (origin); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DEFAULT_ORIGIN]); +} diff --git a/src/calls-manager.h b/src/calls-manager.h new file mode 100644 index 0000000..8312ce5 --- /dev/null +++ b/src/calls-manager.h @@ -0,0 +1,64 @@ +/* calls-manager.c + * + * Copyright (C) 2020 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 . + * + * Authors: Julian Sparber + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "calls-provider.h" +#include "calls-origin.h" + +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_MANAGER (calls_manager_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsManager, calls_manager, CALLS, MANAGER, GObject) + +typedef enum +{ + CALLS_MANAGER_STATE_UNKNOWN = 1, + CALLS_MANAGER_STATE_NO_PLUGIN, + CALLS_MANAGER_STATE_NO_PROVIDER, + CALLS_MANAGER_STATE_NO_ORIGIN, + CALLS_MANAGER_STATE_READY, +} CallsManagerState; + + +CallsManager *calls_manager_new (void); +CallsManager *calls_manager_get_default (void); +const gchar *calls_manager_get_provider (CallsManager *self); +void calls_manager_set_provider (CallsManager *self, + const gchar *name); +CallsProvider *calls_manager_get_real_provider (CallsManager *self); +CallsManagerState calls_manager_get_state (CallsManager *self); +GList *calls_manager_get_origins (CallsManager *self); +GList *calls_manager_get_calls (CallsManager *self); +void calls_manager_dial (CallsManager *self, + CallsOrigin *origin, + const gchar *target); +CallsOrigin *calls_manager_get_default_origin (CallsManager *self); +void calls_manager_set_default_origin (CallsManager *self, + CallsOrigin *origin); + +G_END_DECLS diff --git a/src/meson.build b/src/meson.build index b736268..df8e7b4 100644 --- a/src/meson.build +++ b/src/meson.build @@ -89,6 +89,7 @@ calls_sources = files(['calls-message-source.c', 'calls-message-source.h', 'calls-contacts.c', 'calls-contacts.h', 'calls-best-match.c', 'calls-best-match.h', 'calls-in-app-notification.c', 'calls-in-app-notification.h', + 'calls-manager.c', 'calls-manager.h', ]) calls_config_data = config_data @@ -106,7 +107,7 @@ config_h = vcs_tag( output: 'config.h', ) -calls_enum_headers = files(['calls-call.h']) +calls_enum_headers = files(['calls-call.h', 'calls-manager.h']) calls_enum_sources = gnome.mkenums_simple('enum-types', sources : calls_enum_headers)