From 0e293fc1284cb1e5ea111757589053d26aa68802 Mon Sep 17 00:00:00 2001 From: Bob Ham Date: Fri, 3 Aug 2018 14:22:12 +0000 Subject: [PATCH] Add ModemManager provider --- README.md | 2 +- src/calls-mm-call.c | 411 +++++++++++++++++++++++++++++++ src/calls-mm-call.h | 43 ++++ src/calls-mm-origin.c | 534 ++++++++++++++++++++++++++++++++++++++++ src/calls-mm-origin.h | 41 +++ src/calls-mm-provider.c | 332 +++++++++++++++++++++++++ src/calls-mm-provider.h | 41 +++ src/main.c | 5 +- src/meson.build | 5 + 9 files changed, 1411 insertions(+), 3 deletions(-) create mode 100644 src/calls-mm-call.c create mode 100644 src/calls-mm-call.h create mode 100644 src/calls-mm-origin.c create mode 100644 src/calls-mm-origin.h create mode 100644 src/calls-mm-provider.c create mode 100644 src/calls-mm-provider.h diff --git a/README.md b/README.md index 54698b8..b650995 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Calls is licensed under the GPLv3+. ## Dependencies - sudo apt-get install libgtk-3-dev libhandy-0.0-dev + sudo apt-get install libgtk-3-dev libhandy-0.0-dev modemmanager-dev libmm-glib-dev ## Building diff --git a/src/calls-mm-call.c b/src/calls-mm-call.c new file mode 100644 index 0000000..67dfa88 --- /dev/null +++ b/src/calls-mm-call.c @@ -0,0 +1,411 @@ +/* + * 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-mm-call.h" +#include "calls-call.h" +#include "calls-message-source.h" +#include "util.h" + +#include +#include + + +struct _CallsMMCall +{ + GObject parent_instance; + MMCall *mm_call; + GString *number; + CallsCallState state; + gchar *disconnect_reason; +}; + +static void calls_mm_call_message_source_interface_init (CallsCallInterface *iface); +static void calls_mm_call_call_interface_init (CallsCallInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (CallsMMCall, calls_mm_call, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (CALLS_TYPE_MESSAGE_SOURCE, + calls_mm_call_message_source_interface_init) + G_IMPLEMENT_INTERFACE (CALLS_TYPE_CALL, + calls_mm_call_call_interface_init)) + +enum { + PROP_0, + PROP_MM_CALL, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +static const gchar * +get_name (CallsCall *call) +{ + return NULL; +} + + +static const gchar * +get_number (CallsCall *call) +{ + CallsMMCall *self = CALLS_MM_CALL (call); + return self->number->str; +} + + +static CallsCallState +get_state (CallsCall *call) +{ + CallsMMCall *self = CALLS_MM_CALL (call); + return self->state; +} + + +static void +change_state (CallsMMCall *self, + MMCallState state) +{ + self->state = state; + g_signal_emit_by_name (CALLS_CALL (self), + "state-changed", state); +} + + +static void +notify_number_cb (CallsMMCall *self, + const gchar *number) +{ + g_string_assign (self->number, number); +} + + +struct CallsMMCallStateReasonMap +{ + MMCallStateReason value; + const gchar *desc; +}; + +static const struct CallsMMCallStateReasonMap STATE_REASON_MAP[] = { + +#define row(ENUMVALUE,DESCRIPTION) \ + { MM_CALL_STATE_REASON_##ENUMVALUE, DESCRIPTION } \ + + row (UNKNOWN, "Outgoing call created"), + row (OUTGOING_STARTED, "Outgoing call started"), + row (INCOMING_NEW, "Incoming call"), + row (ACCEPTED, "Call accepted"), + row (TERMINATED, "Call terminated"), + row (REFUSED_OR_BUSY, "Busy or call refused"), + row (ERROR, "Wrong number or network problem"), + +#undef row + + { -1, NULL } +}; + +static void +set_disconnect_reason (CallsMMCall *self, + MMCallStateReason reason) +{ + const struct CallsMMCallStateReasonMap *map_row; + GString *reason_str; + + for (map_row = STATE_REASON_MAP; map_row->desc; ++map_row) + { + if (map_row->value == reason) + { + self->disconnect_reason = g_strdup(map_row->desc); + return; + } + } + + reason_str = g_string_new ("Unknown disconnect reason "); + g_string_append_printf (reason_str, "(%i)", (int)reason); + + g_warning ("%s", reason_str->str); + CALLS_SET_PTR_PROPERTY (self->disconnect_reason, + reason_str->str); + + g_string_free (reason_str, FALSE); +} + + +struct CallsMMCallStateMap +{ + MMCallState mm; + CallsCallState calls; +}; + +static const struct CallsMMCallStateMap STATE_MAP[] = { + +#define row(MMENUM,CALLSENUM) \ + { MM_CALL_STATE_##MMENUM, CALLS_CALL_STATE_##CALLSENUM } \ + + row (DIALING, DIALING), + row (RINGING_OUT, INCOMING), + row (RINGING_IN, ALERTING), + row (ACTIVE, ACTIVE), + row (HELD, HELD), + row (WAITING, INCOMING), + row (TERMINATED, DISCONNECTED), + +#undef row + + { MM_CALL_STATE_UNKNOWN, (CallsCallState)0 }, + { -1, -1 } +}; + +static void +state_changed_cb (CallsMMCall *self, + MMCallState old, + MMCallState mm_new, + MMCallStateReason reason) +{ + const struct CallsMMCallStateMap *map_row; + + if (mm_new == MM_CALL_STATE_TERMINATED) + { + set_disconnect_reason (self, reason); + } + + for (map_row = STATE_MAP; map_row->mm != -1; ++map_row) + { + if (map_row->mm == mm_new) + { + change_state (self, map_row->calls); + return; + } + } +} + + +struct CallsMMOperationData +{ + const gchar *desc; + CallsMMCall *self; + gboolean (*finish_func) (MMCall *, GAsyncResult *, GError **); +}; + +static void +operation_cb (MMCall *mm_call, + GAsyncResult *res, + struct CallsMMOperationData *data) +{ + gboolean ok; + GError *error = NULL; + + ok = data->finish_func (mm_call, res, &error); + if (!ok) + { + g_warning ("Error %s ModemManager call to `%s': %s", + data->desc, data->self->number->str, error->message); + CALLS_ERROR (data->self, error); + } + + g_free (data); +} + +#define DEFINE_OPERATION(op,name,desc_str) \ + static void \ + name (CallsCall *call) \ + { \ + CallsMMCall *self = CALLS_MM_CALL (call); \ + struct CallsMMOperationData *data; \ + \ + data = g_new0 (struct CallsMMOperationData, 1); \ + data->desc = desc_str; \ + data->self = self; \ + data->finish_func = mm_call_##op##_finish; \ + \ + mm_call_##op \ + (self->mm_call, \ + NULL, \ + (GAsyncReadyCallback) operation_cb, \ + data); \ + } + +DEFINE_OPERATION(accept, answer, "accepting"); +DEFINE_OPERATION(hangup, hang_up, "hanging up"); +DEFINE_OPERATION(start, start_call, "starting outgoing call"); + + +static void +tone_start (CallsCall *call, gchar key) +{ + CallsMMCall *self = CALLS_MM_CALL (call); + struct CallsMMOperationData *data; + char key_str[2] = { key, '\0' }; + + data = g_new0 (struct CallsMMOperationData, 1); + data->desc = "sending DTMF"; + data->self = self; + data->finish_func = mm_call_send_dtmf_finish; + + mm_call_send_dtmf + (self->mm_call, + key_str, + NULL, + (GAsyncReadyCallback) operation_cb, + data); +} + + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsMMCall *self = CALLS_MM_CALL (object); + + switch (property_id) { + case PROP_MM_CALL: + g_set_object (&self->mm_call, + MM_CALL (g_value_get_object (value))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +constructed (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsMMCall *self = CALLS_MM_CALL (object); + MmGdbusCall *gdbus_call = MM_GDBUS_CALL (self->mm_call); + + g_signal_connect_swapped (gdbus_call, "notify::number", + G_CALLBACK (notify_number_cb), self); + g_signal_connect_swapped (gdbus_call, "state-changed", + G_CALLBACK (state_changed_cb), self); + + notify_number_cb (self, mm_call_get_number (self->mm_call)); + + /* Start outgoing call */ + if (mm_call_get_state (self->mm_call) == MM_CALL_STATE_UNKNOWN + && mm_call_get_direction (self->mm_call) == MM_CALL_DIRECTION_OUTGOING) + { + start_call (CALLS_CALL (self)); + } + + parent_class->constructed (object); +} + + +static void +dispose (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsMMCall *self = CALLS_MM_CALL (object); + + g_clear_object (&self->mm_call); + + parent_class->dispose (object); +} + + +static void +finalize (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsMMCall *self = CALLS_MM_CALL (object); + + g_free (self->disconnect_reason); + g_string_free (self->number, TRUE); + + parent_class->finalize (object); +} + + +static void +calls_mm_call_class_init (CallsMMCallClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->finalize = finalize; + + props[PROP_MM_CALL] = + g_param_spec_object ("mm-call", + _("MM call"), + _("A libmm-glib proxy object for the underlying call object"), + MM_TYPE_CALL, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} + + +static void +calls_mm_call_message_source_interface_init (CallsCallInterface *iface) +{ +} + + +static void +calls_mm_call_call_interface_init (CallsCallInterface *iface) +{ + iface->get_number = get_number; + iface->get_name = get_name; + iface->get_state = get_state; + iface->answer = answer; + iface->hang_up = hang_up; + iface->tone_start = tone_start; +} + + +static void +calls_mm_call_init (CallsMMCall *self) +{ + self->number = g_string_new (NULL); +} + + +CallsMMCall * +calls_mm_call_new (MMCall *mm_call) +{ + g_return_val_if_fail (MM_IS_CALL (mm_call), NULL); + + return g_object_new (CALLS_TYPE_MM_CALL, + "mm-call", mm_call, + NULL); +} + + +const gchar * +calls_mm_call_get_object_path (CallsMMCall *call) +{ + return mm_call_get_path (call->mm_call); +} + + +const gchar * +calls_mm_call_get_disconnect_reason (CallsMMCall *call) +{ + return call->disconnect_reason; +} diff --git a/src/calls-mm-call.h b/src/calls-mm-call.h new file mode 100644 index 0000000..ccb3d9d --- /dev/null +++ b/src/calls-mm-call.h @@ -0,0 +1,43 @@ +/* + * 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_MM_CALL_H__ +#define CALLS_MM_CALL_H__ + +#include +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_MM_CALL (calls_mm_call_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsMMCall, calls_mm_call, CALLS, MM_CALL, GObject); + +CallsMMCall *calls_mm_call_new (MMCall *mm_call); +const gchar *calls_mm_call_get_object_path (CallsMMCall *call); +const gchar *calls_mm_call_get_disconnect_reason (CallsMMCall *call); + +G_END_DECLS + +#endif /* CALLS_MM_CALL_H__ */ diff --git a/src/calls-mm-origin.c b/src/calls-mm-origin.c new file mode 100644 index 0000000..e3636b3 --- /dev/null +++ b/src/calls-mm-origin.c @@ -0,0 +1,534 @@ +/* + * 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-mm-origin.h" +#include "calls-origin.h" +#include "calls-mm-call.h" +#include "calls-message-source.h" + +#include + + +struct _CallsMMOrigin +{ + GObject parent_instance; + MMObject *mm_obj; + MMModemVoice *voice; + gchar *name; + GHashTable *calls; +}; + +static void calls_mm_origin_message_source_interface_init (CallsOriginInterface *iface); +static void calls_mm_origin_origin_interface_init (CallsOriginInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (CallsMMOrigin, calls_mm_origin, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (CALLS_TYPE_MESSAGE_SOURCE, + calls_mm_origin_message_source_interface_init) + G_IMPLEMENT_INTERFACE (CALLS_TYPE_ORIGIN, + calls_mm_origin_origin_interface_init)) + +enum { + PROP_0, + PROP_MODEM, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +static const gchar * +get_name (CallsOrigin *origin) +{ + CallsMMOrigin *self = CALLS_MM_ORIGIN (origin); + return self->name; +} + + +static GList * +get_calls (CallsOrigin * origin) +{ + CallsMMOrigin *self = CALLS_MM_ORIGIN (origin); + return g_hash_table_get_values (self->calls); +} + + +static void +dial_cb (MMModemVoice *voice, + GAsyncResult *res, + CallsMMOrigin *self) +{ + MMCall *call; + GError *error = NULL; + + call = mm_modem_voice_create_call_finish (voice, res, &error); + if (!call) + { + g_warning ("Error dialing number on ModemManager modem `%s': %s", + self->name, error->message); + CALLS_ERROR (self, error); + } +} + + +static void +dial (CallsOrigin *origin, const gchar *number) +{ + CallsMMOrigin *self = CALLS_MM_ORIGIN (origin); + MMCallProperties *props; + + g_assert (self->voice != NULL); + + props = mm_call_properties_new(); + mm_call_properties_set_number (props, number); + + mm_modem_voice_create_call + (self->voice, + props, + NULL, + (GAsyncReadyCallback) dial_cb, + self); + + g_object_unref (props); +} + + +static void +remove_call (CallsMMOrigin *self, + CallsMMCall *call, + const gchar *path, + const gchar *reason) +{ + g_signal_emit_by_name (CALLS_ORIGIN(self), "call-removed", + CALLS_CALL(call), reason); + g_hash_table_remove (self->calls, path); +} + + +struct CallsMMRemoveCallsData +{ + CallsOrigin *origin; + const gchar *reason; +}; + + +static gboolean +remove_calls_cb (const gchar *path, + CallsMMCall *call, + struct CallsMMRemoveCallsData *data) +{ + g_signal_emit_by_name (data->origin, "call-removed", + CALLS_CALL(call), data->reason); + return TRUE; +} + + +static void +remove_calls (CallsMMOrigin *self, const gchar *reason) +{ + struct CallsMMRemoveCallsData data = { CALLS_ORIGIN (self), reason }; + + g_hash_table_foreach_remove (self->calls, + (GHRFunc) remove_calls_cb, + &data); +} + + +struct CallsMMOriginDeleteCallData +{ + CallsMMOrigin *self; + gchar *path; +}; + + +static void +delete_call_cb (MMModemVoice *voice, + GAsyncResult *res, + struct CallsMMOriginDeleteCallData *data) +{ + gboolean ok; + GError *error = NULL; + + ok = mm_modem_voice_delete_call_finish (voice, res, &error); + if (!ok) + { + g_warning ("Error deleting call `%s' on MMModemVoice `%s': %s", + data->path, data->self->name, error->message); + CALLS_ERROR (data->self, error); + } + + g_free (data->path); + g_free (data); +} + + +static void +call_state_changed_cb (CallsMMOrigin *self, + CallsCallState state, + CallsCall *call) +{ + const gchar *path; + struct CallsMMOriginDeleteCallData *data; + + if (state != CALLS_CALL_STATE_DISCONNECTED) + { + return; + } + + path = calls_mm_call_get_object_path (CALLS_MM_CALL (call)); + + data = g_new0 (struct CallsMMOriginDeleteCallData, 1); + data->self = self; + data->path = g_strdup (path); + + mm_modem_voice_delete_call + (self->voice, + path, + NULL, + (GAsyncReadyCallback) delete_call_cb, + data); +} + + +static void +add_call (CallsMMOrigin *self, + MMCall *mm_call) +{ + CallsMMCall *call; + gchar *path; + + call = calls_mm_call_new (mm_call); + + g_signal_connect_swapped (call, "state-changed", + G_CALLBACK (call_state_changed_cb), + self); + + path = mm_call_dup_path (mm_call); + g_hash_table_insert (self->calls, path, call); + + g_signal_emit_by_name (CALLS_ORIGIN(self), "call-added", + CALLS_CALL(call)); + + g_debug ("Call `%s' added", path); +} + + +struct CallsMMOriginCallAddedData +{ + CallsMMOrigin *self; + gchar *path; +}; + + +static void +call_added_list_calls_cb (MMModemVoice *voice, + GAsyncResult *res, + struct CallsMMOriginCallAddedData *data) +{ + GList *calls; + GError *error = NULL; + + calls = mm_modem_voice_list_calls_finish (voice, res, &error); + if (!calls) + { + if (error) + { + g_warning ("Error listing calls on MMModemVoice `%s'" + " after call-added signal: %s", + data->self->name, error->message); + CALLS_ERROR (data->self, error); + } + else + { + g_warning ("No calls on MMModemVoice `%s'" + " after call-added signal", + data->self->name); + } + } + else + { + GList *node; + MMCall *call; + gboolean found = FALSE; + + for (node = calls; node; node = node->next) + { + call = MM_CALL (node->data); + + if (g_strcmp0 (mm_call_get_path (call), data->path) == 0) + { + add_call (data->self, call); + found = TRUE; + } + } + + if (!found) + { + g_warning ("Could not find new call `%s' in call list" + " on MMModemVoice `%s' after call-added signal", + data->path, data->self->name); + } + + g_list_free_full (calls, g_object_unref); + } + + g_free (data->path); + g_free (data); +} + + +static void +call_added_cb (MMModemVoice *voice, + gchar *path, + CallsMMOrigin *self) +{ + struct CallsMMOriginCallAddedData *data; + + if (g_hash_table_contains (self->calls, path)) + { + g_warning ("Received call-added signal for" + " existing call object path `%s'", path); + return; + } + + data = g_new0 (struct CallsMMOriginCallAddedData, 1); + data->self = self; + data->path = g_strdup (path); + + mm_modem_voice_list_calls + (voice, + NULL, + (GAsyncReadyCallback) call_added_list_calls_cb, + data); +} + + +static void +call_deleted_cb (MMModemVoice *voice, + const gchar *path, + CallsMMOrigin *self) +{ + CallsMMCall *call; + GString *reason; + const gchar *mm_reason; + + g_debug ("Removing call `%s'", path); + + call = g_hash_table_lookup (self->calls, path); + if (!call) + { + g_warning ("Could not find removed call `%s'", path); + return; + } + + reason = g_string_new ("Call removed"); + + mm_reason = calls_mm_call_get_disconnect_reason (call); + if (mm_reason) + { + g_string_assign (reason, mm_reason); + } + + remove_call (self, call, path, reason->str); + + g_string_free (reason, TRUE); + + g_debug ("Removed call `%s'", path); +} + + +static void +list_calls_cb (MMModemVoice *voice, + GAsyncResult *res, + CallsMMOrigin *self) +{ + GList *calls, *node; + GError *error = NULL; + + calls = mm_modem_voice_list_calls_finish (voice, res, &error); + if (!calls) + { + if (error) + { + g_warning ("Error listing calls on MMModemVoice `%s': %s", + self->name, error->message); + CALLS_ERROR (self, error); + } + return; + } + + for (node = calls; node; node = node->next) + { + add_call (self, MM_CALL (node->data)); + } + + g_list_free_full (calls, g_object_unref); +} + + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsMMOrigin *self = CALLS_MM_ORIGIN (object); + + switch (property_id) { + case PROP_MODEM: + g_set_object (&self->mm_obj, g_value_get_object(value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static gchar * +modem_get_name (MMModem *modem) +{ + gchar *name = NULL; + +#define try(prop) \ + name = mm_modem_dup_##prop (modem); \ + if (name) { \ + return name; \ + } + + try (model); + try (manufacturer); + try (device); + try (primary_port); + try (device_identifier); + try (plugin); + +#undef try + + return NULL; +} + + +static void +constructed (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsMMOrigin *self = CALLS_MM_ORIGIN (object); + MmGdbusModemVoice *gdbus_voice; + + self->name = modem_get_name (mm_object_get_modem (self->mm_obj)); + + self->voice = mm_object_get_modem_voice (self->mm_obj); + g_assert (self->voice != NULL); + + gdbus_voice = MM_GDBUS_MODEM_VOICE (self->voice); + g_signal_connect (gdbus_voice, "call-added", + G_CALLBACK (call_added_cb), self); + g_signal_connect (gdbus_voice, "call-deleted", + G_CALLBACK (call_deleted_cb), self); + + mm_modem_voice_list_calls + (self->voice, + NULL, + (GAsyncReadyCallback) list_calls_cb, + self); + parent_class->constructed (object); +} + + +static void +dispose (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsMMOrigin *self = CALLS_MM_ORIGIN (object); + + remove_calls (self, NULL); + g_clear_object (&self->mm_obj); + + parent_class->dispose (object); +} + + +static void +finalize (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsMMOrigin *self = CALLS_MM_ORIGIN (object); + + g_hash_table_unref (self->calls); + g_free (self->name); + + parent_class->finalize (object); +} + + +static void +calls_mm_origin_class_init (CallsMMOriginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->finalize = finalize; + + props[PROP_MODEM] = + g_param_spec_object ("mm-object", + _("Modem Object"), + _("A libmm-glib proxy object for the modem"), + MM_TYPE_OBJECT, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} + + +static void +calls_mm_origin_message_source_interface_init (CallsOriginInterface *iface) +{ +} + + +static void +calls_mm_origin_origin_interface_init (CallsOriginInterface *iface) +{ + iface->get_name = get_name; + iface->get_calls = get_calls; + iface->dial = dial; +} + + +static void +calls_mm_origin_init (CallsMMOrigin *self) +{ + self->calls = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_object_unref); +} + +CallsMMOrigin * +calls_mm_origin_new (MMObject *mm_obj) +{ + return g_object_new (CALLS_TYPE_MM_ORIGIN, + "mm-object", mm_obj, + NULL); +} diff --git a/src/calls-mm-origin.h b/src/calls-mm-origin.h new file mode 100644 index 0000000..daee945 --- /dev/null +++ b/src/calls-mm-origin.h @@ -0,0 +1,41 @@ +/* + * 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_MM_ORIGIN_H__ +#define CALLS_MM_ORIGIN_H__ + +#include +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_MM_ORIGIN (calls_mm_origin_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsMMOrigin, calls_mm_origin, CALLS, MM_ORIGIN, GObject); + +CallsMMOrigin *calls_mm_origin_new (MMObject *modem); + +G_END_DECLS + +#endif /* CALLS_MM_ORIGIN_H__ */ diff --git a/src/calls-mm-provider.c b/src/calls-mm-provider.c new file mode 100644 index 0000000..85471b6 --- /dev/null +++ b/src/calls-mm-provider.c @@ -0,0 +1,332 @@ +/* + * 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-mm-provider.h" +#include "calls-provider.h" +#include "calls-mm-origin.h" +#include "calls-message-source.h" +#include "calls-origin.h" + +#include +#include + +struct _CallsMMProvider +{ + GObject parent_instance; + + /** D-Bus connection */ + GDBusConnection *connection; + /** ModemManager object proxy */ + MMManager *mm; + /** Map of D-Bus object paths to origins */ + GHashTable *origins; +}; + +static void calls_mm_provider_message_source_interface_init (CallsProviderInterface *iface); +static void calls_mm_provider_provider_interface_init (CallsProviderInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (CallsMMProvider, calls_mm_provider, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (CALLS_TYPE_MESSAGE_SOURCE, + calls_mm_provider_message_source_interface_init) + G_IMPLEMENT_INTERFACE (CALLS_TYPE_PROVIDER, + calls_mm_provider_provider_interface_init)) + + +enum { + PROP_0, + PROP_CONNECTION, + PROP_LAST_PROP, +}; +static GParamSpec *props[PROP_LAST_PROP]; + + +static const gchar * +get_name (CallsProvider *iface) +{ + return "ModemManager"; +} + + +static GList * +get_origins (CallsProvider *iface) +{ + CallsMMProvider *self = CALLS_MM_PROVIDER (iface); + return g_hash_table_get_values (self->origins); +} + + +static void +add_origin (CallsMMProvider *self, + GDBusObject *object) +{ + MMObject *mm_obj; + CallsMMOrigin *origin; + + g_debug ("Adding new voice-cable modem `%s'", + g_dbus_object_get_object_path (object)); + + g_assert (MM_IS_OBJECT (object)); + mm_obj = MM_OBJECT (object); + + origin = calls_mm_origin_new (mm_obj); + + g_hash_table_insert (self->origins, + mm_object_dup_path (mm_obj), + origin); + + g_signal_emit_by_name (CALLS_PROVIDER (self), + "origin-added", origin); +} + + +static void +interface_added_cb (CallsMMProvider *self, + GDBusObject *object, + GDBusInterface *interface) +{ + GDBusInterfaceInfo *info; + + info = g_dbus_interface_get_info (interface); + + if (g_strcmp0 (info->name, + "org.freedesktop.ModemManager1.Modem.Voice") == 0) + { + add_origin (self, object); + } +} + + +static void +remove_origin (CallsMMProvider *self, + GDBusObject *object) +{ + const gchar *path; + gpointer *origin; + + path = g_dbus_object_get_object_path (object); + + origin = g_hash_table_lookup (self->origins, path); + g_assert (origin != NULL && CALLS_IS_ORIGIN (origin)); + + g_signal_emit_by_name (CALLS_PROVIDER (self), + "origin-removed", CALLS_ORIGIN (origin)); + + g_hash_table_remove (self->origins, path); +} + + +static void +interface_removed_cb (CallsMMProvider *self, + GDBusObject *object, + GDBusInterface *interface) +{ + GDBusInterfaceInfo *info; + + info = g_dbus_interface_get_info (interface); + + if (g_strcmp0 (info->name, + "org.freedesktop.ModemManager1.Modem.Voice") != 0) + { + remove_origin (self, object); + } +} + + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + CallsMMProvider *self = CALLS_MM_PROVIDER (object); + + switch (property_id) { + case PROP_CONNECTION: + g_set_object (&self->connection, + g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + + +static void +add_mm_object (CallsMMProvider *self, GDBusObject *object) +{ + GList *ifaces, *node; + + ifaces = g_dbus_object_get_interfaces (object); + for (node = ifaces; node; node = node->next) + { + interface_added_cb (self, object, + G_DBUS_INTERFACE (node->data)); + } + + g_list_free_full (ifaces, g_object_unref); +} + + +static void +add_mm_objects (CallsMMProvider *self) +{ + GList *objects, *node; + + objects = g_dbus_object_manager_get_objects (G_DBUS_OBJECT_MANAGER (self->mm)); + for (node = objects; node; node = node->next) + { + add_mm_object (self, G_DBUS_OBJECT (node->data)); + } + + g_list_free_full (objects, g_object_unref); +} + + +static void +mm_manager_new_cb (GDBusConnection *connection, + GAsyncResult *res, + CallsMMProvider *self) +{ + GError *error = NULL; + + self->mm = mm_manager_new_finish (res, &error); + if (!self->mm) + { + g_error ("Error creating ModemManager Manager: %s", + error->message); + g_assert_not_reached(); + } + + + g_signal_connect_swapped (self->mm, "interface-added", + G_CALLBACK (interface_added_cb), self); + g_signal_connect_swapped (self->mm, "interface-removed", + G_CALLBACK (interface_removed_cb), self); + + add_mm_objects (self); + if (g_hash_table_size (self->origins) == 0) + { + g_warning ("No modems available"); + CALLS_EMIT_MESSAGE (self, "No modems available", + GTK_MESSAGE_WARNING); + } +} + + +static void +constructed (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsMMProvider *self = CALLS_MM_PROVIDER (object); + + mm_manager_new (self->connection, + G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE, + NULL, + (GAsyncReadyCallback) mm_manager_new_cb, + self); + + parent_class->constructed (object); +} + + +static void +dispose (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsMMProvider *self = CALLS_MM_PROVIDER (object); + + g_hash_table_remove_all (self->origins); + g_clear_object (&self->mm); + g_clear_object (&self->connection); + + parent_class->dispose (object); +} + + +static void +finalize (GObject *object) +{ + GObjectClass *parent_class = g_type_class_peek (G_TYPE_OBJECT); + CallsMMProvider *self = CALLS_MM_PROVIDER (object); + + g_hash_table_unref (self->origins); + + parent_class->finalize (object); +} + + +static void +calls_mm_provider_class_init (CallsMMProviderClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->finalize = finalize; + + props[PROP_CONNECTION] = + g_param_spec_object ("connection", + _("Connection"), + _("The D-Bus connection to use for communication with ModemManager"), + G_TYPE_DBUS_CONNECTION, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); +} + + +static void +calls_mm_provider_message_source_interface_init (CallsProviderInterface *iface) +{ +} + + +static void +calls_mm_provider_provider_interface_init (CallsProviderInterface *iface) +{ + iface->get_name = get_name; + iface->get_origins = get_origins; +} + + +static void +calls_mm_provider_init (CallsMMProvider *self) +{ + self->origins = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_object_unref); +} + + +CallsMMProvider * +calls_mm_provider_new (GDBusConnection *connection) +{ + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + + return g_object_new (CALLS_TYPE_MM_PROVIDER, + "connection", connection, + NULL); +} diff --git a/src/calls-mm-provider.h b/src/calls-mm-provider.h new file mode 100644 index 0000000..d8f5c40 --- /dev/null +++ b/src/calls-mm-provider.h @@ -0,0 +1,41 @@ +/* + * 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_MM_PROVIDER_H__ +#define CALLS_MM_PROVIDER_H__ + +#include +#include + +G_BEGIN_DECLS + +#define CALLS_TYPE_MM_PROVIDER (calls_mm_provider_get_type ()) + +G_DECLARE_FINAL_TYPE (CallsMMProvider, calls_mm_provider, CALLS, MM_PROVIDER, GObject); + +CallsMMProvider *calls_mm_provider_new (GDBusConnection *connection); + +G_END_DECLS + +#endif /* CALLS_MM_PROVIDER_H__ */ diff --git a/src/main.c b/src/main.c index f240cd9..591ea68 100644 --- a/src/main.c +++ b/src/main.c @@ -29,7 +29,7 @@ #include "calls-encryption-indicator.h" #include "calls-main-window.h" -#include "calls-ofono-provider.h" +#include "calls-mm-provider.h" static void show_window (GtkApplication *app) @@ -50,7 +50,8 @@ show_window (GtkApplication *app) return; } - provider = CALLS_PROVIDER (calls_ofono_provider_new (connection)); + provider = CALLS_PROVIDER (calls_mm_provider_new (connection)); + g_assert (provider != NULL); main_window = calls_main_window_new (app, provider); diff --git a/src/meson.build b/src/meson.build index 7d987ef..21d1066 100644 --- a/src/meson.build +++ b/src/meson.build @@ -26,6 +26,8 @@ gnome = import('gnome') calls_deps = [ dependency('gobject-2.0'), dependency('gtk+-3.0'), dependency('libhandy-0.0'), + dependency('ModemManager'), + dependency('mm-glib'), ] calls_sources = ['calls-message-source.c', 'calls-message-source.h', @@ -35,6 +37,9 @@ calls_sources = ['calls-message-source.c', 'calls-message-source.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', + 'calls-mm-call.c', 'calls-mm-call.h', + 'calls-mm-origin.c', 'calls-mm-origin.h', + 'calls-mm-provider.c', 'calls-mm-provider.h', 'calls-party.c', 'calls-party.h', 'calls-call-data.c', 'calls-call-data.h', 'calls-call-holder.c', 'calls-call-holder.h',