From 74c3987ce1c219b4d8e160dd1ba564086faae55f Mon Sep 17 00:00:00 2001 From: Bob Ham Date: Thu, 1 Nov 2018 11:05:45 +0000 Subject: [PATCH 1/4] src: Generalise Provider object enumeration with calls_enumerate() --- src/calls-call-window.c | 67 +++------- src/calls-enumerate-params.c | 252 +++++++++++++++++++++++++++++++++++ src/calls-enumerate-params.h | 56 ++++++++ src/calls-enumerate.c | 211 +++++++++++++++++++++++++++++ src/calls-enumerate.h | 40 ++++++ src/calls-main-window.c | 97 +++++--------- src/calls-new-call-box.c | 43 +++--- src/calls-provider.h | 12 +- src/meson.build | 2 + 9 files changed, 636 insertions(+), 144 deletions(-) create mode 100644 src/calls-enumerate-params.c create mode 100644 src/calls-enumerate-params.h create mode 100644 src/calls-enumerate.c create mode 100644 src/calls-enumerate.h diff --git a/src/calls-call-window.c b/src/calls-call-window.c index f6c124d..4dbe256 100644 --- a/src/calls-call-window.c +++ b/src/calls-call-window.c @@ -29,6 +29,7 @@ #include "calls-call-holder.h" #include "calls-call-selector-item.h" #include "calls-new-call-box.h" +#include "calls-enumerate.h" #include "util.h" #include @@ -231,9 +232,6 @@ add_call (CallsCallWindow *self, g_return_if_fail (CALLS_IS_CALL_WINDOW (self)); g_return_if_fail (CALLS_IS_CALL (call)); - g_signal_connect_swapped (call, "message", - G_CALLBACK (show_message), self); - holder = calls_call_holder_new (call); display = calls_call_holder_get_display (holder); @@ -288,7 +286,7 @@ remove_calls (CallsCallWindow *self) { GList *children, *child; - /* Safely remove the call stack's children. */ + /* Safely remove the call stack's children. */ children = gtk_container_get_children (GTK_CONTAINER (self->call_stack)); for (child = children; child != NULL; child = child->next) gtk_container_remove (GTK_CONTAINER (self->call_stack), @@ -301,57 +299,24 @@ remove_calls (CallsCallWindow *self) } -static void -add_origin_calls (CallsCallWindow *self, CallsOrigin *origin) -{ - GList *calls, *node; - - calls = calls_origin_get_calls (origin); - - for (node = calls; node; node = node->next) - { - add_call (self, CALLS_CALL (node->data)); - } - - g_list_free (calls); -} - - -static void -add_origin (CallsCallWindow *self, CallsOrigin *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); - - add_origin_calls (self, origin); -} - - -static void -add_provider_origins (CallsCallWindow *self, CallsProvider *provider) -{ - GList *origins, *node; - - origins = calls_provider_get_origins (provider); - - for (node = origins; node; node = node->next) - { - add_origin (self, CALLS_ORIGIN (node->data)); - } - - g_list_free (origins); -} - - static void set_provider (CallsCallWindow *self, CallsProvider *provider) { - g_signal_connect_swapped (provider, "origin-added", - G_CALLBACK (add_origin), self); + CallsEnumerateParams *params; - add_provider_origins (self, provider); + params = calls_enumerate_params_new (self); + + calls_enumerate_params_add + (params, CALLS_TYPE_ORIGIN, "call-added", G_CALLBACK (add_call)); + calls_enumerate_params_add + (params, CALLS_TYPE_ORIGIN, "call-removed", G_CALLBACK (remove_call)); + + calls_enumerate_params_add + (params, CALLS_TYPE_CALL, "message", G_CALLBACK (show_message)); + + calls_enumerate (provider, params); + + g_object_unref (params); } static void diff --git a/src/calls-enumerate-params.c b/src/calls-enumerate-params.c new file mode 100644 index 0000000..38bea18 --- /dev/null +++ b/src/calls-enumerate-params.c @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2018 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: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-enumerate-params.h" +#include "calls-provider.h" +#include "calls-origin.h" +#include "calls-call.h" + +#include +#include + + +typedef enum +{ + CALLS_ENUMERATE_PROVIDER, + CALLS_ENUMERATE_ORIGIN, + CALLS_ENUMERATE_CALL, + CALLS_ENUMERATE_LAST +} CallsEnumerateObjectType; + + +struct _CallsEnumerateParams +{ + GObject parent_instance; + + gboolean enumerating; + gpointer user_data; + GHashTable *callbacks[3]; +}; + +G_DEFINE_TYPE (CallsEnumerateParams, calls_enumerate_params, G_TYPE_OBJECT); + +enum { + PROP_0, + PROP_USER_DATA, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +static void +calls_enumerate_params_init (CallsEnumerateParams *self) +{ + unsigned i; + + self->enumerating = TRUE; + + for (i = 0; i < CALLS_ENUMERATE_LAST; ++i) + { + self->callbacks[i] = + g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + } +} + + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsEnumerateParams *self = CALLS_ENUMERATE_PARAMS (object); + + switch (property_id) { + case PROP_USER_DATA: + self->user_data = g_value_get_pointer (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +finalize (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsEnumerateParams *self = CALLS_ENUMERATE_PARAMS (object); + + unsigned i; + for (i = 0; i < CALLS_ENUMERATE_LAST; ++i) + { + g_hash_table_unref (self->callbacks[i]); + } + + parent_class->finalize (object); +} + + +static void +calls_enumerate_params_class_init (CallsEnumerateParamsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = set_property; + object_class->finalize = finalize; + + props[PROP_USER_DATA] = + g_param_spec_pointer ("user-data", + _("User data"), + _("The pointer to be provided as user data for signal connections"), + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} + + +CallsEnumerateParams * +calls_enumerate_params_new (gpointer user_data) +{ + return g_object_new (CALLS_TYPE_ENUMERATE_PARAMS, + "user-data", user_data, + NULL); +} + + +gpointer +calls_enumerate_params_get_user_data (CallsEnumerateParams *self) +{ + return self->user_data; +} + + +gboolean +calls_enumerate_params_get_enumerating (CallsEnumerateParams *self) +{ + return self->enumerating; +} + + +void +calls_enumerate_params_set_enumerating (CallsEnumerateParams *self, + gboolean enumerating) +{ + self->enumerating = enumerating; +} + + +static GHashTable * +lookup_callbacks (CallsEnumerateParams *self, + GType obj_type) +{ + const GType obj_gtypes[3] = + { + CALLS_TYPE_PROVIDER, + CALLS_TYPE_ORIGIN, + CALLS_TYPE_CALL + }; + unsigned i; + + for (i = 0; i < CALLS_ENUMERATE_LAST; ++i) + { + if (g_type_is_a (obj_type, obj_gtypes[i])) + { + return self->callbacks[i]; + } + } + + g_error ("Unknown GType `%s' converting to Provider enumeration object type", + g_type_name (obj_type)); +} + + +gboolean +calls_enumerate_params_have_callbacks (CallsEnumerateParams *self, + GType obj_type) +{ + GHashTable * const callbacks = lookup_callbacks (self, obj_type); + return g_hash_table_size (callbacks) > 0; +} + + +gboolean +calls_enumerate_params_add (CallsEnumerateParams *self, + GType obj_type, + const gchar *detail, + GCallback callback) +{ + GHashTable * const callbacks = lookup_callbacks (self, obj_type); + return g_hash_table_insert (callbacks, g_strdup (detail), callback); +} + + +struct _CallsEnumerateConnectData +{ + gpointer instance; + gpointer user_data; +}; +typedef struct _CallsEnumerateConnectData CallsEnumerateConnectData; + + +static void +callbacks_connect (const gchar *detail, + GCallback callback, + CallsEnumerateConnectData *data) +{ + g_signal_connect_swapped (data->instance, + detail, + callback, + data->user_data); +} + + +void +calls_enumerate_params_connect (CallsEnumerateParams *self, + GObject *object) +{ + GHashTable * const callbacks = + lookup_callbacks (self, G_TYPE_FROM_INSTANCE (object)); + CallsEnumerateConnectData data; + + data.instance = object; + data.user_data = self->user_data; + + g_hash_table_foreach (callbacks, + (GHFunc)callbacks_connect, + &data); +} + + +GCallback +calls_enumerate_params_lookup (CallsEnumerateParams *self, + GType obj_type, + const gchar *detail) +{ + GHashTable * const callbacks = lookup_callbacks (self, obj_type); + return g_hash_table_lookup (callbacks, detail); +} diff --git a/src/calls-enumerate-params.h b/src/calls-enumerate-params.h new file mode 100644 index 0000000..28b782b --- /dev/null +++ b/src/calls-enumerate-params.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018 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: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_ENUMERATE_PARAMS_H__ +#define CALLS_ENUMERATE_PARAMS_H__ + +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_ENUMERATE_PARAMS (calls_enumerate_params_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsEnumerateParams, calls_enumerate_params, + CALLS, ENUMERATE_PARAMS, GObject); + +CallsEnumerateParams *calls_enumerate_params_new (gpointer user_data); +gpointer calls_enumerate_params_get_user_data (CallsEnumerateParams *self); +gboolean calls_enumerate_params_have_callbacks (CallsEnumerateParams *self, + GType obj_type); +gboolean calls_enumerate_params_get_enumerating (CallsEnumerateParams *self); +void calls_enumerate_params_set_enumerating (CallsEnumerateParams *self, + gboolean enuming); +gboolean calls_enumerate_params_add (CallsEnumerateParams *self, + GType obj_type, + const gchar *detail, + GCallback callback); +void calls_enumerate_params_connect (CallsEnumerateParams *self, + GObject *object); +GCallback calls_enumerate_params_lookup (CallsEnumerateParams *self, + GType obj_type, + const gchar *detail); + +G_END_DECLS + +#endif /* CALLS_ENUMERATE_PARAMS_H__ */ diff --git a/src/calls-enumerate.c b/src/calls-enumerate.c new file mode 100644 index 0000000..e7d263c --- /dev/null +++ b/src/calls-enumerate.c @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2018 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: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-enumerate.h" +#include "calls-origin.h" +#include "calls-call.h" + +#include + + + +typedef void (*CallAddedCallback) (gpointer user_data, + CallsCall *call, + CallsOrigin *origin); + +static void +enum_call_added_cb (CallsOrigin *origin, + CallsCall *call, + CallsEnumerateParams *params) +{ + // Call call-added signal + if (calls_enumerate_params_get_enumerating (params)) + { + CallAddedCallback call_added_cb; + + call_added_cb = (CallAddedCallback) + calls_enumerate_params_lookup (params, + CALLS_TYPE_ORIGIN, + "call-added"); + if (call_added_cb) + { + gpointer user_data = calls_enumerate_params_get_user_data (params); + call_added_cb (user_data, call, origin); + } + } + + // Connect user's callbacks + calls_enumerate_params_connect (params, G_OBJECT (call)); +} + + +static void +enum_origin_calls (CallsOrigin *origin, + CallsEnumerateParams *params) +{ + GList *calls, *node; + + calls = calls_origin_get_calls (origin); + + for (node = calls; node; node = node->next) + { + enum_call_added_cb (origin, + CALLS_CALL (node->data), + params); + } + + g_list_free (calls); +} + + +typedef void (*OriginAddedCallback) (gpointer user_data, + CallsOrigin *origin, + CallsProvider *provider); + +static void +enum_origin_added_cb (CallsProvider *provider, + CallsOrigin *origin, + CallsEnumerateParams *params) +{ + gboolean add_callback; + + // Call origin-added signal + if (calls_enumerate_params_get_enumerating (params)) + { + OriginAddedCallback origin_added_cb; + + origin_added_cb = (OriginAddedCallback) + calls_enumerate_params_lookup (params, + CALLS_TYPE_PROVIDER, + "origin-added"); + if (origin_added_cb) + { + gpointer user_data = calls_enumerate_params_get_user_data (params); + origin_added_cb (user_data, origin, provider); + } + } + + // Connect user's callbacks + calls_enumerate_params_connect (params, G_OBJECT (origin)); + + // We add a callback for ourselves if we have to set callbacks on + // anything lower in the hierarchy in future + add_callback = + calls_enumerate_params_have_callbacks (params, CALLS_TYPE_CALL); + + if (add_callback) + { + g_object_ref (params); + g_signal_connect_data (origin, + "call-added", + G_CALLBACK (enum_call_added_cb), + params, + (GClosureNotify)g_object_unref, + 0); + + } + + // We enumerate if we've added callbacks and if there's the specific + // "call-added" callback for this level + if (add_callback + || + calls_enumerate_params_lookup (params, CALLS_TYPE_ORIGIN, + "call-added")) + { + enum_origin_calls (origin, params); + } +} + + +static void +enum_provider_origins (CallsProvider *provider, + CallsEnumerateParams *params) +{ + GList *origins, *node; + + origins = calls_provider_get_origins (provider); + + for (node = origins; node; node = node->next) + { + enum_origin_added_cb (provider, + CALLS_ORIGIN (node->data), + params); + } + + g_list_free (origins); +} + + +/** + * calls_enumerateerate: + * @provider: a #CallsProvider + * @params: a #CallsEnumerateParams containing callbacks and state + * + * Enumerate all of the #CallsOrigin objects in the #CallsProvider and + * then all of the #CallsCall objects in those #CallsOrigin objects. + * For any callbacks stored in @params, connect them to the + * enumerated objects and connect them to any future objects that + * appear. Call the "origin-added" callback for a #CallsProvider and + * "call-added" for a #CallsOrigin if the target objects are + * enumerated. + */ +void +calls_enumerate (CallsProvider *provider, + CallsEnumerateParams *params) +{ + gboolean add_callback; + + // Connect user's callbacks + calls_enumerate_params_connect (params, G_OBJECT (provider)); + + // We add a callback for ourselves if we have to set callbacks on + // anything lower in the hierarchy in future + add_callback = + calls_enumerate_params_have_callbacks (params, CALLS_TYPE_ORIGIN) + || + calls_enumerate_params_have_callbacks (params, CALLS_TYPE_CALL); + + if (add_callback) + { + g_object_ref (params); + g_signal_connect_data (provider, + "origin-added", + G_CALLBACK (enum_origin_added_cb), + params, + (GClosureNotify)g_object_unref, + 0); + + } + + // We enumerate if we've added callbacks and if there's the specific + // "origin-added" callback for this level + if (add_callback || + calls_enumerate_params_lookup (params, CALLS_TYPE_PROVIDER, + "origin-added")) + { + enum_provider_origins (provider, params); + } + + calls_enumerate_params_set_enumerating (params, FALSE); +} diff --git a/src/calls-enumerate.h b/src/calls-enumerate.h new file mode 100644 index 0000000..74b4eac --- /dev/null +++ b/src/calls-enumerate.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 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: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_ENUMERATE_H__ +#define CALLS_ENUMERATE_H__ + +#include "calls-provider.h" +#include "calls-enumerate-params.h" + +#include + +G_BEGIN_DECLS + +void calls_enumerate (CallsProvider *provider, + CallsEnumerateParams *params); + +G_END_DECLS + +#endif /* CALLS_ENUMERATE_H__ */ diff --git a/src/calls-main-window.c b/src/calls-main-window.c index d320f0d..7336eca 100644 --- a/src/calls-main-window.c +++ b/src/calls-main-window.c @@ -27,6 +27,7 @@ #include "calls-call-holder.h" #include "calls-call-selector-item.h" #include "calls-new-call-box.h" +#include "calls-enumerate.h" #include "config.h" #include "util.h" @@ -165,69 +166,12 @@ info_response_cb (GtkInfoBar *infobar, static void -add_call (CallsMainWindow *self, CallsCall *call) +call_removed_cb (CallsMainWindow *self, CallsCall *call, const gchar *reason) { - g_signal_connect_swapped (call, "message", - G_CALLBACK (show_message), self); -} - - -static void -remove_call (CallsMainWindow *self, CallsCall *call, const gchar *reason) -{ - g_return_if_fail (CALLS_IS_MAIN_WINDOW (self)); - g_return_if_fail (CALLS_IS_CALL (call)); - show_message(self, reason, GTK_MESSAGE_INFO); } -static void -add_origin_calls (CallsMainWindow *self, CallsOrigin *origin) -{ - GList *calls, *node; - - calls = calls_origin_get_calls (origin); - - for (node = calls; node; node = node->next) - { - add_call (self, CALLS_CALL (node->data)); - } - - g_list_free (calls); -} - - -static void -add_origin (CallsMainWindow *self, CallsOrigin *origin) -{ - g_signal_connect_swapped (origin, "message", - G_CALLBACK (show_message), self); - g_signal_connect_swapped (origin, "call-added", - G_CALLBACK (add_call), self); - g_signal_connect_swapped (origin, "call-removed", - G_CALLBACK (remove_call), self); - - add_origin_calls (self, origin); -} - - -static void -add_provider_origins (CallsMainWindow *self, CallsProvider *provider) -{ - GList *origins, *node; - - origins = calls_provider_get_origins (provider); - - for (node = origins; node; node = node->next) - { - add_origin (self, CALLS_ORIGIN (node->data)); - } - - g_list_free (origins); -} - - static void set_property (GObject *object, guint property_id, @@ -248,6 +192,35 @@ set_property (GObject *object, } +static void +set_up_provider (CallsMainWindow *self) +{ + const GType msg_obj_types[3] = + { + CALLS_TYPE_PROVIDER, + CALLS_TYPE_ORIGIN, + CALLS_TYPE_CALL + }; + CallsEnumerateParams *params; + unsigned i; + + params = calls_enumerate_params_new (self); + + for (i = 0; i < 3; ++i) + { + calls_enumerate_params_add + (params, msg_obj_types[i], "message", G_CALLBACK (show_message)); + } + + calls_enumerate_params_add + (params, CALLS_TYPE_ORIGIN, "call-removed", G_CALLBACK (call_removed_cb)); + + calls_enumerate (self->provider, params); + + g_object_unref (params); +} + + static void constructed (GObject *object) { @@ -256,13 +229,7 @@ constructed (GObject *object) GSimpleActionGroup *simple_action_group; CallsNewCallBox *new_call_box; - /* Set up provider */ - g_signal_connect_swapped (self->provider, "message", - G_CALLBACK (show_message), self); - g_signal_connect_swapped (self->provider, "origin-added", - G_CALLBACK (add_origin), self); - - add_provider_origins (self, self->provider); + set_up_provider (self); /* Add new call box */ new_call_box = calls_new_call_box_new (self->provider); diff --git a/src/calls-new-call-box.c b/src/calls-new-call-box.c index 9395ca9..c9151a5 100644 --- a/src/calls-new-call-box.c +++ b/src/calls-new-call-box.c @@ -23,8 +23,7 @@ */ #include "calls-new-call-box.h" - -#include "calls-origin.h" +#include "calls-enumerate.h" #include #define HANDY_USE_UNSTABLE_API @@ -208,33 +207,27 @@ remove_origins (CallsNewCallBox *self) } -static void -add_provider_origins (CallsNewCallBox *self, CallsProvider *provider) -{ - GList *origins, *node; - - origins = calls_provider_get_origins (provider); - - for (node = origins; node; node = node->next) - { - add_origin (self, CALLS_ORIGIN (node->data)); - } - - g_list_free (origins); -} - - static void set_provider (CallsNewCallBox *self, CallsProvider *provider) { - g_signal_connect_swapped (provider, "notify::status", - G_CALLBACK (notify_status_cb), self); - g_signal_connect_swapped (provider, "origin-added", - G_CALLBACK (add_origin), self); - g_signal_connect_swapped (provider, "origin-removed", - G_CALLBACK (remove_origin), self); + CallsEnumerateParams *params; - add_provider_origins (self, provider); + params = calls_enumerate_params_new (self); + +#define add(detail,cb) \ + calls_enumerate_params_add \ + (params, CALLS_TYPE_PROVIDER, detail, \ + G_CALLBACK (cb)); + + add ("notify::status", notify_status_cb); + add ("origin-added", add_origin); + add ("origin-removed", remove_origin); + +#undef add + + calls_enumerate (provider, params); + + g_object_unref (params); } static void diff --git a/src/calls-provider.h b/src/calls-provider.h index bb5af38..a5d2b9c 100644 --- a/src/calls-provider.h +++ b/src/calls-provider.h @@ -25,16 +25,21 @@ #ifndef CALLS_PROVIDER_H__ #define CALLS_PROVIDER_H__ +#include "calls-message-source.h" +#include "calls-origin.h" +#include "calls-call.h" #include "util.h" #include G_BEGIN_DECLS + #define CALLS_TYPE_PROVIDER (calls_provider_get_type ()) G_DECLARE_INTERFACE (CallsProvider, calls_provider, CALLS, PROVIDER, GObject); + struct _CallsProviderInterface { GTypeInterface parent_iface; @@ -44,9 +49,10 @@ struct _CallsProviderInterface }; -const gchar * calls_provider_get_name (CallsProvider *self); -gchar * calls_provider_get_status (CallsProvider *self); -GList * calls_provider_get_origins (CallsProvider *self); +const gchar * calls_provider_get_name (CallsProvider *self); +gchar * calls_provider_get_status (CallsProvider *self); +GList * calls_provider_get_origins (CallsProvider *self); + G_END_DECLS diff --git a/src/meson.build b/src/meson.build index 3c11594..a5b783d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -34,6 +34,8 @@ calls_sources = files(['calls-message-source.c', 'calls-message-source.h', 'calls-call.c', 'calls-origin.c', 'calls-origin.h', 'calls-provider.c', 'calls-provider.h', + 'calls-enumerate-params.c', 'calls-enumerate-params.h', + 'calls-enumerate.c', 'calls-enumerate.h', 'calls-ofono-call.c', 'calls-ofono-call.h', 'calls-ofono-origin.c', 'calls-ofono-origin.h', 'calls-ofono-provider.c', 'calls-ofono-provider.h', From 027c49e8bdd194be06e635130713590956bdd35e Mon Sep 17 00:00:00 2001 From: Bob Ham Date: Fri, 9 Nov 2018 12:38:04 +0000 Subject: [PATCH 2/4] src: Add old state as argument to CallsCall's state-changed signal --- src/calls-call.c | 11 ++++++++--- src/calls-dummy-call.c | 12 +++++++++++- src/calls-dummy-origin.c | 5 +++-- src/calls-mm-call.c | 15 ++++++++++++--- src/calls-mm-origin.c | 5 +++-- src/calls-ofono-call.c | 11 ++++++++++- 6 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/calls-call.c b/src/calls-call.c index 131b156..134e919 100644 --- a/src/calls-call.c +++ b/src/calls-call.c @@ -102,12 +102,17 @@ static guint signals [SIGNAL_LAST_SIGNAL]; static void calls_call_default_init (CallsCallInterface *iface) { - GType arg_types = CALLS_TYPE_CALL_STATE; + GType arg_types[2] = + { + CALLS_TYPE_CALL_STATE, + CALLS_TYPE_CALL_STATE + }; /** * CallsCall::state-changed: * @self: The #CallsCall instance. - * @state: The new state of the call. + * @new_state: The new state of the call. + * @old_state: The old state of the call. * * This signal is emitted when the state of the call changes, for * example when it's answered or when the call is disconnected. @@ -118,7 +123,7 @@ calls_call_default_init (CallsCallInterface *iface) G_SIGNAL_RUN_LAST, NULL, NULL, NULL, NULL, G_TYPE_NONE, - 1, &arg_types); + 2, arg_types); } diff --git a/src/calls-dummy-call.c b/src/calls-dummy-call.c index 473ccfa..bd6bcb6 100644 --- a/src/calls-dummy-call.c +++ b/src/calls-dummy-call.c @@ -85,8 +85,18 @@ change_state (CallsCall *call, CallsDummyCall *self, CallsCallState state) { + CallsCallState old_state = self->state; + + if (old_state == state) + { + return; + } + self->state = state; - g_signal_emit_by_name (call, "state-changed", state); + g_signal_emit_by_name (call, + "state-changed", + state, + old_state); } static void diff --git a/src/calls-dummy-origin.c b/src/calls-dummy-origin.c index 678a646..9057adc 100644 --- a/src/calls-dummy-origin.c +++ b/src/calls-dummy-origin.c @@ -110,10 +110,11 @@ remove_calls (CallsDummyOrigin *self, const gchar *reason) static void call_state_changed_cb (CallsDummyOrigin *self, - CallsCallState state, + CallsCallState new_state, + CallsCallState old_state, CallsCall *call) { - if (state != CALLS_CALL_STATE_DISCONNECTED) + if (new_state != CALLS_CALL_STATE_DISCONNECTED) { return; } diff --git a/src/calls-mm-call.c b/src/calls-mm-call.c index 6c86e7c..467147e 100644 --- a/src/calls-mm-call.c +++ b/src/calls-mm-call.c @@ -81,12 +81,21 @@ get_state (CallsCall *call) static void -change_state (CallsMMCall *self, - MMCallState state) +change_state (CallsMMCall *self, + CallsCallState state) { + CallsCallState old_state = self->state; + + if (old_state == state) + { + return; + } + self->state = state; g_signal_emit_by_name (CALLS_CALL (self), - "state-changed", state); + "state-changed", + state, + old_state); } diff --git a/src/calls-mm-origin.c b/src/calls-mm-origin.c index 1884462..588359a 100644 --- a/src/calls-mm-origin.c +++ b/src/calls-mm-origin.c @@ -204,10 +204,11 @@ delete_call (CallsMMOrigin *self, static void call_state_changed_cb (CallsMMOrigin *self, - CallsCallState state, + CallsCallState new_state, + CallsCallState old_state, CallsCall *call) { - if (state != CALLS_CALL_STATE_DISCONNECTED) + if (new_state != CALLS_CALL_STATE_DISCONNECTED) { return; } diff --git a/src/calls-ofono-call.c b/src/calls-ofono-call.c index d85ae6d..aee1a57 100644 --- a/src/calls-ofono-call.c +++ b/src/calls-ofono-call.c @@ -87,9 +87,18 @@ static void change_state (CallsOfonoCall *self, CallsCallState state) { + CallsCallState old_state = self->state; + + if (old_state == state) + { + return; + } + self->state = state; g_signal_emit_by_name (CALLS_CALL (self), - "state-changed", state); + "state-changed", + state, + old_state); } From 5e636edb7d5816dc2cd514ab965ac7e77007a47c Mon Sep 17 00:00:00 2001 From: Bob Ham Date: Fri, 9 Nov 2018 16:20:39 +0000 Subject: [PATCH 3/4] main: Remove needless cast to GtkApplication --- src/main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.c b/src/main.c index 8ed2948..5261853 100644 --- a/src/main.c +++ b/src/main.c @@ -32,15 +32,15 @@ int main (int argc, char **argv) { - GtkApplication *app; + GApplication *app; int status; textdomain (GETTEXT_PACKAGE); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); - app = GTK_APPLICATION (calls_application_new ()); - status = g_application_run (G_APPLICATION (app), argc, argv); + app = G_APPLICATION (calls_application_new ()); + status = g_application_run (app, argc, argv); g_object_unref (app); return status; From 7252962139a89c7278267cefb49af2ed1715897c Mon Sep 17 00:00:00 2001 From: Bob Ham Date: Fri, 9 Nov 2018 16:30:40 +0000 Subject: [PATCH 4/4] Add ringtone support using GSound --- debian/control | 1 + src/calls-application.c | 10 +- src/calls-ringer.c | 370 ++++++++++++++++++++++++++++++++++++++++ src/calls-ringer.h | 40 +++++ src/meson.build | 2 + 5 files changed, 421 insertions(+), 2 deletions(-) create mode 100644 src/calls-ringer.c create mode 100644 src/calls-ringer.h diff --git a/debian/control b/debian/control index 9911f03..fe6c6bf 100644 --- a/debian/control +++ b/debian/control @@ -8,6 +8,7 @@ Build-Depends: libgtk-3-dev, modemmanager-dev, libmm-glib-dev, + libgsound-dev, meson, pkg-config, # to run the tests diff --git a/src/calls-application.c b/src/calls-application.c index da9df6e..594eb7f 100644 --- a/src/calls-application.c +++ b/src/calls-application.c @@ -37,6 +37,7 @@ #include "calls-new-call-box.h" #include "calls-encryption-indicator.h" #include "calls-mm-provider.h" +#include "calls-ringer.h" #include "calls-call-window.h" #include "calls-main-window.h" #include "calls-application.h" @@ -53,6 +54,7 @@ struct _CallsApplication GtkApplication parent_instance; CallsProvider *provider; + CallsRinger *ringer; }; G_DEFINE_TYPE (CallsApplication, calls_application, GTK_TYPE_APPLICATION) @@ -63,6 +65,7 @@ dispose (GObject *object) { CallsApplication *self = (CallsApplication *)object; + g_clear_object (&self->ringer); g_clear_object (&self->provider); G_OBJECT_CLASS (calls_application_parent_class)->finalize (object); @@ -76,11 +79,14 @@ startup (GApplication *application) G_APPLICATION_CLASS (calls_application_parent_class)->startup (application); + g_set_prgname (APP_ID); + g_set_application_name (_("Calls")); + self->provider = CALLS_PROVIDER (calls_mm_provider_new ()); g_assert (self->provider != NULL); - g_set_prgname (APP_ID); - g_set_application_name (_("Calls")); + self->ringer = calls_ringer_new (self->provider); + g_assert (self->ringer != NULL); } static void diff --git a/src/calls-ringer.c b/src/calls-ringer.c new file mode 100644 index 0000000..2263830 --- /dev/null +++ b/src/calls-ringer.c @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2018 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: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "calls-ringer.h" +#include "calls-enumerate.h" +#include "config.h" + +#include +#include +#include + + +struct _CallsRinger +{ + GtkApplicationWindow parent_instance; + + CallsProvider *provider; + GSoundContext *ctx; + unsigned ring_count; + GCancellable *playing; +}; + +G_DEFINE_TYPE (CallsRinger, calls_ringer, G_TYPE_OBJECT); + +enum { + PROP_0, + PROP_PROVIDER, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +static void +ringer_error (CallsRinger *self, + const gchar *prefix, + GError *error) +{ + g_warning ("%s: %s", prefix, error->message); + g_error_free (error); + + g_clear_object (&self->ctx); +} + + +static gboolean +create_ctx (CallsRinger *self) +{ + GError *error = NULL; + gboolean ok; + + self->ctx = gsound_context_new (NULL, &error); + if (!self->ctx) + { + ringer_error (self, "Error creating GSound context", error); + return FALSE; + } + + ok = gsound_context_set_attributes + (self->ctx, + &error, + GSOUND_ATTR_APPLICATION_ICON_NAME, APP_ID, + NULL); + if (!ok) + { + ringer_error (self, "Error setting GSound attributes", error); + return FALSE; + } + + g_debug ("Created ringtone context"); + + return TRUE; +} + + +static void play (CallsRinger *self); + + +static void +play_cb (GSoundContext *ctx, + GAsyncResult *res, + CallsRinger *self) +{ + gboolean ok; + GError *error = NULL; + + ok = gsound_context_play_full_finish (ctx, res, &error); + if (!ok) + { + g_clear_object (&self->playing); + + if (error->domain == G_IO_ERROR + && error->code == G_IO_ERROR_CANCELLED) + { + g_debug ("Ringtone cancelled"); + } + else + { + ringer_error (self, "Error playing ringtone", error); + } + + return; + } + + g_assert (self->ring_count > 0); + play (self); +} + + +static void +play (CallsRinger *self) +{ + g_assert (self->ctx != NULL); + g_assert (self->playing != NULL); + + g_debug ("Playing ringtone"); + gsound_context_play_full (self->ctx, + self->playing, + (GAsyncReadyCallback)play_cb, + self, + GSOUND_ATTR_MEDIA_ROLE, "event", + GSOUND_ATTR_EVENT_ID, "phone-incoming-call", + GSOUND_ATTR_EVENT_DESCRIPTION, _("Incoming call"), + NULL); +} + + +static void +start (CallsRinger *self) +{ + g_assert (self->playing == NULL); + + if (!self->ctx) + { + gboolean ok; + + ok = create_ctx (self); + if (!ok) + { + return; + } + } + + g_debug ("Starting ringtone"); + self->playing = g_cancellable_new (); + play (self); +} + + +static void +stop (CallsRinger *self) +{ + g_debug ("Stopping ringtone"); + + g_assert (self->ctx != NULL); + + g_cancellable_cancel (self->playing); +} + + +static void +update_ring (CallsRinger *self) +{ + if (!self->playing) + { + if (self->ring_count > 0) + { + g_debug ("Starting ringer"); + start (self); + } + } + else + { + if (self->ring_count == 0) + { + g_debug ("Stopping ringer"); + stop (self); + } + } +} + + +static inline gboolean +is_ring_state (CallsCallState state) +{ + switch (state) + { + case CALLS_CALL_STATE_INCOMING: + case CALLS_CALL_STATE_WAITING: + return TRUE; + default: + return FALSE; + } +} + + +static void +state_changed_cb (CallsRinger *self, + CallsCallState new_state, + CallsCallState old_state) +{ + gboolean old_is_ring; + + g_return_if_fail (old_state != new_state); + + old_is_ring = is_ring_state (old_state); + if (old_is_ring == is_ring_state (new_state)) + { + // No change in ring state + return; + } + + if (old_is_ring) + { + --self->ring_count; + } + else + { + ++self->ring_count; + } + + update_ring (self); +} + + +static void +update_count (CallsRinger *self, + CallsCall *call, + short delta) +{ + if (is_ring_state (calls_call_get_state (call))) + { + self->ring_count += delta; + } + + update_ring (self); +} + + +static void +call_added_cb (CallsRinger *self, CallsCall *call) +{ + update_count (self, call, +1); +} + + +static void +call_removed_cb (CallsRinger *self, CallsCall *call, const gchar *reason) +{ + update_count (self, call, -1); +} + + +static void +calls_ringer_init (CallsRinger *self) +{ +} + + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsRinger *self = CALLS_RINGER (object); + + switch (property_id) { + case PROP_PROVIDER: + g_set_object (&self->provider, CALLS_PROVIDER (g_value_get_object (value))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +set_up_provider (CallsRinger *self) +{ + CallsEnumerateParams *params; + + params = calls_enumerate_params_new (self); + + calls_enumerate_params_add + (params, CALLS_TYPE_ORIGIN, "call-added", G_CALLBACK (call_added_cb)); + calls_enumerate_params_add + (params, CALLS_TYPE_ORIGIN, "call-removed", G_CALLBACK (call_removed_cb)); + + calls_enumerate_params_add + (params, CALLS_TYPE_CALL, "state-changed", G_CALLBACK (state_changed_cb)); + + calls_enumerate (self->provider, params); + + g_object_unref (params); +} + +static void +constructed (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsRinger *self = CALLS_RINGER (object); + + create_ctx (self); + set_up_provider (self); + + parent_class->constructed (object); +} + + +static void +dispose (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsRinger *self = CALLS_RINGER (object); + + g_clear_object (&self->provider); + + parent_class->dispose (object); +} + + +static void +calls_ringer_class_init (CallsRingerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->dispose = dispose; + + props[PROP_PROVIDER] = + g_param_spec_object ("provider", + _("Provider"), + _("An object implementing low-level call-making functionality"), + CALLS_TYPE_PROVIDER, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} + +CallsRinger * +calls_ringer_new (CallsProvider *provider) +{ + return g_object_new (CALLS_TYPE_RINGER, + "provider", provider, + NULL); +} diff --git a/src/calls-ringer.h b/src/calls-ringer.h new file mode 100644 index 0000000..d18e071 --- /dev/null +++ b/src/calls-ringer.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 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: Bob Ham + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef CALLS_RINGER_H__ +#define CALLS_RINGER_H__ + +#include "calls-provider.h" + +G_BEGIN_DECLS + +#define CALLS_TYPE_RINGER (calls_ringer_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsRinger, calls_ringer, CALLS, RINGER, GObject); + +CallsRinger *calls_ringer_new (CallsProvider *provider); + +G_END_DECLS + +#endif /* CALLS_RINGER_H__ */ diff --git a/src/meson.build b/src/meson.build index a5b783d..63cdb2e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -28,6 +28,7 @@ calls_deps = [ dependency('gobject-2.0'), dependency('libhandy-0.0'), dependency('ModemManager'), dependency('mm-glib'), + dependency('gsound'), ] calls_sources = files(['calls-message-source.c', 'calls-message-source.h', @@ -54,6 +55,7 @@ calls_sources = files(['calls-message-source.c', 'calls-message-source.h', 'calls-new-call-box.c', 'calls-new-call-box.h', 'calls-new-call-header-bar.c', 'calls-new-call-header-bar.h', 'calls-main-window.c', 'calls-main-window.h', + 'calls-ringer.c', 'calls-ringer.h', 'calls-application.c', 'calls-application.h', 'util.c', 'util.h', ])