Support direct jingle call invites with call invite messages

This commit is contained in:
fiaxh 2022-02-06 23:48:58 +01:00
parent 071d925e37
commit 29d1abccac
5 changed files with 214 additions and 119 deletions

View file

@ -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 signal void encryption_updated(Xep.Jingle.ContentEncryption? audio_encryption, Xep.Jingle.ContentEncryption? video_encryption, bool same);
public StreamInteractor stream_interactor; public StreamInteractor stream_interactor;
CallState call_state;
public Calls calls; public Calls calls;
public Call call; public Call call;
public Jid jid; public Jid jid;
@ -30,7 +31,6 @@ public class Dino.PeerState : Object {
public HashMap<string, Xep.Jingle.ContentEncryption>? audio_encryptions = null; public HashMap<string, Xep.Jingle.ContentEncryption>? audio_encryptions = null;
public bool first_peer = false; public bool first_peer = false;
public bool accepted_jmi = false;
public bool waiting_for_inbound_muji_connection = false; public bool waiting_for_inbound_muji_connection = false;
public Xep.Muji.GroupCall? group_call { get; set; } 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_audio { get; set; default=false; }
public bool we_should_send_video { 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.jid = jid;
this.call = call; this.call = call;
this.call_state = call_state;
this.stream_interactor = stream_interactor; this.stream_interactor = stream_interactor;
this.calls = stream_interactor.get_module(Calls.IDENTITY); this.calls = stream_interactor.get_module(Calls.IDENTITY);
@ -82,9 +83,6 @@ public class Dino.PeerState : Object {
if (do_jmi) { if (do_jmi) {
XmppStream? stream = stream_interactor.get_stream(call.account); 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<StanzaNode>(); var descriptions = new ArrayList<StanzaNode>();
descriptions.add(new StanzaNode.build("description", Xep.JingleRtp.NS_URI).add_self_xmlns().put_attribute("media", "audio")); descriptions.add(new StanzaNode.build("description", Xep.JingleRtp.NS_URI).add_self_xmlns().put_attribute("media", "audio"));
if (we_should_send_video) { 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); 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) { } else if (jid_for_direct != null) {
yield call_resource(jid_for_direct); yield call_resource(jid_for_direct);
} }
@ -117,11 +116,6 @@ public class Dino.PeerState : Object {
XmppStream stream = stream_interactor.get_stream(call.account); XmppStream stream = stream_interactor.get_stream(call.account);
if (stream == null) return; 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_accept_to_self(stream, sid);
stream.get_module(Xep.JingleMessageInitiation.Module.IDENTITY).send_session_proceed_to_peer(stream, jid, sid); stream.get_module(Xep.JingleMessageInitiation.Module.IDENTITY).send_session_proceed_to_peer(stream, jid, sid);
} }

View file

@ -11,18 +11,20 @@ public class Dino.CallState : Object {
public StreamInteractor stream_interactor; public StreamInteractor stream_interactor;
public Plugins.VideoCallPlugin call_plugin = Dino.Application.get_default().plugin_registry.video_call_plugin; public Plugins.VideoCallPlugin call_plugin = Dino.Application.get_default().plugin_registry.video_call_plugin;
public Call call; public Call call;
public Xep.Muji.GroupCall? group_call { get; set; }
public Jid? parent_muc { get; set; } public Jid? parent_muc { get; set; }
public Jid? invited_to_group_call = null; 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 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_audio { get; set; default=false; }
public bool we_should_send_video { get; set; default=false; } public bool we_should_send_video { get; set; default=false; }
public HashMap<Jid, PeerState> peers = new HashMap<Jid, PeerState>(Jid.hash_func, Jid.equals_func);
private string message_type = Xmpp.MessageStanza.TYPE_CHAT; public HashMap<Jid, PeerState> peers = new HashMap<Jid, PeerState>(Jid.hash_func, Jid.equals_func);
public CallState(Call call, StreamInteractor stream_interactor) { public CallState(Call call, StreamInteractor stream_interactor) {
this.call = call; this.call = call;
@ -44,7 +46,6 @@ public class Dino.CallState : Object {
internal async void initiate_groupchat_call(Jid muc) { internal async void initiate_groupchat_call(Jid muc) {
parent_muc = 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) yield convert_into_group_call();
if (this.group_call == null) return; 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"); 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) { 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; peer_state.first_peer = true;
add_peer(peer_state); add_peer(peer_state);
return peer_state; return peer_state;
@ -82,25 +83,28 @@ public class Dino.CallState : Object {
accepted = true; accepted = true;
call.state = Call.State.ESTABLISHING; call.state = Call.State.ESTABLISHING;
if (invited_to_group_call != null) { if (use_cim) {
XmppStream stream = stream_interactor.get_stream(call.account); XmppStream stream = stream_interactor.get_stream(call.account);
if (stream == null) return; if (stream == null) return;
stream.get_module(Xep.CallInvites.Module.IDENTITY).send_accept(stream, group_call_inviter, invite_id, message_type); stream.get_module(Xep.CallInvites.Module.IDENTITY).send_accept(stream, cim_counterpart, cim_invite_id, cim_message_type);
join_group_call.begin(invited_to_group_call);
} else { } else {
foreach (PeerState peer in peers.values) { foreach (PeerState peer in peers.values) {
peer.accept(); peer.accept();
} }
} }
if (invited_to_group_call != null) {
join_group_call.begin(invited_to_group_call);
}
} }
public void reject() { public void reject() {
call.state = Call.State.DECLINED; call.state = Call.State.DECLINED;
if (invited_to_group_call != null) { if (use_cim) {
XmppStream stream = stream_interactor.get_stream(call.account); XmppStream stream = stream_interactor.get_stream(call.account);
if (stream == null) return; 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<PeerState>(); var peers_cpy = new ArrayList<PeerState>();
peers_cpy.add_all(peers.values); peers_cpy.add_all(peers.values);
@ -130,10 +134,10 @@ public class Dino.CallState : Object {
foreach (PeerState peer in peers_cpy) { foreach (PeerState peer in peers_cpy) {
peer.end(Xep.Jingle.ReasonElement.CANCEL, reason_text); 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); XmppStream stream = stream_interactor.get_stream(call.account);
if (stream == null) return; 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; call.state = Call.State.MISSED;
} else { } 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()); 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"); 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 // 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 // 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) { 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()); 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.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; return false;
}); });
@ -262,18 +266,7 @@ public class Dino.CallState : Object {
peer_state.session_terminated.connect((we_terminated, reason_name, reason_text) => { 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); 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); handle_peer_left(peer_state, we_terminated, reason_name, reason_text);
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);
}
}); });
} }
@ -358,7 +351,7 @@ public class Dino.CallState : Object {
} }
// else: Connection to first peer already active // else: Connection to first peer already active
} else { } 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; peer_state.waiting_for_inbound_muji_connection = true;
debug("[%s] Waiting for call from %s", call.account.bare_jid.to_string(), jid.to_string()); debug("[%s] Waiting for call from %s", call.account.bare_jid.to_string(), jid.to_string());
add_peer(peer_state); add_peer(peer_state);
@ -368,10 +361,9 @@ public class Dino.CallState : Object {
this.group_call.peer_left.connect((jid) => { this.group_call.peer_left.connect((jid) => {
debug("[%s] Group call peer left: %s", call.account.bare_jid.to_string(), jid.to_string()); debug("[%s] Group call peer left: %s", call.account.bare_jid.to_string(), jid.to_string());
PeerState? peer_state = peers[jid]; PeerState? peer_state = peers[jid];
if (!peers.has_key(jid)) return; if (peer_state == null) return;
peer_left(jid, peer_state, Xep.Jingle.ReasonElement.CANCEL, "Peer left the MUJI MUC");
peer_state.end(Xep.Jingle.ReasonElement.CANCEL, "Peer left the MUJI MUC"); 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) { 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()); 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); add_peer(peer_state);
peer_state.call_resource.begin(peer_jid); peer_state.call_resource.begin(peer_jid);
} }
debug("[%s] Finished joining MUJI muc %s", call.account.bare_jid.to_string(), muc_jid.to_string()); 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);
}
}
} }

