Various call fixes

- Use groupchat message type for invites in MUCs
- Use call id (from propose) instead of message id for Call Invite Messages
- Fix call window controlls appearing when hovering controls
This commit is contained in:
fiaxh 2022-02-07 22:09:51 +01:00
parent ee085e3e0d
commit 3088879a7b
6 changed files with 60 additions and 65 deletions

View file

@ -90,7 +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); // call_state.cim_call_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);
} }

View file

@ -16,7 +16,7 @@ public class Dino.CallState : Object {
public bool accepted { get; private set; default=false; } public bool accepted { get; private set; default=false; }
public bool use_cim = false; public bool use_cim = false;
public string? cim_invite_id = null; public string? cim_call_id = null;
public Jid? cim_counterpart = null; public Jid? cim_counterpart = null;
public string cim_message_type { get; set; default=Xmpp.MessageStanza.TYPE_CHAT; } public string cim_message_type { get; set; default=Xmpp.MessageStanza.TYPE_CHAT; }
@ -46,6 +46,7 @@ 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;
cim_message_type = 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;
@ -63,7 +64,7 @@ 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_muji_propose(stream, muc, group_call.muc_jid, we_should_send_video, cim_message_type); stream.get_module(Xep.CallInvites.Module.IDENTITY).send_muji_propose(stream, cim_call_id, 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) {
@ -86,7 +87,7 @@ public class Dino.CallState : Object {
if (use_cim) { 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, cim_counterpart, cim_invite_id, cim_message_type); stream.get_module(Xep.CallInvites.Module.IDENTITY).send_accept(stream, cim_counterpart, cim_call_id, cim_message_type);
} else { } else {
foreach (PeerState peer in peers.values) { foreach (PeerState peer in peers.values) {
peer.accept(); peer.accept();
@ -104,7 +105,7 @@ public class Dino.CallState : Object {
if (use_cim) { 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, cim_counterpart, cim_invite_id, cim_message_type); stream.get_module(Xep.CallInvites.Module.IDENTITY).send_reject(stream, cim_counterpart, cim_call_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);
@ -137,7 +138,7 @@ public class Dino.CallState : Object {
if (call.direction == Call.DIRECTION_OUTGOING && use_cim) { 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, cim_counterpart, cim_invite_id, cim_message_type); stream.get_module(Xep.CallInvites.Module.IDENTITY).send_retract(stream, cim_counterpart, cim_call_id, cim_message_type);
} }
call.state = Call.State.MISSED; call.state = Call.State.MISSED;
} else { } else {
@ -176,7 +177,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_muji_propose(stream, invitee, group_call.muc_jid, we_should_send_video, "chat"); stream.get_module(Xep.CallInvites.Module.IDENTITY).send_muji_propose(stream, cim_call_id, 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
@ -288,6 +289,7 @@ public class Dino.CallState : Object {
return; return;
} }
if (cim_call_id == null) cim_call_id = Xmpp.random_uuid();
muc_jid = new Jid("%08x@".printf(Random.next_int()) + muc_jid.to_string()); // TODO longer? muc_jid = new Jid("%08x@".printf(Random.next_int()) + muc_jid.to_string()); // TODO longer?
debug("[%s] Converting call to groupcall %s", call.account.bare_jid.to_string(), muc_jid.to_string()); debug("[%s] Converting call to groupcall %s", call.account.bare_jid.to_string(), muc_jid.to_string());

View file

@ -244,13 +244,13 @@ namespace Dino {
return peer_state; return peer_state;
} }
private CallState? get_call_state_by_invite_id(Account account, string invite_id, Jid jid1, Jid jid2) { private CallState? get_call_state_by_call_id(Account account, string call_id, Jid jid1, Jid jid2) {
Jid relevant_jid = jid1.equals_bare(account.bare_jid) ? jid2 : jid1; 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.cim_invite_id == invite_id) { if (call_state.cim_call_id == call_id) {
foreach (Jid jid in call_state.peers.keys) { foreach (Jid jid in call_state.peers.keys) {
if (jid.equals_bare(relevant_jid)) { if (jid.equals_bare(relevant_jid)) {
return call_state; return call_state;
@ -278,7 +278,7 @@ namespace Dino {
return null; return null;
} }
private CallState? create_recv_muji_call(Account account, Jid inviter_jid, Jid muc_jid, string invite_id, string message_type) { private CallState? create_recv_muji_call(Account account, Jid inviter_jid, Jid muc_jid, 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) {
@ -286,12 +286,6 @@ namespace Dino {
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 (call_state.parent_muc != null && call_state.parent_muc.equals_bare(inviter_jid)) {
call_state.cim_invite_id = invite_id;
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.
call_state.join_group_call.begin(muc_jid); call_state.join_group_call.begin(muc_jid);
@ -416,35 +410,29 @@ 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_requested, join_methods, message_stanza) => { call_invites_module.call_proposed.connect((from_jid, to_jid, call_id, video_requested, join_methods, message_stanza) => {
if (from_jid.equals_bare(account.bare_jid)) return; if (from_jid.equals_bare(account.bare_jid)) return;
if (stream_interactor.get_module(MucManager.IDENTITY).is_own_muc_jid(from_jid, account)) 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; CallState? call_state = null;
foreach (StanzaNode join_method_node in join_methods) { foreach (StanzaNode join_method_node in join_methods) {
if (join_method_node.name == "muji" && 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 // This is a MUJI invite
// Disregard calls from muc history
DateTime? delay = Xep.DelayedDelivery.get_time_for_message(message_stanza, from_jid.bare_jid);
if (delay != null) return;
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);
call_state = create_recv_muji_call(account, from_jid, room_jid, invite_id, message_stanza.type_); call_state = create_recv_muji_call(account, from_jid, room_jid, message_stanza.type_);
break; break;
} else if (join_method_node.name == "jingle" && join_method_node.ns_uri == Xep.CallInvites.NS_URI) { } 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 // This is an invite for a direct Jingle session
if (message_stanza.type_ != Xmpp.MessageStanza.TYPE_CHAT) return; if (message_stanza.type_ != Xmpp.MessageStanza.TYPE_CHAT) return;
string? sid = join_method_node.get_attribute("sid"); string? sid = join_method_node.get_attribute("sid");
@ -467,7 +455,7 @@ namespace Dino {
call_state.we_should_send_video = video_requested; call_state.we_should_send_video = video_requested;
call_state.use_cim = true; call_state.use_cim = true;
call_state.cim_invite_id = invite_id; call_state.cim_call_id = call_id;
call_state.cim_counterpart = message_stanza.type_ == MessageStanza.TYPE_GROUPCHAT ? from_jid.bare_jid : from_jid; call_state.cim_counterpart = message_stanza.type_ == MessageStanza.TYPE_GROUPCHAT ? from_jid.bare_jid : from_jid;
call_state.cim_message_type = message_stanza.type_; call_state.cim_message_type = message_stanza.type_;
@ -477,8 +465,8 @@ namespace Dino {
call_incoming(call_state.call, call_state, conversation, video_requested); call_incoming(call_state.call, call_state, conversation, video_requested);
}); });
call_invites_module.call_accepted.connect((from_jid, to_jid, invite_id, message_type) => { call_invites_module.call_accepted.connect((from_jid, to_jid, call_id, message_type) => {
CallState? call_state = get_call_state_by_invite_id(account, invite_id, from_jid, to_jid); CallState? call_state = get_call_state_by_call_id(account, call_id, from_jid, to_jid);
if (call_state == null) return; if (call_state == null) return;
Call call = call_state.call; Call call = call_state.call;
@ -497,11 +485,11 @@ namespace Dino {
jmi_request_peer[call].call_resource.begin(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, call_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, invite_id, from_jid, to_jid); CallState? call_state = get_call_state_by_call_id(account, call_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

@ -347,6 +347,12 @@ public class MucManager : StreamInteractionModule, Object {
return null; return null;
} }
public bool is_own_muc_jid(Jid full_jid, Account account) {
if (!is_groupchat(full_jid.bare_jid, account)) return false;
Jid? own_jid = get_own_jid(full_jid, account);
return own_jid != null && own_jid.equals(full_jid);
}
private Xep.Muc.Flag? get_muc_flag(Account account) { private Xep.Muc.Flag? get_muc_flag(Account account) {
XmppStream? stream = stream_interactor.get_stream(account); XmppStream? stream = stream_interactor.get_stream(account);
if (stream != null) { if (stream != null) {

View file

@ -13,7 +13,6 @@ namespace Dino.Ui {
public Overlay overlay = new Overlay() { visible=true }; public Overlay overlay = new Overlay() { visible=true };
public Grid grid = new Grid() { visible=true }; public Grid grid = new Grid() { visible=true };
public EventBox event_box = new EventBox() { visible=true };
public CallBottomBar bottom_bar = new CallBottomBar() { visible=true }; public CallBottomBar bottom_bar = new CallBottomBar() { visible=true };
public Revealer bottom_bar_revealer = new Revealer() { valign=Align.END, transition_type=RevealerTransitionType.CROSSFADE, transition_duration=200, visible=true }; public Revealer bottom_bar_revealer = new Revealer() { valign=Align.END, transition_type=RevealerTransitionType.CROSSFADE, transition_duration=200, visible=true };
public HeaderBar header_bar = new HeaderBar() { valign=Align.START, halign=Align.END, show_close_button=true, visible=true }; public HeaderBar header_bar = new HeaderBar() { valign=Align.START, halign=Align.END, show_close_button=true, visible=true };
@ -50,8 +49,7 @@ namespace Dino.Ui {
overlay.add_overlay(invite_button_revealer); overlay.add_overlay(invite_button_revealer);
overlay.get_child_position.connect(on_get_child_position); overlay.get_child_position.connect(on_get_child_position);
event_box.add(overlay); add(overlay);
add(event_box);
} }
public CallWindow() { public CallWindow() {
@ -59,9 +57,9 @@ namespace Dino.Ui {
this.bind_property("controls-active", header_bar_revealer, "reveal-child", BindingFlags.SYNC_CREATE); this.bind_property("controls-active", header_bar_revealer, "reveal-child", BindingFlags.SYNC_CREATE);
this.bind_property("controls-active", invite_button_revealer, "reveal-child", BindingFlags.SYNC_CREATE); this.bind_property("controls-active", invite_button_revealer, "reveal-child", BindingFlags.SYNC_CREATE);
event_box.motion_notify_event.connect(reveal_control_elements); this.motion_notify_event.connect(reveal_control_elements);
event_box.enter_notify_event.connect(reveal_control_elements); this.enter_notify_event.connect(reveal_control_elements);
event_box.leave_notify_event.connect(reveal_control_elements); this.leave_notify_event.connect(reveal_control_elements);
this.configure_event.connect(reveal_control_elements); // upon resizing this.configure_event.connect(reveal_control_elements); // upon resizing
this.configure_event.connect(reposition_participant_widgets); this.configure_event.connect(reposition_participant_widgets);

View file

@ -6,25 +6,26 @@ namespace Xmpp.Xep.CallInvites {
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, string call_id, 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 call_id, string message_type);
public signal void call_accepted(Jid from, Jid to, string invite_id, string message_type); public signal void call_accepted(Jid from, Jid to, string call_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 call_id, string message_type);
public string send_jingle_propose(XmppStream stream, Jid invitee, string sid, bool video) { public string send_jingle_propose(XmppStream stream, Jid invitee, string sid, bool video) {
StanzaNode jingle_node = new StanzaNode.build("jingle", CallInvites.NS_URI) StanzaNode jingle_node = new StanzaNode.build("jingle", CallInvites.NS_URI)
.put_attribute("sid", sid); .put_attribute("sid", sid);
return send_propose(stream, invitee, jingle_node, video, false, MessageStanza.TYPE_CHAT); return send_propose(stream, sid, 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) { public void send_muji_propose(XmppStream stream, string call_id, Jid invitee, Jid muc_jid, bool video, string message_type) {
StanzaNode muji_node = new StanzaNode.build("muji", Muji.NS_URI).add_self_xmlns() StanzaNode muji_node = new StanzaNode.build("muji", Muji.NS_URI).add_self_xmlns()
.put_attribute("room", muc_jid.to_string()); .put_attribute("room", muc_jid.to_string());
send_propose(stream, invitee, muji_node, video, true, message_type); send_propose(stream, call_id, 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) { private string send_propose(XmppStream stream, string call_id, 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("id", call_id)
.put_attribute("video", video.to_string()) .put_attribute("video", video.to_string())
.put_attribute("multi", multiparty.to_string()) .put_attribute("multi", multiparty.to_string())
.put_node(inner_node); .put_node(inner_node);
@ -35,20 +36,20 @@ namespace Xmpp.Xep.CallInvites {
return invite_message.id; 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 call_id, string message_type) {
send_message(stream, "retract", to, invite_id, message_type); send_message(stream, "retract", to, call_id, message_type);
} }
public void send_accept(XmppStream stream, Jid to, string invite_id, string message_type) { public void send_accept(XmppStream stream, Jid to, string call_id, string message_type) {
send_message(stream, "accept", to, invite_id, message_type); send_message(stream, "accept", to, call_id, message_type);
} }
public void send_reject(XmppStream stream, Jid to, string invite_id, string message_type) { public void send_reject(XmppStream stream, Jid to, string call_id, string message_type) {
send_message(stream, "reject", to, invite_id, message_type); send_message(stream, "reject", to, call_id, message_type);
} }
private void send_message(XmppStream stream, string action, Jid to, string invite_id, string message_type) { private void send_message(XmppStream stream, string action, Jid to, string call_id, string message_type) {
StanzaNode inner_node = new StanzaNode.build(action, NS_URI).add_self_xmlns().put_attribute("id", invite_id); StanzaNode inner_node = new StanzaNode.build(action, NS_URI).add_self_xmlns().put_attribute("id", call_id);
MessageStanza message = new MessageStanza() { to=to, type_=message_type }; MessageStanza message = new MessageStanza() { to=to, type_=message_type };
message.stanza.put_node(inner_node); message.stanza.put_node(inner_node);
MessageProcessingHints.set_message_hint(message, MessageProcessingHints.HINT_STORE); MessageProcessingHints.set_message_hint(message, MessageProcessingHints.HINT_STORE);
@ -69,25 +70,25 @@ namespace Xmpp.Xep.CallInvites {
} }
if (relevant_node == null) return; if (relevant_node == null) return;
string? call_id = relevant_node.get_attribute("id");
if (call_id == null) return;
if (relevant_node.name == "propose") { if (relevant_node.name == "propose") {
if (relevant_node.sub_nodes.is_empty) return; if (relevant_node.sub_nodes.is_empty) return;
bool video = relevant_node.get_attribute_bool("video", false); bool video = relevant_node.get_attribute_bool("video", false);
call_proposed(message.from, message.to, video, relevant_node.sub_nodes, message); call_proposed(message.from, message.to, call_id, video, relevant_node.sub_nodes, message);
return; return;
} }
string? invite_id = relevant_node.get_attribute("id");
if (invite_id == null) return;
switch (relevant_node.name) { switch (relevant_node.name) {
case "accept": case "accept":
call_accepted(message.from, message.to, invite_id, message.type_); call_accepted(message.from, message.to, call_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, call_id, message.type_);
break; break;
case "reject": case "reject":
call_rejected(message.from, message.to, invite_id, message.type_); call_rejected(message.from, message.to, call_id, message.type_);
break; break;
} }
} }