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', ])