From 29d1abccac205189d4ef1d5692493774b7af4bea Mon Sep 17 00:00:00 2001 From: fiaxh Date: Sun, 6 Feb 2022 23:48:58 +0100 Subject: [PATCH] Support direct jingle call invites with call invite messages --- libdino/src/service/call_peer_state.vala | 14 +- libdino/src/service/call_state.vala | 76 ++++--- libdino/src/service/calls.vala | 208 ++++++++++++------ .../call_window/call_window_controller.vala | 10 +- .../module/xep/0353_call_invite_message.vala | 25 ++- 5 files changed, 214 insertions(+), 119 deletions(-) diff --git a/libdino/src/service/call_peer_state.vala b/libdino/src/service/call_peer_state.vala index 902a0792..52b3e6ef 100644 --- a/libdino/src/service/call_peer_state.vala +++ b/libdino/src/service/call_peer_state.vala @@ -12,6 +12,7 @@ public class Dino.PeerState : Object { public signal void encryption_updated(Xep.Jingle.ContentEncryption? audio_encryption, Xep.Jingle.ContentEncryption? video_encryption, bool same); public StreamInteractor stream_interactor; + CallState call_state; public Calls calls; public Call call; public Jid jid; @@ -30,7 +31,6 @@ public class Dino.PeerState : Object { public HashMap? audio_encryptions = null; public bool first_peer = false; - public bool accepted_jmi = false; public bool waiting_for_inbound_muji_connection = false; public Xep.Muji.GroupCall? group_call { get; set; } @@ -38,9 +38,10 @@ public class Dino.PeerState : Object { public bool we_should_send_audio { get; set; default=false; } public bool we_should_send_video { get; set; default=false; } - public PeerState(Jid jid, Call call, StreamInteractor stream_interactor) { + public PeerState(Jid jid, Call call, CallState call_state, StreamInteractor stream_interactor) { this.jid = jid; this.call = call; + this.call_state = call_state; this.stream_interactor = stream_interactor; this.calls = stream_interactor.get_module(Calls.IDENTITY); @@ -82,9 +83,6 @@ public class Dino.PeerState : Object { if (do_jmi) { XmppStream? stream = stream_interactor.get_stream(call.account); - calls.current_jmi_request_call[call.account] = calls.call_states[call]; - calls.current_jmi_request_peer[call.account] = this; - var descriptions = new ArrayList(); descriptions.add(new StanzaNode.build("description", Xep.JingleRtp.NS_URI).add_self_xmlns().put_attribute("media", "audio")); if (we_should_send_video) { @@ -92,6 +90,7 @@ public class Dino.PeerState : Object { } stream.get_module(Xmpp.Xep.JingleMessageInitiation.Module.IDENTITY).send_session_propose_to_peer(stream, jid, sid, descriptions); +// call_state.cim_invite_id = stream.get_module(Xmpp.Xep.CallInvites.Module.IDENTITY).send_jingle_propose(stream, jid, sid, we_should_send_video); } else if (jid_for_direct != null) { yield call_resource(jid_for_direct); } @@ -117,11 +116,6 @@ public class Dino.PeerState : Object { XmppStream stream = stream_interactor.get_stream(call.account); if (stream == null) return; - accepted_jmi = true; - - calls.current_jmi_request_call[call.account] = calls.call_states[call]; - calls.current_jmi_request_peer[call.account] = this; - stream.get_module(Xep.JingleMessageInitiation.Module.IDENTITY).send_session_accept_to_self(stream, sid); stream.get_module(Xep.JingleMessageInitiation.Module.IDENTITY).send_session_proceed_to_peer(stream, jid, sid); } diff --git a/libdino/src/service/call_state.vala b/libdino/src/service/call_state.vala index f49a77da..14821ab1 100644 --- a/libdino/src/service/call_state.vala +++ b/libdino/src/service/call_state.vala @@ -11,18 +11,20 @@ public class Dino.CallState : Object { public StreamInteractor stream_interactor; public Plugins.VideoCallPlugin call_plugin = Dino.Application.get_default().plugin_registry.video_call_plugin; public Call call; - public Xep.Muji.GroupCall? group_call { get; set; } public Jid? parent_muc { get; set; } public Jid? invited_to_group_call = null; - public Jid? group_call_inviter = null; - public string? invite_id = null; public bool accepted { get; private set; default=false; } + public bool use_cim = false; + public string? cim_invite_id = null; + public Jid? cim_counterpart = null; + public string cim_message_type { get; set; default=Xmpp.MessageStanza.TYPE_CHAT; } + + public Xep.Muji.GroupCall? group_call { get; set; } public bool we_should_send_audio { get; set; default=false; } public bool we_should_send_video { get; set; default=false; } - public HashMap peers = new HashMap(Jid.hash_func, Jid.equals_func); - private string message_type = Xmpp.MessageStanza.TYPE_CHAT; + public HashMap peers = new HashMap(Jid.hash_func, Jid.equals_func); public CallState(Call call, StreamInteractor stream_interactor) { this.call = call; @@ -44,7 +46,6 @@ public class Dino.CallState : Object { internal async void initiate_groupchat_call(Jid muc) { parent_muc = muc; - message_type = Xmpp.MessageStanza.TYPE_GROUPCHAT; if (this.group_call == null) yield convert_into_group_call(); if (this.group_call == null) return; @@ -62,11 +63,11 @@ public class Dino.CallState : Object { yield stream.get_module(Xep.Muc.Module.IDENTITY).change_affiliation(stream, group_call.muc_jid, real_jid.bare_jid, null, "owner"); } - stream.get_module(Xep.CallInvites.Module.IDENTITY).send_invite(stream, muc, group_call.muc_jid, we_should_send_video, message_type); + stream.get_module(Xep.CallInvites.Module.IDENTITY).send_muji_propose(stream, muc, group_call.muc_jid, we_should_send_video, cim_message_type); } internal PeerState set_first_peer(Jid peer) { - var peer_state = new PeerState(peer, call, stream_interactor); + var peer_state = new PeerState(peer, call, this, stream_interactor); peer_state.first_peer = true; add_peer(peer_state); return peer_state; @@ -82,25 +83,28 @@ public class Dino.CallState : Object { accepted = true; call.state = Call.State.ESTABLISHING; - if (invited_to_group_call != null) { + if (use_cim) { XmppStream stream = stream_interactor.get_stream(call.account); if (stream == null) return; - stream.get_module(Xep.CallInvites.Module.IDENTITY).send_accept(stream, group_call_inviter, invite_id, message_type); - join_group_call.begin(invited_to_group_call); + stream.get_module(Xep.CallInvites.Module.IDENTITY).send_accept(stream, cim_counterpart, cim_invite_id, cim_message_type); } else { foreach (PeerState peer in peers.values) { peer.accept(); } } + + if (invited_to_group_call != null) { + join_group_call.begin(invited_to_group_call); + } } public void reject() { call.state = Call.State.DECLINED; - if (invited_to_group_call != null) { + if (use_cim) { XmppStream stream = stream_interactor.get_stream(call.account); if (stream == null) return; - stream.get_module(Xep.CallInvites.Module.IDENTITY).send_reject(stream, invited_to_group_call, invite_id, message_type); + stream.get_module(Xep.CallInvites.Module.IDENTITY).send_reject(stream, cim_counterpart, cim_invite_id, cim_message_type); } var peers_cpy = new ArrayList(); peers_cpy.add_all(peers.values); @@ -130,10 +134,10 @@ public class Dino.CallState : Object { foreach (PeerState peer in peers_cpy) { peer.end(Xep.Jingle.ReasonElement.CANCEL, reason_text); } - if (parent_muc != null && group_call != null) { + if (call.direction == Call.DIRECTION_OUTGOING && use_cim) { XmppStream stream = stream_interactor.get_stream(call.account); if (stream == null) return; - stream.get_module(Xep.CallInvites.Module.IDENTITY).send_retract(stream, parent_muc, invite_id, message_type); + stream.get_module(Xep.CallInvites.Module.IDENTITY).send_retract(stream, cim_counterpart, cim_invite_id, cim_message_type); } call.state = Call.State.MISSED; } else { @@ -172,7 +176,7 @@ public class Dino.CallState : Object { debug("[%s] Inviting to muji call %s", call.account.bare_jid.to_string(), invitee.to_string()); yield stream.get_module(Xep.Muc.Module.IDENTITY).change_affiliation(stream, group_call.muc_jid, invitee, null, "owner"); - stream.get_module(Xep.CallInvites.Module.IDENTITY).send_invite(stream, invitee, group_call.muc_jid, we_should_send_video, "chat"); + stream.get_module(Xep.CallInvites.Module.IDENTITY).send_muji_propose(stream, invitee, group_call.muc_jid, we_should_send_video, "chat"); // If the peer hasn't accepted within a minute, retract the invite // TODO this should be unset when we retract the invite. otherwise a second invite attempt might break due to this @@ -189,7 +193,7 @@ public class Dino.CallState : Object { if (!contains_peer) { debug("[%s] Retracting invite to %s from %s", call.account.bare_jid.to_string(), group_call.muc_jid.to_string(), invitee.to_string()); // stream.get_module(Xep.CallInvites.Module.IDENTITY).send_retract(stream, invitee, invite_id); - stream.get_module(Xep.Muc.Module.IDENTITY).change_affiliation.begin(stream, group_call.muc_jid, invitee, null, "none"); +// stream.get_module(Xep.Muc.Module.IDENTITY).change_affiliation.begin(stream, group_call.muc_jid, invitee, null, "none"); } return false; }); @@ -262,18 +266,7 @@ public class Dino.CallState : Object { peer_state.session_terminated.connect((we_terminated, reason_name, reason_text) => { debug("[%s] Peer left %s: %s %s (%i peers remaining)", call.account.bare_jid.to_string(), reason_text ?? "", reason_name ?? "", peer_state.jid.to_string(), peers.size); - peers.unset(peer_state.jid); - - if (peers.is_empty) { - if (group_call != null) { - group_call.leave(stream_interactor.get_stream(call.account)); - on_call_terminated(peer_state.jid, we_terminated, null, "All participants have left the group call"); - } else { - on_call_terminated(peer_state.jid, we_terminated, reason_name, reason_text); - } - } else { - peer_left(peer_state.jid, peer_state, reason_name, reason_text); - } + handle_peer_left(peer_state, we_terminated, reason_name, reason_text); }); } @@ -358,7 +351,7 @@ public class Dino.CallState : Object { } // else: Connection to first peer already active } else { - var peer_state = new PeerState(jid, call, stream_interactor); + var peer_state = new PeerState(jid, call, this, stream_interactor); peer_state.waiting_for_inbound_muji_connection = true; debug("[%s] Waiting for call from %s", call.account.bare_jid.to_string(), jid.to_string()); add_peer(peer_state); @@ -368,10 +361,9 @@ public class Dino.CallState : Object { this.group_call.peer_left.connect((jid) => { debug("[%s] Group call peer left: %s", call.account.bare_jid.to_string(), jid.to_string()); PeerState? peer_state = peers[jid]; - if (!peers.has_key(jid)) return; - peer_left(jid, peer_state, Xep.Jingle.ReasonElement.CANCEL, "Peer left the MUJI MUC"); + if (peer_state == null) return; peer_state.end(Xep.Jingle.ReasonElement.CANCEL, "Peer left the MUJI MUC"); - peers.unset(jid); + handle_peer_left(peer_state, false, Xep.Jingle.ReasonElement.CANCEL, "Peer left the MUJI MUC"); }); if (group_call.peers_to_connect_to.size > 3) { @@ -386,11 +378,27 @@ public class Dino.CallState : Object { debug("[%s] Calling %s because they were in the MUC already", call.account.bare_jid.to_string(), peer_jid.to_string()); - PeerState peer_state = new PeerState(peer_jid, call, stream_interactor); + PeerState peer_state = new PeerState(peer_jid, call, this, stream_interactor); add_peer(peer_state); peer_state.call_resource.begin(peer_jid); } debug("[%s] Finished joining MUJI muc %s", call.account.bare_jid.to_string(), muc_jid.to_string()); } + + private void handle_peer_left(PeerState peer_state, bool we_terminated, string? reason_name, string? reason_text) { + if (!peers.has_key(peer_state.jid)) return; + peers.unset(peer_state.jid); + + if (peers.is_empty) { + if (group_call != null) { + group_call.leave(stream_interactor.get_stream(call.account)); + on_call_terminated(peer_state.jid, we_terminated, null, "All participants have left the call"); + } else { + on_call_terminated(peer_state.jid, we_terminated, reason_name, reason_text); + } + } else { + peer_left(peer_state.jid, peer_state, reason_name, reason_text); + } + } } \ No newline at end of file diff --git a/libdino/src/service/calls.vala b/libdino/src/service/calls.vala index 7b6a2628..c97b296c 100644 --- a/libdino/src/service/calls.vala +++ b/libdino/src/service/calls.vala @@ -19,8 +19,8 @@ namespace Dino { private StreamInteractor stream_interactor; private Database db; - public HashMap current_jmi_request_call = new HashMap(Account.hash_func, Account.equals_func); - public HashMap current_jmi_request_peer = new HashMap(Account.hash_func, Account.equals_func); +// public HashMap current_jmi_request_call = new HashMap(Account.hash_func, Account.equals_func); + public HashMap jmi_request_peer = new HashMap(Call.hash_func, Call.equals_func); public HashMap call_states = new HashMap(Call.hash_func, Call.equals_func); public static void start(StreamInteractor stream_interactor, Database db) { @@ -55,6 +55,7 @@ namespace Dino { if (conversation.type_ == Conversation.Type.CHAT) { call.add_peer(conversation.counterpart); PeerState peer_state = call_state.set_first_peer(conversation.counterpart); + jmi_request_peer[call] = peer_state; yield peer_state.initiate_call(conversation.counterpart); } else { call_state.initiate_groupchat_call.begin(conversation.counterpart); @@ -167,7 +168,7 @@ namespace Dino { peer_state.accept(); } else { debug(@"[%s] Incoming call, but didn't see peer in MUC yet", account.bare_jid.to_string()); - PeerState peer_state = new PeerState(session.peer_full_jid, call_state.call, stream_interactor); + PeerState peer_state = new PeerState(session.peer_full_jid, call_state.call, call_state, stream_interactor); peer_state.set_session(session); call_state.add_peer(peer_state); } @@ -180,15 +181,22 @@ namespace Dino { debug(@"[%s] Incoming call from %s", account.bare_jid.to_string(), session.peer_full_jid.to_string()); // Check if we already accepted this call via Jingle Message Initiation => accept - if (current_jmi_request_call.has_key(account) && - current_jmi_request_peer[account].sid == session.sid && - current_jmi_request_peer[account].we_should_send_video == counterpart_wants_video && - current_jmi_request_peer[account].accepted_jmi) { - current_jmi_request_peer[account].set_session(session); - current_jmi_request_call[account].accept(); - - current_jmi_request_peer.unset(account); - current_jmi_request_call.unset(account); + Call? call = null; + foreach (PeerState peer_state in jmi_request_peer.values) { + CallState call_state = call_states[peer_state.call]; + if (peer_state.sid == session.sid && + call_state.call.account.equals(account) && + peer_state.jid.equals_bare(session.peer_full_jid) && + call_state.we_should_send_video == counterpart_wants_video && + call_state.accepted) { + call = peer_state.call; + break; + } + } + if (call != null) { + jmi_request_peer[call].set_session(session); + jmi_request_peer[call].accept(); + jmi_request_peer.unset(call); return; } @@ -218,9 +226,7 @@ namespace Dino { call.encryption = Encryption.UNKNOWN; Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(call.counterpart.bare_jid, account, Conversation.Type.CHAT); - stream_interactor.get_module(CallStore.IDENTITY).add_call(call, conversation); - conversation.last_active = call.time; var call_state = new CallState(call, stream_interactor); @@ -238,40 +244,58 @@ namespace Dino { return peer_state; } - private CallState? get_call_state_by_invite_id(Account account, Jid peer_jid, string invite_id) { + private CallState? get_call_state_by_invite_id(Account account, string invite_id, Jid jid1, Jid jid2) { + Jid relevant_jid = jid1.equals_bare(account.bare_jid) ? jid2 : jid1; + foreach (CallState call_state in call_states.values) { if (!call_state.call.account.equals(account)) continue; - if (call_state.group_call != null && call_state.invite_id == invite_id) { + if (call_state.cim_invite_id == invite_id) { foreach (Jid jid in call_state.peers.keys) { - if (jid.equals(peer_jid)) { + if (jid.equals_bare(relevant_jid)) { return call_state; } } } - if (call_state.invited_to_group_call != null && call_state.invited_to_group_call.equals(peer_jid)) return call_state; + if (call_state.invited_to_group_call != null && call_state.invited_to_group_call.equals(relevant_jid)) return call_state; } return null; } - private async void on_muji_call_received(Account account, Jid inviter_jid, Jid muc_jid, string invite_id, bool video, string message_type) { + private PeerState? get_peer_by_sid(Account account, string sid, Jid jid1, Jid jid2) { + Jid relevant_jid = jid1.equals_bare(account.bare_jid) ? jid2 : jid1; + + foreach (CallState call_state in call_states.values) { + if (!call_state.call.account.equals(account)) continue; + + foreach (PeerState peer_state in call_state.peers.values) { + if (peer_state.sid != sid) continue; + if (peer_state.jid.equals_bare(relevant_jid)) { + return peer_state; + } + } + } + return null; + } + + private CallState? create_recv_muji_call(Account account, Jid inviter_jid, Jid muc_jid, string invite_id, string message_type) { debug("[%s] Muji call received from %s for MUC %s, type %s", account.bare_jid.to_string(), inviter_jid.to_string(), muc_jid.to_string(), message_type); foreach (Call call in call_states.keys) { - if (!call.account.equals(account)) return; + if (!call.account.equals(account)) return null; CallState call_state = call_states[call]; // If this is a MUC reflection of our own invite, store the sid assigned by the MUC if (call_state.parent_muc != null && call_state.parent_muc.equals_bare(inviter_jid)) { - call_state.invite_id = invite_id; - return; + call_state.cim_invite_id = invite_id; + return null; } if (call.counterparts.contains(inviter_jid) && call_state.accepted) { // A call is converted into a group call. - yield call_state.join_group_call(muc_jid); - return; + call_state.join_group_call.begin(muc_jid); + return null; } } @@ -284,27 +308,23 @@ namespace Dino { call.encryption = Encryption.UNKNOWN; call.state = Call.State.RINGING; + // TODO create conv Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(inviter_jid.bare_jid, account); stream_interactor.get_module(CallStore.IDENTITY).add_call(call, conversation); conversation.last_active = call.time; CallState call_state = new CallState(call, stream_interactor); connect_call_state_signals(call_state); - call_state.we_should_send_audio = true; - call_state.we_should_send_video = video; call_state.invited_to_group_call = muc_jid; - call_state.group_call_inviter = inviter_jid; - call_state.invite_id = invite_id; + call_state.parent_muc = inviter_jid.bare_jid; debug("[%s] on_muji_call_received accepting", account.bare_jid.to_string()); - call_incoming(call_state.call, call_state, conversation, video); + + return call_state; } private void remove_call_from_datastructures(Call call) { - if (current_jmi_request_call.has_key(call.account) && current_jmi_request_call[call.account].call.equals(call)) { - current_jmi_request_call.unset(call.account); - current_jmi_request_peer.unset(call.account); - } + jmi_request_peer.unset(call); call_states.unset(call); } @@ -339,33 +359,36 @@ namespace Dino { PeerState peer_state = create_received_call(account, from, to, video_requested); peer_state.sid = sid; - peer_state.we_should_send_audio = true; - peer_state.we_should_send_video = video_requested; - current_jmi_request_peer[account] = peer_state; - current_jmi_request_call[account] = call_states[peer_state.call]; + CallState call_state = call_states[peer_state.call]; + call_state.we_should_send_audio = true; + call_state.we_should_send_video = video_requested; + + jmi_request_peer[call_state.call] = peer_state; }); mi_module.session_accepted.connect((from, to, sid) => { - if (!current_jmi_request_peer.has_key(account) || current_jmi_request_peer[account].sid != sid) return; + PeerState? peer_state = get_peer_by_sid(account, sid, from, to); + if (peer_state == null) return; + Call call = peer_state.call; if (from.equals_bare(account.bare_jid)) { // Carboned message from our account // Ignore carbon from ourselves if (from.equals(account.full_jid)) return; - Call call = current_jmi_request_peer[account].call; call.ourpart = from; call.state = Call.State.OTHER_DEVICE; remove_call_from_datastructures(call); - } else if (from.equals_bare(current_jmi_request_peer[account].jid) && to.equals(account.full_jid)) { // Message from our peer + } else if (from.equals_bare(peer_state.jid) && to.equals(account.full_jid)) { // Message from our peer // We proposed the call // We know the full jid of our peer now - current_jmi_request_call[account].rename_peer(current_jmi_request_peer[account].jid, from); - current_jmi_request_peer[account].call_resource.begin(from); + call_states[call].rename_peer(jmi_request_peer[call].jid, from); + jmi_request_peer[call].call_resource.begin(from); } }); mi_module.session_rejected.connect((from, to, sid) => { - if (!current_jmi_request_peer.has_key(account) || current_jmi_request_peer[account].sid != sid) return; - Call call = current_jmi_request_peer[account].call; + PeerState? peer_state = get_peer_by_sid(account, sid, from, to); + if (peer_state == null) return; + Call call = peer_state.call; bool outgoing_reject = call.direction == Call.DIRECTION_OUTGOING && from.equals_bare(call.counterparts[0]); bool incoming_reject = call.direction == Call.DIRECTION_INCOMING && from.equals_bare(account.bare_jid); @@ -379,8 +402,9 @@ namespace Dino { remove_call_from_datastructures(call); }); mi_module.session_retracted.connect((from, to, sid) => { - if (!current_jmi_request_peer.has_key(account) || current_jmi_request_peer[account].sid != sid) return; - Call call = current_jmi_request_peer[account].call; + PeerState? peer_state = get_peer_by_sid(account, sid, from, to); + if (peer_state == null) return; + Call call = peer_state.call; bool outgoing_retract = call.direction == Call.DIRECTION_OUTGOING && from.equals_bare(account.bare_jid); bool incoming_retract = call.direction == Call.DIRECTION_INCOMING && from.equals_bare(call.counterpart); @@ -392,42 +416,92 @@ namespace Dino { }); Xep.CallInvites.Module call_invites_module = stream_interactor.module_manager.get_module(account, Xep.CallInvites.Module.IDENTITY); - call_invites_module.call_proposed.connect((from_jid, to_jid, video, join_methods, message_stanza) => { + call_invites_module.call_proposed.connect((from_jid, to_jid, video_requested, join_methods, message_stanza) => { if (from_jid.equals_bare(account.bare_jid)) return; + + string? invite_id = null; + if (message_stanza.type_ == Xmpp.MessageStanza.TYPE_GROUPCHAT) { + invite_id = Xep.UniqueStableStanzaIDs.get_stanza_id(message_stanza, from_jid.bare_jid); + } else { + invite_id = message_stanza.id; + } + if (invite_id == null) { + warning("Got call invite without ID"); + return; + } + + CallState? call_state = null; + foreach (StanzaNode join_method_node in join_methods) { - if (join_method_node.ns_uri == Xep.Muji.NS_URI) { + if (join_method_node.name == "muji" && join_method_node.ns_uri == Xep.Muji.NS_URI) { + + // This is a MUJI invite string? room_jid_str = join_method_node.get_attribute("room"); if (room_jid_str == null) return; Jid room_jid = new Jid(room_jid_str); - string? invite_id = null; - if (message_stanza.type_ == Xmpp.MessageStanza.TYPE_GROUPCHAT) { - invite_id = Xep.UniqueStableStanzaIDs.get_stanza_id(message_stanza, from_jid.bare_jid); - } else { - invite_id = message_stanza.id; - } - if (invite_id == null) { - warning("Got call invite without ID"); - return; - } - on_muji_call_received.begin(account, from_jid, room_jid, id, video, message_stanza.type_); + call_state = create_recv_muji_call(account, from_jid, room_jid, invite_id, message_stanza.type_); + break; + + } else if (join_method_node.name == "jingle" && join_method_node.ns_uri == Xep.CallInvites.NS_URI) { + + // This is an invite for a direct Jingle session + if (message_stanza.type_ != Xmpp.MessageStanza.TYPE_CHAT) return; + + string? sid = join_method_node.get_attribute("sid"); + if (sid == null) return; + + PeerState peer_state = create_received_call(account, from_jid, to_jid, video_requested); + peer_state.sid = sid; + + call_state = call_states[peer_state.call]; + + jmi_request_peer[call_state.call] = peer_state; + break; } } - }); - call_invites_module.call_accepted.connect((from_jid, invite_id, message_type) => { - if (!from_jid.equals_bare(account.bare_jid)) return; - // We accepted the call from another device - CallState? call_state = get_call_state_by_invite_id(account, from_jid, invite_id); + if (call_state == null) return; - call_state.call.state = Call.State.OTHER_DEVICE; - remove_call_from_datastructures(call_state.call); + call_state.we_should_send_audio = true; + call_state.we_should_send_video = video_requested; + + call_state.use_cim = true; + call_state.cim_invite_id = invite_id; + call_state.cim_counterpart = message_stanza.type_ == MessageStanza.TYPE_GROUPCHAT ? from_jid.bare_jid : from_jid; + call_state.cim_message_type = message_stanza.type_; + + Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).approx_conversation_for_stanza(from_jid, to_jid, account, message_stanza.type_); + conversation.last_active = call_state.call.time; + if (conversation == null) return; + + call_incoming(call_state.call, call_state, conversation, video_requested); + }); + call_invites_module.call_accepted.connect((from_jid, to_jid, invite_id, message_type) => { + CallState? call_state = get_call_state_by_invite_id(account, invite_id, from_jid, to_jid); + if (call_state == null) return; + Call call = call_state.call; + + if (from_jid.equals_bare(account.bare_jid)) { // Carboned message from our account + // Ignore carbon from ourselves + if (from_jid.equals(account.full_jid)) return; + + // We accepted the call from another device + call.ourpart = from_jid; + call.state = Call.State.OTHER_DEVICE; + remove_call_from_datastructures(call); + } else if (to_jid.equals(account.full_jid)) { // Message from our peer + // We proposed the call + // We know the full jid of our peer now + call_states[call].rename_peer(jmi_request_peer[call].jid, from_jid); + jmi_request_peer[call].call_resource.begin(from_jid); + } }); call_invites_module.call_retracted.connect((from_jid, to_jid, invite_id, message_type) => { if (from_jid.equals_bare(account.bare_jid)) return; // The call was retracted by the counterpart - CallState? call_state = get_call_state_by_invite_id(account, from_jid, invite_id); + CallState? call_state = get_call_state_by_invite_id(account, invite_id, from_jid, to_jid); if (call_state == null) return; if (call_state.call.state != Call.State.RINGING) { diff --git a/main/src/ui/call_window/call_window_controller.vala b/main/src/ui/call_window/call_window_controller.vala index ebf8774a..94d6e890 100644 --- a/main/src/ui/call_window/call_window_controller.vala +++ b/main/src/ui/call_window/call_window_controller.vala @@ -271,9 +271,12 @@ public class Dino.Ui.CallWindowController : Object { private void update_audio_device_choices() { if (call_plugin.get_devices("audio", true).size == 0 || call_plugin.get_devices("audio", false).size == 0) { call_window.bottom_bar.show_audio_device_error(); - } else if (call_plugin.get_devices("audio", true).size == 1 && call_plugin.get_devices("audio", false).size == 1) { + } /*else if (call_plugin.get_devices("audio", true).size == 1 && call_plugin.get_devices("audio", false).size == 1) { call_window.bottom_bar.show_audio_device_choices(false); return; + }*/ else { + call_window.bottom_bar.show_video_device_choices(false); + return; } AudioSettingsPopover? audio_settings_popover = call_window.bottom_bar.show_audio_device_choices(true); @@ -304,7 +307,10 @@ public class Dino.Ui.CallWindowController : Object { if (device_count == 0) { call_window.bottom_bar.show_video_device_error(); - } else if (device_count == 1 || call_state.get_video_device() == null) { + } /*else if (device_count == 1 || call_state.get_video_device() == null) { + call_window.bottom_bar.show_video_device_choices(false); + return; + }*/ else { call_window.bottom_bar.show_video_device_choices(false); return; } diff --git a/xmpp-vala/src/module/xep/0353_call_invite_message.vala b/xmpp-vala/src/module/xep/0353_call_invite_message.vala index 806db5e7..844e9a7f 100644 --- a/xmpp-vala/src/module/xep/0353_call_invite_message.vala +++ b/xmpp-vala/src/module/xep/0353_call_invite_message.vala @@ -1,25 +1,38 @@ using Gee; namespace Xmpp.Xep.CallInvites { - public const string NS_URI = "urn:xmpp:call-invites:0"; + public const string NS_URI = "urn:xmpp:call-message:1"; public class Module : XmppStreamModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "call_invites"); public signal void call_proposed(Jid from, Jid to, bool video, Gee.List join_methods, MessageStanza message); public signal void call_retracted(Jid from, Jid to, string invite_id, string message_type); - public signal void call_accepted(Jid from, string invite_id, string message_type); + public signal void call_accepted(Jid from, Jid to, string invite_id, string message_type); public signal void call_rejected(Jid from, Jid to, string invite_id, string message_type); - public void send_invite(XmppStream stream, Jid invitee, Jid muc_jid, bool video, string message_type) { - StanzaNode muji_node = new StanzaNode.build("muji", Muji.NS_URI).add_self_xmlns().put_attribute("room", muc_jid.to_string()); + public string send_jingle_propose(XmppStream stream, Jid invitee, string sid, bool video) { + StanzaNode jingle_node = new StanzaNode.build("jingle", CallInvites.NS_URI) + .put_attribute("sid", sid); + return send_propose(stream, invitee, jingle_node, video, false, MessageStanza.TYPE_CHAT); + } + + public void send_muji_propose(XmppStream stream, Jid invitee, Jid muc_jid, bool video, string message_type) { + StanzaNode muji_node = new StanzaNode.build("muji", Muji.NS_URI).add_self_xmlns() + .put_attribute("room", muc_jid.to_string()); + send_propose(stream, invitee, muji_node, video, true, message_type); + } + + private string send_propose(XmppStream stream, Jid invitee, StanzaNode inner_node, bool video, bool multiparty, string message_type) { StanzaNode invite_node = new StanzaNode.build("propose", NS_URI).add_self_xmlns() .put_attribute("video", video.to_string()) - .put_node(muji_node); + .put_attribute("multi", multiparty.to_string()) + .put_node(inner_node); MessageStanza invite_message = new MessageStanza() { to=invitee, type_=message_type }; MessageProcessingHints.set_message_hint(invite_message, MessageProcessingHints.HINT_STORE); invite_message.stanza.put_node(invite_node); stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, invite_message); + return invite_message.id; } public void send_retract(XmppStream stream, Jid to, string invite_id, string message_type) { @@ -68,7 +81,7 @@ namespace Xmpp.Xep.CallInvites { switch (relevant_node.name) { case "accept": - call_accepted(message.from, invite_id, message.type_); + call_accepted(message.from, message.to, invite_id, message.type_); break; case "retract": call_retracted(message.from, message.to, invite_id, message.type_);