Move provider plugins into a dedicated directory
Since we will introduce another type of plugin for the policy engine we want to have each plugin type in separate directories. We also have to adjust: - plugin search directories - po file location - update paths for calls-doc target
This commit is contained in:
committed by
Guido Günther
parent
8c6ece6a87
commit
86a8f3ae22
147
plugins/provider/dummy/calls-dummy-call.c
Normal file
147
plugins/provider/dummy/calls-dummy-call.c
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#include "calls-dummy-call.h"
|
||||
#include "calls-message-source.h"
|
||||
#include "calls-call.h"
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
|
||||
struct _CallsDummyCall {
|
||||
GObject parent_instance;
|
||||
};
|
||||
|
||||
static void calls_dummy_call_message_source_interface_init (CallsMessageSourceInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (CallsDummyCall, calls_dummy_call, CALLS_TYPE_CALL,
|
||||
G_IMPLEMENT_INTERFACE (CALLS_TYPE_MESSAGE_SOURCE,
|
||||
calls_dummy_call_message_source_interface_init))
|
||||
|
||||
static const char*
|
||||
calls_dummy_call_get_protocol (CallsCall *call)
|
||||
{
|
||||
return "tel";
|
||||
}
|
||||
|
||||
static void
|
||||
calls_dummy_call_answer (CallsCall *call)
|
||||
{
|
||||
g_return_if_fail (CALLS_IS_DUMMY_CALL (call));
|
||||
g_return_if_fail (calls_call_get_state (call) == CALLS_CALL_STATE_INCOMING);
|
||||
|
||||
calls_call_set_state (call, CALLS_CALL_STATE_ACTIVE);
|
||||
}
|
||||
|
||||
static void
|
||||
calls_dummy_call_hang_up (CallsCall *call)
|
||||
{
|
||||
g_return_if_fail (CALLS_IS_DUMMY_CALL (call));
|
||||
|
||||
calls_call_set_state (call, CALLS_CALL_STATE_DISCONNECTED);
|
||||
}
|
||||
|
||||
static void
|
||||
calls_dummy_call_send_dtmf_tone (CallsCall *call,
|
||||
char key)
|
||||
{
|
||||
g_debug ("Beep! (%c)", key);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
outbound_timeout_cb (CallsDummyCall *self)
|
||||
{
|
||||
CallsCall *call;
|
||||
|
||||
g_assert (CALLS_IS_DUMMY_CALL (self));
|
||||
|
||||
call = CALLS_CALL (self);
|
||||
|
||||
switch (calls_call_get_state (call)) {
|
||||
case CALLS_CALL_STATE_DIALING:
|
||||
calls_call_set_state (call, CALLS_CALL_STATE_ALERTING);
|
||||
g_timeout_add_seconds
|
||||
(3, (GSourceFunc) outbound_timeout_cb, self);
|
||||
break;
|
||||
|
||||
case CALLS_CALL_STATE_ALERTING:
|
||||
calls_call_set_state (call, CALLS_CALL_STATE_ACTIVE);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
|
||||
CallsDummyCall *
|
||||
calls_dummy_call_new (const gchar *id,
|
||||
gboolean inbound)
|
||||
{
|
||||
return g_object_new (CALLS_TYPE_DUMMY_CALL,
|
||||
"id", id,
|
||||
"inbound", inbound,
|
||||
"call-type", CALLS_CALL_TYPE_CELLULAR,
|
||||
NULL);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
constructed (GObject *object)
|
||||
{
|
||||
CallsDummyCall *self = CALLS_DUMMY_CALL (object);
|
||||
|
||||
if (!calls_call_get_inbound (CALLS_CALL (object)))
|
||||
g_timeout_add_seconds (1, (GSourceFunc) outbound_timeout_cb, self);
|
||||
|
||||
G_OBJECT_CLASS (calls_dummy_call_parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_dummy_call_class_init (CallsDummyCallClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
CallsCallClass *call_class = CALLS_CALL_CLASS (klass);
|
||||
|
||||
object_class->constructed = constructed;
|
||||
|
||||
call_class->get_protocol = calls_dummy_call_get_protocol;
|
||||
call_class->answer = calls_dummy_call_answer;
|
||||
call_class->hang_up = calls_dummy_call_hang_up;
|
||||
call_class->send_dtmf_tone = calls_dummy_call_send_dtmf_tone;
|
||||
}
|
||||
|
||||
static void
|
||||
calls_dummy_call_message_source_interface_init (CallsMessageSourceInterface *iface)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_dummy_call_init (CallsDummyCall *self)
|
||||
{
|
||||
}
|
||||
43
plugins/provider/dummy/calls-dummy-call.h
Normal file
43
plugins/provider/dummy/calls-dummy-call.h
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALLS_DUMMY_CALL_H__
|
||||
#define CALLS_DUMMY_CALL_H__
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "calls-call.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_DUMMY_CALL (calls_dummy_call_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsDummyCall, calls_dummy_call, CALLS, DUMMY_CALL, CallsCall)
|
||||
|
||||
CallsDummyCall *calls_dummy_call_new (const gchar *number,
|
||||
gboolean inbound);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* CALLS_DUMMY_CALL_H__ */
|
||||
309
plugins/provider/dummy/calls-dummy-origin.c
Normal file
309
plugins/provider/dummy/calls-dummy-origin.c
Normal file
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#include "calls-dummy-origin.h"
|
||||
#include "calls-message-source.h"
|
||||
#include "calls-origin.h"
|
||||
#include "calls-dummy-call.h"
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
|
||||
struct _CallsDummyOrigin {
|
||||
GObject parent_instance;
|
||||
GString *name;
|
||||
GList *calls;
|
||||
};
|
||||
|
||||
static void calls_dummy_origin_message_source_interface_init (CallsOriginInterface *iface);
|
||||
static void calls_dummy_origin_origin_interface_init (CallsOriginInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (CallsDummyOrigin, calls_dummy_origin, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (CALLS_TYPE_MESSAGE_SOURCE,
|
||||
calls_dummy_origin_message_source_interface_init)
|
||||
G_IMPLEMENT_INTERFACE (CALLS_TYPE_ORIGIN,
|
||||
calls_dummy_origin_origin_interface_init))
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
|
||||
PROP_ID,
|
||||
/* Property for setting the origins name upon construction */
|
||||
PROP_DUMMY_NAME_CONSTRUCTOR,
|
||||
|
||||
/* The origins name. The implements the name property from CallsOrigin.
|
||||
* Readonly property, can't be set directly. */
|
||||
PROP_NAME,
|
||||
|
||||
PROP_CALLS,
|
||||
PROP_COUNTRY_CODE,
|
||||
PROP_LAST_PROP,
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
|
||||
static void
|
||||
remove_call (CallsDummyOrigin *self,
|
||||
CallsCall *call,
|
||||
const gchar *reason)
|
||||
{
|
||||
CallsOrigin *origin;
|
||||
|
||||
origin = CALLS_ORIGIN (self);
|
||||
self->calls = g_list_remove (self->calls, call);
|
||||
|
||||
g_signal_emit_by_name (origin, "call-removed", call, reason);
|
||||
|
||||
g_object_unref (G_OBJECT (call));
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
remove_calls (CallsDummyOrigin *self, const gchar *reason)
|
||||
{
|
||||
gpointer call;
|
||||
GList *next;
|
||||
|
||||
while (self->calls != NULL) {
|
||||
call = self->calls->data;
|
||||
next = self->calls->next;
|
||||
g_list_free_1 (self->calls);
|
||||
self->calls = next;
|
||||
|
||||
g_signal_emit_by_name (self, "call-removed", call, reason);
|
||||
g_object_unref (call);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct DisconnectedData {
|
||||
CallsDummyOrigin *self;
|
||||
CallsCall *call;
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
call_state_changed_cb (CallsCall *call,
|
||||
GParamSpec *pspec,
|
||||
CallsDummyOrigin *self)
|
||||
{
|
||||
g_assert (CALLS_IS_DUMMY_ORIGIN (self));
|
||||
g_assert (CALLS_IS_DUMMY_CALL (call));
|
||||
|
||||
if (calls_call_get_state (call) != CALLS_CALL_STATE_DISCONNECTED)
|
||||
return;
|
||||
|
||||
remove_call (self, call, "Disconnected");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
add_call (CallsDummyOrigin *self, const gchar *number, gboolean inbound)
|
||||
{
|
||||
CallsDummyCall *dummy_call;
|
||||
CallsCall *call;
|
||||
|
||||
dummy_call = calls_dummy_call_new (number, inbound);
|
||||
g_assert (dummy_call != NULL);
|
||||
|
||||
call = CALLS_CALL (dummy_call);
|
||||
g_signal_connect (call, "notify::state",
|
||||
G_CALLBACK (call_state_changed_cb),
|
||||
self);
|
||||
self->calls = g_list_append (self->calls, dummy_call);
|
||||
|
||||
g_signal_emit_by_name (CALLS_ORIGIN (self), "call-added", call);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dial (CallsOrigin *origin, const gchar *number)
|
||||
{
|
||||
g_return_if_fail (number != NULL);
|
||||
g_return_if_fail (CALLS_IS_DUMMY_ORIGIN (origin));
|
||||
|
||||
add_call (CALLS_DUMMY_ORIGIN (origin), number, FALSE);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
supports_protocol (CallsOrigin *origin,
|
||||
const char *protocol)
|
||||
{
|
||||
g_assert (protocol != NULL);
|
||||
g_assert (CALLS_IS_DUMMY_ORIGIN (origin));
|
||||
|
||||
return g_strcmp0 (protocol, "tel") == 0;
|
||||
}
|
||||
|
||||
|
||||
CallsDummyOrigin *
|
||||
calls_dummy_origin_new (const gchar *name)
|
||||
{
|
||||
return g_object_new (CALLS_TYPE_DUMMY_ORIGIN,
|
||||
"dummy-name-constructor", name,
|
||||
NULL);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsDummyOrigin *self = CALLS_DUMMY_ORIGIN (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_DUMMY_NAME_CONSTRUCTOR:
|
||||
g_string_assign (self->name, g_value_get_string (value));
|
||||
break;
|
||||
|
||||
case PROP_ID: /* ignored for the dummy origin */
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsDummyOrigin *self = CALLS_DUMMY_ORIGIN (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_ID:
|
||||
g_value_set_string (value, self->name->str);
|
||||
break;
|
||||
|
||||
case PROP_NAME:
|
||||
g_value_set_string (value, self->name->str);
|
||||
break;
|
||||
|
||||
case PROP_CALLS:
|
||||
g_value_set_pointer (value, g_list_copy (self->calls));
|
||||
break;
|
||||
|
||||
case PROP_COUNTRY_CODE:
|
||||
g_value_set_string (value, NULL);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dispose (GObject *object)
|
||||
{
|
||||
CallsDummyOrigin *self = CALLS_DUMMY_ORIGIN (object);
|
||||
|
||||
remove_calls (self, NULL);
|
||||
|
||||
G_OBJECT_CLASS (calls_dummy_origin_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
finalize (GObject *object)
|
||||
{
|
||||
CallsDummyOrigin *self = CALLS_DUMMY_ORIGIN (object);
|
||||
|
||||
g_string_free (self->name, TRUE);
|
||||
g_list_free (self->calls);
|
||||
|
||||
G_OBJECT_CLASS (calls_dummy_origin_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_dummy_origin_class_init (CallsDummyOriginClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->dispose = dispose;
|
||||
object_class->finalize = finalize;
|
||||
object_class->get_property = get_property;
|
||||
object_class->set_property = set_property;
|
||||
|
||||
props[PROP_DUMMY_NAME_CONSTRUCTOR] =
|
||||
g_param_spec_string ("dummy-name-constructor",
|
||||
"Name",
|
||||
"The name of the origin",
|
||||
"Dummy origin",
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
|
||||
g_object_class_install_property (object_class, PROP_DUMMY_NAME_CONSTRUCTOR, props[PROP_DUMMY_NAME_CONSTRUCTOR]);
|
||||
|
||||
|
||||
#define IMPLEMENTS(ID, NAME) \
|
||||
g_object_class_override_property (object_class, ID, NAME); \
|
||||
props[ID] = g_object_class_find_property(object_class, NAME);
|
||||
|
||||
IMPLEMENTS (PROP_ID, "id");
|
||||
IMPLEMENTS (PROP_NAME, "name");
|
||||
IMPLEMENTS (PROP_CALLS, "calls");
|
||||
IMPLEMENTS (PROP_COUNTRY_CODE, "country-code");
|
||||
|
||||
#undef IMPLEMENTS
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_dummy_origin_message_source_interface_init (CallsOriginInterface *iface)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_dummy_origin_origin_interface_init (CallsOriginInterface *iface)
|
||||
{
|
||||
iface->dial = dial;
|
||||
iface->supports_protocol = supports_protocol;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_dummy_origin_init (CallsDummyOrigin *self)
|
||||
{
|
||||
self->name = g_string_new (NULL);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
calls_dummy_origin_create_inbound (CallsDummyOrigin *self,
|
||||
const gchar *number)
|
||||
{
|
||||
g_return_if_fail (CALLS_IS_DUMMY_ORIGIN (self));
|
||||
|
||||
add_call (self, number, TRUE);
|
||||
}
|
||||
39
plugins/provider/dummy/calls-dummy-origin.h
Normal file
39
plugins/provider/dummy/calls-dummy-origin.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_DUMMY_ORIGIN (calls_dummy_origin_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsDummyOrigin, calls_dummy_origin, CALLS, DUMMY_ORIGIN, GObject);
|
||||
|
||||
CallsDummyOrigin *calls_dummy_origin_new (const gchar *name);
|
||||
void calls_dummy_origin_create_inbound (CallsDummyOrigin *self,
|
||||
const gchar *number);
|
||||
|
||||
G_END_DECLS
|
||||
232
plugins/provider/dummy/calls-dummy-provider.c
Normal file
232
plugins/provider/dummy/calls-dummy-provider.c
Normal file
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "CallsDummyProvider"
|
||||
|
||||
#include "calls-dummy-provider.h"
|
||||
#include "calls-message-source.h"
|
||||
#include "calls-provider.h"
|
||||
#include "calls-dummy-origin.h"
|
||||
|
||||
#include <libpeas/peas.h>
|
||||
#include <glib-unix.h>
|
||||
|
||||
static const char * const supported_protocols[] = {
|
||||
"tel",
|
||||
NULL
|
||||
};
|
||||
|
||||
struct _CallsDummyProvider {
|
||||
CallsProvider parent_instance;
|
||||
|
||||
GListStore *origins;
|
||||
};
|
||||
|
||||
static void calls_dummy_provider_message_source_interface_init (CallsMessageSourceInterface *iface);
|
||||
|
||||
|
||||
#ifdef FOR_TESTING
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE
|
||||
(CallsDummyProvider, calls_dummy_provider, CALLS_TYPE_PROVIDER,
|
||||
G_IMPLEMENT_INTERFACE (CALLS_TYPE_MESSAGE_SOURCE,
|
||||
calls_dummy_provider_message_source_interface_init))
|
||||
|
||||
#else
|
||||
|
||||
G_DEFINE_DYNAMIC_TYPE_EXTENDED
|
||||
(CallsDummyProvider, calls_dummy_provider, CALLS_TYPE_PROVIDER, 0,
|
||||
G_IMPLEMENT_INTERFACE_DYNAMIC (CALLS_TYPE_MESSAGE_SOURCE,
|
||||
calls_dummy_provider_message_source_interface_init))
|
||||
|
||||
#endif /* FOR_TESTING */
|
||||
|
||||
|
||||
static gboolean
|
||||
usr1_handler (CallsDummyProvider *self)
|
||||
{
|
||||
GListModel *model;
|
||||
|
||||
g_autoptr (CallsDummyOrigin) origin = NULL;
|
||||
|
||||
model = G_LIST_MODEL (self->origins);
|
||||
g_return_val_if_fail (g_list_model_get_n_items (model) > 0, FALSE);
|
||||
|
||||
g_debug ("Received SIGUSR1, adding new incoming call");
|
||||
|
||||
origin = g_list_model_get_item (model, 0);
|
||||
calls_dummy_origin_create_inbound (origin, "0987654321");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
usr2_handler (CallsDummyProvider *self)
|
||||
{
|
||||
g_autoptr (CallsDummyOrigin) origin = NULL;
|
||||
|
||||
GListModel *model;
|
||||
|
||||
model = G_LIST_MODEL (self->origins);
|
||||
g_return_val_if_fail (g_list_model_get_n_items (model) > 0, FALSE);
|
||||
|
||||
g_debug ("Received SIGUSR2, adding new anonymous incoming call");
|
||||
|
||||
origin = g_list_model_get_item (model, 0);
|
||||
calls_dummy_origin_create_inbound (origin, NULL);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static const char *
|
||||
calls_dummy_provider_get_name (CallsProvider *provider)
|
||||
{
|
||||
return "Dummy provider";
|
||||
}
|
||||
|
||||
static const char *
|
||||
calls_dummy_provider_get_status (CallsProvider *provider)
|
||||
{
|
||||
return "Normal";
|
||||
}
|
||||
|
||||
static GListModel *
|
||||
calls_dummy_provider_get_origins (CallsProvider *provider)
|
||||
{
|
||||
CallsDummyProvider *self = CALLS_DUMMY_PROVIDER (provider);
|
||||
|
||||
return G_LIST_MODEL (self->origins);
|
||||
}
|
||||
|
||||
static const char *const *
|
||||
calls_dummy_provider_get_protocols (CallsProvider *provider)
|
||||
{
|
||||
return supported_protocols;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
calls_dummy_provider_is_modem (CallsProvider *provider)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
constructed (GObject *object)
|
||||
{
|
||||
CallsDummyProvider *self = CALLS_DUMMY_PROVIDER (object);
|
||||
|
||||
calls_dummy_provider_add_origin (self, "Dummy origin");
|
||||
|
||||
g_unix_signal_add (SIGUSR1,
|
||||
(GSourceFunc) usr1_handler,
|
||||
self);
|
||||
g_unix_signal_add (SIGUSR2,
|
||||
(GSourceFunc) usr2_handler,
|
||||
self);
|
||||
|
||||
G_OBJECT_CLASS (calls_dummy_provider_parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dispose (GObject *object)
|
||||
{
|
||||
CallsDummyProvider *self = CALLS_DUMMY_PROVIDER (object);
|
||||
|
||||
g_list_store_remove_all (self->origins);
|
||||
g_clear_object (&self->origins);
|
||||
|
||||
G_OBJECT_CLASS (calls_dummy_provider_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_dummy_provider_class_init (CallsDummyProviderClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
CallsProviderClass *provider_class = CALLS_PROVIDER_CLASS (klass);
|
||||
|
||||
object_class->constructed = constructed;
|
||||
object_class->dispose = dispose;
|
||||
|
||||
provider_class->get_name = calls_dummy_provider_get_name;
|
||||
provider_class->get_status = calls_dummy_provider_get_status;
|
||||
provider_class->get_origins = calls_dummy_provider_get_origins;
|
||||
provider_class->get_protocols = calls_dummy_provider_get_protocols;
|
||||
provider_class->is_modem = calls_dummy_provider_is_modem;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_dummy_provider_message_source_interface_init (CallsMessageSourceInterface *iface)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_dummy_provider_init (CallsDummyProvider *self)
|
||||
{
|
||||
self->origins = g_list_store_new (CALLS_TYPE_ORIGIN);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
calls_dummy_provider_add_origin (CallsDummyProvider *self,
|
||||
const gchar *name)
|
||||
{
|
||||
g_autoptr (CallsDummyOrigin) origin = NULL;
|
||||
|
||||
origin = calls_dummy_origin_new (name);
|
||||
g_list_store_append (self->origins, origin);
|
||||
}
|
||||
|
||||
|
||||
CallsDummyProvider *
|
||||
calls_dummy_provider_new (void)
|
||||
{
|
||||
return g_object_new (CALLS_TYPE_DUMMY_PROVIDER, NULL);
|
||||
}
|
||||
|
||||
|
||||
#ifndef FOR_TESTING
|
||||
|
||||
static void
|
||||
calls_dummy_provider_class_finalize (CallsDummyProviderClass *klass)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
G_MODULE_EXPORT void
|
||||
peas_register_types (PeasObjectModule *module)
|
||||
{
|
||||
calls_dummy_provider_register_type (G_TYPE_MODULE (module));
|
||||
|
||||
peas_object_module_register_extension_type (module,
|
||||
CALLS_TYPE_PROVIDER,
|
||||
CALLS_TYPE_DUMMY_PROVIDER);
|
||||
}
|
||||
|
||||
#endif /* FOR_TESTING */
|
||||
46
plugins/provider/dummy/calls-dummy-provider.h
Normal file
46
plugins/provider/dummy/calls-dummy-provider.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALLS_DUMMY_PROVIDER_H__
|
||||
#define CALLS_DUMMY_PROVIDER_H__
|
||||
|
||||
#include "calls-provider.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <libpeas/peas.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_DUMMY_PROVIDER (calls_dummy_provider_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsDummyProvider, calls_dummy_provider, CALLS, DUMMY_PROVIDER, CallsProvider)
|
||||
|
||||
CallsDummyProvider *calls_dummy_provider_new (void);
|
||||
void calls_dummy_provider_add_origin (CallsDummyProvider *self,
|
||||
const gchar *name);
|
||||
void peas_register_types (PeasObjectModule *module);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* CALLS_DUMMY_PROVIDER_H__ */
|
||||
7
plugins/provider/dummy/dummy.plugin.in
Normal file
7
plugins/provider/dummy/dummy.plugin.in
Normal file
@@ -0,0 +1,7 @@
|
||||
[Plugin]
|
||||
Module=dummy
|
||||
Name=Dummy
|
||||
Description=Dummy calls provider
|
||||
Authors=Bob Ham <rah@settrans.net>
|
||||
Copyright=Copyright (C) 2018 Purism SPC
|
||||
Website=@PACKAGE_URL_RAW@
|
||||
57
plugins/provider/dummy/meson.build
Normal file
57
plugins/provider/dummy/meson.build
Normal file
@@ -0,0 +1,57 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author: Bob Ham <bob.ham@puri.sm>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
dummy_include = include_directories('.')
|
||||
|
||||
dummy_install_dir = join_paths(full_calls_plugin_libdir, 'dummy')
|
||||
|
||||
dummy_plugin = configure_file(
|
||||
input: 'dummy.plugin.in',
|
||||
output: 'dummy.plugin',
|
||||
configuration: config_data,
|
||||
install_dir: dummy_install_dir
|
||||
)
|
||||
|
||||
dummy_deps = [
|
||||
dependency('gobject-2.0'),
|
||||
dependency('gtk+-3.0'),
|
||||
dependency('libpeas-1.0'),
|
||||
]
|
||||
|
||||
dummy_sources = files(
|
||||
[
|
||||
'calls-dummy-call.c', 'calls-dummy-call.h',
|
||||
'calls-dummy-origin.c', 'calls-dummy-origin.h',
|
||||
'calls-dummy-provider.c', 'calls-dummy-provider.h'
|
||||
]
|
||||
)
|
||||
|
||||
calls_dummy = shared_module(
|
||||
'dummy',
|
||||
dummy_sources,
|
||||
dependencies: dummy_deps,
|
||||
include_directories: src_include,
|
||||
link_with: libcalls,
|
||||
install: true,
|
||||
install_dir: dummy_install_dir
|
||||
)
|
||||
412
plugins/provider/mm/calls-mm-call.c
Normal file
412
plugins/provider/mm/calls-mm-call.c
Normal file
@@ -0,0 +1,412 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "CallsMMCall"
|
||||
|
||||
#include "calls-mm-call.h"
|
||||
#include "calls-call.h"
|
||||
#include "calls-message-source.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <libmm-glib/libmm-glib.h>
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
|
||||
struct _CallsMMCall {
|
||||
GObject parent_instance;
|
||||
MMCall *mm_call;
|
||||
gchar *disconnect_reason;
|
||||
};
|
||||
|
||||
static void calls_mm_call_message_source_interface_init (CallsMessageSourceInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (CallsMMCall, calls_mm_call, CALLS_TYPE_CALL,
|
||||
G_IMPLEMENT_INTERFACE (CALLS_TYPE_MESSAGE_SOURCE,
|
||||
calls_mm_call_message_source_interface_init))
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_MM_CALL,
|
||||
PROP_LAST_PROP,
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
static void
|
||||
notify_id_cb (CallsMMCall *self,
|
||||
const gchar *id)
|
||||
{
|
||||
calls_call_set_id (CALLS_CALL (self), id);
|
||||
}
|
||||
|
||||
|
||||
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, N_("Unknown reason")),
|
||||
row (OUTGOING_STARTED, N_("Outgoing call started")),
|
||||
row (INCOMING_NEW, N_("New incoming call")),
|
||||
row (ACCEPTED, N_("Call accepted")),
|
||||
row (TERMINATED, N_("Call ended")),
|
||||
row (REFUSED_OR_BUSY, N_("Call disconnected (busy or call refused)")),
|
||||
row (ERROR, N_("Call disconnected (wrong id or network problem)")),
|
||||
row (AUDIO_SETUP_FAILED, N_("Call disconnected (error setting up audio channel)")),
|
||||
row (TRANSFERRED, N_("Call transferred")),
|
||||
row (DEFLECTED, N_("Call deflected")),
|
||||
|
||||
#undef row
|
||||
|
||||
{ -1, NULL }
|
||||
};
|
||||
|
||||
static void
|
||||
set_disconnect_reason (CallsMMCall *self,
|
||||
MMCallStateReason reason)
|
||||
{
|
||||
const struct CallsMMCallStateReasonMap *map_row;
|
||||
|
||||
if (self->disconnect_reason)
|
||||
g_free (self->disconnect_reason);
|
||||
|
||||
for (map_row = STATE_REASON_MAP; map_row->desc; ++map_row) {
|
||||
if (map_row->value == reason) {
|
||||
self->disconnect_reason =
|
||||
g_strdup (gettext (map_row->desc));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self->disconnect_reason =
|
||||
g_strdup_printf (_("Call disconnected (unknown reason code %i)"),
|
||||
(int) reason);
|
||||
|
||||
g_warning ("%s", self->disconnect_reason);
|
||||
}
|
||||
|
||||
|
||||
struct CallsMMCallStateMap {
|
||||
MMCallState mm;
|
||||
CallsCallState calls;
|
||||
const gchar *desc;
|
||||
};
|
||||
|
||||
static const struct CallsMMCallStateMap STATE_MAP[] = {
|
||||
|
||||
#define row(MMENUM,CALLSENUM) \
|
||||
{ MM_CALL_STATE_##MMENUM, CALLS_CALL_STATE_##CALLSENUM, #MMENUM } \
|
||||
|
||||
row (UNKNOWN, DIALING),
|
||||
row (DIALING, DIALING),
|
||||
row (RINGING_OUT, ALERTING),
|
||||
row (RINGING_IN, INCOMING),
|
||||
row (ACTIVE, ACTIVE),
|
||||
row (HELD, HELD),
|
||||
row (WAITING, INCOMING),
|
||||
row (TERMINATED, DISCONNECTED),
|
||||
|
||||
#undef row
|
||||
|
||||
{ -1, -1 }
|
||||
};
|
||||
|
||||
static void
|
||||
state_changed_cb (CallsMMCall *self,
|
||||
MMCallState old_state,
|
||||
MMCallState new_state,
|
||||
MMCallStateReason reason)
|
||||
{
|
||||
const struct CallsMMCallStateMap *state_map_row;
|
||||
const struct CallsMMCallStateReasonMap *reason_map_row;
|
||||
const char *state_str = "state unmatched";
|
||||
const char *reason_str = "reason unmatched";
|
||||
|
||||
if (new_state == MM_CALL_STATE_TERMINATED)
|
||||
set_disconnect_reason (self, reason);
|
||||
|
||||
|
||||
for (state_map_row = STATE_MAP; state_map_row->mm != -1; state_map_row++) {
|
||||
if (state_map_row->mm == new_state) {
|
||||
state_str = state_map_row->desc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_assert_cmpint (state_map_row->mm, !=, -1);
|
||||
|
||||
for (reason_map_row = STATE_REASON_MAP; reason_map_row->value != -1; reason_map_row++) {
|
||||
if (reason_map_row->value == reason) {
|
||||
reason_str = gettext (reason_map_row->desc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_assert_cmpint (reason_map_row->value, !=, -1);
|
||||
|
||||
g_debug ("MM call '%s' changed state to `%s': %s",
|
||||
mm_call_get_path (self->mm_call),
|
||||
state_str,
|
||||
reason_str);
|
||||
calls_call_set_state (CALLS_CALL (self), state_map_row->calls);
|
||||
}
|
||||
|
||||
|
||||
static const char *
|
||||
calls_mm_call_get_protocol (CallsCall *self)
|
||||
{
|
||||
return "tel";
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
ok = data->finish_func (mm_call, res, &error);
|
||||
if (!ok) {
|
||||
g_warning ("Error %s MM call '%s': %s (domain: %s [%d])",
|
||||
data->desc,
|
||||
mm_call_get_path (mm_call),
|
||||
error->message,
|
||||
g_quark_to_string (error->domain),
|
||||
error->code);
|
||||
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, calls_mm_call_answer, "accepting");
|
||||
DEFINE_OPERATION (hangup, calls_mm_call_hang_up, "hanging up");
|
||||
DEFINE_OPERATION (start, calls_mm_call_start_call, "starting outgoing call");
|
||||
|
||||
|
||||
static void
|
||||
calls_mm_call_send_dtmf_tone (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)
|
||||
{
|
||||
CallsMMCall *self = CALLS_MM_CALL (object);
|
||||
MmGdbusCall *gdbus_call = MM_GDBUS_CALL (self->mm_call);
|
||||
MMCallState state;
|
||||
MMCallDirection direction;
|
||||
const char *number;
|
||||
const char *path;
|
||||
gboolean outgoing;
|
||||
|
||||
g_signal_connect_swapped (gdbus_call, "notify::number",
|
||||
G_CALLBACK (notify_id_cb), self);
|
||||
g_signal_connect_swapped (gdbus_call, "state-changed",
|
||||
G_CALLBACK (state_changed_cb), self);
|
||||
|
||||
number = mm_call_get_number (self->mm_call);
|
||||
notify_id_cb (self, number);
|
||||
|
||||
state = mm_call_get_state (self->mm_call);
|
||||
state_changed_cb (self,
|
||||
MM_CALL_STATE_UNKNOWN,
|
||||
state,
|
||||
mm_call_get_state_reason (self->mm_call));
|
||||
|
||||
direction = mm_call_get_direction (self->mm_call);
|
||||
outgoing = direction == MM_CALL_DIRECTION_OUTGOING;
|
||||
|
||||
/* Start outgoing call */
|
||||
if (state == MM_CALL_STATE_UNKNOWN &&
|
||||
outgoing)
|
||||
calls_mm_call_start_call (CALLS_CALL (self));
|
||||
|
||||
path = mm_call_get_path (self->mm_call);
|
||||
|
||||
if (direction == MM_CALL_DIRECTION_UNKNOWN)
|
||||
g_debug ("New call (%s) with '%s'",
|
||||
path, number);
|
||||
else
|
||||
g_debug ("New %s call (%s) %s %s",
|
||||
outgoing ? "outgoing" : "incoming",
|
||||
path,
|
||||
outgoing ? "to" : "from",
|
||||
number);
|
||||
|
||||
G_OBJECT_CLASS (calls_mm_call_parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
static void
|
||||
dispose (GObject *object)
|
||||
{
|
||||
CallsMMCall *self = CALLS_MM_CALL (object);
|
||||
|
||||
g_clear_object (&self->mm_call);
|
||||
|
||||
G_OBJECT_CLASS (calls_mm_call_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
finalize (GObject *object)
|
||||
{
|
||||
CallsMMCall *self = CALLS_MM_CALL (object);
|
||||
|
||||
g_free (self->disconnect_reason);
|
||||
|
||||
G_OBJECT_CLASS (calls_mm_call_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_mm_call_class_init (CallsMMCallClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
CallsCallClass *call_class = CALLS_CALL_CLASS (klass);
|
||||
|
||||
object_class->set_property = set_property;
|
||||
object_class->constructed = constructed;
|
||||
object_class->dispose = dispose;
|
||||
object_class->finalize = finalize;
|
||||
|
||||
call_class->get_protocol = calls_mm_call_get_protocol;
|
||||
call_class->answer = calls_mm_call_answer;
|
||||
call_class->hang_up = calls_mm_call_hang_up;
|
||||
call_class->send_dtmf_tone = calls_mm_call_send_dtmf_tone;
|
||||
|
||||
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_property (object_class, PROP_MM_CALL, props[PROP_MM_CALL]);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_mm_call_message_source_interface_init (CallsMessageSourceInterface *iface)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
calls_mm_call_init (CallsMMCall *self)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
CallsMMCall *
|
||||
calls_mm_call_new (MMCall *mm_call)
|
||||
{
|
||||
gboolean inbound;
|
||||
|
||||
g_return_val_if_fail (MM_IS_CALL (mm_call), NULL);
|
||||
|
||||
inbound = mm_call_get_direction (mm_call) == MM_CALL_DIRECTION_INCOMING;
|
||||
return g_object_new (CALLS_TYPE_MM_CALL,
|
||||
"mm-call", mm_call,
|
||||
"inbound", inbound,
|
||||
"call-type", CALLS_CALL_TYPE_CELLULAR,
|
||||
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;
|
||||
}
|
||||
45
plugins/provider/mm/calls-mm-call.h
Normal file
45
plugins/provider/mm/calls-mm-call.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALLS_MM_CALL_H__
|
||||
#define CALLS_MM_CALL_H__
|
||||
|
||||
#include <libmm-glib.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "calls-call.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_MM_CALL (calls_mm_call_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsMMCall, calls_mm_call, CALLS, MM_CALL, CallsCall)
|
||||
|
||||
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__ */
|
||||
947
plugins/provider/mm/calls-mm-origin.c
Normal file
947
plugins/provider/mm/calls-mm-origin.c
Normal file
@@ -0,0 +1,947 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "CallsMMOrigin"
|
||||
|
||||
#include "calls-mm-origin.h"
|
||||
#include "calls-origin.h"
|
||||
#include "calls-ussd.h"
|
||||
#include "calls-mm-call.h"
|
||||
#include "calls-message-source.h"
|
||||
#include "itu-e212-iso.h"
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
|
||||
struct _CallsMMOrigin {
|
||||
GObject parent_instance;
|
||||
MMObject *mm_obj;
|
||||
MMModemVoice *voice;
|
||||
MMModem3gppUssd *ussd;
|
||||
MMSim *sim;
|
||||
|
||||
/* XXX: These should be used only for pointer comparison,
|
||||
* The content should never be used as it might be
|
||||
* pointing to a freed location */
|
||||
char *last_ussd_request;
|
||||
char *last_ussd_response;
|
||||
|
||||
gulong ussd_handle_id;
|
||||
|
||||
char *id;
|
||||
char *name;
|
||||
GHashTable *calls;
|
||||
char *country_code;
|
||||
};
|
||||
|
||||
static void calls_mm_origin_message_source_interface_init (CallsOriginInterface *iface);
|
||||
static void calls_mm_origin_origin_interface_init (CallsOriginInterface *iface);
|
||||
static void calls_mm_origin_ussd_interface_init (CallsUssdInterface *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_USSD,
|
||||
calls_mm_origin_ussd_interface_init)
|
||||
G_IMPLEMENT_INTERFACE (CALLS_TYPE_ORIGIN,
|
||||
calls_mm_origin_origin_interface_init))
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_ID,
|
||||
PROP_NAME,
|
||||
PROP_CALLS,
|
||||
PROP_MODEM,
|
||||
PROP_COUNTRY_CODE,
|
||||
PROP_LAST_PROP,
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
|
||||
static void
|
||||
ussd_initiate_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
MMModem3gppUssd *ussd = (MMModem3gppUssd *) object;
|
||||
|
||||
g_autoptr (GTask) task = user_data;
|
||||
CallsMMOrigin *self = user_data;
|
||||
char *response = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
g_assert (G_IS_TASK (task));
|
||||
self = g_task_get_source_object (task);
|
||||
|
||||
g_assert (MM_IS_MODEM_3GPP_USSD (ussd));
|
||||
g_assert (CALLS_IS_MM_ORIGIN (self));
|
||||
|
||||
response = mm_modem_3gpp_ussd_initiate_finish (ussd, result, &error);
|
||||
|
||||
if (error)
|
||||
g_task_return_error (task, error);
|
||||
else
|
||||
g_task_return_pointer (task, response, g_free);
|
||||
}
|
||||
|
||||
static void
|
||||
ussd_reinitiate_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
CallsUssd *ussd = (CallsUssd *) object;
|
||||
|
||||
g_autoptr (GTask) task = user_data;
|
||||
CallsMMOrigin *self = user_data;
|
||||
GCancellable *cancellable;
|
||||
GError *error = NULL;
|
||||
const char *command;
|
||||
|
||||
g_assert (G_IS_TASK (task));
|
||||
self = g_task_get_source_object (task);
|
||||
|
||||
g_assert (CALLS_IS_USSD (ussd));
|
||||
g_assert (CALLS_IS_MM_ORIGIN (self));
|
||||
|
||||
calls_ussd_cancel_finish (ussd, result, &error);
|
||||
cancellable = g_task_get_cancellable (task);
|
||||
command = g_task_get_task_data (task);
|
||||
|
||||
if (error)
|
||||
g_task_return_error (task, error);
|
||||
else
|
||||
mm_modem_3gpp_ussd_initiate (self->ussd, command, cancellable,
|
||||
ussd_initiate_cb, g_steal_pointer (&task));
|
||||
}
|
||||
|
||||
static void
|
||||
ussd_respond_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
MMModem3gppUssd *ussd = (MMModem3gppUssd *) object;
|
||||
CallsMMOrigin *self;
|
||||
|
||||
g_autoptr (GTask) task = user_data;
|
||||
char *response = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
g_assert (G_IS_TASK (task));
|
||||
self = g_task_get_source_object (task);
|
||||
|
||||
g_assert (CALLS_IS_MM_ORIGIN (self));
|
||||
g_assert (MM_IS_MODEM_3GPP_USSD (ussd));
|
||||
|
||||
response = mm_modem_3gpp_ussd_respond_finish (ussd, result, &error);
|
||||
|
||||
if (error)
|
||||
g_task_return_error (task, error);
|
||||
else
|
||||
g_task_return_pointer (task, response, g_free);
|
||||
}
|
||||
|
||||
static void
|
||||
ussd_cancel_cb (GObject *object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
MMModem3gppUssd *ussd = (MMModem3gppUssd *) object;
|
||||
CallsMMOrigin *self;
|
||||
|
||||
g_autoptr (GTask) task = user_data;
|
||||
GError *error = NULL;
|
||||
gboolean response;
|
||||
|
||||
g_assert (G_IS_TASK (task));
|
||||
self = g_task_get_source_object (task);
|
||||
|
||||
g_assert (CALLS_IS_MM_ORIGIN (self));
|
||||
g_assert (MM_IS_MODEM_3GPP_USSD (ussd));
|
||||
|
||||
response = mm_modem_3gpp_ussd_cancel_finish (ussd, result, &error);
|
||||
|
||||
if (error)
|
||||
g_task_return_error (task, error);
|
||||
else
|
||||
g_task_return_boolean (task, response);
|
||||
}
|
||||
|
||||
static CallsUssdState
|
||||
calls_mm_ussd_get_state (CallsUssd *ussd)
|
||||
{
|
||||
CallsMMOrigin *self = CALLS_MM_ORIGIN (ussd);
|
||||
|
||||
if (!self->ussd)
|
||||
return CALLS_USSD_STATE_UNKNOWN;
|
||||
|
||||
return mm_modem_3gpp_ussd_get_state (self->ussd);
|
||||
}
|
||||
|
||||
static void
|
||||
calls_mm_ussd_initiate_async (CallsUssd *ussd,
|
||||
const char *command,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
CallsMMOrigin *self = CALLS_MM_ORIGIN (ussd);
|
||||
|
||||
g_autoptr (GTask) task = NULL;
|
||||
CallsUssdState state;
|
||||
|
||||
g_return_if_fail (CALLS_IS_USSD (ussd));
|
||||
|
||||
task = g_task_new (self, cancellable, callback, user_data);
|
||||
|
||||
if (!self->ussd) {
|
||||
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
||||
"No USSD interface found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!command || !*command) {
|
||||
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"USSD command empty");
|
||||
return;
|
||||
}
|
||||
|
||||
state = calls_ussd_get_state (CALLS_USSD (self));
|
||||
g_task_set_task_data (task, g_strdup (command), g_free);
|
||||
|
||||
if (state == CALLS_USSD_STATE_ACTIVE ||
|
||||
state == CALLS_USSD_STATE_USER_RESPONSE)
|
||||
calls_ussd_cancel_async (CALLS_USSD (self), cancellable,
|
||||
ussd_reinitiate_cb, g_steal_pointer (&task));
|
||||
else
|
||||
mm_modem_3gpp_ussd_initiate (self->ussd, command, cancellable,
|
||||
ussd_initiate_cb, g_steal_pointer (&task));
|
||||
}
|
||||
|
||||
static char *
|
||||
calls_mm_ussd_initiate_finish (CallsUssd *ussd,
|
||||
GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_USSD (ussd), NULL);
|
||||
g_return_val_if_fail (G_IS_TASK (result), NULL);
|
||||
|
||||
|
||||
return g_task_propagate_pointer (G_TASK (result), error);
|
||||
}
|
||||
|
||||
static void
|
||||
calls_mm_ussd_respond_async (CallsUssd *ussd,
|
||||
const char *response,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
CallsMMOrigin *self = CALLS_MM_ORIGIN (ussd);
|
||||
GTask *task;
|
||||
|
||||
g_return_if_fail (CALLS_IS_USSD (ussd));
|
||||
|
||||
task = g_task_new (self, cancellable, callback, user_data);
|
||||
mm_modem_3gpp_ussd_respond (self->ussd, response, cancellable,
|
||||
ussd_respond_cb, task);
|
||||
}
|
||||
|
||||
static char *
|
||||
calls_mm_ussd_respond_finish (CallsUssd *ussd,
|
||||
GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_USSD (ussd), NULL);
|
||||
g_return_val_if_fail (G_IS_TASK (result), NULL);
|
||||
|
||||
|
||||
return g_task_propagate_pointer (G_TASK (result), error);
|
||||
}
|
||||
|
||||
static void
|
||||
calls_mm_ussd_cancel_async (CallsUssd *ussd,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
CallsMMOrigin *self = CALLS_MM_ORIGIN (ussd);
|
||||
GTask *task;
|
||||
|
||||
g_return_if_fail (CALLS_IS_USSD (ussd));
|
||||
|
||||
task = g_task_new (self, cancellable, callback, user_data);
|
||||
mm_modem_3gpp_ussd_cancel (self->ussd, cancellable,
|
||||
ussd_cancel_cb, task);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
calls_mm_ussd_cancel_finish (CallsUssd *ussd,
|
||||
GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_USSD (ussd), FALSE);
|
||||
g_return_val_if_fail (G_IS_TASK (result), FALSE);
|
||||
|
||||
|
||||
return g_task_propagate_boolean (G_TASK (result), error);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dial_cb (MMModemVoice *voice,
|
||||
GAsyncResult *res,
|
||||
CallsMMOrigin *self)
|
||||
{
|
||||
MMCall *call;
|
||||
|
||||
g_autoptr (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 *call_props;
|
||||
|
||||
g_assert (self->voice != NULL);
|
||||
|
||||
call_props = mm_call_properties_new ();
|
||||
mm_call_properties_set_number (call_props, number);
|
||||
|
||||
mm_modem_voice_create_call
|
||||
(self->voice,
|
||||
call_props,
|
||||
NULL,
|
||||
(GAsyncReadyCallback) dial_cb,
|
||||
self);
|
||||
|
||||
g_object_unref (call_props);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
supports_protocol (CallsOrigin *origin,
|
||||
const char *protocol)
|
||||
{
|
||||
g_assert (protocol);
|
||||
g_assert (CALLS_IS_MM_ORIGIN (origin));
|
||||
|
||||
return g_strcmp0 (protocol, "tel") == 0;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
remove_calls (CallsMMOrigin *self, const gchar *reason)
|
||||
{
|
||||
GList *paths, *node;
|
||||
gpointer call;
|
||||
|
||||
paths = g_hash_table_get_keys (self->calls);
|
||||
|
||||
for (node = paths; node != NULL; node = node->next) {
|
||||
g_hash_table_steal_extended (self->calls, node->data, NULL, &call);
|
||||
g_signal_emit_by_name (self, "call-removed",
|
||||
CALLS_CALL (call), reason);
|
||||
g_object_unref (call);
|
||||
}
|
||||
|
||||
g_list_free_full (paths, g_free);
|
||||
}
|
||||
|
||||
|
||||
struct CallsMMOriginDeleteCallData {
|
||||
CallsMMOrigin *self;
|
||||
gchar *path;
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
delete_call_cb (MMModemVoice *voice,
|
||||
GAsyncResult *res,
|
||||
struct CallsMMOriginDeleteCallData *data)
|
||||
{
|
||||
gboolean ok;
|
||||
|
||||
g_autoptr (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
|
||||
delete_call (CallsMMOrigin *self,
|
||||
CallsMMCall *call)
|
||||
{
|
||||
const gchar *path;
|
||||
struct CallsMMOriginDeleteCallData *data;
|
||||
|
||||
path = calls_mm_call_get_object_path (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
|
||||
call_state_changed_cb (CallsCall *call,
|
||||
GParamSpec *pspec,
|
||||
CallsMMOrigin *self)
|
||||
{
|
||||
g_assert (CALLS_IS_MM_ORIGIN (self));
|
||||
g_assert (CALLS_IS_MM_CALL (call));
|
||||
|
||||
if (calls_call_get_state (call) != CALLS_CALL_STATE_DISCONNECTED)
|
||||
return;
|
||||
|
||||
delete_call (self, CALLS_MM_CALL (call));
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
add_call (CallsMMOrigin *self,
|
||||
MMCall *mm_call)
|
||||
{
|
||||
CallsMMCall *call;
|
||||
gchar *path;
|
||||
|
||||
call = calls_mm_call_new (mm_call);
|
||||
|
||||
g_signal_connect (call, "notify::state",
|
||||
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));
|
||||
|
||||
if (mm_call_get_state (mm_call) == MM_CALL_STATE_TERMINATED) {
|
||||
/* Delete any remnant disconnected call */
|
||||
delete_call (self, call);
|
||||
}
|
||||
|
||||
g_debug ("Call `%s' added", path);
|
||||
|
||||
/* FIXME: Hang up the call, since accepting a secondary call does not currently work.
|
||||
* CallsMMCall[28822]: WARNING: Error accepting ModemManager call to `+4916XXXXXXXX': GDBus.Error:org.freedesktop.ModemManager1.Error.Core.Failed: This call was not ringing, cannot accept
|
||||
*/
|
||||
if (g_hash_table_size (self->calls) > 1)
|
||||
calls_call_hang_up (CALLS_CALL (call));
|
||||
}
|
||||
|
||||
|
||||
struct CallsMMOriginCallAddedData {
|
||||
CallsMMOrigin *self;
|
||||
gchar *path;
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
call_added_list_calls_cb (MMModemVoice *voice,
|
||||
GAsyncResult *res,
|
||||
struct CallsMMOriginCallAddedData *data)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
GList *calls;
|
||||
|
||||
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)
|
||||
{
|
||||
gpointer call;
|
||||
gpointer key;
|
||||
GString *reason;
|
||||
const gchar *mm_reason;
|
||||
|
||||
g_debug ("Removing call `%s'", path);
|
||||
|
||||
g_hash_table_steal_extended (self->calls, path, &key, &call);
|
||||
|
||||
g_free (key);
|
||||
|
||||
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 (CALLS_MM_CALL (call));
|
||||
if (mm_reason) {
|
||||
g_string_assign (reason, mm_reason);
|
||||
}
|
||||
|
||||
g_signal_emit_by_name (self, "call-removed", call, reason);
|
||||
|
||||
g_object_unref (call);
|
||||
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;
|
||||
|
||||
g_autoptr (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_ID:
|
||||
self->id = g_value_dup_string (value);
|
||||
break;
|
||||
|
||||
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 void
|
||||
get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsMMOrigin *self = CALLS_MM_ORIGIN (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_ID:
|
||||
g_value_set_string (value, self->id);
|
||||
break;
|
||||
|
||||
case PROP_NAME:
|
||||
g_value_set_string (value, self->name);
|
||||
break;
|
||||
|
||||
case PROP_CALLS:
|
||||
g_value_set_pointer (value, g_hash_table_get_values (self->calls));
|
||||
break;
|
||||
|
||||
case PROP_COUNTRY_CODE:
|
||||
g_value_set_string (value, self->country_code);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static gchar *
|
||||
modem_get_name (MMModem *modem)
|
||||
{
|
||||
char *name = NULL;
|
||||
const char * const *numbers = NULL;
|
||||
|
||||
numbers = mm_modem_get_own_numbers (modem);
|
||||
if (numbers && g_strv_length ((char **) numbers) > 0) {
|
||||
name = g_strdup (numbers[0]);
|
||||
return name;
|
||||
}
|
||||
|
||||
#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
|
||||
ussd_properties_changed_cb (CallsMMOrigin *self,
|
||||
GVariant *properties)
|
||||
{
|
||||
const char *response;
|
||||
GVariant *value;
|
||||
CallsUssdState state;
|
||||
|
||||
g_assert (CALLS_IS_MM_ORIGIN (self));
|
||||
|
||||
state = calls_ussd_get_state (CALLS_USSD (self));
|
||||
|
||||
value = g_variant_lookup_value (properties, "State", NULL);
|
||||
if (value)
|
||||
g_signal_emit_by_name (self, "ussd-state-changed");
|
||||
g_clear_pointer (&value, g_variant_unref);
|
||||
|
||||
/* XXX: We check for user state only because the NetworkRequest
|
||||
* dbus property change isn't regularly emitted */
|
||||
if (state == CALLS_USSD_STATE_USER_RESPONSE ||
|
||||
(value = g_variant_lookup_value (properties, "NetworkRequest", NULL))) {
|
||||
response = mm_modem_3gpp_ussd_get_network_request (self->ussd);
|
||||
|
||||
if (response && *response && response != self->last_ussd_request)
|
||||
g_signal_emit_by_name (self, "ussd-added", response);
|
||||
|
||||
if (response && *response)
|
||||
self->last_ussd_request = (char *) response;
|
||||
g_clear_pointer (&value, g_variant_unref);
|
||||
}
|
||||
|
||||
if (state != CALLS_USSD_STATE_USER_RESPONSE &&
|
||||
(value = g_variant_lookup_value (properties, "NetworkNotification", NULL))) {
|
||||
response = mm_modem_3gpp_ussd_get_network_notification (self->ussd);
|
||||
|
||||
if (response && *response && response != self->last_ussd_response)
|
||||
g_signal_emit_by_name (self, "ussd-added", response);
|
||||
|
||||
if (response && *response)
|
||||
self->last_ussd_response = (char *) response;
|
||||
g_clear_pointer (&value, g_variant_unref);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
call_mm_ussd_changed_cb (CallsMMOrigin *self)
|
||||
{
|
||||
g_assert (CALLS_IS_MM_ORIGIN (self));
|
||||
|
||||
if (self->ussd_handle_id)
|
||||
g_signal_handler_disconnect (self, self->ussd_handle_id);
|
||||
|
||||
self->ussd_handle_id = 0;
|
||||
|
||||
g_clear_object (&self->ussd);
|
||||
self->ussd = mm_object_get_modem_3gpp_ussd (self->mm_obj);
|
||||
|
||||
/* XXX: We hook to dbus properties changed because the regular signal emission is inconsistent */
|
||||
if (self->ussd)
|
||||
self->ussd_handle_id = g_signal_connect_object (self->ussd, "g-properties-changed",
|
||||
G_CALLBACK (ussd_properties_changed_cb), self,
|
||||
G_CONNECT_SWAPPED);
|
||||
}
|
||||
|
||||
static void
|
||||
get_sim_ready_cb (MMModem *modem,
|
||||
GAsyncResult *res,
|
||||
gpointer user_data)
|
||||
{
|
||||
const char *code;
|
||||
CallsMMOrigin *self = CALLS_MM_ORIGIN (user_data);
|
||||
|
||||
self->sim = mm_modem_get_sim_finish (modem, res, NULL);
|
||||
|
||||
code = get_country_iso_for_mcc (mm_sim_get_imsi (self->sim));
|
||||
if (code) {
|
||||
if (g_strcmp0 (self->country_code, code) == 0)
|
||||
return;
|
||||
|
||||
g_debug ("Setting the country code to `%s'", code);
|
||||
|
||||
self->country_code = g_strdup (code);
|
||||
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_COUNTRY_CODE]);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
constructed (GObject *object)
|
||||
{
|
||||
CallsMMOrigin *self = CALLS_MM_ORIGIN (object);
|
||||
MmGdbusModemVoice *gdbus_voice;
|
||||
|
||||
self->name = modem_get_name (mm_object_get_modem (self->mm_obj));
|
||||
|
||||
mm_modem_get_sim (mm_object_get_modem (self->mm_obj),
|
||||
NULL,
|
||||
(GAsyncReadyCallback) get_sim_ready_cb,
|
||||
self);
|
||||
|
||||
g_signal_connect_object (self->mm_obj, "notify::modem3gpp-ussd",
|
||||
G_CALLBACK (call_mm_ussd_changed_cb), self,
|
||||
G_CONNECT_SWAPPED);
|
||||
call_mm_ussd_changed_cb (self);
|
||||
|
||||
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);
|
||||
G_OBJECT_CLASS (calls_mm_origin_parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dispose (GObject *object)
|
||||
{
|
||||
CallsMMOrigin *self = CALLS_MM_ORIGIN (object);
|
||||
|
||||
remove_calls (self, NULL);
|
||||
g_clear_object (&self->mm_obj);
|
||||
g_clear_object (&self->ussd);
|
||||
g_clear_object (&self->sim);
|
||||
g_clear_pointer (&self->country_code, g_free);
|
||||
|
||||
G_OBJECT_CLASS (calls_mm_origin_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
finalize (GObject *object)
|
||||
{
|
||||
CallsMMOrigin *self = CALLS_MM_ORIGIN (object);
|
||||
|
||||
g_hash_table_unref (self->calls);
|
||||
g_free (self->name);
|
||||
|
||||
G_OBJECT_CLASS (calls_mm_origin_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_mm_origin_class_init (CallsMMOriginClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->get_property = get_property;
|
||||
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_property (object_class, PROP_MODEM, props[PROP_MODEM]);
|
||||
|
||||
#define IMPLEMENTS(ID, NAME) \
|
||||
g_object_class_override_property (object_class, ID, NAME); \
|
||||
props[ID] = g_object_class_find_property(object_class, NAME);
|
||||
|
||||
IMPLEMENTS (PROP_ID, "id");
|
||||
IMPLEMENTS (PROP_NAME, "name");
|
||||
IMPLEMENTS (PROP_CALLS, "calls");
|
||||
IMPLEMENTS (PROP_COUNTRY_CODE, "country-code");
|
||||
|
||||
#undef IMPLEMENTS
|
||||
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_mm_origin_message_source_interface_init (CallsOriginInterface *iface)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_mm_origin_ussd_interface_init (CallsUssdInterface *iface)
|
||||
{
|
||||
iface->get_state = calls_mm_ussd_get_state;
|
||||
iface->initiate_async = calls_mm_ussd_initiate_async;
|
||||
iface->initiate_finish = calls_mm_ussd_initiate_finish;
|
||||
iface->respond_async = calls_mm_ussd_respond_async;
|
||||
iface->respond_finish = calls_mm_ussd_respond_finish;
|
||||
iface->cancel_async = calls_mm_ussd_cancel_async;
|
||||
iface->cancel_finish = calls_mm_ussd_cancel_finish;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_mm_origin_origin_interface_init (CallsOriginInterface *iface)
|
||||
{
|
||||
iface->dial = dial;
|
||||
iface->supports_protocol = supports_protocol;
|
||||
}
|
||||
|
||||
|
||||
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,
|
||||
const char *id)
|
||||
{
|
||||
return g_object_new (CALLS_TYPE_MM_ORIGIN,
|
||||
"mm-object", mm_obj,
|
||||
"id", id,
|
||||
NULL);
|
||||
}
|
||||
|
||||
gboolean
|
||||
calls_mm_origin_matches (CallsMMOrigin *self,
|
||||
MMObject *mm_obj)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_MM_ORIGIN (self), FALSE);
|
||||
g_return_val_if_fail (MM_IS_OBJECT (mm_obj), FALSE);
|
||||
|
||||
if (self->mm_obj)
|
||||
return g_strcmp0 (mm_object_get_path (mm_obj),
|
||||
mm_object_get_path (self->mm_obj)) == 0;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
44
plugins/provider/mm/calls-mm-origin.h
Normal file
44
plugins/provider/mm/calls-mm-origin.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALLS_MM_ORIGIN_H__
|
||||
#define CALLS_MM_ORIGIN_H__
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <libmm-glib.h>
|
||||
|
||||
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,
|
||||
const char *id);
|
||||
gboolean calls_mm_origin_matches (CallsMMOrigin *self,
|
||||
MMObject *modem);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* CALLS_MM_ORIGIN_H__ */
|
||||
458
plugins/provider/mm/calls-mm-provider.c
Normal file
458
plugins/provider/mm/calls-mm-provider.c
Normal file
@@ -0,0 +1,458 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "CallsMMProvider"
|
||||
|
||||
#include "calls-mm-provider.h"
|
||||
#include "calls-provider.h"
|
||||
#include "calls-mm-origin.h"
|
||||
#include "calls-message-source.h"
|
||||
#include "calls-origin.h"
|
||||
|
||||
#include <libmm-glib.h>
|
||||
#include <libpeas/peas.h>
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
static const char * const supported_protocols[] = {
|
||||
"tel",
|
||||
NULL
|
||||
};
|
||||
|
||||
struct _CallsMMProvider {
|
||||
CallsProvider parent_instance;
|
||||
|
||||
/* The status property */
|
||||
gchar *status;
|
||||
/** ID for the D-Bus watch */
|
||||
guint watch_id;
|
||||
/** ModemManager object proxy */
|
||||
MMManager *mm;
|
||||
/* A list of CallsOrigins */
|
||||
GListStore *origins;
|
||||
};
|
||||
|
||||
static void calls_mm_provider_message_source_interface_init (CallsMessageSourceInterface *iface);
|
||||
|
||||
G_DEFINE_DYNAMIC_TYPE_EXTENDED
|
||||
(CallsMMProvider, calls_mm_provider, CALLS_TYPE_PROVIDER, 0,
|
||||
G_IMPLEMENT_INTERFACE_DYNAMIC (CALLS_TYPE_MESSAGE_SOURCE,
|
||||
calls_mm_provider_message_source_interface_init))
|
||||
|
||||
|
||||
static void
|
||||
set_status (CallsMMProvider *self,
|
||||
const gchar *new_status)
|
||||
{
|
||||
if (strcmp (self->status, new_status) == 0)
|
||||
return;
|
||||
|
||||
g_free (self->status);
|
||||
self->status = g_strdup (new_status);
|
||||
g_object_notify (G_OBJECT (self), "status");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
update_status (CallsMMProvider *self)
|
||||
{
|
||||
const gchar *s;
|
||||
|
||||
if (!self->mm) {
|
||||
s = _("ModemManager unavailable");
|
||||
} else if (g_list_model_get_n_items (G_LIST_MODEL (self->origins)) == 0) {
|
||||
s = _("No voice-capable modem available");
|
||||
} else {
|
||||
s = _("Normal");
|
||||
}
|
||||
|
||||
set_status (self, s);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
mm_provider_contains (CallsMMProvider *self,
|
||||
MMObject *mm_obj)
|
||||
{
|
||||
GListModel *model;
|
||||
guint n_items;
|
||||
|
||||
g_assert (CALLS_IS_MM_PROVIDER (self));
|
||||
g_assert (MM_OBJECT (mm_obj));
|
||||
|
||||
model = G_LIST_MODEL (self->origins);
|
||||
n_items = g_list_model_get_n_items (model);
|
||||
|
||||
for (guint i = 0; i < n_items; i++) {
|
||||
g_autoptr (CallsMMOrigin) origin = NULL;
|
||||
|
||||
origin = g_list_model_get_item (model, i);
|
||||
|
||||
if (calls_mm_origin_matches (origin, mm_obj))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
add_origin (CallsMMProvider *self,
|
||||
GDBusObject *object)
|
||||
{
|
||||
MMObject *mm_obj;
|
||||
|
||||
g_autoptr (CallsMMOrigin) origin = NULL;
|
||||
g_autoptr (MMModem3gpp) modem_3gpp = NULL;
|
||||
const gchar *path;
|
||||
g_autofree char *imei = NULL;
|
||||
|
||||
mm_obj = MM_OBJECT (object);
|
||||
path = g_dbus_object_get_object_path (object);
|
||||
if (mm_provider_contains (self, mm_obj)) {
|
||||
g_warning ("New voice interface on existing"
|
||||
" origin with path `%s'", path);
|
||||
return;
|
||||
}
|
||||
|
||||
g_debug ("Adding new voice-capable modem `%s'",
|
||||
path);
|
||||
|
||||
g_assert (MM_IS_OBJECT (object));
|
||||
|
||||
modem_3gpp = mm_object_get_modem_3gpp (mm_obj);
|
||||
|
||||
origin = calls_mm_origin_new (mm_obj,
|
||||
mm_modem_3gpp_get_imei (modem_3gpp));
|
||||
g_list_store_append (self->origins, origin);
|
||||
|
||||
update_status (self);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
interface_added_cb (CallsMMProvider *self,
|
||||
GDBusObject *object,
|
||||
GDBusInterface *interface)
|
||||
{
|
||||
GDBusInterfaceInfo *info;
|
||||
|
||||
info = g_dbus_interface_get_info (interface);
|
||||
|
||||
g_debug ("ModemManager interface `%s' found on object `%s'",
|
||||
info->name,
|
||||
g_dbus_object_get_object_path (object));
|
||||
|
||||
if (g_strcmp0 (info->name,
|
||||
"org.freedesktop.ModemManager1.Modem.Voice") == 0) {
|
||||
add_origin (self, object);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
remove_modem_object (CallsMMProvider *self,
|
||||
const gchar *path,
|
||||
GDBusObject *object)
|
||||
{
|
||||
GListModel *model;
|
||||
guint n_items;
|
||||
|
||||
model = G_LIST_MODEL (self->origins);
|
||||
n_items = g_list_model_get_n_items (model);
|
||||
|
||||
for (guint i = 0; i < n_items; i++) {
|
||||
g_autoptr (CallsMMOrigin) origin = NULL;
|
||||
|
||||
origin = g_list_model_get_item (model, i);
|
||||
|
||||
if (calls_mm_origin_matches (origin, MM_OBJECT (object))) {
|
||||
g_list_store_remove (self->origins, i);
|
||||
update_status (self);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
interface_removed_cb (CallsMMProvider *self,
|
||||
GDBusObject *object,
|
||||
GDBusInterface *interface)
|
||||
{
|
||||
const gchar *path;
|
||||
GDBusInterfaceInfo *info;
|
||||
|
||||
path = g_dbus_object_get_object_path (object);
|
||||
info = g_dbus_interface_get_info (interface);
|
||||
|
||||
g_debug ("ModemManager interface `%s' removed on object `%s'",
|
||||
info->name, path);
|
||||
|
||||
if (g_strcmp0 (info->name,
|
||||
"org.freedesktop.ModemManager1.Modem.Voice") == 0) {
|
||||
remove_modem_object (self, path, object);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
object_added_cb (CallsMMProvider *self,
|
||||
GDBusObject *object)
|
||||
{
|
||||
g_debug ("ModemManager object `%s' added",
|
||||
g_dbus_object_get_object_path (object));
|
||||
|
||||
add_mm_object (self, object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
object_removed_cb (CallsMMProvider *self,
|
||||
GDBusObject *object)
|
||||
{
|
||||
const gchar *path;
|
||||
|
||||
path = g_dbus_object_get_object_path (object);
|
||||
g_debug ("ModemManager object `%s' removed", path);
|
||||
|
||||
remove_modem_object (self, path, object);
|
||||
}
|
||||
|
||||
|
||||
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 (G_DBUS_OBJECT_MANAGER (self->mm),
|
||||
"interface-added",
|
||||
G_CALLBACK (interface_added_cb), self);
|
||||
g_signal_connect_swapped (G_DBUS_OBJECT_MANAGER (self->mm),
|
||||
"interface-removed",
|
||||
G_CALLBACK (interface_removed_cb), self);
|
||||
g_signal_connect_swapped (G_DBUS_OBJECT_MANAGER (self->mm),
|
||||
"object-added",
|
||||
G_CALLBACK (object_added_cb), self);
|
||||
g_signal_connect_swapped (G_DBUS_OBJECT_MANAGER (self->mm),
|
||||
"object-removed",
|
||||
G_CALLBACK (object_removed_cb), self);
|
||||
|
||||
update_status (self);
|
||||
add_mm_objects (self);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
mm_appeared_cb (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
const gchar *name_owner,
|
||||
CallsMMProvider *self)
|
||||
{
|
||||
g_debug ("ModemManager appeared on D-Bus");
|
||||
|
||||
mm_manager_new (connection,
|
||||
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
|
||||
NULL,
|
||||
(GAsyncReadyCallback) mm_manager_new_cb,
|
||||
self);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
mm_vanished_cb (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
CallsMMProvider *self)
|
||||
{
|
||||
g_debug ("ModemManager vanished from D-Bus");
|
||||
g_list_store_remove_all (self->origins);
|
||||
update_status (self);
|
||||
}
|
||||
|
||||
|
||||
static const char *
|
||||
calls_mm_provider_get_name (CallsProvider *provider)
|
||||
{
|
||||
return "ModemManager";
|
||||
}
|
||||
|
||||
static const char *
|
||||
calls_mm_provider_get_status (CallsProvider *provider)
|
||||
{
|
||||
CallsMMProvider *self = CALLS_MM_PROVIDER (provider);
|
||||
|
||||
return self->status;
|
||||
}
|
||||
|
||||
static GListModel *
|
||||
calls_mm_provider_get_origins (CallsProvider *provider)
|
||||
{
|
||||
CallsMMProvider *self = CALLS_MM_PROVIDER (provider);
|
||||
|
||||
return G_LIST_MODEL (self->origins);
|
||||
}
|
||||
|
||||
static const char *const *
|
||||
calls_mm_provider_get_protocols (CallsProvider *provider)
|
||||
{
|
||||
return supported_protocols;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
calls_mm_provider_is_modem (CallsProvider *provider)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
constructed (GObject *object)
|
||||
{
|
||||
CallsMMProvider *self = CALLS_MM_PROVIDER (object);
|
||||
|
||||
self->watch_id =
|
||||
g_bus_watch_name (G_BUS_TYPE_SYSTEM,
|
||||
MM_DBUS_SERVICE,
|
||||
G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
|
||||
(GBusNameAppearedCallback) mm_appeared_cb,
|
||||
(GBusNameVanishedCallback) mm_vanished_cb,
|
||||
self, NULL);
|
||||
|
||||
g_debug ("Watching for ModemManager");
|
||||
|
||||
G_OBJECT_CLASS (calls_mm_provider_parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dispose (GObject *object)
|
||||
{
|
||||
CallsMMProvider *self = CALLS_MM_PROVIDER (object);
|
||||
|
||||
if (self->watch_id) {
|
||||
g_bus_unwatch_name (self->watch_id);
|
||||
self->watch_id = 0;
|
||||
}
|
||||
|
||||
g_list_store_remove_all (self->origins);
|
||||
|
||||
G_OBJECT_CLASS (calls_mm_provider_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
finalize (GObject *object)
|
||||
{
|
||||
CallsMMProvider *self = CALLS_MM_PROVIDER (object);
|
||||
|
||||
g_object_unref (self->origins);
|
||||
g_free (self->status);
|
||||
|
||||
G_OBJECT_CLASS (calls_mm_provider_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_mm_provider_class_init (CallsMMProviderClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
CallsProviderClass *provider_class = CALLS_PROVIDER_CLASS (klass);
|
||||
|
||||
object_class->constructed = constructed;
|
||||
object_class->dispose = dispose;
|
||||
object_class->finalize = finalize;
|
||||
|
||||
provider_class->get_name = calls_mm_provider_get_name;
|
||||
provider_class->get_status = calls_mm_provider_get_status;
|
||||
provider_class->get_origins = calls_mm_provider_get_origins;
|
||||
provider_class->get_protocols = calls_mm_provider_get_protocols;
|
||||
provider_class->is_modem = calls_mm_provider_is_modem;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_mm_provider_class_finalize (CallsMMProviderClass *klass)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
calls_mm_provider_message_source_interface_init (CallsMessageSourceInterface *iface)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_mm_provider_init (CallsMMProvider *self)
|
||||
{
|
||||
self->status = g_strdup (_("Initialized"));
|
||||
self->origins = g_list_store_new (CALLS_TYPE_ORIGIN);
|
||||
}
|
||||
|
||||
|
||||
G_MODULE_EXPORT void
|
||||
peas_register_types (PeasObjectModule *module)
|
||||
{
|
||||
calls_mm_provider_register_type (G_TYPE_MODULE (module));
|
||||
|
||||
peas_object_module_register_extension_type (module,
|
||||
CALLS_TYPE_PROVIDER,
|
||||
CALLS_TYPE_MM_PROVIDER);
|
||||
}
|
||||
44
plugins/provider/mm/calls-mm-provider.h
Normal file
44
plugins/provider/mm/calls-mm-provider.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALLS_MM_PROVIDER_H__
|
||||
#define CALLS_MM_PROVIDER_H__
|
||||
|
||||
#include "calls-provider.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <gio/gio.h>
|
||||
#include <libpeas/peas.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_MM_PROVIDER (calls_mm_provider_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsMMProvider, calls_mm_provider, CALLS, MM_PROVIDER, CallsProvider)
|
||||
|
||||
void peas_register_types (PeasObjectModule *module);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* CALLS_MM_PROVIDER_H__ */
|
||||
294
plugins/provider/mm/itu-e212-iso.h
Normal file
294
plugins/provider/mm/itu-e212-iso.h
Normal file
@@ -0,0 +1,294 @@
|
||||
/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
|
||||
/* itu-e212-iso.h
|
||||
*
|
||||
* Copyright 2020 Purism SPC
|
||||
*
|
||||
* Mapping from E.212 MCC to Countries in ISO 3166-1 alpha-2 format
|
||||
*
|
||||
* Author(s):
|
||||
* Mohammed Sadiq <sadiq@sadiqpk.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
|
||||
struct mcc_list {
|
||||
guint mcc;
|
||||
char code[3];
|
||||
};
|
||||
|
||||
/*
|
||||
* Extracted from:
|
||||
* https://www.itu.int/dms_pub/itu-t/opb/sp/T-SP-E.212B-2018-PDF-E.pdf
|
||||
*/
|
||||
struct mcc_list mcc_list[] = {
|
||||
{202, "GR"},
|
||||
{204, "NL"},
|
||||
{206, "BE"},
|
||||
{208, "FR"},
|
||||
{212, "MC"},
|
||||
{213, "AD"},
|
||||
{214, "ES"},
|
||||
{216, "HU"},
|
||||
{218, "BA"},
|
||||
{219, "HR"},
|
||||
{220, "RS"},
|
||||
{221, "XK"},
|
||||
{222, "IT"},
|
||||
{225, "VA"},
|
||||
{226, "RO"},
|
||||
{228, "CH"},
|
||||
{230, "CZ"},
|
||||
{231, "SK"},
|
||||
{232, "AT"},
|
||||
{234, "GB"},
|
||||
{235, "GB"},
|
||||
{238, "DK"},
|
||||
{240, "SE"},
|
||||
{242, "NO"},
|
||||
{244, "FI"},
|
||||
{246, "LT"},
|
||||
{247, "LV"},
|
||||
{248, "EE"},
|
||||
{250, "RU"},
|
||||
{255, "UA"},
|
||||
{257, "BY"},
|
||||
{259, "MD"},
|
||||
{260, "PL"},
|
||||
{262, "DE"},
|
||||
{266, "GI"},
|
||||
{268, "PT"},
|
||||
{270, "LU"},
|
||||
{272, "IE"},
|
||||
{274, "IS"},
|
||||
{276, "AL"},
|
||||
{278, "MT"},
|
||||
{280, "CY"},
|
||||
{282, "GE"},
|
||||
{283, "AM"},
|
||||
{284, "BG"},
|
||||
{286, "TR"},
|
||||
{288, "FO"},
|
||||
{290, "GL"},
|
||||
{292, "SM"},
|
||||
{293, "SI"},
|
||||
{294, "MK"},
|
||||
{295, "LI"},
|
||||
{297, "ME"},
|
||||
{302, "CA"},
|
||||
{308, "PM"},
|
||||
{310, "US"},
|
||||
{311, "US"},
|
||||
{312, "US"},
|
||||
{313, "US"},
|
||||
{314, "US"},
|
||||
{315, "US"},
|
||||
{316, "US"},
|
||||
{330, "PR"},
|
||||
{332, "VI"},
|
||||
{334, "MX"},
|
||||
{338, "JM"},
|
||||
/* Guadeloupe and Martinique are part of France */
|
||||
{340, "GP"},
|
||||
{340, "MQ"},
|
||||
{342, "BB"},
|
||||
{344, "AG"},
|
||||
{346, "KY"},
|
||||
{348, "VG"},
|
||||
{350, "BM"},
|
||||
{352, "GD"},
|
||||
{354, "MS"},
|
||||
{356, "KN"},
|
||||
{358, "LC"},
|
||||
{360, "VC"},
|
||||
{362, "CW"},
|
||||
{363, "AW"},
|
||||
{364, "BS"},
|
||||
{365, "AI"},
|
||||
{366, "DM"},
|
||||
{368, "CU"},
|
||||
{370, "DO"},
|
||||
{372, "HT"},
|
||||
{374, "TT"},
|
||||
{376, "TC"},
|
||||
{400, "AZ"},
|
||||
{401, "KZ"},
|
||||
{402, "BT"},
|
||||
{404, "IN"},
|
||||
{405, "IN"},
|
||||
{406, "IN"},
|
||||
{410, "PK"},
|
||||
{412, "AF"},
|
||||
{413, "LK"},
|
||||
{414, "MM"},
|
||||
{415, "LB"},
|
||||
{416, "JO"},
|
||||
{417, "SY"},
|
||||
{418, "IQ"},
|
||||
{419, "KW"},
|
||||
{420, "SA"},
|
||||
{421, "YE"},
|
||||
{422, "OM"},
|
||||
{424, "AE"},
|
||||
{425, "IL"},
|
||||
{426, "BH"},
|
||||
{427, "QA"},
|
||||
{428, "MN"},
|
||||
{429, "NP"},
|
||||
{430, "AE"},
|
||||
{431, "AE"},
|
||||
{432, "IR"},
|
||||
{434, "UZ"},
|
||||
{436, "TJ"},
|
||||
{437, "KG"},
|
||||
{438, "TM"},
|
||||
{440, "JP"},
|
||||
{441, "JP"},
|
||||
{450, "KP"},
|
||||
{452, "VN"},
|
||||
{454, "HK"},
|
||||
{455, "MO"},
|
||||
{456, "KH"},
|
||||
{457, "LA"},
|
||||
{460, "CN"},
|
||||
{461, "CN"},
|
||||
{466, "TW"},
|
||||
{467, "KR"},
|
||||
{470, "BD"},
|
||||
{472, "MV"},
|
||||
{502, "MY"},
|
||||
{505, "AU"},
|
||||
{510, "ID"},
|
||||
{514, "TL"},
|
||||
{515, "PH"},
|
||||
{520, "TH"},
|
||||
{525, "SG"},
|
||||
{528, "BN"},
|
||||
{530, "NZ"},
|
||||
{536, "NR"},
|
||||
{537, "PG"},
|
||||
{539, "TO"},
|
||||
{540, "SB"},
|
||||
{541, "VU"},
|
||||
{542, "FJ"},
|
||||
{543, "WF"},
|
||||
{544, "AS"},
|
||||
{545, "KI"},
|
||||
{546, "NC"},
|
||||
{547, "PF"},
|
||||
{548, "CK"},
|
||||
{549, "AS"},
|
||||
{550, "FM"},
|
||||
{551, "MH"},
|
||||
{552, "PW"},
|
||||
{553, "TV"},
|
||||
{554, "TK"},
|
||||
{555, "NU"},
|
||||
{602, "EG"},
|
||||
{603, "DZ"},
|
||||
{604, "MA"},
|
||||
{605, "TN"},
|
||||
{606, "LY"},
|
||||
{607, "GM"},
|
||||
{608, "SN"},
|
||||
{609, "MR"},
|
||||
{610, "ML"},
|
||||
{611, "GN"},
|
||||
{612, "CI"},
|
||||
{613, "BF"},
|
||||
{614, "NE"},
|
||||
{615, "TG"},
|
||||
{616, "BJ"},
|
||||
{617, "MU"},
|
||||
{618, "LR"},
|
||||
{619, "SL"},
|
||||
{620, "GH"},
|
||||
{621, "NG"},
|
||||
{622, "TD"},
|
||||
{623, "CF"},
|
||||
{624, "CM"},
|
||||
{625, "CV"},
|
||||
{626, "ST"},
|
||||
{627, "GQ"},
|
||||
{628, "GA"},
|
||||
{629, "CG"},
|
||||
{630, "CD"},
|
||||
{631, "AO"},
|
||||
{632, "GW"},
|
||||
{633, "SC"},
|
||||
{634, "SD"},
|
||||
{635, "RW"},
|
||||
{636, "ET"},
|
||||
{637, "SO"},
|
||||
{638, "DJ"},
|
||||
{639, "KE"},
|
||||
{640, "TZ"},
|
||||
{641, "UG"},
|
||||
{642, "BI"},
|
||||
{643, "MZ"},
|
||||
{645, "ZM"},
|
||||
{646, "MG"},
|
||||
{647, "RE"},
|
||||
{648, "ZW"},
|
||||
{649, "NA"},
|
||||
{650, "MW"},
|
||||
{651, "LS"},
|
||||
{652, "BW"},
|
||||
{653, "SZ"},
|
||||
{654, "KM"},
|
||||
{655, "ZA"},
|
||||
{657, "ER"},
|
||||
{658, "SH"},
|
||||
{659, "SS"},
|
||||
{702, "BZ"},
|
||||
{704, "GT"},
|
||||
{706, "SV"},
|
||||
{708, "HN"},
|
||||
{710, "NI"},
|
||||
{712, "CR"},
|
||||
{714, "PA"},
|
||||
{716, "PE"},
|
||||
{722, "AR"},
|
||||
{724, "BR"},
|
||||
{730, "CL"},
|
||||
{732, "CO"},
|
||||
{734, "VE"},
|
||||
{736, "BO"},
|
||||
{738, "GY"},
|
||||
{740, "EC"},
|
||||
{742, "GF"},
|
||||
{744, "PY"},
|
||||
{746, "SR"},
|
||||
{748, "UY"},
|
||||
{750, "FK"},
|
||||
};
|
||||
|
||||
/*
|
||||
* @mcc_str should have MCC as prefix,
|
||||
* It doesn't matter if any thing else is followed.
|
||||
* So it's okay to pass an IMSI.
|
||||
*/
|
||||
static inline const char *
|
||||
get_country_iso_for_mcc (const char *mcc_str)
|
||||
{
|
||||
g_autofree char *str = NULL;
|
||||
guint64 mcc;
|
||||
|
||||
if (!mcc_str || strlen (mcc_str) < 3)
|
||||
return NULL;
|
||||
|
||||
str = g_strndup (mcc_str, 3);
|
||||
mcc = g_ascii_strtoull (str, NULL, 10);
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS (mcc_list); i++)
|
||||
if (mcc_list[i].mcc == mcc)
|
||||
return mcc_list[i].code;
|
||||
|
||||
g_warning ("invalid MCC code: %" G_GUINT64_FORMAT, mcc);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
57
plugins/provider/mm/meson.build
Normal file
57
plugins/provider/mm/meson.build
Normal file
@@ -0,0 +1,57 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author: Bob Ham <bob.ham@puri.sm>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
mm_install_dir = join_paths(full_calls_plugin_libdir, 'mm')
|
||||
|
||||
mm_plugin = configure_file(
|
||||
input: 'mm.plugin.in',
|
||||
output: 'mm.plugin',
|
||||
configuration: config_data,
|
||||
install_dir: mm_install_dir
|
||||
)
|
||||
|
||||
mm_deps = [
|
||||
dependency('gobject-2.0'),
|
||||
dependency('gtk+-3.0'),
|
||||
dependency('ModemManager'),
|
||||
dependency('mm-glib', version: '>= 1.12.0'),
|
||||
dependency('libpeas-1.0'),
|
||||
]
|
||||
|
||||
mm_sources = files(
|
||||
[
|
||||
'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_mm = shared_module(
|
||||
'mm',
|
||||
mm_sources,
|
||||
dependencies: mm_deps,
|
||||
include_directories: src_include,
|
||||
link_with: libcalls,
|
||||
install: true,
|
||||
install_dir: mm_install_dir
|
||||
)
|
||||
7
plugins/provider/mm/mm.plugin.in
Normal file
7
plugins/provider/mm/mm.plugin.in
Normal file
@@ -0,0 +1,7 @@
|
||||
[Plugin]
|
||||
Module=mm
|
||||
Name=ModemManager
|
||||
Description=ModemManager calls provider
|
||||
Authors=Bob Ham <rah@settrans.net>
|
||||
Copyright=Copyright (C) 2018 Purism SPC
|
||||
Website=@PACKAGE_URL_RAW@
|
||||
346
plugins/provider/ofono/calls-ofono-call.c
Normal file
346
plugins/provider/ofono/calls-ofono-call.c
Normal file
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "CallsOfonoCall"
|
||||
|
||||
#include "calls-ofono-call.h"
|
||||
#include "calls-call.h"
|
||||
#include "calls-message-source.h"
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
|
||||
struct _CallsOfonoCall {
|
||||
GObject parent_instance;
|
||||
GDBOVoiceCall *voice_call;
|
||||
gchar *disconnect_reason;
|
||||
};
|
||||
|
||||
static void calls_ofono_call_message_source_interface_init (CallsMessageSourceInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (CallsOfonoCall, calls_ofono_call, CALLS_TYPE_CALL,
|
||||
G_IMPLEMENT_INTERFACE (CALLS_TYPE_MESSAGE_SOURCE,
|
||||
calls_ofono_call_message_source_interface_init))
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_VOICE_CALL,
|
||||
PROP_LAST_PROP,
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
enum {
|
||||
SIGNAL_TONE,
|
||||
SIGNAL_LAST_SIGNAL,
|
||||
};
|
||||
static guint signals[SIGNAL_LAST_SIGNAL];
|
||||
|
||||
static const char *
|
||||
calls_ofono_call_get_protocol (CallsCall *call)
|
||||
{
|
||||
return "tel";
|
||||
}
|
||||
|
||||
struct CallsCallOperationData {
|
||||
const gchar *desc;
|
||||
CallsOfonoCall *self;
|
||||
gboolean (*finish_func) (GDBOVoiceCall *, GAsyncResult *, GError **);
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
operation_cb (GDBOVoiceCall *voice_call,
|
||||
GAsyncResult *res,
|
||||
struct CallsCallOperationData *data)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
gboolean ok;
|
||||
|
||||
ok = data->finish_func (voice_call, res, &error);
|
||||
if (!ok) {
|
||||
g_warning ("Error %s oFono voice call to `%s': %s",
|
||||
data->desc,
|
||||
calls_call_get_id (CALLS_CALL (data->self)),
|
||||
error->message);
|
||||
CALLS_ERROR (data->self, error);
|
||||
}
|
||||
|
||||
g_object_unref (data->self);
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_ofono_call_answer (CallsCall *call)
|
||||
{
|
||||
CallsOfonoCall *self = CALLS_OFONO_CALL (call);
|
||||
struct CallsCallOperationData *data;
|
||||
|
||||
data = g_new0 (struct CallsCallOperationData, 1);
|
||||
data->desc = "answering";
|
||||
data->self = g_object_ref (self);
|
||||
data->finish_func = gdbo_voice_call_call_answer_finish;
|
||||
|
||||
gdbo_voice_call_call_answer
|
||||
(self->voice_call, NULL,
|
||||
(GAsyncReadyCallback) operation_cb,
|
||||
data);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_ofono_call_hang_up (CallsCall *call)
|
||||
{
|
||||
CallsOfonoCall *self = CALLS_OFONO_CALL (call);
|
||||
struct CallsCallOperationData *data;
|
||||
|
||||
data = g_new0 (struct CallsCallOperationData, 1);
|
||||
data->desc = "hanging up";
|
||||
data->self = g_object_ref (self);
|
||||
data->finish_func = gdbo_voice_call_call_hangup_finish;
|
||||
|
||||
gdbo_voice_call_call_hangup
|
||||
(self->voice_call, NULL,
|
||||
(GAsyncReadyCallback) operation_cb,
|
||||
data);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_ofono_call_send_dtmf_tone (CallsCall *call, gchar key)
|
||||
{
|
||||
CallsOfonoCall *self = CALLS_OFONO_CALL (call);
|
||||
|
||||
if (calls_call_get_state (call) != CALLS_CALL_STATE_ACTIVE) {
|
||||
g_warning ("Tone start requested for non-active call to `%s'",
|
||||
calls_call_get_id (call));
|
||||
return;
|
||||
}
|
||||
|
||||
g_signal_emit_by_name (self, "tone", key);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsOfonoCall *self = CALLS_OFONO_CALL (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_VOICE_CALL:
|
||||
g_set_object
|
||||
(&self->voice_call, GDBO_VOICE_CALL (g_value_get_object (value)));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
property_changed_cb (CallsOfonoCall *self,
|
||||
const gchar *name,
|
||||
GVariant *value)
|
||||
{
|
||||
GVariant *str_var;
|
||||
gchar *str = NULL;
|
||||
CallsCallState state;
|
||||
gboolean ok;
|
||||
g_autofree char *text = g_variant_print (value, TRUE);
|
||||
|
||||
g_debug ("Property `%s' for oFono call to `%s' changed to: %s",
|
||||
name,
|
||||
calls_call_get_id (CALLS_CALL (self)),
|
||||
text);
|
||||
|
||||
if (g_strcmp0 (name, "State") != 0)
|
||||
return;
|
||||
|
||||
g_variant_get (value, "v", &str_var);
|
||||
g_variant_get (str_var, "&s", &str);
|
||||
g_return_if_fail (str != NULL);
|
||||
|
||||
ok = calls_call_state_parse_nick (&state, str);
|
||||
if (ok)
|
||||
calls_call_set_state (CALLS_CALL (self), state);
|
||||
else
|
||||
g_warning ("Could not parse new state `%s'"
|
||||
" of oFono call to `%s'",
|
||||
str, calls_call_get_id (CALLS_CALL (self)));
|
||||
|
||||
g_variant_unref (str_var);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
disconnect_reason_cb (CallsOfonoCall *self,
|
||||
const gchar *reason)
|
||||
{
|
||||
if (reason) {
|
||||
g_free (self->disconnect_reason);
|
||||
self->disconnect_reason = g_strdup (reason);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
constructed (GObject *object)
|
||||
{
|
||||
CallsOfonoCall *self = CALLS_OFONO_CALL (object);
|
||||
|
||||
g_return_if_fail (self->voice_call != NULL);
|
||||
|
||||
g_signal_connect_object (self->voice_call, "property-changed",
|
||||
G_CALLBACK (property_changed_cb),
|
||||
self, G_CONNECT_SWAPPED);
|
||||
g_signal_connect_object (self->voice_call, "disconnect-reason",
|
||||
G_CALLBACK (disconnect_reason_cb),
|
||||
self, G_CONNECT_SWAPPED);
|
||||
|
||||
G_OBJECT_CLASS (calls_ofono_call_parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dispose (GObject *object)
|
||||
{
|
||||
CallsOfonoCall *self = CALLS_OFONO_CALL (object);
|
||||
|
||||
g_clear_object (&self->voice_call);
|
||||
|
||||
G_OBJECT_CLASS (calls_ofono_call_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
finalize (GObject *object)
|
||||
{
|
||||
CallsOfonoCall *self = CALLS_OFONO_CALL (object);
|
||||
|
||||
g_free (self->disconnect_reason);
|
||||
|
||||
G_OBJECT_CLASS (calls_ofono_call_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_ofono_call_class_init (CallsOfonoCallClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
CallsCallClass *call_class = CALLS_CALL_CLASS (klass);
|
||||
GType tone_arg_types = G_TYPE_CHAR;
|
||||
|
||||
object_class->set_property = set_property;
|
||||
object_class->constructed = constructed;
|
||||
object_class->dispose = dispose;
|
||||
object_class->finalize = finalize;
|
||||
|
||||
call_class->get_protocol = calls_ofono_call_get_protocol;
|
||||
call_class->answer = calls_ofono_call_answer;
|
||||
call_class->hang_up = calls_ofono_call_hang_up;
|
||||
call_class->send_dtmf_tone = calls_ofono_call_send_dtmf_tone;
|
||||
|
||||
props[PROP_VOICE_CALL] =
|
||||
g_param_spec_object ("voice-call",
|
||||
"Voice call",
|
||||
"A GDBO proxy object for the underlying call object",
|
||||
GDBO_TYPE_VOICE_CALL,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
|
||||
g_object_class_install_property (object_class, PROP_VOICE_CALL, props[PROP_VOICE_CALL]);
|
||||
|
||||
signals[SIGNAL_TONE] =
|
||||
g_signal_newv ("tone",
|
||||
G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
NULL, NULL, NULL, NULL,
|
||||
G_TYPE_NONE,
|
||||
1, &tone_arg_types);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_ofono_call_message_source_interface_init (CallsMessageSourceInterface *iface)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
calls_ofono_call_init (CallsOfonoCall *self)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
CallsOfonoCall *
|
||||
calls_ofono_call_new (GDBOVoiceCall *voice_call,
|
||||
GVariant *call_props)
|
||||
{
|
||||
const char *state_str = NULL;
|
||||
const char *name = NULL;
|
||||
const char *id = NULL;
|
||||
CallsCallState state = CALLS_CALL_STATE_UNKNOWN;
|
||||
gboolean inbound = FALSE;
|
||||
|
||||
g_return_val_if_fail (GDBO_IS_VOICE_CALL (voice_call), NULL);
|
||||
g_return_val_if_fail (call_props != NULL, NULL);
|
||||
|
||||
g_variant_lookup (call_props, "LineIdentification", "s", &id);
|
||||
g_variant_lookup (call_props, "Name", "s", &name);
|
||||
|
||||
g_variant_lookup (call_props, "State", "&s", &state_str);
|
||||
if (state_str)
|
||||
calls_call_state_parse_nick (&state, state_str);
|
||||
|
||||
/* `inbound` is derived from `state` at construction time.
|
||||
* If the call was already somehow accepted and thus state=active,
|
||||
* then it's not possible to know the correct value for `inbound`. */
|
||||
if (state == CALLS_CALL_STATE_INCOMING)
|
||||
inbound = TRUE;
|
||||
|
||||
return g_object_new (CALLS_TYPE_OFONO_CALL,
|
||||
"voice-call", voice_call,
|
||||
"id", id,
|
||||
"name", name,
|
||||
"inbound", inbound,
|
||||
"state", state,
|
||||
"call-type", CALLS_CALL_TYPE_CELLULAR,
|
||||
NULL);
|
||||
}
|
||||
|
||||
|
||||
const gchar *
|
||||
calls_ofono_call_get_object_path (CallsOfonoCall *call)
|
||||
{
|
||||
return g_dbus_proxy_get_object_path (G_DBUS_PROXY (call->voice_call));
|
||||
}
|
||||
|
||||
|
||||
const gchar *
|
||||
calls_ofono_call_get_disconnect_reason (CallsOfonoCall *call)
|
||||
{
|
||||
return call->disconnect_reason;
|
||||
}
|
||||
47
plugins/provider/ofono/calls-ofono-call.h
Normal file
47
plugins/provider/ofono/calls-ofono-call.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALLS_OFONO_CALL_H__
|
||||
#define CALLS_OFONO_CALL_H__
|
||||
|
||||
#include <libgdbofono/gdbo-call.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "calls-call.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_OFONO_CALL (calls_ofono_call_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsOfonoCall, calls_ofono_call, CALLS, OFONO_CALL, CallsCall)
|
||||
|
||||
CallsOfonoCall *calls_ofono_call_new (GDBOVoiceCall *voice_call,
|
||||
GVariant *properties);
|
||||
const gchar *calls_ofono_call_get_object_path (CallsOfonoCall *call);
|
||||
const gchar *calls_ofono_call_get_disconnect_reason (CallsOfonoCall *call);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* CALLS_OFONO_CALL_H__ */
|
||||
588
plugins/provider/ofono/calls-ofono-origin.c
Normal file
588
plugins/provider/ofono/calls-ofono-origin.c
Normal file
@@ -0,0 +1,588 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "CallsOfonoOrigin"
|
||||
|
||||
#include "calls-ofono-origin.h"
|
||||
#include "calls-origin.h"
|
||||
#include "calls-ofono-call.h"
|
||||
#include "calls-message-source.h"
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
|
||||
struct _CallsOfonoOrigin {
|
||||
GObject parent_instance;
|
||||
GDBusConnection *connection;
|
||||
GDBOModem *modem;
|
||||
gchar *name;
|
||||
GDBOVoiceCallManager *voice;
|
||||
gboolean sending_tones;
|
||||
GString *tone_queue;
|
||||
GHashTable *calls;
|
||||
};
|
||||
|
||||
static void calls_ofono_origin_message_source_interface_init (CallsOriginInterface *iface);
|
||||
static void calls_ofono_origin_origin_interface_init (CallsOriginInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (CallsOfonoOrigin, calls_ofono_origin, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (CALLS_TYPE_MESSAGE_SOURCE,
|
||||
calls_ofono_origin_message_source_interface_init)
|
||||
G_IMPLEMENT_INTERFACE (CALLS_TYPE_ORIGIN,
|
||||
calls_ofono_origin_origin_interface_init))
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_ID,
|
||||
PROP_NAME,
|
||||
PROP_CALLS,
|
||||
PROP_MODEM,
|
||||
PROP_COUNTRY_CODE,
|
||||
PROP_LAST_PROP,
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
|
||||
static void
|
||||
dial_cb (GDBOVoiceCallManager *voice,
|
||||
GAsyncResult *res,
|
||||
CallsOfonoOrigin *self)
|
||||
{
|
||||
gboolean ok;
|
||||
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
ok = gdbo_voice_call_manager_call_dial_finish
|
||||
(voice, NULL, res, &error);
|
||||
if (!ok) {
|
||||
g_warning ("Error dialing number on modem `%s': %s",
|
||||
self->name, error->message);
|
||||
CALLS_ERROR (self, error);
|
||||
return;
|
||||
}
|
||||
|
||||
/* We will add the call through the call-added signal */
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dial (CallsOrigin *origin, const gchar *number)
|
||||
{
|
||||
CallsOfonoOrigin *self = CALLS_OFONO_ORIGIN (origin);
|
||||
|
||||
g_return_if_fail (self->voice != NULL);
|
||||
|
||||
gdbo_voice_call_manager_call_dial
|
||||
(self->voice,
|
||||
number,
|
||||
"default" /* default caller id settings */,
|
||||
NULL,
|
||||
(GAsyncReadyCallback) dial_cb,
|
||||
self);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
supports_protocol (CallsOrigin *origin,
|
||||
const char *protocol)
|
||||
{
|
||||
g_assert (protocol);
|
||||
g_assert (CALLS_IS_OFONO_ORIGIN (origin));
|
||||
|
||||
return g_strcmp0 (protocol, "tel") == 0;
|
||||
}
|
||||
|
||||
CallsOfonoOrigin *
|
||||
calls_ofono_origin_new (GDBOModem *modem)
|
||||
{
|
||||
g_return_val_if_fail (GDBO_IS_MODEM (modem), NULL);
|
||||
|
||||
return g_object_new (CALLS_TYPE_OFONO_ORIGIN,
|
||||
"modem", modem,
|
||||
NULL);
|
||||
}
|
||||
|
||||
gboolean
|
||||
calls_ofono_origin_matches (CallsOfonoOrigin *self,
|
||||
const char *path)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_OFONO_ORIGIN (self), FALSE);
|
||||
g_return_val_if_fail (path, FALSE);
|
||||
g_return_val_if_fail (self->modem, FALSE);
|
||||
|
||||
return g_strcmp0 (g_dbus_proxy_get_object_path (G_DBUS_PROXY (self->modem)), path) == 0;
|
||||
}
|
||||
|
||||
static void
|
||||
set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsOfonoOrigin *self = CALLS_OFONO_ORIGIN (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_ID:
|
||||
/* we're using a hardcoded value, so let's ignore it */
|
||||
break;
|
||||
|
||||
case PROP_MODEM:
|
||||
g_set_object
|
||||
(&self->modem, GDBO_MODEM (g_value_get_object (value)));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsOfonoOrigin *self = CALLS_OFONO_ORIGIN (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_ID:
|
||||
g_value_set_string (value, "ofono");
|
||||
break;
|
||||
|
||||
case PROP_NAME:
|
||||
g_value_set_string (value, self->name);
|
||||
break;
|
||||
|
||||
case PROP_CALLS:
|
||||
g_value_set_pointer (value, g_hash_table_get_values (self->calls));
|
||||
break;
|
||||
|
||||
case PROP_COUNTRY_CODE:
|
||||
g_value_set_string (value, NULL);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
remove_call (CallsOfonoOrigin *self,
|
||||
CallsOfonoCall *call,
|
||||
const gchar *reason)
|
||||
{
|
||||
const gchar *path = calls_ofono_call_get_object_path (call);
|
||||
|
||||
g_signal_emit_by_name (CALLS_ORIGIN (self), "call-removed",
|
||||
CALLS_CALL (call), reason);
|
||||
g_hash_table_remove (self->calls, path);
|
||||
}
|
||||
|
||||
|
||||
struct CallsRemoveCallsData {
|
||||
CallsOrigin *origin;
|
||||
const gchar *reason;
|
||||
};
|
||||
|
||||
static gboolean
|
||||
remove_calls_cb (const gchar *path,
|
||||
CallsOfonoCall *call,
|
||||
struct CallsRemoveCallsData *data)
|
||||
{
|
||||
g_signal_emit_by_name (data->origin, "call-removed",
|
||||
CALLS_CALL (call), data->reason);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
remove_calls (CallsOfonoOrigin *self, const gchar *reason)
|
||||
{
|
||||
struct CallsRemoveCallsData data = { CALLS_ORIGIN (self), reason };
|
||||
|
||||
g_hash_table_foreach_remove (self->calls,
|
||||
(GHRFunc) remove_calls_cb,
|
||||
&data);
|
||||
}
|
||||
|
||||
|
||||
struct CallsVoiceCallProxyNewData {
|
||||
CallsOfonoOrigin *self;
|
||||
GVariant *properties;
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
send_tones_cb (GDBOVoiceCallManager *voice,
|
||||
GAsyncResult *res,
|
||||
CallsOfonoOrigin *self)
|
||||
{
|
||||
gboolean ok;
|
||||
GError *error = NULL;
|
||||
|
||||
/* Deal with old tones */
|
||||
ok = gdbo_voice_call_manager_call_send_tones_finish
|
||||
(voice, res, &error);
|
||||
if (!ok) {
|
||||
g_warning ("Error sending DTMF tones to network on modem `%s': %s",
|
||||
self->name, error->message);
|
||||
CALLS_EMIT_MESSAGE (self, error->message, GTK_MESSAGE_WARNING);
|
||||
}
|
||||
|
||||
/* Possibly send new tones */
|
||||
if (self->tone_queue) {
|
||||
g_debug ("Sending queued DTMF tones `%s'", self->tone_queue->str);
|
||||
|
||||
gdbo_voice_call_manager_call_send_tones (voice,
|
||||
self->tone_queue->str,
|
||||
NULL,
|
||||
(GAsyncReadyCallback) send_tones_cb,
|
||||
self);
|
||||
|
||||
g_string_free (self->tone_queue, TRUE);
|
||||
self->tone_queue = NULL;
|
||||
} else {
|
||||
self->sending_tones = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
tone_cb (CallsOfonoOrigin *self,
|
||||
gchar key)
|
||||
{
|
||||
const gchar key_str[2] = { key, '\0' };
|
||||
|
||||
if (self->sending_tones) {
|
||||
if (self->tone_queue) {
|
||||
g_string_append_c (self->tone_queue, key);
|
||||
} else {
|
||||
self->tone_queue = g_string_new (key_str);
|
||||
}
|
||||
} else {
|
||||
g_debug ("Sending immediate DTMF tone `%c'", key);
|
||||
|
||||
gdbo_voice_call_manager_call_send_tones (self->voice,
|
||||
key_str,
|
||||
NULL,
|
||||
(GAsyncReadyCallback) send_tones_cb,
|
||||
self);
|
||||
|
||||
self->sending_tones = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
voice_call_proxy_new_cb (GDBusConnection *connection,
|
||||
GAsyncResult *res,
|
||||
struct CallsVoiceCallProxyNewData *data)
|
||||
{
|
||||
CallsOfonoOrigin *self = data->self;
|
||||
GDBOVoiceCall *voice_call;
|
||||
|
||||
g_autoptr (GError) error = NULL;
|
||||
const gchar *path;
|
||||
CallsOfonoCall *call;
|
||||
|
||||
voice_call = gdbo_voice_call_proxy_new_finish (res, &error);
|
||||
if (!voice_call) {
|
||||
g_variant_unref (data->properties);
|
||||
g_free (data);
|
||||
g_warning ("Error creating oFono VoiceCall proxy: %s",
|
||||
error->message);
|
||||
CALLS_ERROR (self, error);
|
||||
return;
|
||||
}
|
||||
|
||||
call = calls_ofono_call_new (voice_call, data->properties);
|
||||
g_signal_connect_swapped (call, "tone",
|
||||
G_CALLBACK (tone_cb), self);
|
||||
|
||||
path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (voice_call));
|
||||
g_hash_table_insert (self->calls, g_strdup (path), call);
|
||||
|
||||
g_signal_emit_by_name (CALLS_ORIGIN (self), "call-added",
|
||||
CALLS_CALL (call));
|
||||
|
||||
g_debug ("Call `%s' added", path);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
call_added_cb (GDBOVoiceCallManager *voice,
|
||||
const gchar *path,
|
||||
GVariant *properties,
|
||||
CallsOfonoOrigin *self)
|
||||
{
|
||||
struct CallsVoiceCallProxyNewData *data;
|
||||
|
||||
g_debug ("Adding call `%s'", path);
|
||||
|
||||
if (g_hash_table_lookup (self->calls, path)) {
|
||||
g_warning ("Call `%s' already exists", path);
|
||||
return;
|
||||
}
|
||||
|
||||
data = g_new0 (struct CallsVoiceCallProxyNewData, 1);
|
||||
data->self = self;
|
||||
data->properties = properties;
|
||||
g_variant_ref (properties);
|
||||
|
||||
gdbo_voice_call_proxy_new
|
||||
(self->connection,
|
||||
G_DBUS_PROXY_FLAGS_NONE,
|
||||
g_dbus_proxy_get_name (G_DBUS_PROXY (voice)),
|
||||
path,
|
||||
NULL,
|
||||
(GAsyncReadyCallback) voice_call_proxy_new_cb,
|
||||
data);
|
||||
|
||||
g_debug ("Call `%s' addition in progress", path);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
call_removed_cb (GDBOVoiceCallManager *voice,
|
||||
const gchar *path,
|
||||
CallsOfonoOrigin *self)
|
||||
{
|
||||
CallsOfonoCall *ofono_call;
|
||||
GString *reason;
|
||||
const gchar *ofono_reason;
|
||||
|
||||
g_debug ("Removing call `%s'", path);
|
||||
|
||||
ofono_call = g_hash_table_lookup (self->calls, path);
|
||||
if (!ofono_call) {
|
||||
g_warning ("Could not find removed call `%s'", path);
|
||||
return;
|
||||
}
|
||||
|
||||
reason = g_string_new ("Call removed");
|
||||
|
||||
ofono_reason = calls_ofono_call_get_disconnect_reason (ofono_call);
|
||||
if (ofono_reason) {
|
||||
/* The oFono reason is either "local", "remote" or "network".
|
||||
* We just capitalise that to create a nice reason string.
|
||||
*/
|
||||
g_string_assign (reason, ofono_reason);
|
||||
reason->str[0] = g_ascii_toupper (reason->str[0]);
|
||||
g_string_append (reason, " disconnection");
|
||||
}
|
||||
|
||||
remove_call (self, ofono_call, reason->str);
|
||||
|
||||
g_string_free (reason, TRUE);
|
||||
|
||||
g_debug ("Removed call `%s'", path);
|
||||
}
|
||||
|
||||
static void
|
||||
get_calls_cb (GDBOVoiceCallManager *voice,
|
||||
GAsyncResult *res,
|
||||
CallsOfonoOrigin *self)
|
||||
{
|
||||
gboolean ok;
|
||||
GVariant *calls_with_properties = NULL;
|
||||
|
||||
g_autoptr (GError) error = NULL;
|
||||
GVariantIter *iter = NULL;
|
||||
const gchar *path;
|
||||
GVariant *properties;
|
||||
|
||||
ok = gdbo_voice_call_manager_call_get_calls_finish
|
||||
(voice, &calls_with_properties, res, &error);
|
||||
if (!ok) {
|
||||
g_warning ("Error getting calls from oFono"
|
||||
" VoiceCallManager `%s': %s",
|
||||
self->name, error->message);
|
||||
CALLS_ERROR (self, error);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
char *text = g_variant_print (calls_with_properties, TRUE);
|
||||
g_debug ("Received calls from oFono"
|
||||
" VoiceCallManager `%s': %s",
|
||||
self->name, text);
|
||||
g_free (text);
|
||||
}
|
||||
|
||||
g_variant_get (calls_with_properties, "a(oa{sv})", &iter);
|
||||
while (g_variant_iter_loop (iter, "(&o@a{sv})",
|
||||
&path, &properties))
|
||||
{
|
||||
g_debug ("Got call object path `%s'", path);
|
||||
call_added_cb (voice, path, properties, self);
|
||||
}
|
||||
g_variant_iter_free (iter);
|
||||
|
||||
g_variant_unref (calls_with_properties);
|
||||
}
|
||||
|
||||
static void
|
||||
voice_new_cb (GDBusConnection *connection,
|
||||
GAsyncResult *res,
|
||||
CallsOfonoOrigin *self)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
self->voice = gdbo_voice_call_manager_proxy_new_finish
|
||||
(res, &error);
|
||||
if (!self->voice) {
|
||||
g_warning ("Error creating oFono"
|
||||
" VoiceCallManager `%s' proxy: %s",
|
||||
self->name, error->message);
|
||||
CALLS_ERROR (self, error);
|
||||
return;
|
||||
}
|
||||
|
||||
g_signal_connect (self->voice, "call-added",
|
||||
G_CALLBACK (call_added_cb), self);
|
||||
g_signal_connect (self->voice, "call-removed",
|
||||
G_CALLBACK (call_removed_cb), self);
|
||||
|
||||
gdbo_voice_call_manager_call_get_calls
|
||||
(self->voice,
|
||||
NULL,
|
||||
(GAsyncReadyCallback) get_calls_cb,
|
||||
self);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
constructed (GObject *object)
|
||||
{
|
||||
CallsOfonoOrigin *self = CALLS_OFONO_ORIGIN (object);
|
||||
GDBusProxy *modem_proxy;
|
||||
gchar *name;
|
||||
|
||||
g_return_if_fail (self->modem != NULL);
|
||||
|
||||
modem_proxy = G_DBUS_PROXY (self->modem);
|
||||
|
||||
self->connection = g_dbus_proxy_get_connection (modem_proxy);
|
||||
g_object_ref (self->connection);
|
||||
|
||||
name = g_object_get_data (G_OBJECT (self->modem),
|
||||
"calls-modem-name");
|
||||
if (name)
|
||||
self->name = g_strdup (name);
|
||||
|
||||
gdbo_voice_call_manager_proxy_new
|
||||
(self->connection,
|
||||
G_DBUS_PROXY_FLAGS_NONE,
|
||||
g_dbus_proxy_get_name (modem_proxy),
|
||||
g_dbus_proxy_get_object_path (modem_proxy),
|
||||
NULL,
|
||||
(GAsyncReadyCallback) voice_new_cb,
|
||||
self);
|
||||
|
||||
G_OBJECT_CLASS (calls_ofono_origin_parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dispose (GObject *object)
|
||||
{
|
||||
CallsOfonoOrigin *self = CALLS_OFONO_ORIGIN (object);
|
||||
|
||||
remove_calls (self, NULL);
|
||||
g_clear_object (&self->modem);
|
||||
g_clear_object (&self->connection);
|
||||
|
||||
G_OBJECT_CLASS (calls_ofono_origin_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
finalize (GObject *object)
|
||||
{
|
||||
CallsOfonoOrigin *self = CALLS_OFONO_ORIGIN (object);
|
||||
|
||||
if (self->tone_queue) {
|
||||
g_string_free (self->tone_queue, TRUE);
|
||||
}
|
||||
g_free (self->name);
|
||||
|
||||
G_OBJECT_CLASS (calls_ofono_origin_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_ofono_origin_class_init (CallsOfonoOriginClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->get_property = get_property;
|
||||
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 ("modem",
|
||||
"Modem",
|
||||
"A GDBO proxy object for the underlying modem object",
|
||||
GDBO_TYPE_MODEM,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
|
||||
g_object_class_install_property (object_class, PROP_MODEM, props[PROP_MODEM]);
|
||||
|
||||
#define IMPLEMENTS(ID, NAME) \
|
||||
g_object_class_override_property (object_class, ID, NAME); \
|
||||
props[ID] = g_object_class_find_property(object_class, NAME);
|
||||
|
||||
IMPLEMENTS (PROP_NAME, "id");
|
||||
IMPLEMENTS (PROP_NAME, "name");
|
||||
IMPLEMENTS (PROP_CALLS, "calls");
|
||||
IMPLEMENTS (PROP_COUNTRY_CODE, "country-code");
|
||||
|
||||
#undef IMPLEMENTS
|
||||
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_ofono_origin_message_source_interface_init (CallsOriginInterface *iface)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_ofono_origin_origin_interface_init (CallsOriginInterface *iface)
|
||||
{
|
||||
iface->dial = dial;
|
||||
iface->supports_protocol = supports_protocol;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_ofono_origin_init (CallsOfonoOrigin *self)
|
||||
{
|
||||
self->calls = g_hash_table_new_full (g_str_hash, g_str_equal,
|
||||
g_free, g_object_unref);
|
||||
}
|
||||
44
plugins/provider/ofono/calls-ofono-origin.h
Normal file
44
plugins/provider/ofono/calls-ofono-origin.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALLS_OFONO_ORIGIN_H__
|
||||
#define CALLS_OFONO_ORIGIN_H__
|
||||
|
||||
#include <libgdbofono/gdbo-modem.h>
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_OFONO_ORIGIN (calls_ofono_origin_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsOfonoOrigin, calls_ofono_origin, CALLS, OFONO_ORIGIN, GObject);
|
||||
|
||||
CallsOfonoOrigin *calls_ofono_origin_new (GDBOModem *modem);
|
||||
gboolean calls_ofono_origin_matches (CallsOfonoOrigin *self,
|
||||
const char *path);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* CALLS_OFONO_ORIGIN_H__ */
|
||||
558
plugins/provider/ofono/calls-ofono-provider.c
Normal file
558
plugins/provider/ofono/calls-ofono-provider.c
Normal file
@@ -0,0 +1,558 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "CallsOfonoProvider"
|
||||
|
||||
#include "calls-ofono-provider.h"
|
||||
#include "calls-provider.h"
|
||||
#include "calls-ofono-origin.h"
|
||||
#include "calls-message-source.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <libgdbofono/gdbo-manager.h>
|
||||
#include <libgdbofono/gdbo-modem.h>
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include <libpeas/peas.h>
|
||||
|
||||
static const char * const supported_protocols[] = {
|
||||
"tel",
|
||||
NULL
|
||||
};
|
||||
|
||||
struct _CallsOfonoProvider {
|
||||
CallsProvider parent_instance;
|
||||
|
||||
/* The status property */
|
||||
gchar *status;
|
||||
/** ID for the D-Bus watch */
|
||||
guint watch_id;
|
||||
/** D-Bus connection */
|
||||
GDBusConnection *connection;
|
||||
/** D-Bus proxy for the oFono Manager object */
|
||||
GDBOManager *manager;
|
||||
/** Map of D-Bus object paths to a struct CallsModemData */
|
||||
GHashTable *modems;
|
||||
/* A list of CallsOrigins */
|
||||
GListStore *origins;
|
||||
};
|
||||
|
||||
|
||||
static void calls_ofono_provider_message_source_interface_init (CallsMessageSourceInterface *iface);
|
||||
|
||||
|
||||
G_DEFINE_DYNAMIC_TYPE_EXTENDED
|
||||
(CallsOfonoProvider, calls_ofono_provider, CALLS_TYPE_PROVIDER, 0,
|
||||
G_IMPLEMENT_INTERFACE_DYNAMIC (CALLS_TYPE_MESSAGE_SOURCE,
|
||||
calls_ofono_provider_message_source_interface_init))
|
||||
|
||||
|
||||
static void
|
||||
set_status (CallsOfonoProvider *self,
|
||||
const gchar *new_status)
|
||||
{
|
||||
if (strcmp (self->status, new_status) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_free (self->status);
|
||||
self->status = g_strdup (new_status);
|
||||
g_object_notify (G_OBJECT (self), "status");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
update_status (CallsOfonoProvider *self)
|
||||
{
|
||||
const gchar *s;
|
||||
GListModel *model;
|
||||
|
||||
model = G_LIST_MODEL (self->origins);
|
||||
|
||||
if (!self->connection) {
|
||||
s = _("DBus unavailable");
|
||||
} else if (g_list_model_get_n_items (model) == 0) {
|
||||
s = _("No voice-capable modem available");
|
||||
} else {
|
||||
s = _("Normal");
|
||||
}
|
||||
|
||||
set_status (self, s);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
ofono_find_origin_index (CallsOfonoProvider *self,
|
||||
const char *path,
|
||||
guint *index)
|
||||
{
|
||||
GListModel *model;
|
||||
guint n_items;
|
||||
|
||||
g_assert (CALLS_IS_OFONO_PROVIDER (self));
|
||||
|
||||
model = G_LIST_MODEL (self->origins);
|
||||
n_items = g_list_model_get_n_items (model);
|
||||
|
||||
for (guint i = 0; i < n_items; i++) {
|
||||
g_autoptr (CallsOfonoOrigin) origin = NULL;
|
||||
|
||||
origin = g_list_model_get_item (model, i);
|
||||
|
||||
if (calls_ofono_origin_matches (origin, path)) {
|
||||
if (index)
|
||||
*index = i;
|
||||
|
||||
update_status (self);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
object_array_includes (GVariantIter *iter,
|
||||
const gchar *needle)
|
||||
{
|
||||
const gchar *str;
|
||||
gboolean found = FALSE;
|
||||
|
||||
while (g_variant_iter_loop (iter, "&s", &str))
|
||||
{
|
||||
if (g_strcmp0 (str, needle) == 0) {
|
||||
found = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_variant_iter_free (iter);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
modem_check_ifaces (CallsOfonoProvider *self,
|
||||
GDBOModem *modem,
|
||||
const gchar *modem_name,
|
||||
GVariant *ifaces)
|
||||
{
|
||||
gboolean voice;
|
||||
GVariantIter *iter = NULL;
|
||||
const gchar *path;
|
||||
guint index;
|
||||
gboolean has_origin;
|
||||
|
||||
g_variant_get (ifaces, "as", &iter);
|
||||
|
||||
voice = object_array_includes (iter, "org.ofono.VoiceCallManager");
|
||||
|
||||
path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (modem));
|
||||
|
||||
has_origin = ofono_find_origin_index (self, path, &index);
|
||||
if (voice && !has_origin) {
|
||||
g_autoptr (CallsOfonoOrigin) origin = NULL;
|
||||
|
||||
g_debug ("Adding oFono Origin with path `%s'", path);
|
||||
|
||||
origin = calls_ofono_origin_new (modem);
|
||||
g_list_store_append (self->origins, origin);
|
||||
} else if (!voice && has_origin) {
|
||||
g_list_store_remove (self->origins, index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
modem_property_changed_cb (GDBOModem *modem,
|
||||
const gchar *name,
|
||||
GVariant *value,
|
||||
CallsOfonoProvider *self)
|
||||
{
|
||||
gchar *modem_name;
|
||||
|
||||
g_debug ("Modem property `%s' changed", name);
|
||||
|
||||
if (g_strcmp0 (name, "Interfaces") != 0)
|
||||
return;
|
||||
|
||||
modem_name = g_object_get_data (G_OBJECT (modem),
|
||||
"calls-modem-name");
|
||||
|
||||
/* PropertyChanged gives us a variant gvariant containing a string array,
|
||||
but modem_check_ifaces expects the inner string array gvariant */
|
||||
value = g_variant_get_variant (value);
|
||||
modem_check_ifaces (self, modem, modem_name, value);
|
||||
}
|
||||
|
||||
|
||||
struct CallsModemProxyNewData {
|
||||
CallsOfonoProvider *self;
|
||||
gchar *name;
|
||||
GVariant *ifaces;
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
modem_proxy_new_cb (GDBusConnection *connection,
|
||||
GAsyncResult *res,
|
||||
struct CallsModemProxyNewData *data)
|
||||
{
|
||||
GDBOModem *modem;
|
||||
GError *error = NULL;
|
||||
const gchar *path;
|
||||
|
||||
modem = gdbo_modem_proxy_new_finish (res, &error);
|
||||
if (!modem) {
|
||||
g_variant_unref (data->ifaces);
|
||||
g_free (data->name);
|
||||
g_free (data);
|
||||
g_error ("Error creating oFono Modem proxy: %s",
|
||||
error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
g_signal_connect (modem, "property-changed",
|
||||
G_CALLBACK (modem_property_changed_cb),
|
||||
data->self);
|
||||
|
||||
|
||||
/* We want to store the oFono modem's Name property so we can pass it
|
||||
to our Origin when we create it */
|
||||
g_object_set_data_full (G_OBJECT (modem), "calls-modem-name",
|
||||
data->name, g_free);
|
||||
|
||||
path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (modem));
|
||||
|
||||
g_hash_table_insert (data->self->modems, g_strdup (path), modem);
|
||||
|
||||
|
||||
if (data->ifaces) {
|
||||
modem_check_ifaces (data->self, modem,
|
||||
data->name, data->ifaces);
|
||||
g_variant_unref (data->ifaces);
|
||||
}
|
||||
|
||||
g_free (data);
|
||||
|
||||
g_debug ("Modem `%s' added", path);
|
||||
}
|
||||
|
||||
|
||||
static gchar *
|
||||
modem_properties_get_name (GVariant *properties)
|
||||
{
|
||||
gchar *name = NULL;
|
||||
gboolean ok;
|
||||
|
||||
#define try(prop) \
|
||||
ok = g_variant_lookup (properties, prop, "s", &name); \
|
||||
if (ok) { \
|
||||
return name; \
|
||||
}
|
||||
|
||||
try ("Name");
|
||||
try ("Model");
|
||||
try ("Manufacturer");
|
||||
try ("Serial");
|
||||
try ("SystemPath");
|
||||
|
||||
#undef try
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *const *
|
||||
calls_ofono_provider_get_protocols (CallsProvider *provider)
|
||||
{
|
||||
return supported_protocols;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
calls_ofono_provider_is_modem (CallsProvider *provider)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
modem_added_cb (GDBOManager *manager,
|
||||
const gchar *path,
|
||||
GVariant *properties,
|
||||
CallsOfonoProvider *self)
|
||||
{
|
||||
struct CallsModemProxyNewData *data;
|
||||
|
||||
g_debug ("Adding modem `%s'", path);
|
||||
|
||||
if (g_hash_table_lookup (self->modems, path)) {
|
||||
g_warning ("Modem `%s' already exists", path);
|
||||
return;
|
||||
}
|
||||
|
||||
data = g_new0 (struct CallsModemProxyNewData, 1);
|
||||
data->self = self;
|
||||
data->name = modem_properties_get_name (properties);
|
||||
|
||||
data->ifaces = g_variant_lookup_value
|
||||
(properties, "Interfaces", G_VARIANT_TYPE_ARRAY);
|
||||
if (data->ifaces) {
|
||||
g_variant_ref (data->ifaces);
|
||||
}
|
||||
|
||||
gdbo_modem_proxy_new
|
||||
(self->connection,
|
||||
G_DBUS_PROXY_FLAGS_NONE,
|
||||
g_dbus_proxy_get_name (G_DBUS_PROXY (manager)),
|
||||
path,
|
||||
NULL,
|
||||
(GAsyncReadyCallback) modem_proxy_new_cb,
|
||||
data);
|
||||
|
||||
g_debug ("Modem `%s' addition in progress", path);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
modem_removed_cb (GDBOManager *manager,
|
||||
const gchar *path,
|
||||
CallsOfonoProvider *self)
|
||||
{
|
||||
guint index;
|
||||
|
||||
g_debug ("Removing modem `%s'", path);
|
||||
|
||||
if (ofono_find_origin_index (self, path, &index))
|
||||
g_list_store_remove (self->origins, index);
|
||||
|
||||
g_hash_table_remove (self->modems, path);
|
||||
|
||||
g_debug ("Modem `%s' removed", path);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
get_modems_cb (GDBOManager *manager,
|
||||
GAsyncResult *res,
|
||||
CallsOfonoProvider *self)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
gboolean ok;
|
||||
GVariant *modems;
|
||||
GVariantIter *modems_iter = NULL;
|
||||
const gchar *path;
|
||||
GVariant *properties;
|
||||
|
||||
ok = gdbo_manager_call_get_modems_finish (manager, &modems,
|
||||
res, &error);
|
||||
if (!ok) {
|
||||
g_warning ("Error getting modems from oFono Manager: %s",
|
||||
error->message);
|
||||
CALLS_ERROR (self, error);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
char *text = g_variant_print (modems, TRUE);
|
||||
g_debug ("Received modems from oFono Manager: %s", text);
|
||||
g_free (text);
|
||||
}
|
||||
|
||||
g_variant_get (modems, "a(oa{sv})", &modems_iter);
|
||||
while (g_variant_iter_loop (modems_iter, "(&o@a{sv})",
|
||||
&path, &properties))
|
||||
{
|
||||
g_debug ("Got modem object path `%s'", path);
|
||||
modem_added_cb (manager, path, properties, self);
|
||||
}
|
||||
g_variant_iter_free (modems_iter);
|
||||
|
||||
g_variant_unref (modems);
|
||||
}
|
||||
|
||||
static const char *
|
||||
calls_ofono_provider_get_name (CallsProvider *provider)
|
||||
{
|
||||
return "Ofono";
|
||||
}
|
||||
|
||||
static const char *
|
||||
calls_ofono_provider_get_status (CallsProvider *provider)
|
||||
{
|
||||
CallsOfonoProvider *self = CALLS_OFONO_PROVIDER (provider);
|
||||
|
||||
return self->status;
|
||||
}
|
||||
|
||||
static GListModel *
|
||||
calls_ofono_provider_get_origins (CallsProvider *provider)
|
||||
{
|
||||
CallsOfonoProvider *self = CALLS_OFONO_PROVIDER (provider);
|
||||
|
||||
return G_LIST_MODEL (self->origins);
|
||||
}
|
||||
|
||||
static void
|
||||
ofono_appeared_cb (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
const gchar *name_owner,
|
||||
CallsOfonoProvider *self)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
self->connection = connection;
|
||||
if (!self->connection) {
|
||||
g_error ("Error creating D-Bus connection: %s",
|
||||
error->message);
|
||||
}
|
||||
|
||||
self->manager = gdbo_manager_proxy_new_sync
|
||||
(self->connection,
|
||||
G_DBUS_PROXY_FLAGS_NONE,
|
||||
"org.ofono",
|
||||
"/",
|
||||
NULL,
|
||||
&error);
|
||||
if (!self->manager) {
|
||||
g_error ("Error creating ModemManager object manager proxy: %s",
|
||||
error->message);
|
||||
}
|
||||
|
||||
g_signal_connect (self->manager, "modem-added",
|
||||
G_CALLBACK (modem_added_cb), self);
|
||||
g_signal_connect (self->manager, "modem-removed",
|
||||
G_CALLBACK (modem_removed_cb), self);
|
||||
|
||||
gdbo_manager_call_get_modems
|
||||
(self->manager,
|
||||
NULL,
|
||||
(GAsyncReadyCallback) get_modems_cb,
|
||||
self);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ofono_vanished_cb (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
CallsOfonoProvider *self)
|
||||
{
|
||||
g_debug ("Ofono vanished from D-Bus");
|
||||
g_list_store_remove_all (self->origins);
|
||||
update_status (self);
|
||||
}
|
||||
|
||||
static void
|
||||
constructed (GObject *object)
|
||||
{
|
||||
CallsOfonoProvider *self = CALLS_OFONO_PROVIDER (object);
|
||||
|
||||
self->watch_id =
|
||||
g_bus_watch_name (G_BUS_TYPE_SYSTEM,
|
||||
"org.ofono",
|
||||
G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
|
||||
(GBusNameAppearedCallback) ofono_appeared_cb,
|
||||
(GBusNameVanishedCallback) ofono_vanished_cb,
|
||||
self, NULL);
|
||||
|
||||
g_debug ("Watching for Ofono");
|
||||
|
||||
|
||||
G_OBJECT_CLASS (calls_ofono_provider_parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
dispose (GObject *object)
|
||||
{
|
||||
CallsOfonoProvider *self = CALLS_OFONO_PROVIDER (object);
|
||||
|
||||
g_clear_object (&self->manager);
|
||||
g_clear_object (&self->connection);
|
||||
|
||||
G_OBJECT_CLASS (calls_ofono_provider_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
finalize (GObject *object)
|
||||
{
|
||||
CallsOfonoProvider *self = CALLS_OFONO_PROVIDER (object);
|
||||
|
||||
g_object_unref (self->origins);
|
||||
g_free (self->status);
|
||||
g_hash_table_unref (self->modems);
|
||||
|
||||
G_OBJECT_CLASS (calls_ofono_provider_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_ofono_provider_class_init (CallsOfonoProviderClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
CallsProviderClass *provider_class = CALLS_PROVIDER_CLASS (klass);
|
||||
|
||||
object_class->constructed = constructed;
|
||||
object_class->dispose = dispose;
|
||||
object_class->finalize = finalize;
|
||||
|
||||
provider_class->get_name = calls_ofono_provider_get_name;
|
||||
provider_class->get_status = calls_ofono_provider_get_status;
|
||||
provider_class->get_origins = calls_ofono_provider_get_origins;
|
||||
provider_class->get_protocols = calls_ofono_provider_get_protocols;
|
||||
provider_class->is_modem = calls_ofono_provider_is_modem;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_ofono_provider_class_finalize (CallsOfonoProviderClass *klass)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_ofono_provider_message_source_interface_init (CallsMessageSourceInterface *iface)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_ofono_provider_init (CallsOfonoProvider *self)
|
||||
{
|
||||
self->status = g_strdup (_("Initialized"));
|
||||
self->modems = g_hash_table_new_full (g_str_hash, g_str_equal,
|
||||
g_free, g_object_unref);
|
||||
self->origins = g_list_store_new (CALLS_TYPE_ORIGIN);
|
||||
}
|
||||
|
||||
|
||||
G_MODULE_EXPORT void
|
||||
peas_register_types (PeasObjectModule *module)
|
||||
{
|
||||
calls_ofono_provider_register_type (G_TYPE_MODULE (module));
|
||||
|
||||
peas_object_module_register_extension_type (module,
|
||||
CALLS_TYPE_PROVIDER,
|
||||
CALLS_TYPE_OFONO_PROVIDER);
|
||||
}
|
||||
44
plugins/provider/ofono/calls-ofono-provider.h
Normal file
44
plugins/provider/ofono/calls-ofono-provider.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Bob Ham <bob.ham@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALLS_OFONO_PROVIDER_H__
|
||||
#define CALLS_OFONO_PROVIDER_H__
|
||||
|
||||
#include "calls-provider.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <gio/gio.h>
|
||||
#include <libpeas/peas.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_OFONO_PROVIDER (calls_ofono_provider_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsOfonoProvider, calls_ofono_provider, CALLS, OFONO_PROVIDER, CallsProvider)
|
||||
|
||||
void peas_register_types (PeasObjectModule *module);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* CALLS_OFONO_PROVIDER_H__ */
|
||||
12
plugins/provider/ofono/libgdbofono/call.xml
Normal file
12
plugins/provider/ofono/libgdbofono/call.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
|
||||
<interface name="org.ofono.VoiceCall"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="Deflect"><arg name="number" type="s" direction="in"/>
|
||||
</method><method name="Hangup"></method><method name="Answer"></method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
<signal name="DisconnectReason"><arg name="reason" type="s"/>
|
||||
</signal>
|
||||
</interface></node>
|
||||
11
plugins/provider/ofono/libgdbofono/dbus-introspect.sh
Executable file
11
plugins/provider/ofono/libgdbofono/dbus-introspect.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
DEST="$1"
|
||||
OBJ_PATH="$2"
|
||||
METHOD="$3"
|
||||
shift 3
|
||||
|
||||
dbus-send "$@" --print-reply --dest="$DEST" "$OBJ_PATH" "$METHOD" | \
|
||||
grep -v '^method return' | \
|
||||
sed -e 's/^[[:space:]]\+string "</</' \
|
||||
-e 's_</node>"_</node>_'
|
||||
13
plugins/provider/ofono/libgdbofono/manager.xml
Normal file
13
plugins/provider/ofono/libgdbofono/manager.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
|
||||
<interface name="org.ofono.Manager"><method name="GetModems"><arg name="modems" type="a(oa{sv})" direction="out"/>
|
||||
</method><signal name="ModemAdded"><arg name="path" type="o"/>
|
||||
<arg name="properties" type="a{sv}"/>
|
||||
</signal>
|
||||
<signal name="ModemRemoved"><arg name="path" type="o"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
</node>
|
||||
51
plugins/provider/ofono/libgdbofono/meson.build
Normal file
51
plugins/provider/ofono/libgdbofono/meson.build
Normal file
@@ -0,0 +1,51 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author: Bob Ham <bob.ham@puri.sm>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
|
||||
gnome = import('gnome')
|
||||
dbus_interfaces = ['manager', 'modem', 'call']
|
||||
|
||||
gdbofono_src = []
|
||||
gdbofono_headers = []
|
||||
foreach iface: dbus_interfaces
|
||||
src = gnome.gdbus_codegen(
|
||||
'gdbo-' + iface,
|
||||
iface + '.xml',
|
||||
interface_prefix: 'org.ofono.',
|
||||
namespace: 'GDBO'
|
||||
)
|
||||
gdbofono_src += src
|
||||
gdbofono_headers += src[1]
|
||||
endforeach
|
||||
|
||||
gdbofono_deps = [
|
||||
dependency('gio-2.0'),
|
||||
dependency('gio-unix-2.0'),
|
||||
]
|
||||
|
||||
gdbofono_lib = static_library(
|
||||
'gdbofono',
|
||||
gdbofono_src,
|
||||
include_directories: top_include,
|
||||
dependencies: gdbofono_deps
|
||||
)
|
||||
249
plugins/provider/ofono/libgdbofono/modem-full.xml
Normal file
249
plugins/provider/ofono/libgdbofono/modem-full.xml
Normal file
@@ -0,0 +1,249 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
|
||||
<interface name="org.ofono.Modem"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.SimManager"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><method name="ChangePin"><arg name="type" type="s" direction="in"/>
|
||||
<arg name="oldpin" type="s" direction="in"/>
|
||||
<arg name="newpin" type="s" direction="in"/>
|
||||
</method><method name="EnterPin"><arg name="type" type="s" direction="in"/>
|
||||
<arg name="pin" type="s" direction="in"/>
|
||||
</method><method name="ResetPin"><arg name="type" type="s" direction="in"/>
|
||||
<arg name="puk" type="s" direction="in"/>
|
||||
<arg name="newpin" type="s" direction="in"/>
|
||||
</method><method name="LockPin"><arg name="type" type="s" direction="in"/>
|
||||
<arg name="pin" type="s" direction="in"/>
|
||||
</method><method name="UnlockPin"><arg name="type" type="s" direction="in"/>
|
||||
<arg name="pin" type="s" direction="in"/>
|
||||
</method><method name="GetIcon"><arg name="id" type="y" direction="in"/>
|
||||
<arg name="icon" type="ay" direction="out"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.VoiceCallManager"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="Dial"><arg name="number" type="s" direction="in"/>
|
||||
<arg name="hide_callerid" type="s" direction="in"/>
|
||||
<arg name="path" type="o" direction="out"/>
|
||||
</method><method name="DialLast"></method><method name="DialMemory"><arg name="memory_location" type="u" direction="in"/>
|
||||
</method><method name="Transfer"></method><method name="SwapCalls"></method><method name="ReleaseAndAnswer"></method><method name="ReleaseAndSwap"></method><method name="HoldAndAnswer"></method><method name="HangupAll"></method><method name="PrivateChat"><arg name="call" type="o" direction="in"/>
|
||||
<arg name="calls" type="ao" direction="out"/>
|
||||
</method><method name="CreateMultiparty"><arg name="calls" type="ao" direction="out"/>
|
||||
</method><method name="HangupMultiparty"></method><method name="SendTones"><arg name="SendTones" type="s" direction="in"/>
|
||||
</method><method name="GetCalls"><arg name="calls_with_properties" type="a(oa{sv})" direction="out"/>
|
||||
</method><signal name="Forwarded"><arg name="type" type="s"/>
|
||||
</signal>
|
||||
<signal name="BarringActive"><arg name="type" type="s"/>
|
||||
</signal>
|
||||
<signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
<signal name="CallAdded"><arg name="path" type="o"/>
|
||||
<arg name="properties" type="a{sv}"/>
|
||||
</signal>
|
||||
<signal name="CallRemoved"><arg name="path" type="o"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.AllowedAccessPoints"><method name="GetAllowedAccessPoints"><arg name="apnlist" type="as" direction="out"/>
|
||||
</method></interface>
|
||||
|
||||
<interface name="org.ofono.SimAuthentication"><method name="GetApplications"><arg name="applications" type="a{oa{sv}}" direction="out"/>
|
||||
</method><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method></interface>
|
||||
|
||||
<interface name="org.ofono.SimToolkit"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SelectItem"><arg name="item" type="y" direction="in"/>
|
||||
<arg name="agent" type="o" direction="in"/>
|
||||
</method><method name="RegisterAgent"><arg name="path" type="o" direction="in"/>
|
||||
</method><method name="UnregisterAgent"><arg name="path" type="o" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.CallForwarding"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><method name="DisableAll"><arg name="type" type="s" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.RadioSettings"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.TextTelephony"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.Phonebook"><method name="Import"><arg name="entries" type="s" direction="out"/>
|
||||
</method></interface>
|
||||
|
||||
<interface name="org.ofono.MessageManager"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><method name="SendMessage"><arg name="to" type="s" direction="in"/>
|
||||
<arg name="text" type="s" direction="in"/>
|
||||
<arg name="path" type="o" direction="out"/>
|
||||
</method><method name="GetMessages"><arg name="messages" type="a(oa{sv})" direction="out"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
<signal name="IncomingMessage"><arg name="message" type="s"/>
|
||||
<arg name="info" type="a{sv}"/>
|
||||
</signal>
|
||||
<signal name="ImmediateMessage"><arg name="message" type="s"/>
|
||||
<arg name="info" type="a{sv}"/>
|
||||
</signal>
|
||||
<signal name="MessageAdded"><arg name="path" type="o"/>
|
||||
<arg name="properties" type="a{sv}"/>
|
||||
</signal>
|
||||
<signal name="MessageRemoved"><arg name="path" type="o"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.PushNotification"><method name="RegisterAgent"><arg name="path" type="o" direction="in"/>
|
||||
</method><method name="UnregisterAgent"><arg name="path" type="o" direction="in"/>
|
||||
</method></interface>
|
||||
|
||||
<interface name="org.ofono.SmartMessaging"><method name="RegisterAgent"><arg name="path" type="o" direction="in"/>
|
||||
</method><method name="UnregisterAgent"><arg name="path" type="o" direction="in"/>
|
||||
</method><method name="SendBusinessCard"><arg name="to" type="s" direction="in"/>
|
||||
<arg name="card" type="ay" direction="in"/>
|
||||
<arg name="path" type="o" direction="out"/>
|
||||
</method><method name="SendAppointment"><arg name="to" type="s" direction="in"/>
|
||||
<arg name="appointment" type="ay" direction="in"/>
|
||||
<arg name="path" type="o" direction="out"/>
|
||||
</method></interface>
|
||||
|
||||
<interface name="org.ofono.MessageWaiting"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.CallSettings"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="property" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.CallBarring"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
<arg name="pin2" type="s" direction="in"/>
|
||||
</method><method name="DisableAll"><arg name="password" type="s" direction="in"/>
|
||||
</method><method name="DisableAllIncoming"><arg name="password" type="s" direction="in"/>
|
||||
</method><method name="DisableAllOutgoing"><arg name="password" type="s" direction="in"/>
|
||||
</method><method name="ChangePassword"><arg name="old" type="s" direction="in"/>
|
||||
<arg name="new" type="s" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.SupplementaryServices"><method name="Initiate"><arg name="command" type="s" direction="in"/>
|
||||
<arg name="result_name" type="s" direction="out"/>
|
||||
<arg name="value" type="v" direction="out"/>
|
||||
</method><method name="Respond"><arg name="reply" type="s" direction="in"/>
|
||||
<arg name="result" type="s" direction="out"/>
|
||||
</method><method name="Cancel"></method><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><signal name="NotificationReceived"><arg name="message" type="s"/>
|
||||
</signal>
|
||||
<signal name="RequestReceived"><arg name="message" type="s"/>
|
||||
</signal>
|
||||
<signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.CallMeter"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
<arg name="password" type="s" direction="in"/>
|
||||
</method><method name="Reset"><arg name="passoword" type="s" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="property" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
<signal name="NearMaximumWarning"></signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.CallVolume"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="property" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.NetworkRegistration"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="Register"></method><method name="GetOperators"><arg name="operators_with_properties" type="a(oa{sv})" direction="out"/>
|
||||
</method><method name="Scan"><arg name="operators_with_properties" type="a(oa{sv})" direction="out"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.CellBroadcast"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="property" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
<signal name="IncomingBroadcast"><arg name="message" type="s"/>
|
||||
<arg name="channel" type="q"/>
|
||||
</signal>
|
||||
<signal name="EmergencyBroadcast"><arg name="message" type="s"/>
|
||||
<arg name="dict" type="a{sv}"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.AssistedSatelliteNavigation"><method name="SendPositioningElement"><arg name="xml_elements" type="(null)" direction="in"/>
|
||||
</method><method name="RegisterPositioningRequestAgent"><arg name="agent" type="o" direction="in"/>
|
||||
</method><method name="UnregisterPositioningRequestAgent"><arg name="agent" type="o" direction="in"/>
|
||||
</method></interface>
|
||||
|
||||
<interface name="org.ofono.ConnectionManager"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><method name="AddContext"><arg name="type" type="s" direction="in"/>
|
||||
<arg name="path" type="o" direction="out"/>
|
||||
</method><method name="RemoveContext"><arg name="path" type="o" direction="in"/>
|
||||
</method><method name="DeactivateAll"></method><method name="GetContexts"><arg name="contexts_with_properties" type="a(oa{sv})" direction="out"/>
|
||||
</method><method name="ResetContexts"></method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
<signal name="ContextAdded"><arg name="path" type="o"/>
|
||||
<arg name="properties" type="a{sv}"/>
|
||||
</signal>
|
||||
<signal name="ContextRemoved"><arg name="path" type="o"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
</node>
|
||||
37
plugins/provider/ofono/libgdbofono/modem.xml
Normal file
37
plugins/provider/ofono/libgdbofono/modem.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
|
||||
<interface name="org.ofono.Modem"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="SetProperty"><arg name="property" type="s" direction="in"/>
|
||||
<arg name="value" type="v" direction="in"/>
|
||||
</method><signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
<interface name="org.ofono.VoiceCallManager"><method name="GetProperties"><arg name="properties" type="a{sv}" direction="out"/>
|
||||
</method><method name="Dial"><arg name="number" type="s" direction="in"/>
|
||||
<arg name="hide_callerid" type="s" direction="in"/>
|
||||
<arg name="path" type="o" direction="out"/>
|
||||
</method><method name="DialLast"></method><method name="DialMemory"><arg name="memory_location" type="u" direction="in"/>
|
||||
</method><method name="Transfer"></method><method name="SwapCalls"></method><method name="ReleaseAndAnswer"></method><method name="ReleaseAndSwap"></method><method name="HoldAndAnswer"></method><method name="HangupAll"></method><method name="PrivateChat"><arg name="call" type="o" direction="in"/>
|
||||
<arg name="calls" type="ao" direction="out"/>
|
||||
</method><method name="CreateMultiparty"><arg name="calls" type="ao" direction="out"/>
|
||||
</method><method name="HangupMultiparty"></method><method name="SendTones"><arg name="SendTones" type="s" direction="in"/>
|
||||
</method><method name="GetCalls"><arg name="calls_with_properties" type="a(oa{sv})" direction="out"/>
|
||||
</method><signal name="Forwarded"><arg name="type" type="s"/>
|
||||
</signal>
|
||||
<signal name="BarringActive"><arg name="type" type="s"/>
|
||||
</signal>
|
||||
<signal name="PropertyChanged"><arg name="name" type="s"/>
|
||||
<arg name="value" type="v"/>
|
||||
</signal>
|
||||
<signal name="CallAdded"><arg name="path" type="o"/>
|
||||
<arg name="properties" type="a{sv}"/>
|
||||
</signal>
|
||||
<signal name="CallRemoved"><arg name="path" type="o"/>
|
||||
</signal>
|
||||
</interface>
|
||||
|
||||
</node>
|
||||
60
plugins/provider/ofono/meson.build
Normal file
60
plugins/provider/ofono/meson.build
Normal file
@@ -0,0 +1,60 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author: Bob Ham <bob.ham@puri.sm>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
subdir('libgdbofono')
|
||||
|
||||
ofono_install_dir = join_paths(full_calls_plugin_libdir, 'ofono')
|
||||
|
||||
ofono_plugin = configure_file(
|
||||
input: 'ofono.plugin.in',
|
||||
output: 'ofono.plugin',
|
||||
configuration: config_data,
|
||||
install_dir: ofono_install_dir
|
||||
)
|
||||
|
||||
ofono_deps = [
|
||||
dependency('gobject-2.0'),
|
||||
dependency('gtk+-3.0'),
|
||||
dependency('libpeas-1.0'),
|
||||
]
|
||||
|
||||
ofono_sources = files(
|
||||
[
|
||||
'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_ofono = shared_module(
|
||||
'ofono',
|
||||
ofono_sources, gdbofono_headers,
|
||||
dependencies: ofono_deps,
|
||||
include_directories: [
|
||||
src_include,
|
||||
include_directories('.')
|
||||
],
|
||||
link_with: [gdbofono_lib, libcalls],
|
||||
install: true,
|
||||
install_dir: ofono_install_dir
|
||||
)
|
||||
7
plugins/provider/ofono/ofono.plugin.in
Normal file
7
plugins/provider/ofono/ofono.plugin.in
Normal file
@@ -0,0 +1,7 @@
|
||||
[Plugin]
|
||||
Module=ofono
|
||||
Name=oFono
|
||||
Description=oFono calls provider
|
||||
Authors=Bob Ham <rah@settrans.net>
|
||||
Copyright=Copyright (C) 2018 Purism SPC
|
||||
Website=@PACKAGE_URL_RAW@
|
||||
512
plugins/provider/sip/calls-sdp-crypto-context.c
Normal file
512
plugins/provider/sip/calls-sdp-crypto-context.c
Normal file
@@ -0,0 +1,512 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#include "calls-sdp-crypto-context.h"
|
||||
#include "calls-sip-enums.h"
|
||||
|
||||
#include <sofia-sip/sdp.h>
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_STATE,
|
||||
PROP_LAST_PROP
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
struct _CallsSdpCryptoContext {
|
||||
GObject parent_instance;
|
||||
|
||||
GList *local_crypto_attributes;
|
||||
GList *remote_crypto_attributes;
|
||||
|
||||
CallsCryptoContextState state;
|
||||
int negotiated_tag;
|
||||
};
|
||||
|
||||
#if GLIB_CHECK_VERSION (2, 70, 0)
|
||||
G_DEFINE_FINAL_TYPE (CallsSdpCryptoContext, calls_sdp_crypto_context, G_TYPE_OBJECT)
|
||||
#else
|
||||
G_DEFINE_TYPE (CallsSdpCryptoContext, calls_sdp_crypto_context, G_TYPE_OBJECT)
|
||||
#endif
|
||||
|
||||
|
||||
static GStrv
|
||||
get_all_crypto_attributes_strv (sdp_media_t *media)
|
||||
{
|
||||
#if GLIB_CHECK_VERSION (2, 68, 0)
|
||||
g_autoptr (GStrvBuilder) builder = NULL;
|
||||
|
||||
g_assert (media);
|
||||
|
||||
builder = g_strv_builder_new ();
|
||||
|
||||
for (sdp_attribute_t *attr = media->m_attributes; attr; attr = attr->a_next) {
|
||||
g_autofree char *crypto_str = NULL;
|
||||
|
||||
if (g_strcmp0 (attr->a_name, "crypto") != 0)
|
||||
continue;
|
||||
|
||||
crypto_str = g_strconcat ("a=crypto:", attr->a_value, NULL);
|
||||
g_strv_builder_add (builder, crypto_str);
|
||||
}
|
||||
|
||||
return g_strv_builder_end (builder);
|
||||
#else
|
||||
/* implement a poor mans GStrv */
|
||||
g_autofree char *attribute_string = NULL;
|
||||
|
||||
g_assert (media);
|
||||
|
||||
for (sdp_attribute_t *attr = media->m_attributes; attr; attr = attr->a_next) {
|
||||
g_autofree char *crypto_str = NULL;
|
||||
|
||||
if (g_strcmp0 (attr->a_name, "crypto") != 0)
|
||||
continue;
|
||||
|
||||
crypto_str = g_strconcat ("a=crypto:", attr->a_value, NULL);
|
||||
if (!attribute_string) {
|
||||
attribute_string = g_strdup (crypto_str);
|
||||
} else {
|
||||
g_autofree char *tmp = attribute_string;
|
||||
attribute_string = g_strconcat (attribute_string, "\n", crypto_str, NULL);
|
||||
}
|
||||
}
|
||||
return g_strsplit (attribute_string, "\n", -1);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
crypto_attribute_is_supported (CallsSdpCryptoContext *self,
|
||||
calls_srtp_crypto_attribute *attr)
|
||||
{
|
||||
g_assert (attr);
|
||||
|
||||
if (attr->crypto_suite == CALLS_SRTP_SUITE_UNKNOWN)
|
||||
return FALSE;
|
||||
|
||||
/* TODO setup a policy mechanism, for now this is hardcoded */
|
||||
if (attr->unencrypted_srtp ||
|
||||
attr->unauthenticated_srtp ||
|
||||
attr->unencrypted_srtcp)
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static calls_srtp_crypto_attribute *
|
||||
get_crypto_attribute_by_tag (GList *attributes,
|
||||
guint tag)
|
||||
{
|
||||
|
||||
g_assert (attributes);
|
||||
g_assert (tag > 0);
|
||||
|
||||
for (GList *node = attributes; node; node = node->next) {
|
||||
calls_srtp_crypto_attribute *attr = node->data;
|
||||
|
||||
if (attr->tag == tag)
|
||||
return attr;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
set_state (CallsSdpCryptoContext *self,
|
||||
CallsCryptoContextState state)
|
||||
{
|
||||
g_assert (CALLS_IS_SDP_CRYPTO_CONTEXT (self));
|
||||
|
||||
if (self->state == state)
|
||||
return;
|
||||
|
||||
self->state = state;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STATE]);
|
||||
}
|
||||
|
||||
|
||||
/* Returns if %TRUE if the state was updated, %FALSE otherwise */
|
||||
static gboolean
|
||||
update_state (CallsSdpCryptoContext *self)
|
||||
{
|
||||
GList *tags_local = NULL;
|
||||
GList *tags_remote = NULL;
|
||||
gint negotiated_tag = -1;
|
||||
calls_srtp_crypto_attribute *local_crypto;
|
||||
calls_srtp_crypto_attribute *remote_crypto;
|
||||
|
||||
|
||||
g_assert (CALLS_IS_SDP_CRYPTO_CONTEXT (self));
|
||||
|
||||
/* Cannot update final states */
|
||||
if (self->state == CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_FAILED ||
|
||||
self->state == CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS)
|
||||
return FALSE;
|
||||
|
||||
if (self->state == CALLS_CRYPTO_CONTEXT_STATE_INIT) {
|
||||
if (self->local_crypto_attributes) {
|
||||
set_state (self, CALLS_CRYPTO_CONTEXT_STATE_OFFER_LOCAL);
|
||||
return TRUE;
|
||||
}
|
||||
if (self->remote_crypto_attributes) {
|
||||
set_state (self, CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (GList *node = self->local_crypto_attributes; node; node = node->next) {
|
||||
calls_srtp_crypto_attribute *attr = node->data;
|
||||
tags_local = g_list_append (tags_local, GUINT_TO_POINTER (attr->tag));
|
||||
}
|
||||
|
||||
for (GList *node = self->remote_crypto_attributes; node; node = node->next) {
|
||||
calls_srtp_crypto_attribute *attr = node->data;
|
||||
tags_remote = g_list_append (tags_remote, GUINT_TO_POINTER (attr->tag));
|
||||
}
|
||||
|
||||
if (self->state == CALLS_CRYPTO_CONTEXT_STATE_OFFER_LOCAL) {
|
||||
for (GList *node = tags_local; node; node = node->next) {
|
||||
if (g_list_find (tags_remote, node->data)) {
|
||||
negotiated_tag = GPOINTER_TO_UINT (node->data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (self->state == CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE) {
|
||||
for (GList *node = tags_remote; node; node = node->next) {
|
||||
if (g_list_find (tags_local, node->data)) {
|
||||
negotiated_tag = GPOINTER_TO_UINT (node->data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
g_list_free (tags_local);
|
||||
g_list_free (tags_remote);
|
||||
|
||||
if (negotiated_tag == -1) {
|
||||
self->state = CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_FAILED;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
local_crypto = get_crypto_attribute_by_tag (self->local_crypto_attributes,
|
||||
negotiated_tag);
|
||||
remote_crypto = get_crypto_attribute_by_tag (self->remote_crypto_attributes,
|
||||
negotiated_tag);
|
||||
|
||||
if (local_crypto->crypto_suite != remote_crypto->crypto_suite) {
|
||||
set_state (self, CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_FAILED);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* TODO check mandatory parameters and policy constrains */
|
||||
|
||||
self->negotiated_tag = negotiated_tag;
|
||||
set_state (self, CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sdp_crypto_context_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsSdpCryptoContext *self = CALLS_SDP_CRYPTO_CONTEXT (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_STATE:
|
||||
g_value_set_enum (value, self->state);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sdp_crypto_context_dispose (GObject *object)
|
||||
{
|
||||
CallsSdpCryptoContext *self = CALLS_SDP_CRYPTO_CONTEXT (object);
|
||||
|
||||
g_clear_list (&self->local_crypto_attributes, (GDestroyNotify) calls_srtp_crypto_attribute_free);
|
||||
g_clear_list (&self->remote_crypto_attributes, (GDestroyNotify) calls_srtp_crypto_attribute_free);
|
||||
|
||||
G_OBJECT_CLASS (calls_sdp_crypto_context_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sdp_crypto_context_class_init (CallsSdpCryptoContextClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->dispose = calls_sdp_crypto_context_dispose;
|
||||
object_class->get_property = calls_sdp_crypto_context_get_property;
|
||||
|
||||
props[PROP_STATE] =
|
||||
g_param_spec_enum ("state",
|
||||
"State",
|
||||
"State of the crypto context",
|
||||
CALLS_TYPE_CRYPTO_CONTEXT_STATE,
|
||||
CALLS_CRYPTO_CONTEXT_STATE_INIT,
|
||||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sdp_crypto_context_init (CallsSdpCryptoContext *self)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
CallsSdpCryptoContext *
|
||||
calls_sdp_crypto_context_new (void)
|
||||
{
|
||||
return g_object_new (CALLS_TYPE_SDP_CRYPTO_CONTEXT, NULL);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
calls_sdp_crypto_context_set_local_media (CallsSdpCryptoContext *self,
|
||||
sdp_media_t *media)
|
||||
{
|
||||
g_auto (GStrv) crypto_strv = NULL;
|
||||
guint n_crypto_attr;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE);
|
||||
g_return_val_if_fail (media, FALSE);
|
||||
|
||||
if (self->local_crypto_attributes) {
|
||||
g_warning ("Local crypto attributes already set");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
crypto_strv = get_all_crypto_attributes_strv (media);
|
||||
n_crypto_attr = g_strv_length (crypto_strv);
|
||||
|
||||
if (n_crypto_attr == 0) {
|
||||
g_warning ("No crypto attributes found in given SDP media");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (guint i = 0; i < n_crypto_attr; i++) {
|
||||
g_autoptr (GError) error = NULL;
|
||||
calls_srtp_crypto_attribute *attr;
|
||||
|
||||
attr = calls_srtp_parse_sdp_crypto_attribute (crypto_strv[i], &error);
|
||||
if (!attr) {
|
||||
g_warning ("Failed parsing crypto attribute '%s': %s",
|
||||
crypto_strv[i], error->message);
|
||||
continue;
|
||||
}
|
||||
self->local_crypto_attributes =
|
||||
g_list_append (self->local_crypto_attributes, attr);
|
||||
}
|
||||
|
||||
if (!self->local_crypto_attributes) {
|
||||
g_warning ("Could not parse a single crypto attribute, aborting");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return update_state (self);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
calls_sdp_crypto_context_set_remote_media (CallsSdpCryptoContext *self,
|
||||
sdp_media_t *media)
|
||||
{
|
||||
g_auto (GStrv) crypto_strv = NULL;
|
||||
|
||||
guint n_crypto_attr;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE);
|
||||
g_return_val_if_fail (media, FALSE);
|
||||
|
||||
if (self->remote_crypto_attributes) {
|
||||
g_warning ("Remote crypto attributes already set");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
crypto_strv = get_all_crypto_attributes_strv (media);
|
||||
n_crypto_attr = g_strv_length (crypto_strv);
|
||||
|
||||
if (n_crypto_attr == 0) {
|
||||
g_warning ("No crypto attributes found in given SDP media");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (guint i = 0; i < n_crypto_attr; i++) {
|
||||
g_autoptr (GError) error = NULL;
|
||||
calls_srtp_crypto_attribute *attr;
|
||||
|
||||
attr = calls_srtp_parse_sdp_crypto_attribute (crypto_strv[i], &error);
|
||||
if (!attr) {
|
||||
g_warning ("Failed parsing crypto attribute '%s': %s",
|
||||
crypto_strv[i], error->message);
|
||||
continue;
|
||||
}
|
||||
self->remote_crypto_attributes =
|
||||
g_list_append (self->remote_crypto_attributes, attr);
|
||||
}
|
||||
|
||||
if (!self->remote_crypto_attributes) {
|
||||
g_warning ("Could not parse a single crypto attribute, aborting");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return update_state (self);
|
||||
}
|
||||
|
||||
|
||||
calls_srtp_crypto_attribute *
|
||||
calls_sdp_crypto_context_get_local_crypto (CallsSdpCryptoContext *self)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), NULL);
|
||||
|
||||
if (self->state != CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS)
|
||||
return NULL;
|
||||
|
||||
return get_crypto_attribute_by_tag (self->local_crypto_attributes,
|
||||
self->negotiated_tag);
|
||||
}
|
||||
|
||||
|
||||
calls_srtp_crypto_attribute *
|
||||
calls_sdp_crypto_context_get_remote_crypto (CallsSdpCryptoContext *self)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), NULL);
|
||||
|
||||
if (self->state != CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS)
|
||||
return NULL;
|
||||
|
||||
return get_crypto_attribute_by_tag (self->remote_crypto_attributes,
|
||||
self->negotiated_tag);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
calls_sdp_crypto_context_generate_offer (CallsSdpCryptoContext *self)
|
||||
{
|
||||
calls_srtp_crypto_attribute *attr;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE);
|
||||
|
||||
if (self->state != CALLS_CRYPTO_CONTEXT_STATE_INIT) {
|
||||
g_warning ("Cannot generate offer. Need INIT state, but found %d",
|
||||
self->state);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_assert (!self->local_crypto_attributes);
|
||||
|
||||
attr = calls_srtp_crypto_attribute_new (1);
|
||||
attr->tag = 1;
|
||||
attr->crypto_suite = CALLS_SRTP_SUITE_AES_128_SHA1_80;
|
||||
calls_srtp_crypto_attribute_init_keys (attr);
|
||||
|
||||
self->local_crypto_attributes = g_list_append (NULL, attr);
|
||||
|
||||
attr = calls_srtp_crypto_attribute_new (1);
|
||||
attr->tag = 2;
|
||||
attr->crypto_suite = CALLS_SRTP_SUITE_AES_128_SHA1_32;
|
||||
calls_srtp_crypto_attribute_init_keys (attr);
|
||||
|
||||
self->local_crypto_attributes = g_list_append (self->local_crypto_attributes, attr);
|
||||
|
||||
return update_state (self);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
calls_sdp_crypto_context_generate_answer (CallsSdpCryptoContext *self)
|
||||
{
|
||||
calls_srtp_crypto_attribute *attr = NULL;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE);
|
||||
|
||||
if (self->state != CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE) {
|
||||
g_warning ("Cannot generate answer. Need OFFER_REMOTE state, but found %d",
|
||||
self->state);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (GList *node = self->remote_crypto_attributes; node; node = node->next) {
|
||||
calls_srtp_crypto_attribute *attr_offer = node->data;
|
||||
|
||||
if (crypto_attribute_is_supported (self, attr_offer)) {
|
||||
attr = calls_srtp_crypto_attribute_new (1);
|
||||
attr->crypto_suite = attr_offer->crypto_suite;
|
||||
attr->tag = attr_offer->tag;
|
||||
calls_srtp_crypto_attribute_init_keys (attr);
|
||||
}
|
||||
}
|
||||
|
||||
if (!attr)
|
||||
return FALSE;
|
||||
|
||||
self->local_crypto_attributes = g_list_append (NULL, attr);
|
||||
|
||||
return update_state (self);
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
calls_sdp_crypto_context_get_is_negotiated (CallsSdpCryptoContext *self)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), FALSE);
|
||||
|
||||
return self->state == CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
CallsCryptoContextState
|
||||
calls_sdp_crypto_context_get_state (CallsSdpCryptoContext *self)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), CALLS_CRYPTO_CONTEXT_STATE_INIT);
|
||||
|
||||
return self->state;
|
||||
}
|
||||
|
||||
|
||||
GList *
|
||||
calls_sdp_crypto_context_get_crypto_candidates (CallsSdpCryptoContext *self,
|
||||
gboolean remote)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SDP_CRYPTO_CONTEXT (self), NULL);
|
||||
|
||||
return g_list_copy (remote ? self->remote_crypto_attributes : self->local_crypto_attributes);
|
||||
}
|
||||
66
plugins/provider/sip/calls-sdp-crypto-context.h
Normal file
66
plugins/provider/sip/calls-sdp-crypto-context.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "calls-srtp-utils.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
#include <sofia-sip/sdp.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/** XXX media line with cryptographic key parameters or session parameters that we
|
||||
* do not support MUST be rejected (https://datatracker.ietf.org/doc/html/rfc4568#section-7.1.2)
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
CALLS_CRYPTO_CONTEXT_STATE_INIT = 0,
|
||||
CALLS_CRYPTO_CONTEXT_STATE_OFFER_LOCAL,
|
||||
CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE,
|
||||
CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_FAILED,
|
||||
CALLS_CRYPTO_CONTEXT_STATE_NEGOTIATION_SUCCESS
|
||||
} CallsCryptoContextState;
|
||||
|
||||
|
||||
#define CALLS_TYPE_SDP_CRYPTO_CONTEXT (calls_sdp_crypto_context_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsSdpCryptoContext, calls_sdp_crypto_context, CALLS, SDP_CRYPTO_CONTEXT, GObject);
|
||||
|
||||
CallsSdpCryptoContext *calls_sdp_crypto_context_new (void);
|
||||
gboolean calls_sdp_crypto_context_set_local_media (CallsSdpCryptoContext *self,
|
||||
sdp_media_t *media);
|
||||
gboolean calls_sdp_crypto_context_set_remote_media (CallsSdpCryptoContext *self,
|
||||
sdp_media_t *media);
|
||||
calls_srtp_crypto_attribute *calls_sdp_crypto_context_get_local_crypto (CallsSdpCryptoContext *self);
|
||||
calls_srtp_crypto_attribute *calls_sdp_crypto_context_get_remote_crypto (CallsSdpCryptoContext *self);
|
||||
gboolean calls_sdp_crypto_context_generate_offer (CallsSdpCryptoContext *self);
|
||||
gboolean calls_sdp_crypto_context_generate_answer (CallsSdpCryptoContext *self);
|
||||
gboolean calls_sdp_crypto_context_get_is_negotiated (CallsSdpCryptoContext *self);
|
||||
CallsCryptoContextState calls_sdp_crypto_context_get_state (CallsSdpCryptoContext *self);
|
||||
GList *calls_sdp_crypto_context_get_crypto_candidates (CallsSdpCryptoContext *self,
|
||||
gboolean remote);
|
||||
|
||||
G_END_DECLS
|
||||
735
plugins/provider/sip/calls-sip-account-widget.c
Normal file
735
plugins/provider/sip/calls-sip-account-widget.c
Normal file
@@ -0,0 +1,735 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "CallsSipAccountWidget"
|
||||
|
||||
#include "calls-settings.h"
|
||||
#include "calls-sip-account-widget.h"
|
||||
#include "calls-sip-provider.h"
|
||||
#include "calls-sip-origin.h"
|
||||
#include "calls-sip-util.h"
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
/**
|
||||
* Section:calls-sip-account-widget
|
||||
* short_description: A #GtkWidget to edit or add SIP accounts
|
||||
* @Title: CallsSipAccountWidget
|
||||
*
|
||||
* This #GtkWidget allows the user to add a new or edit an existing SIP account.
|
||||
*/
|
||||
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_PROVIDER,
|
||||
PROP_ORIGIN,
|
||||
PROP_LAST_PROP
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
|
||||
struct _CallsSipAccountWidget {
|
||||
GtkBox parent;
|
||||
|
||||
/* Header bar */
|
||||
GtkWidget *header_add;
|
||||
GtkSpinner *spinner_add;
|
||||
GtkWidget *header_edit;
|
||||
GtkSpinner *spinner_edit;
|
||||
GtkWidget *login_btn;
|
||||
GtkWidget *apply_btn;
|
||||
GtkWidget *delete_btn;
|
||||
|
||||
/* widgets for editing account credentials */
|
||||
GtkEntry *host;
|
||||
GtkEntry *display_name;
|
||||
GtkEntry *user;
|
||||
GtkEntry *password;
|
||||
GtkEntry *port;
|
||||
char *last_port;
|
||||
HdyComboRow *protocol;
|
||||
GListStore *protocols_store; /* bound model for protocol HdyComboRow */
|
||||
HdyComboRow *media_encryption;
|
||||
GListStore *media_encryption_store;
|
||||
GtkSwitch *tel_switch;
|
||||
GtkSwitch *auto_connect_switch;
|
||||
|
||||
|
||||
/* properties */
|
||||
CallsSipProvider *provider;
|
||||
CallsSipOrigin *origin; /* nullable to add a new account */
|
||||
|
||||
/* misc */
|
||||
CallsSettings *settings;
|
||||
gboolean connecting;
|
||||
gboolean port_self_change;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (CallsSipAccountWidget, calls_sip_account_widget, GTK_TYPE_BOX)
|
||||
|
||||
|
||||
static gboolean
|
||||
is_form_valid (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
/* TODO perform some sanity checks */
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
is_form_filled (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
return
|
||||
g_strcmp0 (gtk_entry_get_text (self->host), "") != 0 &&
|
||||
g_strcmp0 (gtk_entry_get_text (self->user), "") != 0 &&
|
||||
g_strcmp0 (gtk_entry_get_text (self->password), "") != 0 &&
|
||||
g_strcmp0 (gtk_entry_get_text (self->port), "") != 0;
|
||||
}
|
||||
|
||||
static const char *
|
||||
get_selected_protocol (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_autoptr (HdyValueObject) obj = NULL;
|
||||
const char *protocol = NULL;
|
||||
gint i;
|
||||
|
||||
if ((i = hdy_combo_row_get_selected_index (self->protocol)) != -1) {
|
||||
obj = g_list_model_get_item (G_LIST_MODEL (self->protocols_store), i);
|
||||
protocol = hdy_value_object_get_string (obj);
|
||||
}
|
||||
return protocol;
|
||||
}
|
||||
|
||||
|
||||
static SipMediaEncryption
|
||||
get_selected_media_encryption (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_autoptr (HdyValueObject) obj = NULL;
|
||||
SipMediaEncryption media_encryption = SIP_MEDIA_ENCRYPTION_NONE;
|
||||
gint i;
|
||||
|
||||
if ((i = hdy_combo_row_get_selected_index (self->media_encryption)) != -1) {
|
||||
obj = g_list_model_get_item (G_LIST_MODEL (self->media_encryption_store), i);
|
||||
media_encryption = (SipMediaEncryption) GPOINTER_TO_INT (g_object_get_data (G_OBJECT (obj), "value"));
|
||||
}
|
||||
|
||||
|
||||
return media_encryption;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
update_media_encryption (CallsSipAccountWidget *self)
|
||||
{
|
||||
gboolean transport_is_tls;
|
||||
gboolean sdes_always_allowed;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
transport_is_tls = g_strcmp0 (get_selected_protocol (self), "TLS") == 0;
|
||||
sdes_always_allowed = calls_settings_get_always_allow_sdes (self->settings);
|
||||
|
||||
gtk_widget_set_sensitive (GTK_WIDGET (self->media_encryption),
|
||||
transport_is_tls | sdes_always_allowed);
|
||||
|
||||
if (!transport_is_tls && !sdes_always_allowed)
|
||||
hdy_combo_row_set_selected_index (self->media_encryption, 0);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_user_changed (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
gtk_widget_set_sensitive (self->login_btn,
|
||||
is_form_filled (self) &&
|
||||
is_form_valid (self));
|
||||
|
||||
gtk_widget_set_sensitive (self->apply_btn,
|
||||
is_form_filled (self) &&
|
||||
is_form_valid (self));
|
||||
|
||||
update_media_encryption (self);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
set_password_visibility (CallsSipAccountWidget *self, gboolean visible)
|
||||
{
|
||||
const char *icon_name;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
g_assert (GTK_IS_ENTRY (self->password));
|
||||
|
||||
icon_name = visible ?
|
||||
"view-conceal-symbolic" :
|
||||
"view-reveal-symbolic";
|
||||
|
||||
gtk_entry_set_visibility (self->password, visible);
|
||||
gtk_entry_set_icon_from_icon_name (self->password, GTK_ENTRY_ICON_SECONDARY,
|
||||
icon_name);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_password_visibility_changed (CallsSipAccountWidget *self,
|
||||
GtkEntryIconPosition icon_pos,
|
||||
GdkEvent *event,
|
||||
GtkEntry *entry)
|
||||
{
|
||||
gboolean visible;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
g_assert (GTK_IS_ENTRY (entry));
|
||||
g_assert (icon_pos == GTK_ENTRY_ICON_SECONDARY);
|
||||
|
||||
visible = !gtk_entry_get_visibility (entry);
|
||||
set_password_visibility (self, visible);
|
||||
}
|
||||
|
||||
/*
|
||||
* Stop "insert-text" signal emission if any undesired port
|
||||
* value occurs
|
||||
*/
|
||||
static void
|
||||
on_port_entry_insert_text (CallsSipAccountWidget *self,
|
||||
char *new_text,
|
||||
int new_text_length,
|
||||
gpointer position,
|
||||
GtkEntry *entry)
|
||||
{
|
||||
size_t digit_end, len;
|
||||
int *pos;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
g_assert (GTK_IS_ENTRY (entry));
|
||||
|
||||
if (!new_text || !*new_text || self->port_self_change)
|
||||
return;
|
||||
|
||||
pos = (int *) position;
|
||||
g_object_set_data (G_OBJECT (entry), "old-pos", GINT_TO_POINTER (*pos));
|
||||
|
||||
if (new_text_length == -1)
|
||||
len = strlen (new_text);
|
||||
else
|
||||
len = new_text_length;
|
||||
|
||||
digit_end = strspn (new_text, "1234567890");
|
||||
|
||||
/* If user inserted something other than a digit,
|
||||
* stop inserting the text and warn the user.
|
||||
*/
|
||||
if (digit_end != len) {
|
||||
g_signal_stop_emission_by_name (entry, "insert-text");
|
||||
gtk_widget_error_bell (GTK_WIDGET (entry));
|
||||
} else {
|
||||
g_free (self->last_port);
|
||||
self->last_port = g_strdup (gtk_entry_get_text (entry));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
update_port_cursor_position (GtkEntry *entry)
|
||||
{
|
||||
int pos;
|
||||
|
||||
pos = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (entry), "old-pos"));
|
||||
gtk_editable_set_position (GTK_EDITABLE (entry), pos);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
get_port (CallsSipAccountWidget *self)
|
||||
{
|
||||
const char *text;
|
||||
int port = 0;
|
||||
|
||||
text = gtk_entry_get_text (self->port);
|
||||
port = (int) g_ascii_strtod (text, NULL);
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_port_entry_after_insert_text (CallsSipAccountWidget *self,
|
||||
char *new_text,
|
||||
int new_text_length,
|
||||
gpointer position,
|
||||
GtkEntry *entry)
|
||||
{
|
||||
int port = get_port (self);
|
||||
|
||||
/* Reset to the old value if new port number is invalid */
|
||||
if ((port < 0 || port > 65535) && self->last_port) {
|
||||
self->port_self_change = TRUE;
|
||||
gtk_entry_set_text (entry, self->last_port);
|
||||
g_idle_add (G_SOURCE_FUNC (update_port_cursor_position), entry);
|
||||
gtk_widget_error_bell (GTK_WIDGET (entry));
|
||||
self->port_self_change = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
update_header (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
if (self->origin) {
|
||||
gtk_widget_show (self->header_edit);
|
||||
gtk_widget_hide (self->header_add);
|
||||
|
||||
} else {
|
||||
gtk_widget_show (self->header_add);
|
||||
gtk_widget_hide (self->header_edit);
|
||||
}
|
||||
|
||||
if (self->connecting) {
|
||||
gtk_spinner_start (self->spinner_add);
|
||||
gtk_spinner_start (self->spinner_edit);
|
||||
} else {
|
||||
gtk_spinner_stop (self->spinner_add);
|
||||
gtk_spinner_stop (self->spinner_edit);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
find_protocol (CallsSipAccountWidget *self,
|
||||
const char *protocol,
|
||||
guint *index)
|
||||
{
|
||||
guint len;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
len = g_list_model_get_n_items (G_LIST_MODEL (self->protocols_store));
|
||||
for (guint i = 0; i < len; i++) {
|
||||
g_autoptr (HdyValueObject) obj =
|
||||
g_list_model_get_item (G_LIST_MODEL (self->protocols_store), i);
|
||||
const char *prot = hdy_value_object_get_string (obj);
|
||||
|
||||
if (g_strcmp0 (protocol, prot) == 0) {
|
||||
if (index)
|
||||
*index = i;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
g_warning ("Could not find protocol '%s'", protocol);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
find_media_encryption (CallsSipAccountWidget *self,
|
||||
const SipMediaEncryption encryption,
|
||||
guint *index)
|
||||
{
|
||||
guint len;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
len = g_list_model_get_n_items (G_LIST_MODEL (self->media_encryption_store));
|
||||
|
||||
for (guint i = 0; i < len; i++) {
|
||||
g_autoptr (HdyValueObject) obj =
|
||||
g_list_model_get_item (G_LIST_MODEL (self->media_encryption_store), i);
|
||||
SipMediaEncryption obj_enc =
|
||||
(SipMediaEncryption) GPOINTER_TO_INT (g_object_get_data (G_OBJECT (obj), "value"));
|
||||
|
||||
if (obj_enc == encryption) {
|
||||
if (index)
|
||||
*index = i;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
g_warning ("Could not find encryption mode %d", encryption);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
clear_form (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
gtk_entry_set_text (self->host, "");
|
||||
gtk_entry_set_text (self->display_name, "");
|
||||
gtk_entry_set_text (self->user, "");
|
||||
gtk_entry_set_text (self->password, "");
|
||||
gtk_entry_set_text (self->port, "0");
|
||||
hdy_combo_row_set_selected_index (self->protocol, 0);
|
||||
gtk_widget_set_sensitive (GTK_WIDGET (self->media_encryption), FALSE);
|
||||
hdy_combo_row_set_selected_index (self->media_encryption, 0);
|
||||
gtk_switch_set_state (self->tel_switch, FALSE);
|
||||
gtk_switch_set_state (self->auto_connect_switch, TRUE);
|
||||
|
||||
self->origin = NULL;
|
||||
|
||||
update_header (self);
|
||||
|
||||
if (gtk_widget_get_can_focus (GTK_WIDGET (self->host)))
|
||||
gtk_widget_grab_focus (GTK_WIDGET (self->host));
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
edit_form (CallsSipAccountWidget *self,
|
||||
CallsSipOrigin *origin)
|
||||
{
|
||||
g_autofree char *host = NULL;
|
||||
g_autofree char *display_name = NULL;
|
||||
g_autofree char *user = NULL;
|
||||
g_autofree char *password = NULL;
|
||||
g_autofree char *port_str = NULL;
|
||||
g_autofree char *protocol = NULL;
|
||||
gint port;
|
||||
SipMediaEncryption encryption;
|
||||
guint encryption_index;
|
||||
guint protocol_index;
|
||||
gboolean can_tel;
|
||||
gboolean auto_connect;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
|
||||
if (!origin) {
|
||||
clear_form (self);
|
||||
return;
|
||||
}
|
||||
|
||||
g_assert (CALLS_IS_SIP_ORIGIN (origin));
|
||||
|
||||
self->origin = origin;
|
||||
|
||||
g_object_get (origin,
|
||||
"host", &host,
|
||||
"display-name", &display_name,
|
||||
"user", &user,
|
||||
"password", &password,
|
||||
"port", &port,
|
||||
"transport-protocol", &protocol,
|
||||
"media-encryption", &encryption,
|
||||
"can-tel", &can_tel,
|
||||
"auto-connect", &auto_connect,
|
||||
NULL);
|
||||
|
||||
port_str = g_strdup_printf ("%d", port);
|
||||
|
||||
/* The following should always succeed,
|
||||
TODO inform user in the error case
|
||||
related issue #275 https://source.puri.sm/Librem5/calls/-/issues/275
|
||||
*/
|
||||
if (!find_protocol (self, protocol, &protocol_index))
|
||||
protocol_index = 0;
|
||||
|
||||
if (!find_media_encryption (self, encryption, &encryption_index))
|
||||
encryption_index = 0;
|
||||
|
||||
/* set UI elements */
|
||||
gtk_entry_set_text (self->host, host);
|
||||
gtk_entry_set_text (self->display_name, display_name ?: "");
|
||||
gtk_entry_set_text (self->user, user);
|
||||
gtk_entry_set_text (self->password, password);
|
||||
set_password_visibility (self, FALSE);
|
||||
gtk_entry_set_text (self->port, port_str);
|
||||
hdy_combo_row_set_selected_index (self->protocol, protocol_index);
|
||||
hdy_combo_row_set_selected_index (self->media_encryption, encryption_index);
|
||||
gtk_switch_set_state (self->tel_switch, can_tel);
|
||||
gtk_switch_set_state (self->auto_connect_switch, auto_connect);
|
||||
|
||||
gtk_widget_set_sensitive (self->apply_btn, FALSE);
|
||||
|
||||
update_header (self);
|
||||
|
||||
if (gtk_widget_get_can_focus (GTK_WIDGET (self->host)))
|
||||
gtk_widget_grab_focus (GTK_WIDGET (self->host));
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_login_clicked (CallsSipAccountWidget *self)
|
||||
{
|
||||
CallsSipOrigin *origin;
|
||||
g_autofree char *id = g_uuid_string_random ();
|
||||
|
||||
g_debug ("Logging into newly created account");
|
||||
|
||||
origin = calls_sip_provider_add_origin (self->provider,
|
||||
id,
|
||||
gtk_entry_get_text (GTK_ENTRY (self->host)),
|
||||
gtk_entry_get_text (GTK_ENTRY (self->user)),
|
||||
gtk_entry_get_text (GTK_ENTRY (self->password)),
|
||||
gtk_entry_get_text (GTK_ENTRY (self->display_name)),
|
||||
get_selected_protocol (self),
|
||||
get_port (self),
|
||||
get_selected_media_encryption (self),
|
||||
TRUE);
|
||||
|
||||
self->origin = origin;
|
||||
update_header (self);
|
||||
g_signal_emit_by_name (self->provider, "widget-edit-done");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_delete_clicked (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_debug ("Deleting account");
|
||||
|
||||
calls_sip_provider_remove_origin (self->provider, self->origin);
|
||||
self->origin = NULL;
|
||||
|
||||
update_header (self);
|
||||
g_signal_emit_by_name (self->provider, "widget-edit-done");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_apply_clicked (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_debug ("Applying changes to the account");
|
||||
|
||||
calls_sip_origin_set_credentials (self->origin,
|
||||
gtk_entry_get_text (self->host),
|
||||
gtk_entry_get_text (self->user),
|
||||
gtk_entry_get_text (self->password),
|
||||
gtk_entry_get_text (self->display_name),
|
||||
get_selected_protocol (self),
|
||||
get_port (self),
|
||||
get_selected_media_encryption (self),
|
||||
gtk_switch_get_state (self->tel_switch),
|
||||
gtk_switch_get_state (self->auto_connect_switch));
|
||||
|
||||
update_header (self);
|
||||
calls_sip_provider_save_accounts_to_disk (self->provider);
|
||||
g_signal_emit_by_name (self->provider, "widget-edit-done");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_account_widget_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsSipAccountWidget *self = CALLS_SIP_ACCOUNT_WIDGET (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_PROVIDER:
|
||||
self->provider = g_value_get_object (value);
|
||||
break;
|
||||
|
||||
case PROP_ORIGIN:
|
||||
calls_sip_account_widget_set_origin (self, g_value_get_object (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_account_widget_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsSipAccountWidget *self = CALLS_SIP_ACCOUNT_WIDGET (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_ORIGIN:
|
||||
g_value_set_object (value, calls_sip_account_widget_get_origin (self));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
calls_sip_account_widget_dispose (GObject *object)
|
||||
{
|
||||
CallsSipAccountWidget *self = CALLS_SIP_ACCOUNT_WIDGET (object);
|
||||
|
||||
g_clear_pointer (&self->last_port, g_free);
|
||||
g_clear_object (&self->protocols_store);
|
||||
g_clear_object (&self->media_encryption_store);
|
||||
|
||||
G_OBJECT_CLASS (calls_sip_account_widget_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_account_widget_class_init (CallsSipAccountWidgetClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
|
||||
object_class->set_property = calls_sip_account_widget_set_property;
|
||||
object_class->get_property = calls_sip_account_widget_get_property;
|
||||
object_class->dispose = calls_sip_account_widget_dispose;
|
||||
|
||||
props[PROP_PROVIDER] =
|
||||
g_param_spec_object ("provider",
|
||||
"Provider",
|
||||
"The SIP provider",
|
||||
CALLS_TYPE_SIP_PROVIDER,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
props[PROP_ORIGIN] =
|
||||
g_param_spec_object ("origin",
|
||||
"Origin",
|
||||
"The origin to edit",
|
||||
CALLS_TYPE_SIP_ORIGIN,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
|
||||
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Calls/ui/sip-account-widget.ui");
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, header_add);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, spinner_add);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, header_edit);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, spinner_edit);
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, login_btn);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, apply_btn);
|
||||
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, host);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, display_name);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, user);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, password);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, port);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, protocol);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, media_encryption);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, tel_switch);
|
||||
gtk_widget_class_bind_template_child (widget_class, CallsSipAccountWidget, auto_connect_switch);
|
||||
|
||||
gtk_widget_class_bind_template_callback (widget_class, on_login_clicked);
|
||||
gtk_widget_class_bind_template_callback (widget_class, on_delete_clicked);
|
||||
gtk_widget_class_bind_template_callback (widget_class, on_apply_clicked);
|
||||
gtk_widget_class_bind_template_callback (widget_class, on_user_changed);
|
||||
gtk_widget_class_bind_template_callback (widget_class, on_password_visibility_changed);
|
||||
gtk_widget_class_bind_template_callback (widget_class, on_port_entry_insert_text);
|
||||
gtk_widget_class_bind_template_callback (widget_class, on_port_entry_after_insert_text);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_account_widget_init (CallsSipAccountWidget *self)
|
||||
{
|
||||
HdyValueObject *obj;
|
||||
|
||||
self->settings = calls_settings_get_default ();
|
||||
|
||||
g_signal_connect_swapped (self->settings,
|
||||
"notify::always-allow-sdes",
|
||||
G_CALLBACK (update_media_encryption),
|
||||
self);
|
||||
|
||||
gtk_widget_init_template (GTK_WIDGET (self));
|
||||
|
||||
self->media_encryption_store = g_list_store_new (HDY_TYPE_VALUE_OBJECT);
|
||||
|
||||
obj = hdy_value_object_new_string (_("No encryption"));
|
||||
g_object_set_data (G_OBJECT (obj),
|
||||
"value", GINT_TO_POINTER (SIP_MEDIA_ENCRYPTION_NONE));
|
||||
g_list_store_insert (self->media_encryption_store, 0, obj);
|
||||
g_clear_object (&obj);
|
||||
|
||||
/* TODO Optional encryption */
|
||||
obj = hdy_value_object_new_string (_("Force encryption"));
|
||||
g_object_set_data (G_OBJECT (obj),
|
||||
"value", GINT_TO_POINTER (SIP_MEDIA_ENCRYPTION_FORCED));
|
||||
g_list_store_insert (self->media_encryption_store, 1, obj);
|
||||
g_clear_object (&obj);
|
||||
|
||||
hdy_combo_row_bind_name_model (self->media_encryption,
|
||||
G_LIST_MODEL (self->media_encryption_store),
|
||||
(HdyComboRowGetNameFunc) hdy_value_object_dup_string,
|
||||
NULL, NULL);
|
||||
|
||||
self->protocols_store = g_list_store_new (HDY_TYPE_VALUE_OBJECT);
|
||||
|
||||
obj = hdy_value_object_new_string ("UDP");
|
||||
g_list_store_insert (self->protocols_store, 0, obj);
|
||||
g_clear_object (&obj);
|
||||
|
||||
obj = hdy_value_object_new_string ("TCP");
|
||||
g_list_store_insert (self->protocols_store, 1, obj);
|
||||
g_clear_object (&obj);
|
||||
|
||||
obj = hdy_value_object_new_string ("TLS");
|
||||
g_list_store_insert (self->protocols_store, 2, obj);
|
||||
g_clear_object (&obj);
|
||||
|
||||
hdy_combo_row_bind_name_model (self->protocol,
|
||||
G_LIST_MODEL (self->protocols_store),
|
||||
(HdyComboRowGetNameFunc) hdy_value_object_dup_string,
|
||||
NULL, NULL);
|
||||
}
|
||||
|
||||
|
||||
CallsSipAccountWidget *
|
||||
calls_sip_account_widget_new (CallsSipProvider *provider)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SIP_PROVIDER (provider), NULL);
|
||||
|
||||
return g_object_new (CALLS_TYPE_SIP_ACCOUNT_WIDGET,
|
||||
"provider", provider,
|
||||
NULL);
|
||||
}
|
||||
|
||||
|
||||
CallsSipOrigin *
|
||||
calls_sip_account_widget_get_origin (CallsSipAccountWidget *self)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SIP_ACCOUNT_WIDGET (self), NULL);
|
||||
|
||||
return self->origin;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
calls_sip_account_widget_set_origin (CallsSipAccountWidget *self,
|
||||
CallsSipOrigin *origin)
|
||||
{
|
||||
g_return_if_fail (CALLS_IS_SIP_ACCOUNT_WIDGET (self));
|
||||
g_return_if_fail (!origin || CALLS_IS_SIP_ORIGIN (origin));
|
||||
|
||||
edit_form (self, origin);
|
||||
}
|
||||
42
plugins/provider/sip/calls-sip-account-widget.h
Normal file
42
plugins/provider/sip/calls-sip-account-widget.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "calls-sip-provider.h"
|
||||
|
||||
#include <handy.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_SIP_ACCOUNT_WIDGET (calls_sip_account_widget_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsSipAccountWidget, calls_sip_account_widget, CALLS, SIP_ACCOUNT_WIDGET, GtkBox)
|
||||
|
||||
CallsSipAccountWidget *calls_sip_account_widget_new (CallsSipProvider *provider);
|
||||
void calls_sip_account_widget_set_origin (CallsSipAccountWidget *self,
|
||||
CallsSipOrigin *origin);
|
||||
CallsSipOrigin *calls_sip_account_widget_get_origin (CallsSipAccountWidget *self);
|
||||
|
||||
G_END_DECLS
|
||||
457
plugins/provider/sip/calls-sip-call.c
Normal file
457
plugins/provider/sip/calls-sip-call.c
Normal file
@@ -0,0 +1,457 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "CallsSipCall"
|
||||
|
||||
|
||||
#include "calls-call.h"
|
||||
#include "calls-message-source.h"
|
||||
#include "calls-sdp-crypto-context.h"
|
||||
#include "calls-sip-call.h"
|
||||
#include "calls-sip-enums.h"
|
||||
#include "calls-sip-media-manager.h"
|
||||
#include "calls-sip-media-pipeline.h"
|
||||
#include "calls-sip-util.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
|
||||
#include <sofia-sip/nua.h>
|
||||
|
||||
/**
|
||||
* SECTION:sip-call
|
||||
* @short_description: A #CallsCall for the SIP protocol
|
||||
* @Title: CallsSipCall
|
||||
*
|
||||
* #CallsSipCall derives from #CallsCall. Apart from allowing call control
|
||||
* like answering and hanging up it also coordinates with #CallsSipMediaManager
|
||||
* to prepare and control appropriate #CallsSipMediaPipeline objects.
|
||||
*/
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_CALL_HANDLE,
|
||||
PROP_IP,
|
||||
PROP_PIPELINE,
|
||||
PROP_MEDIA_ENCRYPTION,
|
||||
PROP_LAST_PROP
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
struct _CallsSipCall {
|
||||
GObject parent_instance;
|
||||
|
||||
CallsSipMediaManager *manager;
|
||||
CallsSipMediaPipeline *pipeline;
|
||||
|
||||
char *ip;
|
||||
|
||||
guint rport_rtp;
|
||||
guint rport_rtcp;
|
||||
gchar *remote;
|
||||
|
||||
nua_handle_t *nh;
|
||||
GList *codecs;
|
||||
|
||||
CallsSdpCryptoContext *sdp_crypto_context;
|
||||
SipMediaEncryption media_encryption;
|
||||
};
|
||||
|
||||
static void calls_sip_call_message_source_interface_init (CallsMessageSourceInterface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (CallsSipCall, calls_sip_call, CALLS_TYPE_CALL,
|
||||
G_IMPLEMENT_INTERFACE (CALLS_TYPE_MESSAGE_SOURCE,
|
||||
calls_sip_call_message_source_interface_init))
|
||||
|
||||
static void
|
||||
calls_sip_call_answer (CallsCall *call)
|
||||
{
|
||||
CallsSipCall *self;
|
||||
g_autofree gchar *local_sdp = NULL;
|
||||
guint rtp_port, rtcp_port;
|
||||
g_autoptr (GList) local_crypto = NULL;
|
||||
gboolean got_crypto_offer;
|
||||
|
||||
g_assert (CALLS_IS_CALL (call));
|
||||
g_assert (CALLS_IS_SIP_CALL (call));
|
||||
|
||||
self = CALLS_SIP_CALL (call);
|
||||
|
||||
g_assert (self->nh);
|
||||
|
||||
if (calls_call_get_state (CALLS_CALL (self)) != CALLS_CALL_STATE_INCOMING) {
|
||||
g_warning ("Call must be in 'incoming' state in order to answer");
|
||||
return;
|
||||
}
|
||||
|
||||
rtp_port = calls_sip_media_pipeline_get_rtp_port (self->pipeline);
|
||||
rtcp_port = calls_sip_media_pipeline_get_rtcp_port (self->pipeline);
|
||||
|
||||
got_crypto_offer =
|
||||
calls_sdp_crypto_context_get_state (self->sdp_crypto_context) ==
|
||||
CALLS_CRYPTO_CONTEXT_STATE_OFFER_REMOTE;
|
||||
|
||||
if (got_crypto_offer) {
|
||||
if (self->media_encryption == SIP_MEDIA_ENCRYPTION_NONE) {
|
||||
g_warning ("Encryption disabled, but got offer. Call should have already been declined!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!calls_sdp_crypto_context_generate_answer (self->sdp_crypto_context)) {
|
||||
g_warning ("Could not generate answer for crypto key exchange. Aborting!");
|
||||
CALLS_EMIT_MESSAGE(self, _("Cryptographic key exchange unsuccessful"), GTK_MESSAGE_WARNING);
|
||||
/* XXX this should (probably) never be reached */
|
||||
nua_respond (self->nh, 488, "Not acceptable here", TAG_END ());
|
||||
return;
|
||||
}
|
||||
|
||||
local_crypto = calls_sdp_crypto_context_get_crypto_candidates (self->sdp_crypto_context, FALSE);
|
||||
} else {
|
||||
if (self->media_encryption == SIP_MEDIA_ENCRYPTION_FORCED) {
|
||||
g_warning ("Encryption forced, but got no offer. Call should have already been declined!");
|
||||
return;
|
||||
} else if (self->media_encryption == SIP_MEDIA_ENCRYPTION_PREFERRED) {
|
||||
g_debug ("Encryption optional, got no offer. Continuing unencrypted");
|
||||
}
|
||||
}
|
||||
|
||||
local_sdp = calls_sip_media_manager_get_capabilities (self->manager,
|
||||
self->ip,
|
||||
rtp_port,
|
||||
rtcp_port,
|
||||
local_crypto,
|
||||
self->codecs);
|
||||
|
||||
g_assert (local_sdp);
|
||||
g_debug ("Setting local SDP to string:\n%s", local_sdp);
|
||||
|
||||
nua_respond (self->nh, 200, NULL,
|
||||
SOATAG_USER_SDP_STR (local_sdp),
|
||||
SOATAG_AF (SOA_AF_IP4_IP6),
|
||||
TAG_END ());
|
||||
|
||||
calls_call_set_state (CALLS_CALL (self), CALLS_CALL_STATE_ACTIVE);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_call_hang_up (CallsCall *call)
|
||||
{
|
||||
CallsSipCall *self;
|
||||
|
||||
g_assert (CALLS_IS_CALL (call));
|
||||
g_assert (CALLS_IS_SIP_CALL (call));
|
||||
|
||||
self = CALLS_SIP_CALL (call);
|
||||
|
||||
switch (calls_call_get_state (call)) {
|
||||
case CALLS_CALL_STATE_DIALING:
|
||||
nua_cancel (self->nh, TAG_END ());
|
||||
g_debug ("Hanging up on outgoing ringing call");
|
||||
break;
|
||||
|
||||
case CALLS_CALL_STATE_ACTIVE:
|
||||
nua_bye (self->nh, TAG_END ());
|
||||
|
||||
g_debug ("Hanging up ongoing call");
|
||||
break;
|
||||
|
||||
case CALLS_CALL_STATE_INCOMING:
|
||||
nua_respond (self->nh, 480, NULL, TAG_END ());
|
||||
g_debug ("Hanging up incoming call");
|
||||
break;
|
||||
|
||||
case CALLS_CALL_STATE_DISCONNECTED:
|
||||
g_warning ("Tried hanging up already disconnected call");
|
||||
break;
|
||||
|
||||
default:
|
||||
g_warning ("Hanging up not possible in state %d",
|
||||
calls_call_get_state (call));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_call_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsSipCall *self = CALLS_SIP_CALL (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_CALL_HANDLE:
|
||||
self->nh = g_value_get_pointer (value);
|
||||
break;
|
||||
|
||||
case PROP_IP:
|
||||
g_free (self->ip);
|
||||
self->ip = g_value_dup_string (value);
|
||||
break;
|
||||
|
||||
case PROP_PIPELINE:
|
||||
self->pipeline = g_value_dup_object (value);
|
||||
break;
|
||||
|
||||
case PROP_MEDIA_ENCRYPTION:
|
||||
self->media_encryption = g_value_get_enum (value);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_call_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsSipCall *self = CALLS_SIP_CALL (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_CALL_HANDLE:
|
||||
g_value_set_pointer (value, self->nh);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_call_finalize (GObject *object)
|
||||
{
|
||||
CallsSipCall *self = CALLS_SIP_CALL (object);
|
||||
|
||||
calls_sip_media_pipeline_stop (self->pipeline);
|
||||
|
||||
g_clear_object (&self->pipeline);
|
||||
g_clear_pointer (&self->codecs, g_list_free);
|
||||
g_clear_pointer (&self->remote, g_free);
|
||||
g_clear_pointer (&self->ip, g_free);
|
||||
|
||||
G_OBJECT_CLASS (calls_sip_call_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_call_class_init (CallsSipCallClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
CallsCallClass *call_class = CALLS_CALL_CLASS (klass);
|
||||
|
||||
object_class->get_property = calls_sip_call_get_property;
|
||||
object_class->set_property = calls_sip_call_set_property;
|
||||
object_class->finalize = calls_sip_call_finalize;
|
||||
|
||||
call_class->answer = calls_sip_call_answer;
|
||||
call_class->hang_up = calls_sip_call_hang_up;
|
||||
|
||||
props[PROP_CALL_HANDLE] =
|
||||
g_param_spec_pointer ("nua-handle",
|
||||
"NUA handle",
|
||||
"The used NUA handler",
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
||||
|
||||
props[PROP_IP] =
|
||||
g_param_spec_string ("own-ip",
|
||||
"Own IP",
|
||||
"Own IP for media and SDP",
|
||||
NULL,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT);
|
||||
|
||||
props[PROP_PIPELINE] =
|
||||
g_param_spec_object ("pipeline",
|
||||
"Pipeline",
|
||||
"Media pipeline for this call",
|
||||
CALLS_TYPE_SIP_MEDIA_PIPELINE,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
|
||||
|
||||
props[PROP_MEDIA_ENCRYPTION] =
|
||||
g_param_spec_enum ("media-encryption",
|
||||
"Media encryption",
|
||||
"The media encryption mode",
|
||||
SIP_TYPE_MEDIA_ENCRYPTION,
|
||||
SIP_MEDIA_ENCRYPTION_NONE,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_call_message_source_interface_init (CallsMessageSourceInterface *iface)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_call_init (CallsSipCall *self)
|
||||
{
|
||||
self->manager = calls_sip_media_manager_default ();
|
||||
self->sdp_crypto_context = calls_sdp_crypto_context_new ();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* calls_sip_call_setup_remote_media_connection:
|
||||
* @self: A #CallsSipCall
|
||||
* @remote: The remote host
|
||||
* @port_rtp: The RTP port on the remote host
|
||||
* @port_rtcp: The RTCP port on the remote host
|
||||
*/
|
||||
void
|
||||
calls_sip_call_setup_remote_media_connection (CallsSipCall *self,
|
||||
const char *remote,
|
||||
guint port_rtp,
|
||||
guint port_rtcp)
|
||||
{
|
||||
g_return_if_fail (CALLS_IS_SIP_CALL (self));
|
||||
|
||||
g_free (self->remote);
|
||||
self->remote = g_strdup (remote);
|
||||
self->rport_rtp = port_rtp;
|
||||
self->rport_rtcp = port_rtcp;
|
||||
|
||||
g_debug ("Setting remote ports: RTP/RTCP %u/%u",
|
||||
self->rport_rtp, self->rport_rtcp);
|
||||
|
||||
g_object_set (self->pipeline,
|
||||
"remote", self->remote,
|
||||
"rport-rtp", self->rport_rtp,
|
||||
"rport-rtcp", self->rport_rtcp,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_sip_call_activate_media:
|
||||
* @self: A #CallsSipCall
|
||||
* @enabled: %TRUE to enable the media pipeline, %FALSE to disable
|
||||
*
|
||||
* Controls the state of the #CallsSipMediaPipeline
|
||||
*/
|
||||
void
|
||||
calls_sip_call_activate_media (CallsSipCall *self,
|
||||
gboolean enabled)
|
||||
{
|
||||
g_return_if_fail (CALLS_IS_SIP_CALL (self));
|
||||
|
||||
/* when hanging up an incoming call the pipeline has not yet been setup */
|
||||
if (self->pipeline == NULL && !enabled)
|
||||
return;
|
||||
g_return_if_fail (CALLS_IS_SIP_MEDIA_PIPELINE (self->pipeline));
|
||||
|
||||
if (enabled) {
|
||||
gboolean negotiated = calls_sdp_crypto_context_get_is_negotiated (self->sdp_crypto_context);
|
||||
|
||||
if (negotiated) {
|
||||
calls_srtp_crypto_attribute *remote_crypto =
|
||||
calls_sdp_crypto_context_get_remote_crypto (self->sdp_crypto_context);
|
||||
calls_srtp_crypto_attribute *local_crypto =
|
||||
calls_sdp_crypto_context_get_local_crypto (self->sdp_crypto_context);
|
||||
|
||||
calls_sip_media_pipeline_set_crypto (self->pipeline, local_crypto, remote_crypto);
|
||||
} else {
|
||||
if (self->media_encryption == SIP_MEDIA_ENCRYPTION_FORCED) {
|
||||
g_warning ("Encryption is forced, but parameters were not negotiated! Aborting");
|
||||
return;
|
||||
} else if (self->media_encryption == SIP_MEDIA_ENCRYPTION_PREFERRED) {
|
||||
g_debug ("No encryption parameters negotiated, continuing unencrypted");
|
||||
}
|
||||
}
|
||||
|
||||
if (calls_sip_media_pipeline_get_state (self->pipeline) ==
|
||||
CALLS_MEDIA_PIPELINE_STATE_NEED_CODEC) {
|
||||
MediaCodecInfo *codec = (MediaCodecInfo *) self->codecs->data;
|
||||
|
||||
g_debug ("Setting codec '%s' for pipeline", codec->name);
|
||||
calls_sip_media_pipeline_set_codec (self->pipeline, codec);
|
||||
}
|
||||
|
||||
calls_sip_media_pipeline_start (self->pipeline);
|
||||
} else {
|
||||
calls_sip_media_pipeline_stop (self->pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CallsSipCall *
|
||||
calls_sip_call_new (const gchar *id,
|
||||
gboolean inbound,
|
||||
const char *own_ip,
|
||||
CallsSipMediaPipeline *pipeline,
|
||||
SipMediaEncryption media_encryption,
|
||||
nua_handle_t *handle)
|
||||
{
|
||||
g_return_val_if_fail (id, NULL);
|
||||
|
||||
return g_object_new (CALLS_TYPE_SIP_CALL,
|
||||
"id", id,
|
||||
"inbound", inbound,
|
||||
"own-ip", own_ip,
|
||||
"pipeline", pipeline,
|
||||
"media-encryption", media_encryption,
|
||||
"nua-handle", handle,
|
||||
"call-type", CALLS_CALL_TYPE_SIP_VOICE,
|
||||
NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_sip_call_set_codecs:
|
||||
* @self: A #CallsSipCall
|
||||
* @codecs: A #GList of #MediaCodecInfo elements
|
||||
*
|
||||
* Set the supported codecs. This is used when answering the call
|
||||
*/
|
||||
void
|
||||
calls_sip_call_set_codecs (CallsSipCall *self,
|
||||
GList *codecs)
|
||||
{
|
||||
g_return_if_fail (CALLS_IS_SIP_CALL (self));
|
||||
g_return_if_fail (codecs);
|
||||
|
||||
g_list_free (self->codecs);
|
||||
self->codecs = g_list_copy (codecs);
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_sip_call_get_sdp_crypto_context:
|
||||
* @self: A #CallsSipCall
|
||||
*
|
||||
* Returns: (transfer full): The #CallsSdpCryptoContext of this call
|
||||
*/
|
||||
CallsSdpCryptoContext *
|
||||
calls_sip_call_get_sdp_crypto_context (CallsSipCall *self)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_CALL (self), NULL);
|
||||
|
||||
return g_object_ref (self->sdp_crypto_context);
|
||||
}
|
||||
59
plugins/provider/sip/calls-sip-call.h
Normal file
59
plugins/provider/sip/calls-sip-call.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "calls-call.h"
|
||||
#include "calls-sdp-crypto-context.h"
|
||||
#include "calls-sip-media-pipeline.h"
|
||||
#include "calls-sip-util.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <sofia-sip/nua.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_SIP_CALL (calls_sip_call_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsSipCall, calls_sip_call, CALLS, SIP_CALL, CallsCall)
|
||||
|
||||
CallsSipCall *calls_sip_call_new (const char *number,
|
||||
gboolean inbound,
|
||||
const char *own_ip,
|
||||
CallsSipMediaPipeline *pipeline,
|
||||
SipMediaEncryption encryption,
|
||||
nua_handle_t *handle);
|
||||
void calls_sip_call_setup_remote_media_connection (CallsSipCall *self,
|
||||
const char *remote,
|
||||
guint port_rtp,
|
||||
guint port_rtcp);
|
||||
void calls_sip_call_activate_media (CallsSipCall *self,
|
||||
gboolean enabled);
|
||||
void calls_sip_call_set_state (CallsSipCall *self,
|
||||
CallsCallState state);
|
||||
void calls_sip_call_set_codecs (CallsSipCall *self,
|
||||
GList *codecs);
|
||||
CallsSdpCryptoContext *calls_sip_call_get_sdp_crypto_context (CallsSipCall *self);
|
||||
|
||||
G_END_DECLS
|
||||
405
plugins/provider/sip/calls-sip-media-manager.c
Normal file
405
plugins/provider/sip/calls-sip-media-manager.c
Normal file
@@ -0,0 +1,405 @@
|
||||
/*
|
||||
* Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "CallsSipMediaManager"
|
||||
|
||||
#include "calls-settings.h"
|
||||
#include "calls-sip-media-manager.h"
|
||||
#include "calls-sip-media-pipeline.h"
|
||||
#include "calls-srtp-utils.h"
|
||||
#include "gst-rfc3551.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
|
||||
|
||||
/**
|
||||
* SECTION:sip-media-manager
|
||||
* @short_description: The media manager singleton
|
||||
* @Title: CallsSipMediaManager
|
||||
*
|
||||
* #CallsSipMediaManager is mainly responsible for generating appropriate
|
||||
* SDP messages for the set of supported codecs. It also holds a list of
|
||||
* #CallsSipMediaPipeline objects that are ready to be used.
|
||||
*/
|
||||
|
||||
typedef struct _CallsSipMediaManager {
|
||||
GObject parent;
|
||||
|
||||
int address_family;
|
||||
struct addrinfo hints;
|
||||
|
||||
CallsSettings *settings;
|
||||
GList *preferred_codecs;
|
||||
GListStore *pipelines;
|
||||
} CallsSipMediaManager;
|
||||
|
||||
G_DEFINE_TYPE (CallsSipMediaManager, calls_sip_media_manager, G_TYPE_OBJECT);
|
||||
|
||||
|
||||
static const char *
|
||||
get_address_family_string (CallsSipMediaManager *self,
|
||||
const char *ip)
|
||||
{
|
||||
struct addrinfo *result = NULL;
|
||||
const char *family;
|
||||
|
||||
if (getaddrinfo (ip, NULL, &self->hints, &result) != 0) {
|
||||
g_warning ("Cannot parse session IP %s", ip);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* check if IP is IPv4 or IPv6. We need to specify this in the c= line of SDP */
|
||||
self->address_family = result->ai_family;
|
||||
|
||||
if (result->ai_family == AF_INET)
|
||||
family = "IP4";
|
||||
else if (result->ai_family == AF_INET6)
|
||||
family = "IP6";
|
||||
else
|
||||
family = NULL;
|
||||
|
||||
freeaddrinfo (result);
|
||||
|
||||
return family;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_notify_preferred_audio_codecs (CallsSipMediaManager *self)
|
||||
{
|
||||
GList *supported_codecs;
|
||||
|
||||
g_auto (GStrv) settings_codec_preference = NULL;
|
||||
|
||||
g_assert (CALLS_IS_SIP_MEDIA_MANAGER (self));
|
||||
|
||||
g_clear_list (&self->preferred_codecs, NULL);
|
||||
supported_codecs = media_codecs_get_candidates ();
|
||||
|
||||
if (!supported_codecs) {
|
||||
g_warning ("There aren't any supported codecs installed on your system");
|
||||
return;
|
||||
}
|
||||
|
||||
settings_codec_preference = calls_settings_get_preferred_audio_codecs (self->settings);
|
||||
|
||||
if (!settings_codec_preference) {
|
||||
g_debug ("No audio codec preference set. Using all supported codecs");
|
||||
self->preferred_codecs = supported_codecs;
|
||||
return;
|
||||
}
|
||||
|
||||
for (guint i = 0; settings_codec_preference[i] != NULL; i++) {
|
||||
MediaCodecInfo *codec = media_codec_by_name (settings_codec_preference[i]);
|
||||
|
||||
if (!codec) {
|
||||
g_debug ("Did not find audio codec %s", settings_codec_preference[i]);
|
||||
continue;
|
||||
}
|
||||
if (media_codec_available_in_gst (codec))
|
||||
self->preferred_codecs = g_list_append (self->preferred_codecs, codec);
|
||||
}
|
||||
|
||||
if (!self->preferred_codecs) {
|
||||
g_warning ("Cannot satisfy audio codec preference, "
|
||||
"falling back to all supported codecs");
|
||||
self->preferred_codecs = supported_codecs;
|
||||
} else {
|
||||
g_list_free (supported_codecs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
add_new_pipeline (CallsSipMediaManager *self)
|
||||
{
|
||||
CallsSipMediaPipeline *pipeline;
|
||||
|
||||
g_assert (CALLS_IS_SIP_MEDIA_MANAGER (self));
|
||||
|
||||
pipeline = calls_sip_media_pipeline_new (NULL);
|
||||
g_list_store_append (self->pipelines, pipeline);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_media_manager_finalize (GObject *object)
|
||||
{
|
||||
CallsSipMediaManager *self = CALLS_SIP_MEDIA_MANAGER (object);
|
||||
|
||||
g_list_free (self->preferred_codecs);
|
||||
g_object_unref (self->pipelines);
|
||||
|
||||
G_OBJECT_CLASS (calls_sip_media_manager_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_media_manager_class_init (CallsSipMediaManagerClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = calls_sip_media_manager_finalize;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_media_manager_init (CallsSipMediaManager *self)
|
||||
{
|
||||
if (!gst_is_initialized ())
|
||||
gst_init (NULL, NULL);
|
||||
|
||||
self->settings = calls_settings_get_default ();
|
||||
g_signal_connect_swapped (self->settings,
|
||||
"notify::preferred-audio-codecs",
|
||||
G_CALLBACK (on_notify_preferred_audio_codecs),
|
||||
self);
|
||||
on_notify_preferred_audio_codecs (self);
|
||||
|
||||
/* Hints are used with getaddrinfo() when setting the session IP */
|
||||
self->hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG | AI_NUMERICHOST;
|
||||
self->hints.ai_family = AF_UNSPEC;
|
||||
|
||||
self->pipelines = g_list_store_new (CALLS_TYPE_SIP_MEDIA_PIPELINE);
|
||||
|
||||
add_new_pipeline (self);
|
||||
}
|
||||
|
||||
|
||||
/* Public functions */
|
||||
|
||||
CallsSipMediaManager *
|
||||
calls_sip_media_manager_default (void)
|
||||
{
|
||||
static CallsSipMediaManager *instance = NULL;
|
||||
|
||||
if (instance == NULL) {
|
||||
g_debug ("Creating CallsSipMediaManager");
|
||||
instance = g_object_new (CALLS_TYPE_SIP_MEDIA_MANAGER, NULL);
|
||||
g_object_add_weak_pointer (G_OBJECT (instance), (gpointer *) &instance);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
/* calls_sip_media_manager_get_capabilities:
|
||||
*
|
||||
* @self: A #CallsSipMediaManager
|
||||
* @port: Should eventually come from the ICE stack
|
||||
* @crypto_attributes: A #GList of #calls_srtp_crypto_attribute
|
||||
* @supported_codecs: A #GList of #MediaCodecInfo
|
||||
*
|
||||
* Returns: (transfer full): string describing capabilities
|
||||
* to be used in the session description (SDP)
|
||||
*/
|
||||
char *
|
||||
calls_sip_media_manager_get_capabilities (CallsSipMediaManager *self,
|
||||
const char *own_ip,
|
||||
gint rtp_port,
|
||||
gint rtcp_port,
|
||||
GList *crypto_attributes,
|
||||
GList *supported_codecs)
|
||||
{
|
||||
char *payload_type = crypto_attributes ? "SAVP" : "AVP";
|
||||
|
||||
g_autoptr (GString) media_line = NULL;
|
||||
g_autoptr (GString) attribute_lines = NULL;
|
||||
GList *node;
|
||||
const char *address_family_string;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SIP_MEDIA_MANAGER (self), NULL);
|
||||
|
||||
media_line = g_string_new (NULL);
|
||||
attribute_lines = g_string_new (NULL);
|
||||
|
||||
if (supported_codecs == NULL) {
|
||||
g_warning ("No supported codecs found. Can't build meaningful SDP message");
|
||||
g_string_append_printf (media_line, "m=audio 0 RTP/AVP");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* media lines look f.e like "audio 31337 RTP/AVP 9 8 0" */
|
||||
g_string_append_printf (media_line,
|
||||
"m=audio %d RTP/%s", rtp_port, payload_type);
|
||||
|
||||
for (node = supported_codecs; node != NULL; node = node->next) {
|
||||
MediaCodecInfo *codec = node->data;
|
||||
|
||||
g_string_append_printf (media_line, " %u", codec->payload_id);
|
||||
g_string_append_printf (attribute_lines,
|
||||
"a=rtpmap:%u %s/%u%s",
|
||||
codec->payload_id,
|
||||
codec->name,
|
||||
codec->clock_rate,
|
||||
"\r\n");
|
||||
}
|
||||
|
||||
for (node = crypto_attributes; node != NULL; node = node->next) {
|
||||
calls_srtp_crypto_attribute *attr = node->data;
|
||||
g_autoptr (GError) error = NULL;
|
||||
g_autofree char *crypto_line =
|
||||
calls_srtp_print_sdp_crypto_attribute(attr, &error);
|
||||
|
||||
if (!crypto_line) {
|
||||
g_warning ("Could not print SDP crypto line for tag %d: %s", attr->tag, error->message);
|
||||
continue;
|
||||
}
|
||||
g_string_append_printf (attribute_lines, "%s\r\n", crypto_line);
|
||||
}
|
||||
|
||||
g_string_append_printf (attribute_lines, "a=rtcp:%d\r\n", rtcp_port);
|
||||
|
||||
done:
|
||||
if (own_ip && *own_ip)
|
||||
address_family_string = get_address_family_string (self, own_ip);
|
||||
|
||||
if (own_ip && *own_ip && address_family_string)
|
||||
return g_strdup_printf ("v=0\r\n"
|
||||
"c=IN %s %s\r\n"
|
||||
"%s\r\n"
|
||||
"%s\r\n",
|
||||
address_family_string,
|
||||
own_ip,
|
||||
media_line->str,
|
||||
attribute_lines->str);
|
||||
else
|
||||
return g_strdup_printf ("v=0\r\n"
|
||||
"%s\r\n"
|
||||
"%s\r\n",
|
||||
media_line->str,
|
||||
attribute_lines->str);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* calls_sip_media_manager_static_capabilities:
|
||||
*
|
||||
* @self: A #CallsSipMediaManager
|
||||
* @rtp_port: Port to use for RTP. Should eventually come from the ICE stack
|
||||
* @rtcp_port: Port to use for RTCP.Should eventually come from the ICE stack
|
||||
* @crypto_attributes: A #GList of #calls_srtp_crypto_attribute
|
||||
*
|
||||
* Returns: (transfer full): string describing capabilities
|
||||
* to be used in the session description (SDP)
|
||||
*/
|
||||
char *
|
||||
calls_sip_media_manager_static_capabilities (CallsSipMediaManager *self,
|
||||
const char *own_ip,
|
||||
gint rtp_port,
|
||||
gint rtcp_port,
|
||||
GList *crypto_attributes)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SIP_MEDIA_MANAGER (self), NULL);
|
||||
|
||||
return calls_sip_media_manager_get_capabilities (self,
|
||||
own_ip,
|
||||
rtp_port,
|
||||
rtcp_port,
|
||||
crypto_attributes,
|
||||
self->preferred_codecs);
|
||||
}
|
||||
|
||||
|
||||
/* calls_sip_media_manager_codec_candiates:
|
||||
*
|
||||
* @self: A #CallsSipMediaManager
|
||||
*
|
||||
* Returns: (transfer none): A #GList of supported
|
||||
* #MediaCodecInfo
|
||||
*/
|
||||
GList *
|
||||
calls_sip_media_manager_codec_candidates (CallsSipMediaManager *self)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_SIP_MEDIA_MANAGER (self), NULL);
|
||||
|
||||
return self->preferred_codecs;
|
||||
}
|
||||
|
||||
|
||||
/* calls_sip_media_manager_get_codecs_from_sdp
|
||||
*
|
||||
* @self: A #CallsSipMediaManager
|
||||
* @sdp: A #sdp_media_t media description
|
||||
*
|
||||
* Returns: (transfer container): A #GList of codecs found in the
|
||||
* SDP message
|
||||
*/
|
||||
GList *
|
||||
calls_sip_media_manager_get_codecs_from_sdp (CallsSipMediaManager *self,
|
||||
sdp_media_t *sdp_media)
|
||||
{
|
||||
GList *codecs = NULL;
|
||||
sdp_rtpmap_t *rtpmap = NULL;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SIP_MEDIA_MANAGER (self), NULL);
|
||||
g_return_val_if_fail (sdp_media, NULL);
|
||||
|
||||
if (sdp_media->m_type != sdp_media_audio) {
|
||||
g_warning ("Only the 'audio' media type is supported");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (rtpmap = sdp_media->m_rtpmaps; rtpmap != NULL; rtpmap = rtpmap->rm_next) {
|
||||
MediaCodecInfo *codec = media_codec_by_payload_id (rtpmap->rm_pt);
|
||||
if (codec)
|
||||
codecs = g_list_append (codecs, codec);
|
||||
}
|
||||
|
||||
if (sdp_media->m_next != NULL)
|
||||
g_warning ("Currently only a single media session is supported");
|
||||
|
||||
if (codecs == NULL)
|
||||
g_warning ("Did not find any common codecs");
|
||||
|
||||
return codecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_sip_media_manager_get_pipeline:
|
||||
* @self: A #CallsSipMediaManager
|
||||
*
|
||||
* Returns: (transfer full): A #CallsSipMediaPipeline
|
||||
*/
|
||||
CallsSipMediaPipeline *
|
||||
calls_sip_media_manager_get_pipeline (CallsSipMediaManager *self)
|
||||
{
|
||||
g_autoptr (CallsSipMediaPipeline) pipeline = NULL;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SIP_MEDIA_MANAGER (self), NULL);
|
||||
|
||||
pipeline = g_list_model_get_item (G_LIST_MODEL (self->pipelines), 0);
|
||||
|
||||
g_list_store_remove (self->pipelines, 0);
|
||||
|
||||
/* add a pipeline for the one we just removed */
|
||||
add_new_pipeline (self);
|
||||
|
||||
return pipeline;
|
||||
}
|
||||
59
plugins/provider/sip/calls-sip-media-manager.h
Normal file
59
plugins/provider/sip/calls-sip-media-manager.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "calls-sip-media-pipeline.h"
|
||||
#include "gst-rfc3551.h"
|
||||
|
||||
#include <sofia-sip/sdp.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_SIP_MEDIA_MANAGER (calls_sip_media_manager_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsSipMediaManager, calls_sip_media_manager, CALLS, SIP_MEDIA_MANAGER, GObject);
|
||||
|
||||
|
||||
CallsSipMediaManager *calls_sip_media_manager_default (void);
|
||||
gchar *calls_sip_media_manager_get_capabilities (CallsSipMediaManager *self,
|
||||
const char *own_ip,
|
||||
gint rtp_port,
|
||||
gint rtcp_port,
|
||||
GList *crypto_attributes,
|
||||
GList *supported_codecs);
|
||||
gchar *calls_sip_media_manager_static_capabilities (CallsSipMediaManager *self,
|
||||
const char *own_ip,
|
||||
gint rtp_port,
|
||||
gint rtcp_port,
|
||||
GList *crypto_attributes);
|
||||
gboolean calls_sip_media_manager_supports_media (CallsSipMediaManager *self,
|
||||
const char *media_type);
|
||||
GList *calls_sip_media_manager_codec_candidates (CallsSipMediaManager *self);
|
||||
GList *calls_sip_media_manager_get_codecs_from_sdp (CallsSipMediaManager *self,
|
||||
sdp_media_t *sdp_media);
|
||||
CallsSipMediaPipeline *calls_sip_media_manager_get_pipeline (CallsSipMediaManager *self);
|
||||
|
||||
G_END_DECLS
|
||||
1410
plugins/provider/sip/calls-sip-media-pipeline.c
Normal file
1410
plugins/provider/sip/calls-sip-media-pipeline.c
Normal file
File diff suppressed because it is too large
Load Diff
82
plugins/provider/sip/calls-sip-media-pipeline.h
Normal file
82
plugins/provider/sip/calls-sip-media-pipeline.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "calls-srtp-utils.h"
|
||||
#include "gst-rfc3551.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* CallsMediaPipelineState:
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_UNKNOWN: Default state for new pipelines
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_ERROR: Pipeline is in an error state
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_INITIALIZING: Pipeline is initializing
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_NEED_CODEC: Pipeline was initialized and needs a codec set
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_READY: Pipeline is ready to be set into playing state
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_PLAY_PENDING: Request to start pipeline pending
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_PLAYING: Pipeline is currently playing
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_PAUSE_PENDING: Request to pause pipeline pending
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_PAUSED: Pipeline is currently paused
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_STOP_PENDING: Request to stop pipeline pending
|
||||
* @CALLS_MEDIA_PIPELINE_STATE_STOPPED: Pipeline has stopped playing (f.e. received BYE packet)
|
||||
*/
|
||||
typedef enum {
|
||||
CALLS_MEDIA_PIPELINE_STATE_UNKNOWN = 0,
|
||||
CALLS_MEDIA_PIPELINE_STATE_ERROR,
|
||||
CALLS_MEDIA_PIPELINE_STATE_INITIALIZING,
|
||||
CALLS_MEDIA_PIPELINE_STATE_NEED_CODEC,
|
||||
CALLS_MEDIA_PIPELINE_STATE_READY,
|
||||
CALLS_MEDIA_PIPELINE_STATE_PLAY_PENDING,
|
||||
CALLS_MEDIA_PIPELINE_STATE_PLAYING,
|
||||
CALLS_MEDIA_PIPELINE_STATE_PAUSE_PENDING,
|
||||
CALLS_MEDIA_PIPELINE_STATE_PAUSED,
|
||||
CALLS_MEDIA_PIPELINE_STATE_STOP_PENDING,
|
||||
CALLS_MEDIA_PIPELINE_STATE_STOPPED
|
||||
} CallsMediaPipelineState;
|
||||
|
||||
|
||||
#define CALLS_TYPE_SIP_MEDIA_PIPELINE (calls_sip_media_pipeline_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsSipMediaPipeline, calls_sip_media_pipeline, CALLS, SIP_MEDIA_PIPELINE, GObject)
|
||||
|
||||
|
||||
CallsSipMediaPipeline* calls_sip_media_pipeline_new (MediaCodecInfo *codec);
|
||||
void calls_sip_media_pipeline_set_codec (CallsSipMediaPipeline *self,
|
||||
MediaCodecInfo *info);
|
||||
void calls_sip_media_pipeline_set_crypto (CallsSipMediaPipeline *self,
|
||||
calls_srtp_crypto_attribute *crypto_own,
|
||||
calls_srtp_crypto_attribute *crypto_theirs);
|
||||
void calls_sip_media_pipeline_start (CallsSipMediaPipeline *self);
|
||||
void calls_sip_media_pipeline_stop (CallsSipMediaPipeline *self);
|
||||
void calls_sip_media_pipeline_pause (CallsSipMediaPipeline *self,
|
||||
gboolean pause);
|
||||
int calls_sip_media_pipeline_get_rtp_port (CallsSipMediaPipeline *self);
|
||||
int calls_sip_media_pipeline_get_rtcp_port (CallsSipMediaPipeline *self);
|
||||
CallsMediaPipelineState calls_sip_media_pipeline_get_state (CallsSipMediaPipeline *self);
|
||||
|
||||
G_END_DECLS
|
||||
1743
plugins/provider/sip/calls-sip-origin.c
Normal file
1743
plugins/provider/sip/calls-sip-origin.c
Normal file
File diff suppressed because it is too large
Load Diff
48
plugins/provider/sip/calls-sip-origin.h
Normal file
48
plugins/provider/sip/calls-sip-origin.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "calls-sip-util.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_SIP_ORIGIN (calls_sip_origin_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsSipOrigin, calls_sip_origin, CALLS, SIP_ORIGIN, GObject)
|
||||
|
||||
void calls_sip_origin_set_credentials (CallsSipOrigin *self,
|
||||
const char *host,
|
||||
const char *user,
|
||||
const char *password,
|
||||
const char *display_name,
|
||||
const char *transport_protocol,
|
||||
gint port,
|
||||
SipMediaEncryption media_encryption,
|
||||
gboolean use_for_tel,
|
||||
gboolean auto_connect);
|
||||
|
||||
G_END_DECLS
|
||||
877
plugins/provider/sip/calls-sip-provider.c
Normal file
877
plugins/provider/sip/calls-sip-provider.c
Normal file
@@ -0,0 +1,877 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "CallsSipProvider"
|
||||
|
||||
#define SIP_ACCOUNT_FILE "sip-account.cfg"
|
||||
#define CALLS_PROTOCOL_SIP_STR "sip"
|
||||
|
||||
#include "calls-account-provider.h"
|
||||
#include "calls-message-source.h"
|
||||
#include "calls-provider.h"
|
||||
#include "calls-secret-store.h"
|
||||
#include "calls-sip-account-widget.h"
|
||||
#include "calls-sip-enums.h"
|
||||
#include "calls-sip-origin.h"
|
||||
#include "calls-sip-provider.h"
|
||||
#include "calls-sip-util.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <libpeas/peas.h>
|
||||
#include <sofia-sip/nua.h>
|
||||
#include <sofia-sip/su_glib.h>
|
||||
|
||||
static const char * const supported_protocols[] = {
|
||||
"tel",
|
||||
"sip",
|
||||
"sips",
|
||||
NULL
|
||||
};
|
||||
|
||||
/**
|
||||
* SECTION:sip-provider
|
||||
* @short_description: A #CallsProvider for the SIP protocol
|
||||
* @Title: CallsSipProvider
|
||||
*
|
||||
* #CallsSipProvider is derived from #CallsProvider and is responsible
|
||||
* for setting up the sofia-sip stack.
|
||||
*/
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_SIP_STATE,
|
||||
PROP_LAST_PROP,
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
struct _CallsSipProvider {
|
||||
CallsProvider parent_instance;
|
||||
|
||||
GListStore *origins;
|
||||
/* SIP */
|
||||
CallsSipContext *ctx;
|
||||
SipEngineState sip_state;
|
||||
|
||||
gboolean use_memory_backend;
|
||||
gchar *filename;
|
||||
|
||||
CallsSipAccountWidget *account_widget;
|
||||
};
|
||||
|
||||
static void calls_sip_provider_message_source_interface_init (CallsMessageSourceInterface *iface);
|
||||
static void calls_sip_provider_account_provider_interface_init (CallsAccountProviderInterface *iface);
|
||||
|
||||
#ifdef FOR_TESTING
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE
|
||||
(CallsSipProvider, calls_sip_provider, CALLS_TYPE_PROVIDER,
|
||||
G_IMPLEMENT_INTERFACE (CALLS_TYPE_MESSAGE_SOURCE,
|
||||
calls_sip_provider_message_source_interface_init)
|
||||
G_IMPLEMENT_INTERFACE (CALLS_TYPE_ACCOUNT_PROVIDER,
|
||||
calls_sip_provider_account_provider_interface_init))
|
||||
|
||||
#else
|
||||
|
||||
G_DEFINE_DYNAMIC_TYPE_EXTENDED
|
||||
(CallsSipProvider, calls_sip_provider, CALLS_TYPE_PROVIDER, 0,
|
||||
G_IMPLEMENT_INTERFACE_DYNAMIC (CALLS_TYPE_MESSAGE_SOURCE,
|
||||
calls_sip_provider_message_source_interface_init)
|
||||
G_IMPLEMENT_INTERFACE_DYNAMIC (CALLS_TYPE_ACCOUNT_PROVIDER,
|
||||
calls_sip_provider_account_provider_interface_init))
|
||||
|
||||
#endif /* FOR_TESTING */
|
||||
|
||||
typedef struct {
|
||||
CallsSipProvider *provider;
|
||||
GKeyFile *key_file;
|
||||
char *name;
|
||||
} SipOriginLoadData;
|
||||
|
||||
static void
|
||||
on_origin_pw_looked_up (GObject *source,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
SipOriginLoadData *data;
|
||||
|
||||
g_autoptr (GError) error = NULL;
|
||||
g_autofree char *id = NULL;
|
||||
g_autofree char *name = NULL;
|
||||
g_autofree char *host = NULL;
|
||||
g_autofree char *user = NULL;
|
||||
g_autofree char *password = NULL;
|
||||
g_autofree char *display_name = NULL;
|
||||
g_autofree char *protocol = NULL;
|
||||
gint port = 0;
|
||||
gint local_port = 0;
|
||||
gboolean auto_connect = TRUE;
|
||||
gboolean direct_mode = FALSE;
|
||||
gboolean can_tel = FALSE;
|
||||
SipMediaEncryption media_encryption = SIP_MEDIA_ENCRYPTION_NONE;
|
||||
|
||||
g_assert (user_data);
|
||||
|
||||
data = user_data;
|
||||
|
||||
if (g_key_file_has_key (data->key_file, data->name, "Id", NULL))
|
||||
id = g_key_file_get_string (data->key_file, data->name, "Id", NULL);
|
||||
else
|
||||
id = g_strdup (data->name);
|
||||
|
||||
host = g_key_file_get_string (data->key_file, data->name, "Host", NULL);
|
||||
user = g_key_file_get_string (data->key_file, data->name, "User", NULL);
|
||||
display_name = g_key_file_get_string (data->key_file, data->name, "DisplayName", NULL);
|
||||
protocol = g_key_file_get_string (data->key_file, data->name, "Protocol", NULL);
|
||||
port = g_key_file_get_integer (data->key_file, data->name, "Port", NULL);
|
||||
display_name = g_key_file_get_string (data->key_file, data->name, "DisplayName", NULL);
|
||||
local_port = g_key_file_get_integer (data->key_file, data->name, "LocalPort", NULL);
|
||||
|
||||
if (g_key_file_has_key (data->key_file, data->name, "AutoConnect", NULL))
|
||||
auto_connect = g_key_file_get_boolean (data->key_file, data->name, "AutoConnect", NULL);
|
||||
|
||||
if (protocol == NULL)
|
||||
protocol = g_strdup ("UDP");
|
||||
|
||||
if (g_key_file_has_key (data->key_file, data->name, "DirectMode", NULL))
|
||||
direct_mode = g_key_file_get_boolean (data->key_file, data->name, "DirectMode", NULL);
|
||||
|
||||
if (g_key_file_has_key (data->key_file, data->name, "CanTel", NULL))
|
||||
can_tel =
|
||||
g_key_file_get_boolean (data->key_file, data->name, "CanTel", NULL);
|
||||
|
||||
if (g_key_file_has_key (data->key_file, data->name, "MediaEncryption", NULL))
|
||||
media_encryption =
|
||||
(SipMediaEncryption) g_key_file_get_integer (data->key_file, data->name, "MediaEncryption", NULL);
|
||||
|
||||
/* PW */
|
||||
password = secret_password_lookup_finish (result, &error);
|
||||
if (!direct_mode && error) {
|
||||
g_warning ("Could not lookup password: %s", error->message);
|
||||
return;
|
||||
}
|
||||
|
||||
#define IS_NULL_OR_EMPTY(x) ((x) == NULL || (x)[0] == '\0')
|
||||
if (!direct_mode &&
|
||||
(IS_NULL_OR_EMPTY (host) ||
|
||||
IS_NULL_OR_EMPTY (user) ||
|
||||
IS_NULL_OR_EMPTY (password))) {
|
||||
g_warning ("Host, user and password must not be empty");
|
||||
|
||||
return;
|
||||
}
|
||||
#undef IS_NULL_OR_EMPTY
|
||||
|
||||
calls_sip_provider_add_origin_full (data->provider,
|
||||
id,
|
||||
host,
|
||||
user,
|
||||
password,
|
||||
display_name,
|
||||
protocol,
|
||||
port,
|
||||
media_encryption,
|
||||
auto_connect,
|
||||
direct_mode,
|
||||
local_port,
|
||||
can_tel,
|
||||
FALSE);
|
||||
}
|
||||
static void
|
||||
new_origin_from_keyfile_secret (CallsSipProvider *self,
|
||||
GKeyFile *key_file,
|
||||
const char *name)
|
||||
{
|
||||
g_autofree char *host = NULL;
|
||||
g_autofree char *user = NULL;
|
||||
SipOriginLoadData *data;
|
||||
|
||||
g_assert (CALLS_IS_SIP_PROVIDER (self));
|
||||
g_assert (key_file);
|
||||
g_assert (name);
|
||||
|
||||
if (!g_key_file_has_group (key_file, name)) {
|
||||
g_warning ("Keyfile has no group %s", name);
|
||||
return;
|
||||
}
|
||||
|
||||
host = g_key_file_get_string (key_file, name, "Host", NULL);
|
||||
user = g_key_file_get_string (key_file, name, "User", NULL);
|
||||
|
||||
data = g_new0 (SipOriginLoadData, 1);
|
||||
data->provider = self;
|
||||
g_key_file_ref (key_file);
|
||||
data->key_file = key_file;
|
||||
data->name = g_strdup (name);
|
||||
|
||||
secret_password_lookup (calls_secret_get_schema (), NULL,
|
||||
on_origin_pw_looked_up, data,
|
||||
CALLS_SERVER_ATTRIBUTE, host,
|
||||
CALLS_USERNAME_ATTRIBUTE, user,
|
||||
CALLS_PROTOCOL_ATTRIBUTE, CALLS_PROTOCOL_SIP_STR,
|
||||
NULL);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_origin_pw_cleared (GObject *source,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
if (!secret_password_clear_finish (result, &error))
|
||||
g_warning ("Could not delete the password in the keyring: %s",
|
||||
error ? error->message : "No reason given");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
origin_pw_delete_secret (CallsSipOrigin *origin)
|
||||
{
|
||||
g_autofree char *host = NULL;
|
||||
g_autofree char *user = NULL;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ORIGIN (origin));
|
||||
|
||||
g_object_get (origin,
|
||||
"host", &host,
|
||||
"user", &user,
|
||||
NULL);
|
||||
|
||||
secret_password_clear (calls_secret_get_schema (), NULL, on_origin_pw_cleared, NULL,
|
||||
CALLS_SERVER_ATTRIBUTE, host,
|
||||
CALLS_USERNAME_ATTRIBUTE, user,
|
||||
CALLS_PROTOCOL_ATTRIBUTE, CALLS_PROTOCOL_SIP_STR,
|
||||
NULL);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
on_origin_pw_saved (GObject *source,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
|
||||
if (!secret_password_store_finish (result, &error)) {
|
||||
g_warning ("Could not store the password in the keyring: %s",
|
||||
error ? error->message : "No reason given");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
origin_to_keyfile (CallsSipOrigin *origin,
|
||||
GKeyFile *key_file,
|
||||
const char *name)
|
||||
{
|
||||
g_autofree char *id = NULL;
|
||||
g_autofree char *host = NULL;
|
||||
g_autofree char *user = NULL;
|
||||
g_autofree char *password = NULL;
|
||||
g_autofree char *display_name = NULL;
|
||||
g_autofree char *protocol = NULL;
|
||||
g_autofree char *label_secret = NULL;
|
||||
gint port;
|
||||
gint local_port;
|
||||
gboolean auto_connect;
|
||||
gboolean direct_mode;
|
||||
gboolean can_tel;
|
||||
SipMediaEncryption media_encryption;
|
||||
|
||||
g_assert (CALLS_IS_SIP_ORIGIN (origin));
|
||||
g_assert (key_file);
|
||||
|
||||
g_object_get (origin,
|
||||
"id", &id,
|
||||
"host", &host,
|
||||
"user", &user,
|
||||
"password", &password,
|
||||
"display-name", &display_name,
|
||||
"transport-protocol", &protocol,
|
||||
"port", &port,
|
||||
"auto-connect", &auto_connect,
|
||||
"direct-mode", &direct_mode,
|
||||
"local-port", &local_port,
|
||||
"can-tel", &can_tel,
|
||||
"media-encryption", &media_encryption,
|
||||
NULL);
|
||||
|
||||
g_key_file_set_string (key_file, name, "Id", id);
|
||||
g_key_file_set_string (key_file, name, "Host", host);
|
||||
g_key_file_set_string (key_file, name, "User", user);
|
||||
g_key_file_set_string (key_file, name, "DisplayName", display_name ?: "");
|
||||
g_key_file_set_string (key_file, name, "Protocol", protocol);
|
||||
g_key_file_set_integer (key_file, name, "Port", port);
|
||||
g_key_file_set_boolean (key_file, name, "AutoConnect", auto_connect);
|
||||
g_key_file_set_boolean (key_file, name, "DirectMode", direct_mode);
|
||||
g_key_file_set_integer (key_file, name, "LocalPort", local_port);
|
||||
g_key_file_set_boolean (key_file, name, "CanTel", can_tel);
|
||||
g_key_file_set_integer (key_file, name, "MediaEncryption", media_encryption);
|
||||
|
||||
label_secret = g_strdup_printf ("Calls Password for %s", id);
|
||||
|
||||
/* save to keyring */
|
||||
secret_password_store (calls_secret_get_schema (), NULL, label_secret, password,
|
||||
NULL, on_origin_pw_saved, NULL,
|
||||
CALLS_SERVER_ATTRIBUTE, host,
|
||||
CALLS_USERNAME_ATTRIBUTE, user,
|
||||
CALLS_PROTOCOL_ATTRIBUTE, CALLS_PROTOCOL_SIP_STR,
|
||||
NULL);
|
||||
}
|
||||
|
||||
|
||||
static const char *
|
||||
calls_sip_provider_get_name (CallsProvider *provider)
|
||||
{
|
||||
return "SIP provider";
|
||||
}
|
||||
|
||||
|
||||
static const char *
|
||||
calls_sip_provider_get_status (CallsProvider *provider)
|
||||
{
|
||||
CallsSipProvider *self = CALLS_SIP_PROVIDER (provider);
|
||||
|
||||
switch (self->sip_state) {
|
||||
case SIP_ENGINE_ERROR:
|
||||
return "Error";
|
||||
|
||||
case SIP_ENGINE_READY:
|
||||
return "Normal";
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
|
||||
static GListModel *
|
||||
calls_sip_provider_get_origins (CallsProvider *provider)
|
||||
{
|
||||
CallsSipProvider *self = CALLS_SIP_PROVIDER (provider);
|
||||
|
||||
return G_LIST_MODEL (self->origins);
|
||||
}
|
||||
|
||||
static const char *const *
|
||||
calls_sip_provider_get_protocols (CallsProvider *provider)
|
||||
{
|
||||
return supported_protocols;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_provider_deinit_sip (CallsSipProvider *self)
|
||||
{
|
||||
GSource *gsource;
|
||||
|
||||
if (self->sip_state == SIP_ENGINE_NULL)
|
||||
return;
|
||||
|
||||
/* clean up sofia */
|
||||
if (!self->ctx)
|
||||
goto bail;
|
||||
|
||||
|
||||
if (self->ctx->root) {
|
||||
gsource = su_glib_root_gsource (self->ctx->root);
|
||||
g_source_destroy (gsource);
|
||||
su_root_destroy (self->ctx->root);
|
||||
|
||||
if (su_home_unref (self->ctx->home) != 1)
|
||||
g_warning ("Error in su_home_unref ()");
|
||||
}
|
||||
g_clear_pointer (&self->ctx, g_free);
|
||||
|
||||
bail:
|
||||
self->sip_state = SIP_ENGINE_NULL;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SIP_STATE]);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
calls_sip_provider_init_sofia (CallsSipProvider *self,
|
||||
GError **error)
|
||||
{
|
||||
GSource *gsource;
|
||||
|
||||
g_assert (CALLS_SIP_PROVIDER (self));
|
||||
|
||||
self->ctx = g_new0 (CallsSipContext, 1);
|
||||
if (self->ctx == NULL) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Could not allocate memory for the SIP context");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (su_init () != su_success) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"su_init () failed");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (su_home_init (self->ctx->home) != su_success) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"su_home_init () failed");
|
||||
goto err;
|
||||
}
|
||||
|
||||
self->ctx->root = su_glib_root_create (self);
|
||||
if (self->ctx->root == NULL) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"su_glib_root_create () failed");
|
||||
goto err;
|
||||
}
|
||||
gsource = su_glib_root_gsource (self->ctx->root);
|
||||
if (gsource == NULL) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"su_glib_root_gsource () failed");
|
||||
goto err;
|
||||
}
|
||||
|
||||
g_source_attach (gsource, NULL);
|
||||
self->sip_state = SIP_ENGINE_READY;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SIP_STATE]);
|
||||
return TRUE;
|
||||
|
||||
err:
|
||||
self->sip_state = SIP_ENGINE_ERROR;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SIP_STATE]);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_provider_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsSipProvider *self = CALLS_SIP_PROVIDER (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_SIP_STATE:
|
||||
g_value_set_enum (value, self->sip_state);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_provider_constructed (GObject *object)
|
||||
{
|
||||
CallsSipProvider *self = CALLS_SIP_PROVIDER (object);
|
||||
|
||||
g_autoptr (GError) error = NULL;
|
||||
const gchar *env_sip_test;
|
||||
|
||||
env_sip_test = g_getenv ("CALLS_SIP_TEST");
|
||||
if (env_sip_test && env_sip_test[0] != '\0')
|
||||
self->use_memory_backend = TRUE;
|
||||
|
||||
if (calls_sip_provider_init_sofia (self, &error)) {
|
||||
if (!self->use_memory_backend) {
|
||||
g_autoptr (GKeyFile) key_file = g_key_file_new ();
|
||||
|
||||
if (!g_key_file_load_from_file (key_file, self->filename, G_KEY_FILE_NONE, &error)) {
|
||||
g_debug ("Error loading key file: %s", error->message);
|
||||
goto out;
|
||||
}
|
||||
calls_sip_provider_load_accounts (self, key_file);
|
||||
}
|
||||
} else {
|
||||
g_warning ("Could not initialize sofia stack: %s", error->message);
|
||||
}
|
||||
|
||||
out:
|
||||
|
||||
G_OBJECT_CLASS (calls_sip_provider_parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_provider_dispose (GObject *object)
|
||||
{
|
||||
CallsSipProvider *self = CALLS_SIP_PROVIDER (object);
|
||||
|
||||
g_list_store_remove_all (self->origins);
|
||||
g_clear_object (&self->origins);
|
||||
g_clear_object (&self->account_widget);
|
||||
|
||||
g_clear_pointer (&self->filename, g_free);
|
||||
|
||||
calls_sip_provider_deinit_sip (self);
|
||||
|
||||
G_OBJECT_CLASS (calls_sip_provider_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
|
||||
#ifndef FOR_TESTING
|
||||
|
||||
static void
|
||||
calls_sip_provider_class_finalize (CallsSipProviderClass *klass)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* FOR_TESTING */
|
||||
|
||||
static void
|
||||
calls_sip_provider_class_init (CallsSipProviderClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
CallsProviderClass *provider_class = CALLS_PROVIDER_CLASS (klass);
|
||||
|
||||
object_class->constructed = calls_sip_provider_constructed;
|
||||
object_class->dispose = calls_sip_provider_dispose;
|
||||
object_class->get_property = calls_sip_provider_get_property;
|
||||
|
||||
provider_class->get_name = calls_sip_provider_get_name;
|
||||
provider_class->get_status = calls_sip_provider_get_status;
|
||||
provider_class->get_origins = calls_sip_provider_get_origins;
|
||||
provider_class->get_protocols = calls_sip_provider_get_protocols;
|
||||
|
||||
props[PROP_SIP_STATE] =
|
||||
g_param_spec_enum ("sip-state",
|
||||
"SIP state",
|
||||
"The state of the SIP engine",
|
||||
SIP_TYPE_ENGINE_STATE,
|
||||
SIP_ENGINE_NULL,
|
||||
G_PARAM_READABLE);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_sip_provider_message_source_interface_init (CallsMessageSourceInterface *iface)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
ensure_account_widget (CallsSipProvider *self)
|
||||
{
|
||||
if (!self->account_widget) {
|
||||
self->account_widget = calls_sip_account_widget_new (self);
|
||||
g_object_ref_sink (self->account_widget);
|
||||
}
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
get_account_widget (CallsAccountProvider *provider)
|
||||
{
|
||||
CallsSipProvider *self = CALLS_SIP_PROVIDER (provider);
|
||||
|
||||
ensure_account_widget (self);
|
||||
return GTK_WIDGET (self->account_widget);
|
||||
}
|
||||
|
||||
static void
|
||||
add_new_account (CallsAccountProvider *provider)
|
||||
{
|
||||
CallsSipProvider *self = CALLS_SIP_PROVIDER (provider);
|
||||
|
||||
ensure_account_widget (self);
|
||||
calls_sip_account_widget_set_origin (self->account_widget, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
edit_account (CallsAccountProvider *provider,
|
||||
CallsAccount *account)
|
||||
{
|
||||
CallsSipProvider *self = CALLS_SIP_PROVIDER (provider);
|
||||
|
||||
ensure_account_widget (self);
|
||||
calls_sip_account_widget_set_origin (self->account_widget, CALLS_SIP_ORIGIN (account));
|
||||
}
|
||||
|
||||
static void
|
||||
calls_sip_provider_account_provider_interface_init (CallsAccountProviderInterface *iface)
|
||||
{
|
||||
iface->get_account_widget = get_account_widget;
|
||||
iface->add_new_account = add_new_account;
|
||||
iface->edit_account = edit_account;
|
||||
}
|
||||
|
||||
static void
|
||||
calls_sip_provider_init (CallsSipProvider *self)
|
||||
{
|
||||
g_autofree char *directory = NULL;
|
||||
const char *filename_env = g_getenv ("CALLS_SIP_ACCOUNT_FILE");
|
||||
const char *sip_test_env = g_getenv ("CALLS_SIP_TEST");
|
||||
|
||||
self->origins = g_list_store_new (CALLS_TYPE_ORIGIN);
|
||||
|
||||
if (filename_env && filename_env[0] != '\0')
|
||||
self->filename = g_strdup (filename_env);
|
||||
else
|
||||
self->filename = g_build_filename (g_get_user_config_dir (),
|
||||
APP_DATA_NAME,
|
||||
SIP_ACCOUNT_FILE,
|
||||
NULL);
|
||||
|
||||
if (!sip_test_env || sip_test_env[0] == '\0') {
|
||||
directory = g_path_get_dirname (self->filename);
|
||||
if (g_mkdir_with_parents (directory, 0750) == -1) {
|
||||
int err_save = errno;
|
||||
g_warning ("Failed to create directory '%s': %d\n"
|
||||
"Can not store credentials persistently!",
|
||||
directory, err_save);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_sip_provider_add_origin:
|
||||
* @self: A #CallsSipProvider
|
||||
* @id: The id of the new origin (should be unique)
|
||||
* @host: The host to connect to
|
||||
* @user: The username to use
|
||||
* @password: The password to use
|
||||
* @display_name: The display name
|
||||
* @transport_protocol: The transport protocol to use, can be one of "UDP", "TCP" or "TLS"
|
||||
* @port: The port of the host
|
||||
* @media_encryption: A #SipMediaEncryption specifying if media should be encrypted
|
||||
* @store_credentials: Whether to store credentials for this origin to disk
|
||||
*
|
||||
* Adds a new origin (SIP account)
|
||||
*
|
||||
* Return: (transfer none): A #CallsSipOrigin
|
||||
*/
|
||||
CallsSipOrigin *
|
||||
calls_sip_provider_add_origin (CallsSipProvider *self,
|
||||
const char *id,
|
||||
const char *host,
|
||||
const char *user,
|
||||
const char *password,
|
||||
const char *display_name,
|
||||
const char *transport_protocol,
|
||||
gint port,
|
||||
SipMediaEncryption media_encryption,
|
||||
gboolean store_credentials)
|
||||
{
|
||||
return calls_sip_provider_add_origin_full (self,
|
||||
id,
|
||||
host,
|
||||
user,
|
||||
password,
|
||||
display_name,
|
||||
transport_protocol,
|
||||
port,
|
||||
media_encryption,
|
||||
TRUE,
|
||||
FALSE,
|
||||
0,
|
||||
FALSE,
|
||||
store_credentials);
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_sip_provider_add_origin_full:
|
||||
* @self: A #CallsSipProvider
|
||||
* @id: The id of the new origin (should be unique)
|
||||
* @host: The host to connect to
|
||||
* @user: The username to use
|
||||
* @password: The password to use
|
||||
* @display_name: The display name
|
||||
* @transport_protocol: The transport protocol to use, can be one of "UDP", "TCP" or "TLS"
|
||||
* @port: The port of the host
|
||||
* @media_encryption: A #SipMediaEncryption specifying if media should be encrypted
|
||||
* @auto_connect: Whether to automatically try going online
|
||||
* @direct_mode: Whether to use direct connection mode. Useful when you don't want to
|
||||
* connect to a SIP server. Mostly useful for testing and debugging.
|
||||
* @can_tel: Whether this origin can be used for PSTN telephony
|
||||
* @store_credentials: Whether to store credentials for this origin to disk
|
||||
*
|
||||
* Adds a new origin (SIP account). If @direct_mode is %TRUE then @host, @user and
|
||||
* @password do not have to be set.
|
||||
*
|
||||
* Return: (transfer none): A #CallsSipOrigin
|
||||
*/
|
||||
CallsSipOrigin *
|
||||
calls_sip_provider_add_origin_full (CallsSipProvider *self,
|
||||
const char *id,
|
||||
const char *host,
|
||||
const char *user,
|
||||
const char *password,
|
||||
const char *display_name,
|
||||
const char *transport_protocol,
|
||||
gint port,
|
||||
SipMediaEncryption media_encryption,
|
||||
gboolean auto_connect,
|
||||
gboolean direct_mode,
|
||||
gint local_port,
|
||||
gboolean can_tel,
|
||||
gboolean store_credentials)
|
||||
{
|
||||
g_autoptr (CallsSipOrigin) origin = NULL;
|
||||
g_autofree char *protocol = NULL;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SIP_PROVIDER (self), NULL);
|
||||
g_return_val_if_fail (id || *id, NULL);
|
||||
|
||||
/* direct-mode is mostly useful for testing without a SIP server */
|
||||
if (!direct_mode) {
|
||||
g_return_val_if_fail (host, NULL);
|
||||
g_return_val_if_fail (user, NULL);
|
||||
g_return_val_if_fail (password, NULL);
|
||||
}
|
||||
|
||||
if (transport_protocol) {
|
||||
g_return_val_if_fail (protocol_is_valid (transport_protocol), NULL);
|
||||
|
||||
protocol = g_ascii_strup (transport_protocol, -1);
|
||||
}
|
||||
|
||||
origin = g_object_new (CALLS_TYPE_SIP_ORIGIN,
|
||||
"id", id,
|
||||
"sip-context", self->ctx,
|
||||
"host", host,
|
||||
"user", user,
|
||||
"password", password,
|
||||
"display-name", display_name,
|
||||
"transport-protocol", protocol ?: "UDP",
|
||||
"port", port,
|
||||
"media-encryption", media_encryption,
|
||||
"auto-connect", auto_connect,
|
||||
"direct-mode", direct_mode,
|
||||
"local-port", local_port,
|
||||
"can-tel", can_tel,
|
||||
NULL);
|
||||
|
||||
g_list_store_append (self->origins, origin);
|
||||
|
||||
if (store_credentials && !self->use_memory_backend)
|
||||
calls_sip_provider_save_accounts_to_disk (self);
|
||||
|
||||
return origin;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
calls_sip_provider_remove_origin (CallsSipProvider *self,
|
||||
CallsSipOrigin *origin)
|
||||
{
|
||||
guint position;
|
||||
|
||||
g_return_val_if_fail (CALLS_IS_SIP_PROVIDER (self), FALSE);
|
||||
g_return_val_if_fail (CALLS_IS_SIP_ORIGIN (origin), FALSE);
|
||||
|
||||
if (g_list_store_find (self->origins, origin, &position)) {
|
||||
g_object_ref (origin);
|
||||
g_list_store_remove (self->origins, position);
|
||||
|
||||
if (!self->use_memory_backend) {
|
||||
origin_pw_delete_secret (origin);
|
||||
calls_sip_provider_save_accounts_to_disk (self);
|
||||
}
|
||||
g_object_unref (origin);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
CallsSipProvider *
|
||||
calls_sip_provider_new (void)
|
||||
{
|
||||
return g_object_new (CALLS_TYPE_SIP_PROVIDER, NULL);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
calls_sip_provider_load_accounts (CallsSipProvider *self,
|
||||
GKeyFile *key_file)
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
g_auto (GStrv) groups = NULL;
|
||||
|
||||
g_return_if_fail (CALLS_IS_SIP_PROVIDER (self));
|
||||
g_return_if_fail (key_file);
|
||||
|
||||
groups = g_key_file_get_groups (key_file, NULL);
|
||||
|
||||
for (gsize i = 0; groups[i] != NULL; i++) {
|
||||
new_origin_from_keyfile_secret (self, key_file, groups[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
calls_sip_provider_save_accounts (CallsSipProvider *self,
|
||||
GKeyFile *key_file)
|
||||
{
|
||||
guint n_origins;
|
||||
|
||||
g_return_if_fail (CALLS_IS_SIP_PROVIDER (self));
|
||||
g_return_if_fail (key_file);
|
||||
|
||||
n_origins = g_list_model_get_n_items (G_LIST_MODEL (self->origins));
|
||||
for (guint i = 0; i < n_origins; i++) {
|
||||
g_autoptr (CallsSipOrigin) origin =
|
||||
g_list_model_get_item (G_LIST_MODEL (self->origins), i);
|
||||
g_autofree char *group_name = g_strdup_printf ("sip-%02d", i);
|
||||
|
||||
origin_to_keyfile (origin, key_file, group_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
calls_sip_provider_save_accounts_to_disk (CallsSipProvider *self)
|
||||
{
|
||||
g_autoptr (GKeyFile) key_file = g_key_file_new ();
|
||||
g_autoptr (GError) error = NULL;
|
||||
gboolean saved = FALSE;
|
||||
|
||||
g_assert (CALLS_IS_SIP_PROVIDER (self));
|
||||
|
||||
calls_sip_provider_save_accounts (self, key_file);
|
||||
|
||||
if (!(saved = g_key_file_save_to_file (key_file, self->filename, &error)))
|
||||
g_warning ("Error saving keyfile to file %s: %s", self->filename, error->message);
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
|
||||
#ifndef FOR_TESTING
|
||||
|
||||
G_MODULE_EXPORT void
|
||||
peas_register_types (PeasObjectModule *module)
|
||||
{
|
||||
calls_sip_provider_register_type (G_TYPE_MODULE (module));
|
||||
|
||||
peas_object_module_register_extension_type (module,
|
||||
CALLS_TYPE_PROVIDER,
|
||||
CALLS_TYPE_SIP_PROVIDER);
|
||||
}
|
||||
|
||||
#endif /* FOR_TESTING */
|
||||
73
plugins/provider/sip/calls-sip-provider.h
Normal file
73
plugins/provider/sip/calls-sip-provider.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "calls-provider.h"
|
||||
#include "calls-sip-origin.h"
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <libpeas/peas.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_SIP_PROVIDER (calls_sip_provider_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsSipProvider, calls_sip_provider, CALLS, SIP_PROVIDER, CallsProvider);
|
||||
|
||||
CallsSipProvider *calls_sip_provider_new (void);
|
||||
CallsSipOrigin *calls_sip_provider_add_origin (CallsSipProvider *self,
|
||||
const char *id,
|
||||
const char *host,
|
||||
const char *user,
|
||||
const char *password,
|
||||
const char *display_name,
|
||||
const char *transport_protocol,
|
||||
gint port,
|
||||
SipMediaEncryption media_encryption,
|
||||
gboolean store_credentials);
|
||||
CallsSipOrigin *calls_sip_provider_add_origin_full (CallsSipProvider *self,
|
||||
const char *id,
|
||||
const char *host,
|
||||
const char *user,
|
||||
const char *password,
|
||||
const char *display_name,
|
||||
const char *transport_protocol,
|
||||
gint port,
|
||||
SipMediaEncryption media_encryption,
|
||||
gboolean auto_connect,
|
||||
gboolean direct_mode,
|
||||
gint local_port,
|
||||
gboolean use_for_tel,
|
||||
gboolean store_credentials);
|
||||
gboolean calls_sip_provider_remove_origin (CallsSipProvider *self,
|
||||
CallsSipOrigin *origin);
|
||||
void calls_sip_provider_load_accounts (CallsSipProvider *self,
|
||||
GKeyFile *key_file);
|
||||
void calls_sip_provider_save_accounts (CallsSipProvider *self,
|
||||
GKeyFile *key_file);
|
||||
gboolean calls_sip_provider_save_accounts_to_disk (CallsSipProvider *self);
|
||||
void peas_register_types (PeasObjectModule *module);
|
||||
|
||||
G_END_DECLS
|
||||
63
plugins/provider/sip/calls-sip-util.c
Normal file
63
plugins/provider/sip/calls-sip-util.c
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2021-2022 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#include "calls-sip-util.h"
|
||||
|
||||
gboolean
|
||||
check_ipv6 (const char *host)
|
||||
{
|
||||
/* TODO DNS SRV records to determine whether or not to use IPv6 */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
check_sips (const char *addr)
|
||||
{
|
||||
/* To keep it simple we only check if the URL starts with "sips:" */
|
||||
return g_str_has_prefix (addr, "sips:");
|
||||
}
|
||||
|
||||
|
||||
const gchar *
|
||||
get_protocol_prefix (const char *protocol)
|
||||
{
|
||||
if (g_strcmp0 (protocol, "UDP") == 0 ||
|
||||
g_strcmp0 (protocol, "TCP") == 0)
|
||||
return "sip";
|
||||
|
||||
if (g_strcmp0 (protocol, "TLS") == 0)
|
||||
return "sips";
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
gboolean
|
||||
protocol_is_valid (const char *protocol)
|
||||
{
|
||||
return g_strcmp0 (protocol, "UDP") == 0 ||
|
||||
g_strcmp0 (protocol, "TCP") == 0 ||
|
||||
g_strcmp0 (protocol, "TLS") == 0;
|
||||
}
|
||||
73
plugins/provider/sip/calls-sip-util.h
Normal file
73
plugins/provider/sip/calls-sip-util.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sofia-sip/nua.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
typedef struct {
|
||||
su_home_t home[1];
|
||||
su_root_t *root;
|
||||
} CallsSipContext;
|
||||
|
||||
typedef struct {
|
||||
nua_handle_t *register_handle;
|
||||
nua_handle_t *call_handle;
|
||||
CallsSipContext *context;
|
||||
} CallsSipHandles;
|
||||
|
||||
|
||||
/**
|
||||
* SipEngineState:
|
||||
* @SIP_ENGINE_NULL: Not initialized
|
||||
* @SIP_ENGINE_INITIALIZING: Need to complete initialization
|
||||
* @SIP_ENGINE_ERROR: Unrecoverable/Unhandled sofia-sip error
|
||||
* @SIP_ENGINE_READY: Ready for operation
|
||||
*/
|
||||
typedef enum {
|
||||
SIP_ENGINE_NULL = 0,
|
||||
SIP_ENGINE_INITIALIZING,
|
||||
SIP_ENGINE_ERROR,
|
||||
SIP_ENGINE_READY,
|
||||
} SipEngineState;
|
||||
|
||||
/**
|
||||
* SipMediaEncryption:
|
||||
* @SIP_MEDIA_ENCRYPTION_NONE: Don't encrypt media streams
|
||||
* @SIP_MEDIA_ENCRYPTION_PREFERRED: Prefer using encryption, but also allow unencrypted media
|
||||
* @SIP_MEDIA_ENCRYPTION_FORCED: Force using encryption, drop unencrypted calls
|
||||
*/
|
||||
typedef enum {
|
||||
SIP_MEDIA_ENCRYPTION_NONE = 0,
|
||||
SIP_MEDIA_ENCRYPTION_PREFERRED,
|
||||
SIP_MEDIA_ENCRYPTION_FORCED,
|
||||
} SipMediaEncryption;
|
||||
|
||||
|
||||
gboolean check_sips (const char *addr);
|
||||
gboolean check_ipv6 (const char *host);
|
||||
const char *get_protocol_prefix (const char *protocol);
|
||||
gboolean protocol_is_valid (const char *protocol);
|
||||
771
plugins/provider/sip/calls-srtp-utils.c
Normal file
771
plugins/provider/sip/calls-srtp-utils.c
Normal file
@@ -0,0 +1,771 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#include "calls-srtp-utils.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <sys/random.h>
|
||||
|
||||
/**
|
||||
* SECTION:srtp-utils
|
||||
* @short_description: SRTP utilities for SDP parsing
|
||||
* @Title: CallsSrtpUtils
|
||||
*
|
||||
* Utilities for parsing and generating the crypto attribute
|
||||
* in SDP for SRTP use based on RFC 4568.
|
||||
*
|
||||
* Note that limitations of libsrtp are taken into account when checking validity
|
||||
* of the parsed attribute. These are:
|
||||
* A maximum of 16 keys,
|
||||
* key derivation rate must be 0,
|
||||
* lifetimes other than 2^48 (we actually ignore the specified lifetimes)
|
||||
*/
|
||||
|
||||
|
||||
/* The default used in libsrtp. No API to change this. See https://github.com/cisco/libsrtp/issues/588 */
|
||||
#define SRTP_DEFAULT_LIFETIME_POW2 48
|
||||
#define SRTP_MAX_LIFETIME_POW2 48
|
||||
|
||||
/* The default used in libsrtp (and GstSrtpEnc/GstSrtpDec) */
|
||||
#define SRTP_DEFAULT_WINDOW_SIZE 128
|
||||
|
||||
const char * srtp_crypto_suites[] = {
|
||||
"AES_CM_128_HMAC_SHA1_32", /* RFC 4568 */
|
||||
"AES_CM_128_HMAC_SHA1_80", /* RFC 4568 */
|
||||
"F8_128_HMAC_SHA1_32", /* RFC 4568 but not supported by GstSrtpEnc/GstSrtpDec */
|
||||
"AEAD_AES_128_GCM", /* RFC 7714 TODO support in the future */
|
||||
"AEAD_AES_256_GCM", /* RFC 7714 TODO support in the future */
|
||||
NULL
|
||||
};
|
||||
|
||||
|
||||
static gsize
|
||||
get_key_size_for_suite (calls_srtp_crypto_suite suite)
|
||||
{
|
||||
switch (suite) {
|
||||
case CALLS_SRTP_SUITE_AES_128_SHA1_32:
|
||||
case CALLS_SRTP_SUITE_AES_128_SHA1_80:
|
||||
return 30;
|
||||
|
||||
case CALLS_SRTP_SUITE_UNKNOWN:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
validate_crypto_attribute (calls_srtp_crypto_attribute *attr,
|
||||
GError **error)
|
||||
{
|
||||
guint expected_key_salt_length;
|
||||
gboolean need_mki;
|
||||
guint expected_mki_length = 0;
|
||||
calls_srtp_crypto_key_param *key_param;
|
||||
GSList *mki_list = NULL; /* for checking uniqueness of MKIs */
|
||||
GSList *key_list = NULL; /* for checking uniqueness of keys */
|
||||
|
||||
if (!attr) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Attribute is NULL");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (attr->tag <= 0 || attr->tag >= 1000000000) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Tag is not valid: %d", attr->tag);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
switch (attr->crypto_suite) {
|
||||
case CALLS_SRTP_SUITE_AES_128_SHA1_32:
|
||||
case CALLS_SRTP_SUITE_AES_128_SHA1_80:
|
||||
expected_key_salt_length = 30; /* 16 byte key + 14 byte salt */
|
||||
break;
|
||||
|
||||
case CALLS_SRTP_SUITE_UNKNOWN:
|
||||
default:
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Crypto suite unknown");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* at least one and a maximum of 16 key parameters */
|
||||
if (attr->n_key_params == 0 || attr->n_key_params > 16) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Must have between 1 and 16 keys, got %d", attr->n_key_params);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
need_mki = attr->n_key_params > 1 ||
|
||||
attr->key_params[0].mki ||
|
||||
attr->key_params[0].mki_length;
|
||||
|
||||
if (need_mki) {
|
||||
expected_mki_length = attr->key_params[0].mki_length;
|
||||
if (expected_mki_length == 0 ||
|
||||
expected_mki_length > 128) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"MKI length must be between 1 and 128, got %u",
|
||||
expected_key_salt_length);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
for (guint i = 0; i < attr->n_key_params; i++) {
|
||||
g_autofree guchar *key_salt = NULL;
|
||||
gsize key_salt_length;
|
||||
|
||||
key_param = &attr->key_params[i];
|
||||
|
||||
/* must have a key */
|
||||
if (!key_param->b64_keysalt) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"No key found in parameter %d", i);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
key_salt = g_base64_decode (key_param->b64_keysalt, &key_salt_length);
|
||||
|
||||
/* key must have length consistent with suite */
|
||||
if (key_salt_length != expected_key_salt_length) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Key %d has length %" G_GSIZE_FORMAT ", but expected %d",
|
||||
i, key_salt_length, expected_key_salt_length);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/* key must be unique */
|
||||
if (g_slist_find_custom (key_list,
|
||||
key_param->b64_keysalt,
|
||||
(GCompareFunc) g_strcmp0)) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Key %d is not unique: %s", i, key_param->b64_keysalt);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
key_list = g_slist_append (key_list, key_param->b64_keysalt);
|
||||
|
||||
/* lifetime in range */
|
||||
if (key_param->lifetime_type == CALLS_SRTP_LIFETIME_AS_DECIMAL_NUMBER &&
|
||||
key_param->lifetime >= (1ULL << SRTP_MAX_LIFETIME_POW2)) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Lifetime of key %d out of bounds: Got %" G_GINT64_FORMAT " but maximum is 2^%d",
|
||||
i, key_param->lifetime, SRTP_MAX_LIFETIME_POW2);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (key_param->lifetime_type == CALLS_SRTP_LIFETIME_AS_POWER_OF_TWO &&
|
||||
key_param->lifetime >= SRTP_MAX_LIFETIME_POW2) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Lifetime of key %d out of bounds: Got 2^%" G_GINT64_FORMAT " but maximum is 2^%d",
|
||||
i, key_param->lifetime, SRTP_MAX_LIFETIME_POW2);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/* if MKI length is set, it must be the same for all key parameters */
|
||||
if (need_mki) {
|
||||
if (key_param->mki_length != expected_mki_length) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"MKI length must be the same for all keys. Key %d has length %d but expected %d",
|
||||
i, key_param->mki_length, expected_mki_length);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/* MKI must not have leading zero */
|
||||
if (key_param->mki == 0) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"No MKI set for key %d", i);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/* MKI must be unique */
|
||||
if (g_slist_find (mki_list, GINT_TO_POINTER (key_param->mki))) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"MKI for key %d is not unique", i);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
mki_list = g_slist_append (mki_list, GINT_TO_POINTER (key_param->mki));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* check session parameters */
|
||||
|
||||
/* libsrtp does only support kdr=0 */
|
||||
if (attr->kdr != 0) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Key derivation rate must be 0, got %d",
|
||||
attr->kdr);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
g_slist_free (mki_list);
|
||||
g_slist_free (key_list);
|
||||
|
||||
return TRUE;
|
||||
|
||||
failed:
|
||||
|
||||
g_slist_free (mki_list);
|
||||
g_slist_free (key_list);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_srtp_generate_key_salt:
|
||||
* @length: Desired length of random data
|
||||
*
|
||||
* Generate random data to be used as master key and master salt of desired length @length
|
||||
*
|
||||
* Returns: (transfer full): Random data to be used as key and salt in SRTP
|
||||
* or %NULL if failed. Free with g_free() when done.
|
||||
*/
|
||||
guchar *
|
||||
calls_srtp_generate_key_salt (gsize length)
|
||||
{
|
||||
g_autofree guchar *key_salt = NULL;
|
||||
gsize n_bytes;
|
||||
|
||||
g_return_val_if_fail (length > 0, NULL);
|
||||
|
||||
key_salt = g_malloc (length);
|
||||
|
||||
n_bytes = getrandom (key_salt, length, GRND_NONBLOCK);
|
||||
if (n_bytes == -1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_steal_pointer (&key_salt);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* calls_srtp_generate_key_salt_for_suite:
|
||||
* @suite: a #calls_srtp_crypto_suite
|
||||
*
|
||||
* Generate random data to be used as master key and master salt.
|
||||
* The required length is determined by the requirements of the @suite
|
||||
*
|
||||
* Returns: (transfer full): Random data to be used as key and salt in SRTP
|
||||
* or %NULL if failed. Free with g_free() when done.
|
||||
*/
|
||||
guchar *
|
||||
calls_srtp_generate_key_salt_for_suite (calls_srtp_crypto_suite suite)
|
||||
{
|
||||
gsize size = get_key_size_for_suite (suite);
|
||||
|
||||
if (size == 0)
|
||||
return NULL;
|
||||
|
||||
return calls_srtp_generate_key_salt (size);
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_srtp_parse_sdp_crypto_attribute:
|
||||
* @attribute: attribute line
|
||||
* @error: a #GError
|
||||
*
|
||||
* Parse textual attribute line into structured data.
|
||||
*
|
||||
* Returns: (transfer full): A #calls_srtp_crypto_attribute containing
|
||||
* parsed attribute data, or %NULL if parsing failed.
|
||||
*/
|
||||
calls_srtp_crypto_attribute *
|
||||
calls_srtp_parse_sdp_crypto_attribute (const char *attribute,
|
||||
GError **error)
|
||||
{
|
||||
g_auto (GStrv) attr_fields = NULL;
|
||||
g_auto (GStrv) key_info_strv = NULL;
|
||||
guint n_attr_fields;
|
||||
guint n_key_params;
|
||||
char *tag_str;
|
||||
gint tag;
|
||||
calls_srtp_crypto_attribute *attr;
|
||||
calls_srtp_crypto_suite crypto_suite;
|
||||
gboolean need_mki;
|
||||
gboolean attr_invalid = FALSE;
|
||||
g_autofree char *err_msg = NULL;
|
||||
|
||||
if (STR_IS_NULL_OR_EMPTY (attribute)) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Cannot parse null or empty strings");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!g_str_has_prefix (attribute, "a=crypto:")) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Does not look like a SDP crypto attribute: %s",
|
||||
attribute);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
attr_fields = g_strsplit (attribute, " ", -1);
|
||||
n_attr_fields = g_strv_length (attr_fields);
|
||||
|
||||
if (n_attr_fields < 3) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Need at least three fields in a SDP crypto attribute: %s",
|
||||
attribute);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tag_str = &attr_fields[0][9]; /* 9 is the length of "a=crypto:" */
|
||||
|
||||
/* leading zeros MUST NOT be used */
|
||||
if (*tag_str == '0') {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Tag must not have a leading zero: %s",
|
||||
tag_str);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tag = (int) strtol (tag_str, NULL, 10);
|
||||
if (tag == 0) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Tag set to 0: %s", tag_str);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (tag < 0) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Tag must be positive: %s", tag_str);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (tag >= 1000000000) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Tag must have a maximum of 9 digits: %s", tag_str);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* f.e. attr_fields[1] = "AES_CM_128_HMAC_SHA1_32" */
|
||||
if (g_strcmp0 (attr_fields[1], "AES_CM_128_HMAC_SHA1_32") == 0)
|
||||
crypto_suite = CALLS_SRTP_SUITE_AES_128_SHA1_32;
|
||||
else if (g_strcmp0 (attr_fields[1], "AES_CM_128_HMAC_SHA1_80") == 0)
|
||||
crypto_suite = CALLS_SRTP_SUITE_AES_128_SHA1_80;
|
||||
else
|
||||
crypto_suite = CALLS_SRTP_SUITE_UNKNOWN; /* error */
|
||||
|
||||
/* key infos are split by ';' */
|
||||
key_info_strv = g_strsplit (attr_fields[2], ";", -1);
|
||||
n_key_params = g_strv_length (key_info_strv);
|
||||
|
||||
/* libsrtp supports a maximum of 16 master keys */
|
||||
if (n_key_params > 16) {
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"More than 16 keys are not supported by libsrtp");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
need_mki = n_key_params > 1;
|
||||
|
||||
attr = calls_srtp_crypto_attribute_new (n_key_params);
|
||||
|
||||
attr->tag = tag;
|
||||
attr->crypto_suite = crypto_suite;
|
||||
|
||||
for (guint i = 0; i < n_key_params; i++) {
|
||||
char *key_info; /* srtp-key-info = key-salt ["|" lifetime] ["|" mki] */
|
||||
g_auto (GStrv) key_info_fields = NULL;
|
||||
guint n_key_infos;
|
||||
guint key_info_lifetime_index;
|
||||
guint key_info_mki_index;
|
||||
calls_srtp_crypto_key_param *key_param = &attr->key_params[i];
|
||||
|
||||
if (!g_str_has_prefix (key_info_strv[i], "inline:")) {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Key method not 'inline': %s", key_info_strv[i]);
|
||||
break;
|
||||
}
|
||||
|
||||
key_info = &key_info_strv[i][7]; /* 7 is the length of "inline:" */
|
||||
key_info_fields = g_strsplit (key_info, "|", -1);
|
||||
|
||||
n_key_infos = g_strv_length (key_info_fields);
|
||||
|
||||
key_param->b64_keysalt = g_strdup (key_info_fields[0]);
|
||||
|
||||
if (n_key_infos == 1) {
|
||||
key_info_lifetime_index = 0;
|
||||
key_info_mki_index = 0;
|
||||
} else if (n_key_infos == 2) {
|
||||
/* either MKI or lifetime */
|
||||
if (g_strstr_len (key_info_fields[1], -1, ":")) {
|
||||
key_info_lifetime_index = 0;
|
||||
key_info_mki_index = 1;
|
||||
} else {
|
||||
key_info_lifetime_index = 1;
|
||||
key_info_mki_index = 0;
|
||||
}
|
||||
} else if (n_key_infos == 3) {
|
||||
key_info_lifetime_index = 1;
|
||||
key_info_mki_index = 2;
|
||||
} else {
|
||||
/* invalid */
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Unexpected number of key-info fields: %s", key_info);
|
||||
break;
|
||||
}
|
||||
|
||||
/* lifetime type */
|
||||
if (key_info_lifetime_index) {
|
||||
char *lifetime_number;
|
||||
char *endptr;
|
||||
if (g_str_has_prefix (key_info_fields[key_info_lifetime_index], "2^")) {
|
||||
key_param->lifetime_type = CALLS_SRTP_LIFETIME_AS_POWER_OF_TWO;
|
||||
lifetime_number = &key_info_fields[key_info_lifetime_index][2]; /* 2 is the length of "2^" */
|
||||
} else {
|
||||
key_param->lifetime_type = CALLS_SRTP_LIFETIME_AS_DECIMAL_NUMBER;
|
||||
lifetime_number = key_info_fields[key_info_lifetime_index];
|
||||
}
|
||||
|
||||
if (*lifetime_number == '0') {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Leading zero in lifetime: %s",
|
||||
key_info_fields[key_info_lifetime_index]);
|
||||
break;
|
||||
}
|
||||
|
||||
key_param->lifetime = g_ascii_strtoull (lifetime_number, &endptr, 10);
|
||||
if (key_param->lifetime == 0) {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Lifetime set to zero: %s",
|
||||
key_info_fields[key_info_lifetime_index]);
|
||||
break;
|
||||
}
|
||||
|
||||
if (*endptr != '\0') {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Non numeric characters in lifetime: %s",
|
||||
key_info_fields[key_info_lifetime_index]);
|
||||
break;
|
||||
}
|
||||
|
||||
/* out of bounds check will be performed during validation of the attribute */
|
||||
}
|
||||
|
||||
if (need_mki && key_info_mki_index == 0) {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("MKI needed, but not found: %s", key_info);
|
||||
break;
|
||||
}
|
||||
|
||||
if (need_mki) {
|
||||
g_auto (GStrv) mki_split = g_strsplit (key_info_fields[key_info_mki_index], ":", -1);
|
||||
guint n_mki = g_strv_length (mki_split);
|
||||
guint64 mki;
|
||||
guint64 mki_length;
|
||||
char *endptr;
|
||||
|
||||
if (n_mki != 2) {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("MKI field not separated into two fields by colon: %s",
|
||||
key_info_fields[key_info_mki_index]);
|
||||
break;
|
||||
}
|
||||
|
||||
/* no leading zero allowed */
|
||||
if (*mki_split[0] == '0') {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Leading zero in MKI: %s", mki_split[0]);
|
||||
break;
|
||||
}
|
||||
|
||||
if (*mki_split[1] == '0') {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Leading zero in MKI length: %s", mki_split[1]);
|
||||
break;
|
||||
}
|
||||
|
||||
mki = g_ascii_strtoull (mki_split[0], &endptr, 10);
|
||||
if (mki == 0) {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("MKI set to 0: %s", mki_split[0]);
|
||||
break;
|
||||
}
|
||||
|
||||
if (*endptr != '\0') {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Non numeric characters found in MKI: %s", mki_split[0]);
|
||||
break;
|
||||
}
|
||||
|
||||
mki_length = g_ascii_strtoull (mki_split[1], &endptr, 10);
|
||||
/* number of bytes of the MKI field in the SRTP packet */
|
||||
if (mki_length == 0 || mki_length > 128) {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("MKI length not between 0 and 128: %s", mki_split[1]);
|
||||
break;
|
||||
}
|
||||
|
||||
if (*endptr != '\0') {
|
||||
attr_invalid = TRUE;
|
||||
err_msg = g_strdup_printf ("Non numeric characters found in MKI length: %s", mki_split[1]);
|
||||
break;
|
||||
}
|
||||
|
||||
key_param->mki = mki;
|
||||
key_param->mki_length = (guint) mki_length;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (attr_invalid) {
|
||||
calls_srtp_crypto_attribute_free (attr);
|
||||
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, err_msg);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* TODO session parameters */
|
||||
|
||||
if (!validate_crypto_attribute (attr, error)) {
|
||||
calls_srtp_crypto_attribute_free (attr);
|
||||
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
return attr;
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_srtp_print_sdp_crypto_attribute:
|
||||
* @attr: Structured crypto attribute
|
||||
* @error: A #GError
|
||||
*
|
||||
* Returns: (transfer full): Textual representation of crypto attribute
|
||||
* or %NULL if attribute contains invalid data.
|
||||
*/
|
||||
char *
|
||||
calls_srtp_print_sdp_crypto_attribute (calls_srtp_crypto_attribute *attr,
|
||||
GError **error)
|
||||
{
|
||||
const char *crypto_suite;
|
||||
GString *attr_str;
|
||||
|
||||
if (!validate_crypto_attribute (attr, error))
|
||||
return NULL;
|
||||
|
||||
if (attr->crypto_suite == CALLS_SRTP_SUITE_AES_128_SHA1_32)
|
||||
crypto_suite = "AES_CM_128_HMAC_SHA1_32";
|
||||
else if (attr->crypto_suite == CALLS_SRTP_SUITE_AES_128_SHA1_80)
|
||||
crypto_suite = "AES_CM_128_HMAC_SHA1_80";
|
||||
else
|
||||
return NULL;
|
||||
|
||||
attr_str = g_string_sized_new (96); /* minimal string length is 82 */
|
||||
|
||||
g_string_append_printf (attr_str, "a=crypto:%d %s ",
|
||||
attr->tag, crypto_suite);
|
||||
|
||||
/* key parameters */
|
||||
for (guint i = 0; i < attr->n_key_params; i++) {
|
||||
calls_srtp_crypto_key_param *key_param = &attr->key_params[i];
|
||||
|
||||
g_string_append_printf (attr_str, "inline:%s", key_param->b64_keysalt);
|
||||
if (key_param->lifetime_type == CALLS_SRTP_LIFETIME_AS_DECIMAL_NUMBER)
|
||||
g_string_append_printf (attr_str, "|%" G_GINT64_FORMAT, key_param->lifetime);
|
||||
if (key_param->lifetime_type == CALLS_SRTP_LIFETIME_AS_POWER_OF_TWO)
|
||||
g_string_append_printf (attr_str, "|2^%" G_GINT64_FORMAT, key_param->lifetime);
|
||||
|
||||
if (key_param->mki > 0) {
|
||||
g_string_append_printf(attr_str, "|%" G_GUINT64_FORMAT ":%u",
|
||||
key_param->mki, key_param->mki_length);
|
||||
}
|
||||
|
||||
if (i + 1 < attr->n_key_params)
|
||||
g_string_append (attr_str, ";");
|
||||
}
|
||||
|
||||
/* TODO session parameters */
|
||||
|
||||
return g_string_free (attr_str, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_srtp_crypto_attribute_new:
|
||||
* @n_key_params: The number of key parameters
|
||||
*
|
||||
* Returns: (transfer full): A new empty #calls_srtp_crypto_attribute
|
||||
* with @n_key_params key parameters allocated. Key parameters must be set either
|
||||
* manually or by using calls_srtp_crypto_attribute_init_keys().
|
||||
*
|
||||
* Free the attribute with calls_srtp_crypto_attribute_free() after you are done.
|
||||
*/
|
||||
calls_srtp_crypto_attribute *
|
||||
calls_srtp_crypto_attribute_new (guint n_key_params)
|
||||
{
|
||||
calls_srtp_crypto_attribute *attr;
|
||||
|
||||
g_return_val_if_fail (n_key_params > 0 || n_key_params < 16, NULL);
|
||||
|
||||
attr = g_new0 (calls_srtp_crypto_attribute, 1);
|
||||
attr->key_params = g_new0 (calls_srtp_crypto_key_param, n_key_params);
|
||||
attr->n_key_params = n_key_params;
|
||||
|
||||
return attr;
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_srtp_crypto_attribute_init_keys:
|
||||
* @attr: A #calls_srtp_crypto_attribute
|
||||
*
|
||||
* Generate key material and set sane default parameters for each
|
||||
* key parameter.
|
||||
*/
|
||||
gboolean
|
||||
calls_srtp_crypto_attribute_init_keys (calls_srtp_crypto_attribute *attr)
|
||||
{
|
||||
gsize key_size;
|
||||
gboolean need_mki;
|
||||
|
||||
g_return_val_if_fail (attr, FALSE);
|
||||
|
||||
key_size = get_key_size_for_suite (attr->crypto_suite);
|
||||
if (key_size == 0)
|
||||
return FALSE;
|
||||
|
||||
need_mki = attr->n_key_params > 1;
|
||||
|
||||
for (uint i = 0; i < attr->n_key_params; i++) {
|
||||
g_autofree guchar *key = calls_srtp_generate_key_salt (key_size);
|
||||
g_free (attr->key_params[i].b64_keysalt);
|
||||
attr->key_params[i].b64_keysalt = g_base64_encode (key, key_size);
|
||||
|
||||
if (need_mki) {
|
||||
attr->key_params[i].mki = i + 1;
|
||||
attr->key_params[i].mki_length = 4;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_srtp_crypto_attribute_free:
|
||||
* @attr: A #calls_srtp_crypto_attribute
|
||||
*
|
||||
* Frees all memory allocated for @attr.
|
||||
*/
|
||||
void
|
||||
calls_srtp_crypto_attribute_free (calls_srtp_crypto_attribute *attr)
|
||||
{
|
||||
for (guint i = 0; i < attr->n_key_params; i++) {
|
||||
g_free (attr->key_params[i].b64_keysalt);
|
||||
}
|
||||
|
||||
g_free (attr->key_params);
|
||||
g_free (attr->b64_fec_key);
|
||||
g_free (attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* calls_srtp_crypto_get_srtpdec_params:
|
||||
* @attr: A #calls_srtp_crypto_attribute
|
||||
* @srtp_cipher (out): SRTP cipher
|
||||
* @srtp_auth (out): SRTP auth
|
||||
* @srtcp_cipher (out): SRTCP cipher
|
||||
* @srtcp_auth (out): SRTCP auth
|
||||
*
|
||||
* Sets the parameters suitable for #GstSrtpDec (as a #GstCaps).
|
||||
*/
|
||||
gboolean
|
||||
calls_srtp_crypto_get_srtpdec_params (calls_srtp_crypto_attribute *attr,
|
||||
const char **srtp_cipher,
|
||||
const char **srtp_auth,
|
||||
const char **srtcp_cipher,
|
||||
const char **srtcp_auth)
|
||||
{
|
||||
g_return_val_if_fail (attr, FALSE);
|
||||
|
||||
if (attr->crypto_suite == CALLS_SRTP_SUITE_AES_128_SHA1_32) {
|
||||
*srtp_cipher = attr->unencrypted_srtp ? "null" : "aes-128-icm";
|
||||
*srtp_auth = attr->unauthenticated_srtp ? "null" : "hmac-sha1-32";
|
||||
*srtcp_cipher = attr->unencrypted_srtcp ? "null" : "aes-128-icm";
|
||||
*srtcp_auth = attr->unencrypted_srtcp ? "null" : "hmac-sha1-32";
|
||||
|
||||
return TRUE;
|
||||
} else if (attr->crypto_suite == CALLS_SRTP_SUITE_AES_128_SHA1_80) {
|
||||
*srtp_cipher = attr->unencrypted_srtp ? "null" : "aes-128-icm";
|
||||
*srtp_auth = attr->unauthenticated_srtp ? "null" : "hmac-sha1-80";
|
||||
*srtcp_cipher = attr->unencrypted_srtcp ? "null" : "aes-128-icm";
|
||||
*srtcp_auth = attr->unencrypted_srtcp ? "null" : "hmac-sha1-80";
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* calls_srtp_crypto_get_srtpenc_params:
|
||||
* @attr: A #calls_srtp_crypto_attribute
|
||||
* @srtp_cipher (out): SRTP cipher
|
||||
* @srtp_auth (out): SRTP auth
|
||||
* @srtcp_cipher (out): SRTCP cipher
|
||||
* @srtcp_auth (out): SRTCP auth
|
||||
*
|
||||
* Sets the parameters suitable for #GstSrtpDec (as a #GstCaps).
|
||||
*/
|
||||
gboolean
|
||||
calls_srtp_crypto_get_srtpenc_params (calls_srtp_crypto_attribute *attr,
|
||||
GstSrtpCipherType *srtp_cipher,
|
||||
GstSrtpAuthType *srtp_auth,
|
||||
GstSrtpCipherType *srtcp_cipher,
|
||||
GstSrtpAuthType *srtcp_auth)
|
||||
{
|
||||
g_return_val_if_fail (attr, FALSE);
|
||||
|
||||
if (attr->crypto_suite == CALLS_SRTP_SUITE_AES_128_SHA1_32) {
|
||||
*srtp_cipher = attr->unencrypted_srtp ? GST_SRTP_CIPHER_NULL : GST_SRTP_CIPHER_AES_128_ICM;
|
||||
*srtp_auth = attr->unauthenticated_srtp ? GST_SRTP_AUTH_NULL : GST_SRTP_AUTH_HMAC_SHA1_32;
|
||||
*srtcp_cipher = attr->unencrypted_srtcp ? GST_SRTP_CIPHER_NULL : GST_SRTP_CIPHER_AES_128_ICM;
|
||||
*srtcp_auth = attr->unencrypted_srtcp ? GST_SRTP_AUTH_NULL : GST_SRTP_AUTH_HMAC_SHA1_32;
|
||||
|
||||
return TRUE;
|
||||
} else if (attr->crypto_suite == CALLS_SRTP_SUITE_AES_128_SHA1_80) {
|
||||
|
||||
*srtp_cipher = attr->unencrypted_srtp ? GST_SRTP_CIPHER_NULL : GST_SRTP_CIPHER_AES_128_ICM;
|
||||
*srtp_auth = attr->unauthenticated_srtp ? GST_SRTP_AUTH_NULL : GST_SRTP_AUTH_HMAC_SHA1_80;
|
||||
*srtcp_cipher = attr->unencrypted_srtcp ? GST_SRTP_CIPHER_NULL : GST_SRTP_CIPHER_AES_128_ICM;
|
||||
*srtcp_auth = attr->unencrypted_srtcp ? GST_SRTP_AUTH_NULL : GST_SRTP_AUTH_HMAC_SHA1_80;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
133
plugins/provider/sip/calls-srtp-utils.h
Normal file
133
plugins/provider/sip/calls-srtp-utils.h
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef enum {
|
||||
CALLS_SRTP_SUITE_UNKNOWN = 0,
|
||||
CALLS_SRTP_SUITE_AES_128_SHA1_32,
|
||||
CALLS_SRTP_SUITE_AES_128_SHA1_80,
|
||||
} calls_srtp_crypto_suite;
|
||||
|
||||
|
||||
typedef enum {
|
||||
CALLS_SRTP_FEC_ORDER_UNSET = 0,
|
||||
CALLS_SRTP_FEC_ORDER_FEC_SRTP,
|
||||
CALLS_SRTP_FEC_ORDER_SRTP_FEC
|
||||
} calls_srtp_fec_order;
|
||||
|
||||
|
||||
typedef enum {
|
||||
CALLS_SRTP_LIFETIME_UNSET = 0,
|
||||
CALLS_SRTP_LIFETIME_AS_DECIMAL_NUMBER,
|
||||
CALLS_SRTP_LIFETIME_AS_POWER_OF_TWO
|
||||
} calls_srtp_lifetime_type;
|
||||
|
||||
|
||||
/* from GStreamer */
|
||||
typedef enum {
|
||||
GST_SRTP_CIPHER_NULL,
|
||||
GST_SRTP_CIPHER_AES_128_ICM,
|
||||
GST_SRTP_CIPHER_AES_256_ICM,
|
||||
GST_SRTP_CIPHER_AES_128_GCM,
|
||||
GST_SRTP_CIPHER_AES_256_GCM
|
||||
} GstSrtpCipherType;
|
||||
|
||||
|
||||
typedef enum {
|
||||
GST_SRTP_AUTH_NULL,
|
||||
GST_SRTP_AUTH_HMAC_SHA1_32,
|
||||
GST_SRTP_AUTH_HMAC_SHA1_80
|
||||
} GstSrtpAuthType;
|
||||
|
||||
|
||||
typedef struct {
|
||||
char *b64_keysalt;
|
||||
calls_srtp_lifetime_type lifetime_type;
|
||||
/* maximum lifetime: SRTP 2^48 packets; SRTCP 2^31 packets; libsrtp uses a hardcoded limit of 2^48 */
|
||||
guint64 lifetime;
|
||||
|
||||
guint64 mki;
|
||||
guint mki_length;
|
||||
} calls_srtp_crypto_key_param;
|
||||
|
||||
|
||||
typedef struct {
|
||||
gint tag;
|
||||
calls_srtp_crypto_suite crypto_suite;
|
||||
|
||||
calls_srtp_crypto_key_param *key_params;
|
||||
guint n_key_params;
|
||||
|
||||
/** session parameters
|
||||
* For more information see https://datatracker.ietf.org/doc/html/rfc4568#section-6.3
|
||||
* KDR (key derivation rate) defaults to 0; declarative parameter
|
||||
* decimal integer in {1,2,...,24}, denotes a power of two. 0 means unspecified
|
||||
* defaulting to a single initial key derivation
|
||||
* UNENCRYPTED_SRTCP, UNENCRYPTED_SRTP; negotiated parameter
|
||||
* UNAUTHENTICATED_SRTP; negotiated parameter
|
||||
* FEC_ORDER; declarative parameter
|
||||
* FEC_KEY separate key-params for forward error correction; declarative parameter
|
||||
* WSH (Window Size Hint); declarative parameter; MAY be ignored
|
||||
*/
|
||||
gint kdr;
|
||||
gboolean unencrypted_srtp;
|
||||
gboolean unencrypted_srtcp;
|
||||
gboolean unauthenticated_srtp;
|
||||
calls_srtp_fec_order fec_order; /* FEC in RTP: RFC2733 */
|
||||
char *b64_fec_key; /* TODO this should also be an array of calls_srtp_crypto_key_param */
|
||||
guint window_size_hint;
|
||||
} calls_srtp_crypto_attribute;
|
||||
|
||||
|
||||
guchar *calls_srtp_generate_key_salt (gsize length);
|
||||
guchar *calls_srtp_generate_key_salt_for_suite (calls_srtp_crypto_suite suite);
|
||||
calls_srtp_crypto_attribute *calls_srtp_parse_sdp_crypto_attribute (const char *attr,
|
||||
GError **error);
|
||||
char *calls_srtp_print_sdp_crypto_attribute (calls_srtp_crypto_attribute *attr,
|
||||
GError **error);
|
||||
calls_srtp_crypto_attribute *calls_srtp_crypto_attribute_new (guint n_key_params);
|
||||
gboolean calls_srtp_crypto_attribute_init_keys (calls_srtp_crypto_attribute *attr);
|
||||
void calls_srtp_crypto_attribute_free (calls_srtp_crypto_attribute *attr);
|
||||
char *calls_srtp_generate_crypto_for_offer (void);
|
||||
gboolean calls_srtp_crypto_get_srtpdec_params (calls_srtp_crypto_attribute *attr,
|
||||
const char **srtp_cipher,
|
||||
const char **srtp_auth,
|
||||
const char **srtcp_cipher,
|
||||
const char **srtcp_auth);
|
||||
gboolean calls_srtp_crypto_get_srtpenc_params (calls_srtp_crypto_attribute *attr,
|
||||
GstSrtpCipherType *srtp_cipher,
|
||||
GstSrtpAuthType *srtp_auth,
|
||||
GstSrtpCipherType *srtcp_cipher,
|
||||
GstSrtpAuthType *srtcp_auth);
|
||||
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (calls_srtp_crypto_attribute, calls_srtp_crypto_attribute_free)
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
144
plugins/provider/sip/gst-rfc3551.c
Normal file
144
plugins/provider/sip/gst-rfc3551.c
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "CallsGstRfc3551"
|
||||
|
||||
#include "gst-rfc3551.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
/* Use the following codecs in order of preference */
|
||||
static MediaCodecInfo gst_codecs[] = {
|
||||
{8, "PCMA", 8000, 1, "rtppcmapay", "rtppcmadepay", "alawenc", "alawdec", "libgstalaw.so"},
|
||||
{0, "PCMU", 8000, 1, "rtppcmupay", "rtppcmudepay", "mulawenc", "mulawdec", "libgstmulaw.so"},
|
||||
{3, "GSM", 8000, 1, "rtpgsmpay", "rtpgsmdepay", "gsmenc", "gsmdec", "libgstgsm.so"},
|
||||
/* {9, "G722", 8000, 1, "rtpg722pay", "rtpg722depay", "avenc_g722", "avdec_g722", "libgstlibav.so"}, */
|
||||
{4, "G723", 8000, 1, "rtpg723pay", "rtpg723depay", "avenc_g723_1", "avdec_g723_1", "libgstlibav.so"}, // does not seem to work
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* media_codec_available_in_gst:
|
||||
* @codec: A #MediaCodecInfo
|
||||
*
|
||||
* Returns: %TRUE if codec is available on your system, %FALSE otherwise
|
||||
*/
|
||||
gboolean
|
||||
media_codec_available_in_gst (MediaCodecInfo *codec)
|
||||
{
|
||||
gboolean available = FALSE;
|
||||
GstRegistry *registry = gst_registry_get ();
|
||||
GstPlugin *plugin = NULL;
|
||||
|
||||
plugin = gst_registry_lookup (registry, codec->filename);
|
||||
available = !!plugin;
|
||||
|
||||
if (plugin)
|
||||
gst_object_unref (plugin);
|
||||
|
||||
g_debug ("Gstreamer plugin for %s %s available",
|
||||
codec->name, available ? "is" : "is not");
|
||||
return available;
|
||||
}
|
||||
|
||||
/* media_codec_by_name:
|
||||
*
|
||||
* @name: The name of the codec
|
||||
*
|
||||
* Returns: (transfer none): A #MediaCodecInfo, if found.
|
||||
* You should check if the codec is available on your system before
|
||||
* trying to use it with media_codec_available_in_gst()
|
||||
*/
|
||||
MediaCodecInfo *
|
||||
media_codec_by_name (const char *name)
|
||||
{
|
||||
g_return_val_if_fail (name, NULL);
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS (gst_codecs); i++) {
|
||||
if (g_strcmp0 (name, gst_codecs[i].name) == 0)
|
||||
return &gst_codecs[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* media_codec_by_payload_id:
|
||||
*
|
||||
* @payload_id: The payload id (see RFC 3551, 3555, 4733, 4855)
|
||||
*
|
||||
* Returns: (transfer none): A #MediaCodecInfo, if found.
|
||||
* You should check if the codec is available on your system before
|
||||
* trying to use it with media_codec_available_in_gst()
|
||||
*/
|
||||
MediaCodecInfo *
|
||||
media_codec_by_payload_id (guint payload_id)
|
||||
{
|
||||
for (guint i = 0; i < G_N_ELEMENTS (gst_codecs); i++) {
|
||||
if (payload_id == gst_codecs[i].payload_id)
|
||||
return &gst_codecs[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* media_codec_get_gst_capabilities:
|
||||
*
|
||||
* @codec: A #MediaCodecInfo
|
||||
* @use_srtp: Whether to use SRTP
|
||||
*
|
||||
* Returns: (transfer full): The capability string describing GstCaps.
|
||||
* Used for the RTP source element.
|
||||
*/
|
||||
gchar *
|
||||
media_codec_get_gst_capabilities (MediaCodecInfo *codec,
|
||||
gboolean use_srtp)
|
||||
{
|
||||
return g_strdup_printf ("application/%s,media=(string)audio,clock-rate=(int)%u"
|
||||
",encoding-name=(string)%s,payload=(int)%u",
|
||||
use_srtp ? "x-srtp" : "x-rtp",
|
||||
codec->clock_rate,
|
||||
codec->name,
|
||||
codec->payload_id);
|
||||
}
|
||||
|
||||
/* media_codecs_get_candidates:
|
||||
*
|
||||
* Returns: (transfer container): A #GList of codec candidates of type #MediaCodecInfo.
|
||||
* Free the list with g_list_free when done.
|
||||
*/
|
||||
GList *
|
||||
media_codecs_get_candidates (void)
|
||||
{
|
||||
GList *candidates = NULL;
|
||||
|
||||
for (guint i = 0; i < G_N_ELEMENTS (gst_codecs); i++) {
|
||||
if (media_codec_available_in_gst (&gst_codecs[i])) {
|
||||
g_debug ("Adding %s to the codec candidates", gst_codecs[i].name);
|
||||
candidates = g_list_append (candidates, &gst_codecs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
52
plugins/provider/sip/gst-rfc3551.h
Normal file
52
plugins/provider/sip/gst-rfc3551.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
/*
|
||||
* For more information
|
||||
* see: https://tools.ietf.org/html/rfc3551#section-6
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
guint payload_id;
|
||||
char *name;
|
||||
gint clock_rate;
|
||||
gint channels;
|
||||
char *gst_payloader_name;
|
||||
char *gst_depayloader_name;
|
||||
char *gst_encoder_name;
|
||||
char *gst_decoder_name;
|
||||
char *filename;
|
||||
} MediaCodecInfo;
|
||||
|
||||
|
||||
gboolean media_codec_available_in_gst (MediaCodecInfo *codec);
|
||||
MediaCodecInfo *media_codec_by_name (const char *name);
|
||||
MediaCodecInfo *media_codec_by_payload_id (uint payload_id);
|
||||
gchar *media_codec_get_gst_capabilities (MediaCodecInfo *codec,
|
||||
gboolean use_srtp);
|
||||
GList *media_codecs_get_candidates (void);
|
||||
96
plugins/provider/sip/meson.build
Normal file
96
plugins/provider/sip/meson.build
Normal file
@@ -0,0 +1,96 @@
|
||||
#
|
||||
# Copyright (C) 2021 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
|
||||
sip_include = include_directories('.')
|
||||
|
||||
sip_install_dir = join_paths(full_calls_plugin_libdir, 'sip')
|
||||
|
||||
sip_plugin = configure_file(
|
||||
input: 'sip.plugin.in',
|
||||
output: 'sip.plugin',
|
||||
configuration: config_data,
|
||||
install_dir: sip_install_dir
|
||||
)
|
||||
|
||||
sip_deps = [
|
||||
dependency('gobject-2.0'),
|
||||
dependency('gstreamer-1.0'),
|
||||
dependency('gtk+-3.0'),
|
||||
dependency('libhandy-1', version: '>= 1.1.90'),
|
||||
dependency('libpeas-1.0'),
|
||||
dependency('sofia-sip-ua-glib'),
|
||||
dependency('libsecret-1'),
|
||||
]
|
||||
|
||||
sip_sources = files(
|
||||
[
|
||||
'calls-sdp-crypto-context.c', 'calls-sdp-crypto-context.h',
|
||||
'calls-sip-call.c', 'calls-sip-call.h',
|
||||
'calls-sip-origin.c', 'calls-sip-origin.h',
|
||||
'calls-sip-provider.c', 'calls-sip-provider.h',
|
||||
'calls-sip-util.c', 'calls-sip-util.h',
|
||||
'calls-sip-media-manager.c', 'calls-sip-media-manager.h',
|
||||
'calls-sip-media-pipeline.c', 'calls-sip-media-pipeline.h',
|
||||
'calls-sip-account-widget.c', 'calls-sip-account-widget.h',
|
||||
'calls-srtp-utils.c', 'calls-srtp-utils.h',
|
||||
'gst-rfc3551.c', 'gst-rfc3551.h',
|
||||
]
|
||||
)
|
||||
|
||||
pipeline_enum_headers = [
|
||||
'calls-sip-media-pipeline.h',
|
||||
]
|
||||
|
||||
pipeline_enums = gnome.mkenums_simple('calls-media-pipeline-enums',
|
||||
sources: pipeline_enum_headers)
|
||||
sip_sources += pipeline_enums
|
||||
|
||||
sip_enum_headers = [
|
||||
'calls-sdp-crypto-context.h',
|
||||
'calls-sip-util.h',
|
||||
]
|
||||
|
||||
sip_enums = gnome.mkenums_simple('calls-sip-enums',
|
||||
sources: sip_enum_headers,
|
||||
)
|
||||
|
||||
sip_sources += sip_enums
|
||||
|
||||
sip_resources = gnome.compile_resources(
|
||||
'sip-resources',
|
||||
'sip.gresources.xml',
|
||||
source_dir: '.',
|
||||
c_name: 'call',
|
||||
)
|
||||
|
||||
sip_sources += sip_resources
|
||||
|
||||
calls_sip = shared_module(
|
||||
'sip',
|
||||
sip_sources,
|
||||
dependencies: sip_deps,
|
||||
include_directories: src_include,
|
||||
link_with: libcalls,
|
||||
install: true,
|
||||
install_dir: sip_install_dir
|
||||
)
|
||||
239
plugins/provider/sip/sip-account-widget.ui
Normal file
239
plugins/provider/sip/sip-account-widget.ui
Normal file
@@ -0,0 +1,239 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.24"/>
|
||||
<requires lib="libhandy" version="1.0"/>
|
||||
<template class="CallsSipAccountWidget" parent="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
|
||||
<child>
|
||||
<object class="HdyHeaderBar" id="header_add">
|
||||
<property name="title" translatable="yes">Add Account</property>
|
||||
<property name="show-close-button">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="login_btn">
|
||||
<property name="visible">True</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="label" translatable="yes">_Log In</property>
|
||||
<signal name="clicked" handler="on_login_clicked" swapped="yes"/>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="spinner_add">
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="HdyHeaderBar" id="header_edit">
|
||||
<property name="visible">True</property>
|
||||
<property name="show-close-button">True</property>
|
||||
<property name="title" translatable="yes">Manage Account</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="apply_btn">
|
||||
<property name="visible">True</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="label" translatable="yes">_Apply</property>
|
||||
<signal name="clicked" handler="on_apply_clicked" swapped="yes"/>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="delete_btn">
|
||||
<property name="visible">True</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="label" translatable="yes">_Delete</property>
|
||||
<signal name="clicked" handler="on_delete_clicked" swapped="yes"/>
|
||||
<style>
|
||||
<class name="destructive-action"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinner" id="spinner_edit">
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="HdyPreferencesPage">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="HdyPreferencesGroup">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="HdyActionRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Server</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="host">
|
||||
<property name="visible">True</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="changed" handler="on_user_changed" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyPreferencesGroup">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="HdyActionRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Display Name</property>
|
||||
<property name="subtitle" translatable="yes">Optional</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="display_name">
|
||||
<property name="visible">True</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="changed" handler="on_user_changed" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyPreferencesGroup">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="HdyActionRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">User ID</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="user">
|
||||
<property name="visible">True</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="changed" handler="on_user_changed" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyActionRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Password</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="password">
|
||||
<property name="visible">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="input-purpose">password</property>
|
||||
<property name="visibility">False</property>
|
||||
<property name="primary_icon_sensitive">False</property>
|
||||
<property name="secondary_icon_activatable">True</property>
|
||||
<property name="secondary_icon_name">view-reveal-symbolic</property>
|
||||
<property name="secondary_icon_sensitive">True</property>
|
||||
<signal name="changed" handler="on_user_changed" swapped="yes"/>
|
||||
<signal name="icon-press" handler="on_password_visibility_changed" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyPreferencesGroup">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="HdyActionRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Port</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="port">
|
||||
<property name="visible">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="input-purpose">digits</property>
|
||||
<signal name="changed" handler="on_user_changed" swapped="yes"/>
|
||||
<signal name="insert-text" handler="on_port_entry_insert_text" swapped="yes"/>
|
||||
<signal name="insert-text" handler="on_port_entry_after_insert_text" swapped="yes" after="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyComboRow" id="protocol">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Transport</property>
|
||||
<signal name="notify::selected-index" handler="on_user_changed" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyComboRow" id="media_encryption">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Media Encryption</property>
|
||||
<signal name="notify::selected-index" handler="on_user_changed" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyPreferencesGroup">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="HdyActionRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Use for Phone Calls</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="tel_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="notify::active" handler="on_user_changed" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="HdyActionRow">
|
||||
<property name="visible">True</property>
|
||||
<property name="title" translatable="yes">Automatically Connect</property>
|
||||
<child>
|
||||
<object class="GtkSwitch" id="auto_connect_switch">
|
||||
<property name="visible">True</property>
|
||||
<property name="valign">center</property>
|
||||
<signal name="notify::active" handler="on_user_changed" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
<object class="GtkSizeGroup">
|
||||
<widgets>
|
||||
<widget name="host"/>
|
||||
<widget name="display_name"/>
|
||||
<widget name="user"/>
|
||||
<widget name="password"/>
|
||||
<widget name="port"/>
|
||||
</widgets>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
6
plugins/provider/sip/sip.gresources.xml
Normal file
6
plugins/provider/sip/sip.gresources.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/org/gnome/Calls/ui">
|
||||
<file preprocess="xml-stripblanks">sip-account-widget.ui</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
7
plugins/provider/sip/sip.plugin.in
Normal file
7
plugins/provider/sip/sip.plugin.in
Normal file
@@ -0,0 +1,7 @@
|
||||
[Plugin]
|
||||
Module=sip
|
||||
Name=SIP
|
||||
Description=SIP calls provider
|
||||
Authors=Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
Copyright=Copyright (C) 2021 Purism SPC
|
||||
Website=@PACKAGE_URL_RAW@
|
||||
Reference in New Issue
Block a user