1
0
Fork 0
mirror of https://gitlab.gnome.org/GNOME/calls.git synced 2024-05-13 16:49:29 +00:00
Purism-Calls/src/calls-ui-call-data.c
Evangelos Ribeiro Tzaras ac83323de6 ui-call-data: Always use calls_best_match_get_avatar() to retrieve avatars
self->best_match is never NULL:
The test suite used to wrap calls_contacts_provder_new() to always return
NULL which in turn caused the best match to be NULL.

This was done to avoid warnings raised by libfolks
about missing the primary store (eds).

This is no longer necessary as the environment now tells folks which
backend to use.
2023-01-20 17:12:02 +01:00

785 lines
21 KiB
C

/*
* 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 <devrtz@fortysixandtwo.eu>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
*/
#include "calls-ui-call-data.h"
#include "calls-contacts-provider.h"
#include "calls-manager.h"
#include <cui-call.h>
#include <cui-enums.h>
enum {
PROP_0,
PROP_CALL,
PROP_ORIGIN_ID,
PROP_INBOUND,
PROP_PROTOCOL,
PROP_DISPLAY_NAME,
PROP_ID,
PROP_STATE,
PROP_ENCRYPTED,
PROP_CAN_DTMF,
PROP_AVATAR_ICON,
PROP_ACTIVE_TIME,
PROP_SILENCED,
PROP_UI_ACTIVE,
PROP_LAST_PROP
};
enum {
STATE_CHANGED,
N_SIGNALS,
};
static GParamSpec *props[PROP_LAST_PROP];
static guint signals[N_SIGNALS];
struct _CallsUiCallData {
GObject parent_instance;
CallsCall *call;
CallsBestMatch *best_match;
GTimer *timer;
gdouble active_time;
guint timer_id;
CuiCallState state;
char *origin_id;
gboolean silenced;
gboolean ui_active; /* whether a UI should be shown (or the ringer should ring) */
guint set_active_id;
};
static void calls_ui_call_data_cui_call_interface_init (CuiCallInterface *iface);
G_DEFINE_TYPE_WITH_CODE (CallsUiCallData, calls_ui_call_data, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (CUI_TYPE_CALL,
calls_ui_call_data_cui_call_interface_init))
static const char *
calls_ui_call_data_get_display_name (CuiCall *call_data)
{
CallsUiCallData *self = (CallsUiCallData *) call_data;
g_return_val_if_fail (CALLS_IS_UI_CALL_DATA (self), NULL);
g_return_val_if_fail (!!self->call, NULL);
if (calls_best_match_has_individual (self->best_match))
return calls_best_match_get_name (self->best_match);
else
return calls_call_get_name (self->call);
}
static const char *
calls_ui_call_data_get_id (CuiCall *call_data)
{
CallsUiCallData *self = (CallsUiCallData *) call_data;
g_return_val_if_fail (CALLS_IS_UI_CALL_DATA (self), NULL);
g_return_val_if_fail (!!self->call, NULL);
return calls_call_get_id (self->call);
}
static CuiCallState
calls_ui_call_data_get_state (CuiCall *call_data)
{
CallsUiCallData *self = (CallsUiCallData *) call_data;
g_return_val_if_fail (CALLS_IS_UI_CALL_DATA (self), CUI_CALL_STATE_UNKNOWN);
g_return_val_if_fail (!!self->call, CUI_CALL_STATE_UNKNOWN);
return self->state;
}
static gboolean
calls_ui_call_data_get_encrypted (CuiCall *call_data)
{
CallsUiCallData *self = (CallsUiCallData *) call_data;
g_return_val_if_fail (CALLS_IS_UI_CALL_DATA (self), FALSE);
g_return_val_if_fail (!!self->call, FALSE);
return calls_call_get_encrypted (self->call);
}
static gboolean
calls_ui_call_data_get_can_dtmf (CuiCall *call_data)
{
CallsUiCallData *self = (CallsUiCallData *) call_data;
g_return_val_if_fail (CALLS_IS_UI_CALL_DATA (self), FALSE);
g_return_val_if_fail (!!self->call, FALSE);
return calls_call_can_dtmf (self->call);
}
static GLoadableIcon *
calls_ui_call_data_get_avatar_icon (CuiCall *call_data)
{
CallsUiCallData *self = (CallsUiCallData *) call_data;
g_return_val_if_fail (CALLS_UI_CALL_DATA (self), NULL);
return calls_best_match_get_avatar (self->best_match);
}
static gdouble
calls_ui_call_data_get_active_time (CuiCall *call_data)
{
CallsUiCallData *self = (CallsUiCallData *) call_data;
g_return_val_if_fail (CALLS_IS_UI_CALL_DATA (self), 0.0);
return self->active_time;
}
static gboolean
calls_ui_call_data_get_inbound (CallsUiCallData *self)
{
g_assert (CALLS_IS_UI_CALL_DATA (self));
return calls_call_get_inbound (self->call);
}
static const char *
calls_ui_call_data_get_protocol (CallsUiCallData *self)
{
g_assert (CALLS_IS_UI_CALL_DATA (self));
return calls_call_get_protocol (self->call);
}
static void
calls_ui_call_data_accept (CuiCall *call_data)
{
CallsUiCallData *self = (CallsUiCallData *) call_data;
g_return_if_fail (CALLS_IS_UI_CALL_DATA (self));
g_return_if_fail (!!self->call);
calls_call_answer (self->call);
}
static void
calls_ui_call_data_hang_up (CuiCall *call_data)
{
CallsUiCallData *self = (CallsUiCallData *) call_data;
g_return_if_fail (CALLS_IS_UI_CALL_DATA (self));
g_return_if_fail (!!self->call);
calls_call_hang_up (self->call);
}
static void
calls_ui_call_data_send_dtmf (CuiCall *call_data,
const char *dtmf)
{
CallsUiCallData *self = (CallsUiCallData *) call_data;
g_return_if_fail (CALLS_IS_UI_CALL_DATA (self));
g_return_if_fail (!!self->call);
calls_call_send_dtmf_tone (self->call, *dtmf);
}
static void
calls_ui_call_data_cui_call_interface_init (CuiCallInterface *iface)
{
iface->get_id = calls_ui_call_data_get_id;
iface->get_display_name = calls_ui_call_data_get_display_name;
iface->get_state = calls_ui_call_data_get_state;
iface->get_encrypted = calls_ui_call_data_get_encrypted;
iface->get_can_dtmf = calls_ui_call_data_get_can_dtmf;
iface->get_avatar_icon = calls_ui_call_data_get_avatar_icon;
iface->get_active_time = calls_ui_call_data_get_active_time;
iface->accept = calls_ui_call_data_accept;
iface->hang_up = calls_ui_call_data_hang_up;
iface->send_dtmf = calls_ui_call_data_send_dtmf;
}
static gboolean
on_timer_ticked (CallsUiCallData *self)
{
g_assert (CALLS_IS_UI_CALL_DATA (self));
self->active_time = g_timer_elapsed (self->timer, NULL);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACTIVE_TIME]);
return G_SOURCE_CONTINUE;
}
static void
set_state (CallsUiCallData *self,
CuiCallState new_state)
{
CuiCallState old_state;
g_assert (CALLS_IS_UI_CALL_DATA (self));
if (self->state == new_state)
return;
old_state = self->state;
self->state = new_state;
g_debug ("Setting UI call state from %s to %s",
cui_call_state_to_string (old_state),
cui_call_state_to_string (new_state));
/* Check for started timer, because state could have changed like this:
* ACTIVE -> HELD -> ACTIVE
* and we don't want to start the timer multiple times
*/
if (new_state == CUI_CALL_STATE_ACTIVE && !self->timer) {
self->timer = g_timer_new ();
self->timer_id = g_timeout_add (500,
G_SOURCE_FUNC (on_timer_ticked),
self);
g_source_set_name_by_id (self->timer_id, "ui-call: active_time_timer");
g_debug ("Start tracking active time; source id %u", self->timer_id);
} else if (new_state == CUI_CALL_STATE_DISCONNECTED) {
g_debug ("Stop tracking active time; source id %u", self->timer_id);
g_clear_handle_id (&self->timer_id, g_source_remove);
g_clear_pointer (&self->timer, g_timer_destroy);
g_clear_handle_id (&self->set_active_id, g_source_remove);
}
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_STATE]);
g_signal_emit (self, signals[STATE_CHANGED], 0, new_state, old_state);
}
static void
on_notify_state (CallsUiCallData *self)
{
CallsCallState state;
g_assert (CALLS_IS_UI_CALL_DATA (self));
state = calls_call_get_state (self->call);
set_state (self, calls_call_state_to_cui_call_state (state));
}
static void
on_notify_name (CallsUiCallData *self)
{
g_assert (CALLS_IS_UI_CALL_DATA (self));
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DISPLAY_NAME]);
}
static void
on_notify_avatar (CallsUiCallData *self)
{
g_assert (CALLS_IS_UI_CALL_DATA (self));
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_AVATAR_ICON]);
}
static void
on_notify_encrypted (CallsUiCallData *self)
{
g_assert (CALLS_IS_UI_CALL_DATA (self));
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ENCRYPTED]);
}
static void
set_call_data (CallsUiCallData *self,
CallsCall *call)
{
CallsManager *manager;
CallsContactsProvider *contacts_provider;
g_return_if_fail (CALLS_IS_UI_CALL_DATA (self));
g_return_if_fail (CALLS_IS_CALL (call));
self->call = call;
g_signal_connect_object (self->call,
"notify::state",
G_CALLBACK (on_notify_state),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (self->call,
"notify::encrypted",
G_CALLBACK (on_notify_encrypted),
self,
G_CONNECT_SWAPPED);
on_notify_state (self);
manager = calls_manager_get_default ();
contacts_provider = calls_manager_get_contacts_provider (manager);
self->best_match =
calls_contacts_provider_lookup_id (contacts_provider,
calls_call_get_id (call));
g_assert (self->best_match);
g_signal_connect_object (self->best_match,
"notify::name",
G_CALLBACK (on_notify_name),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (self->best_match,
"notify::has-individual",
G_CALLBACK (on_notify_name),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (self->best_match,
"notify::avatar",
G_CALLBACK (on_notify_avatar),
self,
G_CONNECT_SWAPPED);
}
static void
set_ui_active (CallsUiCallData *self,
gboolean active)
{
g_assert (CALLS_IS_UI_CALL_DATA (self));
if (self->ui_active == active)
return;
self->ui_active = active;
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_UI_ACTIVE]);
}
static gboolean
on_delay_set_active (CallsUiCallData *self)
{
g_assert (CALLS_IS_UI_CALL_DATA (self));
set_ui_active (self, TRUE);
self->set_active_id = 0;
g_object_unref (self);
return G_SOURCE_REMOVE;
}
#define DELAY_UI_MS 100
static void
calls_ui_call_data_constructed (GObject *object)
{
CallsUiCallData *self = CALLS_UI_CALL_DATA (object);
G_OBJECT_CLASS (calls_ui_call_data_parent_class)->constructed (object);
if (!calls_call_get_inbound (self->call) || self->state != CUI_CALL_STATE_INCOMING) {
set_ui_active (self, TRUE);
return;
}
set_ui_active (self, FALSE);
self->set_active_id = g_timeout_add (DELAY_UI_MS,
G_SOURCE_FUNC (on_delay_set_active),
g_object_ref (self));
}
#undef DELAY_UI_MS
static void
calls_ui_call_data_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
CallsUiCallData *self = CALLS_UI_CALL_DATA (object);
switch (property_id) {
case PROP_CALL:
// construct only
set_call_data (self, g_value_dup_object (value));
break;
case PROP_ORIGIN_ID:
self->origin_id = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
calls_ui_call_data_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
CallsUiCallData *self = CALLS_UI_CALL_DATA (object);
CuiCall *cui_call = CUI_CALL (object);
switch (property_id) {
case PROP_CALL:
g_value_set_object (value, self->call);
break;
case PROP_ORIGIN_ID:
g_value_set_string (value, calls_ui_call_data_get_origin_id (self));
break;
case PROP_INBOUND:
g_value_set_boolean (value, calls_ui_call_data_get_inbound (self));
break;
case PROP_PROTOCOL:
g_value_set_string (value, calls_ui_call_data_get_protocol (self));
break;
case PROP_DISPLAY_NAME:
g_value_set_string (value, calls_ui_call_data_get_display_name (cui_call));
break;
case PROP_ID:
g_value_set_string (value, calls_ui_call_data_get_id (cui_call));
break;
case PROP_STATE:
g_value_set_enum (value, calls_ui_call_data_get_state (cui_call));
break;
case PROP_ENCRYPTED:
g_value_set_boolean (value, calls_ui_call_data_get_encrypted (cui_call));
break;
case PROP_CAN_DTMF:
g_value_set_boolean (value, calls_ui_call_data_get_can_dtmf (cui_call));
break;
case PROP_AVATAR_ICON:
g_value_set_object (value, calls_ui_call_data_get_avatar_icon (cui_call));
break;
case PROP_ACTIVE_TIME:
g_value_set_double (value, self->active_time);
case PROP_SILENCED:
g_value_set_boolean (value, calls_ui_call_data_get_silenced (self));
break;
case PROP_UI_ACTIVE:
g_value_set_boolean (value, calls_ui_call_data_get_ui_active (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
calls_ui_call_data_dispose (GObject *object)
{
CallsUiCallData *self = CALLS_UI_CALL_DATA (object);
g_clear_object (&self->call);
g_clear_object (&self->best_match);
g_clear_pointer (&self->origin_id, g_free);
g_clear_handle_id (&self->timer_id, g_source_remove);
g_clear_pointer (&self->timer, g_timer_destroy);
G_OBJECT_CLASS (calls_ui_call_data_parent_class)->dispose (object);
}
static void
calls_ui_call_data_init (CallsUiCallData *self)
{
}
static void
calls_ui_call_data_class_init (CallsUiCallDataClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = calls_ui_call_data_constructed;
object_class->set_property = calls_ui_call_data_set_property;
object_class->get_property = calls_ui_call_data_get_property;
object_class->dispose = calls_ui_call_data_dispose;
props[PROP_CALL] =
g_param_spec_object ("call",
"Call",
"The call",
CALLS_TYPE_CALL,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_CALL, props[PROP_CALL]);
props[PROP_ORIGIN_ID] =
g_param_spec_string ("origin-id",
"Origin ID",
"ID of the origin used for the call",
NULL,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_ORIGIN_ID, props[PROP_ORIGIN_ID]);
props[PROP_INBOUND] =
g_param_spec_boolean ("inbound",
"Inbound",
"Whether the call is inbound",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_INBOUND, props[PROP_INBOUND]);
props[PROP_PROTOCOL] =
g_param_spec_string ("protocol",
"Protocol",
"The protocol for the call, e.g. tel, sip",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_PROTOCOL, props[PROP_PROTOCOL]);
props[PROP_SILENCED] =
g_param_spec_boolean ("silenced",
"Silenced",
"Whether the call ringing should be silenced",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_SILENCED, props[PROP_SILENCED]);
props[PROP_UI_ACTIVE] =
g_param_spec_boolean ("ui-active",
"UI active",
"Whether the UI should be shown",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_property (object_class, PROP_UI_ACTIVE, props[PROP_UI_ACTIVE]);
g_object_class_override_property (object_class, PROP_ID, "id");
props[PROP_ID] = g_object_class_find_property (object_class, "id");
g_object_class_override_property (object_class, PROP_DISPLAY_NAME, "display-name");
props[PROP_DISPLAY_NAME] = g_object_class_find_property (object_class, "display-name");
g_object_class_override_property (object_class, PROP_STATE, "state");
props[PROP_STATE] = g_object_class_find_property (object_class, "state");
g_object_class_override_property (object_class, PROP_ENCRYPTED, "encrypted");
props[PROP_ENCRYPTED] = g_object_class_find_property (object_class, "encrypted");
g_object_class_override_property (object_class, PROP_CAN_DTMF, "can-dtmf");
props[PROP_CAN_DTMF] = g_object_class_find_property (object_class, "can-dtmf");
g_object_class_override_property (object_class, PROP_AVATAR_ICON, "avatar-icon");
props[PROP_AVATAR_ICON] = g_object_class_find_property (object_class, "avatar-icon");
g_object_class_override_property (object_class, PROP_ACTIVE_TIME, "active-time");
props[PROP_ACTIVE_TIME] = g_object_class_find_property (object_class, "active-time");
/**
* CallsUiCallData::state-changed:
* @self: The #CallsUiCallData instance.
* @new_state: The new state of the call.
* @old_state: The old state of the call.
*
* This signal is emitted when the state of the call changes, for
* example when it's answered or when the call is disconnected.
*/
signals[STATE_CHANGED] =
g_signal_new ("state-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL,
G_TYPE_NONE,
2, CUI_TYPE_CALL_STATE, CUI_TYPE_CALL_STATE);
}
CallsUiCallData *
calls_ui_call_data_new (CallsCall *call,
const char *origin_id)
{
return g_object_new (CALLS_TYPE_UI_CALL_DATA,
"call", call,
"origin-id", origin_id,
NULL);
}
/**
* calls_ui_call_data_silence_ring:
* @self: a #CallsUiCallData
*
* Inhibit ringing
*/
void
calls_ui_call_data_silence_ring (CallsUiCallData *self)
{
g_return_if_fail (CALLS_IS_UI_CALL_DATA (self));
g_return_if_fail (self->state == CUI_CALL_STATE_INCOMING);
if (self->silenced)
return;
self->silenced = TRUE;
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SILENCED]);
}
/**
* calls_ui_call_data_get_silenced:
* @self: a #CallsUiCallData
*
* Returns: %TRUE if call has been silenced to not ring, %FALSE otherwise
*/
gboolean
calls_ui_call_data_get_silenced (CallsUiCallData *self)
{
g_return_val_if_fail (CALLS_IS_UI_CALL_DATA (self), FALSE);
return self->silenced;
}
/**
* calls_ui_call_data_get_ui_active:
* @self: a #CallsUiCallData
*
* Returns: %TRUE if the UI should be shown, %FALSE otherwise
*/
gboolean
calls_ui_call_data_get_ui_active (CallsUiCallData *self)
{
g_return_val_if_fail (CALLS_UI_CALL_DATA (self), FALSE);
return self->ui_active;
}
/**
* calls_ui_call_data_get_call_type:
* @self: a #CallsUiCallData
*
* Returns: The type of call, or #CALLS_CALL_TYPE_UNKNOWN if not known.
*/
CallsCallType
calls_ui_call_data_get_call_type (CallsUiCallData *self)
{
g_return_val_if_fail (CALLS_IS_UI_CALL_DATA (self), CALLS_CALL_TYPE_UNKNOWN);
g_return_val_if_fail (CALLS_CALL (self->call), CALLS_CALL_TYPE_UNKNOWN);
return calls_call_get_call_type (self->call);
}
/**
* calls_ui_call_data_get_origin_id:
* @self: a #CallsUiCallData
*
* Returns: (transfer none): The id of the origin this call was placed from
* or %NULL, if unknown.
*/
const char *
calls_ui_call_data_get_origin_id (CallsUiCallData *self)
{
g_return_val_if_fail (CALLS_IS_UI_CALL_DATA (self), NULL);
return self->origin_id;
}
/**
* calls_ui_call_data_dup_origin_name:
* @self: a #CallsUiCallData
*
* Returns: (transfer full): The name of the origin this call was placed from
* or %NULL, if unknown.
*/
char *
calls_ui_call_data_dup_origin_name (CallsUiCallData *self)
{
CallsOrigin *origin;
g_return_val_if_fail (CALLS_IS_UI_CALL_DATA (self), NULL);
origin = calls_manager_get_origin_by_id (calls_manager_get_default (),
self->origin_id);
if (origin)
return calls_origin_get_name (origin);
return NULL;
}
/**
* calls_call_state_to_cui_call_state:
* @state: A #CallsCallState
*
* Returns: a #CuiCallState @state is mapped to.
*/
CuiCallState
calls_call_state_to_cui_call_state (CallsCallState state)
{
switch (state) {
case CALLS_CALL_STATE_ACTIVE:
return CUI_CALL_STATE_ACTIVE;
case CALLS_CALL_STATE_HELD:
return CUI_CALL_STATE_HELD;
case CALLS_CALL_STATE_DIALING:
case CALLS_CALL_STATE_ALERTING:
return CUI_CALL_STATE_CALLING;
case CALLS_CALL_STATE_INCOMING:
return CUI_CALL_STATE_INCOMING;
case CALLS_CALL_STATE_DISCONNECTED:
return CUI_CALL_STATE_DISCONNECTED;
default:
return CUI_CALL_STATE_UNKNOWN;
}
}