diff options
author | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2010-02-22 18:10:09 +0000 |
---|---|---|
committer | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2010-02-22 18:10:09 +0000 |
commit | bc62ca21f71fc6a84cad4f5c4e105e5c0870e6e3 (patch) | |
tree | 1b6c90d0c3a9dc5cadc6a71185a64a49051f2842 | |
parent | 0fdda7da58232f3bacded364ad6cd9c61db9c2a8 (diff) | |
parent | 7bc9ce497b1ad511c90e20ce2672d2df1e4b45e7 (diff) | |
download | telepathy-glib-bc62ca21f71fc6a84cad4f5c4e105e5c0870e6e3.tar.gz |
Merge branch 'from-the-future'
42 files changed, 8309 insertions, 2 deletions
diff --git a/.gitignore b/.gitignore index 81660359e..192b34ae8 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,8 @@ docs/reference/xml examples/client/telepathy-example-* examples/cm/*/telepathy-example-* examples/extensions/extensions.html +/examples/future/*/telepathy-example-* +/extensions/extensions.html gtk-doc.make install-sh lcov.info diff --git a/Makefile.am b/Makefile.am index f9d0158e8..0b6ab8db5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,14 @@ ACLOCAL_AMFLAGS = -I m4 -SUBDIRS = m4 tools spec telepathy-glib examples tests docs +SUBDIRS = \ + m4 \ + tools \ + spec \ + telepathy-glib \ + extensions \ + examples \ + tests \ + docs DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc --disable-debug diff --git a/configure.ac b/configure.ac index 809331a79..3e772d05f 100644 --- a/configure.ac +++ b/configure.ac @@ -210,6 +210,9 @@ AC_OUTPUT( Makefile \ examples/cm/extended/Makefile \ examples/cm/no-protocols/Makefile \ examples/extensions/Makefile \ + examples/future/Makefile \ + examples/future/call-cm/Makefile \ + extensions/Makefile \ spec/Makefile \ telepathy-glib/Makefile \ telepathy-glib/telepathy-glib.pc \ diff --git a/examples/Makefile.am b/examples/Makefile.am index e8fe94091..fde86c064 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -1,4 +1,4 @@ # extensions has to be compiled first, because other examples need it. -SUBDIRS = extensions client cm +SUBDIRS = extensions client cm future EXTRA_DIST = README diff --git a/examples/future/Makefile.am b/examples/future/Makefile.am new file mode 100644 index 000000000..c7ae472ce --- /dev/null +++ b/examples/future/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = call-cm diff --git a/examples/future/call-cm/Makefile.am b/examples/future/call-cm/Makefile.am new file mode 100644 index 000000000..c5ee64513 --- /dev/null +++ b/examples/future/call-cm/Makefile.am @@ -0,0 +1,71 @@ +# Example connection manager with audio/video calls. + +EXAMPLES = telepathy-example-cm-call +noinst_LTLIBRARIES = libexample-cm-call.la + +if INSTALL_EXAMPLES +libexec_PROGRAMS = $(EXAMPLES) +else +noinst_PROGRAMS = $(EXAMPLES) +endif + +libexample_cm_call_la_SOURCES = \ + cm.c \ + cm.h \ + conn.c \ + conn.h \ + call-channel.c \ + call-channel.h \ + call-manager.c \ + call-manager.h \ + call-content.c \ + call-content.h \ + call-stream.c \ + call-stream.h + +# In an external project you'd use $(TP_GLIB_LIBS) (obtained from +# pkg-config via autoconf) instead of the .la path +libexample_cm_call_la_LIBADD = \ + $(GLIB_LIBS) \ + $(DBUS_LIBS) \ + $(top_builddir)/telepathy-glib/libtelepathy-glib.la \ + $(top_builddir)/extensions/libfuture-extensions.la + +telepathy_example_cm_call_SOURCES = \ + main.c + +telepathy_example_cm_call_LDADD = \ + $(noinst_LTLIBRARIES) + +AM_CFLAGS = \ + $(ERROR_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(TP_GLIB_CFLAGS) + +EXTRA_DIST = manager-file.py + +servicedir = ${datadir}/dbus-1/services + +if INSTALL_EXAMPLES +service_DATA = _gen/org.freedesktop.Telepathy.ConnectionManager.example_call.service +$(service_DATA): %: Makefile + $(mkdir_p) _gen + { echo "[D-BUS Service]" && \ + echo "Name=org.freedesktop.Telepathy.ConnectionManager.example_call" && \ + echo "Exec=${libexecdir}/telepathy-example-cm-call"; } > $@ + +managerdir = ${datadir}/telepathy/managers +manager_DATA = _gen/example_call.manager +endif + +_gen/example_call.manager _gen/param-spec-struct.h: \ + manager-file.py $(top_srcdir)/tools/manager-file.py + $(mkdir_p) _gen + $(PYTHON) $(top_srcdir)/tools/manager-file.py $(srcdir)/manager-file.py _gen + +BUILT_SOURCES = _gen/param-spec-struct.h +CLEANFILES = $(BUILT_SOURCES) + +clean-local: + rm -rf _gen diff --git a/examples/future/call-cm/call-channel.c b/examples/future/call-cm/call-channel.c new file mode 100644 index 000000000..e46dd07dc --- /dev/null +++ b/examples/future/call-cm/call-channel.c @@ -0,0 +1,1624 @@ +/* + * 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. <http://www.collabora.co.uk/> + * 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 "call-channel.h" + +#include <string.h> + +#include <gobject/gvaluecollector.h> + +#include <telepathy-glib/base-connection.h> +#include <telepathy-glib/channel-iface.h> +#include <telepathy-glib/svc-channel.h> +#include <telepathy-glib/telepathy-glib.h> + +#include "extensions/extensions.h" + +#include "call-content.h" +#include "call-stream.h" + +static void call_iface_init (gpointer iface, gpointer data); +static void channel_iface_init (gpointer iface, gpointer data); +static void hold_iface_init (gpointer iface, gpointer data); + +G_DEFINE_TYPE_WITH_CODE (ExampleCallChannel, + example_call_channel, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, + tp_dbus_properties_mixin_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init); + G_IMPLEMENT_INTERFACE (FUTURE_TYPE_SVC_CHANNEL_TYPE_CALL, + call_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_HOLD, + hold_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL); + G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL)) + +enum +{ + PROP_OBJECT_PATH = 1, + PROP_CHANNEL_TYPE, + PROP_HANDLE_TYPE, + PROP_HANDLE, + PROP_TARGET_ID, + PROP_REQUESTED, + PROP_INITIATOR_HANDLE, + PROP_INITIATOR_ID, + PROP_CONNECTION, + PROP_INTERFACES, + PROP_CHANNEL_DESTROYED, + PROP_CHANNEL_PROPERTIES, + PROP_SIMULATION_DELAY, + PROP_INITIAL_AUDIO, + PROP_INITIAL_VIDEO, + PROP_CONTENT_PATHS, + PROP_CALL_STATE, + PROP_CALL_FLAGS, + PROP_CALL_STATE_REASON, + PROP_CALL_STATE_DETAILS, + PROP_HARDWARE_STREAMING, + PROP_CALL_MEMBERS, + PROP_INITIAL_TRANSPORT, + PROP_MUTABLE_CONTENTS, + N_PROPS +}; + +struct _ExampleCallChannelPrivate +{ + TpBaseConnection *conn; + gchar *object_path; + TpHandle handle; + TpHandle initiator; + + FutureCallState call_state; + FutureCallFlags call_flags; + GValueArray *call_state_reason; + GHashTable *call_state_details; + FutureCallMemberFlags peer_flags; + + guint simulation_delay; + + guint next_stream_id; + + /* strdup'd name => referenced ExampleCallContent */ + GHashTable *contents; + + guint hold_state; + guint hold_state_reason; + + gboolean locally_requested; + gboolean initial_audio; + gboolean initial_video; + gboolean disposed; + gboolean closed; +}; + +static const char * example_call_channel_interfaces[] = { + TP_IFACE_CHANNEL_INTERFACE_HOLD, + NULL +}; + +/* 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, + FutureCallState state, + FutureCallFlags flags, + TpHandle actor, + FutureCallStateChangeReason reason, + const gchar *error, + ...) +{ + TpHandleRepoIface *contact_handles = tp_base_connection_get_handles + (self->priv->conn, TP_HANDLE_TYPE_CONTACT); + TpHandle old_actor; + const gchar *key; + va_list va; + + self->priv->call_state = state; + self->priv->call_flags = flags; + + old_actor = g_value_get_uint (self->priv->call_state_reason->values + 0); + + if (actor != 0) + tp_handle_ref (contact_handles, actor); + + if (old_actor != 0) + tp_handle_unref (contact_handles, old_actor); + + g_value_set_uint (self->priv->call_state_reason->values + 0, actor); + g_value_set_uint (self->priv->call_state_reason->values + 1, reason); + g_value_set_string (self->priv->call_state_reason->values + 2, error); + + g_hash_table_remove_all (self->priv->call_state_details); + + va_start (va, error); + + /* This is basically tp_asv_new_va(), but that doesn't exist yet + * (and when it does, we still won't want to use it in this example + * just yet, because examples shouldn't use unreleased API) */ + for (key = va_arg (va, const gchar *); + key != NULL; + key = va_arg (va, const gchar *)) + { + GType type = va_arg (va, GType); + GValue *value = tp_g_value_slice_new (type); + gchar *collect_error = NULL; + + G_VALUE_COLLECT (value, va, 0, &collect_error); + + if (collect_error != NULL) + { + g_critical ("key %s: %s", key, collect_error); + g_free (collect_error); + collect_error = NULL; + tp_g_value_slice_free (value); + continue; + } + + g_hash_table_insert (self->priv->call_state_details, + (gchar *) key, value); + } + + va_end (va); + + future_svc_channel_type_call_emit_call_state_changed (self, + self->priv->call_state, self->priv->call_flags, + self->priv->call_state_reason, self->priv->call_state_details); +} + +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->contents = g_hash_table_new_full (g_str_hash, + g_str_equal, g_free, g_object_unref); + + self->priv->call_state = FUTURE_CALL_STATE_UNKNOWN; /* set in constructed */ + self->priv->call_flags = 0; + self->priv->call_state_reason = tp_value_array_build (3, + G_TYPE_UINT, 0, /* actor */ + G_TYPE_UINT, FUTURE_CALL_STATE_CHANGE_REASON_UNKNOWN, + G_TYPE_STRING, "", + G_TYPE_INVALID); + self->priv->call_state_details = tp_asv_new ( + NULL, NULL); + + 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); + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles + (self->priv->conn, TP_HANDLE_TYPE_CONTACT); + TpDBusDaemon *dbus_daemon; + + if (chain_up != NULL) + chain_up (object); + + dbus_daemon = tp_dbus_daemon_dup (NULL); + g_return_if_fail (dbus_daemon != NULL); + + tp_handle_ref (contact_repo, self->priv->handle); + tp_handle_ref (contact_repo, self->priv->initiator); + + dbus_g_connection_register_g_object ( + tp_proxy_get_dbus_connection (dbus_daemon), + self->priv->object_path, object); + + g_object_unref (dbus_daemon); + dbus_daemon = NULL; + + 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, + FUTURE_CALL_STATE_PENDING_INITIATOR, 0, 0, + FUTURE_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, + FUTURE_CALL_STATE_PENDING_RECEIVER, 0, self->priv->handle, + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", + NULL); + } + + if (self->priv->locally_requested) + { + if (self->priv->initial_audio) + { + g_message ("Channel initially has an audio stream"); + example_call_channel_add_content (self, + TP_MEDIA_STREAM_TYPE_AUDIO, TRUE, TRUE, NULL, NULL); + } + + if (self->priv->initial_video) + { + g_message ("Channel initially has a video stream"); + example_call_channel_add_content (self, + TP_MEDIA_STREAM_TYPE_VIDEO, TRUE, TRUE, NULL, NULL); + } + } + else + { + /* the caller has almost certainly asked us for some streams - there's + * not much point in having a call otherwise */ + + if (self->priv->initial_audio) + { + g_message ("Channel initially has an audio stream"); + example_call_channel_add_content (self, + TP_MEDIA_STREAM_TYPE_AUDIO, FALSE, TRUE, NULL, NULL); + } + + if (self->priv->initial_video) + { + g_message ("Channel initially has a video stream"); + example_call_channel_add_content (self, + TP_MEDIA_STREAM_TYPE_VIDEO, FALSE, TRUE, NULL, NULL); + } + } +} + +static void +get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (object); + + switch (property_id) + { + case PROP_OBJECT_PATH: + g_value_set_string (value, self->priv->object_path); + break; + + case PROP_CHANNEL_TYPE: + g_value_set_static_string (value, FUTURE_IFACE_CHANNEL_TYPE_CALL); + break; + + case PROP_HANDLE_TYPE: + g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT); + break; + + case PROP_HANDLE: + g_value_set_uint (value, self->priv->handle); + break; + + case PROP_TARGET_ID: + { + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( + self->priv->conn, TP_HANDLE_TYPE_CONTACT); + + g_value_set_string (value, + tp_handle_inspect (contact_repo, self->priv->handle)); + } + break; + + case PROP_REQUESTED: + g_value_set_boolean (value, self->priv->locally_requested); + break; + + case PROP_INITIATOR_HANDLE: + g_value_set_uint (value, self->priv->initiator); + break; + + case PROP_INITIATOR_ID: + { + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( + self->priv->conn, TP_HANDLE_TYPE_CONTACT); + + g_value_set_string (value, + tp_handle_inspect (contact_repo, self->priv->initiator)); + } + break; + + case PROP_CONNECTION: + g_value_set_object (value, self->priv->conn); + break; + + case PROP_INTERFACES: + g_value_set_boxed (value, example_call_channel_interfaces); + break; + + case PROP_CHANNEL_DESTROYED: + g_value_set_boolean (value, + (self->priv->call_state == FUTURE_CALL_STATE_ENDED)); + break; + + case PROP_CHANNEL_PROPERTIES: + g_value_take_boxed (value, + tp_dbus_properties_mixin_make_properties_hash (object, + TP_IFACE_CHANNEL, "ChannelType", + TP_IFACE_CHANNEL, "TargetHandleType", + TP_IFACE_CHANNEL, "TargetHandle", + TP_IFACE_CHANNEL, "TargetID", + TP_IFACE_CHANNEL, "InitiatorHandle", + TP_IFACE_CHANNEL, "InitiatorID", + TP_IFACE_CHANNEL, "Requested", + TP_IFACE_CHANNEL, "Interfaces", + NULL)); + break; + + case PROP_SIMULATION_DELAY: + g_value_set_uint (value, self->priv->simulation_delay); + break; + + case PROP_INITIAL_AUDIO: + g_value_set_boolean (value, self->priv->initial_audio); + break; + + case PROP_INITIAL_VIDEO: + g_value_set_boolean (value, self->priv->initial_video); + break; + + case PROP_CONTENT_PATHS: + { + GPtrArray *paths = g_ptr_array_sized_new (g_hash_table_size ( + self->priv->contents)); + GHashTableIter iter; + gpointer v; + + g_hash_table_iter_init (&iter, self->priv->contents); + + while (g_hash_table_iter_next (&iter, NULL, &v)) + { + gchar *path; + + g_object_get (v, + "object-path", &path, + NULL); + + g_ptr_array_add (paths, path); + } + + g_value_take_boxed (value, paths); + } + break; + + case PROP_CALL_STATE: + g_value_set_uint (value, self->priv->call_state); + break; + + case PROP_CALL_FLAGS: + g_value_set_uint (value, self->priv->call_flags); + break; + + case PROP_CALL_STATE_REASON: + g_value_set_boxed (value, self->priv->call_state_reason); + break; + + case PROP_CALL_STATE_DETAILS: + g_value_set_boxed (value, self->priv->call_state_details); + break; + + case PROP_HARDWARE_STREAMING: + /* yes, this implementation has hardware streaming */ + g_value_set_boolean (value, TRUE); + break; + + case PROP_MUTABLE_CONTENTS: + /* yes, this implementation can add contents */ + g_value_set_boolean (value, TRUE); + break; + + case PROP_INITIAL_TRANSPORT: + /* this implementation has hardware_streaming, so the initial + * transport is rather meaningless */ + g_value_set_static_string (value, ""); + break; + + case PROP_CALL_MEMBERS: + { + GHashTable *uu_map = g_hash_table_new (NULL, NULL); + + /* There is one contact, other than the self-handle. */ + g_hash_table_insert (uu_map, GUINT_TO_POINTER (self->priv->handle), + GUINT_TO_POINTER (self->priv->peer_flags)); + g_value_take_boxed (value, uu_map); + } + 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_OBJECT_PATH: + g_assert (self->priv->object_path == NULL); /* construct-only */ + self->priv->object_path = g_value_dup_string (value); + break; + + case PROP_HANDLE: + /* we don't ref it here because we don't necessarily have access to the + * contact repo yet - instead we ref it in the constructor. + */ + self->priv->handle = g_value_get_uint (value); + break; + + case PROP_INITIATOR_HANDLE: + /* likewise */ + self->priv->initiator = g_value_get_uint (value); + break; + + case PROP_REQUESTED: + self->priv->locally_requested = g_value_get_boolean (value); + break; + + case PROP_HANDLE_TYPE: + case PROP_CHANNEL_TYPE: + /* these properties are writable in the interface, but not actually + * meaningfully changable on this channel, so we do nothing */ + break; + + case PROP_CONNECTION: + self->priv->conn = g_value_get_object (value); + break; + + case PROP_SIMULATION_DELAY: + self->priv->simulation_delay = g_value_get_uint (value); + break; + + case PROP_INITIAL_AUDIO: + self->priv->initial_audio = g_value_get_boolean (value); + break; + + case PROP_INITIAL_VIDEO: + self->priv->initial_video = g_value_get_boolean (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, + FutureCallStateChangeReason call_reason, + const gchar *error_name) +{ + if (self->priv->call_state != FUTURE_CALL_STATE_ENDED) + { + GList *values; + GHashTable *empty_uu_map = g_hash_table_new (NULL, NULL); + GArray *au = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1); + + example_call_channel_set_state (self, + FUTURE_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. */ + g_array_append_val (au, self->priv->handle); + future_svc_channel_type_call_emit_call_members_changed (self, + empty_uu_map, au); + g_hash_table_unref (empty_uu_map); + g_array_free (au, TRUE); + + 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 = "<user-is-busy/>"; + break; + + case TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER: + send_reason = "<no-answer/>"; + break; + + default: + send_reason = "<call-terminated/>"; + } + + g_message ("SIGNALLING: send: Terminating call: %s", send_reason); + } + + /* terminate all streams: to avoid modifying the hash table (in the + * stream-removed handler) while iterating over it, we have to copy the + * keys and iterate over those */ + values = g_hash_table_get_values (self->priv->contents); + g_list_foreach (values, (GFunc) g_object_ref, NULL); + + for (; values != NULL; values = g_list_delete_link (values, values)) + { + ExampleCallStream *stream = + example_call_content_get_stream (values->data); + + if (stream != NULL) + example_call_stream_close (stream); + + g_object_unref (values->data); + } + } +} + +void +example_call_channel_disconnected (ExampleCallChannel *self) +{ + example_call_channel_terminate (self, 0, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE, + FUTURE_CALL_STATE_CHANGE_REASON_UNKNOWN, + TP_ERROR_STR_DISCONNECTED); + + if (!self->priv->closed) + { + self->priv->closed = TRUE; + tp_svc_channel_emit_closed (self); + } +} + +static void +dispose (GObject *object) +{ + ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (object); + + if (self->priv->disposed) + return; + + self->priv->disposed = TRUE; + + g_hash_table_destroy (self->priv->contents); + self->priv->contents = NULL; + + /* FIXME: right error code? arguably this should always be a no-op */ + example_call_channel_terminate (self, + tp_base_connection_get_self_handle (self->priv->conn), + TP_CHANNEL_GROUP_CHANGE_REASON_NONE, + FUTURE_CALL_STATE_CHANGE_REASON_UNKNOWN, ""); + + /* 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 +finalize (GObject *object) +{ + ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (object); + TpHandleRepoIface *contact_handles = tp_base_connection_get_handles + (self->priv->conn, TP_HANDLE_TYPE_CONTACT); + + g_value_array_free (self->priv->call_state_reason); + g_hash_table_destroy (self->priv->call_state_details); + + tp_handle_unref (contact_handles, self->priv->handle); + tp_handle_unref (contact_handles, self->priv->initiator); + + g_free (self->priv->object_path); + + ((GObjectClass *) example_call_channel_parent_class)->finalize (object); +} + +static void +example_call_channel_class_init (ExampleCallChannelClass *klass) +{ + static TpDBusPropertiesMixinPropImpl channel_props[] = { + { "TargetHandleType", "handle-type", NULL }, + { "TargetHandle", "handle", NULL }, + { "ChannelType", "channel-type", NULL }, + { "Interfaces", "interfaces", NULL }, + { "TargetID", "target-id", NULL }, + { "Requested", "requested", NULL }, + { "InitiatorHandle", "initiator-handle", NULL }, + { "InitiatorID", "initiator-id", NULL }, + { NULL } + }; + static TpDBusPropertiesMixinPropImpl call_props[] = { + { "Contents", "content-paths", NULL }, + { "CallState", "call-state", NULL }, + { "CallFlags", "call-flags", NULL }, + { "CallStateReason", "call-state-reason", NULL }, + { "CallStateDetails", "call-state-details", NULL }, + { "HardwareStreaming", "hardware-streaming", NULL }, + { "CallMembers", "call-members", NULL }, + { "InitialTransport", "initial-transport", NULL }, + { "InitialAudio", "initial-audio", NULL }, + { "InitialVideo", "initial-video", NULL }, + { "MutableContents", "mutable-contents", NULL }, + { NULL } + }; + static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { + { TP_IFACE_CHANNEL, + tp_dbus_properties_mixin_getter_gobject_properties, + NULL, + channel_props, + }, + { FUTURE_IFACE_CHANNEL_TYPE_CALL, + tp_dbus_properties_mixin_getter_gobject_properties, + NULL, + call_props, + }, + { NULL } + }; + GObjectClass *object_class = (GObjectClass *) klass; + GParamSpec *param_spec; + + g_type_class_add_private (klass, + sizeof (ExampleCallChannelPrivate)); + + object_class->constructed = constructed; + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + g_object_class_override_property (object_class, PROP_OBJECT_PATH, + "object-path"); + g_object_class_override_property (object_class, PROP_CHANNEL_TYPE, + "channel-type"); + g_object_class_override_property (object_class, PROP_HANDLE_TYPE, + "handle-type"); + g_object_class_override_property (object_class, PROP_HANDLE, "handle"); + + g_object_class_override_property (object_class, PROP_CHANNEL_DESTROYED, + "channel-destroyed"); + g_object_class_override_property (object_class, PROP_CHANNEL_PROPERTIES, + "channel-properties"); + + param_spec = g_param_spec_object ("connection", "TpBaseConnection object", + "Connection object that owns this channel", + TP_TYPE_BASE_CONNECTION, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); + + param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces", + "Additional Channel.Interface.* interfaces", + G_TYPE_STRV, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INTERFACES, param_spec); + + param_spec = g_param_spec_string ("target-id", "Peer's ID", + "The string obtained by inspecting the target handle", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec); + + param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle", + "The contact who initiated the channel", + 0, G_MAXUINT32, 0, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE, + param_spec); + + param_spec = g_param_spec_string ("initiator-id", "Initiator's ID", + "The string obtained by inspecting the initiator-handle", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INITIATOR_ID, + param_spec); + + param_spec = g_param_spec_boolean ("requested", "Requested?", + "True if this channel was requested by the local user", + FALSE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_REQUESTED, param_spec); + + 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); + + param_spec = g_param_spec_boolean ("initial-audio", "Initial audio?", + "True if this channel had an audio stream when first announced", + FALSE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INITIAL_AUDIO, + param_spec); + + param_spec = g_param_spec_boolean ("initial-video", "Initial video?", + "True if this channel had a video stream when first announced", + FALSE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INITIAL_VIDEO, + param_spec); + + param_spec = g_param_spec_boxed ("content-paths", "Content paths", + "A list of the object paths of contents", + TP_ARRAY_TYPE_OBJECT_PATH_LIST, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONTENT_PATHS, + param_spec); + + param_spec = g_param_spec_uint ("call-state", "Call state", + "High-level state of the call", + 0, NUM_FUTURE_CALL_STATES - 1, FUTURE_CALL_STATE_PENDING_INITIATOR, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CALL_STATE, + param_spec); + + param_spec = g_param_spec_uint ("call-flags", "Call flags", + "Flags for additional sub-states", + 0, G_MAXUINT32, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CALL_FLAGS, + param_spec); + + param_spec = g_param_spec_boxed ("call-state-reason", "Call state reason", + "Reason for call-state and call-flags", + FUTURE_STRUCT_TYPE_CALL_STATE_REASON, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CALL_STATE_REASON, + param_spec); + + param_spec = g_param_spec_boxed ("call-state-details", "Call state details", + "Additional details of the call state/flags/reason", + TP_HASH_TYPE_STRING_VARIANT_MAP, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CALL_STATE_DETAILS, + param_spec); + + param_spec = g_param_spec_boolean ("hardware-streaming", + "Hardware streaming?", + "True if this channel does all of its own streaming (it does)", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_HARDWARE_STREAMING, + param_spec); + + param_spec = g_param_spec_boxed ("call-members", "Call members", + "A map from call members (only one in this example) to their states", + FUTURE_HASH_TYPE_CALL_MEMBER_MAP, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CALL_MEMBERS, + param_spec); + + param_spec = g_param_spec_string ("initial-transport", "Initial transport", + "The initial transport for this channel (there is none)", + "", + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_INITIAL_TRANSPORT, + param_spec); + + param_spec = g_param_spec_boolean ("mutable-contents", "Mutable contents?", + "True if contents can be added to this channel (they can)", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_MUTABLE_CONTENTS, + param_spec); + + klass->dbus_properties_class.interfaces = prop_interfaces; + tp_dbus_properties_mixin_class_init (object_class, + G_STRUCT_OFFSET (ExampleCallChannelClass, + dbus_properties_class)); +} + +static void +channel_close (TpSvcChannel *iface, + DBusGMethodInvocation *context) +{ + ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (iface); + + example_call_channel_terminate (self, + tp_base_connection_get_self_handle (self->priv->conn), + TP_CHANNEL_GROUP_CHANGE_REASON_NONE, + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, ""); + + if (!self->priv->closed) + { + self->priv->closed = TRUE; + tp_svc_channel_emit_closed (self); + } + + tp_svc_channel_return_from_close (context); +} + +static void +channel_get_channel_type (TpSvcChannel *iface G_GNUC_UNUSED, + DBusGMethodInvocation *context) +{ + tp_svc_channel_return_from_get_channel_type (context, + FUTURE_IFACE_CHANNEL_TYPE_CALL); +} + +static void +channel_get_handle (TpSvcChannel *iface, + DBusGMethodInvocation *context) +{ + ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (iface); + + tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT, + self->priv->handle); +} + +static void +channel_get_interfaces (TpSvcChannel *iface G_GNUC_UNUSED, + DBusGMethodInvocation *context) +{ + tp_svc_channel_return_from_get_interfaces (context, + example_call_channel_interfaces); +} + +static void +channel_iface_init (gpointer iface, + gpointer data) +{ + TpSvcChannelClass *klass = iface; + +#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, channel_##x) + IMPLEMENT (close); + IMPLEMENT (get_channel_type); + IMPLEMENT (get_handle); + IMPLEMENT (get_interfaces); +#undef IMPLEMENT +} + +#if 0 +/* FIXME: there's no equivalent of this in Call (yet?) */ + +/* This is expressed in terms of streams because it's the old API, but it + * really means removing contents. */ +static void +media_remove_streams (TpSvcChannelTypeStreamedMedia *iface, + const GArray *stream_ids, + DBusGMethodInvocation *context) +{ + ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (iface); + guint i; + + for (i = 0; i < stream_ids->len; i++) + { + guint id = g_array_index (stream_ids, guint, i); + + if (g_hash_table_lookup (self->priv->contents, + GUINT_TO_POINTER (id)) == NULL) + { + GError *error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "No stream with ID %u in this channel", id); + + dbus_g_method_return_error (context, error); + g_error_free (error); + return; + } + } + + for (i = 0; i < stream_ids->len; i++) + { + guint id = g_array_index (stream_ids, guint, i); + ExampleCallContent *content = + g_hash_table_lookup (self->priv->contents, GUINT_TO_POINTER (id)); + ExampleCallStream *stream = example_call_content_get_stream (content); + + if (stream != NULL) + example_call_stream_close (stream); + } + + tp_svc_channel_type_streamed_media_return_from_remove_streams (context); +} +#endif + +static void +stream_removed_cb (ExampleCallContent *content, + const gchar *stream_path G_GNUC_UNUSED, + ExampleCallChannel *self) +{ + gchar *path, *name; + + /* Contents in this example CM can only have one stream, so if their + * stream disappears, the content has to be removed too. */ + + g_object_get (content, + "object-path", &path, + "name", &name, + NULL); + + g_hash_table_remove (self->priv->contents, name); + + future_svc_channel_type_call_emit_content_removed (self, path); + g_free (path); + g_free (name); + + if (g_hash_table_size (self->priv->contents) == 0) + { + /* no contents left, so the call terminates */ + example_call_channel_terminate (self, 0, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE, + FUTURE_CALL_STATE_CHANGE_REASON_UNKNOWN, ""); + /* FIXME: is there an appropriate error? */ + } +} + +static gboolean +simulate_contact_ended_cb (gpointer p) +{ + ExampleCallChannel *self = p; + + /* if the call has been cancelled while we were waiting for the + * contact to do so, do nothing! */ + if (self->priv->call_state == FUTURE_CALL_STATE_ENDED) + return FALSE; + + g_message ("SIGNALLING: receive: call terminated: <call-terminated/>"); + + example_call_channel_terminate (self, self->priv->handle, + TP_CHANNEL_GROUP_CHANGE_REASON_NONE, + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, ""); + + return FALSE; +} + +static gboolean +simulate_contact_answered_cb (gpointer p) +{ + ExampleCallChannel *self = p; + GHashTableIter iter; + gpointer v; + TpHandleRepoIface *contact_repo; + const gchar *peer; + + /* if the call has been cancelled while we were waiting for the + * contact to answer, do nothing! */ + if (self->priv->call_state == FUTURE_CALL_STATE_ENDED) + return FALSE; + + /* otherwise, we're waiting for a response from the contact, which now + * arrives */ + g_assert (self->priv->call_state == FUTURE_CALL_STATE_PENDING_RECEIVER); + + g_message ("SIGNALLING: receive: contact answered our call"); + + example_call_channel_set_state (self, + FUTURE_CALL_STATE_ACCEPTED, 0, self->priv->handle, + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", + NULL); + + g_hash_table_iter_init (&iter, self->priv->contents); + + while (g_hash_table_iter_next (&iter, NULL, &v)) + { + ExampleCallStream *stream = example_call_content_get_stream (v); + + 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; + + /* if the call has been cancelled while we were waiting for the + * contact to answer, do nothing */ + if (self->priv->call_state == FUTURE_CALL_STATE_ENDED) + return FALSE; + + /* otherwise, we're waiting for a response from the contact, which now + * arrives */ + g_assert (self->priv->call_state == FUTURE_CALL_STATE_PENDING_RECEIVER); + + g_message ("SIGNALLING: receive: call terminated: <user-is-busy/>"); + + example_call_channel_terminate (self, self->priv->handle, + TP_CHANNEL_GROUP_CHANGE_REASON_BUSY, + FUTURE_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) +{ + ExampleCallContent *content; + ExampleCallStream *stream; + guint id = self->priv->next_stream_id++; + const gchar *type_str; + TpHandle creator; + gchar *name; + gchar *path; + FutureCallContentDisposition disposition = + FUTURE_CALL_CONTENT_DISPOSITION_NONE; + guint i; + + type_str = (media_type == TP_MEDIA_STREAM_TYPE_AUDIO ? "audio" : "video"); + creator = self->priv->handle; + + /* 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. */ + if (g_hash_table_size (self->priv->contents) > MAX_CONTENTS_PER_CALL) + { + g_set_error (error, TP_ERRORS, TP_ERROR_PERMISSION_DENIED, + "What are you doing with all those contents anyway?!"); + return NULL; + } + + if (requested_name == NULL || requested_name[0] == '\0') + { + requested_name = type_str; + } + + for (i = 0; ; i++) + { + if (i == 0) + name = g_strdup (requested_name); + else + name = g_strdup_printf ("%s (%u)", requested_name, i); + + if (!g_hash_table_lookup_extended (self->priv->contents, name, + NULL, NULL)) + { + /* this name hasn't been used - good enough */ + break; + } + + g_free (name); + name = NULL; + } + + if (initial) + disposition = FUTURE_CALL_CONTENT_DISPOSITION_INITIAL; + + if (locally_requested) + { + g_message ("SIGNALLING: send: new %s stream %s", type_str, name); + creator = self->priv->conn->self_handle; + } + + path = g_strdup_printf ("%s/Content%u", self->priv->object_path, id); + content = g_object_new (EXAMPLE_TYPE_CALL_CONTENT, + "connection", self->priv->conn, + "creator", creator, + "type", media_type, + "name", name, + "disposition", disposition, + "object-path", path, + NULL); + + g_hash_table_insert (self->priv->contents, name, content); + future_svc_channel_type_call_emit_content_added (self, path, media_type); + g_free (path); + + path = g_strdup_printf ("%s/Stream%u", self->priv->object_path, 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); + + tp_g_signal_connect_object (content, "stream-removed", + G_CALLBACK (stream_removed_cb), self, 0); + + 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; + GHashTable *uu_map = g_hash_table_new (NULL, NULL); + GArray *empty_au = g_array_sized_new (FALSE, FALSE, sizeof (guint), 0); + + /* ring, ring! */ + self->priv->peer_flags = FUTURE_CALL_MEMBER_FLAG_RINGING; + g_hash_table_insert (uu_map, GUINT_TO_POINTER (self->priv->handle), + GUINT_TO_POINTER (self->priv->peer_flags)); + future_svc_channel_type_call_emit_call_members_changed (self, + uu_map, empty_au); + g_hash_table_unref (uu_map); + g_array_free (empty_au, TRUE); + + + /* 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, + FUTURE_CALL_STATE_PENDING_RECEIVER, 0, + tp_base_connection_get_self_handle (self->priv->conn), + FUTURE_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 +call_ringing (FutureSvcChannelTypeCall *iface, + DBusGMethodInvocation *context) +{ + ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (iface); + GError *error = NULL; + + if (self->priv->locally_requested) + { + g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "Ringing() makes no sense on an outgoing call"); + goto finally; + } + + if (self->priv->call_state != FUTURE_CALL_STATE_PENDING_RECEIVER) + { + g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "Ringing() makes no sense now that we're not pending receiver"); + goto finally; + } + + g_message ("SIGNALLING: send: ring, ring!"); + + example_call_channel_set_state (self, FUTURE_CALL_STATE_PENDING_RECEIVER, + self->priv->call_flags | FUTURE_CALL_FLAG_LOCALLY_RINGING, + tp_base_connection_get_self_handle (self->priv->conn), + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", NULL); + +finally: + if (error == NULL) + { + future_svc_channel_type_call_return_from_ringing (context); + } + else + { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +static void +accept_incoming_call (ExampleCallChannel *self) +{ + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles + (self->priv->conn, TP_HANDLE_TYPE_CONTACT); + GHashTableIter iter; + gpointer v; + + g_assert (self->priv->call_state == FUTURE_CALL_STATE_PENDING_RECEIVER); + + g_message ("SIGNALLING: send: Accepting incoming call from %s", + tp_handle_inspect (contact_repo, self->priv->handle)); + + example_call_channel_set_state (self, + FUTURE_CALL_STATE_ACCEPTED, 0, + tp_base_connection_get_self_handle (self->priv->conn), + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", + NULL); + + g_hash_table_iter_init (&iter, self->priv->contents); + + while (g_hash_table_iter_next (&iter, NULL, &v)) + { + ExampleCallStream *stream = example_call_content_get_stream (v); + guint disposition; + + g_object_get (v, + "disposition", &disposition, + NULL); + + if (stream == NULL || + disposition != FUTURE_CALL_CONTENT_DISPOSITION_INITIAL) + continue; + + /* we accept the proposed stream direction */ + example_call_stream_accept_proposed_direction (stream); + } +} + +static void +call_accept (FutureSvcChannelTypeCall *iface G_GNUC_UNUSED, + DBusGMethodInvocation *context) +{ + ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (iface); + + if (self->priv->locally_requested) + { + if (self->priv->call_state == FUTURE_CALL_STATE_PENDING_INITIATOR) + { + /* Take the contents we've already added, and make them happen */ + example_call_channel_initiate_outgoing (self); + + future_svc_channel_type_call_return_from_accept (context); + } + else if (self->priv->call_state == FUTURE_CALL_STATE_ENDED) + { + GError na = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "This call has already ended" }; + + dbus_g_method_return_error (context, &na); + } + else + { + GError na = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "This outgoing call has already been started" }; + + dbus_g_method_return_error (context, &na); + } + } + else + { + if (self->priv->call_state == FUTURE_CALL_STATE_PENDING_RECEIVER) + { + accept_incoming_call (self); + future_svc_channel_type_call_return_from_accept (context); + } + else if (self->priv->call_state == FUTURE_CALL_STATE_ENDED) + { + GError na = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "This call has already ended" }; + + dbus_g_method_return_error (context, &na); + } + else + { + GError na = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "This incoming call has already been accepted" }; + + dbus_g_method_return_error (context, &na); + } + } +} + +static void +call_hangup (FutureSvcChannelTypeCall *iface, + guint reason, + const gchar *detailed_reason, + const gchar *message G_GNUC_UNUSED, + DBusGMethodInvocation *context) +{ + ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (iface); + + if (self->priv->call_state == FUTURE_CALL_STATE_ENDED) + { + GError na = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, + "This call has already ended" }; + + dbus_g_method_return_error (context, &na); + return; + } + else + { + example_call_channel_terminate (self, + tp_base_connection_get_self_handle (self->priv->conn), + TP_CHANNEL_GROUP_CHANGE_REASON_NONE, reason, detailed_reason); + future_svc_channel_type_call_return_from_hangup (context); + } +} + +static void +call_add_content (FutureSvcChannelTypeCall *iface, + const gchar *content_name, + guint content_type, + DBusGMethodInvocation *context) +{ + ExampleCallChannel *self = EXAMPLE_CALL_CHANNEL (iface); + GError *error = NULL; + gchar *content_path; + ExampleCallContent *content; + + switch (content_type) + { + case TP_MEDIA_STREAM_TYPE_AUDIO: + case TP_MEDIA_STREAM_TYPE_VIDEO: + break; + + default: + g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "%u is not a supported Media_Stream_Type", content_type); + goto error; + } + + content = example_call_channel_add_content (self, content_type, TRUE, FALSE, + content_name, &error); + + if (content == NULL) + goto error; + + g_object_get (content, + "object-path", &content_path, + NULL); + future_svc_channel_type_call_return_from_add_content (context, + content_path); + g_free (content_path); + + return; + +error: + dbus_g_method_return_error (context, error); + g_error_free (error); +} + +static void +call_iface_init (gpointer iface, + gpointer data) +{ + FutureSvcChannelTypeCallClass *klass = iface; + +#define IMPLEMENT(x) \ + future_svc_channel_type_call_implement_##x (klass, call_##x) + IMPLEMENT (ringing); + IMPLEMENT (hangup); + IMPLEMENT (accept); + IMPLEMENT (add_content); +#undef IMPLEMENT +} + +static gboolean +simulate_hold (gpointer p) +{ + ExampleCallChannel *self = p; + + 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, self->priv->call_state, + self->priv->call_flags | FUTURE_CALL_FLAG_LOCALLY_HELD, + tp_base_connection_get_self_handle (self->priv->conn), + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", NULL); + + return FALSE; +} + +static gboolean +simulate_unhold (gpointer p) +{ + ExampleCallChannel *self = p; + + 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, self->priv->call_state, + self->priv->call_flags & ~FUTURE_CALL_FLAG_LOCALLY_HELD, + tp_base_connection_get_self_handle (self->priv->conn), + FUTURE_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_ERRORS, 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 +} diff --git a/examples/future/call-cm/call-channel.h b/examples/future/call-cm/call-channel.h new file mode 100644 index 000000000..26b9c85a5 --- /dev/null +++ b/examples/future/call-cm/call-channel.h @@ -0,0 +1,74 @@ +/* + * call-channel.h - header for an example channel + * + * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * 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 + */ + +#ifndef EXAMPLE_CALL_CHANNEL_H +#define EXAMPLE_CALL_CHANNEL_H + +#include <glib-object.h> +#include <telepathy-glib/telepathy-glib.h> + +G_BEGIN_DECLS + +typedef struct _ExampleCallChannel ExampleCallChannel; +typedef struct _ExampleCallChannelPrivate + ExampleCallChannelPrivate; + +typedef struct _ExampleCallChannelClass + ExampleCallChannelClass; +typedef struct _ExampleCallChannelClassPrivate + ExampleCallChannelClassPrivate; + +GType example_call_channel_get_type (void); + +#define EXAMPLE_TYPE_CALL_CHANNEL \ + (example_call_channel_get_type ()) +#define EXAMPLE_CALL_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CALL_CHANNEL, \ + ExampleCallChannel)) +#define EXAMPLE_CALL_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CALL_CHANNEL, \ + ExampleCallChannelClass)) +#define EXAMPLE_IS_CALL_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CALL_CHANNEL)) +#define EXAMPLE_IS_CALL_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CALL_CHANNEL)) +#define EXAMPLE_CALL_CHANNEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALL_CHANNEL, \ + ExampleCallChannelClass)) + +struct _ExampleCallChannelClass { + GObjectClass parent_class; + TpDBusPropertiesMixinClass dbus_properties_class; + + ExampleCallChannelClassPrivate *priv; +}; + +struct _ExampleCallChannel { + GObject parent; + + ExampleCallChannelPrivate *priv; +}; + +void example_call_channel_disconnected (ExampleCallChannel *self); + +G_END_DECLS + +#endif diff --git a/examples/future/call-cm/call-content.c b/examples/future/call-cm/call-content.c new file mode 100644 index 000000000..b6097acef --- /dev/null +++ b/examples/future/call-cm/call-content.c @@ -0,0 +1,369 @@ +/* + * call-content.c - a content in a call. + * + * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * 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 "call-content.h" + +#include <telepathy-glib/base-connection.h> +#include <telepathy-glib/telepathy-glib.h> + +#include "extensions/extensions.h" + +G_DEFINE_TYPE_WITH_CODE (ExampleCallContent, + example_call_content, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, + tp_dbus_properties_mixin_iface_init); + /* no methods, so no vtable needed */ + G_IMPLEMENT_INTERFACE (FUTURE_TYPE_SVC_CALL_CONTENT, NULL)) + +enum +{ + PROP_OBJECT_PATH = 1, + PROP_CONNECTION, + PROP_NAME, + PROP_TYPE, + PROP_CREATOR, + PROP_DISPOSITION, + PROP_STREAM_PATHS, + N_PROPS +}; + +struct _ExampleCallContentPrivate +{ + gchar *object_path; + TpBaseConnection *conn; + gchar *name; + TpMediaStreamType type; + TpHandle creator; + FutureCallContentDisposition disposition; + ExampleCallStream *stream; +}; + +static void +example_call_content_init (ExampleCallContent *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + EXAMPLE_TYPE_CALL_CONTENT, + ExampleCallContentPrivate); +} + +static void +constructed (GObject *object) +{ + ExampleCallContent *self = EXAMPLE_CALL_CONTENT (object); + void (*chain_up) (GObject *) = + ((GObjectClass *) example_call_content_parent_class)->constructed; + TpHandleRepoIface *contact_repo; + TpDBusDaemon *dbus_daemon; + + if (chain_up != NULL) + chain_up (object); + + dbus_daemon = tp_dbus_daemon_dup (NULL); + g_return_if_fail (dbus_daemon != NULL); + + dbus_g_connection_register_g_object ( + tp_proxy_get_dbus_connection (dbus_daemon), + self->priv->object_path, object); + + g_object_unref (dbus_daemon); + dbus_daemon = NULL; + + contact_repo = tp_base_connection_get_handles (self->priv->conn, + TP_HANDLE_TYPE_CONTACT); + tp_handle_ref (contact_repo, self->priv->creator); +} + +static void +get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + ExampleCallContent *self = EXAMPLE_CALL_CONTENT (object); + + switch (property_id) + { + case PROP_OBJECT_PATH: + g_value_set_string (value, self->priv->object_path); + break; + + case PROP_CONNECTION: + g_value_set_object (value, self->priv->conn); + break; + + case PROP_NAME: + g_value_set_string (value, self->priv->name); + break; + + case PROP_CREATOR: + g_value_set_uint (value, self->priv->creator); + break; + + case PROP_TYPE: + g_value_set_uint (value, self->priv->type); + break; + + case PROP_DISPOSITION: + g_value_set_uint (value, self->priv->disposition); + break; + + case PROP_STREAM_PATHS: + { + GPtrArray *paths = g_ptr_array_sized_new (1); + + if (self->priv->stream != NULL) + { + gchar *path; + + g_object_get (self->priv->stream, + "object-path", &path, + NULL); + + g_ptr_array_add (paths, path); + } + + g_value_take_boxed (value, paths); + } + 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) +{ + ExampleCallContent *self = EXAMPLE_CALL_CONTENT (object); + + switch (property_id) + { + case PROP_OBJECT_PATH: + g_assert (self->priv->object_path == NULL); /* construct-only */ + self->priv->object_path = g_value_dup_string (value); + break; + + case PROP_CONNECTION: + g_assert (self->priv->conn == NULL); + self->priv->conn = g_value_dup_object (value); + break; + + case PROP_CREATOR: + /* we don't ref it here because we don't necessarily have access to the + * contact repo yet - instead we ref it in the constructor. + */ + g_assert (self->priv->creator == 0); /* construct-only */ + self->priv->creator = g_value_get_uint (value); + break; + + case PROP_NAME: + g_assert (self->priv->name == NULL); /* construct-only */ + self->priv->name = g_value_dup_string (value); + break; + + case PROP_TYPE: + self->priv->type = g_value_get_uint (value); + break; + + case PROP_DISPOSITION: + self->priv->disposition = g_value_get_uint (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + ExampleCallContent *self = EXAMPLE_CALL_CONTENT (object); + + if (self->priv->stream != NULL) + { + g_object_unref (self->priv->stream); + self->priv->stream = NULL; + } + + if (self->priv->conn != NULL) + { + TpHandleRepoIface *contact_handles = tp_base_connection_get_handles + (self->priv->conn, TP_HANDLE_TYPE_CONTACT); + + tp_handle_unref (contact_handles, self->priv->creator); + self->priv->creator = 0; + + g_object_unref (self->priv->conn); + self->priv->conn = NULL; + } + + ((GObjectClass *) example_call_content_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + ExampleCallContent *self = EXAMPLE_CALL_CONTENT (object); + void (*chain_up) (GObject *) = + ((GObjectClass *) example_call_content_parent_class)->finalize; + + g_free (self->priv->object_path); + g_free (self->priv->name); + + if (chain_up != NULL) + chain_up (object); +} + +static void +example_call_content_class_init (ExampleCallContentClass *klass) +{ + static TpDBusPropertiesMixinPropImpl content_props[] = { + { "Name", "name", NULL }, + { "Type", "type", NULL }, + { "Creator", "creator", NULL }, + { "Disposition", "disposition", NULL }, + { "Streams", "stream-paths", NULL }, + { NULL } + }; + static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { + { FUTURE_IFACE_CALL_CONTENT, + tp_dbus_properties_mixin_getter_gobject_properties, + NULL, + content_props, + }, + { NULL } + }; + GObjectClass *object_class = (GObjectClass *) klass; + GParamSpec *param_spec; + + g_type_class_add_private (klass, + sizeof (ExampleCallContentPrivate)); + + object_class->constructed = constructed; + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + param_spec = g_param_spec_string ("object-path", "D-Bus object path", + "The D-Bus object path used for this object on the bus.", NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec); + + param_spec = g_param_spec_object ("connection", "TpBaseConnection", + "Connection that owns this content", + TP_TYPE_BASE_CONNECTION, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); + + param_spec = g_param_spec_uint ("type", "TpMediaStreamType", + "Media stream type", + 0, NUM_TP_MEDIA_STREAM_TYPES - 1, 0, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_TYPE, param_spec); + + param_spec = g_param_spec_uint ("disposition", + "FutureCallContentDisposition", + "Disposition of the content", + 0, NUM_FUTURE_CALL_CONTENT_DISPOSITIONS - 1, + FUTURE_CALL_CONTENT_DISPOSITION_NONE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_DISPOSITION, param_spec); + + param_spec = g_param_spec_uint ("creator", "Creator's handle", + "The contact who initiated this content", + 0, G_MAXUINT32, 0, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CREATOR, param_spec); + + param_spec = g_param_spec_string ("name", "Content name", + "The name of the content", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_NAME, param_spec); + + param_spec = g_param_spec_boxed ("stream-paths", "Stream paths", + "Streams' object paths", + TP_ARRAY_TYPE_OBJECT_PATH_LIST, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_STREAM_PATHS, + param_spec); + + klass->dbus_properties_class.interfaces = prop_interfaces; + tp_dbus_properties_mixin_class_init (object_class, + G_STRUCT_OFFSET (ExampleCallContentClass, + dbus_properties_class)); +} + +ExampleCallStream * +example_call_content_get_stream (ExampleCallContent *self) +{ + g_return_val_if_fail (EXAMPLE_IS_CALL_CONTENT (self), NULL); + return self->priv->stream; +} + +static void +example_call_content_stream_removed_cb (ExampleCallContent *self, + ExampleCallStream *stream) +{ + gchar *path; + + g_return_if_fail (EXAMPLE_IS_CALL_CONTENT (self)); + g_return_if_fail (EXAMPLE_IS_CALL_STREAM (stream)); + g_return_if_fail (self->priv->stream == stream); + + g_object_get (stream, + "object-path", &path, + NULL); + future_svc_call_content_emit_stream_removed (self, path); + g_free (path); + + g_object_unref (self->priv->stream); + self->priv->stream = NULL; +} + +void +example_call_content_add_stream (ExampleCallContent *self, + ExampleCallStream *stream) +{ + gchar *path; + + g_return_if_fail (EXAMPLE_IS_CALL_CONTENT (self)); + g_return_if_fail (EXAMPLE_IS_CALL_STREAM (stream)); + g_return_if_fail (self->priv->stream == NULL); + + self->priv->stream = g_object_ref (stream); + g_object_get (stream, + "object-path", &path, + NULL); + future_svc_call_content_emit_stream_added (self, path); + g_free (path); + + tp_g_signal_connect_object (stream, "removed", + G_CALLBACK (example_call_content_stream_removed_cb), self, + G_CONNECT_SWAPPED); +} diff --git a/examples/future/call-cm/call-content.h b/examples/future/call-cm/call-content.h new file mode 100644 index 000000000..4425dfa23 --- /dev/null +++ b/examples/future/call-cm/call-content.h @@ -0,0 +1,81 @@ +/* + * call-content.h - header for an example content + * + * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * 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 + */ + +#ifndef EXAMPLE_CALL_CONTENT_H +#define EXAMPLE_CALL_CONTENT_H + +#include <glib-object.h> + +#include <telepathy-glib/telepathy-glib.h> + +#include "call-stream.h" + +G_BEGIN_DECLS + +typedef struct _ExampleCallContent ExampleCallContent; +typedef struct _ExampleCallContentPrivate + ExampleCallContentPrivate; + +typedef struct _ExampleCallContentClass + ExampleCallContentClass; +typedef struct _ExampleCallContentClassPrivate + ExampleCallContentClassPrivate; + +GType example_call_content_get_type (void); + +#define EXAMPLE_TYPE_CALL_CONTENT \ + (example_call_content_get_type ()) +#define EXAMPLE_CALL_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CALL_CONTENT, \ + ExampleCallContent)) +#define EXAMPLE_CALL_CONTENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CALL_CONTENT, \ + ExampleCallContentClass)) +#define EXAMPLE_IS_CALL_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CALL_CONTENT)) +#define EXAMPLE_IS_CALL_CONTENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CALL_CONTENT)) +#define EXAMPLE_CALL_CONTENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALL_CONTENT, \ + ExampleCallContentClass)) + +struct _ExampleCallContentClass { + GObjectClass parent_class; + TpDBusPropertiesMixinClass dbus_properties_class; + + ExampleCallContentClassPrivate *priv; +}; + +struct _ExampleCallContent { + GObject parent; + + ExampleCallContentPrivate *priv; +}; + +/* In this example, each content can only have one stream. */ +ExampleCallStream *example_call_content_get_stream (ExampleCallContent *self); + +void example_call_content_add_stream (ExampleCallContent *self, + ExampleCallStream *stream); + +G_END_DECLS + +#endif diff --git a/examples/future/call-cm/call-manager.c b/examples/future/call-cm/call-manager.c new file mode 100644 index 000000000..9bb34a5a2 --- /dev/null +++ b/examples/future/call-cm/call-manager.c @@ -0,0 +1,537 @@ +/* + * call-manager.c - an example channel manager for Call channels. + * + * This channel manager emulates a protocol like XMPP Jingle, where you can + * make several simultaneous calls to the same or different contacts. + * + * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * 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 "call-manager.h" + +#include <dbus/dbus-glib.h> + +#include <telepathy-glib/base-connection.h> +#include <telepathy-glib/channel-manager.h> +#include <telepathy-glib/dbus.h> +#include <telepathy-glib/errors.h> +#include <telepathy-glib/interfaces.h> + +#include "extensions/extensions.h" + +#include "call-channel.h" + +static void channel_manager_iface_init (gpointer, gpointer); + +G_DEFINE_TYPE_WITH_CODE (ExampleCallManager, + example_call_manager, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER, + channel_manager_iface_init)) + +/* type definition stuff */ + +enum +{ + PROP_CONNECTION = 1, + PROP_SIMULATION_DELAY, + N_PROPS +}; + +struct _ExampleCallManagerPrivate +{ + TpBaseConnection *conn; + guint simulation_delay; + + /* Map from reffed ExampleCallChannel to the same pointer; used as a + * set. + */ + GHashTable *channels; + + /* Next channel will be ("CallChannel%u", next_channel_index) */ + guint next_channel_index; + + gulong status_changed_id; + gulong available_id; +}; + +static void +example_call_manager_init (ExampleCallManager *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + EXAMPLE_TYPE_CALL_MANAGER, + ExampleCallManagerPrivate); + + self->priv->conn = NULL; + self->priv->channels = g_hash_table_new_full (NULL, NULL, g_object_unref, + NULL); + self->priv->status_changed_id = 0; + self->priv->available_id = 0; +} + +static void +example_call_manager_close_all (ExampleCallManager *self) +{ + if (self->priv->channels != NULL) + { + GHashTable *tmp = self->priv->channels; + GHashTableIter iter; + gpointer v; + + self->priv->channels = NULL; + + g_hash_table_iter_init (&iter, tmp); + + while (g_hash_table_iter_next (&iter, NULL, &v)) + example_call_channel_disconnected (v); + + g_hash_table_unref (tmp); + } + + if (self->priv->available_id != 0) + { + g_signal_handler_disconnect (self->priv->conn, + self->priv->available_id); + self->priv->available_id = 0; + } + + if (self->priv->status_changed_id != 0) + { + g_signal_handler_disconnect (self->priv->conn, + self->priv->status_changed_id); + self->priv->status_changed_id = 0; + } +} + +static void +dispose (GObject *object) +{ + ExampleCallManager *self = EXAMPLE_CALL_MANAGER (object); + + example_call_manager_close_all (self); + g_assert (self->priv->channels == NULL); + + ((GObjectClass *) example_call_manager_parent_class)->dispose ( + object); +} + +static void +get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + ExampleCallManager *self = EXAMPLE_CALL_MANAGER (object); + + switch (property_id) + { + case PROP_CONNECTION: + g_value_set_object (value, self->priv->conn); + break; + + 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); + } +} + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + ExampleCallManager *self = EXAMPLE_CALL_MANAGER (object); + + switch (property_id) + { + case PROP_CONNECTION: + /* We don't ref the connection, because it owns a reference to the + * channel manager, and it guarantees that the manager's lifetime is + * less than its lifetime */ + self->priv->conn = g_value_get_object (value); + break; + + 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); + } +} + +static void +status_changed_cb (TpBaseConnection *conn, + guint status, + guint reason, + ExampleCallManager *self) +{ + switch (status) + { + case TP_CONNECTION_STATUS_DISCONNECTED: + { + example_call_manager_close_all (self); + } + break; + + default: + break; + } +} + +static ExampleCallChannel *new_channel (ExampleCallManager *self, + TpHandle handle, TpHandle initiator, gpointer request_token, + gboolean initial_audio, gboolean initial_video); + +static gboolean +simulate_incoming_call_cb (gpointer p) +{ + ExampleCallManager *self = p; + TpHandleRepoIface *contact_repo; + TpHandle caller; + + /* do nothing if we've been disconnected while waiting for the contact to + * call us */ + if (self->priv->available_id == 0) + return FALSE; + + /* We're called by someone whose ID on the IM service is "caller" */ + contact_repo = tp_base_connection_get_handles (self->priv->conn, + TP_HANDLE_TYPE_CONTACT); + caller = tp_handle_ensure (contact_repo, "caller", NULL, NULL); + + new_channel (self, caller, caller, NULL, TRUE, FALSE); + + return FALSE; +} + +/* Whenever our presence changes from away to available, and whenever our + * presence message changes while remaining available, simulate a call from + * a contact */ +static void +available_cb (GObject *conn G_GNUC_UNUSED, + const gchar *message, + ExampleCallManager *self) +{ + g_timeout_add_full (G_PRIORITY_DEFAULT, self->priv->simulation_delay, + simulate_incoming_call_cb, g_object_ref (self), g_object_unref); +} + +static void +constructed (GObject *object) +{ + ExampleCallManager *self = EXAMPLE_CALL_MANAGER (object); + void (*chain_up) (GObject *) = + ((GObjectClass *) example_call_manager_parent_class)->constructed; + + if (chain_up != NULL) + { + chain_up (object); + } + + self->priv->status_changed_id = g_signal_connect (self->priv->conn, + "status-changed", (GCallback) status_changed_cb, self); + + self->priv->available_id = g_signal_connect (self->priv->conn, + "available", (GCallback) available_cb, self); +} + +static void +example_call_manager_class_init (ExampleCallManagerClass *klass) +{ + GParamSpec *param_spec; + GObjectClass *object_class = (GObjectClass *) klass; + + object_class->constructed = constructed; + object_class->dispose = dispose; + object_class->get_property = get_property; + object_class->set_property = set_property; + + param_spec = g_param_spec_object ("connection", "Connection object", + "The connection that owns this channel manager", + TP_TYPE_BASE_CONNECTION, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); + + 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); + + g_type_class_add_private (klass, + sizeof (ExampleCallManagerPrivate)); +} + +static void +example_call_manager_foreach_channel (TpChannelManager *iface, + TpExportableChannelFunc callback, + gpointer user_data) +{ + ExampleCallManager *self = EXAMPLE_CALL_MANAGER (iface); + GHashTableIter iter; + gpointer chan; + + g_hash_table_iter_init (&iter, self->priv->channels); + + while (g_hash_table_iter_next (&iter, &chan, NULL)) + callback (chan, user_data); +} + +static void +channel_closed_cb (ExampleCallChannel *chan, + ExampleCallManager *self) +{ + tp_channel_manager_emit_channel_closed_for_object (self, + TP_EXPORTABLE_CHANNEL (chan)); + + if (self->priv->channels != NULL) + g_hash_table_remove (self->priv->channels, chan); +} + +static ExampleCallChannel * +new_channel (ExampleCallManager *self, + TpHandle handle, + TpHandle initiator, + gpointer request_token, + gboolean initial_audio, + gboolean initial_video) +{ + ExampleCallChannel *chan; + gchar *object_path; + GSList *requests = NULL; + + /* FIXME: This could potentially wrap around, but only after 4 billion + * calls, which is probably plenty. */ + object_path = g_strdup_printf ("%s/CallChannel%u", + self->priv->conn->object_path, self->priv->next_channel_index++); + + chan = g_object_new (EXAMPLE_TYPE_CALL_CHANNEL, + "connection", self->priv->conn, + "object-path", object_path, + "handle", handle, + "initiator-handle", initiator, + "requested", (self->priv->conn->self_handle == initiator), + "simulation-delay", self->priv->simulation_delay, + "initial-audio", initial_audio, + "initial-video", initial_video, + NULL); + + g_free (object_path); + + g_signal_connect (chan, "closed", G_CALLBACK (channel_closed_cb), self); + + g_hash_table_insert (self->priv->channels, chan, chan); + + if (request_token != NULL) + requests = g_slist_prepend (requests, request_token); + + tp_channel_manager_emit_new_channel (self, TP_EXPORTABLE_CHANNEL (chan), + requests); + g_slist_free (requests); + + return chan; +} + +static const gchar * const audio_fixed_properties[] = { + TP_PROP_CHANNEL_CHANNEL_TYPE, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, + FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, + NULL +}; + +static const gchar * const video_fixed_properties[] = { + TP_PROP_CHANNEL_CHANNEL_TYPE, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, + FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, + NULL +}; + +static const gchar * const audio_allowed_properties[] = { + TP_PROP_CHANNEL_TARGET_HANDLE, + TP_PROP_CHANNEL_TARGET_ID, + FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, + NULL +}; + +static const gchar * const video_allowed_properties[] = { + TP_PROP_CHANNEL_TARGET_HANDLE, + TP_PROP_CHANNEL_TARGET_ID, + FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, + NULL +}; + +static void +example_call_manager_foreach_channel_class ( + TpChannelManager *manager, + TpChannelManagerChannelClassFunc func, + gpointer user_data) +{ + GHashTable *table = tp_asv_new ( + TP_PROP_CHANNEL_CHANNEL_TYPE, + G_TYPE_STRING, FUTURE_IFACE_CHANNEL_TYPE_CALL, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT, + FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, G_TYPE_BOOLEAN, TRUE, + NULL); + + func (manager, table, audio_allowed_properties, user_data); + + g_hash_table_remove (table, FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO); + tp_asv_set_boolean (table, FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, + TRUE); + + func (manager, table, video_allowed_properties, user_data); + + g_hash_table_destroy (table); +} + +static gboolean +example_call_manager_request (ExampleCallManager *self, + gpointer request_token, + GHashTable *request_properties, + gboolean require_new) +{ + TpHandle handle; + GError *error = NULL; + gboolean initial_audio, initial_video; + + if (tp_strdiff (tp_asv_get_string (request_properties, + TP_PROP_CHANNEL_CHANNEL_TYPE), + FUTURE_IFACE_CHANNEL_TYPE_CALL)) + { + return FALSE; + } + + if (tp_asv_get_uint32 (request_properties, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL) != TP_HANDLE_TYPE_CONTACT) + { + return FALSE; + } + + handle = tp_asv_get_uint32 (request_properties, + TP_PROP_CHANNEL_TARGET_HANDLE, NULL); + g_assert (handle != 0); + + initial_audio = tp_asv_get_boolean (request_properties, + FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, NULL); + initial_video = tp_asv_get_boolean (request_properties, + FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, NULL); + + if (!initial_audio && !initial_video) + { + g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, + "Call channels must initially have either audio or video content"); + goto error; + } + + /* the set of (fixed | allowed) properties is the same for audio and video, + * so we only need to check with one set */ + if (tp_channel_manager_asv_has_unknown_properties (request_properties, + audio_fixed_properties, audio_allowed_properties, &error)) + { + goto error; + } + + if (handle == self->priv->conn->self_handle) + { + /* In protocols with a concept of multiple "resources" signed in to + * one account (XMPP, and possibly MSN) it is technically possible to + * call yourself - e.g. if you're signed in on two PCs, you can call one + * from the other. For simplicity, this example simulates a protocol + * where this is not the case. + */ + g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, + "In this protocol, you can't call yourself"); + goto error; + } + + if (!require_new) + { + /* see if we're already calling that handle */ + GHashTableIter iter; + gpointer chan; + + g_hash_table_iter_init (&iter, self->priv->channels); + + while (g_hash_table_iter_next (&iter, &chan, NULL)) + { + guint its_handle; + + g_object_get (chan, + "handle", &its_handle, + NULL); + + if (its_handle == handle) + { + tp_channel_manager_emit_request_already_satisfied (self, + request_token, TP_EXPORTABLE_CHANNEL (chan)); + return TRUE; + } + } + } + + new_channel (self, handle, self->priv->conn->self_handle, request_token, + initial_audio, initial_video); + return TRUE; + +error: + tp_channel_manager_emit_request_failed (self, request_token, + error->domain, error->code, error->message); + g_error_free (error); + return TRUE; +} + +static gboolean +example_call_manager_create_channel (TpChannelManager *manager, + gpointer request_token, + GHashTable *request_properties) +{ + return example_call_manager_request ( + EXAMPLE_CALL_MANAGER (manager), + request_token, request_properties, TRUE); +} + +static gboolean +example_call_manager_ensure_channel (TpChannelManager *manager, + gpointer request_token, + GHashTable *request_properties) +{ + return example_call_manager_request ( + EXAMPLE_CALL_MANAGER (manager), + request_token, request_properties, FALSE); +} + +static void +channel_manager_iface_init (gpointer g_iface, + gpointer iface_data G_GNUC_UNUSED) +{ + TpChannelManagerIface *iface = g_iface; + + iface->foreach_channel = example_call_manager_foreach_channel; + iface->foreach_channel_class = example_call_manager_foreach_channel_class; + iface->create_channel = example_call_manager_create_channel; + iface->ensure_channel = example_call_manager_ensure_channel; + /* In this channel manager, RequestChannel is not supported; Call is not + * designed to work with the old RequestChannel API. */ + iface->request_channel = NULL; +} diff --git a/examples/future/call-cm/call-manager.h b/examples/future/call-cm/call-manager.h new file mode 100644 index 000000000..96f5aee39 --- /dev/null +++ b/examples/future/call-cm/call-manager.h @@ -0,0 +1,71 @@ +/* + * media-manager.h - header for an example channel manager + * + * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * 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 + */ + +#ifndef EXAMPLE_CALL_MANAGER_H +#define EXAMPLE_CALL_MANAGER_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +typedef struct _ExampleCallManager ExampleCallManager; +typedef struct _ExampleCallManagerPrivate + ExampleCallManagerPrivate; + +typedef struct _ExampleCallManagerClass + ExampleCallManagerClass; +typedef struct _ExampleCallManagerClassPrivate + ExampleCallManagerClassPrivate; + +struct _ExampleCallManagerClass { + GObjectClass parent_class; + + ExampleCallManagerClassPrivate *priv; +}; + +struct _ExampleCallManager { + GObject parent; + + ExampleCallManagerPrivate *priv; +}; + +GType example_call_manager_get_type (void); + +/* TYPE MACROS */ +#define EXAMPLE_TYPE_CALL_MANAGER \ + (example_call_manager_get_type ()) +#define EXAMPLE_CALL_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CALL_MANAGER, \ + ExampleCallManager)) +#define EXAMPLE_CALL_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CALL_MANAGER, \ + ExampleCallManagerClass)) +#define EXAMPLE_IS_CALL_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CALL_MANAGER)) +#define EXAMPLE_IS_CALL_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CALL_MANAGER)) +#define EXAMPLE_CALL_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALL_MANAGER, \ + ExampleCallManagerClass)) + +G_END_DECLS + +#endif diff --git a/examples/future/call-cm/call-stream.c b/examples/future/call-cm/call-stream.c new file mode 100644 index 000000000..cf7e49c02 --- /dev/null +++ b/examples/future/call-cm/call-stream.c @@ -0,0 +1,705 @@ +/* + * call-stream.c - a stream in a call. + * + * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * 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 "call-stream.h" + +#include <telepathy-glib/base-connection.h> +#include <telepathy-glib/gtypes.h> + +#include "extensions/extensions.h" + +static void stream_iface_init (gpointer, gpointer); + +G_DEFINE_TYPE_WITH_CODE (ExampleCallStream, + example_call_stream, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, + tp_dbus_properties_mixin_iface_init); + G_IMPLEMENT_INTERFACE (FUTURE_TYPE_SVC_CALL_STREAM, stream_iface_init)) + +enum +{ + PROP_OBJECT_PATH = 1, + PROP_CONNECTION, + PROP_HANDLE, + PROP_SIMULATION_DELAY, + PROP_LOCALLY_REQUESTED, + PROP_SENDERS, + N_PROPS +}; + +enum +{ + SIGNAL_REMOVED, + N_SIGNALS +}; + +static guint signals[N_SIGNALS] = { 0 }; + +struct _ExampleCallStreamPrivate +{ + gchar *object_path; + TpBaseConnection *conn; + TpHandle handle; + FutureSendingState local_sending_state; + FutureSendingState remote_sending_state; + + guint simulation_delay; + + guint connected_event_id; + + gboolean locally_requested; + gboolean removed; +}; + +static void +example_call_stream_init (ExampleCallStream *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + EXAMPLE_TYPE_CALL_STREAM, + ExampleCallStreamPrivate); + + /* start off directionless */ + self->priv->local_sending_state = FUTURE_SENDING_STATE_NONE; + self->priv->remote_sending_state = FUTURE_SENDING_STATE_NONE; +} + +static void example_call_stream_receive_direction_request ( + ExampleCallStream *self, gboolean local_send, gboolean remote_send); +static void example_call_stream_change_direction (ExampleCallStream *self, + gboolean want_to_send, gboolean want_to_receive); + +static void +constructed (GObject *object) +{ + ExampleCallStream *self = EXAMPLE_CALL_STREAM (object); + TpDBusDaemon *dbus_daemon; + void (*chain_up) (GObject *) = + ((GObjectClass *) example_call_stream_parent_class)->constructed; + + dbus_daemon = tp_dbus_daemon_dup (NULL); + g_return_if_fail (dbus_daemon != NULL); + + if (chain_up != NULL) + chain_up (object); + + dbus_daemon = tp_dbus_daemon_dup (NULL); + g_return_if_fail (dbus_daemon != NULL); + + dbus_g_connection_register_g_object ( + tp_proxy_get_dbus_connection (dbus_daemon), + self->priv->object_path, object); + + g_object_unref (dbus_daemon); + dbus_daemon = NULL; + + if (self->priv->locally_requested) + { + example_call_stream_change_direction (self, TRUE, TRUE); + } + else + { + example_call_stream_receive_direction_request (self, TRUE, TRUE); + } + + if (self->priv->handle != 0) + { + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( + self->priv->conn, TP_HANDLE_TYPE_CONTACT); + + tp_handle_ref (contact_repo, self->priv->handle); + } +} + +static void +get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + ExampleCallStream *self = EXAMPLE_CALL_STREAM (object); + + switch (property_id) + { + case PROP_OBJECT_PATH: + g_value_set_string (value, self->priv->object_path); + break; + + case PROP_HANDLE: + g_value_set_uint (value, self->priv->handle); + break; + + case PROP_CONNECTION: + g_value_set_object (value, self->priv->conn); + break; + + case PROP_SIMULATION_DELAY: + g_value_set_uint (value, self->priv->simulation_delay); + break; + + case PROP_LOCALLY_REQUESTED: + g_value_set_boolean (value, self->priv->locally_requested); + break; + + case PROP_SENDERS: + { + GHashTable *senders = g_hash_table_new (NULL, NULL); + + g_hash_table_insert (senders, GUINT_TO_POINTER (self->priv->handle), + GUINT_TO_POINTER (self->priv->remote_sending_state)); + + g_hash_table_insert (senders, + GUINT_TO_POINTER (tp_base_connection_get_self_handle ( + self->priv->conn)), + GUINT_TO_POINTER (self->priv->local_sending_state)); + + g_value_take_boxed (value, senders); + } + 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) +{ + ExampleCallStream *self = EXAMPLE_CALL_STREAM (object); + + switch (property_id) + { + case PROP_OBJECT_PATH: + g_assert (self->priv->object_path == NULL); /* construct-only */ + self->priv->object_path = g_value_dup_string (value); + break; + + case PROP_HANDLE: + self->priv->handle = g_value_get_uint (value); + break; + + case PROP_CONNECTION: + g_assert (self->priv->conn == NULL); + self->priv->conn = g_value_dup_object (value); + break; + + case PROP_SIMULATION_DELAY: + self->priv->simulation_delay = g_value_get_uint (value); + break; + + case PROP_LOCALLY_REQUESTED: + self->priv->locally_requested = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +dispose (GObject *object) +{ + ExampleCallStream *self = EXAMPLE_CALL_STREAM (object); + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( + self->priv->conn, TP_HANDLE_TYPE_CONTACT); + + example_call_stream_close (self); + + if (self->priv->handle != 0) + { + tp_handle_unref (contact_repo, self->priv->handle); + self->priv->handle = 0; + } + + if (self->priv->conn != NULL) + { + g_object_unref (self->priv->conn); + self->priv->conn = NULL; + } + + ((GObjectClass *) example_call_stream_parent_class)->dispose (object); +} + +static void +finalize (GObject *object) +{ + ExampleCallStream *self = EXAMPLE_CALL_STREAM (object); + void (*chain_up) (GObject *) = + ((GObjectClass *) example_call_stream_parent_class)->finalize; + + g_free (self->priv->object_path); + + if (chain_up != NULL) + chain_up (object); +} + +static void +example_call_stream_class_init (ExampleCallStreamClass *klass) +{ + static TpDBusPropertiesMixinPropImpl stream_props[] = { + { "Senders", "senders", NULL }, + { NULL } + }; + static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { + { FUTURE_IFACE_CALL_STREAM, + tp_dbus_properties_mixin_getter_gobject_properties, + NULL, + stream_props, + }, + { NULL } + }; + GObjectClass *object_class = (GObjectClass *) klass; + GParamSpec *param_spec; + + g_type_class_add_private (klass, + sizeof (ExampleCallStreamPrivate)); + + object_class->constructed = constructed; + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->dispose = dispose; + object_class->finalize = finalize; + + param_spec = g_param_spec_string ("object-path", "D-Bus object path", + "The D-Bus object path used for this object on the bus.", NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec); + + param_spec = g_param_spec_object ("connection", "TpBaseConnection", + "Connection that (indirectly) owns this stream", + TP_TYPE_BASE_CONNECTION, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); + + param_spec = g_param_spec_uint ("handle", "Peer's TpHandle", + "The handle with which this stream communicates or 0 if not applicable", + 0, G_MAXUINT32, 0, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_HANDLE, param_spec); + + 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); + + param_spec = g_param_spec_boolean ("locally-requested", "Locally requested?", + "True if this channel was requested by the local user", + FALSE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_LOCALLY_REQUESTED, + param_spec); + + param_spec = g_param_spec_boxed ("senders", "Senders", + "Map from contact handles to their sending states", + FUTURE_HASH_TYPE_CONTACT_SENDING_STATE_MAP, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_SENDERS, param_spec); + + signals[SIGNAL_REMOVED] = g_signal_new ("removed", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + klass->dbus_properties_class.interfaces = prop_interfaces; + tp_dbus_properties_mixin_class_init (object_class, + G_STRUCT_OFFSET (ExampleCallStreamClass, + dbus_properties_class)); +} + +void +example_call_stream_close (ExampleCallStream *self) +{ + if (!self->priv->removed) + { + self->priv->removed = TRUE; + + g_message ("%s: Sending to server: Closing stream", + self->priv->object_path); + + if (self->priv->connected_event_id != 0) + { + g_source_remove (self->priv->connected_event_id); + } + + /* this has to come last, because the MediaChannel may unref us in + * response to the removed signal */ + g_signal_emit (self, signals[SIGNAL_REMOVED], 0); + } +} + +void +example_call_stream_accept_proposed_direction (ExampleCallStream *self) +{ + GHashTable *updated_senders; + GArray *removed_senders; + + if (self->priv->removed || + self->priv->local_sending_state != FUTURE_SENDING_STATE_PENDING_SEND) + return; + + g_message ("%s: SIGNALLING: Sending to server: OK, I'll send you media", + self->priv->object_path); + + self->priv->local_sending_state = FUTURE_SENDING_STATE_SENDING; + + updated_senders = g_hash_table_new (NULL, NULL); + removed_senders = g_array_sized_new (FALSE, FALSE, sizeof (guint), 0); + g_hash_table_insert (updated_senders, + GUINT_TO_POINTER (tp_base_connection_get_self_handle (self->priv->conn)), + GUINT_TO_POINTER (FUTURE_SENDING_STATE_SENDING)); + future_svc_call_stream_emit_senders_changed (self, updated_senders, + removed_senders); + g_hash_table_unref (updated_senders); + g_array_free (removed_senders, TRUE); +} + +void +example_call_stream_simulate_contact_agreed_to_send (ExampleCallStream *self) +{ + GHashTable *updated_senders; + GArray *removed_senders; + + if (self->priv->removed || + self->priv->remote_sending_state != FUTURE_SENDING_STATE_PENDING_SEND) + return; + + g_message ("%s: SIGNALLING: received: OK, I'll send you media", + self->priv->object_path); + + self->priv->remote_sending_state = FUTURE_SENDING_STATE_SENDING; + + updated_senders = g_hash_table_new (NULL, NULL); + removed_senders = g_array_sized_new (FALSE, FALSE, sizeof (guint), 0); + g_hash_table_insert (updated_senders, GUINT_TO_POINTER (self->priv->handle), + GUINT_TO_POINTER (FUTURE_SENDING_STATE_SENDING)); + future_svc_call_stream_emit_senders_changed (self, updated_senders, + removed_senders); + g_hash_table_unref (updated_senders); + g_array_free (removed_senders, TRUE); +} + +static gboolean +simulate_contact_agreed_to_send_cb (gpointer p) +{ + example_call_stream_simulate_contact_agreed_to_send (p); + return FALSE; +} + +static void +example_call_stream_change_direction (ExampleCallStream *self, + gboolean want_to_send, gboolean want_to_receive) +{ + GHashTable *updated_senders = g_hash_table_new (NULL, NULL); + + if (want_to_send) + { + if (self->priv->local_sending_state != FUTURE_SENDING_STATE_SENDING) + { + if (self->priv->local_sending_state == + FUTURE_SENDING_STATE_PENDING_SEND) + { + g_message ("%s: SIGNALLING: send: I will now send you media", + self->priv->object_path); + } + + g_message ("%s: MEDIA: sending media to peer", + self->priv->object_path); + self->priv->local_sending_state = FUTURE_SENDING_STATE_SENDING; + + g_hash_table_insert (updated_senders, + GUINT_TO_POINTER (tp_base_connection_get_self_handle ( + self->priv->conn)), + GUINT_TO_POINTER (FUTURE_SENDING_STATE_SENDING)); + } + } + else + { + if (self->priv->local_sending_state == FUTURE_SENDING_STATE_SENDING) + { + g_message ("%s: SIGNALLING: send: I will no longer send you media", + self->priv->object_path); + g_message ("%s: MEDIA: no longer sending media to peer", + self->priv->object_path); + self->priv->local_sending_state = FUTURE_SENDING_STATE_NONE; + + g_hash_table_insert (updated_senders, + GUINT_TO_POINTER (tp_base_connection_get_self_handle ( + self->priv->conn)), + GUINT_TO_POINTER (FUTURE_SENDING_STATE_NONE)); + } + else if (self->priv->local_sending_state == + FUTURE_SENDING_STATE_PENDING_SEND) + { + g_message ("%s: SIGNALLING: send: refusing to send you media", + self->priv->object_path); + self->priv->local_sending_state = FUTURE_SENDING_STATE_NONE; + + g_hash_table_insert (updated_senders, + GUINT_TO_POINTER (tp_base_connection_get_self_handle ( + self->priv->conn)), + GUINT_TO_POINTER (FUTURE_SENDING_STATE_NONE)); + } + } + + if (want_to_receive) + { + if (self->priv->remote_sending_state == FUTURE_SENDING_STATE_NONE) + { + g_message ("%s: SIGNALLING: send: send me media, please?", + self->priv->object_path); + self->priv->remote_sending_state = FUTURE_SENDING_STATE_PENDING_SEND; + g_timeout_add_full (G_PRIORITY_DEFAULT, self->priv->simulation_delay, + simulate_contact_agreed_to_send_cb, g_object_ref (self), + g_object_unref); + + g_hash_table_insert (updated_senders, + GUINT_TO_POINTER (self->priv->handle), + GUINT_TO_POINTER (FUTURE_SENDING_STATE_PENDING_SEND)); + } + } + else + { + if (self->priv->remote_sending_state != FUTURE_SENDING_STATE_NONE) + { + g_message ("%s: SIGNALLING: send: Please stop sending me media", + self->priv->object_path); + g_message ("%s: MEDIA: suppressing output of stream", + self->priv->object_path); + self->priv->remote_sending_state = FUTURE_SENDING_STATE_NONE; + + g_hash_table_insert (updated_senders, + GUINT_TO_POINTER (self->priv->handle), + GUINT_TO_POINTER (FUTURE_SENDING_STATE_NONE)); + } + } + + if (g_hash_table_size (updated_senders) != 0) + { + GArray *removed_senders = g_array_sized_new (FALSE, FALSE, + sizeof (guint), 0); + + future_svc_call_stream_emit_senders_changed (self, updated_senders, + removed_senders); + + g_array_free (removed_senders, TRUE); + } + + g_hash_table_unref (updated_senders); +} + +/* The remote user wants to change the direction of this stream according + * to @local_send and @remote_send. Shall we let him? */ +static void +example_call_stream_receive_direction_request (ExampleCallStream *self, + gboolean local_send, + gboolean remote_send) +{ + GHashTable *updated_senders = g_hash_table_new (NULL, NULL); + + /* In some protocols, streams cannot be neither sending nor receiving, so + * if a stream is set to TP_MEDIA_STREAM_DIRECTION_NONE, this is equivalent + * to removing it. (This is true in XMPP, for instance.) + * + * However, for this example we'll emulate a protocol where streams can be + * directionless. + */ + + if (local_send) + { + g_message ("%s: SIGNALLING: send: Please start sending me media", + self->priv->object_path); + + if (self->priv->local_sending_state == FUTURE_SENDING_STATE_NONE) + { + /* ask the user for permission */ + self->priv->local_sending_state = FUTURE_SENDING_STATE_PENDING_SEND; + + g_hash_table_insert (updated_senders, + GUINT_TO_POINTER (tp_base_connection_get_self_handle ( + self->priv->conn)), + GUINT_TO_POINTER (FUTURE_SENDING_STATE_PENDING_SEND)); + } + else + { + /* nothing to do, we're already sending (or asking the user for + * permission to do so) on that stream */ + } + } + else + { + g_message ("%s: SIGNALLING: receive: Please stop sending me media", + self->priv->object_path); + g_message ("%s: SIGNALLING: reply: OK!", + self->priv->object_path); + + if (self->priv->local_sending_state == FUTURE_SENDING_STATE_SENDING) + { + g_message ("%s: MEDIA: no longer sending media to peer", + self->priv->object_path); + self->priv->local_sending_state = FUTURE_SENDING_STATE_NONE; + + g_hash_table_insert (updated_senders, + GUINT_TO_POINTER (tp_base_connection_get_self_handle ( + self->priv->conn)), + GUINT_TO_POINTER (FUTURE_SENDING_STATE_NONE)); + } + else if (self->priv->local_sending_state == + FUTURE_SENDING_STATE_PENDING_SEND) + { + self->priv->local_sending_state = FUTURE_SENDING_STATE_NONE; + + g_hash_table_insert (updated_senders, + GUINT_TO_POINTER (tp_base_connection_get_self_handle ( + self->priv->conn)), + GUINT_TO_POINTER (FUTURE_SENDING_STATE_NONE)); + } + else + { + /* nothing to do, we're not sending on that stream anyway */ + } + } + + if (remote_send) + { + g_message ("%s: SIGNALLING: receive: I will now send you media", + self->priv->object_path); + + if (self->priv->remote_sending_state != FUTURE_SENDING_STATE_SENDING) + { + self->priv->remote_sending_state = FUTURE_SENDING_STATE_SENDING; + + g_hash_table_insert (updated_senders, + GUINT_TO_POINTER (self->priv->handle), + GUINT_TO_POINTER (FUTURE_SENDING_STATE_SENDING)); + } + } + else + { + if (self->priv->remote_sending_state == + FUTURE_SENDING_STATE_PENDING_SEND) + { + g_message ("%s: SIGNALLING: receive: No, I refuse to send you media", + self->priv->object_path); + self->priv->remote_sending_state = FUTURE_SENDING_STATE_NONE; + + g_hash_table_insert (updated_senders, + GUINT_TO_POINTER (self->priv->handle), + GUINT_TO_POINTER (FUTURE_SENDING_STATE_NONE)); + } + else if (self->priv->remote_sending_state == + FUTURE_SENDING_STATE_SENDING) + { + g_message ("%s: SIGNALLING: receive: I will no longer send media", + self->priv->object_path); + self->priv->remote_sending_state = FUTURE_SENDING_STATE_NONE; + + g_hash_table_insert (updated_senders, + GUINT_TO_POINTER (self->priv->handle), + GUINT_TO_POINTER (FUTURE_SENDING_STATE_NONE)); + } + } + + if (g_hash_table_size (updated_senders) != 0) + { + GArray *removed_senders = g_array_sized_new (FALSE, FALSE, + sizeof (guint), 0); + + future_svc_call_stream_emit_senders_changed (self, updated_senders, + removed_senders); + + g_array_free (removed_senders, TRUE); + } + + g_hash_table_unref (updated_senders); +} + +static void +stream_set_sending (FutureSvcCallStream *iface G_GNUC_UNUSED, + gboolean sending, + DBusGMethodInvocation *context) +{ + ExampleCallStream *self = EXAMPLE_CALL_STREAM (iface); + + example_call_stream_change_direction (self, sending, + (self->priv->remote_sending_state == FUTURE_SENDING_STATE_SENDING)); + + future_svc_call_stream_return_from_set_sending (context); +} + +static void +stream_request_receiving (FutureSvcCallStream *iface, + TpHandle contact, + gboolean receive, + DBusGMethodInvocation *context) +{ + ExampleCallStream *self = EXAMPLE_CALL_STREAM (iface); + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles + (self->priv->conn, TP_HANDLE_TYPE_CONTACT); + GError *error = NULL; + + if (!tp_handle_is_valid (contact_repo, contact, &error)) + { + goto finally; + } + + if (contact != self->priv->handle) + { + g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "Can't receive from contact #%u: this stream only contains #%u", + contact, self->priv->handle); + goto finally; + } + + example_call_stream_change_direction (self, + (self->priv->local_sending_state == FUTURE_SENDING_STATE_SENDING), + receive); + +finally: + if (error == NULL) + { + future_svc_call_stream_return_from_request_receiving (context); + } + else + { + dbus_g_method_return_error (context, error); + g_error_free (error); + } +} + +static void +stream_iface_init (gpointer iface, + gpointer data) +{ + FutureSvcCallStreamClass *klass = iface; + +#define IMPLEMENT(x) \ + future_svc_call_stream_implement_##x (klass, stream_##x) + IMPLEMENT (set_sending); + IMPLEMENT (request_receiving); +#undef IMPLEMENT +} diff --git a/examples/future/call-cm/call-stream.h b/examples/future/call-cm/call-stream.h new file mode 100644 index 000000000..e3093202e --- /dev/null +++ b/examples/future/call-cm/call-stream.h @@ -0,0 +1,82 @@ +/* + * call-stream.h - header for an example stream + * + * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * 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 + */ + +#ifndef EXAMPLE_CALL_STREAM_H +#define EXAMPLE_CALL_STREAM_H + +#include <glib-object.h> + +#include <telepathy-glib/telepathy-glib.h> + +G_BEGIN_DECLS + +typedef struct _ExampleCallStream ExampleCallStream; +typedef struct _ExampleCallStreamPrivate + ExampleCallStreamPrivate; + +typedef struct _ExampleCallStreamClass + ExampleCallStreamClass; +typedef struct _ExampleCallStreamClassPrivate + ExampleCallStreamClassPrivate; + +GType example_call_stream_get_type (void); + +#define EXAMPLE_TYPE_CALL_STREAM \ + (example_call_stream_get_type ()) +#define EXAMPLE_CALL_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), EXAMPLE_TYPE_CALL_STREAM, \ + ExampleCallStream)) +#define EXAMPLE_CALL_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), EXAMPLE_TYPE_CALL_STREAM, \ + ExampleCallStreamClass)) +#define EXAMPLE_IS_CALL_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EXAMPLE_TYPE_CALL_STREAM)) +#define EXAMPLE_IS_CALL_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), EXAMPLE_TYPE_CALL_STREAM)) +#define EXAMPLE_CALL_STREAM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALL_STREAM, \ + ExampleCallStreamClass)) + +struct _ExampleCallStreamClass { + GObjectClass parent_class; + TpDBusPropertiesMixinClass dbus_properties_class; + + ExampleCallStreamClassPrivate *priv; +}; + +struct _ExampleCallStream { + GObject parent; + + ExampleCallStreamPrivate *priv; +}; + +void example_call_stream_close (ExampleCallStream *self); +void example_call_stream_accept_proposed_direction (ExampleCallStream *self); +void example_call_stream_connect (ExampleCallStream *self); + +/* This controls receiving emulated network events, so it wouldn't exist in + * a real connection manager */ +void example_call_stream_simulate_contact_agreed_to_send ( + ExampleCallStream *self); + +G_END_DECLS + +#endif diff --git a/examples/future/call-cm/cm.c b/examples/future/call-cm/cm.c new file mode 100644 index 000000000..4d06ad2a9 --- /dev/null +++ b/examples/future/call-cm/cm.c @@ -0,0 +1,129 @@ +/* + * manager.c - an example connection manager + * + * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * 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 "cm.h" + +#include <dbus/dbus-glib.h> + +#include <telepathy-glib/dbus.h> +#include <telepathy-glib/errors.h> + +#include "conn.h" + +G_DEFINE_TYPE (ExampleCallConnectionManager, + example_call_connection_manager, + TP_TYPE_BASE_CONNECTION_MANAGER) + +struct _ExampleCallConnectionManagerPrivate +{ + int dummy; +}; + +static void +example_call_connection_manager_init (ExampleCallConnectionManager *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + EXAMPLE_TYPE_CALL_CONNECTION_MANAGER, + ExampleCallConnectionManagerPrivate); +} + +typedef struct { + gchar *account; + guint simulation_delay; +} ExampleParams; + +static gboolean +account_param_filter (const TpCMParamSpec *paramspec, + GValue *value, + GError **error) +{ + const gchar *id = g_value_get_string (value); + + g_value_take_string (value, + example_call_normalize_contact (NULL, id, NULL, error)); + + if (g_value_get_string (value) == NULL) + return FALSE; + + return TRUE; +} + +#include "_gen/param-spec-struct.h" + +static gpointer +alloc_params (void) +{ + ExampleParams *params = g_slice_new0 (ExampleParams); + + params->simulation_delay = 1000; + return params; +} + +static void +free_params (gpointer p) +{ + ExampleParams *params = p; + + g_free (params->account); + + g_slice_free (ExampleParams, params); +} + +static const TpCMProtocolSpec example_protocols[] = { + { "example", example_call_example_params, + alloc_params, free_params }, + { NULL, NULL } +}; + +static TpBaseConnection * +new_connection (TpBaseConnectionManager *self, + const gchar *proto, + TpIntSet *params_present, + gpointer parsed_params, + GError **error) +{ + ExampleParams *params = parsed_params; + ExampleCallConnection *conn; + + conn = EXAMPLE_CALL_CONNECTION + (g_object_new (EXAMPLE_TYPE_CALL_CONNECTION, + "account", params->account, + "simulation-delay", params->simulation_delay, + "protocol", proto, + NULL)); + + return (TpBaseConnection *) conn; +} + +static void +example_call_connection_manager_class_init ( + ExampleCallConnectionManagerClass *klass) +{ + TpBaseConnectionManagerClass *base_class = + (TpBaseConnectionManagerClass *) klass; + + g_type_class_add_private (klass, + sizeof (ExampleCallConnectionManagerPrivate)); + + base_class->new_connection = new_connection; + base_class->cm_dbus_name = "example_call"; + base_class->protocol_params = example_protocols; +} diff --git a/examples/future/call-cm/cm.h b/examples/future/call-cm/cm.h new file mode 100644 index 000000000..275abb229 --- /dev/null +++ b/examples/future/call-cm/cm.h @@ -0,0 +1,73 @@ +/* + * manager.h - header for an example connection manager + * + * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * 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 + */ + +#ifndef EXAMPLE_CALL_CM_H +#define EXAMPLE_CALL_CM_H + +#include <glib-object.h> +#include <telepathy-glib/base-connection-manager.h> + +G_BEGIN_DECLS + +typedef struct _ExampleCallConnectionManager + ExampleCallConnectionManager; +typedef struct _ExampleCallConnectionManagerPrivate + ExampleCallConnectionManagerPrivate; + +typedef struct _ExampleCallConnectionManagerClass + ExampleCallConnectionManagerClass; +typedef struct _ExampleCallConnectionManagerClassPrivate + ExampleCallConnectionManagerClassPrivate; + +struct _ExampleCallConnectionManagerClass { + TpBaseConnectionManagerClass parent_class; + + ExampleCallConnectionManagerClassPrivate *priv; +}; + +struct _ExampleCallConnectionManager { + TpBaseConnectionManager parent; + + ExampleCallConnectionManagerPrivate *priv; +}; + +GType example_call_connection_manager_get_type (void); + +/* TYPE MACROS */ +#define EXAMPLE_TYPE_CALL_CONNECTION_MANAGER \ + (example_call_connection_manager_get_type ()) +#define EXAMPLE_CALL_CONNECTION_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CALL_CONNECTION_MANAGER, \ + ExampleCallConnectionManager)) +#define EXAMPLE_CALL_CONNECTION_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CALL_CONNECTION_MANAGER, \ + ExampleCallConnectionManagerClass)) +#define EXAMPLE_IS_CALL_CONNECTION_MANAGER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CALL_CONNECTION_MANAGER)) +#define EXAMPLE_IS_CALL_CONNECTION_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CALL_CONNECTION_MANAGER)) +#define EXAMPLE_CALL_CONNECTION_MANAGER_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALL_CONNECTION_MANAGER, \ + ExampleCallConnectionManagerClass)) + +G_END_DECLS + +#endif diff --git a/examples/future/call-cm/conn.c b/examples/future/call-cm/conn.c new file mode 100644 index 000000000..321e5e0a7 --- /dev/null +++ b/examples/future/call-cm/conn.c @@ -0,0 +1,421 @@ +/* + * conn.c - an example connection + * + * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * 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 "conn.h" + +#include <string.h> + +#include <dbus/dbus-glib.h> + +#include <telepathy-glib/telepathy-glib.h> +#include <telepathy-glib/handle-repo-dynamic.h> +#include <telepathy-glib/handle-repo-static.h> + +#include "call-manager.h" + +G_DEFINE_TYPE_WITH_CODE (ExampleCallConnection, + example_call_connection, + TP_TYPE_BASE_CONNECTION, + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS, + tp_contacts_mixin_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE, + tp_presence_mixin_iface_init); + G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE, + tp_presence_mixin_simple_presence_iface_init)) + +enum +{ + PROP_ACCOUNT = 1, + PROP_SIMULATION_DELAY, + N_PROPS +}; + +enum +{ + SIGNAL_AVAILABLE, + N_SIGNALS +}; + +static guint signals[N_SIGNALS] = { 0 }; + +struct _ExampleCallConnectionPrivate +{ + gchar *account; + guint simulation_delay; + gboolean away; + gchar *presence_message; +}; + +static void +example_call_connection_init (ExampleCallConnection *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + EXAMPLE_TYPE_CALL_CONNECTION, + ExampleCallConnectionPrivate); + self->priv->away = FALSE; + self->priv->presence_message = g_strdup (""); +} + +static void +get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *spec) +{ + ExampleCallConnection *self = EXAMPLE_CALL_CONNECTION (object); + + switch (property_id) + { + case PROP_ACCOUNT: + g_value_set_string (value, self->priv->account); + break; + + case PROP_SIMULATION_DELAY: + g_value_set_uint (value, self->priv->simulation_delay); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec); + } +} + +static void +set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *spec) +{ + ExampleCallConnection *self = EXAMPLE_CALL_CONNECTION (object); + + switch (property_id) + { + case PROP_ACCOUNT: + g_free (self->priv->account); + self->priv->account = g_value_dup_string (value); + break; + + case PROP_SIMULATION_DELAY: + self->priv->simulation_delay = g_value_get_uint (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec); + } +} + +static void +finalize (GObject *object) +{ + ExampleCallConnection *self = EXAMPLE_CALL_CONNECTION (object); + + tp_contacts_mixin_finalize (object); + g_free (self->priv->account); + g_free (self->priv->presence_message); + + G_OBJECT_CLASS (example_call_connection_parent_class)->finalize (object); +} + +static gchar * +get_unique_connection_name (TpBaseConnection *conn) +{ + ExampleCallConnection *self = EXAMPLE_CALL_CONNECTION (conn); + + return g_strdup_printf ("%s@%p", self->priv->account, self); +} + +gchar * +example_call_normalize_contact (TpHandleRepoIface *repo, + const gchar *id, + gpointer context, + GError **error) +{ + if (id[0] == '\0') + { + g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE, + "Contact ID must not be empty"); + return NULL; + } + + return g_utf8_normalize (id, -1, G_NORMALIZE_ALL_COMPOSE); +} + +static void +create_handle_repos (TpBaseConnection *conn, + TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES]) +{ + repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new + (TP_HANDLE_TYPE_CONTACT, example_call_normalize_contact, NULL); +} + +static GPtrArray * +create_channel_managers (TpBaseConnection *conn) +{ + ExampleCallConnection *self = EXAMPLE_CALL_CONNECTION (conn); + GPtrArray *ret = g_ptr_array_sized_new (1); + + g_ptr_array_add (ret, + g_object_new (EXAMPLE_TYPE_CALL_MANAGER, + "connection", conn, + "simulation-delay", self->priv->simulation_delay, + NULL)); + + return ret; +} + +static gboolean +start_connecting (TpBaseConnection *conn, + GError **error) +{ + ExampleCallConnection *self = EXAMPLE_CALL_CONNECTION (conn); + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn, + TP_HANDLE_TYPE_CONTACT); + + /* In a real connection manager we'd ask the underlying implementation to + * start connecting, then go to state CONNECTED when finished, but here + * we can do it immediately. */ + + conn->self_handle = tp_handle_ensure (contact_repo, self->priv->account, + NULL, error); + + if (conn->self_handle == 0) + return FALSE; + + tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTED, + TP_CONNECTION_STATUS_REASON_REQUESTED); + + return TRUE; +} + +static void +shut_down (TpBaseConnection *conn) +{ + /* In a real connection manager we'd ask the underlying implementation to + * start shutting down, then call this function when finished, but here + * we can do it immediately. */ + tp_base_connection_finish_shutdown (conn); +} + +static void +constructed (GObject *object) +{ + TpBaseConnection *base = TP_BASE_CONNECTION (object); + void (*chain_up) (GObject *) = + G_OBJECT_CLASS (example_call_connection_parent_class)->constructed; + + if (chain_up != NULL) + chain_up (object); + + tp_contacts_mixin_init (object, + G_STRUCT_OFFSET (ExampleCallConnection, contacts_mixin)); + tp_base_connection_register_with_contacts_mixin (base); + + tp_presence_mixin_init (object, + G_STRUCT_OFFSET (ExampleCallConnection, presence_mixin)); + tp_presence_mixin_simple_presence_register_with_contacts_mixin (object); +} + +static gboolean +status_available (GObject *object, + guint index_) +{ + TpBaseConnection *base = TP_BASE_CONNECTION (object); + + if (base->status != TP_CONNECTION_STATUS_CONNECTED) + return FALSE; + + return TRUE; +} + +static GHashTable * +get_contact_statuses (GObject *object, + const GArray *contacts, + GError **error) +{ + ExampleCallConnection *self = + EXAMPLE_CALL_CONNECTION (object); + TpBaseConnection *base = TP_BASE_CONNECTION (object); + guint i; + GHashTable *result = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, (GDestroyNotify) tp_presence_status_free); + + for (i = 0; i < contacts->len; i++) + { + TpHandle contact = g_array_index (contacts, guint, i); + ExampleCallPresence presence; + GHashTable *parameters; + + parameters = g_hash_table_new_full (g_str_hash, + g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free); + + /* we know our own status from the connection; for this example CM, + * everyone else's status is assumed to be "available" */ + if (contact == base->self_handle) + { + presence = (self->priv->away ? EXAMPLE_CALL_PRESENCE_AWAY + : EXAMPLE_CALL_PRESENCE_AVAILABLE); + + if (self->priv->presence_message[0] != '\0') + g_hash_table_insert (parameters, "message", + tp_g_value_slice_new_string (self->priv->presence_message)); + } + else + { + presence = EXAMPLE_CALL_PRESENCE_AVAILABLE; + } + + g_hash_table_insert (result, GUINT_TO_POINTER (contact), + tp_presence_status_new (presence, parameters)); + g_hash_table_destroy (parameters); + } + + return result; +} + +static gboolean +set_own_status (GObject *object, + const TpPresenceStatus *status, + GError **error) +{ + ExampleCallConnection *self = + EXAMPLE_CALL_CONNECTION (object); + TpBaseConnection *base = TP_BASE_CONNECTION (object); + GHashTable *presences; + const gchar *message = ""; + + if (status->optional_arguments != NULL) + { + GValue *v = g_hash_table_lookup (status->optional_arguments, "message"); + + if (v != NULL && G_VALUE_HOLDS_STRING (v)) + { + message = g_value_get_string (v); + + if (message == NULL) + message = ""; + } + } + + if (status->index == EXAMPLE_CALL_PRESENCE_AWAY) + { + if (self->priv->away && !tp_strdiff (message, + self->priv->presence_message)) + return TRUE; + + self->priv->away = TRUE; + } + else + { + if (!self->priv->away && !tp_strdiff (message, + self->priv->presence_message)) + return TRUE; + + self->priv->away = FALSE; + } + + g_free (self->priv->presence_message); + self->priv->presence_message = g_strdup (message); + + presences = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, NULL); + g_hash_table_insert (presences, GUINT_TO_POINTER (base->self_handle), + (gpointer) status); + tp_presence_mixin_emit_presence_update (object, presences); + g_hash_table_destroy (presences); + + if (!self->priv->away) + { + g_signal_emit (self, signals[SIGNAL_AVAILABLE], 0, message); + } + + return TRUE; +} + +static const TpPresenceStatusOptionalArgumentSpec can_have_message[] = { + { "message", "s", NULL, NULL }, + { NULL } +}; + +/* Must be kept in sync with ExampleCallPresence enum in header */ +static const TpPresenceStatusSpec presence_statuses[] = { + { "offline", TP_CONNECTION_PRESENCE_TYPE_OFFLINE, FALSE, NULL }, + { "unknown", TP_CONNECTION_PRESENCE_TYPE_UNKNOWN, FALSE, NULL }, + { "error", TP_CONNECTION_PRESENCE_TYPE_ERROR, FALSE, NULL }, + { "away", TP_CONNECTION_PRESENCE_TYPE_AWAY, TRUE, can_have_message }, + { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE, + can_have_message }, + { NULL } +}; + +static void +example_call_connection_class_init ( + ExampleCallConnectionClass *klass) +{ + static const gchar *interfaces_always_present[] = { + TP_IFACE_CONNECTION_INTERFACE_CONTACTS, + TP_IFACE_CONNECTION_INTERFACE_PRESENCE, + TP_IFACE_CONNECTION_INTERFACE_REQUESTS, + TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE, + NULL }; + TpBaseConnectionClass *base_class = (TpBaseConnectionClass *) klass; + GObjectClass *object_class = (GObjectClass *) klass; + GParamSpec *param_spec; + + object_class->get_property = get_property; + object_class->set_property = set_property; + object_class->constructed = constructed; + object_class->finalize = finalize; + g_type_class_add_private (klass, + sizeof (ExampleCallConnectionPrivate)); + + base_class->create_handle_repos = create_handle_repos; + base_class->get_unique_connection_name = get_unique_connection_name; + base_class->create_channel_managers = create_channel_managers; + base_class->start_connecting = start_connecting; + base_class->shut_down = shut_down; + base_class->interfaces_always_present = interfaces_always_present; + + param_spec = g_param_spec_string ("account", "Account name", + "The username of this user", NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec); + + 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); + + /* Used in the call manager, to simulate an incoming call when we become + * available */ + signals[SIGNAL_AVAILABLE] = g_signal_new ("available", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); + + tp_contacts_mixin_class_init (object_class, + G_STRUCT_OFFSET (ExampleCallConnectionClass, contacts_mixin)); + tp_presence_mixin_class_init (object_class, + G_STRUCT_OFFSET (ExampleCallConnectionClass, presence_mixin), + status_available, get_contact_statuses, set_own_status, + presence_statuses); + tp_presence_mixin_simple_presence_init_dbus_properties (object_class); +} diff --git a/examples/future/call-cm/conn.h b/examples/future/call-cm/conn.h new file mode 100644 index 000000000..3e3fb8ca9 --- /dev/null +++ b/examples/future/call-cm/conn.h @@ -0,0 +1,78 @@ +/* + * conn.h - header for an example connection + * + * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright © 2007-2009 Nokia Corporation + * + * Copying and distribution of this file, with or without modification, + * are permitted in any medium without royalty provided the copyright + * notice and this notice are preserved. + */ + +#ifndef EXAMPLE_CALL_CONN_H +#define EXAMPLE_CALL_CONN_H + +#include <glib-object.h> +#include <telepathy-glib/base-connection.h> +#include <telepathy-glib/contacts-mixin.h> +#include <telepathy-glib/presence-mixin.h> + +G_BEGIN_DECLS + +typedef struct _ExampleCallConnection ExampleCallConnection; +typedef struct _ExampleCallConnectionPrivate + ExampleCallConnectionPrivate; + +typedef struct _ExampleCallConnectionClass ExampleCallConnectionClass; +typedef struct _ExampleCallConnectionClassPrivate + ExampleCallConnectionClassPrivate; + +struct _ExampleCallConnectionClass { + TpBaseConnectionClass parent_class; + TpPresenceMixinClass presence_mixin; + TpContactsMixinClass contacts_mixin; + + ExampleCallConnectionClassPrivate *priv; +}; + +struct _ExampleCallConnection { + TpBaseConnection parent; + TpPresenceMixin presence_mixin; + TpContactsMixin contacts_mixin; + + ExampleCallConnectionPrivate *priv; +}; + +GType example_call_connection_get_type (void); + +#define EXAMPLE_TYPE_CALL_CONNECTION \ + (example_call_connection_get_type ()) +#define EXAMPLE_CALL_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), EXAMPLE_TYPE_CALL_CONNECTION, \ + ExampleCallConnection)) +#define EXAMPLE_CALL_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), EXAMPLE_TYPE_CALL_CONNECTION, \ + ExampleCallConnectionClass)) +#define EXAMPLE_IS_CALL_CONNECTION(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), EXAMPLE_TYPE_CALL_CONNECTION)) +#define EXAMPLE_IS_CALL_CONNECTION_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), EXAMPLE_TYPE_CALL_CONNECTION)) +#define EXAMPLE_CALL_CONNECTION_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), EXAMPLE_TYPE_CALL_CONNECTION, \ + ExampleCallConnectionClass)) + +gchar *example_call_normalize_contact (TpHandleRepoIface *repo, + const gchar *id, gpointer context, GError **error); + +/* Must be kept in sync with the array presence_statuses in conn.c */ +typedef enum { + EXAMPLE_CALL_PRESENCE_OFFLINE = 0, + EXAMPLE_CALL_PRESENCE_UNKNOWN, + EXAMPLE_CALL_PRESENCE_ERROR, + EXAMPLE_CALL_PRESENCE_AWAY, + EXAMPLE_CALL_PRESENCE_AVAILABLE +} ExampleCallPresence; + +G_END_DECLS + +#endif diff --git a/examples/future/call-cm/main.c b/examples/future/call-cm/main.c new file mode 100644 index 000000000..f3db411ca --- /dev/null +++ b/examples/future/call-cm/main.c @@ -0,0 +1,61 @@ +/* + * main.c - entry point for an example Telepathy connection manager + * + * Copyright © 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * 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 <telepathy-glib/debug.h> +#include <telepathy-glib/run.h> + +#include "extensions/extensions.h" + +#include "cm.h" + +static TpBaseConnectionManager * +construct_cm (void) +{ + return (TpBaseConnectionManager *) g_object_new ( + EXAMPLE_TYPE_CALL_CONNECTION_MANAGER, + NULL); +} + +int +main (int argc, + char **argv) +{ +#ifdef ENABLE_DEBUG + tp_debug_divert_messages (g_getenv ("EXAMPLE_CM_LOGFILE")); + tp_debug_set_flags (g_getenv ("EXAMPLE_DEBUG")); + + if (g_getenv ("EXAMPLE_TIMING") != NULL) + g_log_set_default_handler (tp_debug_timestamped_log_handler, NULL); + + if (g_getenv ("EXAMPLE_PERSIST") != NULL) + tp_debug_set_persistent (TRUE); +#endif + + /* strictly speaking, this is only necessary for client code, but it's + * harmless here */ + g_type_init (); + future_cli_init (); + + return tp_run_connection_manager ("telepathy-example-cm-call", + VERSION, construct_cm, argc, argv); +} diff --git a/examples/future/call-cm/manager-file.py b/examples/future/call-cm/manager-file.py new file mode 100644 index 000000000..137751ad1 --- /dev/null +++ b/examples/future/call-cm/manager-file.py @@ -0,0 +1,23 @@ +# Input for tools/manager-file.py + +MANAGER = 'example_call' +PARAMS = { + 'example' : { + 'account': { + 'dtype': 's', + 'flags': 'required register', + 'filter': 'account_param_filter', + # 'filter_data': 'NULL', + # 'default': ..., + # 'struct_field': '...', + # 'setter_data': 'NULL', + }, + 'simulation-delay': { + 'dtype': 'u', + 'default': 1000, + }, + }, + } +STRUCTS = { + 'example': 'ExampleParams' + } diff --git a/extensions/Call_Content.xml b/extensions/Call_Content.xml new file mode 100644 index 000000000..1d5e891df --- /dev/null +++ b/extensions/Call_Content.xml @@ -0,0 +1,141 @@ +<?xml version="1.0" ?> +<node name="/Call_Content" + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <tp:copyright>Copyright © 2009 Collabora Ltd.</tp:copyright> + <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright> + <tp:license xmlns="http://www.w3.org/1999/xhtml"> + <p>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.</p> + + <p>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.</p> + + <p>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 Street, Fifth Floor, Boston, MA + 02110-1301, USA.</p> + </tp:license> + + <interface name="org.freedesktop.Telepathy.Call.Content.DRAFT" + tp:causes-havoc="experimental"> + <tp:added version="0.19.0">(draft 1)</tp:added> + + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + This object represents one Content inside a Call. For example in an + audio/video call there would be one audio and one video content. Each + content has one or more <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call">Stream.DRAFT</tp:dbus-ref> + objects which represent the actual transport to one or more contacts. + + </tp:docstring> + + <property name="Name" tp:name-for-bindings="Name" type="s" access="read"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The name of the content. + [FIXME: rationale?]</p> + </tp:docstring> + </property> + + <property name="Type" tp:name-for-bindings="Type" + type="u" tp:type="Media_Stream_Type" access="read"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The media type of this content</p> + </tp:docstring> + </property> + + <property name="Creator" tp:name-for-bindings="Creator" + type="u" tp:type="Contact_Handle" access="read"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The creator of this content</p> + </tp:docstring> + </property> + + <tp:enum name="Call_Content_Disposition" type="u"> + <tp:docstring> + [FIXME] + </tp:docstring> + + <tp:enumvalue suffix="None" value="0"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + The content has no specific disposition + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="Early_Media" value="1"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + [FIXME: what does this mean?] + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="Initial" value="2"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The content was initially part of the call. When <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Channel.Type.Call.DRAFT" + >Accept</tp:dbus-ref> is called on the channel, all streams of + this content where the self-handle's sending state in <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call.Stream.DRAFT" + >Senders</tp:dbus-ref> is Sending_State_Pending_Send + will be moved to Sending_State_Sending as if <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call.Stream.DRAFT" + >SetSending</tp:dbus-ref>(TRUE) had been called.</p> + </tp:docstring> + </tp:enumvalue> + </tp:enum> + + <property name="Disposition" tp:name-for-bindings="Disposition" + type="u" tp:type="Call_Content_Disposition" access="read"> + <tp:docstring> + The disposition of this content. This property cannot change. + </tp:docstring> + </property> + + <signal name="StreamAdded" tp:name-for-bindings="Stream_Added"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Emitted when a stream is added to a call</p> + </tp:docstring> + <arg name="Stream" type="o"> + <tp:docstring> + The stream which was added + </tp:docstring> + </arg> + </signal> + + <signal name="StreamRemoved" tp:name-for-bindings="Stream_Removed"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Emitted when a stream is added to a call</p> + </tp:docstring> + <arg name="Stream" type="o"> + <tp:docstring> + The stream which was removed + </tp:docstring> + </arg> + </signal> + + <property name="Streams" tp:name-for-bindings="Streams" + type="ao" access="read"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The list of + <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call">Stream.DRAFT</tp:dbus-ref> + objects that exist in this content.</p> + + <tp:rationale> + <p>In a conference call multiple parties can share one media content + (say, audio), but the streaming of that media can either be shared + or separate. For example, in a multicast conference all contacts + would share one stream, while in a Muji conference there would be + a stream for each participant.</p> + </tp:rationale> + + <p>Change notification is via + <tp:member-ref>StreamAdded</tp:member-ref> and + <tp:member-ref>StreamRemoved</tp:member-ref>.</p> + </tp:docstring> + </property> + </interface> +</node> +<!-- vim:set sw=2 sts=2 et ft=xml: --> diff --git a/extensions/Call_Content_Codec_Offer.xml b/extensions/Call_Content_Codec_Offer.xml new file mode 100644 index 000000000..31ff0b3c9 --- /dev/null +++ b/extensions/Call_Content_Codec_Offer.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" ?> +<node name="/Call_Content_Codec_Offer" + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <tp:copyright>Copyright © 2009 Collabora Ltd.</tp:copyright> + <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright> + <tp:license xmlns="http://www.w3.org/1999/xhtml"> + <p>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.</p> + + <p>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.</p> + + <p>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 Street, Fifth Floor, Boston, MA + 02110-1301, USA.</p> + </tp:license> + + <interface name="org.freedesktop.Telepathy.Call.Content.CodecOffer.DRAFT" + tp:causes-havoc="experimental"> + <tp:added version="0.19.0">(draft 1)</tp:added> + + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + This object represents an offer of a Codec payload mapping. + FIXME add Accept and Reject signals ? + </tp:docstring> + + <method name="Accept" tp:name-for-bindings="Accept"> + <arg name="Codecs" direction="in" + type="a(usuua{ss})" tp:type="Codec[]" /> + <tp:docstring> + Accept the updated Codec mapping and update the local mapping + </tp:docstring> + </method> + + <method name="Reject" tp:name-for-bindings="Reject"> + <tp:docstring> + Reject the proposed update to the codecs + FIXME add error codes and strings here + </tp:docstring> + </method> + + <property name="RemoteContactCodecMap" + tp:name-for-bindings="Remote_Contact_Codec_Map" + type="a{ua(usuua{ss})}" tp:type="Contact_Codec_Map" access="read"> + <tp:docstring> + A map from remote contacts to the lists of codecs they support. + </tp:docstring> + </property> + + </interface> +</node> +<!-- vim:set sw=2 sts=2 et ft=xml: --> diff --git a/extensions/Call_Content_Interface_Media.xml b/extensions/Call_Content_Interface_Media.xml new file mode 100644 index 000000000..2b3eb65d5 --- /dev/null +++ b/extensions/Call_Content_Interface_Media.xml @@ -0,0 +1,229 @@ +<?xml version="1.0" ?> +<node name="/Call_Content_Interface_Media" + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <tp:copyright>Copyright © 2009 Collabora Ltd.</tp:copyright> + <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright> + <tp:license xmlns="http://www.w3.org/1999/xhtml"> + <p>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.</p> + + <p>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.</p> + + <p>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 Street, Fifth Floor, Boston, MA + 02110-1301, USA.</p> + </tp:license> + + <interface name="org.freedesktop.Telepathy.Call.Content.Interface.Media.DRAFT" + tp:causes-havoc="experimental"> + <tp:added version="0.19.0">(draft 1)</tp:added> + <tp:requires interface="org.freedesktop.Telepathy.Call.Content"/> + + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + Interface to use by a software implementation of media streaming. + + FIXME: How should the streaming implementation know when it is its turn + to set the codecs. + </tp:docstring> + + <tp:struct name="Codec" array-name="Codec_List"> + <tp:docstring> + A description of a codec. + </tp:docstring> + + <tp:member name="Identifier" type="u"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + Numeric identifier for the codec. This will be used as the PT in the + SDP or content description. + </tp:docstring> + </tp:member> + + <tp:member name="Name" type="s"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + The name of the codec. + </tp:docstring> + </tp:member> + + <tp:member name="Clockrate" type="u"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + The clockrate of the codec + </tp:docstring> + </tp:member> + <tp:member name="Channels" type="u"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + Number of channels of the codec if applicable, otherwise 0 + </tp:docstring> + </tp:member> + + <tp:member name="Parameters" type="a{ss}"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + Extra parameters for this codec + </tp:docstring> + </tp:member> + </tp:struct> + + <tp:mapping name="Contact_Codec_Map"> + <tp:docstring> + A map from contacts to the lists of codecs they support. + </tp:docstring> + + <tp:member name="Handle" type="u" tp:type="Contact_Handle"> + <tp:docstring> + A contact + </tp:docstring> + </tp:member> + + <tp:member name="Codecs" type="a(usuua{ss})" tp:type="Codec[]"> + <tp:docstring> + The codecs that the contact supports + </tp:docstring> + </tp:member> + </tp:mapping> + + <tp:struct name="Codec_Offering"> + <tp:docstring> + A codec offer and its corresponding remote contact codec map. + </tp:docstring> + + <tp:member name="Codec_Offer" type="o"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + The object path to the + <tp:dbus-ref namespace="org.freedesktop.Telepathy.Call.Content" + >CodecOffer.DRAFT</tp:dbus-ref> + </tp:docstring> + </tp:member> + + <tp:member name="Remote_Contact_Codec_Map" type="a{ua(usuua{ss})}" + tp:type="Contact_Codec_Map"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + The <tp:dbus-ref namespace="org.freedesktop.Telepathy.Call.Content" + >CodecOffer.DRAFT.RemoteContactCodecMap</tp:dbus-ref> property + of the codec offer. + </tp:docstring> + </tp:member> + </tp:struct> + + <signal name="CodecsChanged" tp:name-for-bindings="Codecs_Changed"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Emitted when the codecs in use change.</p> + + <p>As well as acting as change notification for the + <tp:member-ref>ContactCodecMap</tp:member-ref>, emission of this + signal implies that the <tp:member-ref>CodecOffer</tp:member-ref> + property has changed to <code>('/', {})</code>.</p> + </tp:docstring> + + <arg name="Updated_Codecs" type="a{ua(usuua{ss})}" + tp:type="Contact_Codec_Map"> + <tp:docstring> + A map from contacts to their codecs. Each pair in this map is added + to the <tp:member-ref>ContactCodecMap</tp:member-ref> property, + replacing any previous pair with that key. + </tp:docstring> + </arg> + + <arg name="Removed_Contacts" type="au" tp:type="Contact_Handle[]"> + <tp:docstring> + A list of keys which were removed from the + <tp:member-ref>ContactCodecMap</tp:member-ref>, probably because + those contacts left the call. + </tp:docstring> + </arg> + </signal> + + <method name="SetCodecs" tp:name-for-bindings="Set_Codecs"> + <tp:docstring> + Set or update the local codec mapping. + </tp:docstring> + + <arg name="Codecs" direction="in" + type="a(usuua{ss})" tp:type="Codec[]"> + <tp:docstring> + The codecs now supported by the local user. + </tp:docstring> + </arg> + </method> + + <property name="ContactCodecMap" tp:name-for-bindings="Contact_Codec_Map" + type="a{ua(usuua{ss})}" tp:type="Contact_Codec_Map" access="read"> + <tp:docstring> + <p>A map from contact handles (including the local user's own handle) + to the codecs supported by that contact.</p> + + <p>Change notification is via + <tp:member-ref>CodecsChanged</tp:member-ref>.</p> + </tp:docstring> + </property> + + <signal name="NewCodecOffer" tp:name-for-bindings="New_Codec_Offer"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Emitted when a new <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call.Content" + >CodecOffer.DRAFT</tp:dbus-ref> appears. The streaming + implementation MUST respond by calling the <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call.Content.CodecOffer.DRAFT" + >Accept</tp:dbus-ref> or <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call.Content.CodecOffer.DRAFT" + >Reject</tp:dbus-ref> method on the codec offer object.</p> + + <p>Emission of this signal indicates that the + <tp:member-ref>CodecOffer</tp:member-ref> property has changed to + <code>(Offer, Codecs)</code>.</p> + </tp:docstring> + + <arg name="Offer" type="o"> + <tp:docstring> + The object path of the new codec offer. This replaces any previous + codec offer. + </tp:docstring> + </arg> + + <arg name="Codecs" type="a{ua(usuua{ss})}" tp:type="Contact_Codec_Map"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The <tp:dbus-ref namespace="org.freedesktop.Telepathy.Call.Content" + >CodecOffer.DRAFT.RemoteContactCodecMap</tp:dbus-ref> property + of the codec offer.</p> + + <tp:rationale> + <p>Having the RemoteContactCodecMap property here saves a D-Bus + round-trip - it shouldn't be necessary to get the property + from the CodecOffer object, in practice.</p> + </tp:rationale> + </tp:docstring> + </arg> + </signal> + + <property name="CodecOffer" tp:name-for-bindings="Codec_Offer" + type="(oa{ua(usuua{ss})})" tp:type="Codec_Offering" access="read"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The object path to the current + <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call.Content" + >CodecOffer.DRAFT</tp:dbus-ref> object, and its + <tp:dbus-ref namespace="org.freedesktop.Telepathy.Call.Content" + >CodecOffer.DRAFT.RemoteContactCodecMap</tp:dbus-ref> property. + If the object path is "/" then there isn't an outstanding + codec offer, and the mapping MUST be empty.</p> + + <tp:rationale> + <p>Having the RemoteContactCodecMap property here saves a D-Bus + round-trip - it shouldn't be necessary to get the property + from the CodecOffer object, in practice.</p> + </tp:rationale> + + <p>Change notification is via + <tp:member-ref>NewCodecOffer</tp:member-ref> (which replaces the + value of this property with a new codec offer), and + <tp:member-ref>CodecsChanged</tp:member-ref> (which implies that + there is no longer any active codec offer).</p> + </tp:docstring> + </property> + </interface> +</node> +<!-- vim:set sw=2 sts=2 et ft=xml: --> diff --git a/extensions/Call_Stream.xml b/extensions/Call_Stream.xml new file mode 100644 index 000000000..302bd5e9b --- /dev/null +++ b/extensions/Call_Stream.xml @@ -0,0 +1,165 @@ +<?xml version="1.0" ?> +<node name="/Call_Stream" + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <tp:copyright>Copyright © 2009 Collabora Ltd.</tp:copyright> + <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright> + <tp:license xmlns="http://www.w3.org/1999/xhtml"> + <p>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.</p> + + <p>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.</p> + + <p>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 Street, Fifth Floor, Boston, MA + 02110-1301, USA.</p> + </tp:license> + + <interface name="org.freedesktop.Telepathy.Call.Stream.DRAFT" + tp:causes-havoc="experimental"> + <tp:added version="0.19.0">(draft 1)</tp:added> + + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + One stream inside a content + FIXME, direction should be a mapping of contact -> (bool)sending ? + </tp:docstring> + + <method name="SetSending" tp:name-for-bindings="Set_Sending"> + <p>Set the stream to start or stop sending media from the local + user to other contacts.</p> + + <arg name="Send" type="b" direction="in"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>If true, the local user's sending state should change + to Sending, if it isn't already.</p> + + <p>If false, the local user's sending state should change to None, + if it isn't already.</p> + </tp:docstring> + </arg> + + <tp:possible-errors> + <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented"> + <tp:docstring> + [FIXME: when?] + </tp:docstring> + </tp:error> + </tp:possible-errors> + </method> + + <method name="RequestReceiving" tp:name-for-bindings="Request_Receiving"> + <tp:docstring> + Request that a remote contact stops or starts sending on this stream. + </tp:docstring> + + <arg name="Contact" type="u" tp:type="Contact_Handle" direction="in"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Contact from which sending is requested</p> + </tp:docstring> + </arg> + + <arg name="Receive" type="b" direction="in"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>If true, request that the given contact starts to send media. + If false, request that the given contact stops sending media.</p> + </tp:docstring> + </arg> + + <tp:possible-errors> + <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented"> + <tp:docstring> + [FIXME: when?] + </tp:docstring> + </tp:error> + </tp:possible-errors> + </method> + + <signal name="SendersChanged" + tp:name-for-bindings="Senders_Changed"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + Emitted when <tp:member-ref>Senders</tp:member-ref> changes. + </tp:docstring> + + <arg name="Updates" type="a{uu}" tp:type="Contact_Sending_State_Map"> + <tp:docstring> + A mapping from channel-specific handles to their updated sending + state, whose keys include at least the senders who were added, + and the senders whose states changed. + </tp:docstring> + </arg> + <arg name="Removed" type="au" tp:type="Contact_Handle[]"> + <tp:docstring> + The channel-specific handles that were removed from + the keys of the Senders property, as a result of the + contact leaving this stream + </tp:docstring> + </arg> + </signal> + + <tp:enum name="Sending_State" type="u"> + <tp:docstring> + Tristate indicating whether a contact is sending media. + </tp:docstring> + + <tp:enumvalue suffix="None" value="0"> + <tp:docstring> + The contact is not sending media and has not been asked to do so. + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="Pending_Send" value="1"> + <tp:docstring> + The contact has been asked to start sending media. + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="Sending" value="2"> + <tp:docstring> + The contact is sending media. + </tp:docstring> + </tp:enumvalue> + </tp:enum> + + <tp:mapping name="Contact_Sending_State_Map"> + <tp:docstring> + A map from contacts to their sending state. + </tp:docstring> + <tp:member name="Contact" type="u" tp:type="Contact_Handle"> + </tp:member> + <tp:member name="Sending" type="u" tp:type="Sending_State"> + <tp:docstring> + </tp:docstring> + </tp:member> + </tp:mapping> + + <property name="Senders" tp:name-for-bindings="Senders" + type="a{uu}" access="read" tp:type="Contact_Sending_State_Map"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A map from contacts to their sending state.</p> + + <p>The local user's handle in this map (the <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Channel.Interface" + >Group.SelfHandle</tp:dbus-ref> if the channel implements + Group, or the <tp:dbus-ref + namespace="org.freedesktop.Telepathy" + >Connection.SelfHandle</tp:dbus-ref> otherwise) indicates + whether the local user is sending media. Media sent on this stream + should be assumed to be received, directly or indirectly, by every + other contact in the Senders mapping. Sending_State_Pending_Send + indicates that another contact has asked the local user to send + media.</p> + + <p>Other contacts' handles in this map indicate whether they are + sending media to the contacts in this stream. + Sending_State_Pending_Send indicates contacts who are not sending but + have been asked to do so.</p> + </tp:docstring> + </property> + </interface> +</node> +<!-- vim:set sw=2 sts=2 et ft=xml: --> diff --git a/extensions/Call_Stream_Endpoint.xml b/extensions/Call_Stream_Endpoint.xml new file mode 100644 index 000000000..fbab2cfa3 --- /dev/null +++ b/extensions/Call_Stream_Endpoint.xml @@ -0,0 +1,95 @@ +<?xml version="1.0" ?> +<node name="/Call_Stream_Endpoint" + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <tp:copyright>Copyright © 2009 Collabora Ltd.</tp:copyright> + <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright> + <tp:license xmlns="http://www.w3.org/1999/xhtml"> + <p>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.</p> + + <p>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.</p> + + <p>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 Street, Fifth Floor, Boston, MA + 02110-1301, USA.</p> + </tp:license> + + <interface name="org.freedesktop.Telepathy.Call.Stream.Endpoint.DRAFT" + tp:causes-havoc="experimental"> + <tp:added version="0.19.0">(draft 1)</tp:added> + + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + This object represents a set of candidates of one end-point. + </tp:docstring> + + <property name="RemoteCredentials" + tp:name-for-bindings="Remote_Credentials" + type="(ss)" tp:type="Stream_Credentials" access="read"> + </property> + + <signal name="RemoteCredentialsSet" + tp:name-for-bindings="Remote_Credentials_Set"> + <arg name="Username" type="s" /> + <arg name="Password" type="s" /> + </signal> + + <property name="RemoteCandidates" tp:name-for-bindings="Remote_Candidates" + type="a(usqa{sv})" tp:type="Candidate[]" access="read"> + </property> + + <signal name="RemoteCandidatesAdded" + tp:name-for-bindings="Remote_Candidates_Added"> + <arg name="Candidates" + type="a(usqa{sv})" tp:type="Candidate[]"/> + </signal> + + <signal name="CandidateSelected" + tp:name-for-bindings="Candidate_Selected"> + <arg name="Candidate" + type="(usqa{sv})" tp:type="Candidate"/> + </signal> + + <property name="SelectedCandidate" + tp:name-for-bindings="Selected_Candidate" + type="(usqa{sv})" tp:type="Candidate" access="read"> + </property> + + <method name="SetSelectedCandidate" + tp:name-for-bindings="Set_Selected_Candidate"> + <arg name="Candidate" + type="(usqa{sv})" tp:type="Candidate" direction="in"> + <tp:docstring> + </tp:docstring> + </arg> + </method> + + <property name="StreamState" tp:name-for-bindings="Stream_State" + type="u" tp:type="Media_Stream_State" + access="read"> + </property> + + <signal name="StreamStateChanged" + tp:name-for-bindings="Stream_State_Changed"> + <arg name="state" + type="u" tp:type="Media_Stream_State"/> + </signal> + + <method name="SetStreamState" + tp:name-for-bindings="Set_Stream_State"> + <arg name="State" type="u" tp:type="Media_Stream_State" + direction="in" /> + </method> + + <property name="Transport" tp:name-for-bindings="Transport" + type="u" tp:type="Stream_Transport_Type" access="read"> + </property> + + </interface> +</node> +<!-- vim:set sw=2 sts=2 et ft=xml: --> diff --git a/extensions/Call_Stream_Interface_Media.xml b/extensions/Call_Stream_Interface_Media.xml new file mode 100644 index 000000000..ccf903374 --- /dev/null +++ b/extensions/Call_Stream_Interface_Media.xml @@ -0,0 +1,400 @@ +<?xml version="1.0" ?> +<node name="/Call_Stream_Interface_Media" + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <tp:copyright>Copyright © 2009 Collabora Ltd.</tp:copyright> + <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright> + <tp:license xmlns="http://www.w3.org/1999/xhtml"> + <p>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.</p> + + <p>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.</p> + + <p>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 Street, Fifth Floor, Boston, MA + 02110-1301, USA.</p> + </tp:license> + + <interface name="org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT" + tp:causes-havoc="experimental"> + <tp:added version="0.19.0">(draft 1)</tp:added> + <tp:requires interface="org.freedesktop.Telepathy.Call.Stream"/> + + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + [FIXME] + </tp:docstring> + + <tp:method name="SetCredentials" tp:name-for-bindings="Set_Credentials"> + <tp:docstring> + Used to set the username fragment and password for streams that have + global credentials. + + <tp:rationale> + [FIXME: rationale?] + </tp:rationale> + </tp:docstring> + <arg name="Username" type="s" direction="in"/> + <arg name="Password" type="s" direction="in" /> + </tp:method> + + <tp:mapping name="Candidate_Info"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + Extra information about the candidate. Allowed and mandatory keys + depend on the transport protocol used. The following keys are commenly + used: + <dl> + <dt>Type (u)</dt> + <dd>type of candidate (host, srflx, prflx, relay)</dd> + + <dt>Foundation (s)</dt> + <dd>the foundation of this candiate</dd> + + <dt>Protocol (u) </dt> + <dd>Underlying protocol of the candidate (udp, tcp) </dd> + + <dt>Priority (u) </dt> + <dd>Priority of the candidate </dd> + + <dt>BaseIP (u) </dt> + <dd>Base IP of this candidate </dd> + + <dt>Username (s) </dt> + <dd>Username of this candidate + (only if credentials are per candidate)</dd> + + <dt>Password (s) </dt> + <dd>Password of this candidate + (only if credentials are per candidate)</dd> + + <dt>RawUDPFallback (b) </dt> + <dd>Indicate whether this candidate may be used to provide a UDP + fallback</dd> + </dl> + </tp:docstring> + <tp:member name="Key" type="s"> + <tp:docstring>One of the well-known keys documented here, or an + implementation-specific key</tp:docstring> + </tp:member> + <tp:member name="Value" type="v"> + <tp:docstring>The value corresponding to that key</tp:docstring> + </tp:member> + </tp:mapping> + + <tp:struct name="Candidate" array-name="Candidate_List"> + <tp:docstring>A Stream Candidate</tp:docstring> + + <tp:member name="Component" type="u"> + <tp:docstring>The component number</tp:docstring> + </tp:member> + <tp:member name="IP" type="s"> + <tp:docstring>The IP address to use</tp:docstring> + </tp:member> + <tp:member name="Port" type="q"> + <tp:docstring>The port number to use</tp:docstring> + </tp:member> + <tp:member name="Info" type="a{sv}" tp:type="Candidate_Info"> + <tp:docstring>Additional information about the candidate</tp:docstring> + </tp:member> + </tp:struct> + + <method name="AddCandidates" tp:name-for-bindings="Add_Candidates"> + <tp:docstring> + Add candidates to <tp:member-ref>LocalCandidates</tp:member-ref> + and signal them to the remote contact(s). + </tp:docstring> + + <arg name="candidates" direction="in" + type="a(usqa{sv})" tp:type="Candidate[]"> + <tp:docstring> + Candidates to be appended to + <tp:member-ref>LocalCandidates</tp:member-ref> + </tp:docstring> + </arg> + </method> + + <method name="CandidatesPrepared" + tp:name-for-bindings="Candidates_Prepared"> + <tp:docstring> + This indicates to the CM that the initial batch of candidates has been + added. + + <tp:rationale> + [FIXME: rationale] + </tp:rationale> + </tp:docstring> + </method> + + <tp:enum type="u" name="Stream_Transport_Type"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + A transport that can be used for streaming. + </tp:docstring> + + <tp:enumvalue suffix="Raw_UDP" value="0"> + <tp:docstring> + Raw UDP, with or without STUN. All streaming clients are assumed to + support this transport, so there is no handler capability token for + it in the <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Channel.Type" + >Call.DRAFT</tp:dbus-ref> interface. + [This corresponds to "none" or "stun" in the old Media.StreamHandler + interface.] + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="ICE" value="1"> + <tp:docstring> + Interactive Connectivity Establishment, as defined by the IETF MMUSIC + working group. + [FIXME: do we want this to cover both ICE-UDP and ICE-TCP, or split + them?] + [This corresponds to "ice-udp" in the old Media.StreamHandler + interface.] + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="GTalk_P2P" value="2"> + <tp:docstring> + Google Talk peer-to-peer connectivity establishment, as implemented + by libjingle 0.3. + [This corresponds to "gtalk-p2p" in the old Media.StreamHandler + interface.] + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="WLM_8_5" value="3"> + <tp:docstring> + The transport used by Windows Live Messenger 8.5 or later, which + resembles ICE draft 6. + [This corresponds to "wlm-8.5" in the old Media.StreamHandler + interface.] + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="WLM_2009" value="4"> + <tp:docstring> + The transport used by Windows Live Messenger 2009 or later, which + resembles ICE draft 19. + [This corresponds to "wlm-2009" in the old Media.StreamHandler + interface.] + </tp:docstring> + </tp:enumvalue> + </tp:enum> + + <property name="Transport" tp:name-for-bindings="Transport" + type="u" tp:type="Stream_Transport_Type" access="read"> + <tp:docstring> + The transport for this stream. This property is immutable. + </tp:docstring> + </property> + + <property name="LocalCandidates" tp:name-for-bindings="Local_Candidates" + type="a(usqa{sv})" tp:type="Candidate[]" access="read"> + <tp:docstring> + [FIXME]. Change notification is via + <tp:member-ref>LocalCandidatesAdded</tp:member-ref>. + </tp:docstring> + </property> + + <signal name="LocalCandidatesAdded" + tp:name-for-bindings="Local_Candidates_Added"> + <tp:docstring> + Emitted when local candidates are added to + <tp:member-ref>LocalCandidates</tp:member-ref>. + </tp:docstring> + + <arg name="Candidates" + type="a(usqa{sv})" tp:type="Candidate[]"> + <tp:docstring> + Candidates that have been appended to + <tp:member-ref>LocalCandidates</tp:member-ref> + </tp:docstring> + </arg> + </signal> + + <tp:struct name="Stream_Credentials"> + <tp:docstring>A username/password pair.</tp:docstring> + + <tp:member name="Username" type="s"> + <tp:docstring>The username</tp:docstring> + </tp:member> + + <tp:member name="Password" type="s"> + <tp:docstring>The password</tp:docstring> + </tp:member> + </tp:struct> + + <property name="LocalCredentials" tp:name-for-bindings="Local_Credentials" + type="(ss)" tp:type="Stream_Credentials" access="read"> + <tp:docstring> + [FIXME]. Change notification is via + <tp:member-ref>LocalCredentialsSet</tp:member-ref>. + </tp:docstring> + + </property> + + <signal name="LocalCredentialsSet" + tp:name-for-bindings="Local_Credentials_Set"> + <tp:docstring> + Emitted when the value of + <tp:member-ref>LocalCredentials</tp:member-ref> changes. + </tp:docstring> + + <arg name="Username" type="s" /> + <arg name="Password" type="s" /> + </signal> + + <property name="STUNServers" tp:name-for-bindings="STUN_Servers" + type="a(sq)" tp:type="Socket_Address_IP[]" access="read"> + <tp:docstring> + The IP addresses of possible STUN servers to use for NAT traversal, as + dotted-quad IPv4 address literals or RFC2373 IPv6 address literals. + This property cannot change once the stream has been created, so there + is no change notification. The IP addresses MUST NOT be given as DNS + hostnames. + + <tp:rationale> + High-quality connection managers already need an asynchronous + DNS resolver, so they might as well resolve this name to an IP + to make life easier for streaming implementations. + </tp:rationale> + </tp:docstring> + </property> + + <property name="RelayInfo" type="aa{sv}" access="read" + tp:type="String_Variant_Map[]" tp:name-for-bindings="Relay_Info"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A list of mappings describing TURN or Google relay servers + available for the client to use in its candidate gathering, as + determined from the protocol. Map keys are:</p> + + <dl> + <dt><code>ip</code> - s</dt> + <dd>The IP address of the relay server as a dotted-quad IPv4 + address literal or an RFC2373 IPv6 address literal. This MUST NOT + be a DNS hostname. + + <tp:rationale> + High-quality connection managers already need an asynchronous + DNS resolver, so they might as well resolve this name to an IP + and make life easier for streaming implementations. + </tp:rationale> + </dd> + + <dt><code>type</code> - s</dt> + <dd> + <p>Either <code>udp</code> for UDP (UDP MUST be assumed if this + key is omitted), <code>tcp</code> for TCP, or + <code>tls</code>.</p> + + <p>The precise meaning of this key depends on the + <tp:member-ref>Transport</tp:member-ref> property: if + Transport is ICE, <code>tls</code> means + TLS over TCP as referenced by ICE draft 19, and if + Transport is GTalk_P2P, <code>tls</code> means + a fake SSL session over TCP as implemented by libjingle.</p> + </dd> + + <dt><code>port</code> - q</dt> + <dd>The UDP or TCP port of the relay server as an ASCII unsigned + integer</dd> + + <dt><code>username</code> - s</dt> + <dd>The username to use</dd> + + <dt><code>password</code> - s</dt> + <dd>The password to use</dd> + + <dt><code>component</code> - u</dt> + <dd>The component number to use this relay server for, as an + ASCII unsigned integer; if not included, this relay server + may be used for any or all components. + + <tp:rationale> + In ICE draft 6, as used by Google Talk, credentials are only + valid once, so each component needs relaying separately. + </tp:rationale> + </dd> + </dl> + + <tp:rationale> + <p>An equivalent of the gtalk-p2p-relay-token property on + MediaSignalling channels is not included here. The connection + manager should be responsible for making the necessary HTTP + requests to turn the token into a username and password.</p> + </tp:rationale> + + <p>The type of relay server that this represents depends on + the value of the <tp:member-ref>Transport</tp:member-ref> + property. If Transport is ICE, this is a TURN server; + if Transport is GTalk_P2P, this is a Google relay server; + otherwise, the meaning of RelayInfo is undefined.</p> + + <p>If relaying is not possible for this stream, the list is empty.</p> + </tp:docstring> + </property> + + <signal name="ServerInfoRetrieved" + tp:name-for-bindings="Server_Info_Retrieved"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Signals that the initial information about STUN and Relay servers + has been retrieved, i.e. the + <tp:member-ref>RetrievedServerInfo</tp:member-ref> property is now + true.</p> + </tp:docstring> + </signal> + + <property name="RetrievedServerInfo" type="b" + tp:name-for-bindings="Retrieved_Server_Info" access="read"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>True if the initial information about STUN servers and Relay servers + has been retrieved. Change notification is via the + <tp:member-ref>ServerInfoRetrieved</tp:member-ref> signal.</p> + + <tp:rationale> + <p>Streaming implementations that can't cope with STUN and relay + servers being added later SHOULD wait for this property + to become true before proceeding.</p> + </tp:rationale> + </tp:docstring> + </property> + + <signal name="EndpointsChanged" + tp:name-for-bindings="Endpoints_Changed"> + <tp:docstring> + Emitted when the <tp:member-ref>Endpoints</tp:member-ref> property + changes. + </tp:docstring> + + <arg name="EndpointsAdded" type="ao"> + <tp:docstring> + Endpoints that were added. + </tp:docstring> + </arg> + + <arg name="EndpointsRemoved" type="ao"> + <tp:docstring> + Endpoints that no longer exist. + </tp:docstring> + </arg> + </signal> + + <property name="Endpoints" tp:name-for-bindings="Endpoints" + type="ao" access="read"> + <p> The list of endpoints + <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call.Stream" + >Endpoint.DRAFT</tp:dbus-ref> + that exist for this stream. + </p> + + <p>Change notification is via the + <tp:member-ref>EndpointsChanged</tp:member-ref> signal.</p> + </property> + </interface> +</node> +<!-- vim:set sw=2 sts=2 et ft=xml: --> diff --git a/extensions/Channel_Type_Call.xml b/extensions/Channel_Type_Call.xml new file mode 100644 index 000000000..702eb1759 --- /dev/null +++ b/extensions/Channel_Type_Call.xml @@ -0,0 +1,931 @@ +<?xml version="1.0" ?> +<node name="/Channel_Type_Call" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <tp:copyright>Copyright © 2009 Collabora Limited</tp:copyright> + <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright> + <tp:license> + 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + </tp:license> + <interface name="org.freedesktop.Telepathy.Channel.Type.Call.DRAFT" + tp:causes-havoc="experimental"> + <tp:added version="0.19.0">(draft 1)</tp:added> + + <tp:requires interface="org.freedesktop.Telepathy.Channel"/> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A channel type for making audio and video calls.</p> + + <p>A Call channel can have one or more <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call">Content.DRAFT</tp:dbus-ref> + objects, which represent the actual Media that forms the Call (e.g. an + audio content and a video content).</p> + </tp:docstring> + + <method name="Ringing" tp:name-for-bindings="Ringing"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Indicate that the local user has been alerted about the incoming + call.</p> + + <p>This method is only useful if the channel's + <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel" + >Requested</tp:dbus-ref> property is false, and the + <tp:member-ref>CallState</tp:member-ref> is + Call_State_Pending_Initiator. While this is the case, + this method SHOULD change the + <tp:member-ref>CallFlags</tp:member-ref> to include + Call_Flag_Ringing, and notify the remote contact that the local + user has been alerted (if the protocol implements this); repeated + calls to this method SHOULD succeed, but have no further effect.</p> + + <p>In all other states, this method SHOULD fail with the error + NotAvailable.</p> + </tp:docstring> + + <tp:possible-errors> + <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"> + <tp:docstring> + The call was <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Channel" + >Requested</tp:dbus-ref>, so ringing does not make sense. + </tp:docstring> + </tp:error> + <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"> + <tp:docstring> + The call is no longer in state Call_State_Pending_Initiator. + </tp:docstring> + </tp:error> + </tp:possible-errors> + </method> + + <method name="Accept" tp:name-for-bindings="Accept"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>For incoming calls in state Call_State_Pending_Receiver, accept the + incoming call; this changes the + <tp:member-ref>CallState</tp:member-ref> to Call_State_Accepted.</p> + + <p>For outgoing calls in state Call_State_Pending_Initiator, actually + call the remote contact; this changes the + <tp:member-ref>CallState</tp:member-ref> to + Call_State_Pending_Receiver.</p> + + <p>Otherwise, this method SHOULD fail with the error NotAvailable.</p> + + <p>This method should be called exactly once per Call, by whatever + client (user interface) is handling the channel.</p> + + <p>When this method is called, for each <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call" + >Content.DRAFT</tp:dbus-ref> whose <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call.Content.DRAFT" + >Disposition</tp:dbus-ref> is Call_Content_Disposition_Initial, + any streams where the self-handle's sending state in <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call.Stream.DRAFT" + >Senders</tp:dbus-ref> is Sending_State_Pending_Send + will be moved to Sending_State_Sending as if <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call.Stream.DRAFT" + >SetSending</tp:dbus-ref>(TRUE) had been called.</p> + </tp:docstring> + + <tp:possible-errors> + <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"> + <tp:docstring> + The call is not in one of the states where this method makes sense. + </tp:docstring> + </tp:error> + </tp:possible-errors> + </method> + + <method name="Hangup" tp:name-for-bindings="Hangup"> + <tp:docstring> + Request that the call is ended. + </tp:docstring> + + <arg direction="in" name="Reason" + type="u" tp:type="Call_State_Change_Reason"> + <tp:docstring> + A generic hangup reason. + </tp:docstring> + </arg> + + <arg direction="in" name="Detailed_Hangup_Reason" + type="s" tp:type="DBus_Error_Name"> + <tp:docstring> + A more specific reason for the call hangup, if one is available, or + an empty string otherwise. + </tp:docstring> + </arg> + + <arg direction="in" name="Message" type="s"> + <tp:docstring> + A human-readable message to be sent to the remote contact(s). + + <tp:rationale> + XMPP Jingle allows calls to be terminated with a human-readable + message. + </tp:rationale> + </tp:docstring> + </arg> + + <tp:possible-errors> + <tp:error name="org.freedesktop.Telepathy.Error.NotAvailable"> + <tp:docstring> + The call has already been ended. + </tp:docstring> + </tp:error> + </tp:possible-errors> + </method> + + <method name="AddContent" tp:name-for-bindings="Add_Content"> + <tp:docstring> + [FIXME] + </tp:docstring> + <arg direction="in" name="Content_Name" type="s"> + <tp:docstring> + The suggested name of the content to add + + <tp:rationale> + [FIXME: rationale] + </tp:rationale> + </tp:docstring> + </arg> + <arg direction="in" name="Content_Type" type="u" + tp:type="Media_Stream_Type"> + <tp:docstring> + The media type of the content to add + </tp:docstring> + </arg> + <arg direction="out" name="Content" type="o"> + <tp:docstring> + Path to the newly-created <tp:dbus-ref + namespace="org.freedesktop.Telepathy" + >Call.Content.DRAFT</tp:dbus-ref> object. + </tp:docstring> + </arg> + + <tp:possible-errors> + <tp:error name="org.freedesktop.Telepathy.Error.InvalidArgument"> + <tp:docstring> + [FIXME: when?] + </tp:docstring> + </tp:error> + <tp:error name="org.freedesktop.Telepathy.Error.NotImplemented"> + <tp:docstring> + [FIXME: when?] + </tp:docstring> + </tp:error> + </tp:possible-errors> + </method> + + <signal name="ContentAdded" + tp:name-for-bindings="Content_Added"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Emitted when a new <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call" + >Content.DRAFT</tp:dbus-ref> is added to the call.</p> + </tp:docstring> + <arg name="Content" type="o"> + <tp:docstring> + Path to the newly-created <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call" + >Content.DRAFT</tp:dbus-ref> object. + </tp:docstring> + </arg> + <arg name="Content_Type" type="u" tp:type="Media_Stream_Type"> + <tp:docstring> + The media type of the content which was added + </tp:docstring> + </arg> + </signal> + + <signal name="ContentRemoved" tp:name-for-bindings="Content_Removed"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Emitted when a <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call" + >Content.DRAFT</tp:dbus-ref> is removed from the call.</p> + </tp:docstring> + <arg name="Content" type="o"> + <tp:docstring> + The <tp:dbus-ref namespace="org.freedesktop.Telepathy.Call" + >Content.DRAFT</tp:dbus-ref> which was removed. + </tp:docstring> + </arg> + </signal> + + <property name="Contents" type="ao" access="read" + tp:name-for-bindings="Contents"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The list of + <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call">Content.DRAFT</tp:dbus-ref> + objects that are part of this call. Change notification + is via the <tp:member-ref>ContentAdded</tp:member-ref> and + <tp:member-ref>ContentRemoved</tp:member-ref> signals. + </p> + </tp:docstring> + </property> + + <tp:enum type="u" name="Call_State"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The state of a call, as a whole.</p> + + <p>The allowed transitions are:</p> + + <ul> + <li>Pending_Initiator → Pending_Receiver (for outgoing calls, + when <tp:member-ref>Accept</tp:member-ref> is called)</li> + <li>Pending_Receiver → Accepted (for incoming calls, when + <tp:member-ref>Accept</tp:member-ref> is called; for outgoing + calls to a contact, when the remote contact accepts the call; + for joining a conference call, when the local user successfully + joins the conference)</li> + <li>Accepted → Pending_Receiver (when transferred to another + contact)</li> + <li>any state → Ended (when the call is terminated normally, or + when an error occurs)</li> + </ul> + + <p>Clients MAY consider unknown values from this enum to be an + error - additional values will not be defined after the Call + specification is declared to be stable.</p> + </tp:docstring> + + <tp:enumvalue suffix="Unknown" value = "0"> + <tp:docstring> + The call state is not known. This call state MUST NOT appear as a + value of the <tp:member-ref>CallState</tp:member-ref> property, but + MAY be used by client code to represent calls whose state is as yet + unknown. + </tp:docstring> + </tp:enumvalue> + <tp:enumvalue suffix="Pending_Initiator" value = "1"> + <tp:docstring> + The initiator of the call hasn't accepted the call yet. This state + only makes sense for outgoing calls, where it means that the local + user has not yet sent any signalling messages to the remote user(s), + and will not do so until <tp:member-ref>Accept</tp:member-ref> is + called. + </tp:docstring> + </tp:enumvalue> + <tp:enumvalue suffix="Pending_Receiver" value = "2"> + <tp:docstring> + The receiver (the contact being called) hasn't accepted the call yet. + </tp:docstring> + </tp:enumvalue> + <tp:enumvalue suffix="Accepted" value = "3"> + <tp:docstring> + The contact being called has accepted the call. + </tp:docstring> + </tp:enumvalue> + <tp:enumvalue suffix="Ended" value = "4"> + <tp:docstring> + The call has ended, either via normal termination or an error. + </tp:docstring> + </tp:enumvalue> + </tp:enum> + + <tp:flags name="Call_Flags" value-prefix="Call_Flag" type="u"> + <tp:docstring> + A set of flags representing the status of the call as a whole, + providing more specific information than the + <tp:member-ref>CallState</tp:member-ref>. Many of these flags only make + sense in a particular state. + </tp:docstring> + + <tp:flag suffix="Locally_Ringing" value="1"> + <tp:docstring> + The local contact has been alerted about the call but has not + responded; if possible, the remote contact(s) have been informed of + this fact. This flag only makes sense on incoming calls in + state Call_State_Pending_Receiver. It SHOULD be set when + <tp:member-ref>Ringing</tp:member-ref> is called successfully, and + unset when the state changes. + </tp:docstring> + </tp:flag> + + <tp:flag suffix="Queued" value="2"> + <tp:docstring> + The contact is temporarily unavailable, and the call has been placed + in a queue (e.g. 182 Queued in SIP, or call-waiting in telephony). + This flag only makes sense on outgoing 1-1 calls in + state Call_State_Pending_Receiver. It SHOULD be set or unset + according to informational messages from other contacts. + </tp:docstring> + </tp:flag> + + <tp:flag suffix="Locally_Held" value="4"> + <tp:docstring> + The call has been put on hold by the local user, e.g. using the + <tp:dbus-ref namespace="org.freedesktop.Telepathy.Channel.Interface" + >Hold</tp:dbus-ref> interface. This flag SHOULD only be set if + there is at least one Content, and all Contents are locally held; + it makes sense on calls in state Call_State_Pending_Receiver or + Call_State_Accepted. + + <tp:rationale> + Otherwise, in transient situations where some but not all contents + are on hold, UIs would falsely indicate that the call as a whole + is on hold, which could lead to the user saying something they'll + regret, while under the impression that the other contacts can't + hear them! + </tp:rationale> + </tp:docstring> + </tp:flag> + + <tp:flag suffix="Forwarded" value="8"> + <tp:docstring> + The initiator of the call originally called a contact other than the + current recipient of the call, but the call was then forwarded or + diverted. This flag only makes sense on outgoing calls, in state + Call_State_Pending_Receiver or Call_State_Accepted. It SHOULD be + set or unset according to informational messages from other contacts. + </tp:docstring> + </tp:flag> + + <tp:flag suffix="In_Progress" value="16"> + <tp:docstring> + Progress has been made in placing the outgoing call, but the + contact may not have been made aware of the call yet + (so the Ringing state is not appropriate). This corresponds to SIP's + status code 183 Session Progress, and could be used when the + outgoing call has reached a gateway, for instance. + This flag only makes sense on outgoing calls in state + Call_State_Pending_Receiver, and SHOULD be set or unset according to + informational messages from servers, gateways and other + infrastructure. + </tp:docstring> + </tp:flag> + + <tp:flag suffix="Clearing" value="32"> + <tp:docstring> + This flag only occurs when the CallState is Ended. The call with + this flag set has ended, but not all resources corresponding to the + call have been freed yet. + + Depending on the protocol there might be some audible feedback while + the clearing flag is set. + + <tp:rationale> + In calls following the ITU-T Q.931 standard there is a period of + time between the call ending and the underlying channel being + completely free for re-use. + </tp:rationale> + </tp:docstring> + </tp:flag> + </tp:flags> + + <property name="CallStateDetails" + tp:name-for-bindings="Call_State_Details" type="a{sv}" access="read"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A map used to provide optional extensible details for the + <tp:member-ref>CallState</tp:member-ref>, + <tp:member-ref>CallFlags</tp:member-ref> and/or + <tp:member-ref>CallStateReason</tp:member-ref>.</p> + + <p>Well-known keys and their corresponding value types include:</p> + + <dl> + <dt>hangup-message - s</dt> + <dd>An optional human-readable message sent when the call was ended, + corresponding to the Message argument to the + <tp:member-ref>Hangup</tp:member-ref> method. This is only + applicable when the call state is Call_State_Ended. + <tp:rationale> + XMPP Jingle can send such messages. + </tp:rationale> + </dd> + + <dt>queue-message - s</dt> + <dd>An optional human-readable message sent when the local contact + is being held in a queue. This is only applicable when + Call_Flag_Queued is in the call flags. + <tp:rationale> + SIP 182 notifications can have human-readable messages attached. + </tp:rationale> + </dd> + + <dt>debug-message - s</dt> + <dd>A message giving further details of any error indicated by the + <tp:member-ref>CallStateReason</tp:member-ref>. This will not + normally be localized or suitable for display to users, and is only + applicable when the call state is Call_State_Ended.</dd> + </dl> + </tp:docstring> + </property> + + <property name="CallState" type="u" access="read" + tp:name-for-bindings="Call_State" tp:type="Call_State"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The current high-level state of this call. The + <tp:member-ref>CallFlags</tp:member-ref> provide additional + information, and the <tp:member-ref>CallStateReason</tp:member-ref> + and <tp:member-ref>CallStateDetails</tp:member-ref> explain the + reason for the current values for those properties.</p> + + <p>Clients MAY consider unknown values in this property to be an + error.</p> + </tp:docstring> + </property> + + <property name="CallFlags" type="u" access="read" + tp:name-for-bindings="Call_Flags" tp:type="Call_Flags"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Flags representing the status of the call as a whole, + providing more specific information than the + <tp:member-ref>CallState</tp:member-ref>.</p> + + <p>Clients are expected to ignore unknown flags in this property, + without error.</p> + </tp:docstring> + </property> + + <tp:enum name="Call_State_Change_Reason" type="u"> + <tp:docstring> + A simple representation of the reason for a change in the call's + state, which may be used by simple clients, or used as a fallback + when the DBus_Reason member of a <tp:type>Call_State_Reason</tp:type> + struct is not understood. + </tp:docstring> + + <tp:enumvalue suffix="Unknown" value="0"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + We just don't know. Unknown values of this enum SHOULD also be + treated like this. + </tp:docstring> + </tp:enumvalue> + + <tp:enumvalue suffix="User_Requested" value="1"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The change was requested by the contact indicated by the Actor + member of a <tp:type>Call_State_Reason</tp:type> struct.</p> + + <p>If the Actor is the local user, the DBus_Reason SHOULD be the + empty string.</p> + + <p>If the Actor is a remote user, the DBus_Reason SHOULD be the empty + string if the call was terminated normally, but MAY be a non-empty + error name to indicate error-like call termination reasons (call + rejected as busy, kicked from a conference by a moderator, etc.).</p> + </tp:docstring> + </tp:enumvalue> + </tp:enum> + + <tp:struct name="Call_State_Reason"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A description of the reason for a change to the + <tp:member-ref>CallState</tp:member-ref> and/or + <tp:member-ref>CallFlags</tp:member-ref>.</p> + </tp:docstring> + + <tp:member type="u" tp:type="Contact_Handle" name="Actor"> + <tp:docstring> + The contact responsible for the change, or 0 if no contact was + responsible. + </tp:docstring> + </tp:member> + + <tp:member type="u" tp:type="Call_State_Change_Reason" name="Reason"> + <tp:docstring> + The reason, chosen from a limited set of possibilities defined by + the Telepathy specification. + </tp:docstring> + </tp:member> + + <tp:member type="s" tp:type="DBus_Error_Name" name="DBus_Reason"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A specific reason for the change, which may be a D-Bus error in + the Telepathy namespace, a D-Bus error in any other namespace + (for implementation-specific errors), or the empty string to + indicate that the state change was not an error.</p> + + <p>This SHOULD be an empty string for changes to any state other + than Ended.</p> + + <p>The errors Cancelled and Terminated SHOULD NOT be used here; + an empty string SHOULD be used instead.</p> + + <tp:rationale> + <p>Those error names are used to indicate normal call + termination by the local user or another user, respectively, + in contexts where a D-Bus error name must appear.</p> + </tp:rationale> + </tp:docstring> + </tp:member> + </tp:struct> + + <property name="CallStateReason" tp:name-for-bindings="Call_State_Reason" + type="(uus)" access="read" tp:type="Call_State_Reason"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The reason for the last change to the + <tp:member-ref>CallState</tp:member-ref> and/or + <tp:member-ref>CallFlags</tp:member-ref>. The + <tp:member-ref>CallStateDetails</tp:member-ref> MAY provide additional + information.</p> + </tp:docstring> + </property> + + <signal name="CallStateChanged" + tp:name-for-bindings="Call_State_Changed"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Emitted when the state of the call as a whole changes.</p> + + <p>This signal is emitted for any change in the properties + corresponding to its arguments, even if the other properties + referenced remain unchanged.</p> + </tp:docstring> + + <arg name="Call_State" type="u" tp:type="Call_State"> + <tp:docstring> + The new value of the <tp:member-ref>CallState</tp:member-ref> + property. + </tp:docstring> + </arg> + + <arg name="Call_Flags" type="u" tp:type="Call_Flags"> + <tp:docstring> + The new value of the <tp:member-ref>CallFlags</tp:member-ref> + property. + </tp:docstring> + </arg> + + <arg name="Call_State_Reason" type="(uus)"> + <tp:docstring> + The new value of the <tp:member-ref>CallStateReason</tp:member-ref> + property. + </tp:docstring> + </arg> + + <arg name="Call_State_Details" type="a{sv}"> + <tp:docstring> + The new value of the <tp:member-ref>CallStateDetails</tp:member-ref> + property. + </tp:docstring> + </arg> + </signal> + + <property name="HardwareStreaming" tp:name-for-bindings="Hardware_Streaming" + type="b" access="read"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>If this property is TRUE, all of the media streaming is done by some + mechanism outside the scope of Telepathy.</p> + + <tp:rationale> + <p>A connection manager might be intended for a specialized hardware + device, which will take care of the audio streaming (e.g. + telepathy-yafono, which uses GSM hardware which does the actual + audio streaming for the call).</p> + </tp:rationale> + + <p>If this is FALSE, the handler is responsible for doing the actual + media streaming for at least some contents itself. Those contents + will have the <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call.Content.Interface" + >Media.DRAFT</tp:dbus-ref> interface, to communicate the necessary + information to a streaming implementation. Connection managers SHOULD + operate like this, if possible.</p> + + <tp:rationale> + <p>Many connection managers (such as telepathy-gabble) only do the + call signalling, and expect the client to do the actual streaming + using something like + <a href="http://farsight.freedesktop.org/">Farsight</a>, to improve + latency and allow better UI integration.</p> + </tp:rationale> + </tp:docstring> + </property> + + <tp:flags type="u" name="Call_Member_Flags" value-prefix="Call_Member_Flag"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A set of flags representing the status of a remote contact in a + call.</p> + + <p>It is protocol- and client-specific whether a particular contact + will ever have a particular flag set on them, and Telepathy clients + SHOULD NOT assume that a flag will ever be set.</p> + + <tp:rationale> + <p>180 Ringing in SIP, and its equivalent in XMPP, are optional + informational messages, and implementations are not required + to send them. The same applies to the messages used to indicate + hold state.</p> + </tp:rationale> + </tp:docstring> + + <tp:flag suffix="Ringing" value = "1"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The remote contact's client has told us that the contact has been + alerted about the call but has not responded.</p> + + <tp:rationale> + <p>This is a flag per member, not a flag for the call as a whole, + because in Muji conference calls, you could invite someone and + have their state be "ringing" for a while.</p> + </tp:rationale> + </tp:docstring> + </tp:flag> + + <tp:flag suffix="Held" value = "2"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The call member has put this call on hold.</p> + + <tp:rationale> + <p>This is a flag per member, not a flag for the call as a whole, + because in conference calls, any member could put the conference + on hold.</p> + </tp:rationale> + </tp:docstring> + </tp:flag> + </tp:flags> + + <tp:mapping name="Call_Member_Map" array-name="Call_Member_Map_List"> + <tp:docstring>A mapping from handles to their current state in the call. + </tp:docstring> + <tp:member type="u" tp:type="Handle" name="key"/> + <tp:member type="u" tp:type="Call_Member_Flags" name="Flag"/> + </tp:mapping> + + <signal name="CallMembersChanged" + tp:name-for-bindings="Call_Members_Changed"> + <tp:docstring> + Emitted when the <tp:member-ref>CallMembers</tp:member-ref> property + changes in any way, either because contacts have been added to the + call, contacts have been removed from the call, or contacts' flags + have changed. + </tp:docstring> + + <arg name="Flags_Changed" type="a{uu}" tp:type="Call_Member_Map"> + <tp:docstring> + A map from members of the call to their new call member flags, + including at least the members who have been added to + <tp:member-ref>CallMembers</tp:member-ref>, and the members whose + flags have changed. + </tp:docstring> + </arg> + <arg name="Removed" type="au" tp:type="Contact_Handle[]"> + <tp:docstring> + A list of members who have left the call, i.e. keys to be removed + from <tp:member-ref>CallMembers</tp:member-ref>. + </tp:docstring> + </arg> + </signal> + + <property name="CallMembers" tp:name-for-bindings="Call_Members" + type="a{uu}" access="read" tp:type="Call_Member_Map"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + A mapping from the remote contacts that are part of this call to flags + discribing their status. This mapping never has the local user's handle + as a key. + </tp:docstring> + </property> + + <property name="InitialTransport" tp:name-for-bindings="Initial_Transport" + type="s" access="read"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p> + If set on a requested channel this indicates the transport that + should be used for this call. + <tp:rationale> + When implementing a voip gateway one wants the outgoing leg of the + gatewayed to have the same transport as the incoming leg. This + property allows the gateway to request a Call with the right + transport from the CM. + </tp:rationale> + </p> + </tp:docstring> + </property> + + <property name="InitialAudio" tp:name-for-bindings="Initial_Audio" + type="b" access="read"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>If set to true in a channel request that will create a new channel, + the connection manager should immediately attempt to establish an + audio stream to the remote contact, making it unnecessary for the + client to call + <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Channel.Type.Call.DRAFT">AddContent</tp:dbus-ref>. + </p> + + <p>If this property, or InitialVideo, is passed to EnsureChannel + (as opposed to CreateChannel), the connection manager SHOULD ignore + these properties when checking whether it can return an existing + channel as suitable; these properties only become significant when + the connection manager has decided to create a new channel.</p> + + <p>If true on a requested channel, this indicates that the audio + stream has already been requested and the client does not need to + call RequestStreams, although it MAY still do so.</p> + + <p>If true on an unrequested (incoming) channel, this indicates that + the remote contact initially requested an audio stream; this does + not imply that that audio stream is still active (as indicated by + <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Channel.Type.Call.DRAFT">Contents</tp:dbus-ref>).</p> + + <p>This property is immutable (cannot change), and therefore SHOULD + appear wherever immutable properties are reported, e.g. <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">NewChannels</tp:dbus-ref> + signals.</p> + + <tp:rationale><p>This reduces D-Bus round trips.</p></tp:rationale> + + <p>Connection managers capable of signalling audio calls to contacts + SHOULD include a channel class in <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">RequestableChannelClasses</tp:dbus-ref> + with <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Channel">ChannelType</tp:dbus-ref> + <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Channel.Type">Call.DRAFT</tp:dbus-ref> + and <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Channel">TargetHandleType</tp:dbus-ref> + = Contact in the fixed properties dictionary, and InitialAudio + (and also InitialVideo, if applicable) in the allowed properties + list. Clients wishing to discover whether a connection manager + can signal audio and/or video calls SHOULD use this information.</p> + + <tp:rationale> + <p>Not all protocols support signalling video calls, and it would be + possible (although unlikely) to have a protocol where only video, + and not audio, could be signalled.</p> + </tp:rationale> + + <p>Connection managers that support the <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Connection.Interface">ContactCapabilities</tp:dbus-ref> + interface SHOULD represent the capabilities of receiving audio + and/or video calls by including a channel class in + a contact's capabilities with ChannelType = Call + in the fixed properties dictionary, and InitialAudio and/or + InitialVideo in the allowed properties list. Clients wishing to + discover whether a particular contact is likely to be able to + receive audio and/or video calls SHOULD use this information.</p> + + <tp:rationale> + <p>Not all clients support video calls, and it would also be + possible (although unlikely) to have a client which could only + stream video, not audio.</p> + </tp:rationale> + + <p>Clients that are willing to receive audio and/or video calls + SHOULD include the following among their channel classes if + calling <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Connection.Interface.ContactCapabilities">UpdateCapabilities</tp:dbus-ref> + (clients of a <tp:dbus-ref + namespace="org.freedesktop.Telepathy">ChannelDispatcher</tp:dbus-ref> + SHOULD instead arrange for the ChannelDispatcher to do this, + by including the filters in their <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Client.Handler">HandlerChannelFilter</tp:dbus-ref> + properties):</p> + + <ul> + <li>{ ChannelType = Call }</li> + <li>{ ChannelType = Call, InitialAudio = true } + if receiving calls with audio is supported</li> + <li>{ ChannelType = Call, InitialVideo = true } + if receiving calls with video is supported</li> + </ul> + + <tp:rationale> + <p>Connection managers for protocols with capability discovery, + like XMPP, need this information to advertise the appropriate + capabilities for their protocol.</p> + </tp:rationale> + </tp:docstring> + </property> + + <property name="InitialVideo" tp:name-for-bindings="Initial_Video" + type="b" access="read"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The same as <tp:member-ref>InitialAudio</tp:member-ref>, but for + a video stream. This property is immutable (cannot change).</p> + + <p>In particular, note that if this property is false, this does not + imply that an active video stream has not been added, only that no + video stream was active at the time the channel appeared.</p> + + <p>This property is the correct way to discover whether connection + managers, contacts etc. support video calls; it appears in + capabilities structures in the same way as InitialAudio.</p> + </tp:docstring> + </property> + + <property name="MutableContents" tp:name-for-bindings="Mutable_Contents" + type="b" access="read"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>If <tt>True</tt>, a stream of a different content type can be added + after the Channel has been requested </p> + + <p>If this property is missing, clients SHOULD assume that it is false, + and thus that the channel's streams cannot be changed once the call + has started.</p> + + <p>If this property isn't present in the "allowed" set in any of the + Call entries contact capabilities, then user interfaces MAY choose to + show a separate "call" option for each class of call.</p> + + <tp:rationale> + <p>For example, once an audio-only Google Talk call has started, + it is not possible to add a video stream; both audio and video + must be requested at the start of the call if video is desired. + User interfaces may use this pseudo-capability as a hint to + display separate "Audio call" and "Video call" buttons, rather + than a single "Call" button with the option to add and remove + video once the call has started for contacts without this flag. + </p> + </tp:rationale> + + <p>This property is immutable, and therefore SHOULD be announced + in <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Connection.Interface.Requests">NewChannels</tp:dbus-ref>, + etc.</p> + </tp:docstring> + </property> + + <tp:handler-capability-token name="gtalk-p2p"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The client can implement streaming for streams whose <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref> + property is Stream_Transport_Type_GTalk_P2P.</p> + </tp:docstring> + </tp:handler-capability-token> + + <tp:handler-capability-token name="ice"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The client can implement streaming for streams whose <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref> + property is Stream_Transport_Type_ICE.</p> + </tp:docstring> + </tp:handler-capability-token> + + <tp:handler-capability-token name="wlm-8.5"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The client can implement streaming for streams whose <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref> + property is Stream_Transport_Type_WLM_8_5.</p> + </tp:docstring> + </tp:handler-capability-token> + + <tp:handler-capability-token name="wlm-2009"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The client can implement streaming for streams whose <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Call.Stream.Interface.Media.DRAFT">Transport</tp:dbus-ref> + property is Stream_Transport_Type_WLM_2009.</p> + </tp:docstring> + </tp:handler-capability-token> + + <tp:handler-capability-token name="video/h264" is-family="yes"> + <tp:docstring> + <p>The client supports media streaming with H264 (etc.).</p> + + <p>This handler capability token is a one of a family + of similar tokens: for any other audio or video codec whose MIME + type is audio/<em>subtype</em> or video/<em>subtype</em>, a handler + capability token of this form may exist (the subtype MUST appear + in lower case in this context). Clients MAY support more + codecs than they explicitly advertise support for; clients SHOULD + explicitly advertise support for their preferred codec(s), and + for codecs like H264 that are, in practice, significant in codec + negotiation.</p> + + <tp:rationale> + <p>For instance, the XMPP capability used by the Google Video + Chat web client to determine whether a client is compatible + with it requires support for H264 video, so an XMPP + connection manager that supports this version of Jingle should + not advertise the Google Video Chat capability unless there + is at least one installed client that declares that it supports + <code>video/h264</code> on Call channels.</p> + </tp:rationale> + + <p>For example, a client could advertise support for + Speex, Theora and H264 by having three + handler capability tokens, + <code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/audio/speex</code>, + <code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/video/theora</code> and + <code>org.freedesktop.Telepathy.Channel.Type.Call.DRAFT/video/h264</code>, + in its <tp:dbus-ref + namespace="org.freedesktop.Telepathy.Client.Handler">Capabilities</tp:dbus-ref> + property.</p> + + <p>Clients MAY have media signalling abilities without explicitly + supporting any particular codec, and connection managers SHOULD + support this usage.</p> + + <tp:rationale> + <p>This is necessary to support gatewaying between two Telepathy + connections, in which case the available codecs might not be + known to the gatewaying process.</p> + </tp:rationale> + </tp:docstring> + </tp:handler-capability-token> + + </interface> +</node> +<!-- vim:set sw=2 sts=2 et ft=xml: --> diff --git a/extensions/Makefile.am b/extensions/Makefile.am new file mode 100644 index 000000000..9e30a81f2 --- /dev/null +++ b/extensions/Makefile.am @@ -0,0 +1,172 @@ +# This directory re-uses telepathy-glib's code generation mechanisms to +# generate code for interfaces that aren't stable enough for telepathy-glib +# yet, so we can start to adapt example code to use them. + +tools_dir = $(top_srcdir)/tools + +AM_CFLAGS = \ + $(ERROR_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(TP_GLIB_CFLAGS) + +EXTRA_DIST = \ + all.xml \ + call-content.xml \ + call-stream.xml \ + channel.xml \ + misc.xml + +noinst_LTLIBRARIES = libfuture-extensions.la + +# In an external project you'd use $(TP_GLIB_LIBS) (obtained from +# pkg-config via autoconf) instead of the .la path +libfuture_extensions_la_LIBADD = \ + $(GLIB_LIBS) \ + $(DBUS_LIBS) \ + $(top_builddir)/telepathy-glib/libtelepathy-glib.la + +libfuture_extensions_la_SOURCES = \ + call-content.c \ + call-content.h \ + call-stream.c \ + call-stream.h \ + extensions.c \ + extensions-cli.c \ + extensions.h + +nodist_libfuture_extensions_la_SOURCES = \ + _gen/signals-marshal.c \ + _gen/signals-marshal.h \ + _gen/signals-marshal.list \ + _gen/register-dbus-glib-marshallers-body.h \ + _gen/enums.h \ + _gen/gtypes.h \ + _gen/gtypes-body.h \ + _gen/interfaces.h \ + _gen/interfaces-body.h \ + _gen/cli-call-content.h \ + _gen/cli-call-content-body.h \ + _gen/cli-call-stream.h \ + _gen/cli-call-stream-body.h \ + _gen/cli-channel.h \ + _gen/cli-channel-body.h \ + _gen/cli-misc.h \ + _gen/cli-misc-body.h \ + _gen/svc-call-content.h \ + _gen/svc-call-content.c \ + _gen/svc-call-stream.h \ + _gen/svc-call-stream.c \ + _gen/svc-channel.h \ + _gen/svc-channel.c \ + _gen/svc-misc.h \ + _gen/svc-misc.c + +BUILT_SOURCES = \ + _gen/all.xml \ + _gen/call-content.xml \ + _gen/call-stream.xml \ + _gen/channel.xml \ + $(nodist_libfuture_extensions_la_SOURCES) + +CLEANFILES = $(BUILT_SOURCES) + +XSLTPROCFLAGS = --nonet --novalid + +# Generated files which can be generated for all categories simultaneously + +_gen/all.xml: all.xml $(wildcard *.xml) $(tools_dir)/xincludator.py + $(mkdir_p) _gen + $(AM_V_GEN)$(PYTHON) $(tools_dir)/xincludator.py $< > $@ + +_gen/gtypes.h _gen/gtypes-body.h: _gen/all.xml \ + $(top_srcdir)/tools/glib-gtypes-generator.py + $(AM_V_GEN)$(PYTHON) $(top_srcdir)/tools/glib-gtypes-generator.py \ + $< _gen/gtypes Future + +_gen/signals-marshal.list: _gen/all.xml \ + $(tools_dir)/glib-signals-marshal-gen.py + $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-signals-marshal-gen.py $< > $@ + +_gen/signals-marshal.h: _gen/signals-marshal.list Makefile.am + $(AM_V_GEN)$(GLIB_GENMARSHAL) --header --prefix=_future_ext_marshal $< > $@ + +_gen/signals-marshal.c: _gen/signals-marshal.list Makefile.am + $(AM_V_GEN){ echo '#include "_gen/signals-marshal.h"' && \ + $(GLIB_GENMARSHAL) --body --prefix=_future_ext_marshal $< ; } > $@ + +_gen/register-dbus-glib-marshallers-body.h: _gen/all.xml \ + $(tools_dir)/glib-client-marshaller-gen.py + $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-marshaller-gen.py $< \ + _future_ext > $@ + +_gen/enums.h: _gen/all.xml \ + $(tools_dir)/c-constants-gen.py + $(AM_V_GEN)$(PYTHON) $(tools_dir)/c-constants-gen.py \ + Future \ + $< > $@ + +_gen/interfaces-body.h _gen/interfaces.h: _gen/all.xml \ + $(tools_dir)/glib-interfaces-gen.py + $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-interfaces-gen.py \ + Future _gen/interfaces-body.h _gen/interfaces.h $< + +# Generated files which must be generated per "category". Each TpProxy +# subclass you want to use with --subclass will need to have its own category, +# although you can subdivide further if you want. + +_gen/%.xml: %.xml $(wildcard *.xml) $(tools_dir)/xincludator.py + $(mkdir_p) _gen + $(AM_V_GEN)$(PYTHON) $(tools_dir)/xincludator.py $< > $@ + +_gen/svc-%.c _gen/svc-%.h: _gen/%.xml \ + $(tools_dir)/glib-ginterface-gen.py + $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-ginterface-gen.py \ + --filename=_gen/svc-$* \ + --signal-marshal-prefix=_future_ext \ + --include='<telepathy-glib/dbus.h>' \ + --include='"_gen/signals-marshal.h"' \ + --not-implemented-func='tp_dbus_g_method_return_not_implemented' \ + --allow-unstable \ + $< Future_Svc_ + +_gen/cli-channel-body.h _gen/cli-channel.h: _gen/channel.xml \ + $(tools_dir)/glib-client-gen.py Makefile.am + $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-gen.py \ + --group=channel \ + --subclass=TpChannel \ + --subclass-assert=TP_IS_CHANNEL \ + --iface-quark-prefix=FUTURE_IFACE_QUARK \ + --tp-proxy-api=0.7.6 \ + $< Future_Cli _gen/cli-channel + +_gen/cli-call-content-body.h _gen/cli-call-content.h: _gen/call-content.xml \ + $(tools_dir)/glib-client-gen.py Makefile.am + $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-gen.py \ + --group=call_content \ + --subclass=FutureCallContent \ + --subclass-assert=FUTURE_IS_CALL_CONTENT \ + --iface-quark-prefix=FUTURE_IFACE_QUARK \ + --tp-proxy-api=0.7.6 \ + $< Future_Cli _gen/cli-call-content + +_gen/cli-call-stream-body.h _gen/cli-call-stream.h: _gen/call-stream.xml \ + $(tools_dir)/glib-client-gen.py Makefile.am + $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-gen.py \ + --group=call_stream \ + --subclass=FutureCallStream \ + --subclass-assert=FUTURE_IS_CALL_STREAM \ + --iface-quark-prefix=FUTURE_IFACE_QUARK \ + --tp-proxy-api=0.7.6 \ + $< Future_Cli _gen/cli-call-stream + +# for now the Endpoint etc. interfaces are on every TpProxy - when we +# have a TpCallEndpoint etc., they should appear on that + +_gen/cli-misc-body.h _gen/cli-misc.h: _gen/misc.xml \ + $(tools_dir)/glib-client-gen.py Makefile.am + $(AM_V_GEN)$(PYTHON) $(tools_dir)/glib-client-gen.py \ + --group=misc \ + --iface-quark-prefix=FUTURE_IFACE_QUARK \ + --tp-proxy-api=0.7.6 \ + $< Future_Cli _gen/cli-misc diff --git a/extensions/all.xml b/extensions/all.xml new file mode 100644 index 000000000..618cef7b0 --- /dev/null +++ b/extensions/all.xml @@ -0,0 +1,12 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Extensions from the future</tp:title> + +<xi:include href="call-content.xml"/> +<xi:include href="call-stream.xml"/> +<xi:include href="channel.xml"/> +<xi:include href="misc.xml"/> + +</tp:spec> diff --git a/extensions/call-content.c b/extensions/call-content.c new file mode 100644 index 000000000..29d10cec4 --- /dev/null +++ b/extensions/call-content.c @@ -0,0 +1,161 @@ +/* + * call-content.c - proxy for a Content in a Call channel + * + * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright (C) 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 "extensions/call-content.h" + +#include <telepathy-glib/proxy-subclass.h> +#include <telepathy-glib/telepathy-glib.h> + +#include "extensions/extensions.h" + +/* Generated code */ +#include "_gen/cli-call-content-body.h" + +/** + * SECTION:call-content + * @title: FutureCallContent + * @short_description: proxy for a Content in a Call channel + * @see_also: #TpChannel + * + * FIXME + * + * Since: FIXME + */ + +/** + * FutureCallContentClass: + * + * The class of a #FutureCallContent. + * + * Since: FIXME + */ +struct _FutureCallContentClass { + TpProxyClass parent_class; + /*<private>*/ + gpointer priv; +}; + +/** + * FutureCallContent: + * + * A proxy object for a Telepathy connection manager. + * + * Since: FIXME + */ +struct _FutureCallContent { + TpProxy parent; + /*<private>*/ + FutureCallContentPrivate *priv; +}; + +struct _FutureCallContentPrivate { + int dummy; +}; + +G_DEFINE_TYPE (FutureCallContent, + future_call_content, + TP_TYPE_PROXY); + +static void +future_call_content_init (FutureCallContent *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, FUTURE_TYPE_CALL_CONTENT, + FutureCallContentPrivate); +} + +static void +future_call_content_class_init (FutureCallContentClass *klass) +{ + TpProxyClass *proxy_class = (TpProxyClass *) klass; + + g_type_class_add_private (klass, sizeof (FutureCallContentPrivate)); + + proxy_class->must_have_unique_name = TRUE; + proxy_class->interface = FUTURE_IFACE_QUARK_CALL_CONTENT; + future_call_content_init_known_interfaces (); +} + +/** + * future_call_content_new: + * @channel: the Call channel + * @object_path: the object path of the content; may not be %NULL + * @error: used to indicate the error if %NULL is returned + * + * <!-- --> + * + * Returns: a new content proxy, or %NULL on invalid arguments + * + * Since: FIXME + */ +FutureCallContent * +future_call_content_new (TpChannel *channel, + const gchar *object_path, + GError **error) +{ + FutureCallContent *ret = NULL; + + g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL); + g_return_val_if_fail (object_path != NULL, NULL); + + if (!tp_dbus_check_valid_object_path (object_path, error)) + goto finally; + + ret = FUTURE_CALL_CONTENT (g_object_new (FUTURE_TYPE_CALL_CONTENT, + /* FIXME: pass in the Channel as a property? */ + "dbus-daemon", tp_proxy_get_dbus_daemon (channel), + "bus-name", tp_proxy_get_bus_name (channel), + "object-path", object_path, + NULL)); + +finally: + return ret; +} + +/** + * future_call_content_init_known_interfaces: + * + * Ensure that the known interfaces for FutureCallContent have been set up. + * This is done automatically when necessary, but for correct + * overriding of library interfaces by local extensions, you should + * call this function before calling + * tp_proxy_or_subclass_hook_on_interface_add() with first argument + * %FUTURE_TYPE_CALL_CONTENT. + * + * Since: 0.7.32 + */ +void +future_call_content_init_known_interfaces (void) +{ + static gsize once = 0; + + if (g_once_init_enter (&once)) + { + GType tp_type = FUTURE_TYPE_CALL_CONTENT; + + tp_proxy_init_known_interfaces (); + tp_proxy_or_subclass_hook_on_interface_add (tp_type, + future_cli_call_content_add_signals); + tp_proxy_subclass_add_error_mapping (tp_type, + TP_ERROR_PREFIX, TP_ERRORS, TP_TYPE_ERROR); + + g_once_init_leave (&once, 1); + } +} diff --git a/extensions/call-content.h b/extensions/call-content.h new file mode 100644 index 000000000..5f674f2e1 --- /dev/null +++ b/extensions/call-content.h @@ -0,0 +1,62 @@ +/* + * call-content.h - proxy for a Content in a Call channel + * + * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright (C) 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 + */ + +#ifndef FUTURE_CALL_CONTENT_H +#define FUTURE_CALL_CONTENT_H + +#include <telepathy-glib/channel.h> +#include <telepathy-glib/proxy.h> + +G_BEGIN_DECLS + +typedef struct _FutureCallContent FutureCallContent; +typedef struct _FutureCallContentPrivate FutureCallContentPrivate; +typedef struct _FutureCallContentClass FutureCallContentClass; + +GType future_call_content_get_type (void); + +/* TYPE MACROS */ +#define FUTURE_TYPE_CALL_CONTENT \ + (future_call_content_get_type ()) +#define FUTURE_CALL_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), FUTURE_TYPE_CALL_CONTENT, \ + FutureCallContent)) +#define FUTURE_CALL_CONTENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), FUTURE_TYPE_CALL_CONTENT, \ + FutureCallContentClass)) +#define FUTURE_IS_CALL_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), FUTURE_TYPE_CALL_CONTENT)) +#define FUTURE_IS_CALL_CONTENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), FUTURE_TYPE_CALL_CONTENT)) +#define FUTURE_CALL_CONTENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FUTURE_TYPE_CALL_CONTENT, \ + FutureCallContentClass)) + +FutureCallContent *future_call_content_new (TpChannel *channel, + const gchar *object_path, GError **error); + +void future_call_content_init_known_interfaces (void); + +G_END_DECLS + +#include "extensions/_gen/cli-call-content.h" + +#endif diff --git a/extensions/call-content.xml b/extensions/call-content.xml new file mode 100644 index 000000000..cfb100035 --- /dev/null +++ b/extensions/call-content.xml @@ -0,0 +1,10 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Call Content</tp:title> + +<xi:include href="Call_Content.xml"/> +<xi:include href="Call_Content_Interface_Media.xml"/> + +</tp:spec> diff --git a/extensions/call-stream.c b/extensions/call-stream.c new file mode 100644 index 000000000..b99539896 --- /dev/null +++ b/extensions/call-stream.c @@ -0,0 +1,161 @@ +/* + * call-stream.c - proxy for a Stream in a Call channel + * + * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright (C) 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 "extensions/call-stream.h" + +#include <telepathy-glib/proxy-subclass.h> +#include <telepathy-glib/telepathy-glib.h> + +#include "extensions/extensions.h" + +/* Generated code */ +#include "_gen/cli-call-stream-body.h" + +/** + * SECTION:call-stream + * @title: FutureCallStream + * @short_description: proxy for a Stream in a Call channel + * @see_also: #TpChannel + * + * FIXME + * + * Since: FIXME + */ + +/** + * FutureCallStreamClass: + * + * The class of a #FutureCallStream. + * + * Since: FIXME + */ +struct _FutureCallStreamClass { + TpProxyClass parent_class; + /*<private>*/ + gpointer priv; +}; + +/** + * FutureCallStream: + * + * A proxy object for a Telepathy connection manager. + * + * Since: FIXME + */ +struct _FutureCallStream { + TpProxy parent; + /*<private>*/ + FutureCallStreamPrivate *priv; +}; + +struct _FutureCallStreamPrivate { + int dummy; +}; + +G_DEFINE_TYPE (FutureCallStream, + future_call_stream, + TP_TYPE_PROXY); + +static void +future_call_stream_init (FutureCallStream *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, FUTURE_TYPE_CALL_STREAM, + FutureCallStreamPrivate); +} + +static void +future_call_stream_class_init (FutureCallStreamClass *klass) +{ + TpProxyClass *proxy_class = (TpProxyClass *) klass; + + g_type_class_add_private (klass, sizeof (FutureCallStreamPrivate)); + + proxy_class->must_have_unique_name = TRUE; + proxy_class->interface = FUTURE_IFACE_QUARK_CALL_STREAM; + future_call_stream_init_known_interfaces (); +} + +/** + * future_call_stream_new: + * @channel: the Call channel + * @object_path: the object path of the stream; may not be %NULL + * @error: used to indicate the error if %NULL is returned + * + * <!-- --> + * + * Returns: a new stream proxy, or %NULL on invalid arguments + * + * Since: FIXME + */ +FutureCallStream * +future_call_stream_new (TpChannel *channel, + const gchar *object_path, + GError **error) +{ + FutureCallStream *ret = NULL; + + g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL); + g_return_val_if_fail (object_path != NULL, NULL); + + if (!tp_dbus_check_valid_object_path (object_path, error)) + goto finally; + + ret = FUTURE_CALL_STREAM (g_object_new (FUTURE_TYPE_CALL_STREAM, + /* FIXME: pass in the Channel as a property? */ + "dbus-daemon", tp_proxy_get_dbus_daemon (channel), + "bus-name", tp_proxy_get_bus_name (channel), + "object-path", object_path, + NULL)); + +finally: + return ret; +} + +/** + * future_call_stream_init_known_interfaces: + * + * Ensure that the known interfaces for FutureCallStream have been set up. + * This is done automatically when necessary, but for correct + * overriding of library interfaces by local extensions, you should + * call this function before calling + * tp_proxy_or_subclass_hook_on_interface_add() with first argument + * %FUTURE_TYPE_CALL_STREAM. + * + * Since: 0.7.32 + */ +void +future_call_stream_init_known_interfaces (void) +{ + static gsize once = 0; + + if (g_once_init_enter (&once)) + { + GType tp_type = FUTURE_TYPE_CALL_STREAM; + + tp_proxy_init_known_interfaces (); + tp_proxy_or_subclass_hook_on_interface_add (tp_type, + future_cli_call_stream_add_signals); + tp_proxy_subclass_add_error_mapping (tp_type, + TP_ERROR_PREFIX, TP_ERRORS, TP_TYPE_ERROR); + + g_once_init_leave (&once, 1); + } +} diff --git a/extensions/call-stream.h b/extensions/call-stream.h new file mode 100644 index 000000000..0a195bde0 --- /dev/null +++ b/extensions/call-stream.h @@ -0,0 +1,62 @@ +/* + * call-stream.h - proxy for a Stream in a Call channel + * + * Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright (C) 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 + */ + +#ifndef FUTURE_CALL_STREAM_H +#define FUTURE_CALL_STREAM_H + +#include <telepathy-glib/channel.h> +#include <telepathy-glib/proxy.h> + +G_BEGIN_DECLS + +typedef struct _FutureCallStream FutureCallStream; +typedef struct _FutureCallStreamPrivate FutureCallStreamPrivate; +typedef struct _FutureCallStreamClass FutureCallStreamClass; + +GType future_call_stream_get_type (void); + +/* TYPE MACROS */ +#define FUTURE_TYPE_CALL_STREAM \ + (future_call_stream_get_type ()) +#define FUTURE_CALL_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), FUTURE_TYPE_CALL_STREAM, \ + FutureCallStream)) +#define FUTURE_CALL_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), FUTURE_TYPE_CALL_STREAM, \ + FutureCallStreamClass)) +#define FUTURE_IS_CALL_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), FUTURE_TYPE_CALL_STREAM)) +#define FUTURE_IS_CALL_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), FUTURE_TYPE_CALL_STREAM)) +#define FUTURE_CALL_STREAM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), FUTURE_TYPE_CALL_STREAM, \ + FutureCallStreamClass)) + +FutureCallStream *future_call_stream_new (TpChannel *channel, + const gchar *object_path, GError **error); + +void future_call_stream_init_known_interfaces (void); + +G_END_DECLS + +#include "extensions/_gen/cli-call-stream.h" + +#endif diff --git a/extensions/call-stream.xml b/extensions/call-stream.xml new file mode 100644 index 000000000..3e85b3791 --- /dev/null +++ b/extensions/call-stream.xml @@ -0,0 +1,10 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Call Stream</tp:title> + +<xi:include href="Call_Stream.xml"/> +<xi:include href="Call_Stream_Interface_Media.xml"/> + +</tp:spec> diff --git a/extensions/channel.xml b/extensions/channel.xml new file mode 100644 index 000000000..b4e0a469c --- /dev/null +++ b/extensions/channel.xml @@ -0,0 +1,9 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Channel extensions from the future</tp:title> + +<xi:include href="Channel_Type_Call.xml"/> + +</tp:spec> diff --git a/extensions/extensions-cli.c b/extensions/extensions-cli.c new file mode 100644 index 000000000..d29c5ecbd --- /dev/null +++ b/extensions/extensions-cli.c @@ -0,0 +1,35 @@ +#include "extensions.h" + +#include <telepathy-glib/telepathy-glib.h> +#include <telepathy-glib/proxy-subclass.h> + +static void _future_ext_register_dbus_glib_marshallers (void); + +/* include auto-generated stubs for client-specific code */ +#include "_gen/signals-marshal.h" +#include "_gen/cli-channel-body.h" +#include "_gen/cli-misc-body.h" +#include "_gen/register-dbus-glib-marshallers-body.h" + +static gpointer +future_cli_once (gpointer data) +{ + _future_ext_register_dbus_glib_marshallers (); + + tp_channel_init_known_interfaces (); + + tp_proxy_or_subclass_hook_on_interface_add (TP_TYPE_PROXY, + future_cli_misc_add_signals); + tp_proxy_or_subclass_hook_on_interface_add (TP_TYPE_CHANNEL, + future_cli_channel_add_signals); + + return NULL; +} + +void +future_cli_init (void) +{ + static GOnce once = G_ONCE_INIT; + + g_once (&once, future_cli_once, NULL); +} diff --git a/extensions/extensions.c b/extensions/extensions.c new file mode 100644 index 000000000..eeda4623c --- /dev/null +++ b/extensions/extensions.c @@ -0,0 +1,6 @@ +#include "extensions.h" + +/* include auto-generated stubs for things common to service and client */ +#include "_gen/gtypes-body.h" +#include "_gen/interfaces-body.h" +#include "_gen/signals-marshal.h" diff --git a/extensions/extensions.h b/extensions/extensions.h new file mode 100644 index 000000000..24d978748 --- /dev/null +++ b/extensions/extensions.h @@ -0,0 +1,27 @@ +#ifndef FUTURE_EXTENSIONS_H +#define FUTURE_EXTENSIONS_H + +#include <glib-object.h> +#include <telepathy-glib/telepathy-glib.h> + +#include "extensions/_gen/enums.h" +#include "extensions/_gen/cli-channel.h" +#include "extensions/_gen/cli-misc.h" +#include "extensions/_gen/svc-call-content.h" +#include "extensions/_gen/svc-call-stream.h" +#include "extensions/_gen/svc-channel.h" +#include "extensions/_gen/svc-misc.h" + +#include "extensions/call-content.h" +#include "extensions/call-stream.h" + +G_BEGIN_DECLS + +#include "extensions/_gen/gtypes.h" +#include "extensions/_gen/interfaces.h" + +void future_cli_init (void); + +G_END_DECLS + +#endif diff --git a/extensions/misc.xml b/extensions/misc.xml new file mode 100644 index 000000000..0384410f6 --- /dev/null +++ b/extensions/misc.xml @@ -0,0 +1,10 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Miscellaneous extensions from the future</tp:title> + +<xi:include href="Call_Content_Codec_Offer.xml"/> +<xi:include href="Call_Stream_Endpoint.xml"/> + +</tp:spec> diff --git a/tests/dbus/Makefile.am b/tests/dbus/Makefile.am index 0ecc9a49c..067abd1ae 100644 --- a/tests/dbus/Makefile.am +++ b/tests/dbus/Makefile.am @@ -2,6 +2,7 @@ noinst_PROGRAMS = \ test-account \ test-account-manager \ test-call-cancellation \ + test-call-example \ test-callable-example \ test-channel-dispatcher \ test-channel-dispatch-operation \ @@ -61,6 +62,11 @@ test_channel_introspect_SOURCES = channel-introspect.c test_channel_request_SOURCES = channel-request.c +test_call_example_SOURCES = call-example.c +test_call_example_LDADD = \ + $(LDADD) \ + $(top_builddir)/examples/future/call-cm/libexample-cm-call.la + test_callable_example_SOURCES = callable-example.c test_callable_example_LDADD = \ $(LDADD) \ diff --git a/tests/dbus/call-example.c b/tests/dbus/call-example.c new file mode 100644 index 000000000..7a400253c --- /dev/null +++ b/tests/dbus/call-example.c @@ -0,0 +1,1133 @@ +/* Feature test for example StreamedMedia CM code. + * + * Copyright © 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * Copyright © 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 <telepathy-glib/telepathy-glib.h> + +#include "examples/future/call-cm/cm.h" +#include "examples/future/call-cm/conn.h" +#include "examples/future/call-cm/call-channel.h" +#include "examples/future/call-cm/call-stream.h" +#include "extensions/extensions.h" + +#include "tests/lib/util.h" + +#define CLEAR_OBJECT(o) \ + G_STMT_START { \ + if (*(o) != NULL) \ + { \ + g_object_unref (*(o)); \ + *(o) = NULL; \ + } \ + } G_STMT_END + +#define CLEAR_BOXED(g, o) \ + G_STMT_START { \ + if (*(o) != NULL) \ + { \ + g_boxed_free ((g), *(o)); \ + *(o) = NULL; \ + } \ + } G_STMT_END + +#define CLEAR_HASH(h) \ + G_STMT_START { \ + if (*(h) != NULL) \ + { \ + g_hash_table_unref (*(h)); \ + *(h) = NULL; \ + } \ + } G_STMT_END + +/* FIXME: if this isn't needed for Senders, remove it */ +G_GNUC_UNUSED static void +test_assert_uu_hash_contains (GHashTable *hash, + guint key, + guint expected) +{ + gpointer v; + + if (!g_hash_table_lookup_extended (hash, GUINT_TO_POINTER (key), NULL, &v)) + g_error ("Expected %u => %u in hash table, but key was absent", key, + expected); + + g_assert_cmpuint (GPOINTER_TO_UINT (v), ==, expected); +} + +typedef struct +{ + GMainLoop *mainloop; + TpDBusDaemon *dbus; + GError *error /* statically initialized to NULL */ ; + + ExampleCallConnectionManager *service_cm; + + TpConnectionManager *cm; + TpConnection *conn; + TpChannel *chan; + TpHandle self_handle; + TpHandle peer_handle; + + GHashTable *get_all_return; + + GArray *audio_request; + GArray *video_request; + GArray *invalid_request; + + GArray *stream_ids; + GArray *contacts; + GPtrArray *get_contents_return; + GHashTable *get_senders_return; + + gulong members_changed_detailed_id; + + FutureCallContent *added_content; + FutureCallContent *audio_content; + FutureCallContent *video_content; + FutureCallStream *audio_stream; + FutureCallStream *video_stream; +} Test; + +static void +cm_ready_cb (TpConnectionManager *cm G_GNUC_UNUSED, + const GError *error, + gpointer user_data, + GObject *weak_object G_GNUC_UNUSED) +{ + Test *test = user_data; + + test_assert_no_error (error); + g_main_loop_quit (test->mainloop); +} + +static void +conn_ready_cb (TpConnection *conn G_GNUC_UNUSED, + const GError *error, + gpointer user_data) +{ + Test *test = user_data; + + test_assert_no_error (error); + g_main_loop_quit (test->mainloop); +} + +static void +setup (Test *test, + gconstpointer data G_GNUC_UNUSED) +{ + TpBaseConnectionManager *service_cm_as_base; + gboolean ok; + gchar *bus_name; + gchar *object_path; + GHashTable *parameters; + guint audio = TP_MEDIA_STREAM_TYPE_AUDIO; + guint video = TP_MEDIA_STREAM_TYPE_VIDEO; + guint not_a_media_type = 31337; + + g_type_init (); + tp_debug_set_flags ("all"); + + test->mainloop = g_main_loop_new (NULL, FALSE); + test->dbus = tp_dbus_daemon_dup (NULL); + g_assert (test->dbus != NULL); + + test->service_cm = EXAMPLE_CALL_CONNECTION_MANAGER (g_object_new ( + EXAMPLE_TYPE_CALL_CONNECTION_MANAGER, + NULL)); + g_assert (test->service_cm != NULL); + service_cm_as_base = TP_BASE_CONNECTION_MANAGER (test->service_cm); + g_assert (service_cm_as_base != NULL); + + ok = tp_base_connection_manager_register (service_cm_as_base); + g_assert (ok); + + test->cm = tp_connection_manager_new (test->dbus, "example_call", + NULL, &test->error); + g_assert (test->cm != NULL); + tp_connection_manager_call_when_ready (test->cm, cm_ready_cb, test, NULL, + NULL); + g_main_loop_run (test->mainloop); + + parameters = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, + (GDestroyNotify) tp_g_value_slice_free); + g_hash_table_insert (parameters, "account", + tp_g_value_slice_new_static_string ("me")); + g_hash_table_insert (parameters, "simulation-delay", + tp_g_value_slice_new_uint (0)); + + tp_cli_connection_manager_run_request_connection (test->cm, -1, + "example", parameters, &bus_name, &object_path, &test->error, NULL); + test_assert_no_error (test->error); + + test->conn = tp_connection_new (test->dbus, bus_name, object_path, + &test->error); + test_assert_no_error (test->error); + g_assert (test->conn != NULL); + tp_cli_connection_call_connect (test->conn, -1, NULL, NULL, NULL, NULL); + tp_connection_call_when_ready (test->conn, conn_ready_cb, test); + g_main_loop_run (test->mainloop); + + test->self_handle = tp_connection_get_self_handle (test->conn); + g_assert (test->self_handle != 0); + + test->audio_request = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1); + g_array_append_val (test->audio_request, audio); + + test->video_request = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1); + g_array_append_val (test->video_request, video); + + test->invalid_request = g_array_sized_new (FALSE, FALSE, sizeof (guint), 1); + g_array_append_val (test->invalid_request, not_a_media_type); + + test->stream_ids = g_array_sized_new (FALSE, FALSE, sizeof (guint), 2); + + g_hash_table_destroy (parameters); + g_free (bus_name); + g_free (object_path); +} + +static void +channel_created_cb (TpConnection *connection, + const gchar *object_path, + GHashTable *immutable_properties, + const GError *error, + gpointer user_data, + GObject *weak_object G_GNUC_UNUSED) +{ + Test *test = user_data; + GError *new_error = NULL; + + test_assert_no_error (error); + + test->chan = tp_channel_new_from_properties (connection, object_path, + immutable_properties, &new_error); + test_assert_no_error (new_error); + + test->peer_handle = tp_channel_get_handle (test->chan, NULL); + + g_main_loop_quit (test->mainloop); +} + +static void +channel_ready_cb (TpChannel *channel G_GNUC_UNUSED, + const GError *error, + gpointer user_data) +{ + Test *test = user_data; + + test_assert_no_error (error); + g_main_loop_quit (test->mainloop); +} + +static void +added_content_cb (TpChannel *chan G_GNUC_UNUSED, + const gchar *object_path, + const GError *error, + gpointer user_data, + GObject *weak_object G_GNUC_UNUSED) +{ + Test *test = user_data; + + CLEAR_OBJECT (&test->added_content); + + if (error != NULL) + { + test->error = g_error_copy (error); + } + else + { + test->added_content = future_call_content_new (test->chan, object_path, + NULL); + g_assert (test->added_content != NULL); + } + + g_main_loop_quit (test->mainloop); +} + +static void +got_all_cb (TpProxy *proxy, + GHashTable *properties, + const GError *error, + gpointer user_data, + GObject *weak_object G_GNUC_UNUSED) +{ + Test *test = user_data; + + test_assert_no_error (error); + + CLEAR_HASH (&test->get_all_return); + test->get_all_return = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) tp_g_value_slice_free); + tp_g_hash_table_update (test->get_all_return, properties, + (GBoxedCopyFunc) g_strdup, (GBoxedCopyFunc) tp_g_value_slice_dup); + + g_main_loop_quit (test->mainloop); +} + +static void +got_contents_cb (TpProxy *proxy, + const GValue *value, + const GError *error, + gpointer user_data, + GObject *weak_object G_GNUC_UNUSED) +{ + Test *test = user_data; + + test_assert_no_error (error); + + CLEAR_BOXED (TP_ARRAY_TYPE_OBJECT_PATH_LIST, &test->get_contents_return); + g_assert (G_VALUE_HOLDS (value, TP_ARRAY_TYPE_OBJECT_PATH_LIST)); + test->get_contents_return = g_value_dup_boxed (value); + + g_main_loop_quit (test->mainloop); +} + +static void +got_senders_cb (TpProxy *proxy, + const GValue *value, + const GError *error, + gpointer user_data, + GObject *weak_object G_GNUC_UNUSED) +{ + Test *test = user_data; + + CLEAR_HASH (&test->get_senders_return); + + if (test->error != NULL) + g_clear_error (&test->error); + + test_assert_no_error (error); + + g_assert (G_VALUE_HOLDS (value, FUTURE_HASH_TYPE_CONTACT_SENDING_STATE_MAP)); + test->get_senders_return = g_value_dup_boxed (value); + + g_main_loop_quit (test->mainloop); +} + +static void +void_cb (TpChannel *chan G_GNUC_UNUSED, + const GError *error, + gpointer user_data, + GObject *weak_object G_GNUC_UNUSED) +{ + Test *test = user_data; + + if (error != NULL) + { + test->error = g_error_copy (error); + } + + g_main_loop_quit (test->mainloop); +} + +static void +test_connect_channel_signals (Test *test) +{ +} + +static void +outgoing_call (Test *test, + const gchar *id, + gboolean initial_audio, + gboolean initial_video) +{ + GHashTable *request = tp_asv_new ( + TP_PROP_CHANNEL_CHANNEL_TYPE, + G_TYPE_STRING, FUTURE_IFACE_CHANNEL_TYPE_CALL, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_CONTACT, + TP_PROP_CHANNEL_TARGET_ID, G_TYPE_STRING, id, + FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, + G_TYPE_BOOLEAN, initial_audio, + FUTURE_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, + G_TYPE_BOOLEAN, initial_video, + NULL); + + tp_cli_connection_interface_requests_call_create_channel (test->conn, -1, + request, channel_created_cb, test, NULL, NULL); + g_hash_table_destroy (request); + request = NULL; + g_main_loop_run (test->mainloop); + + /* Do this before waiting for it to become ready - we knew its channel type + * and interfaces anyway */ + test_connect_channel_signals (test); + + tp_channel_call_when_ready (test->chan, channel_ready_cb, test); + g_main_loop_run (test->mainloop); +} + +static void +assert_call_properties (GHashTable *get_all_return, + FutureCallState call_state, + TpHandle actor, + FutureCallStateChangeReason reason, + const gchar *dbus_reason, + gboolean check_call_flags, FutureCallFlags call_flags, + gboolean check_initials, gboolean initial_audio, gboolean initial_video) +{ + gboolean valid; + GValueArray *state_reason; + + g_assert_cmpuint (tp_asv_get_uint32 (get_all_return, "CallState", + &valid), ==, call_state); + g_assert (valid); + state_reason = tp_asv_get_boxed (get_all_return, "CallStateReason", + FUTURE_STRUCT_TYPE_CALL_STATE_REASON); + g_assert (state_reason != NULL); + g_assert_cmpuint (g_value_get_uint (state_reason->values + 0), ==, + actor); + g_assert_cmpuint (g_value_get_uint (state_reason->values + 1), ==, + reason); + g_assert_cmpstr (g_value_get_string (state_reason->values + 2), ==, + dbus_reason); + + /* Hard-coded properties */ + g_assert_cmpint (tp_asv_get_boolean (get_all_return, + "HardwareStreaming", &valid), ==, TRUE); + g_assert (valid); + g_assert_cmpint (tp_asv_get_boolean (get_all_return, + "MutableContents", &valid), ==, TRUE); + g_assert (valid); + g_assert_cmpstr (tp_asv_get_string (get_all_return, + "InitialTransport"), ==, ""); + + if (check_call_flags) + { + g_assert_cmpuint (tp_asv_get_uint32 (get_all_return, + "CallFlags", &valid), ==, 0); + g_assert (valid); + } + + if (check_initials) + { + g_assert_cmpint (tp_asv_get_boolean (get_all_return, + "InitialAudio", &valid), ==, initial_audio); + g_assert (valid); + + g_assert_cmpint (tp_asv_get_boolean (get_all_return, + "InitialVideo", &valid), ==, initial_video); + g_assert (valid); + } + + /* FIXME: CallStateDetails */ +} + +static void +assert_content_properties (GHashTable *get_all_return, + TpMediaStreamType type, + TpHandle creator, + FutureCallContentDisposition disposition) +{ + gboolean valid; + + g_assert_cmpstr (tp_asv_get_string (get_all_return, "Name"), !=, NULL); + g_assert_cmpuint (tp_asv_get_uint32 (get_all_return, "Type", &valid), + ==, type); + g_assert_cmpint (valid, ==, TRUE); + g_assert_cmpuint (tp_asv_get_uint32 (get_all_return, "Creator", + &valid), ==, creator); + g_assert_cmpint (valid, ==, TRUE); + g_assert_cmpuint (tp_asv_get_uint32 (get_all_return, "Disposition", + &valid), ==, disposition); + g_assert_cmpint (valid, ==, TRUE); +} + +static void +loop_until_ended (Test *test) +{ + while (1) + { + tp_cli_dbus_properties_call_get_all (test->chan, -1, + FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + if (tp_asv_get_uint32 (test->get_all_return, "CallState", + NULL) == FUTURE_CALL_STATE_ENDED) + return; + } +} + +static void +loop_until_answered (Test *test) +{ + while (1) + { + tp_cli_dbus_properties_call_get_all (test->chan, -1, + FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + if (tp_asv_get_uint32 (test->get_all_return, "CallState", + NULL) != FUTURE_CALL_STATE_PENDING_RECEIVER) + return; + } +} + +static void +assert_ended_and_run_close (Test *test, + TpHandle expected_actor, + FutureCallStateChangeReason expected_reason, + const gchar *expected_error) +{ + /* In response to whatever we just did, the call ends... */ + tp_cli_dbus_properties_call_get_all (test->chan, -1, + FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + assert_call_properties (test->get_all_return, + FUTURE_CALL_STATE_ENDED, + expected_actor, + expected_reason, + expected_error, + FALSE, 0, /* ignore call flags */ + FALSE, FALSE, FALSE); /* ignore initial audio/video */ + + /* ... which means there are no contents ... */ + tp_cli_dbus_properties_call_get (test->chan, -1, + FUTURE_IFACE_CHANNEL_TYPE_CALL, "Contents", + got_contents_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + g_assert_cmpuint (test->get_contents_return->len, ==, 0); + + /* ... but the channel doesn't close */ + test_connection_run_until_dbus_queue_processed (test->conn); + g_assert (tp_proxy_get_invalidated (test->chan) == NULL); + + /* When we call Close it finally closes */ + tp_cli_channel_call_close (test->chan, -1, void_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + test_connection_run_until_dbus_queue_processed (test->conn); + g_assert (tp_proxy_get_invalidated (test->chan) != NULL); +} + +static void +test_basics (Test *test, + gconstpointer data G_GNUC_UNUSED) +{ + const GPtrArray *stream_paths; + gpointer v; + + outgoing_call (test, "basic-test", TRUE, FALSE); + + /* Get initial state */ + tp_cli_dbus_properties_call_get_all (test->chan, -1, + FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + assert_call_properties (test->get_all_return, + FUTURE_CALL_STATE_PENDING_INITIATOR, 0, + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", + TRUE, 0, /* call flags */ + TRUE, TRUE, FALSE); /* initial audio/video must be what we said */ + + /* We have one audio content but it's not active just yet */ + + tp_cli_dbus_properties_call_get (test->chan, -1, + FUTURE_IFACE_CHANNEL_TYPE_CALL, "Contents", + got_contents_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + g_assert_cmpuint (test->get_contents_return->len, ==, 1); + + g_assert (test->audio_content == NULL); + test->audio_content = future_call_content_new (test->chan, + g_ptr_array_index (test->get_contents_return, 0), NULL); + g_assert (test->audio_content != NULL); + + tp_cli_dbus_properties_call_get_all (test->audio_content, -1, + FUTURE_IFACE_CALL_CONTENT, got_all_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + assert_content_properties (test->get_all_return, + TP_MEDIA_STREAM_TYPE_AUDIO, test->self_handle, + FUTURE_CALL_CONTENT_DISPOSITION_INITIAL); + + stream_paths = tp_asv_get_boxed (test->get_all_return, "Streams", + TP_ARRAY_TYPE_OBJECT_PATH_LIST); + g_assert (stream_paths != NULL); + g_assert_cmpuint (stream_paths->len, ==, 1); + + g_assert (test->audio_stream == NULL); + test->audio_stream = future_call_stream_new (test->chan, + g_ptr_array_index (stream_paths, 0), NULL); + g_assert (test->audio_stream != NULL); + + tp_cli_dbus_properties_call_get (test->audio_stream, -1, + FUTURE_IFACE_CALL_STREAM, "Senders", got_senders_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + + test_assert_no_error (test->error); + + g_assert_cmpuint (g_hash_table_size (test->get_senders_return), ==, 2); + g_assert (!g_hash_table_lookup_extended (test->get_senders_return, + GUINT_TO_POINTER (0), NULL, NULL)); + g_assert (g_hash_table_lookup_extended (test->get_senders_return, + GUINT_TO_POINTER (test->self_handle), NULL, &v)); + g_assert_cmpuint (GPOINTER_TO_UINT (v), ==, FUTURE_SENDING_STATE_SENDING); + g_assert (g_hash_table_lookup_extended (test->get_senders_return, + GUINT_TO_POINTER (tp_channel_get_handle (test->chan, NULL)), + NULL, &v)); + g_assert_cmpuint (GPOINTER_TO_UINT (v), ==, + FUTURE_SENDING_STATE_PENDING_SEND); + + /* OK, that looks good. Actually make the call */ + future_cli_channel_type_call_call_accept (test->chan, -1, void_cb, + test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + /* Calling Accept again makes no sense, but mustn't crash */ + future_cli_channel_type_call_call_accept (test->chan, -1, void_cb, + test, NULL, NULL); + g_main_loop_run (test->mainloop); + g_assert_error (test->error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE); + g_clear_error (&test->error); + + /* Wait for the remote contact to answer, if they haven't already */ + + loop_until_answered (test); + + /* Calling Accept again makes no sense, but mustn't crash */ + future_cli_channel_type_call_call_accept (test->chan, -1, void_cb, + test, NULL, NULL); + g_main_loop_run (test->mainloop); + g_assert_error (test->error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE); + g_clear_error (&test->error); + + /* Check the call state */ + + tp_cli_dbus_properties_call_get_all (test->chan, -1, + FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + assert_call_properties (test->get_all_return, + FUTURE_CALL_STATE_ACCEPTED, tp_channel_get_handle (test->chan, NULL), + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", + TRUE, 0, /* call flags */ + FALSE, FALSE, FALSE); /* don't care about initial audio/video */ + + /* There's still one content */ + CLEAR_BOXED (TP_ARRAY_TYPE_OBJECT_PATH_LIST, &test->get_contents_return); + test->get_contents_return = g_boxed_copy (TP_ARRAY_TYPE_OBJECT_PATH_LIST, + tp_asv_get_boxed (test->get_all_return, + "Contents", TP_ARRAY_TYPE_OBJECT_PATH_LIST)); + g_assert_cmpuint (test->get_contents_return->len, ==, 1); + g_assert_cmpstr (g_ptr_array_index (test->get_contents_return, 0), + ==, tp_proxy_get_object_path (test->audio_content)); + + /* Other contact is sending now */ + CLEAR_HASH (&test->get_senders_return); + tp_cli_dbus_properties_call_get (test->audio_stream, -1, + FUTURE_IFACE_CALL_STREAM, "Senders", got_senders_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + g_assert_cmpuint (g_hash_table_size (test->get_senders_return), ==, 2); + g_assert (!g_hash_table_lookup_extended (test->get_senders_return, + GUINT_TO_POINTER (0), NULL, NULL)); + g_assert (g_hash_table_lookup_extended (test->get_senders_return, + GUINT_TO_POINTER (test->self_handle), NULL, &v)); + g_assert_cmpuint (GPOINTER_TO_UINT (v), ==, FUTURE_SENDING_STATE_SENDING); + g_assert (g_hash_table_lookup_extended (test->get_senders_return, + GUINT_TO_POINTER (tp_channel_get_handle (test->chan, NULL)), + NULL, &v)); + g_assert_cmpuint (GPOINTER_TO_UINT (v), ==, FUTURE_SENDING_STATE_SENDING); + + /* AddContent with bad content-type must fail */ + + future_cli_channel_type_call_call_add_content (test->chan, -1, + "", 31337, added_content_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + g_assert (test->error != NULL); + g_clear_error (&test->error); + + /* AddContent again, to add a video stream */ + + future_cli_channel_type_call_call_add_content (test->chan, -1, + "", TP_MEDIA_STREAM_TYPE_VIDEO, added_content_cb, + test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + g_assert (test->added_content != NULL); + CLEAR_OBJECT (&test->video_content); + test->video_content = g_object_ref (test->added_content); + + /* There are two Contents, because now we have the video content too */ + + tp_cli_dbus_properties_call_get (test->chan, -1, + FUTURE_IFACE_CHANNEL_TYPE_CALL, "Contents", + got_contents_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + g_assert_cmpuint (test->get_contents_return->len, ==, 2); + + /* they could be either way round */ + if (!tp_strdiff (g_ptr_array_index (test->get_contents_return, 0), + tp_proxy_get_object_path (test->audio_content))) + { + g_assert_cmpstr (g_ptr_array_index (test->get_contents_return, 1), + ==, tp_proxy_get_object_path (test->video_content)); + } + else + { + g_assert_cmpstr (g_ptr_array_index (test->get_contents_return, 0), + ==, tp_proxy_get_object_path (test->video_content)); + g_assert_cmpstr (g_ptr_array_index (test->get_contents_return, 1), + ==, tp_proxy_get_object_path (test->audio_content)); + } + + tp_cli_dbus_properties_call_get_all (test->video_content, -1, + FUTURE_IFACE_CALL_CONTENT, got_all_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + assert_content_properties (test->get_all_return, + TP_MEDIA_STREAM_TYPE_VIDEO, test->self_handle, + FUTURE_CALL_CONTENT_DISPOSITION_NONE); + + stream_paths = tp_asv_get_boxed (test->get_all_return, "Streams", + TP_ARRAY_TYPE_OBJECT_PATH_LIST); + g_assert (stream_paths != NULL); + g_assert_cmpuint (stream_paths->len, ==, 1); + + g_assert (test->video_stream == NULL); + test->video_stream = future_call_stream_new (test->chan, + g_ptr_array_index (stream_paths, 0), NULL); + g_assert (test->video_stream != NULL); + + tp_cli_dbus_properties_call_get (test->video_stream, -1, + FUTURE_IFACE_CALL_STREAM, "Senders", got_senders_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + g_assert_cmpuint (g_hash_table_size (test->get_senders_return), ==, 2); + g_assert (!g_hash_table_lookup_extended (test->get_senders_return, + GUINT_TO_POINTER (0), NULL, NULL)); + g_assert (g_hash_table_lookup_extended (test->get_senders_return, + GUINT_TO_POINTER (test->self_handle), NULL, &v)); + g_assert_cmpuint (GPOINTER_TO_UINT (v), ==, FUTURE_SENDING_STATE_SENDING); + g_assert (g_hash_table_lookup_extended (test->get_senders_return, + GUINT_TO_POINTER (tp_channel_get_handle (test->chan, NULL)), + NULL, &v)); + + /* After a moment, the video stream becomes connected, and the remote user + * accepts our proposed direction change. These might happen in either + * order, at least in this implementation. */ + + if (GPOINTER_TO_UINT (v) != FUTURE_SENDING_STATE_SENDING) + g_assert_cmpuint (GPOINTER_TO_UINT (v), ==, + FUTURE_SENDING_STATE_PENDING_SEND); + +#if 0 + /* FIXME: Call has no equivalent of RemoveStreams yet, afaics... */ + + /* RemoveStreams with a bad stream ID must fail */ + + g_array_set_size (test->stream_ids, 0); + g_array_append_val (test->stream_ids, not_a_stream_id); + tp_cli_channel_type_streamed_media_call_remove_streams (test->chan, -1, + test->stream_ids, + void_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + g_assert (test->error != NULL); + g_clear_error (&test->error); + + /* Drop the video stream with RemoveStreams */ + + g_array_set_size (test->stream_ids, 0); + g_array_append_val (test->stream_ids, video_stream_id); + tp_cli_channel_type_streamed_media_call_remove_streams (test->chan, -1, + test->stream_ids, + void_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + /* Get contents again: now there's only the audio */ + + tp_cli_dbus_properties_call_get (test->chan, -1, + FUTURE_IFACE_CHANNEL_TYPE_CALL, "Contents", + got_contents_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + g_assert_cmpuint (test->get_contents_return->len, ==, 1); + g_assert_cmpstr (g_ptr_array_index (test->get_contents_return, 0), ==, + tp_proxy_get_object_path (test->audio_content)); +#endif + + /* Hang up the call in the recommended way */ + + future_cli_channel_type_call_call_hangup (test->chan, + -1, FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", "", + void_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + assert_ended_and_run_close (test, test->self_handle, + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, + ""); + + /* FIXME: untested things include: + * + * RequestStreamDirection + * StreamDirectionChanged being emitted correctly (part of RSD) + * RequestStreamDirection failing (invalid direction, stream ID) + */ +} + +static void +test_no_answer (Test *test, + gconstpointer data G_GNUC_UNUSED) +{ + /* This identifier contains the magic string (no answer), which means the + * example will never answer. */ + outgoing_call (test, "smcv (no answer)", TRUE, FALSE); + + future_cli_channel_type_call_call_accept (test->chan, -1, void_cb, + test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + /* After the initial flurry of D-Bus messages, smcv still hasn't answered */ + test_connection_run_until_dbus_queue_processed (test->conn); + + tp_cli_dbus_properties_call_get_all (test->chan, -1, + FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + assert_call_properties (test->get_all_return, + FUTURE_CALL_STATE_PENDING_RECEIVER, test->self_handle, + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", + TRUE, 0, /* call flags */ + TRUE, TRUE, FALSE); /* initial audio/video must be TRUE, FALSE */ + + /* assume we're never going to get an answer, and hang up */ + future_cli_channel_type_call_call_hangup (test->chan, + -1, FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", "", + void_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + assert_ended_and_run_close (test, test->self_handle, + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, + ""); +} + +static void +test_busy (Test *test, + gconstpointer data G_GNUC_UNUSED) +{ + /* This identifier contains the magic string (busy), which means the example + * will simulate rejection of the call as busy rather than accepting it. */ + outgoing_call (test, "Robot101 (busy)", TRUE, FALSE); + + future_cli_channel_type_call_call_accept (test->chan, -1, void_cb, + test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + /* Wait for the remote contact to end the call as busy */ + loop_until_ended (test); + assert_ended_and_run_close (test, tp_channel_get_handle (test->chan, NULL), + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, + TP_ERROR_STR_BUSY); +} + +static void +test_terminated_by_peer (Test *test, + gconstpointer data G_GNUC_UNUSED) +{ + /* This contact contains the magic string "(terminate)", meaning the example + * simulates answering the call but then terminating it */ + outgoing_call (test, "The Governator (terminate)", TRUE, TRUE); + + future_cli_channel_type_call_call_accept (test->chan, -1, void_cb, + test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + /* Wait for the remote contact to answer, if they haven't already */ + + loop_until_answered (test); + + /* After that, the remote contact immediately ends the call */ + loop_until_ended (test); + assert_ended_and_run_close (test, tp_channel_get_handle (test->chan, NULL), + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, + ""); +} + +static void +test_terminate_via_close (Test *test, + gconstpointer data G_GNUC_UNUSED) +{ + outgoing_call (test, "basic-test", FALSE, TRUE); + + future_cli_channel_type_call_call_accept (test->chan, -1, void_cb, + test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + /* Wait for the remote contact to answer, if they haven't already */ + + loop_until_answered (test); + + tp_cli_dbus_properties_call_get_all (test->chan, -1, + FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + assert_call_properties (test->get_all_return, + FUTURE_CALL_STATE_ACCEPTED, test->peer_handle, + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", + TRUE, 0, /* call flags */ + TRUE, FALSE, TRUE); /* initial audio/video must be FALSE, TRUE */ + + /* Terminate the call unceremoniously, by calling Close. This is not a + * graceful hangup; rather, it's what the ChannelDispatcher would do to + * signal a client crash, undispatchability, or whatever */ + + tp_cli_channel_call_close (test->chan, -1, void_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + /* In response to termination, the channel does genuinely close */ + test_connection_run_until_dbus_queue_processed (test->conn); + g_assert (tp_proxy_get_invalidated (test->chan) != NULL); + + /* FIXME: when we hook up signals, check for expected call state + * transition before invalidation */ +} + +/* FIXME: try removing the last stream. In StreamedMedia that terminated the + * call, but in Call it's meant to just fail */ + +/* FIXME: add a special contact who refuses to have video */ + +/* FIXME: add a special contact who asks us for video */ + +/* FIXME: add a special contact whose stream errors */ + +static void +expect_incoming_call_cb (TpConnection *conn, + const GPtrArray *channels, + gpointer user_data, + GObject *weak_object G_GNUC_UNUSED) +{ + Test *test = user_data; + guint i; + + for (i = 0; i < channels->len; i++) + { + GValueArray *va = g_ptr_array_index (channels, i); + const gchar *object_path = g_value_get_boxed (va->values + 0); + GHashTable *properties = g_value_get_boxed (va->values + 1); + const gchar *channel_type; + + channel_type = tp_asv_get_string (properties, + TP_PROP_CHANNEL_CHANNEL_TYPE); + if (tp_strdiff (channel_type, FUTURE_IFACE_CHANNEL_TYPE_CALL)) + { + /* don't care about this channel */ + continue; + } + + g_assert_cmpuint (tp_asv_get_uint32 (properties, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL), + ==, TP_HANDLE_TYPE_CONTACT); + g_assert_cmpint (tp_asv_get_boolean (properties, + TP_PROP_CHANNEL_REQUESTED, NULL), ==, FALSE); + + /* we only expect to receive one call */ + g_assert (test->chan == NULL); + + /* save the channel */ + test->chan = tp_channel_new_from_properties (conn, object_path, + properties, &test->error); + test_assert_no_error (test->error); + } +} + +/* In this example connection manager, every time the presence status changes + * to available or the message changes, an incoming call is simulated. */ +static void +trigger_incoming_call (Test *test, + const gchar *message, + const gchar *expected_caller) +{ + TpProxySignalConnection *new_channels_sig; + + tp_cli_connection_interface_simple_presence_run_set_presence (test->conn, -1, + "away", "preparing for a test", &test->error, NULL); + test_assert_no_error (test->error); + + new_channels_sig = + tp_cli_connection_interface_requests_connect_to_new_channels (test->conn, + expect_incoming_call_cb, test, NULL, NULL, &test->error); + test_assert_no_error (test->error); + + tp_cli_connection_interface_simple_presence_run_set_presence (test->conn, -1, + "available", message, &test->error, NULL); + test_assert_no_error (test->error); + + /* wait for the call to happen if it hasn't already */ + while (test->chan == NULL) + { + g_main_context_iteration (NULL, TRUE); + } + + g_assert_cmpstr (tp_channel_get_identifier (test->chan), ==, + expected_caller); + test->peer_handle = tp_channel_get_handle (test->chan, NULL); + + tp_proxy_signal_connection_disconnect (new_channels_sig); + + tp_channel_call_when_ready (test->chan, channel_ready_cb, test); + g_main_loop_run (test->mainloop); + test_connect_channel_signals (test); +} + +static void +test_incoming (Test *test, + gconstpointer data G_GNUC_UNUSED) +{ + trigger_incoming_call (test, "call me?", "caller"); + + /* ring, ring! */ + tp_cli_dbus_properties_call_get_all (test->chan, -1, + FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + assert_call_properties (test->get_all_return, + FUTURE_CALL_STATE_PENDING_RECEIVER, test->peer_handle, + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", + TRUE, 0, /* call flags */ + TRUE, TRUE, FALSE); /* initial audio/video must be TRUE, FALSE */ + + /* Get Contents: we have an audio content (FIXME: assert that) */ + + tp_cli_dbus_properties_call_get (test->chan, -1, + FUTURE_IFACE_CHANNEL_TYPE_CALL, "Contents", + got_contents_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + g_assert_cmpuint (test->get_contents_return->len, ==, 1); + + /* FIXME: assert about the properties of the content and the stream */ + + /* Accept the call */ + future_cli_channel_type_call_call_accept (test->chan, -1, void_cb, + test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + tp_cli_dbus_properties_call_get_all (test->chan, -1, + FUTURE_IFACE_CHANNEL_TYPE_CALL, got_all_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + assert_call_properties (test->get_all_return, + FUTURE_CALL_STATE_ACCEPTED, test->self_handle, + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", + TRUE, 0, /* call flags */ + TRUE, TRUE, FALSE); /* initial audio/video are still TRUE, FALSE */ + + /* FIXME: check for stream directionality changes */ + + /* Hang up the call */ + future_cli_channel_type_call_call_hangup (test->chan, + -1, FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, "", "", + void_cb, test, NULL, NULL); + g_main_loop_run (test->mainloop); + test_assert_no_error (test->error); + + assert_ended_and_run_close (test, test->self_handle, + FUTURE_CALL_STATE_CHANGE_REASON_USER_REQUESTED, ""); +} + +static void +teardown (Test *test, + gconstpointer data G_GNUC_UNUSED) +{ + tp_cli_connection_run_disconnect (test->conn, -1, &test->error, NULL); + test_assert_no_error (test->error); + + if (test->members_changed_detailed_id != 0) + { + g_signal_handler_disconnect (test->chan, + test->members_changed_detailed_id); + } + + g_array_free (test->audio_request, TRUE); + g_array_free (test->video_request, TRUE); + g_array_free (test->invalid_request, TRUE); + g_array_free (test->stream_ids, TRUE); + CLEAR_HASH (&test->get_all_return); + + CLEAR_BOXED (TP_ARRAY_TYPE_OBJECT_PATH_LIST, + &test->get_contents_return); + CLEAR_HASH (&test->get_senders_return); + + CLEAR_OBJECT (&test->audio_stream); + CLEAR_OBJECT (&test->video_stream); + CLEAR_OBJECT (&test->added_content); + CLEAR_OBJECT (&test->audio_content); + CLEAR_OBJECT (&test->video_content); + CLEAR_OBJECT (&test->chan); + CLEAR_OBJECT (&test->conn); + CLEAR_OBJECT (&test->cm); + + CLEAR_OBJECT (&test->service_cm); + + CLEAR_OBJECT (&test->dbus); + g_main_loop_unref (test->mainloop); + test->mainloop = NULL; +} + +int +main (int argc, + char **argv) +{ + g_set_prgname ("call-example"); + g_test_init (&argc, &argv, NULL); + g_test_bug_base ("http://bugs.freedesktop.org/show_bug.cgi?id="); + + g_type_init (); + future_cli_init (); + + g_test_add ("/call/basics", Test, NULL, setup, test_basics, teardown); + g_test_add ("/call/busy", Test, NULL, setup, test_busy, teardown); + g_test_add ("/call/no-answer", Test, NULL, setup, test_no_answer, + teardown); + g_test_add ("/call/terminated-by-peer", Test, NULL, setup, + test_terminated_by_peer, teardown); + g_test_add ("/call/terminate-via-close", Test, NULL, setup, + test_terminate_via_close, teardown); + g_test_add ("/call/incoming", Test, NULL, setup, test_incoming, + teardown); + + return g_test_run (); +} |