summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Claessens <xavier.claessens@collabora.co.uk>2012-01-20 13:19:04 +0100
committerXavier Claessens <xavier.claessens@collabora.co.uk>2012-01-27 10:12:11 +0100
commit8ad70c119f10d5c4f2603f01d488e1c65b3d144f (patch)
treea658d11830cec307d89618621fde704da55d3540
parentbe92c904d473f42f6f85066bf3535125fb4f0cce (diff)
TpCallChannel, TpCallContent: add _send_tones_async()
tp_call_channel_send_tones_async() is helper API calling tp_call_content_send_tones_async() on each of its contents supporting the DTMF interfaces. tp_call_content_send_tones_async() does the queuing of events
-rw-r--r--docs/reference/telepathy-glib-sections.txt5
-rw-r--r--telepathy-glib/call-channel.c103
-rw-r--r--telepathy-glib/call-channel.h9
-rw-r--r--telepathy-glib/call-content.c246
-rw-r--r--telepathy-glib/call-content.h9
5 files changed, 372 insertions, 0 deletions
diff --git a/docs/reference/telepathy-glib-sections.txt b/docs/reference/telepathy-glib-sections.txt
index 2d0954294..7dbd5bdb8 100644
--- a/docs/reference/telepathy-glib-sections.txt
+++ b/docs/reference/telepathy-glib-sections.txt
@@ -6466,6 +6466,8 @@ tp_call_channel_has_mutable_contents
tp_call_channel_get_members
<SUBSECTION>
tp_call_channel_has_dtmf
+tp_call_channel_send_tones_async
+tp_call_channel_send_tones_finish
<SUBSECTION>
tp_call_channel_set_ringing_async
tp_call_channel_set_ringing_finish
@@ -6569,6 +6571,9 @@ tp_call_content_get_streams
<SUBSECTION>
tp_call_content_remove_async
tp_call_content_remove_finish
+<SUBSECTION>
+tp_call_content_send_tones_async
+tp_call_content_send_tones_finish
<SUBSECTION Standard>
TpCallContentPrivate
tp_call_content_get_type
diff --git a/telepathy-glib/call-channel.c b/telepathy-glib/call-channel.c
index 8b2e9dde2..a73903c92 100644
--- a/telepathy-glib/call-channel.c
+++ b/telepathy-glib/call-channel.c
@@ -1513,3 +1513,106 @@ tp_call_channel_add_content_finish (TpCallChannel *self,
_tp_implement_finish_return_copy_pointer (self,
tp_call_channel_add_content_async, g_object_ref);
}
+
+static void
+send_tones_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result = user_data;
+ guint count;
+ GError *error = NULL;
+
+ if (!tp_call_content_send_tones_finish ((TpCallContent *) source, res,
+ &error))
+ g_simple_async_result_take_error (result, error);
+
+ /* Decrement the op count */
+ count = GPOINTER_TO_UINT (g_simple_async_result_get_op_res_gpointer (result));
+ g_simple_async_result_set_op_res_gpointer (result, GUINT_TO_POINTER (--count),
+ NULL);
+
+ if (count == 0)
+ g_simple_async_result_complete (result);
+
+ g_object_unref (result);
+}
+
+/**
+ * tp_call_channel_send_tones_async:
+ * @self: a #TpCallChannel
+ * @tones: a string representation of one or more DTMF events.
+ * @cancellable: optional #GCancellable object, %NULL to ignore
+ * @callback: a callback to call when the operation finishes
+ * @user_data: data to pass to @callback
+ *
+ * Send @tones on every of @self's contents which have the
+ * %TP_IFACE_CALL_CONTENT_INTERFACE_DTMF interface.
+ *
+ * For more details, see tp_call_content_send_tones_async().
+ *
+ * Since: 0.UNRELEASED
+ */
+void
+tp_call_channel_send_tones_async (TpCallChannel *self,
+ const gchar *tones,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+ guint i;
+ guint count = 0;
+
+ g_return_if_fail (TP_IS_CALL_CHANNEL (self));
+
+ result = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ tp_call_channel_send_tones_async);
+
+ for (i = 0; i < self->priv->contents->len; i++)
+ {
+ TpCallContent *content = g_ptr_array_index (self->priv->contents, i);
+
+ if (!tp_proxy_has_interface_by_id (content,
+ TP_IFACE_QUARK_CALL_CONTENT_INTERFACE_DTMF))
+ continue;
+
+ count++;
+ tp_call_content_send_tones_async (content, tones, cancellable,
+ send_tones_cb, g_object_ref (result));
+ }
+
+ if (count == 0)
+ {
+ g_simple_async_result_set_error (result,
+ TP_ERRORS, TP_ERROR_NOT_CAPABLE,
+ "Channel has no content implementing DTMF interface");
+ g_simple_async_result_complete_in_idle (result);
+ }
+ else
+ {
+ g_simple_async_result_set_op_res_gpointer (result,
+ GUINT_TO_POINTER (count), NULL);
+ }
+
+ g_object_unref (result);
+}
+
+/**
+ * tp_call_channel_send_tones_finish:
+ * @self: a #TpCallChannel
+ * @result: a #GAsyncResult
+ * @error: a #GError to fill
+ *
+ * Finishes tp_call_channel_send_tones_async().
+ *
+ * Returns: %TRUE on success, %FALSE otherwise.
+ * Since: 0.UNRELEASED
+ */
+gboolean
+tp_call_channel_send_tones_finish (TpCallChannel *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ _tp_implement_finish_void (self, tp_call_channel_send_tones_async)
+}
diff --git a/telepathy-glib/call-channel.h b/telepathy-glib/call-channel.h
index 580d07b42..a48da5152 100644
--- a/telepathy-glib/call-channel.h
+++ b/telepathy-glib/call-channel.h
@@ -128,6 +128,15 @@ TpCallContent *tp_call_channel_add_content_finish (TpCallChannel *self,
GAsyncResult *result,
GError **error);
+void tp_call_channel_send_tones_async (TpCallChannel *self,
+ const gchar *tones,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean tp_call_channel_send_tones_finish (TpCallChannel *self,
+ GAsyncResult *result,
+ GError **error);
+
G_END_DECLS
#endif
diff --git a/telepathy-glib/call-content.c b/telepathy-glib/call-content.c
index a76d8675e..3d80a48a6 100644
--- a/telepathy-glib/call-content.c
+++ b/telepathy-glib/call-content.c
@@ -50,6 +50,7 @@
#include <telepathy-glib/call-misc.h>
#include <telepathy-glib/call-stream.h>
#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/dtmf.h>
#include <telepathy-glib/enums.h>
#include <telepathy-glib/errors.h>
#include <telepathy-glib/gtypes.h>
@@ -68,6 +69,8 @@
G_DEFINE_TYPE (TpCallContent, tp_call_content, TP_TYPE_PROXY)
+typedef struct _SendTonesData SendTonesData;
+
struct _TpCallContentPrivate
{
TpConnection *connection;
@@ -78,6 +81,9 @@ struct _TpCallContentPrivate
GPtrArray *streams;
gboolean properties_retrieved;
+
+ GQueue *tones_queue;
+ SendTonesData *current_tones;
};
enum
@@ -242,6 +248,158 @@ got_all_properties_cb (TpProxy *proxy,
_tp_proxy_set_feature_prepared (proxy, TP_CALL_CONTENT_FEATURE_CORE, TRUE);
}
+struct _SendTonesData
+{
+ TpCallContent *content;
+ gchar *tones;
+ GSimpleAsyncResult *result;
+ GCancellable *cancellable;
+ guint cancel_id;
+};
+
+static void maybe_send_tones (TpCallContent *self);
+static void send_tones_cancelled_cb (GCancellable *cancellable,
+ SendTonesData *data);
+
+static SendTonesData *
+send_tones_data_new (TpCallContent *self,
+ const gchar *tones,
+ GSimpleAsyncResult *result,
+ GCancellable *cancellable)
+{
+ SendTonesData *data;
+
+ data = g_slice_new0 (SendTonesData);
+ data->content = g_object_ref (self);
+ data->tones = g_strdup (tones);
+ data->result = g_object_ref (result);
+
+ if (cancellable != NULL)
+ {
+ data->cancellable = g_object_ref (cancellable);
+ data->cancel_id = g_cancellable_connect (cancellable,
+ G_CALLBACK (send_tones_cancelled_cb), data, NULL);
+ }
+
+ return data;
+}
+
+static void
+send_tones_data_free (SendTonesData *data)
+{
+ g_free (data->tones);
+ g_object_unref (data->result);
+ g_object_unref (data->content);
+
+ if (data->cancellable != NULL)
+ {
+ if (data->cancel_id != 0)
+ g_cancellable_disconnect (data->cancellable, data->cancel_id);
+
+ g_object_unref (data->cancellable);
+ }
+
+ g_slice_free (SendTonesData, data);
+}
+
+static gboolean
+send_tones_cancelled_idle_cb (gpointer user_data)
+{
+ SendTonesData *data = user_data;
+ TpCallContent *self = data->content;
+
+ /* If it is the tone currently being played, stop it. Otherwise wait for its
+ * turn in the queue to preserve order. */
+ if (self->priv->current_tones == data)
+ {
+ tp_cli_call_content_interface_dtmf_call_stop_tone (self, -1,
+ NULL, NULL, NULL, NULL);
+ }
+
+ return FALSE;
+}
+
+static void
+send_tones_cancelled_cb (GCancellable *cancellable,
+ SendTonesData *data)
+{
+ /* Cancel in idle for thread-safeness */
+ g_idle_add (send_tones_cancelled_idle_cb, data);
+}
+
+static void
+complete_sending_tones (TpCallContent *self,
+ const GError *error)
+{
+ if (self->priv->current_tones == NULL)
+ return;
+
+ if (error != NULL)
+ {
+ g_simple_async_result_set_from_error (self->priv->current_tones->result,
+ error);
+ }
+
+ g_simple_async_result_complete (self->priv->current_tones->result);
+
+ send_tones_data_free (self->priv->current_tones);
+ self->priv->current_tones = NULL;
+
+ maybe_send_tones (self);
+}
+
+static void
+tones_stopped_cb (TpCallContent *self,
+ gboolean cancelled,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ if (cancelled)
+ {
+ GError e = { TP_ERRORS, TP_ERROR_CANCELLED,
+ "The DTMF tones were actively cancelled via StopTones" };
+ complete_sending_tones (self, &e);
+ return;
+ }
+
+ complete_sending_tones (self, NULL);
+}
+
+static void
+multiple_tones_cb (TpCallContent *self,
+ const GError *error,
+ gpointer user_data,
+ GObject *weak_object)
+{
+ if (error != NULL)
+ complete_sending_tones (self, error);
+}
+
+static void
+maybe_send_tones (TpCallContent *self)
+{
+ if (self->priv->current_tones != NULL)
+ return;
+
+ if (g_queue_is_empty (self->priv->tones_queue))
+ return;
+
+ self->priv->current_tones = g_queue_pop_head (self->priv->tones_queue);
+
+ /* Yes this is safe if cancellable is NULL! */
+ if (g_cancellable_is_cancelled (self->priv->current_tones->cancellable))
+ {
+ GError e = { TP_ERRORS, TP_ERROR_CANCELLED,
+ "The DTMF tones were cancelled before it has started" };
+ complete_sending_tones (self, &e);
+ return;
+ }
+
+ DEBUG ("Emitting multiple tones: %s", self->priv->current_tones->tones);
+ tp_cli_call_content_interface_dtmf_call_multiple_tones (self, -1,
+ self->priv->current_tones->tones, multiple_tones_cb, NULL, NULL, NULL);
+}
+
static void
tp_call_content_constructed (GObject *obj)
{
@@ -255,6 +413,13 @@ tp_call_content_constructed (GObject *obj)
tp_cli_call_content_connect_to_streams_removed (self,
streams_removed_cb, NULL, NULL, G_OBJECT (self), NULL);
+ if (tp_proxy_has_interface_by_id (self,
+ TP_IFACE_QUARK_CALL_CONTENT_INTERFACE_DTMF))
+ {
+ tp_cli_call_content_interface_dtmf_connect_to_stopped_tones (self,
+ tones_stopped_cb, NULL, NULL, NULL, NULL);
+ }
+
tp_cli_dbus_properties_call_get_all (self, -1,
TP_IFACE_CALL_CONTENT,
got_all_properties_cb, NULL, NULL, G_OBJECT (self));
@@ -273,6 +438,19 @@ tp_call_content_dispose (GObject *object)
}
static void
+tp_call_content_finalize (GObject *object)
+{
+ TpCallContent *self = (TpCallContent *) object;
+
+ /* Results hold a ref on self, finalize can't happen if queue isn't empty */
+ g_assert (self->priv->current_tones == NULL);
+ g_assert (g_queue_is_empty (self->priv->tones_queue));
+ g_queue_free (self->priv->tones_queue);
+
+ G_OBJECT_CLASS (tp_call_content_parent_class)->finalize (object);
+}
+
+static void
tp_call_content_get_property (GObject *object,
guint property_id,
GValue *value,
@@ -357,6 +535,7 @@ tp_call_content_class_init (TpCallContentClass *klass)
gobject_class->get_property = tp_call_content_get_property;
gobject_class->set_property = tp_call_content_set_property;
gobject_class->dispose = tp_call_content_dispose;
+ gobject_class->finalize = tp_call_content_finalize;
proxy_class->list_features = tp_call_content_list_features;
proxy_class->interface = TP_IFACE_QUARK_CALL_CONTENT;
@@ -515,6 +694,7 @@ tp_call_content_init (TpCallContent *self)
TpCallContentPrivate);
self->priv->streams = g_ptr_array_new_with_free_func (g_object_unref);
+ self->priv->tones_queue = g_queue_new ();
}
/**
@@ -693,3 +873,69 @@ tp_call_content_remove_finish (TpCallContent *self,
{
_tp_implement_finish_void (self, tp_call_content_remove_async);
}
+
+/**
+ * tp_call_content_send_tones_async:
+ * @self: a #TpCallContent
+ * @tones: a string representation of one or more DTMF events.
+ * @cancellable: optional #GCancellable object, %NULL to ignore
+ * @callback: a callback to call when the operation finishes
+ * @user_data: data to pass to @callback
+ *
+ * Send @tones DTMF code on @self content. @self must have the
+ * %TP_IFACE_CALL_CONTENT_INTERFACE_DTMF interface.
+ *
+ * If DTMF tones are already being played, this request is queued.
+ *
+ * Since: 0.UNRELEASED
+ */
+void
+tp_call_content_send_tones_async (TpCallContent *self,
+ const gchar *tones,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+ SendTonesData *data;
+
+ g_return_if_fail (TP_IS_CALL_CONTENT (self));
+
+ if (!tp_proxy_has_interface_by_id (self,
+ TP_IFACE_QUARK_CALL_CONTENT_INTERFACE_DTMF))
+ {
+ g_simple_async_report_error_in_idle (G_OBJECT (self),
+ callback, user_data, TP_ERRORS, TP_ERROR_NOT_CAPABLE,
+ "Content does not support DTMF");
+ return;
+ }
+
+ result = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ tp_call_content_send_tones_async);
+
+ data = send_tones_data_new (self, tones, result, cancellable);
+ g_queue_push_tail (self->priv->tones_queue, data);
+
+ maybe_send_tones (self);
+
+ g_object_unref (result);
+}
+
+/**
+ * tp_call_content_send_tones_finish:
+ * @self: a #TpCallContent
+ * @result: a #GAsyncResult
+ * @error: a #GError to fill
+ *
+ * Finishes tp_call_content_send_tones_async().
+ *
+ * Returns: %TRUE on success, %FALSE otherwise.
+ * Since: 0.UNRELEASED
+ */
+gboolean
+tp_call_content_send_tones_finish (TpCallContent *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ _tp_implement_finish_void (self, tp_call_content_send_tones_async);
+}
diff --git a/telepathy-glib/call-content.h b/telepathy-glib/call-content.h
index 4d55f9670..468485f89 100644
--- a/telepathy-glib/call-content.h
+++ b/telepathy-glib/call-content.h
@@ -71,6 +71,15 @@ gboolean tp_call_content_remove_finish (TpCallContent *self,
GAsyncResult *result,
GError **error);
+void tp_call_content_send_tones_async (TpCallContent *self,
+ const gchar *tones,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean tp_call_content_send_tones_finish (TpCallContent *self,
+ GAsyncResult *result,
+ GError **error);
+
G_END_DECLS
#include "_gen/tp-cli-call-content.h"