/* * sip-media-channel.c - Source for RakiaMediaChannel * Copyright (C) 2005-2008 Collabora Ltd. * Copyright (C) 2005-2010 Nokia Corporation * @author Kai Vehmanen * @author Mikhail Zabaluev * * Based on telepathy-gabble implementation (gabble-media-channel). * @author Ole Andre Vadla Ravnaas * * This work 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 work 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 work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include "rakia/media-channel.h" #include #include #include #include #include #include #include #include #include #include #define DEBUG_FLAG RAKIA_DEBUG_MEDIA #include "rakia/debug.h" #include #include #include #define RAKIA_CHANNEL_CALL_STATE_PROCEEDING_MASK \ (TP_CHANNEL_CALL_STATE_RINGING | \ TP_CHANNEL_CALL_STATE_QUEUED | \ TP_CHANNEL_CALL_STATE_IN_PROGRESS) /* DTMF dialstring playback durations in milliseconds */ #define RAKIA_DTMF_TONE_DURATION 250 #define RAKIA_DTMF_GAP_DURATION 100 #define RAKIA_DTMF_PAUSE_DURATION 3000 static void event_target_init (gpointer, gpointer); static void channel_iface_init (gpointer, gpointer); static void media_signalling_iface_init (gpointer, gpointer); static void streamed_media_iface_init (gpointer, gpointer); static void dtmf_iface_init (gpointer, gpointer); static void call_state_iface_init (gpointer, gpointer); static void hold_iface_init (gpointer, gpointer); static void priv_session_dtmf_ready_cb (RakiaMediaSession *session, RakiaMediaChannel *channel); G_DEFINE_TYPE_WITH_CODE (RakiaMediaChannel, rakia_media_channel, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (RAKIA_TYPE_EVENT_TARGET, event_target_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES, tp_dbus_properties_mixin_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_PROPERTIES_INTERFACE, tp_properties_mixin_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP, tp_group_mixin_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_MEDIA_SIGNALLING, media_signalling_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_DTMF, dtmf_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_CALL_STATE, call_state_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_HOLD, hold_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_STREAMED_MEDIA, streamed_media_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_EXPORTABLE_CHANNEL, NULL); G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL)); static const gchar *rakia_media_channel_interfaces[] = { TP_IFACE_CHANNEL_INTERFACE_GROUP, TP_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING, TP_IFACE_CHANNEL_INTERFACE_DTMF, TP_IFACE_CHANNEL_INTERFACE_CALL_STATE, TP_IFACE_CHANNEL_INTERFACE_HOLD, TP_IFACE_PROPERTIES_INTERFACE, NULL }; /* properties */ enum { PROP_CONNECTION = 1, PROP_OBJECT_PATH, PROP_CHANNEL_TYPE, PROP_HANDLE_TYPE, PROP_HANDLE, PROP_TARGET_ID, PROP_INITIATOR, PROP_INITIATOR_ID, PROP_REQUESTED, PROP_INTERFACES, PROP_CHANNEL_DESTROYED, PROP_CHANNEL_PROPERTIES, PROP_INITIAL_AUDIO, PROP_INITIAL_VIDEO, PROP_IMMUTABLE_STREAMS, PROP_CURRENTLY_SENDING_TONES, PROP_INITIAL_TONES, PROP_DEFERRED_TONES, PROP_SIP_SESSION, /* Telepathy properties (see below too) */ PROP_NAT_TRAVERSAL, PROP_STUN_SERVER, PROP_STUN_PORT, LAST_PROPERTY }; /* TP channel properties */ enum { TP_PROP_NAT_TRAVERSAL = 0, TP_PROP_STUN_SERVER, TP_PROP_STUN_PORT, NUM_TP_PROPS }; static const TpPropertySignature media_channel_property_signatures[NUM_TP_PROPS] = { { "nat-traversal", G_TYPE_STRING }, { "stun-server", G_TYPE_STRING }, { "stun-port", G_TYPE_UINT }, }; /* signals */ enum { NUM_SIGNALS }; //static guint signals[NUM_SIGNALS] = { 0 }; /* private structure */ struct _RakiaMediaChannelPrivate { RakiaBaseConnection *conn; RakiaMediaSession *session; RakiaSipSession *sipsession; gchar *object_path; TpHandle handle; TpHandle initiator; GHashTable *call_states; gchar *stun_server; guint stun_port; TpDTMFPlayer *dtmf_player; gchar *initial_tones; gchar *deferred_tones; gboolean initial_audio; gboolean initial_video; gboolean immutable_streams; gboolean closed; gboolean dispose_has_run; }; static void rakia_media_channel_dispose (GObject *object); static void rakia_media_channel_finalize (GObject *object); static void rakia_media_channel_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void rakia_media_channel_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void priv_create_session (RakiaMediaChannel *channel, TpHandle peer); static void priv_destroy_session(RakiaMediaChannel *channel); static gboolean rakia_media_channel_remove_with_reason ( GObject *iface, TpHandle handle, const gchar *message, guint reason, GError **error); static void priv_session_state_changed_cb (RakiaSipSession *session, guint old_state, guint state, RakiaMediaChannel *channel); #define RAKIA_MEDIA_CHANNEL_GET_PRIVATE(chan) ((chan)->priv) /*********************************************************************** * Set: Gobject interface ***********************************************************************/ static void rakia_media_channel_init (RakiaMediaChannel *self) { RakiaMediaChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, RAKIA_TYPE_MEDIA_CHANNEL, RakiaMediaChannelPrivate); self->priv = priv; /* allocate any data required by the object here */ priv->call_states = g_hash_table_new (NULL, NULL); /* initialise the properties mixin *before* GObject * sets the construct-time properties */ tp_properties_mixin_init (G_OBJECT (self), G_STRUCT_OFFSET (RakiaMediaChannel, properties)); } static void session_ringing_cb (RakiaSipSession *sipsession, RakiaMediaChannel *self) { RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); rakia_media_channel_change_call_state (self, priv->handle, TP_CHANNEL_CALL_STATE_RINGING, 0); } static void session_queued_cb (RakiaSipSession *sipsession, RakiaMediaChannel *self) { RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); rakia_media_channel_change_call_state (self, priv->handle, TP_CHANNEL_CALL_STATE_QUEUED, 0); } static void session_in_progress_cb (RakiaSipSession *sipsession, RakiaMediaChannel *self) { RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); rakia_media_channel_change_call_state (self, priv->handle, TP_CHANNEL_CALL_STATE_IN_PROGRESS, 0); } static void session_established_cb (RakiaSipSession *sipsession, RakiaMediaChannel *self) { RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); TpIntSet *add = tp_intset_new_containing (priv->handle); tp_group_mixin_change_members ((GObject *) self, "", add, /* add */ NULL, /* remove */ NULL, NULL, priv->handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE); tp_intset_destroy (add); } static void session_ended_cb (RakiaSipSession *sipsession, gboolean self_actor, guint cause, gchar *message, RakiaMediaChannel *self) { RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); TpGroupMixin *mixin = TP_GROUP_MIXIN (self); TpIntSet *remove; TpHandle actor; TpChannelGroupChangeReason reason; remove = tp_intset_new (); tp_intset_add (remove, priv->handle); tp_intset_add (remove, mixin->self_handle); if (self_actor) actor = mixin->self_handle; else actor = priv->handle; if (message == NULL) message = ""; switch (cause) { case 410: case 604: reason = TP_CHANNEL_GROUP_CHANGE_REASON_INVALID_CONTACT; break; case 486: case 600: reason = TP_CHANNEL_GROUP_CHANGE_REASON_BUSY; break; case 408: reason = TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER; break; case 404: case 480: if (rakia_sip_session_get_state (priv->sipsession) <= RAKIA_SIP_SESSION_STATE_INVITE_SENT) reason = TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER; else reason = TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE; break; case 603: /* No reason means roughly "rejected" */ reason = TP_CHANNEL_GROUP_CHANGE_REASON_NONE; break; case 403: case 401: case 407: reason = TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED; break; default: if (cause < 300) reason = TP_CHANNEL_GROUP_CHANGE_REASON_NONE; else reason = TP_CHANNEL_GROUP_CHANGE_REASON_ERROR; break; } tp_group_mixin_change_members ((GObject *) self, message, NULL, remove, NULL, NULL, actor, reason); tp_intset_destroy (remove); } static void rakia_media_channel_constructed (GObject *obj) { RakiaMediaChannel *chan = RAKIA_MEDIA_CHANNEL (obj); RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (chan); TpBaseConnection *conn = (TpBaseConnection *)(priv->conn); GObjectClass *parent_object_class = G_OBJECT_CLASS (rakia_media_channel_parent_class); TpDBusDaemon *bus; TpHandleRepoIface *contact_repo; TpIntSet *add; if (parent_object_class->constructed != NULL) parent_object_class->constructed (obj); contact_repo = tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT); if (priv->handle != 0) tp_handle_ref (contact_repo, priv->handle); /* register object on the bus */ bus = tp_base_connection_get_dbus_daemon (conn); DEBUG("registering object to dbus path=%s", priv->object_path); tp_dbus_daemon_register_object (bus, priv->object_path, obj); /* initialize group mixin */ tp_group_mixin_init (obj, G_STRUCT_OFFSET (RakiaMediaChannel, group), contact_repo, conn->self_handle); /* automatically add initiator to channel, but also ref them again (because * priv->initiator is the InitiatorHandle) */ g_assert (priv->initiator != 0); tp_handle_ref (contact_repo, priv->initiator); add = tp_intset_new_containing (priv->initiator); tp_group_mixin_change_members (obj, "", add, NULL, NULL, NULL, 0, 0); tp_intset_destroy (add); /* We start off with lots of flags, and then delete them as we work out what * kind of channel we are, rather than trying to track what we need to * add/remove over time. We should always have the right flags before we are * advertised on the bus. */ tp_group_mixin_change_flags (obj, TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | TP_CHANNEL_GROUP_FLAG_CAN_RESCIND | TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0); g_signal_connect_object (priv->sipsession, "ringing", G_CALLBACK (session_ringing_cb), chan, 0); g_signal_connect_object (priv->sipsession, "queued", G_CALLBACK (session_queued_cb), chan, 0); g_signal_connect_object (priv->sipsession, "in-progress", G_CALLBACK (session_in_progress_cb), chan, 0); g_signal_connect_object (priv->sipsession, "established", G_CALLBACK (session_established_cb), chan, 0); g_signal_connect_object (priv->sipsession, "ended", G_CALLBACK (session_ended_cb), chan, 0); g_signal_connect_object (priv->sipsession, "state-changed", G_CALLBACK(priv_session_state_changed_cb), chan, 0); if (priv->initiator != conn->self_handle) { /* Incoming */ priv->initial_audio = rakia_sip_session_has_media (priv->sipsession, TP_MEDIA_STREAM_TYPE_AUDIO); priv->initial_video = rakia_sip_session_has_media (priv->sipsession, TP_MEDIA_STREAM_TYPE_VIDEO); priv_create_session (chan, priv->initiator); g_assert (priv->session != NULL); //rakia_sip_session_receive_invite (priv->sipsession); } else { priv_create_session (chan, priv->handle); } } static void rakia_media_channel_class_init (RakiaMediaChannelClass *klass) { static TpDBusPropertiesMixinPropImpl channel_props[] = { { "ChannelType", "channel-type", NULL }, { "Interfaces", "interfaces", NULL }, { "TargetHandleType", "handle-type", NULL }, { "TargetHandle", "handle", NULL }, { "TargetID", "target-id", NULL }, { "InitiatorHandle", "initiator", NULL }, { "InitiatorID", "initiator-id", NULL }, { "Requested", "requested", NULL }, { NULL } }; static TpDBusPropertiesMixinPropImpl streamed_media_props[] = { { "InitialAudio", "initial-audio", NULL }, { "InitialVideo", "initial-video", NULL }, { "ImmutableStreams", "immutable-streams", NULL }, { NULL } }; static TpDBusPropertiesMixinPropImpl dtmf_props[] = { { "CurrentlySendingTones", "currently-sending-tones", NULL }, { "InitialTones", "initial-tones", NULL }, { "DeferredTones", "deferred-tones", NULL }, { NULL } }; static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = { { TP_IFACE_CHANNEL, tp_dbus_properties_mixin_getter_gobject_properties, NULL, channel_props, }, { TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, tp_dbus_properties_mixin_getter_gobject_properties, NULL, streamed_media_props, }, { TP_IFACE_CHANNEL_INTERFACE_DTMF, tp_dbus_properties_mixin_getter_gobject_properties, NULL, dtmf_props, }, { NULL } }; GObjectClass *object_class = G_OBJECT_CLASS (klass); GParamSpec *param_spec; DEBUG("enter"); g_type_class_add_private (klass, sizeof (RakiaMediaChannelPrivate)); object_class->constructed = rakia_media_channel_constructed; object_class->dispose = rakia_media_channel_dispose; object_class->finalize = rakia_media_channel_finalize; object_class->get_property = rakia_media_channel_get_property; object_class->set_property = rakia_media_channel_set_property; 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_OBJECT_PATH, "object-path"); g_object_class_override_property (object_class, PROP_CHANNEL_TYPE, "channel-type"); 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", "RakiaConnection object", "SIP connection object that owns this SIP media channel object.", RAKIA_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_string ("nat-traversal", "NAT traversal mechanism", "A string representing the type of NAT traversal that should be " "performed for streams on this channel.", "none", G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_NAT_TRAVERSAL, param_spec); param_spec = g_param_spec_object ("sip-session", "RakiaSipSession object", "SIP session object that is used for this SIP media channel object.", RAKIA_TYPE_SIP_SESSION, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_SIP_SESSION, param_spec); param_spec = g_param_spec_string ("stun-server", "STUN server", "IP or address of STUN server.", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_STUN_SERVER, param_spec); param_spec = g_param_spec_uint ("stun-port", "STUN port", "UDP port of STUN server.", 0, G_MAXUINT16, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_STUN_PORT, param_spec); param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces", "Addition 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", "Target SIP URI", "Currently empty, because this channel always has handle 0.", 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", "Channel initiator", "The TpHandle representing the contact who created 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, param_spec); param_spec = g_param_spec_string ("initiator-id", "Creator URI", "The URI 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_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_REQUESTED, param_spec); param_spec = g_param_spec_boolean ("initial-audio", "InitialAudio", "Whether the channel initially contained an audio stream", 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", "InitialVideo", "Whether the channel initially contained a video stream", 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_boolean ("immutable-streams", "ImmutableStreams", "Whether the set of streams on this channel are fixed once requested", FALSE, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_IMMUTABLE_STREAMS, param_spec); param_spec = g_param_spec_boolean ("currently-sending-tones", "Currently sending tones", "True if the channel is currently sending DTMF tones", FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_CURRENTLY_SENDING_TONES, param_spec); param_spec = g_param_spec_string ("initial-tones", "Initial tones", "The initial DTMF tones to send after audio stream(s) are established.", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_INITIAL_TONES, param_spec); param_spec = g_param_spec_string ("deferred-tones", "Deferred tones", "The DTMF tones deferred waiting for user input.", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_DEFERRED_TONES, param_spec); tp_properties_mixin_class_init (object_class, G_STRUCT_OFFSET (RakiaMediaChannelClass, properties_class), media_channel_property_signatures, NUM_TP_PROPS, NULL); klass->dbus_props_class.interfaces = prop_interfaces; tp_dbus_properties_mixin_class_init (object_class, G_STRUCT_OFFSET (RakiaMediaChannelClass, dbus_props_class)); tp_group_mixin_class_init (object_class, G_STRUCT_OFFSET (RakiaMediaChannelClass, group_class), _rakia_media_channel_add_member, NULL); tp_group_mixin_class_allow_self_removal (object_class); tp_group_mixin_class_set_remove_with_reason_func(object_class, rakia_media_channel_remove_with_reason); tp_group_mixin_init_dbus_properties (object_class); } static void rakia_media_channel_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { RakiaMediaChannel *chan = RAKIA_MEDIA_CHANNEL (object); RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (chan); TpBaseConnection *base_conn = TP_BASE_CONNECTION (priv->conn); switch (property_id) { case PROP_CONNECTION: g_value_set_object (value, priv->conn); break; case PROP_OBJECT_PATH: g_value_set_string (value, priv->object_path); break; case PROP_CHANNEL_TYPE: g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA); break; case PROP_HANDLE: g_value_set_uint (value, priv->handle); break; case PROP_HANDLE_TYPE: g_value_set_uint (value, priv->handle? TP_HANDLE_TYPE_CONTACT : TP_HANDLE_TYPE_NONE); break; case PROP_TARGET_ID: if (priv->handle != 0) { TpHandleRepoIface *repo = tp_base_connection_get_handles ( base_conn, TP_HANDLE_TYPE_CONTACT); g_value_set_string (value, tp_handle_inspect (repo, priv->handle)); } else g_value_set_static_string (value, ""); break; case PROP_INITIATOR: g_value_set_uint (value, priv->initiator); break; case PROP_INITIATOR_ID: { TpHandleRepoIface *repo = tp_base_connection_get_handles ( base_conn, TP_HANDLE_TYPE_CONTACT); g_value_set_string (value, tp_handle_inspect (repo, priv->initiator)); } break; case PROP_REQUESTED: g_value_set_boolean (value, (priv->initiator == base_conn->self_handle)); break; case PROP_INTERFACES: g_value_set_static_boxed (value, rakia_media_channel_interfaces); break; case PROP_CHANNEL_DESTROYED: g_value_set_boolean (value, priv->closed); 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", TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, "InitialAudio", TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, "InitialVideo", TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA, "ImmutableStreams", NULL)); break; case PROP_INITIAL_AUDIO: g_value_set_boolean (value, priv->initial_audio); break; case PROP_INITIAL_VIDEO: g_value_set_boolean (value, priv->initial_video); break; case PROP_IMMUTABLE_STREAMS: g_value_set_boolean (value, priv->immutable_streams); break; case PROP_STUN_SERVER: g_value_set_string (value, priv->stun_server); break; case PROP_STUN_PORT: g_value_set_uint (value, priv->stun_port); break; case PROP_CURRENTLY_SENDING_TONES: g_value_set_boolean (value, priv->dtmf_player != NULL && tp_dtmf_player_is_active (priv->dtmf_player)); break; case PROP_INITIAL_TONES: if (priv->initial_tones == NULL) g_value_set_static_string (value, ""); else g_value_set_string (value, priv->initial_tones); break; case PROP_DEFERRED_TONES: if (priv->deferred_tones == NULL) g_value_set_static_string (value, ""); else g_value_set_string (value, priv->deferred_tones); break; default: /* Some properties live in the mixin */ { const gchar *param_name; guint tp_property_id; GValue *tp_property_value; param_name = g_param_spec_get_name (pspec); if (G_LIKELY (tp_properties_mixin_has_property (object, param_name, &tp_property_id))) { tp_property_value = chan->properties.properties[tp_property_id].value; if (G_LIKELY (tp_property_value != NULL)) { g_value_copy (tp_property_value, value); return; } } } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static gboolean rakia_media_channel_set_tp_property (RakiaMediaChannel *chan, const GValue *value, GParamSpec *pspec) { GObject *obj = (GObject *) chan; const gchar *param_name = g_param_spec_get_name (pspec); guint tp_property_id; if (G_LIKELY (tp_properties_mixin_has_property (obj, param_name, &tp_property_id))) { tp_properties_mixin_change_value (obj, tp_property_id, value, NULL); tp_properties_mixin_change_flags (obj, tp_property_id, TP_PROPERTY_FLAG_READ, 0, NULL); return TRUE; } else { WARNING("Telepathy property '%s' is not defined for media channels", param_name); return FALSE; } } static void rakia_media_channel_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { RakiaMediaChannel *chan = RAKIA_MEDIA_CHANNEL (object); RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (chan); switch (property_id) { case PROP_HANDLE_TYPE: case PROP_CHANNEL_TYPE: /* this property is writable in the interface, but not actually * meaningfully changable on this channel, so we do nothing */ break; case PROP_CONNECTION: priv->conn = g_value_dup_object (value); break; case PROP_OBJECT_PATH: g_free (priv->object_path); priv->object_path = g_value_dup_string (value); break; case PROP_HANDLE: /* XXX: this property is defined as writable, * but don't set it after construction, mmkay? */ /* we don't ref it here because we don't necessarily have access to the * contact repo yet - instead we ref it in constructed. */ priv->handle = g_value_get_uint (value); break; case PROP_INITIATOR: /* similarly we can't ref this yet */ priv->initiator = g_value_get_uint (value); break; case PROP_INITIAL_AUDIO: priv->initial_audio = g_value_get_boolean (value); break; case PROP_INITIAL_VIDEO: priv->initial_video = g_value_get_boolean (value); break; case PROP_IMMUTABLE_STREAMS: priv->immutable_streams = g_value_get_boolean (value); break; case PROP_STUN_SERVER: priv->stun_server = g_value_dup_string (value); /* Also expose as a legacy Telepathy property */ rakia_media_channel_set_tp_property (chan, value, pspec); break; case PROP_STUN_PORT: priv->stun_port = g_value_get_uint (value); /* Also expose as a legacy Telepathy property */ rakia_media_channel_set_tp_property (chan, value, pspec); break; case PROP_INITIAL_TONES: priv->initial_tones = g_value_dup_string (value); break; case PROP_SIP_SESSION: priv->sipsession = g_value_dup_object (value); break; default: /* some properties live in the mixin */ if (rakia_media_channel_set_tp_property (chan, value, pspec)) return; G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void rakia_media_channel_dispose (GObject *object) { RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (object); RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); TpHandleRepoIface *contact_handles; if (priv->dispose_has_run) return; DEBUG("enter"); priv->dispose_has_run = TRUE; if (!priv->closed) rakia_media_channel_close (self); if (priv->sipsession) g_object_unref (priv->sipsession); if (priv->dtmf_player != NULL) g_object_unref (priv->dtmf_player); contact_handles = tp_base_connection_get_handles ( TP_BASE_CONNECTION (priv->conn), TP_HANDLE_TYPE_CONTACT); tp_handle_unref (contact_handles, priv->initiator); priv->initiator = 0; g_object_unref (priv->conn); if (G_OBJECT_CLASS (rakia_media_channel_parent_class)->dispose) G_OBJECT_CLASS (rakia_media_channel_parent_class)->dispose (object); DEBUG("exit"); } static void rakia_media_channel_finalize (GObject *object) { RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (object); RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); g_hash_table_unref (priv->call_states); g_free (priv->initial_tones); g_free (priv->deferred_tones); g_free (priv->stun_server); g_free (priv->object_path); tp_group_mixin_finalize (object); tp_properties_mixin_finalize (object); G_OBJECT_CLASS (rakia_media_channel_parent_class)->finalize (object); DEBUG("exit"); } /*********************************************************************** * Set: Channel interface implementation (same for 0.12/0.13) ***********************************************************************/ /** * rakia_media_channel_close_async * * Implements DBus method Close * on interface org.freedesktop.Telepathy.Channel */ static void rakia_media_channel_dbus_close (TpSvcChannel *iface, DBusGMethodInvocation *context) { RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface); rakia_media_channel_close (self); tp_svc_channel_return_from_close (context); } void rakia_media_channel_close (RakiaMediaChannel *obj) { RakiaMediaChannelPrivate *priv; DEBUG("enter"); g_assert (RAKIA_IS_MEDIA_CHANNEL (obj)); priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (obj); if (priv->closed) return; priv->closed = TRUE; if (priv->sipsession) { rakia_sip_session_terminate (priv->sipsession); } tp_svc_channel_emit_closed ((TpSvcChannel *)obj); return; } /** * rakia_media_channel_get_channel_type * * Implements DBus method GetChannelType * on interface org.freedesktop.Telepathy.Channel */ static void rakia_media_channel_get_channel_type (TpSvcChannel *obj, DBusGMethodInvocation *context) { tp_svc_channel_return_from_get_channel_type (context, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA); } /** * rakia_media_channel_get_handle * * Implements DBus method GetHandle * on interface org.freedesktop.Telepathy.Channel */ static void rakia_media_channel_get_handle (TpSvcChannel *iface, DBusGMethodInvocation *context) { RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface); RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); if (priv->handle != 0) tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT, priv->handle); else tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_NONE, 0); } /** * rakia_media_channel_get_interfaces * * Implements DBus method GetInterfaces * on interface org.freedesktop.Telepathy.Channel */ static void rakia_media_channel_get_interfaces (TpSvcChannel *iface, DBusGMethodInvocation *context) { tp_svc_channel_return_from_get_interfaces (context, rakia_media_channel_interfaces); } /*********************************************************************** * Set: Channel.Interface.MediaSignalling Telepathy-0.13 interface ***********************************************************************/ /** * rakia_media_channel_get_session_handlers * * Implements DBus method GetSessionHandlers * on interface org.freedesktop.Telepathy.Channel.Interface.MediaSignalling * * @error: Used to return a pointer to a GError detailing any error * that occured, DBus will throw the error only if this * function returns false. * * Returns: TRUE if successful, FALSE if an error was thrown. */ static void rakia_media_channel_get_session_handlers (TpSvcChannelInterfaceMediaSignalling *iface, DBusGMethodInvocation *context) { RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface); RakiaMediaChannelPrivate *priv; GPtrArray *ret; GValue handler = { 0 }; DEBUG("enter"); g_assert (RAKIA_IS_MEDIA_CHANNEL (self)); priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); ret = g_ptr_array_new (); if (priv->session) { GType handler_type; gchar *path; g_object_get (priv->session, "object-path", &path, NULL); handler_type = dbus_g_type_get_struct ("GValueArray", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_STRING, G_TYPE_INVALID); g_value_init (&handler, handler_type); g_value_take_boxed (&handler, dbus_g_type_specialized_construct (handler_type)); dbus_g_type_struct_set (&handler, 0, path, 1, "rtp", G_MAXUINT); g_free (path); g_ptr_array_add (ret, g_value_get_boxed (&handler)); } tp_svc_channel_interface_media_signalling_return_from_get_session_handlers ( context, ret); if (G_IS_VALUE(&handler)) g_value_unset (&handler); g_ptr_array_unref (ret); } /*********************************************************************** * Set: Channel.Type.StreamedMedia Telepathy-0.13 interface ***********************************************************************/ /** * rakia_media_channel_list_streams * * Implements D-Bus method ListStreams * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia */ static void rakia_media_channel_list_streams (TpSvcChannelTypeStreamedMedia *iface, DBusGMethodInvocation *context) { RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface); RakiaMediaChannelPrivate *priv; GPtrArray *ret = NULL; priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); ret = g_ptr_array_new (); if (priv->session != NULL) rakia_media_session_list_streams (priv->session, ret); tp_svc_channel_type_streamed_media_return_from_list_streams (context, ret); g_boxed_free (TP_ARRAY_TYPE_MEDIA_STREAM_INFO_LIST, ret); } /** * rakia_media_channel_remove_streams * * Implements D-Bus method RemoveStreams * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia */ static void rakia_media_channel_remove_streams (TpSvcChannelTypeStreamedMedia *iface, const GArray *streams, DBusGMethodInvocation *context) { RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface); RakiaMediaChannelPrivate *priv; GError *error = NULL; priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); if (priv->immutable_streams) { error = g_error_new (TP_ERRORS, TP_ERROR_NOT_IMPLEMENTED, "Cannot remove streams from the existing channel"); } else if (priv->session != NULL) { rakia_media_session_remove_streams(priv->session, streams, &error); } else { error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "No session is available"); } if (error != NULL) { dbus_g_method_return_error (context, error); g_error_free (error); return; } tp_svc_channel_type_streamed_media_return_from_remove_streams (context); } /** * rakia_media_channel_request_stream_direction * * Implements D-Bus method RequestStreamDirection * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia */ static void rakia_media_channel_request_stream_direction (TpSvcChannelTypeStreamedMedia *iface, guint stream_id, guint stream_direction, DBusGMethodInvocation *context) { RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface); RakiaMediaChannelPrivate *priv; GError *error = NULL; priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); if (priv->immutable_streams) { GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "Cannot change directions on an immutable channel" }; dbus_g_method_return_error (context, &e); return; } if (priv->session != NULL) { rakia_media_session_request_stream_direction (priv->session, stream_id, stream_direction, &error); } else { error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "The media session is not available"); } if (error == NULL) { tp_svc_channel_type_streamed_media_return_from_request_stream_direction (context); } else { dbus_g_method_return_error (context, error); g_error_free (error); } } /** * rakia_media_channel_request_streams * * Implements D-Bus method RequestStreams * on interface org.freedesktop.Telepathy.Channel.Type.StreamedMedia */ static void rakia_media_channel_request_streams (TpSvcChannelTypeStreamedMedia *iface, guint contact_handle, const GArray *types, DBusGMethodInvocation *context) { RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface); GError *error = NULL; GPtrArray *ret = NULL; RakiaMediaChannelPrivate *priv; TpHandleRepoIface *contact_repo; DEBUG("enter"); priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); if (priv->immutable_streams) { GError e = { TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "Cannot add streams to the immutable channel" }; dbus_g_method_return_error (context, &e); return; } contact_repo = tp_base_connection_get_handles ( (TpBaseConnection *)(priv->conn), TP_HANDLE_TYPE_CONTACT); if (!tp_handle_is_valid (contact_repo, contact_handle, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); return; } ret = g_ptr_array_sized_new (types->len); if (rakia_media_session_request_streams (priv->session, types, ret, &error)) { g_assert (types->len == ret->len); tp_svc_channel_type_streamed_media_return_from_request_streams (context, ret); } else { dbus_g_method_return_error (context, error); g_error_free (error); } g_boxed_free (TP_ARRAY_TYPE_MEDIA_STREAM_INFO_LIST, ret); DEBUG ("exit"); } /*********************************************************************** * Set: sip-media-channel API towards sip-connection ***********************************************************************/ void rakia_media_channel_create_initial_streams (RakiaMediaChannel *self) { RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); g_assert (priv->initiator != priv->handle); /* RequestChannel(None, 0) => channel is anonymous: * caller uses RequestStreams to set the peer and start the call. */ if (priv->handle == 0) return; g_assert (priv->session != NULL); if (priv->initial_audio) rakia_media_session_add_stream (priv->session, TP_MEDIA_STREAM_TYPE_AUDIO, TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, TRUE); if (priv->initial_video) rakia_media_session_add_stream (priv->session, TP_MEDIA_STREAM_TYPE_VIDEO, TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, TRUE); } guint rakia_media_channel_change_call_state (RakiaMediaChannel *self, TpHandle peer, guint flags_add, guint flags_remove) { RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); gpointer key = GUINT_TO_POINTER (peer); guint old_state; guint new_state; /* XXX: check if the peer is a member? */ old_state = GPOINTER_TO_UINT (g_hash_table_lookup (priv->call_states, key)); new_state = (old_state | flags_add) & ~flags_remove; if (new_state != old_state) { DEBUG ("setting call state %u for peer %u", new_state, peer); if (new_state == 0) g_hash_table_remove (priv->call_states, key); else g_hash_table_replace (priv->call_states, key, GUINT_TO_POINTER (new_state)); tp_svc_channel_interface_call_state_emit_call_state_changed (self, peer, new_state); } return new_state; } static void priv_session_state_changed_cb (RakiaSipSession *session, guint old_state, guint state, RakiaMediaChannel *channel) { RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (channel); TpGroupMixin *mixin = TP_GROUP_MIXIN (channel); TpHandle self_handle; TpHandle peer; TpIntSet *set = NULL; DEBUG("enter"); self_handle = mixin->self_handle; peer = rakia_media_session_get_peer (priv->session); switch (state) { case RAKIA_SIP_SESSION_STATE_INVITE_SENT: g_assert (priv->initiator == self_handle); /* add the peer to remote pending */ set = tp_intset_new_containing (peer); tp_group_mixin_change_members ((GObject *)channel, "", NULL, /* add */ NULL, /* remove */ NULL, /* local pending */ set, /* remote pending */ self_handle, /* actor */ TP_CHANNEL_GROUP_CHANGE_REASON_INVITED); /* update flags: no more adding */ tp_group_mixin_change_flags ((GObject *)channel, 0, TP_CHANNEL_GROUP_FLAG_CAN_ADD); break; case RAKIA_SIP_SESSION_STATE_INVITE_RECEIVED: /* add ourself to local pending */ set = tp_intset_new_containing (self_handle); tp_group_mixin_change_members ((GObject *) channel, "", NULL, /* add */ NULL, /* remove */ set, /* local pending */ NULL, /* remote pending */ priv->initiator, /* actor */ TP_CHANNEL_GROUP_CHANGE_REASON_INVITED); /* No adding more members to the incoming call. Therefore also not * possible to add anyone to remote-pending, so rescinding would make * utterly no sense. We also disallow removing the remote peer if * we are not the initiator, so disallow that too. * Removing yourself to end the call is not represented by group flags. */ tp_group_mixin_change_flags ((GObject *) channel, 0, TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_REMOVE | TP_CHANNEL_GROUP_FLAG_CAN_RESCIND); break; case RAKIA_SIP_SESSION_STATE_ACTIVE: if (priv->initiator == self_handle) { if (!tp_handle_set_is_member (mixin->remote_pending, peer)) break; /* no-op */ /* the peer has promoted itself to members */ set = tp_intset_new_containing (peer); tp_group_mixin_change_members ((GObject *)channel, "", set, /* add */ NULL, /* remove */ NULL, NULL, peer, 0); } else { if (!tp_handle_set_is_member (mixin->local_pending, self_handle)) break; /* no-op */ /* promote ourselves to members */ set = tp_intset_new_containing (self_handle); tp_group_mixin_change_members ((GObject *)channel, "", set, /* add */ NULL, /* remove */ NULL, NULL, self_handle, 0); } /* update flags: deny adding and rescinding. Removing the remote peer is * still allowed. * Removing yourself to end the call is not represented by group flags. */ tp_group_mixin_change_flags ((GObject *)channel, 0, TP_CHANNEL_GROUP_FLAG_CAN_ADD | TP_CHANNEL_GROUP_FLAG_CAN_RESCIND); break; case RAKIA_SIP_SESSION_STATE_ENDED: set = tp_intset_new (); /* remove us and the peer from the member list */ tp_intset_add (set, self_handle); tp_intset_add (set, peer); tp_group_mixin_change_members ((GObject *)channel, "", NULL, /* add */ set, /* remove */ NULL, NULL, 0, 0); /* Close the channel; destroy the session first to avoid * the rakia_media_session_terminate() path in this case */ priv_destroy_session (channel); rakia_media_channel_close (channel); break; } if (set != NULL) tp_intset_destroy (set); } /** * priv_create_session: * * Creates a RakiaMediaSession object for given peer. **/ static void priv_create_session (RakiaMediaChannel *channel, TpHandle peer) { RakiaMediaChannelPrivate *priv; RakiaMediaSession *session; TpBaseConnection *conn; TpHandleRepoIface *contact_repo; gchar *object_path; gchar *local_ip_address = NULL; DEBUG("enter"); priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (channel); conn = (TpBaseConnection *)(priv->conn); contact_repo = tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT); g_assert (priv->session == NULL); object_path = g_strdup_printf ("%s/MediaSession%u", priv->object_path, peer); DEBUG("allocating session, peer=%u", peer); /* The channel manages references to the peer handle for the session */ tp_handle_ref (contact_repo, peer); g_object_get (priv->conn, "local-ip-address", &local_ip_address, NULL); session = g_object_new (RAKIA_TYPE_MEDIA_SESSION, "dbus-daemon", tp_base_connection_get_dbus_daemon (conn), "media-channel", channel, "object-path", object_path, "sip-session", priv->sipsession, "peer", peer, "local-ip-address", local_ip_address, NULL); g_free (local_ip_address); g_signal_connect_object (session, "dtmf-ready", G_CALLBACK (priv_session_dtmf_ready_cb), channel, 0); priv->session = session; tp_svc_channel_interface_media_signalling_emit_new_session_handler ( (TpSvcChannelInterfaceMediaSignalling *)channel, object_path, "rtp"); g_free (object_path); DEBUG ("exit"); } static void priv_destroy_session(RakiaMediaChannel *channel) { RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (channel); RakiaMediaSession *session; TpBaseConnection *conn; TpHandleRepoIface *contact_repo; session = priv->session; if (session == NULL) return; DEBUG("enter"); /* Release the peer handle */ conn = (TpBaseConnection *)(priv->conn); contact_repo = tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT); tp_handle_unref (contact_repo, rakia_media_session_get_peer (session)); priv->session = NULL; g_object_unref (session); DEBUG("exit"); } gboolean _rakia_media_channel_add_member (GObject *iface, TpHandle handle, const gchar *message, GError **error) { RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface); RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); TpGroupMixin *mixin = TP_GROUP_MIXIN (iface); DEBUG("mixin->self_handle=%d, handle=%d", mixin->self_handle, handle); if (priv->initiator == mixin->self_handle) { TpIntSet *remote_pending; /* Backwards compatible behavior: * add the peer to remote pending without waiting for the actual request * to be sent */ remote_pending = tp_intset_new_containing (handle); tp_group_mixin_change_members (iface, "", NULL, /* add */ NULL, /* remove */ NULL, /* local pending */ remote_pending, /* remote pending */ mixin->self_handle, /* actor */ TP_CHANNEL_GROUP_CHANGE_REASON_INVITED); tp_intset_destroy (remote_pending); /* update flags: no more adding. * Removal and rescinding are still allowed. */ tp_group_mixin_change_flags (iface, 0, TP_CHANNEL_GROUP_FLAG_CAN_ADD); return TRUE; } if (priv->session && handle == mixin->self_handle && tp_handle_set_is_member (mixin->local_pending, handle)) { /* case b: an incoming invite */ DEBUG("accepting an incoming invite"); g_return_val_if_fail (priv->session != NULL, FALSE); rakia_media_session_accept (priv->session); return TRUE; } MESSAGE ("unsupported member change requested for a media channel"); g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "handle %u cannot be added in the current state", handle); return FALSE; } static gint rakia_status_from_tp_reason (TpChannelGroupChangeReason reason) { switch (reason) { case TP_CHANNEL_GROUP_CHANGE_REASON_NONE: return 603; /* Decline */ case TP_CHANNEL_GROUP_CHANGE_REASON_NO_ANSWER: case TP_CHANNEL_GROUP_CHANGE_REASON_OFFLINE: return 480; /* Temporarily Unavailable */ case TP_CHANNEL_GROUP_CHANGE_REASON_BUSY: return 486; /* Busy Here */ case TP_CHANNEL_GROUP_CHANGE_REASON_PERMISSION_DENIED: case TP_CHANNEL_GROUP_CHANGE_REASON_BANNED: return 403; /* Forbidden */ case TP_CHANNEL_GROUP_CHANGE_REASON_INVALID_CONTACT: return 404; /* Not Found */ default: return 500; /* Server Internal Error */ } } static gboolean rakia_media_channel_remove_with_reason (GObject *obj, TpHandle handle, const gchar *message, guint reason, GError **error) { RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (obj); RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); TpGroupMixin *mixin = TP_GROUP_MIXIN (obj); TpIntSet *set = NULL; TpHandle self_handle; gboolean rejected; self_handle = mixin->self_handle; if (priv->initiator != self_handle && handle != self_handle) { g_set_error (error, TP_ERRORS, TP_ERROR_PERMISSION_DENIED, "handle %u cannot be removed because you are not the initiator of the" " channel", handle); return FALSE; } if (priv->session == NULL) { g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "handle %u cannot be removed in the current state", handle); return FALSE; } rejected = (handle == self_handle && tp_handle_set_is_member (mixin->local_pending, handle)); /* We have excluded all the problem cases. * Now we always want to remove both members on behalf of the local user */ set = tp_intset_new (); tp_intset_add (set, self_handle); tp_intset_add (set, rakia_media_session_get_peer (priv->session)); tp_group_mixin_change_members (obj, "", NULL, /* add */ set, /* remove */ NULL, NULL, self_handle, 0); tp_intset_destroy (set); if (rejected) { /* The user has rejected the call */ gint status; status = rakia_status_from_tp_reason (reason); /* XXX: raise NotAvailable if it's the wrong state? */ rakia_sip_session_respond (priv->sipsession, status, message); /* This session is effectively ended, prevent the nua_i_state handler * from useless work */ rakia_sip_session_change_state (priv->sipsession, RAKIA_SIP_SESSION_STATE_ENDED); } else { /* Want to terminate the call in whatever other situation; * rescinding is handled by sending CANCEL */ rakia_sip_session_terminate (priv->sipsession); } return TRUE; } static void rakia_media_channel_get_call_states (TpSvcChannelInterfaceCallState *iface, DBusGMethodInvocation *context) { RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface); RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); tp_svc_channel_interface_call_state_return_from_get_call_states ( context, priv->call_states); } static void rakia_media_channel_get_hold_state (TpSvcChannelInterfaceHold *iface, DBusGMethodInvocation *context) { RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface); RakiaMediaChannelPrivate *priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); TpLocalHoldState hold_state = TP_LOCAL_HOLD_STATE_UNHELD; TpLocalHoldStateReason hold_reason = TP_LOCAL_HOLD_STATE_REASON_NONE; if (priv->session == NULL) { GError e = {TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "The media session is not available"}; dbus_g_method_return_error (context, &e); } g_object_get (priv->session, "hold-state", &hold_state, "hold-state-reason", &hold_reason, NULL); tp_svc_channel_interface_hold_return_from_get_hold_state (context, hold_state, hold_reason); } static void rakia_media_channel_request_hold (TpSvcChannelInterfaceHold *iface, gboolean hold, DBusGMethodInvocation *context) { RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface); RakiaMediaChannelPrivate *priv; priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); if (priv->immutable_streams) { GError e = {TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "Session modification disabled"}; dbus_g_method_return_error (context, &e); return; } else if (priv->session != NULL) { rakia_media_session_request_hold (priv->session, hold); } else { GError e = {TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "The media session is not available"}; dbus_g_method_return_error (context, &e); return; } tp_svc_channel_interface_hold_return_from_request_hold (context); } static void dtmf_player_started_tone_cb (TpDTMFPlayer *dtmf_player, guint event, gpointer user_data) { RakiaMediaChannel *self = user_data; RakiaMediaChannelPrivate *priv = self->priv; if (priv->session != NULL) rakia_media_session_start_telephony_event (priv->session, event); } static void dtmf_player_stopped_tone_cb (TpDTMFPlayer *dtmf_player, gpointer user_data) { RakiaMediaChannel *self = user_data; RakiaMediaChannelPrivate *priv = self->priv; if (priv->session != NULL) rakia_media_session_stop_telephony_event (priv->session); } static void dtmf_player_finished_cb (TpDTMFPlayer *dtmf_player, gboolean cancelled, gpointer user_data) { RakiaMediaChannel *self = user_data; tp_svc_channel_interface_dtmf_emit_stopped_tones (self, cancelled); } static void dtmf_player_tones_deferred_cb (TpDTMFPlayer *dtmf_player, gchar *tones, gpointer user_data) { RakiaMediaChannel *self = user_data; RakiaMediaChannelPrivate *priv = self->priv; g_free (priv->deferred_tones); priv->deferred_tones = g_strdup (tones); tp_svc_channel_interface_dtmf_emit_tones_deferred (self, tones); } static void priv_ensure_dtmf_player (RakiaMediaChannel *self) { RakiaMediaChannelPrivate *priv = self->priv; if (priv->dtmf_player != NULL) return; priv->dtmf_player = tp_dtmf_player_new (); g_signal_connect (priv->dtmf_player, "started-tone", G_CALLBACK (dtmf_player_started_tone_cb), self); g_signal_connect (priv->dtmf_player, "stopped-tone", G_CALLBACK (dtmf_player_stopped_tone_cb), self); g_signal_connect (priv->dtmf_player, "finished", G_CALLBACK (dtmf_player_finished_cb), self); g_signal_connect (priv->dtmf_player, "tones-deferred", G_CALLBACK (dtmf_player_tones_deferred_cb), self); } static gboolean rakia_media_channel_send_dtmf_tones (RakiaMediaChannel *self, const gchar *tones, guint tone_duration, GError **error) { RakiaMediaChannelPrivate *priv = self->priv; /* Perform sanity checks on the session */ if (priv->session == NULL) { g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "the media session is not available, has the channel been closed?"); return FALSE; } if (!rakia_media_session_has_media (priv->session, TP_MEDIA_STREAM_TYPE_AUDIO)) { g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE, "no audio streams are available"); return FALSE; } priv_ensure_dtmf_player (self); if (!tp_dtmf_player_play (priv->dtmf_player, tones, tone_duration, RAKIA_DTMF_GAP_DURATION, RAKIA_DTMF_PAUSE_DURATION, error)) return FALSE; g_free (priv->deferred_tones); priv->deferred_tones = NULL; tp_svc_channel_interface_dtmf_emit_sending_tones (self, tones); return TRUE; } static void rakia_media_channel_start_tone (TpSvcChannelInterfaceDTMF *iface, guint stream_id, guchar event, DBusGMethodInvocation *context) { RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface); GError *error = NULL; gchar tone[2]; DEBUG("enter"); if (event >= NUM_TP_DTMF_EVENTS) { g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "event %u is not a known DTMF event", event); dbus_g_method_return_error (context, error); g_error_free (error); return; } tone[0] = tp_dtmf_event_to_char (event); tone[1] = '\0'; if (!rakia_media_channel_send_dtmf_tones (self, tone, G_MAXUINT, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); return; } tp_svc_channel_interface_dtmf_return_from_start_tone (context); } static void rakia_media_channel_stop_tone (TpSvcChannelInterfaceDTMF *iface, guint stream_id, DBusGMethodInvocation *context) { RakiaMediaChannel *self = RAKIA_MEDIA_CHANNEL (iface); RakiaMediaChannelPrivate *priv; DEBUG("enter"); priv = RAKIA_MEDIA_CHANNEL_GET_PRIVATE (self); if (priv->dtmf_player != NULL) tp_dtmf_player_cancel (priv->dtmf_player); tp_svc_channel_interface_dtmf_return_from_stop_tone (context); } static void rakia_media_channel_multiple_tones (TpSvcChannelInterfaceDTMF *iface, const gchar *tones, DBusGMethodInvocation *context) { RakiaMediaChannel *self = (RakiaMediaChannel *) iface; GError *error = NULL; if (!rakia_media_channel_send_dtmf_tones (self, tones, RAKIA_DTMF_TONE_DURATION, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); return; } tp_svc_channel_interface_dtmf_return_from_multiple_tones (context); } static void priv_session_dtmf_ready_cb (RakiaMediaSession *session, RakiaMediaChannel *channel) { RakiaMediaChannelPrivate *priv = channel->priv; if (!tp_str_empty (priv->initial_tones)) rakia_media_channel_send_dtmf_tones (channel, priv->initial_tones, RAKIA_DTMF_TONE_DURATION, NULL); } static void event_target_init(gpointer g_iface, gpointer iface_data) { } static void channel_iface_init(gpointer g_iface, gpointer iface_data) { TpSvcChannelClass *klass = (TpSvcChannelClass *)g_iface; tp_svc_channel_implement_close ( klass, rakia_media_channel_dbus_close); #define IMPLEMENT(x) tp_svc_channel_implement_##x (\ klass, rakia_media_channel_##x) IMPLEMENT(get_channel_type); IMPLEMENT(get_handle); IMPLEMENT(get_interfaces); #undef IMPLEMENT } static void streamed_media_iface_init(gpointer g_iface, gpointer iface_data) { TpSvcChannelTypeStreamedMediaClass *klass = (TpSvcChannelTypeStreamedMediaClass *)g_iface; #define IMPLEMENT(x) tp_svc_channel_type_streamed_media_implement_##x (\ klass, rakia_media_channel_##x) IMPLEMENT(list_streams); IMPLEMENT(remove_streams); IMPLEMENT(request_stream_direction); IMPLEMENT(request_streams); #undef IMPLEMENT } static void media_signalling_iface_init(gpointer g_iface, gpointer iface_data) { TpSvcChannelInterfaceMediaSignallingClass *klass = (TpSvcChannelInterfaceMediaSignallingClass *)g_iface; #define IMPLEMENT(x) tp_svc_channel_interface_media_signalling_implement_##x (\ klass, rakia_media_channel_##x) IMPLEMENT(get_session_handlers); #undef IMPLEMENT } static void dtmf_iface_init (gpointer g_iface, gpointer iface_data) { TpSvcChannelInterfaceDTMFClass *klass = (TpSvcChannelInterfaceDTMFClass *)g_iface; #define IMPLEMENT(x) tp_svc_channel_interface_dtmf_implement_##x (\ klass, rakia_media_channel_##x) IMPLEMENT(start_tone); IMPLEMENT(stop_tone); IMPLEMENT(multiple_tones); #undef IMPLEMENT } static void call_state_iface_init (gpointer g_iface, gpointer iface_data) { TpSvcChannelInterfaceCallStateClass *klass = g_iface; #define IMPLEMENT(x) tp_svc_channel_interface_call_state_implement_##x (\ klass, rakia_media_channel_##x) IMPLEMENT (get_call_states); #undef IMPLEMENT } static void hold_iface_init (gpointer g_iface, gpointer iface_data) { TpSvcChannelInterfaceHoldClass *klass = g_iface; #define IMPLEMENT(x) tp_svc_channel_interface_hold_implement_##x (\ klass, rakia_media_channel_##x) IMPLEMENT (get_hold_state); IMPLEMENT (request_hold); #undef IMPLEMENT }