/*
* call-channel.c - an example 1-1 audio/video call
*
* For simplicity, this channel emulates a device with its own
* audio/video user interface, like a video-equipped form of the phones
* manipulated by telepathy-snom or gnome-phone-manager.
*
* As a result, this channel has the HardwareStreaming flag, its contents
* and streams do not have the Media interface, and clients should not attempt
* to do their own streaming using telepathy-farsight, telepathy-stream-engine
* or maemo-stream-engine.
*
* In practice, nearly all connection managers do not have HardwareStreaming,
* and do have the Media interface on their contents/streams. Usage for those
* CMs is the same, except that whichever client is the primary handler
* for the channel should also hand the channel over to telepathy-farsight or
* telepathy-stream-engine to implement the actual streaming.
*
* Copyright © 2007-2009 Collabora Ltd.
* Copyright © 2007-2009 Nokia Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "config.h"
#include "call-channel.h"
#include
#include
#include
#include
#include "call-content.h"
#include "call-stream.h"
static void hold_iface_init (gpointer iface, gpointer data);
G_DEFINE_TYPE_WITH_CODE (ExampleCallChannel,
example_call_channel,
TP_TYPE_BASE_MEDIA_CALL_CHANNEL,
G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_HOLD,
hold_iface_init))
enum
{
PROP_SIMULATION_DELAY = 1,
N_PROPS
};
struct _ExampleCallChannelPrivate
{
guint simulation_delay;
TpBaseConnection *conn;
TpHandle handle;
gboolean locally_requested;
guint hold_state;
guint hold_state_reason;
guint next_stream_id;
gboolean closed;
};
static GPtrArray *
example_call_channel_get_interfaces (TpBaseChannel *self)
{
GPtrArray *interfaces;
interfaces = TP_BASE_CHANNEL_CLASS (
example_call_channel_parent_class)->get_interfaces (self);
g_ptr_array_add (interfaces, TP_IFACE_CHANNEL_INTERFACE_HOLD);
return interfaces;
}
/* In practice you need one for audio, plus one per video (e.g. a
* presentation might have separate video contents for the slides
* and a camera pointed at the presenter), so having more than three
* would be highly unusual */
#define MAX_CONTENTS_PER_CALL 100
G_GNUC_NULL_TERMINATED static void
example_call_channel_set_state (ExampleCallChannel *self,
TpCallState state,
TpCallFlags flags,
TpHandle actor,
TpCallStateChangeReason reason,
const gchar *error,
...)
{
/* FIXME: TpBaseCallChannel is not that flexible */
tp_base_call_channel_set_state ((TpBaseCallChannel *) self,
state, actor, reason, error, "");
}
static void
example_call_channel_init (ExampleCallChannel *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
EXAMPLE_TYPE_CALL_CHANNEL,
ExampleCallChannelPrivate);
self->priv->next_stream_id = 1;
self->priv->hold_state = TP_LOCAL_HOLD_STATE_UNHELD;
self->priv->hold_state_reason = TP_LOCAL_HOLD_STATE_REASON_NONE;
}
static ExampleCallContent *example_call_channel_add_content (
ExampleCallChannel *self, TpMediaStreamType media_type,
gboolean locally_requested, gboolean initial,
const gchar *requested_name, GError **error);
static void example_call_channel_initiate_outgoing (ExampleCallChannel *self);
static void
constructed (GObject *object)
{
void (*chain_up) (GObject *) =
((GObjectClass *) example_call_channel_parent_class)->constructed;
ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (object);
TpBaseChannel *base = (TpBaseChannel *) self;
TpBaseCallChannel *call = (TpBaseCallChannel *) self;
if (chain_up != NULL)
chain_up (object);
self->priv->handle = tp_base_channel_get_target_handle (base);
self->priv->locally_requested = tp_base_channel_is_requested (base);
self->priv->conn = tp_base_channel_get_connection (base);
tp_base_call_channel_update_member_flags (call, self->priv->handle, 0,
0, TP_CALL_STATE_CHANGE_REASON_UNKNOWN, "", "");
if (self->priv->locally_requested)
{
/* Nobody is locally pending. The remote peer will turn up in
* remote-pending state when we actually contact them, which is done
* in example_call_channel_initiate_outgoing. */
example_call_channel_set_state (self,
TP_CALL_STATE_PENDING_INITIATOR, 0, 0,
TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
NULL);
}
else
{
/* This is an incoming call, so the self-handle is locally
* pending, to indicate that we need to answer. */
example_call_channel_set_state (self,
TP_CALL_STATE_INITIALISED, 0, self->priv->handle,
TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
NULL);
}
/* FIXME: should respect initial names */
if (tp_base_call_channel_has_initial_audio (call, NULL))
{
g_message ("Channel initially has an audio stream");
example_call_channel_add_content (self, TP_MEDIA_STREAM_TYPE_AUDIO,
self->priv->locally_requested, TRUE, NULL, NULL);
}
if (tp_base_call_channel_has_initial_video (call, NULL))
{
g_message ("Channel initially has a video stream");
example_call_channel_add_content (self, TP_MEDIA_STREAM_TYPE_VIDEO,
self->priv->locally_requested, TRUE, NULL, NULL);
}
tp_base_channel_register (base);
}
static void
get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (object);
switch (property_id)
{
case PROP_SIMULATION_DELAY:
g_value_set_uint (value, self->priv->simulation_delay);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (object);
switch (property_id)
{
case PROP_SIMULATION_DELAY:
self->priv->simulation_delay = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
example_call_channel_terminate (ExampleCallChannel *self,
TpHandle actor,
TpChannelGroupChangeReason reason,
TpCallStateChangeReason call_reason,
const gchar *error_name)
{
TpBaseCallChannel *base = (TpBaseCallChannel *) self;
TpCallState call_state = tp_base_call_channel_get_state (base);
if (call_state != TP_CALL_STATE_ENDED)
{
GList *contents;
example_call_channel_set_state (self,
TP_CALL_STATE_ENDED, 0, actor,
call_reason, error_name,
NULL);
/* FIXME: fd.o #24936 #c20: it's unclear in the spec whether we should
* remove peers on call termination or not. For now this example does. */
tp_base_call_channel_remove_member (base, self->priv->handle,
actor, call_reason, error_name, NULL);
if (actor == tp_base_connection_get_self_handle (self->priv->conn))
{
const gchar *send_reason;
/* In a real protocol these would be some sort of real protocol
* construct, like an XMPP error stanza or a SIP error code */
switch (reason)
{
case TP_CHANNEL_GROUP_CHANGE_REASON_BUSY:
send_reason = "";
break;
case TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER:
send_reason = "";
break;
default:
send_reason = "";
}
g_message ("SIGNALLING: send: Terminating call: %s", send_reason);
}
/* terminate all streams: to avoid modifying the hash table (in the
* streams-removed handler) while iterating over it, we have to copy the
* keys and iterate over those */
contents = tp_base_call_channel_get_contents (base);
contents = g_list_copy (contents);
for (; contents != NULL; contents = g_list_delete_link (contents, contents))
{
example_call_content_remove_stream (contents->data);
tp_base_call_channel_remove_content (base, contents->data,
0, call_reason, error_name, "");
}
}
}
static void
dispose (GObject *object)
{
ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (object);
/* the manager is meant to hold a ref to us until we've closed */
g_assert (self->priv->closed);
((GObjectClass *) example_call_channel_parent_class)->dispose (object);
}
static void
close_channel (TpBaseChannel *base)
{
ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (base);
example_call_channel_terminate (self,
tp_base_connection_get_self_handle (self->priv->conn),
TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "");
self->priv->closed = TRUE;
tp_base_channel_destroyed (base);
}
static void call_accept (TpBaseCallChannel *self);
static TpBaseCallContent * call_add_content (TpBaseCallChannel *self,
const gchar *name,
TpMediaStreamType media,
TpMediaStreamDirection initial_direction,
GError **error);
static void call_hangup (TpBaseCallChannel *self,
guint reason,
const gchar *detailed_reason,
const gchar *message);
static void
example_call_channel_class_init (ExampleCallChannelClass *klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
TpBaseChannelClass *base_class = TP_BASE_CHANNEL_CLASS (klass);
TpBaseCallChannelClass *call_class = (TpBaseCallChannelClass *) klass;
GParamSpec *param_spec;
g_type_class_add_private (klass,
sizeof (ExampleCallChannelPrivate));
call_class->accept = call_accept;
call_class->add_content = call_add_content;
call_class->hangup = call_hangup;
base_class->target_handle_type = TP_HANDLE_TYPE_CONTACT;
base_class->get_interfaces = example_call_channel_get_interfaces;
base_class->close = close_channel;
object_class->constructed = constructed;
object_class->set_property = set_property;
object_class->get_property = get_property;
object_class->dispose = dispose;
param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
"Delay between simulated network events",
0, G_MAXUINT32, 1000,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
param_spec);
}
static gboolean
simulate_contact_ended_cb (gpointer p)
{
ExampleCallChannel *self = p;
TpBaseCallChannel *base = (TpBaseCallChannel *) self;
TpCallState call_state = tp_base_call_channel_get_state (base);
/* if the call has been cancelled while we were waiting for the
* contact to do so, do nothing! */
if (call_state == TP_CALL_STATE_ENDED)
return FALSE;
g_message ("SIGNALLING: receive: call terminated: ");
example_call_channel_terminate (self, self->priv->handle,
TP_CHANNEL_GROUP_CHANGE_REASON_NONE,
TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "");
return FALSE;
}
static gboolean
simulate_contact_answered_cb (gpointer p)
{
ExampleCallChannel *self = p;
TpBaseCallChannel *base = (TpBaseCallChannel *) self;
TpCallState call_state = tp_base_call_channel_get_state (base);
GList *contents;
TpHandleRepoIface *contact_repo;
const gchar *peer;
/* if the call has been cancelled while we were waiting for the
* contact to answer, do nothing! */
if (call_state == TP_CALL_STATE_ENDED)
return FALSE;
/* otherwise, we're waiting for a response from the contact, which now
* arrives */
g_assert_cmpuint (call_state, ==, TP_CALL_STATE_INITIALISED);
g_message ("SIGNALLING: receive: contact answered our call");
tp_base_call_channel_remote_accept (base);
contents = tp_base_call_channel_get_contents (base);
for (; contents != NULL; contents = contents->next)
{
ExampleCallStream *stream = example_call_content_get_stream (contents->data);
if (stream == NULL)
continue;
/* remote contact accepts our proposed stream direction */
example_call_stream_simulate_contact_agreed_to_send (stream);
}
contact_repo = tp_base_connection_get_handles
(self->priv->conn, TP_HANDLE_TYPE_CONTACT);
peer = tp_handle_inspect (contact_repo, self->priv->handle);
/* If the contact's ID contains the magic string "(terminate)", simulate
* them hanging up after a moment. */
if (strstr (peer, "(terminate)") != NULL)
{
g_timeout_add_full (G_PRIORITY_DEFAULT,
self->priv->simulation_delay,
simulate_contact_ended_cb, g_object_ref (self),
g_object_unref);
}
return FALSE;
}
static gboolean
simulate_contact_busy_cb (gpointer p)
{
ExampleCallChannel *self = p;
TpBaseCallChannel *base = (TpBaseCallChannel *) self;
TpCallState call_state = tp_base_call_channel_get_state (base);
/* if the call has been cancelled while we were waiting for the
* contact to answer, do nothing */
if (call_state == TP_CALL_STATE_ENDED)
return FALSE;
/* otherwise, we're waiting for a response from the contact, which now
* arrives */
g_assert_cmpuint (call_state, ==, TP_CALL_STATE_INITIALISED);
g_message ("SIGNALLING: receive: call terminated: ");
example_call_channel_terminate (self, self->priv->handle,
TP_CHANNEL_GROUP_CHANGE_REASON_BUSY,
TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED,
TP_ERROR_STR_BUSY);
return FALSE;
}
static ExampleCallContent *
example_call_channel_add_content (ExampleCallChannel *self,
TpMediaStreamType media_type,
gboolean locally_requested,
gboolean initial,
const gchar *requested_name,
GError **error)
{
TpBaseCallChannel *base = (TpBaseCallChannel *) self;
GList *contents;
const gchar *type_str;
TpHandle creator = self->priv->handle;
TpCallContentDisposition disposition =
TP_CALL_CONTENT_DISPOSITION_NONE;
guint id = self->priv->next_stream_id++;
ExampleCallContent *content;
ExampleCallStream *stream;
gchar *name;
gchar *path;
guint i;
/* an arbitrary limit much less than 2**32 means we don't use ridiculous
* amounts of memory, and also means @i can't wrap around when we use it to
* uniquify content names. */
contents = tp_base_call_channel_get_contents (base);
if (g_list_length (contents) > MAX_CONTENTS_PER_CALL)
{
g_set_error (error, TP_ERROR, TP_ERROR_PERMISSION_DENIED,
"What are you doing with all those contents anyway?!");
return NULL;
}
type_str = (media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video");
if (tp_str_empty (requested_name))
{
requested_name = type_str;
}
for (i = 0; ; i++)
{
GList *l;
if (i == 0)
name = g_strdup (requested_name);
else
name = g_strdup_printf ("%s (%u)", requested_name, i);
for (l = contents; l != NULL; l = l->next)
{
if (!tp_strdiff (tp_base_call_content_get_name (l->data), name))
break;
}
if (l == NULL)
{
/* this name hasn't been used - good enough */
break;
}
g_free (name);
name = NULL;
}
if (initial)
disposition = TP_CALL_CONTENT_DISPOSITION_INITIAL;
if (locally_requested)
{
g_message ("SIGNALLING: send: new %s stream %s", type_str, name);
creator = tp_base_connection_get_self_handle (self->priv->conn);
}
path = g_strdup_printf ("%s/Content%u",
tp_base_channel_get_object_path ((TpBaseChannel *) self),
id);
content = g_object_new (EXAMPLE_TYPE_CALL_CONTENT,
"connection", self->priv->conn,
"creator", creator,
"media-type", media_type,
"name", name,
"disposition", disposition,
"object-path", path,
NULL);
tp_base_call_channel_add_content (base, (TpBaseCallContent *) content);
g_free (path);
path = g_strdup_printf ("%s/Stream%u",
tp_base_channel_get_object_path ((TpBaseChannel *) self),
id);
stream = g_object_new (EXAMPLE_TYPE_CALL_STREAM,
"connection", self->priv->conn,
"handle", self->priv->handle,
"locally-requested", locally_requested,
"object-path", path,
NULL);
example_call_content_add_stream (content, stream);
g_free (path);
g_object_unref (content);
g_object_unref (stream);
return content;
}
static gboolean
simulate_contact_ringing_cb (gpointer p)
{
ExampleCallChannel *self = p;
TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
(self->priv->conn, TP_HANDLE_TYPE_CONTACT);
const gchar *peer;
tp_base_call_channel_update_member_flags ((TpBaseCallChannel *) self,
self->priv->handle, TP_CALL_MEMBER_FLAG_RINGING,
0, TP_CALL_STATE_CHANGE_REASON_UNKNOWN, "", "");
/* In this example there is no real contact, so just simulate them
* answering after a short time - unless the contact's name
* contains "(no answer)" or "(busy)" */
peer = tp_handle_inspect (contact_repo, self->priv->handle);
if (strstr (peer, "(busy)") != NULL)
{
g_timeout_add_full (G_PRIORITY_DEFAULT,
self->priv->simulation_delay,
simulate_contact_busy_cb, g_object_ref (self),
g_object_unref);
}
else if (strstr (peer, "(no answer)") != NULL)
{
/* do nothing - the call just rings forever */
}
else
{
g_timeout_add_full (G_PRIORITY_DEFAULT,
self->priv->simulation_delay,
simulate_contact_answered_cb, g_object_ref (self),
g_object_unref);
}
return FALSE;
}
static void
example_call_channel_initiate_outgoing (ExampleCallChannel *self)
{
g_message ("SIGNALLING: send: new streamed media call");
example_call_channel_set_state (self,
TP_CALL_STATE_INITIALISED, 0,
tp_base_connection_get_self_handle (self->priv->conn),
TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
NULL);
/* After a moment, we're sent an informational message saying it's ringing */
g_timeout_add_full (G_PRIORITY_DEFAULT,
self->priv->simulation_delay,
simulate_contact_ringing_cb, g_object_ref (self),
g_object_unref);
}
static void
accept_incoming_call (ExampleCallChannel *self)
{
TpBaseCallChannel *base = (TpBaseCallChannel *) self;
TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
(self->priv->conn, TP_HANDLE_TYPE_CONTACT);
GList *contents;
g_message ("SIGNALLING: send: Accepting incoming call from %s",
tp_handle_inspect (contact_repo, self->priv->handle));
example_call_channel_set_state (self,
TP_CALL_STATE_ACCEPTED, 0,
tp_base_connection_get_self_handle (self->priv->conn),
TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "",
NULL);
contents = tp_base_call_channel_get_contents (base);
for (; contents != NULL; contents = contents->next)
{
ExampleCallStream *stream = example_call_content_get_stream (contents->data);
guint disposition = tp_base_call_content_get_disposition (contents->data);
if (stream == NULL || disposition != TP_CALL_CONTENT_DISPOSITION_INITIAL)
continue;
/* we accept the proposed stream direction */
example_call_stream_accept_proposed_direction (stream);
}
}
static void
call_accept (TpBaseCallChannel *base)
{
ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (base);
if (self->priv->locally_requested)
{
/* Take the contents we've already added, and make them happen */
example_call_channel_initiate_outgoing (self);
}
else
{
accept_incoming_call (self);
}
}
static void
call_hangup (TpBaseCallChannel *base,
guint reason,
const gchar *detailed_reason,
const gchar *message G_GNUC_UNUSED)
{
ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (base);
example_call_channel_terminate (self,
tp_base_connection_get_self_handle (self->priv->conn),
TP_CHANNEL_GROUP_CHANGE_REASON_NONE, reason, detailed_reason);
}
static TpBaseCallContent *
call_add_content (TpBaseCallChannel *base,
const gchar *content_name,
guint content_type,
TpMediaStreamDirection initial_direction,
GError **error)
{
ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (base);
return (TpBaseCallContent *) example_call_channel_add_content (self,
content_type, TRUE, FALSE, content_name, error);
}
static gboolean
simulate_hold (gpointer p)
{
ExampleCallChannel *self = p;
TpBaseCallChannel *base = (TpBaseCallChannel *) self;
TpCallState call_state = tp_base_call_channel_get_state (base);
TpCallFlags call_flags = 0; /* FIXME */
self->priv->hold_state = TP_LOCAL_HOLD_STATE_HELD;
g_message ("SIGNALLING: hold state changed to held");
tp_svc_channel_interface_hold_emit_hold_state_changed (self,
self->priv->hold_state, self->priv->hold_state_reason);
example_call_channel_set_state (self, call_state,
call_flags | TP_CALL_FLAG_LOCALLY_HELD,
tp_base_connection_get_self_handle (self->priv->conn),
TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", NULL);
return FALSE;
}
static gboolean
simulate_unhold (gpointer p)
{
ExampleCallChannel *self = p;
TpBaseCallChannel *base = (TpBaseCallChannel *) self;
TpCallState call_state = tp_base_call_channel_get_state (base);
TpCallFlags call_flags = 0; /* FIXME */
self->priv->hold_state = TP_LOCAL_HOLD_STATE_UNHELD;
g_message ("SIGNALLING: hold state changed to unheld");
tp_svc_channel_interface_hold_emit_hold_state_changed (self,
self->priv->hold_state, self->priv->hold_state_reason);
example_call_channel_set_state (self, call_state,
call_flags & ~TP_CALL_FLAG_LOCALLY_HELD,
tp_base_connection_get_self_handle (self->priv->conn),
TP_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", NULL);
return FALSE;
}
static gboolean
simulate_inability_to_unhold (gpointer p)
{
ExampleCallChannel *self = p;
self->priv->hold_state = TP_LOCAL_HOLD_STATE_PENDING_HOLD;
g_message ("SIGNALLING: unable to unhold - hold state changed to "
"pending hold");
tp_svc_channel_interface_hold_emit_hold_state_changed (self,
self->priv->hold_state, self->priv->hold_state_reason);
/* hold again */
g_timeout_add_full (G_PRIORITY_DEFAULT,
self->priv->simulation_delay,
simulate_hold, g_object_ref (self),
g_object_unref);
return FALSE;
}
static void
hold_get_hold_state (TpSvcChannelInterfaceHold *iface,
DBusGMethodInvocation *context)
{
ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (iface);
tp_svc_channel_interface_hold_return_from_get_hold_state (context,
self->priv->hold_state, self->priv->hold_state_reason);
}
static void
hold_request_hold (TpSvcChannelInterfaceHold *iface,
gboolean hold,
DBusGMethodInvocation *context)
{
ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (iface);
TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
(self->priv->conn, TP_HANDLE_TYPE_CONTACT);
GError *error = NULL;
const gchar *peer;
GSourceFunc callback;
if ((hold && self->priv->hold_state == TP_LOCAL_HOLD_STATE_HELD) ||
(!hold && self->priv->hold_state == TP_LOCAL_HOLD_STATE_UNHELD))
{
tp_svc_channel_interface_hold_return_from_request_hold (context);
return;
}
peer = tp_handle_inspect (contact_repo, self->priv->handle);
if (!hold && strstr (peer, "(no unhold)") != NULL)
{
g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
"unable to unhold");
goto error;
}
self->priv->hold_state_reason = TP_LOCAL_HOLD_STATE_REASON_REQUESTED;
if (hold)
{
self->priv->hold_state = TP_LOCAL_HOLD_STATE_PENDING_HOLD;
callback = simulate_hold;
}
else
{
self->priv->hold_state = TP_LOCAL_HOLD_STATE_PENDING_UNHOLD;
peer = tp_handle_inspect (contact_repo, self->priv->handle);
if (strstr (peer, "(inability to unhold)") != NULL)
{
callback = simulate_inability_to_unhold;
}
else
{
callback = simulate_unhold;
}
}
g_message ("SIGNALLING: hold state changed to pending %s",
(hold ? "hold" : "unhold"));
tp_svc_channel_interface_hold_emit_hold_state_changed (iface,
self->priv->hold_state, self->priv->hold_state_reason);
/* No need to change the call flags - we never change the actual hold state
* here, only the pending hold state */
g_timeout_add_full (G_PRIORITY_DEFAULT,
self->priv->simulation_delay,
callback, g_object_ref (self),
g_object_unref);
tp_svc_channel_interface_hold_return_from_request_hold (context);
return;
error:
dbus_g_method_return_error (context, error);
g_error_free (error);
}
void
hold_iface_init (gpointer iface,
gpointer data)
{
TpSvcChannelInterfaceHoldClass *klass = iface;
#define IMPLEMENT(x) \
tp_svc_channel_interface_hold_implement_##x (klass, hold_##x)
IMPLEMENT (get_hold_state);
IMPLEMENT (request_hold);
#undef IMPLEMENT
}