View file

@ -19,8 +19,8 @@ namespace Dino {
private StreamInteractor stream_interactor; private StreamInteractor stream_interactor;
private Database db; private Database db;
public HashMap<Account, CallState> current_jmi_request_call = new HashMap<Account, CallState>(Account.hash_func, Account.equals_func); // public HashMap<Account, CallState> current_jmi_request_call = new HashMap<Account, CallState>(Account.hash_func, Account.equals_func);
public HashMap<Account, PeerState> current_jmi_request_peer = new HashMap<Account, PeerState>(Account.hash_func, Account.equals_func); public HashMap<Call, PeerState> jmi_request_peer = new HashMap<Call, PeerState>(Call.hash_func, Call.equals_func);
public HashMap<Call, CallState> call_states = new HashMap<Call, CallState>(Call.hash_func, Call.equals_func); public HashMap<Call, CallState> call_states = new HashMap<Call, CallState>(Call.hash_func, Call.equals_func);
public static void start(StreamInteractor stream_interactor, Database db) { public static void start(StreamInteractor stream_interactor, Database db) {
@ -55,6 +55,7 @@ namespace Dino {
if (conversation.type_ == Conversation.Type.CHAT) { if (conversation.type_ == Conversation.Type.CHAT) {
call.add_peer(conversation.counterpart); call.add_peer(conversation.counterpart);
PeerState peer_state = call_state.set_first_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); yield peer_state.initiate_call(conversation.counterpart);
} else { } else {
call_state.initiate_groupchat_call.begin(conversation.counterpart); call_state.initiate_groupchat_call.begin(conversation.counterpart);
@ -167,7 +168,7 @@ namespace Dino {
peer_state.accept(); peer_state.accept();
} else { } else {
debug(@"[%s] Incoming call, but didn't see peer in MUC yet", account.bare_jid.to_string()); 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); peer_state.set_session(session);
call_state.add_peer(peer_state); 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()); 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 // Check if we already accepted this call via Jingle Message Initiation => accept
if (current_jmi_request_call.has_key(account) && Call? call = null;
current_jmi_request_peer[account].sid == session.sid && foreach (PeerState peer_state in jmi_request_peer.values) {
current_jmi_request_peer[account].we_should_send_video == counterpart_wants_video && CallState call_state = call_states[peer_state.call];
current_jmi_request_peer[account].accepted_jmi) { if (peer_state.sid == session.sid &&
current_jmi_request_peer[account].set_session(session); call_state.call.account.equals(account) &&
current_jmi_request_call[account].accept(); peer_state.jid.equals_bare(session.peer_full_jid) &&
call_state.we_should_send_video == counterpart_wants_video &&
current_jmi_request_peer.unset(account); call_state.accepted) {
current_jmi_request_call.unset(account); 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; return;
} }
@ -218,9 +226,7 @@ namespace Dino {
call.encryption = Encryption.UNKNOWN; call.encryption = Encryption.UNKNOWN;
Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(call.counterpart.bare_jid, account, Conversation.Type.CHAT); 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); stream_interactor.get_module(CallStore.IDENTITY).add_call(call, conversation);
conversation.last_active = call.time; conversation.last_active = call.time;
var call_state = new CallState(call, stream_interactor); var call_state = new CallState(call, stream_interactor);
@ -238,40 +244,58 @@ namespace Dino {
return peer_state; 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) { foreach (CallState call_state in call_states.values) {
if (!call_state.call.account.equals(account)) continue; 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) { foreach (Jid jid in call_state.peers.keys) {
if (jid.equals(peer_jid)) { if (jid.equals_bare(relevant_jid)) {
return call_state; 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; 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); 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) { 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]; CallState call_state = call_states[call];
// If this is a MUC reflection of our own invite, store the sid assigned by the MUC // 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)) { if (call_state.parent_muc != null && call_state.parent_muc.equals_bare(inviter_jid)) {
call_state.invite_id = invite_id; call_state.cim_invite_id = invite_id;
return; return null;
} }
if (call.counterparts.contains(inviter_jid) && call_state.accepted) { if (call.counterparts.contains(inviter_jid) && call_state.accepted) {
// A call is converted into a group call. // A call is converted into a group call.
yield call_state.join_group_call(muc_jid); call_state.join_group_call.begin(muc_jid);
return; return null;
} }
} }
@ -284,27 +308,23 @@ namespace Dino {
call.encryption = Encryption.UNKNOWN; call.encryption = Encryption.UNKNOWN;
call.state = Call.State.RINGING; call.state = Call.State.RINGING;
// TODO create conv
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(inviter_jid.bare_jid, account); 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); stream_interactor.get_module(CallStore.IDENTITY).add_call(call, conversation);
conversation.last_active = call.time; conversation.last_active = call.time;
CallState call_state = new CallState(call, stream_interactor); CallState call_state = new CallState(call, stream_interactor);
connect_call_state_signals(call_state); 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.invited_to_group_call = muc_jid;
call_state.group_call_inviter = inviter_jid; call_state.parent_muc = inviter_jid.bare_jid;
call_state.invite_id = invite_id;
debug("[%s] on_muji_call_received accepting", account.bare_jid.to_string()); 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) { 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)) { jmi_request_peer.unset(call);
current_jmi_request_call.unset(call.account);
current_jmi_request_peer.unset(call.account);
}
call_states.unset(call); call_states.unset(call);
} }
@ -339,33 +359,36 @@ namespace Dino {
PeerState peer_state = create_received_call(account, from, to, video_requested); PeerState peer_state = create_received_call(account, from, to, video_requested);
peer_state.sid = sid; 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; CallState call_state = call_states[peer_state.call];
current_jmi_request_call[account] = 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) => { 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 if (from.equals_bare(account.bare_jid)) { // Carboned message from our account
// Ignore carbon from ourselves // Ignore carbon from ourselves
if (from.equals(account.full_jid)) return; if (from.equals(account.full_jid)) return;
Call call = current_jmi_request_peer[account].call;
call.ourpart = from; call.ourpart = from;
call.state = Call.State.OTHER_DEVICE; call.state = Call.State.OTHER_DEVICE;
remove_call_from_datastructures(call); 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 proposed the call
// We know the full jid of our peer now // We know the full jid of our peer now
current_jmi_request_call[account].rename_peer(current_jmi_request_peer[account].jid, from); call_states[call].rename_peer(jmi_request_peer[call].jid, from);
current_jmi_request_peer[account].call_resource.begin(from); jmi_request_peer[call].call_resource.begin(from);
} }
}); });
mi_module.session_rejected.connect((from, to, sid) => { mi_module.session_rejected.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);
Call call = current_jmi_request_peer[account].call; 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 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); 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); remove_call_from_datastructures(call);
}); });
mi_module.session_retracted.connect((from, to, sid) => { mi_module.session_retracted.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);
Call call = current_jmi_request_peer[account].call; 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 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); 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); 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; 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) { 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"); string? room_jid_str = join_method_node.get_attribute("room");
if (room_jid_str == null) return; if (room_jid_str == null) return;
Jid room_jid = new Jid(room_jid_str); Jid room_jid = new Jid(room_jid_str);
string? invite_id = null; call_state = create_recv_muji_call(account, from_jid, room_jid, invite_id, message_stanza.type_);
if (message_stanza.type_ == Xmpp.MessageStanza.TYPE_GROUPCHAT) { break;
invite_id = Xep.UniqueStableStanzaIDs.get_stanza_id(message_stanza, from_jid.bare_jid);
} else { } else if (join_method_node.name == "jingle" && join_method_node.ns_uri == Xep.CallInvites.NS_URI) {
invite_id = message_stanza.id;
} // This is an invite for a direct Jingle session
if (invite_id == null) { if (message_stanza.type_ != Xmpp.MessageStanza.TYPE_CHAT) return;
warning("Got call invite without ID");
return; string? sid = join_method_node.get_attribute("sid");
} if (sid == null) return;
on_muji_call_received.begin(account, from_jid, room_jid, id, video, message_stanza.type_);
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; if (call_state == null) return;
call_state.call.state = Call.State.OTHER_DEVICE; call_state.we_should_send_audio = true;
remove_call_from_datastructures(call_state.call); 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) => { call_invites_module.call_retracted.connect((from_jid, to_jid, invite_id, message_type) => {
if (from_jid.equals_bare(account.bare_jid)) return; if (from_jid.equals_bare(account.bare_jid)) return;
// The call was retracted by the counterpart // 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 == null) return;
if (call_state.call.state != Call.State.RINGING) { if (call_state.call.state != Call.State.RINGING) {

View file

@ -271,9 +271,12 @@ public class Dino.Ui.CallWindowController : Object {
private void update_audio_device_choices() { private void update_audio_device_choices() {
if (call_plugin.get_devices("audio", true).size == 0 || call_plugin.get_devices("audio", false).size == 0) { 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(); 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); call_window.bottom_bar.show_audio_device_choices(false);
return; 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); 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) { if (device_count == 0) {
call_window.bottom_bar.show_video_device_error(); 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); call_window.bottom_bar.show_video_device_choices(false);
return; return;
} }

View file

@ -1,25 +1,38 @@
using Gee; using Gee;
namespace Xmpp.Xep.CallInvites { 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 class Module : XmppStreamModule {
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "call_invites"); public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "call_invites");
public signal void call_proposed(Jid from, Jid to, bool video, Gee.List<StanzaNode> join_methods, MessageStanza message); public signal void call_proposed(Jid from, Jid to, bool video, Gee.List<StanzaNode> join_methods, MessageStanza message);
public signal void call_retracted(Jid from, Jid to, string invite_id, string message_type); 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 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) { public string send_jingle_propose(XmppStream stream, Jid invitee, string sid, bool video) {
StanzaNode muji_node = new StanzaNode.build("muji", Muji.NS_URI).add_self_xmlns().put_attribute("room", muc_jid.to_string()); 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() StanzaNode invite_node = new StanzaNode.build("propose", NS_URI).add_self_xmlns()
.put_attribute("video", video.to_string()) .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 }; MessageStanza invite_message = new MessageStanza() { to=invitee, type_=message_type };
MessageProcessingHints.set_message_hint(invite_message, MessageProcessingHints.HINT_STORE); MessageProcessingHints.set_message_hint(invite_message, MessageProcessingHints.HINT_STORE);
invite_message.stanza.put_node(invite_node); invite_message.stanza.put_node(invite_node);
stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, invite_message); 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) { 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) { switch (relevant_node.name) {
case "accept": case "accept":
call_accepted(message.from, invite_id, message.type_); call_accepted(message.from, message.to, invite_id, message.type_);
break; break;
case "retract": case "retract":
call_retracted(message.from, message.to, invite_id, message.type_); call_retracted(message.from, message.to, invite_id, message.type_);