UI + libdino: Improve MUJI calls from MUC

- Move calls from ICE-thead onto main thread
- Identify Call.ourpart as MUC nick if in MUC
- Keep track of the initiator of a call
This commit is contained in:
fiaxh 2021-12-20 00:15:05 +01:00
parent ff4e2540ae
commit f0c7dd0682
13 changed files with 74 additions and 43 deletions

View file

@ -20,9 +20,12 @@ namespace Dino.Entities {
public int id { get; set; default=-1; }
public Account account { get; set; }
public Jid counterpart { get; set; } // For backwards compatibility with db version 21. Not to be used anymore.
public Jid counterpart { get; set; }
public Gee.List<Jid> counterparts = new Gee.ArrayList<Jid>(Jid.equals_bare_func);
public Jid ourpart { get; set; }
public Jid proposer {
get { return direction == DIRECTION_OUTGOING ? ourpart : counterpart; }
}
public bool direction { get; set; }
public DateTime time { get; set; }
public DateTime local_time { get; set; }
@ -58,7 +61,6 @@ namespace Dino.Entities {
if (!counterparts.contains(peer)) { // Legacy: The first peer is also in the `call` table. Don't add twice.
counterparts.add(peer);
}
if (counterpart == null) counterpart = peer;
}
counterpart = db.get_jid_by_id(row[db.call.counterpart_id]);
@ -66,7 +68,6 @@ namespace Dino.Entities {
if (counterpart_resource != null) counterpart = counterpart.with_resource(counterpart_resource);
if (counterparts.is_empty) {
counterparts.add(counterpart);
counterpart = counterpart;
}
notify.connect(on_update);
@ -107,8 +108,6 @@ namespace Dino.Entities {
}
public void add_peer(Jid peer) {
if (counterpart == null) counterpart = peer;
if (counterparts.contains(peer)) return;
counterparts.add(peer);

View file

@ -127,8 +127,6 @@ public class Dino.PeerState : Object {
}
public void reject() {
call.state = Call.State.DECLINED;
if (session != null) {
foreach (Xep.Jingle.Content content in session.contents) {
content.reject();
@ -299,7 +297,12 @@ public class Dino.PeerState : Object {
debug(@"[%s] %s connecting content signals %s", call.account.bare_jid.to_string(), jid.to_string(), rtp_content_parameter.media);
rtp_content_parameter.stream_created.connect((stream) => on_stream_created(rtp_content_parameter.media, stream));
rtp_content_parameter.connection_ready.connect((status) => on_connection_ready(content, rtp_content_parameter.media));
rtp_content_parameter.connection_ready.connect((status) => {
Idle.add(() => {
on_connection_ready(content, rtp_content_parameter.media);
return false;
});
});
content.senders_modify_incoming.connect((content, proposed_senders) => {
if (content.session.senders_include_us(content.senders) != content.session.senders_include_us(proposed_senders)) {
@ -342,7 +345,10 @@ public class Dino.PeerState : Object {
if (media == "video" && stream.receiving) {
counterpart_sends_video = true;
video_content_parameter.connection_ready.connect((status) => {
counterpart_sends_video_updated(false);
Idle.add(() => {
counterpart_sends_video_updated(false);
return false;
});
});
}

View file

@ -126,7 +126,7 @@ public class Dino.CallState : Object {
foreach (PeerState peer in peers_cpy) {
peer.end(Xep.Jingle.ReasonElement.CANCEL);
}
if (parent_muc != null) {
if (parent_muc != null && group_call != null) {
XmppStream stream = stream_interactor.get_stream(call.account);
if (stream == null) return;
stream.get_module(Xep.MujiMeta.Module.IDENTITY).send_invite_retract_to_peer(stream, parent_muc, group_call.muc_jid, message_type);
@ -242,7 +242,12 @@ public class Dino.CallState : Object {
XmppStream stream = stream_interactor.get_stream(call.account);
if (stream == null) return;
Jid muc_jid = stream_interactor.get_module(MucManager.IDENTITY).default_muc_server[call.account] ?? new Jid("chat.jabberfr.org");
Jid? muc_jid = null;
if (muc_jid == null) {
warning("Failed to initiate group call: MUC server not known.");
return;
}
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());

View file

@ -30,7 +30,7 @@ namespace Dino {
cache_call(call);
}
public Call? get_call_by_id(int id) {
public Call? get_call_by_id(int id, Conversation conversation) {
Call? call = calls_by_db_id[id];
if (call != null) {
return call;
@ -38,14 +38,17 @@ namespace Dino {
RowOption row_option = db.call.select().with(db.call.id, "=", id).row();
return create_call_from_row_opt(row_option);
return create_call_from_row_opt(row_option, conversation);
}
private Call? create_call_from_row_opt(RowOption row_opt) {
private Call? create_call_from_row_opt(RowOption row_opt, Conversation conversation) {
if (!row_opt.is_present()) return null;
try {
Call call = new Call.from_row(db, row_opt.inner);
if (conversation.type_.is_muc_semantic()) {
call.ourpart = conversation.counterpart.with_resource(call.ourpart.resourcepart);
}
cache_call(call);
return call;
} catch (InvalidJidError e) {

View file

@ -39,9 +39,8 @@ namespace Dino {
Call call = new Call();
call.direction = Call.DIRECTION_OUTGOING;
call.account = conversation.account;
// TODO we should only do that for Conversation.Type.CHAT, but the database currently requires a counterpart from the start
call.counterpart = conversation.counterpart;
call.ourpart = conversation.account.full_jid;
call.ourpart = stream_interactor.get_module(MucManager.IDENTITY).get_own_jid(conversation.counterpart, conversation.account) ?? conversation.account.full_jid;
call.time = call.local_time = call.end_time = new DateTime.now_utc();
call.state = Call.State.RINGING;
@ -57,7 +56,7 @@ namespace Dino {
PeerState peer_state = call_state.set_first_peer(conversation.counterpart);
yield peer_state.initiate_call(conversation.counterpart);
} else {
call_state.initiate_groupchat_call(conversation.counterpart);
call_state.initiate_groupchat_call.begin(conversation.counterpart);
}
conversation.last_active = call.time;
@ -213,24 +212,23 @@ namespace Dino {
private PeerState create_received_call(Account account, Jid from, Jid to, bool video_requested) {
Call call = new Call();
Jid counterpart = null;
if (from.equals_bare(account.bare_jid)) {
// Call requested by another of our devices
call.direction = Call.DIRECTION_OUTGOING;
call.ourpart = from;
call.state = Call.State.OTHER_DEVICE;
counterpart = to;
call.counterpart = to;
} else {
call.direction = Call.DIRECTION_INCOMING;
call.ourpart = account.full_jid;
call.state = Call.State.RINGING;
counterpart = from;
call.counterpart = from;
}
call.add_peer(counterpart);
call.add_peer(call.counterpart);
call.account = account;
call.time = call.local_time = call.end_time = new DateTime.now_utc();
Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(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);
@ -238,7 +236,7 @@ namespace Dino {
var call_state = new CallState(call, stream_interactor);
connect_call_state_signals(call_state);
PeerState peer_state = call_state.set_first_peer(counterpart);
PeerState peer_state = call_state.set_first_peer(call.counterpart);
call_state.we_should_send_video = video_requested;
call_state.we_should_send_audio = true;
@ -283,7 +281,7 @@ namespace Dino {
Call call = new Call();
call.direction = Call.DIRECTION_INCOMING;
call.ourpart = account.full_jid;
call.add_peer(inviter_jid); // not rly
call.counterpart = inviter_jid;
call.account = account;
call.time = call.local_time = call.end_time = new DateTime.now_utc();
call.state = Call.State.RINGING;
@ -361,13 +359,14 @@ namespace Dino {
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
// 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(from);
current_jmi_request_peer[account].call_resource.begin(from);
}
});
mi_module.session_rejected.connect((from, to, sid) => {
@ -378,6 +377,9 @@ namespace Dino {
bool incoming_reject = call.direction == Call.DIRECTION_INCOMING && from.equals_bare(account.bare_jid);
if (!outgoing_reject && !incoming_reject) return;
// We don't care if a single person in a group call rejected the call
if (incoming_reject && call_states[call].group_call != null) return;
call.state = Call.State.DECLINED;
call_states[call].terminated(from, Xep.Jingle.ReasonElement.DECLINE, "JMI reject");
remove_call_from_datastructures(call);

View file

@ -69,7 +69,7 @@ public class ContentItemStore : StreamInteractionModule, Object {
}
break;
case 3:
Call? call = stream_interactor.get_module(CallStore.IDENTITY).get_call_by_id(foreign_id);
Call? call = stream_interactor.get_module(CallStore.IDENTITY).get_call_by_id(foreign_id, conversation);
if (call != null) {
var call_item = new CallItem(call, conversation, row[db.content_item.id]);
items.add(call_item);
@ -321,7 +321,7 @@ public class CallItem : ContentItem {
public Conversation conversation;
public CallItem(Call call, Conversation conversation, int id) {
base(id, TYPE, call.direction == Call.DIRECTION_OUTGOING ? conversation.account.bare_jid : conversation.counterpart, call.time, call.encryption, Message.Marked.NONE);
base(id, TYPE, call.proposer, call.time, call.encryption, Message.Marked.NONE);
this.call = call;
this.conversation = conversation;

View file

@ -424,7 +424,7 @@ public class MucManager : StreamInteractionModule, Object {
foreach (Xep.ServiceDiscovery.Identity identity in identities) {
if (identity.category == Xep.ServiceDiscovery.Identity.CATEGORY_CONFERENCE) {
default_muc_server[account] = item.jid;
print(@"$(account.bare_jid) Default MUC: $(item.jid)\n");
debug("[%s] Default MUC: %s", account.bare_jid.to_string(), item.jid.to_string());
return;
}
}

View file

@ -98,7 +98,7 @@
<child>
<object class="GtkBox" id="multiparty_peer_box">
<property name="margin">10</property>
<property name="spacing">5</property>
<property name="spacing">7</property>
<property name="hexpand">True</property>
</object>
</child>

View file

@ -206,9 +206,14 @@ public class Dino.Ui.Application : Gtk.Application, Dino.Application {
});
add_action(open_shortcuts_action);
SimpleAction accept_call_action = new SimpleAction("accept-call", VariantType.INT32);
SimpleAction accept_call_action = new SimpleAction("accept-call", new VariantType.tuple(new VariantType[]{VariantType.INT32, VariantType.INT32}));
accept_call_action.activate.connect((variant) => {
Call? call = stream_interactor.get_module(CallStore.IDENTITY).get_call_by_id(variant.get_int32());
int conversation_id = variant.get_child_value(0).get_int32();
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(conversation_id);
if (conversation == null) return;
int call_id = variant.get_child_value(1).get_int32();
Call? call = stream_interactor.get_module(CallStore.IDENTITY).get_call_by_id(call_id, conversation);
CallState? call_state = stream_interactor.get_module(Calls.IDENTITY).call_states[call];
if (call_state == null) return;
@ -220,9 +225,14 @@ public class Dino.Ui.Application : Gtk.Application, Dino.Application {
});
add_action(accept_call_action);
SimpleAction deny_call_action = new SimpleAction("reject-call", VariantType.INT32);
SimpleAction deny_call_action = new SimpleAction("reject-call", new VariantType.tuple(new VariantType[]{VariantType.INT32, VariantType.INT32}));
deny_call_action.activate.connect((variant) => {
Call? call = stream_interactor.get_module(CallStore.IDENTITY).get_call_by_id(variant.get_int32());
int conversation_id = variant.get_child_value(0).get_int32();
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation_by_id(conversation_id);
if (conversation == null) return;
int call_id = variant.get_child_value(1).get_int32();
Call? call = stream_interactor.get_module(CallStore.IDENTITY).get_call_by_id(call_id, conversation);
CallState? call_state = stream_interactor.get_module(Calls.IDENTITY).call_states[call];
if (call_state == null) return;

View file

@ -20,7 +20,7 @@ namespace Dino.Ui {
public Revealer header_bar_revealer = new Revealer() { halign=Align.END, valign=Align.START, transition_type=RevealerTransitionType.CROSSFADE, transition_duration=200, visible=true };
public Box own_video_box = new Box(Orientation.HORIZONTAL, 0) { halign=Align.END, valign=Align.END, visible=true };
public Revealer invite_button_revealer = new Revealer() { margin_top=50, margin_right=30, halign=Align.END, valign=Align.START, transition_type=RevealerTransitionType.CROSSFADE, transition_duration=200 };
public Button invite_button = new Button.from_icon_name("dino-account-plus") { relief=ReliefStyle.NONE };
public Button invite_button = new Button.from_icon_name("dino-account-plus") { relief=ReliefStyle.NONE, visible=false };
private Widget? own_video = null;
private HashMap<string, ParticipantWidget> participant_widgets = new HashMap<string, ParticipantWidget>();
private ArrayList<string> participants = new ArrayList<string>();

View file

@ -86,7 +86,7 @@ namespace Dino.Ui {
private void update_counterparts() {
if (call.state != Call.State.IN_PROGRESS && call.state != Call.State.ENDED) return;
if (call.counterparts.size <= 1) return;
if (call.counterparts.size <= 1 && conversation.type_ == Conversation.Type.CHAT) return;
multiparty_peer_box.foreach((widget) => { multiparty_peer_box.remove(widget); });

View file

@ -115,13 +115,17 @@ namespace Dino.Ui {
return;
}
Conversation conv_bak = conversation;
bool audio_works = yield stream_interactor.get_module(Calls.IDENTITY).can_do_audio_calls_async(conversation);
bool video_works = yield stream_interactor.get_module(Calls.IDENTITY).can_do_video_calls_async(conversation);
if (conv_bak != conversation) return;
if (conversation.type_ == Conversation.Type.CHAT) {
Conversation conv_bak = conversation;
bool audio_works = yield stream_interactor.get_module(Calls.IDENTITY).can_do_audio_calls_async(conversation);
bool video_works = yield stream_interactor.get_module(Calls.IDENTITY).can_do_video_calls_async(conversation);
if (conv_bak != conversation) return;
visible = audio_works;
video_button.visible = video_works;
visible = audio_works;
video_button.visible = video_works;
} else {
visible = false;
}
}
public new void unset_conversation() { }

View file

@ -141,10 +141,12 @@ public class Dino.Ui.FreeDesktopNotifier : NotificationProvider, Object {
GLib.Application.get_default().activate_action("open-conversation", new Variant.int32(conversation.id));
});
add_action_listener(notification_id, "reject", () => {
GLib.Application.get_default().activate_action("reject-call", new Variant.int32(call.id));
var variant = new Variant.tuple(new Variant[] {new Variant.int32(conversation.id), new Variant.int32(call.id)});
GLib.Application.get_default().activate_action("reject-call", variant);
});
add_action_listener(notification_id, "accept", () => {
GLib.Application.get_default().activate_action("accept-call", new Variant.int32(call.id));
var variant = new Variant.tuple(new Variant[] {new Variant.int32(conversation.id), new Variant.int32(call.id)});
GLib.Application.get_default().activate_action("accept-call", variant);
});
} catch (Error e) {
warning("Failed showing subscription request notification: %s", e.message);