From 4ef50db3e581016365087759d5af8649e37ab8a7 Mon Sep 17 00:00:00 2001 From: fiaxh Date: Wed, 2 Feb 2022 21:37:05 +0100 Subject: [PATCH] Various call UI/UX improvements --- libdino/src/plugin/interfaces.vala | 2 +- libdino/src/service/call_peer_state.vala | 11 ++- libdino/src/service/call_state.vala | 79 ++++++++++++++++--- libdino/src/service/calls.vala | 45 +++-------- libdino/src/service/notification_events.vala | 1 + main/src/ui/call_window/call_window.vala | 8 +- .../call_window/call_window_controller.vala | 69 ++++++++-------- .../call_widget.vala | 25 ++++-- .../ui/conversation_titlebar/call_entry.vala | 14 +--- .../src/dtls_srtp_verification_draft.vala | 15 +++- plugins/rtp/src/plugin.vala | 3 +- xmpp-vala/src/module/xep/0045_muc/module.vala | 5 +- xmpp-vala/src/module/xep/0272_muji.vala | 5 +- xmpp-vala/src/module/xep/muji_meta.vala | 14 ++-- 14 files changed, 179 insertions(+), 117 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(); 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 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? 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? 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 participant_widgets = new HashMap(); private ArrayList participants = new ArrayList(); @@ -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; + 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 = audio_works; - video_button.visible = video_works; - } else { - visible = false; - } + 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 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);