aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libdino/src/plugin/interfaces.vala2
-rw-r--r--libdino/src/service/call_peer_state.vala11
-rw-r--r--libdino/src/service/call_state.vala79
-rw-r--r--libdino/src/service/calls.vala45
-rw-r--r--libdino/src/service/notification_events.vala1
-rw-r--r--main/src/ui/call_window/call_window.vala8
-rw-r--r--main/src/ui/call_window/call_window_controller.vala69
-rw-r--r--main/src/ui/conversation_content_view/call_widget.vala25
-rw-r--r--main/src/ui/conversation_titlebar/call_entry.vala16
-rw-r--r--plugins/omemo/src/dtls_srtp_verification_draft.vala15
-rw-r--r--plugins/rtp/src/plugin.vala3
-rw-r--r--xmpp-vala/src/module/xep/0045_muc/module.vala5
-rw-r--r--xmpp-vala/src/module/xep/0272_muji.vala5
-rw-r--r--xmpp-vala/src/module/xep/muji_meta.vala14
14 files changed, 180 insertions, 118 deletions
diff --git a/libdino/src/plugin/interfaces.vala b/libdino/src/plugin/interfaces.vala
index fb80fef6..23e64373 100644
--- a/libdino/src/plugin/interfaces.vala
+++ b/libdino/src/plugin/interfaces.vala
@@ -96,7 +96,7 @@ public abstract interface ConversationAdditionPopulator : ConversationItemPopula
public abstract interface VideoCallPlugin : Object {
- public abstract bool supports(string media);
+ public abstract bool supports(string? media);
// Video widget
public abstract VideoCallWidget? create_widget(WidgetType type);
diff --git a/libdino/src/service/call_peer_state.vala b/libdino/src/service/call_peer_state.vala
index 09440371..902a0792 100644
--- a/libdino/src/service/call_peer_state.vala
+++ b/libdino/src/service/call_peer_state.vala
@@ -3,6 +3,7 @@ using Gee;
using Xmpp;
public class Dino.PeerState : Object {
+ public signal void stream_created(string media);
public signal void counterpart_sends_video_updated(bool mute);
public signal void info_received(Xep.JingleRtp.CallSessionInfo session_info);
@@ -214,14 +215,14 @@ public class Dino.PeerState : Object {
// If video_content_parameter == null && !mute we're trying to mute a non-existant feed. It will be muted as soon as it is created.
}
- public Xep.JingleRtp.Stream? get_video_stream(Call call) {
+ public Xep.JingleRtp.Stream? get_video_stream() {
if (video_content_parameter != null) {
return video_content_parameter.stream;
}
return null;
}
- public Xep.JingleRtp.Stream? get_audio_stream(Call call) {
+ public Xep.JingleRtp.Stream? get_audio_stream() {
if (audio_content_parameter != null) {
return audio_content_parameter.stream;
}
@@ -235,8 +236,8 @@ public class Dino.PeerState : Object {
session.terminated.connect((stream, we_terminated, reason_name, reason_text) =>
session_terminated(we_terminated, reason_name, reason_text)
);
- session.additional_content_add_incoming.connect((session,stream, content) =>
- on_incoming_content_add(stream, session, content)
+ session.additional_content_add_incoming.connect((stream, content) =>
+ on_incoming_content_add(stream, content.session, content)
);
foreach (Xep.Jingle.Content content in session.contents) {
@@ -358,6 +359,8 @@ public class Dino.PeerState : Object {
} else if (media == "audio" && !we_should_send_audio) {
mute_own_audio(true);
}
+
+ stream_created(media);
}
private void on_counterpart_mute_update(bool mute, string? media) {
diff --git a/libdino/src/service/call_state.vala b/libdino/src/service/call_state.vala
index 7d205f7f..03ee9595 100644
--- a/libdino/src/service/call_state.vala
+++ b/libdino/src/service/call_state.vala
@@ -9,6 +9,7 @@ public class Dino.CallState : Object {
public signal void peer_left(Jid jid, PeerState peer_state, string? reason_name, string? reason_text);
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; }
@@ -109,22 +110,25 @@ public class Dino.CallState : Object {
terminated(call.account.bare_jid, null, null);
}
- public void end() {
+ public void end(string? reason_text = null) {
var peers_cpy = new ArrayList<PeerState>();
peers_cpy.add_all(peers.values);
if (group_call != null) {
- stream_interactor.get_module(MucManager.IDENTITY).part(call.account, group_call.muc_jid);
+ XmppStream stream = stream_interactor.get_stream(call.account);
+ if (stream != null) {
+ stream.get_module(Xep.Muc.Module.IDENTITY).exit(stream, group_call.muc_jid);
+ }
}
if (call.state == Call.State.IN_PROGRESS || call.state == Call.State.ESTABLISHING) {
foreach (PeerState peer in peers_cpy) {
- peer.end(Xep.Jingle.ReasonElement.SUCCESS);
+ peer.end(Xep.Jingle.ReasonElement.SUCCESS, reason_text);
}
call.state = Call.State.ENDED;
} else if (call.state == Call.State.RINGING) {
foreach (PeerState peer in peers_cpy) {
- peer.end(Xep.Jingle.ReasonElement.CANCEL);
+ peer.end(Xep.Jingle.ReasonElement.CANCEL, reason_text);
}
if (parent_muc != null && group_call != null) {
XmppStream stream = stream_interactor.get_stream(call.account);
@@ -138,7 +142,7 @@ public class Dino.CallState : Object {
call.end_time = new DateTime.now_utc();
- terminated(call.account.bare_jid, null, null);
+ terminated(call.account.bare_jid, null, reason_text);
}
public void mute_own_audio(bool mute) {
@@ -168,7 +172,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.MujiMeta.Module.IDENTITY).send_invite(stream, invitee, group_call.muc_jid, we_should_send_video);
+ stream.get_module(Xep.MujiMeta.Module.IDENTITY).send_invite(stream, invitee, group_call.muc_jid, we_should_send_video, message_type);
// If the peer hasn't accepted within a minute, retract the invite
Timeout.add_seconds(60, () => {
@@ -183,13 +187,43 @@ 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.MujiMeta.Module.IDENTITY).send_invite_retract_to_peer(stream, invitee, group_call.muc_jid);
+ stream.get_module(Xep.MujiMeta.Module.IDENTITY).send_invite_retract_to_peer(stream, invitee, group_call.muc_jid, message_type);
stream.get_module(Xep.Muc.Module.IDENTITY).change_affiliation.begin(stream, group_call.muc_jid, invitee, null, "none");
}
return false;
});
}
+ public Plugins.MediaDevice? get_microphone_device() {
+ if (peers.is_empty) return null;
+ var audio_stream = peers.values.to_array()[0].get_audio_stream();
+ return call_plugin.get_device(audio_stream, false);
+ }
+
+ public Plugins.MediaDevice? get_speaker_device() {
+ if (peers.is_empty) return null;
+ var audio_stream = peers.values.to_array()[0].get_audio_stream();
+ return call_plugin.get_device(audio_stream, true);
+ }
+
+ public Plugins.MediaDevice? get_video_device() {
+ if (peers.is_empty) return null;
+ var video_stream = peers.values.to_array()[0].get_video_stream();
+ return call_plugin.get_device(video_stream, false);
+ }
+
+ public void set_audio_device(Plugins.MediaDevice? device) {
+ foreach (PeerState peer_state in peers.values) {
+ call_plugin.set_device(peer_state.get_audio_stream(), device);
+ }
+ }
+
+ public void set_video_device(Plugins.MediaDevice? device) {
+ foreach (PeerState peer_state in peers.values) {
+ call_plugin.set_device(peer_state.get_video_stream(), device);
+ }
+ }
+
internal void rename_peer(Jid from_jid, Jid to_jid) {
debug("[%s] Renaming %s to %s exists %s", call.account.bare_jid.to_string(), from_jid.to_string(), to_jid.to_string(), peers.has_key(from_jid).to_string());
PeerState? peer_state = peers[from_jid];
@@ -226,23 +260,35 @@ public class Dino.CallState : Object {
this.bind_property("group-call", peer_state, "group-call", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
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);
- debug("[%s] Peer left %s left %i", call.account.bare_jid.to_string(), peer_state.jid.to_string(), peers.size);
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, reason_name, reason_text);
+ 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);
}
});
}
+ public async bool can_convert_into_groupcall() {
+ if (peers.size == 0) return false;
+ Jid peer = peers.keys.to_array()[0];
+ bool peer_has_feature = yield stream_interactor.get_module(EntityInfo.IDENTITY).has_feature(call.account, peer, Xep.Muji.NS_URI);
+ bool can_initiate = stream_interactor.get_module(Calls.IDENTITY).can_initiate_groupcall(call.account);
+ return peer_has_feature && can_initiate;
+ }
+
public async void convert_into_group_call() {
XmppStream stream = stream_interactor.get_stream(call.account);
if (stream == null) return;
- Jid? muc_jid = null;
+ Jid? muc_jid = stream_interactor.get_module(MucManager.IDENTITY).default_muc_server[call.account];
if (muc_jid == null) {
warning("Failed to initiate group call: MUC server not known.");
return;
@@ -320,11 +366,18 @@ 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;
- // end() will in the end cause a `peer_left` signal and removal from `peers`
- peers[jid].end(Xep.Jingle.ReasonElement.CANCEL, "Peer left the MUJI MUC");
+ 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");
+ peers.unset(jid);
});
+ if (group_call.peers_to_connect_to.size > 3) {
+ end("Call too full - P2p calls don't work well with many participants");
+ return;
+ }
+
// Call all peers that are in the room already
foreach (Jid peer_jid in group_call.peers_to_connect_to) {
// Don't establish connection if we have one already (the person that invited us to the call)
diff --git a/libdino/src/service/calls.vala b/libdino/src/service/calls.vala
index 741aa673..44790014 100644
--- a/libdino/src/service/calls.vala
+++ b/libdino/src/service/calls.vala
@@ -67,38 +67,24 @@ namespace Dino {
return call_state;
}
- public async bool can_do_audio_calls_async(Conversation conversation) {
- if (!can_do_audio_calls()) return false;
-
- if (conversation.type_ == Conversation.Type.CHAT) {
- return (yield get_call_resources(conversation.account, conversation.counterpart)).size > 0 || has_jmi_resources(conversation.counterpart);
- } else {
- return stream_interactor.get_module(MucManager.IDENTITY).is_private_room(conversation.account, conversation.counterpart);
- }
- }
-
- private bool can_do_audio_calls() {
+ public bool can_we_do_calls(Account account) {
Plugins.VideoCallPlugin? plugin = Application.get_default().plugin_registry.video_call_plugin;
if (plugin == null) return false;
- return plugin.supports("audio");
+ return plugin.supports(null);
}
- public async bool can_do_video_calls_async(Conversation conversation) {
- if (!can_do_video_calls()) return false;
-
+ public async bool can_conversation_do_calls(Conversation conversation) {
if (conversation.type_ == Conversation.Type.CHAT) {
return (yield get_call_resources(conversation.account, conversation.counterpart)).size > 0 || has_jmi_resources(conversation.counterpart);
} else {
- return stream_interactor.get_module(MucManager.IDENTITY).is_private_room(conversation.account, conversation.counterpart);
+ bool is_private = stream_interactor.get_module(MucManager.IDENTITY).is_private_room(conversation.account, conversation.counterpart);
+ return is_private && can_initiate_groupcall(conversation.account);
}
}
- private bool can_do_video_calls() {
- Plugins.VideoCallPlugin? plugin = Application.get_default().plugin_registry.video_call_plugin;
- if (plugin == null) return false;
-
- return plugin.supports("video");
+ public bool can_initiate_groupcall(Account account) {
+ return stream_interactor.get_module(MucManager.IDENTITY).default_muc_server[account] != null;
}
public async Gee.List<Jid> get_call_resources(Account account, Jid counterpart) {
@@ -107,7 +93,10 @@ namespace Dino {
XmppStream? stream = stream_interactor.get_stream(account);
if (stream == null) return ret;
- Gee.List<Jid>? full_jids = stream.get_flag(Presence.Flag.IDENTITY).get_resources(counterpart);
+ Presence.Flag? presence_flag = stream.get_flag(Presence.Flag.IDENTITY);
+ if (presence_flag == null) return ret;
+
+ Gee.List<Jid>? full_jids = presence_flag.get_resources(counterpart);
if (full_jids == null) return ret;
foreach (Jid full_jid in full_jids) {
@@ -148,11 +137,6 @@ namespace Dino {
}
private void on_incoming_call(Account account, Xep.Jingle.Session session) {
- if (!can_do_audio_calls()) {
- warning("Incoming call but no call support detected. Ignoring.");
- return;
- }
-
Jid? muji_muc = null;
bool counterpart_wants_video = false;
foreach (Xep.Jingle.Content content in session.contents) {
@@ -268,7 +252,7 @@ namespace Dino {
if (!call.account.equals(account)) return;
// We already know the call; this is a reflection of our own invite
- if (call_states[call].parent_muc.equals_bare(inviter_jid)) return;
+ if (call_states[call].parent_muc != null && call_states[call].parent_muc.equals_bare(inviter_jid)) return;
if (call.counterparts.contains(inviter_jid) && call_states[call].accepted) {
// A call is converted into a group call.
@@ -337,11 +321,6 @@ namespace Dino {
Xep.JingleMessageInitiation.Module mi_module = stream_interactor.module_manager.get_module(account, Xep.JingleMessageInitiation.Module.IDENTITY);
mi_module.session_proposed.connect((from, to, sid, descriptions) => {
- if (!can_do_audio_calls()) {
- warning("Incoming call but no call support detected. Ignoring.");
- return;
- }
-
bool audio_requested = descriptions.any_match((description) => description.ns_uri == Xep.JingleRtp.NS_URI && description.get_attribute("media") == "audio");
bool video_requested = descriptions.any_match((description) => description.ns_uri == Xep.JingleRtp.NS_URI && description.get_attribute("media") == "video");
if (!audio_requested && !video_requested) return;
diff --git a/libdino/src/service/notification_events.vala b/libdino/src/service/notification_events.vala
index 2408aadc..5b8db842 100644
--- a/libdino/src/service/notification_events.vala
+++ b/libdino/src/service/notification_events.vala
@@ -118,6 +118,7 @@ public class NotificationEvents : StreamInteractionModule, Object {
}
private async void on_call_incoming(Call call, CallState call_state, Conversation conversation, bool video) {
+ if (!stream_interactor.get_module(Calls.IDENTITY).can_we_do_calls(call.account)) return;
string conversation_display_name = get_conversation_display_name(stream_interactor, conversation, null);
NotificationProvider notifier = yield notifier.wait_async();
diff --git a/main/src/ui/call_window/call_window.vala b/main/src/ui/call_window/call_window.vala
index c610444f..cd490bf9 100644
--- a/main/src/ui/call_window/call_window.vala
+++ b/main/src/ui/call_window/call_window.vala
@@ -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, visible=false };
+ public Button invite_button = new Button.from_icon_name("dino-account-plus") { relief=ReliefStyle.NONE, visible=true };
private Widget? own_video = null;
private HashMap<string, ParticipantWidget> participant_widgets = new HashMap<string, ParticipantWidget>();
private ArrayList<string> participants = new ArrayList<string>();
@@ -191,7 +191,11 @@ namespace Dino.Ui {
} else if (reason_name == Xmpp.Xep.Jingle.ReasonElement.DECLINE || reason_name == Xmpp.Xep.Jingle.ReasonElement.BUSY) {
text = _("%s declined the call").printf(who_terminated);
} else {
- text = "The call has been terminated: " + (reason_name ?? "") + " " + (reason_text ?? "");
+ if (reason_text == null) {
+ text = "The call has been terminated" + " " + (reason_name ?? "");
+ } else {
+ text = reason_text + " " + (reason_name ?? "");
+ }
}
bottom_bar.show_counterpart_ended(text);
diff --git a/main/src/ui/call_window/call_window_controller.vala b/main/src/ui/call_window/call_window_controller.vala
index 34f3a910..ebf8774a 100644
--- a/main/src/ui/call_window/call_window_controller.vala
+++ b/main/src/ui/call_window/call_window_controller.vala
@@ -134,16 +134,23 @@ public class Dino.Ui.CallWindowController : Object {
Jid peer_jid = peer_state.jid;
peer_states[peer_id] = peer_state;
+ peer_state.stream_created.connect((media) => {
+ if (media == "audio") {
+ update_audio_device_choices();
+ } else if (media == "video") {
+ update_video_device_choices();
+ }
+ });
peer_state.connection_ready.connect(() => {
call_window.set_status(peer_state.internal_id, "");
if (participant_widgets.size == 1) {
// This is the first peer.
// If it can do MUJI, show invite button.
- call_window.invite_button_revealer.visible = true;
-// stream_interactor.get_module(EntityInfo.IDENTITY).has_feature.begin(call.account, peer_state.jid, Xep.Muji.NS_URI, (_, res) => {
-// bool has_feature = stream_interactor.get_module(EntityInfo.IDENTITY).has_feature.end(res);
-// call_window.invite_button_revealer.visible = has_feature;
-// });
+
+ call_state.can_convert_into_groupcall.begin((_, res) => {
+ bool can_convert = call_state.can_convert_into_groupcall.end(res);
+ call_window.invite_button_revealer.visible = can_convert;
+ });
call_plugin.devices_changed.connect((media, incoming) => {
if (media == "audio") update_audio_device_choices();
@@ -165,7 +172,7 @@ public class Dino.Ui.CallWindowController : Object {
if (!(participant_videos[peer_id] is Widget)) return;
Widget widget = (Widget) participant_videos[peer_id];
call_window.set_video(peer_id, widget);
- participant_videos[peer_id].display_stream(peer_state.get_video_stream(call), peer_jid);
+ participant_videos[peer_id].display_stream(peer_state.get_video_stream(), peer_jid);
}
});
peer_state.info_received.connect((session_info) => {
@@ -264,7 +271,7 @@ 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;
}
@@ -273,34 +280,31 @@ public class Dino.Ui.CallWindowController : Object {
update_current_audio_device(audio_settings_popover);
audio_settings_popover.microphone_selected.connect((device) => {
- call_plugin.set_device(calls.get_audio_stream(call), device);
+ call_state.set_audio_device(device);
update_current_audio_device(audio_settings_popover);
});
audio_settings_popover.speaker_selected.connect((device) => {
- call_plugin.set_device(calls.get_audio_stream(call), device);
+ call_state.set_audio_device(device);
update_current_audio_device(audio_settings_popover);
});
- calls.stream_created.connect((call, media) => {
- if (media == "audio") {
- update_current_audio_device(audio_settings_popover);
- }
- });*/
+// calls.stream_created.connect((call, media) => {
+// if (media == "audio") {
+// update_current_audio_device(audio_settings_popover);
+// }
+// });
}
- /*private void update_current_audio_device(AudioSettingsPopover audio_settings_popover) {
- Xmpp.Xep.JingleRtp.Stream stream = calls.get_audio_stream(call);
- if (stream != null) {
- audio_settings_popover.current_microphone_device = call_plugin.get_device(stream, false);
- audio_settings_popover.current_speaker_device = call_plugin.get_device(stream, true);
- }
- }*/
+ private void update_current_audio_device(AudioSettingsPopover audio_settings_popover) {
+ audio_settings_popover.current_microphone_device = call_state.get_microphone_device();
+ audio_settings_popover.current_speaker_device = call_state.get_speaker_device();
+ }
private void update_video_device_choices() {
int device_count = call_plugin.get_devices("video", false).size;
if (device_count == 0) {
call_window.bottom_bar.show_video_device_error();
- } /*else if (device_count == 1 || calls.get_video_stream(call) == null) {
+ } else if (device_count == 1 || call_state.get_video_device() == null) {
call_window.bottom_bar.show_video_device_choices(false);
return;
}
@@ -309,23 +313,20 @@ public class Dino.Ui.CallWindowController : Object {
update_current_video_device(video_settings_popover);
video_settings_popover.camera_selected.connect((device) => {
- call_plugin.set_device(calls.get_video_stream(call), device);
+ call_state.set_video_device(device);
update_current_video_device(video_settings_popover);
own_video.display_device(device);
});
- calls.stream_created.connect((call, media) => {
- if (media == "video") {
- update_current_video_device(video_settings_popover);
- }
- });*/
+// call_state.stream_created.connect((call, media) => {
+// if (media == "video") {
+// update_current_video_device(video_settings_popover);
+// }
+// });
}
- /*private void update_current_video_device(VideoSettingsPopover video_settings_popover) {
- Xmpp.Xep.JingleRtp.Stream stream = calls.get_video_stream(call);
- if (stream != null) {
- video_settings_popover.current_device = call_plugin.get_device(stream, false);
- }
- }*/
+ private void update_current_video_device(VideoSettingsPopover video_settings_popover) {
+ video_settings_popover.current_device = call_state.get_video_device();
+ }
private void update_own_video() {
if (this.call_window.bottom_bar.video_enabled) {
diff --git a/main/src/ui/conversation_content_view/call_widget.vala b/main/src/ui/conversation_content_view/call_widget.vala
index a2c8c0c2..d9060872 100644
--- a/main/src/ui/conversation_content_view/call_widget.vala
+++ b/main/src/ui/conversation_content_view/call_widget.vala
@@ -111,17 +111,30 @@ namespace Dino.Ui {
incoming_call_revealer.get_style_context().remove_class("incoming");
outer_additional_box.get_style_context().remove_class("incoming-call-box");
- switch (call.state) {
+ // It doesn't make sense to display MUC calls as missed or declined by the whole MUC. Just display as ended.
+ // TODO: maybe not let them be missed/declined in first place.
+ Call.State relevant_state = call.state;
+ if (conversation.type_ == Conversation.Type.GROUPCHAT && call.direction == Call.DIRECTION_OUTGOING &&
+ (relevant_state == Call.State.MISSED || relevant_state == Call.State.DECLINED)) {
+ relevant_state = Call.State.ENDED;
+ }
+
+ switch (relevant_state) {
case Call.State.RINGING:
image.set_from_icon_name("dino-phone-ring-symbolic", IconSize.LARGE_TOOLBAR);
if (call.direction == Call.DIRECTION_INCOMING) {
bool video = call_manager.should_we_send_video();
title_label.label = video ? _("Incoming video call") : _("Incoming call");
- subtitle_label.label = "Ring ring…!";
- incoming_call_box.visible = true;
- incoming_call_revealer.reveal_child = true;
- incoming_call_revealer.get_style_context().add_class("incoming");
- outer_additional_box.get_style_context().add_class("incoming-call-box");
+
+ if (stream_interactor.get_module(Calls.IDENTITY).can_we_do_calls(call.account)) {
+ subtitle_label.label = "Ring ring…!";
+ incoming_call_box.visible = true;
+ incoming_call_revealer.reveal_child = true;
+ incoming_call_revealer.get_style_context().add_class("incoming");
+ outer_additional_box.get_style_context().add_class("incoming-call-box");
+ } else {
+ subtitle_label.label = "Dependencies for call support not met";
+ }
} else {
title_label.label = _("Calling…");
subtitle_label.label = "Ring ring…?";
diff --git a/main/src/ui/conversation_titlebar/call_entry.vala b/main/src/ui/conversation_titlebar/call_entry.vala
index 3b3a5b39..e5b7281c 100644
--- a/main/src/ui/conversation_titlebar/call_entry.vala
+++ b/main/src/ui/conversation_titlebar/call_entry.vala
@@ -115,17 +115,11 @@ namespace Dino.Ui {
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;
- } else {
- visible = false;
- }
+ Conversation conv_bak = conversation;
+ bool can_do_calls = yield stream_interactor.get_module(Calls.IDENTITY).can_conversation_do_calls(conversation);
+ if (conv_bak != conversation) return;
+
+ visible = video_button.visible = can_do_calls;
}
public new void unset_conversation() { }
diff --git a/plugins/omemo/src/dtls_srtp_verification_draft.vala b/plugins/omemo/src/dtls_srtp_verification_draft.vala
index 0cb08696..577c77e7 100644
--- a/plugins/omemo/src/dtls_srtp_verification_draft.vala
+++ b/plugins/omemo/src/dtls_srtp_verification_draft.vala
@@ -101,11 +101,18 @@ namespace Dino.Plugins.Omemo.DtlsSrtpVerificationDraft {
if (fingerprint_node == null) continue;
string fingerprint = fingerprint_node.get_deep_string_content();
- Xep.Omemo.OmemoEncryptor encryptor = stream.get_module(Xep.Omemo.OmemoEncryptor.IDENTITY);
- Xep.Omemo.EncryptionData enc_data = encryptor.encrypt_plaintext(fingerprint);
- encryptor.encrypt_key(enc_data, iq.to.bare_jid, device_id_by_jingle_sid[sid]);
+ StanzaNode? encrypted_node = null;
+ try {
+ Xep.Omemo.OmemoEncryptor encryptor = stream.get_module(Xep.Omemo.OmemoEncryptor.IDENTITY);
+ Xep.Omemo.EncryptionData enc_data = encryptor.encrypt_plaintext(fingerprint);
+ encryptor.encrypt_key(enc_data, iq.to.bare_jid, device_id_by_jingle_sid[sid]);
+ encrypted_node = enc_data.get_encrypted_node();
+ } catch (Error e) {
+ warning("Error while OMEMO-encrypting call keys: %s", e.message);
+ return;
+ }
- StanzaNode new_fingerprint_node = new StanzaNode.build("fingerprint", NS_URI).add_self_xmlns().put_node(enc_data.get_encrypted_node());
+ StanzaNode new_fingerprint_node = new StanzaNode.build("fingerprint", NS_URI).add_self_xmlns().put_node(encrypted_node);
string? hash_attr = fingerprint_node.get_attribute("hash", Xep.JingleIceUdp.DTLS_NS_URI);
string? setup_attr = fingerprint_node.get_attribute("setup", Xep.JingleIceUdp.DTLS_NS_URI);
if (hash_attr != null) new_fingerprint_node.put_attribute("hash", hash_attr);
diff --git a/plugins/rtp/src/plugin.vala b/plugins/rtp/src/plugin.vala
index 6d6da79a..0e519b37 100644
--- a/plugins/rtp/src/plugin.vala
+++ b/plugins/rtp/src/plugin.vala
@@ -285,7 +285,7 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object {
Gst.deinit();
}
- public bool supports(string media) {
+ public bool supports(string? media) {
if (!codec_util.is_element_supported("rtpbin")) return false;
if (media == "audio") {
@@ -310,6 +310,7 @@ public class Dino.Plugins.Rtp.Plugin : RootInterface, VideoCallPlugin, Object {
}
public Gee.List<MediaDevice> get_devices(string media, bool incoming) {
+
if (media == "video" && !incoming) {
return get_video_sources();
}
diff --git a/xmpp-vala/src/module/xep/0045_muc/module.vala b/xmpp-vala/src/module/xep/0045_muc/module.vala
index e8711742..9969f507 100644
--- a/xmpp-vala/src/module/xep/0045_muc/module.vala
+++ b/xmpp-vala/src/module/xep/0045_muc/module.vala
@@ -218,7 +218,10 @@ public class Module : XmppStreamModule {
public async void change_affiliation(XmppStream stream, Jid muc_jid, Jid? user_jid, string? nick, string new_affiliation) {
StanzaNode item_node = new StanzaNode.build("item", NS_URI_ADMIN)
.put_attribute("affiliation", new_affiliation, NS_URI_ADMIN);
- if (user_jid != null) item_node.put_attribute("jid", user_jid.to_string(), NS_URI_ADMIN);
+ if (user_jid != null) {
+ // Some servers don't allow full JIDs and reply error:modify - jid-malformed - "Bare JID expected, got full JID". Make them bare JIDs.
+ item_node.put_attribute("jid", user_jid.bare_jid.to_string(), NS_URI_ADMIN);
+ }
if (nick != null) item_node.put_attribute("nick", nick, NS_URI_ADMIN);
StanzaNode query = new StanzaNode.build("query", NS_URI_ADMIN).add_self_xmlns().put_node(item_node);
diff --git a/xmpp-vala/src/module/xep/0272_muji.vala b/xmpp-vala/src/module/xep/0272_muji.vala
index b602d94c..f8b45e25 100644
--- a/xmpp-vala/src/module/xep/0272_muji.vala
+++ b/xmpp-vala/src/module/xep/0272_muji.vala
@@ -237,11 +237,14 @@ namespace Xmpp.Xep.Muji {
public override void attach(XmppStream stream) {
stream.add_flag(new Flag());
+ stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI);
stream.get_module(Presence.Module.IDENTITY).received_available.connect(on_received_available);
stream.get_module(Presence.Module.IDENTITY).received_unavailable.connect(on_received_unavailable);
}
- public override void detach(XmppStream stream) { }
+ public override void detach(XmppStream stream) {
+ stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature(stream, NS_URI);
+ }
public override string get_ns() {
return NS_URI;
diff --git a/xmpp-vala/src/module/xep/muji_meta.vala b/xmpp-vala/src/module/xep/muji_meta.vala
index 89a0e8de..fa161f28 100644
--- a/xmpp-vala/src/module/xep/muji_meta.vala
+++ b/xmpp-vala/src/module/xep/muji_meta.vala
@@ -11,7 +11,7 @@ namespace Xmpp.Xep.MujiMeta {
public signal void call_accepted(Jid from, Jid muc_jid, string message_type);
public signal void call_rejected(Jid from, Jid to, Jid muc_jid, string message_type);
- public void send_invite(XmppStream stream, Jid invitee, Jid muc_jid, bool video, string? message_type = null) {
+ public void send_invite(XmppStream stream, Jid invitee, Jid muc_jid, bool video, string message_type) {
var invite_node = new StanzaNode.build("propose", NS_URI).put_attribute("muc", muc_jid.to_string());
invite_node.put_node(new StanzaNode.build("description", Xep.JingleRtp.NS_URI).add_self_xmlns().put_attribute("media", "audio"));
if (video) {
@@ -23,27 +23,27 @@ namespace Xmpp.Xep.MujiMeta {
stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, invite_message);
}
- public void send_invite_retract_to_peer(XmppStream stream, Jid invitee, Jid muc_jid, string? message_type = null) {
+ public void send_invite_retract_to_peer(XmppStream stream, Jid invitee, Jid muc_jid, string message_type) {
send_jmi_message(stream, "retract", invitee, muc_jid, message_type);
}
- public void send_invite_accept_to_peer(XmppStream stream, Jid invitor, Jid muc_jid, string? message_type = null) {
+ public void send_invite_accept_to_peer(XmppStream stream, Jid invitor, Jid muc_jid, string message_type) {
send_jmi_message(stream, "accept", invitor, muc_jid, message_type);
}
public void send_invite_accept_to_self(XmppStream stream, Jid muc_jid) {
- send_jmi_message(stream, "accept", Bind.Flag.get_my_jid(stream).bare_jid, muc_jid);
+ send_jmi_message(stream, "accept", Bind.Flag.get_my_jid(stream).bare_jid, muc_jid, MessageStanza.TYPE_CHAT);
}
- public void send_invite_reject_to_peer(XmppStream stream, Jid invitor, Jid muc_jid, string? message_type = null) {
+ public void send_invite_reject_to_peer(XmppStream stream, Jid invitor, Jid muc_jid, string message_type) {
send_jmi_message(stream, "reject", invitor, muc_jid, message_type);
}
public void send_invite_reject_to_self(XmppStream stream, Jid muc_jid) {
- send_jmi_message(stream, "reject", Bind.Flag.get_my_jid(stream).bare_jid, muc_jid);
+ send_jmi_message(stream, "reject", Bind.Flag.get_my_jid(stream).bare_jid, muc_jid, MessageStanza.TYPE_CHAT);
}
- private void send_jmi_message(XmppStream stream, string name, Jid to, Jid muc, string? message_type = null) {
+ private void send_jmi_message(XmppStream stream, string name, Jid to, Jid muc, string message_type) {
var jmi_node = new StanzaNode.build(name, NS_URI).add_self_xmlns().put_attribute("muc", muc.to_string());
var muji_node = new StanzaNode.build("muji", NS_URI).add_self_xmlns().put_node(jmi_node);