Add (disabled) multi-party call UI
This commit is contained in:
parent
26d10d1dcb
commit
3dc4d7f155
|
@ -16,6 +16,7 @@ find_packages(MAIN_PACKAGES REQUIRED
|
||||||
)
|
)
|
||||||
|
|
||||||
set(RESOURCE_LIST
|
set(RESOURCE_LIST
|
||||||
|
icons/dino-account-plus-symbolic.svg
|
||||||
icons/dino-changes-allowed-symbolic.svg
|
icons/dino-changes-allowed-symbolic.svg
|
||||||
icons/dino-changes-prevent-symbolic.svg
|
icons/dino-changes-prevent-symbolic.svg
|
||||||
icons/dino-conversation-list-placeholder-arrow.svg
|
icons/dino-conversation-list-placeholder-arrow.svg
|
||||||
|
@ -139,9 +140,11 @@ SOURCES
|
||||||
|
|
||||||
src/ui/call_window/audio_settings_popover.vala
|
src/ui/call_window/audio_settings_popover.vala
|
||||||
src/ui/call_window/call_bottom_bar.vala
|
src/ui/call_window/call_bottom_bar.vala
|
||||||
|
src/ui/call_window/call_connection_details_window.vala
|
||||||
src/ui/call_window/call_encryption_button.vala
|
src/ui/call_window/call_encryption_button.vala
|
||||||
src/ui/call_window/call_window.vala
|
src/ui/call_window/call_window.vala
|
||||||
src/ui/call_window/call_window_controller.vala
|
src/ui/call_window/call_window_controller.vala
|
||||||
|
src/ui/call_window/participant_widget.vala
|
||||||
src/ui/call_window/video_settings_popover.vala
|
src/ui/call_window/video_settings_popover.vala
|
||||||
|
|
||||||
src/ui/chat_input/chat_input_controller.vala
|
src/ui/chat_input/chat_input_controller.vala
|
||||||
|
|
|
@ -66,19 +66,15 @@
|
||||||
<property name="reveal-child">True</property>
|
<property name="reveal-child">True</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkBox" id="outer_additional_box">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<style>
|
|
||||||
<class name="incoming-call-box"/>
|
|
||||||
</style>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkBox" id="incoming_call_box">
|
||||||
<property name="halign">end</property>
|
<property name="halign">end</property>
|
||||||
<property name="orientation">horizontal</property>
|
<property name="orientation">horizontal</property>
|
||||||
<property name="spacing">5</property>
|
<property name="spacing">5</property>
|
||||||
<property name="margin">10</property>
|
<property name="margin">10</property>
|
||||||
<property name="hexpand">True</property>
|
<property name="hexpand">True</property>
|
||||||
<property name="visible">True</property>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="reject_call_button">
|
<object class="GtkButton" id="reject_call_button">
|
||||||
<property name="label" translatable="yes">Reject</property>
|
<property name="label" translatable="yes">Reject</property>
|
||||||
|
@ -99,6 +95,13 @@
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="multiparty_peer_box">
|
||||||
|
<property name="margin">10</property>
|
||||||
|
<property name="spacing">5</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
1
main/data/icons/dino-account-plus-symbolic.svg
Normal file
1
main/data/icons/dino-account-plus-symbolic.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M15,14C12.33,14 7,15.33 7,18V20H23V18C23,15.33 17.67,14 15,14M6,10V7H4V10H1V12H4V15H6V12H9V10M15,12A4,4 0 0,0 19,8A4,4 0 0,0 15,4A4,4 0 0,0 11,8A4,4 0 0,0 15,12Z" /></svg>
|
After Width: | Height: | Size: 456 B |
|
@ -114,6 +114,11 @@ window.dino-main .incoming-call-box {
|
||||||
background: alpha(@theme_selected_bg_color, 0.1);
|
background: alpha(@theme_selected_bg_color, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.dino-main .multiparty-participants {
|
||||||
|
border-top: 1px solid alpha(@theme_fg_color, 0.05);
|
||||||
|
background: alpha(@theme_fg_color, 0.04)
|
||||||
|
}
|
||||||
|
|
||||||
window.dino-main .dino-sidebar > frame.collapsed {
|
window.dino-main .dino-sidebar > frame.collapsed {
|
||||||
border-bottom: 1px solid @borders;
|
border-bottom: 1px solid @borders;
|
||||||
}
|
}
|
||||||
|
@ -280,20 +285,27 @@ box.dino-input-error label.input-status-highlight-once {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dino-call-window .encryption-box {
|
.dino-call-window .black-element {
|
||||||
color: rgba(255,255,255,0.7);
|
color: rgba(255,255,255,0.7);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
background: rgba(0,0,0,0.5);
|
background: rgba(0,0,0,0.5);
|
||||||
padding: 0px;
|
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dino-call-window .encryption-box.unencrypted {
|
.dino-call-window label.black-element {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dino-call-window button.black-element {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dino-call-window button.black-element.unencrypted {
|
||||||
color: @error_color;
|
color: @error_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dino-call-window .encryption-box:hover {
|
.dino-call-window .black-element:hover {
|
||||||
background: rgba(20,20,20,0.5);
|
background: rgba(20,20,20,0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,22 +315,46 @@ box.dino-input-error label.input-status-highlight-once {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dino-call-window .call-header-bar,
|
.dino-call-window .call-header-bar {
|
||||||
.dino-call-window .call-header-bar image {
|
|
||||||
color: #ededec;
|
color: #ededec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dino-call-window .call-header-bar button image {
|
||||||
|
color: alpha(white, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dino-call-window .call-header-bar button:hover image {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dino-call-window .participant-title-button {
|
||||||
|
background: none;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
.dino-call-window .call-bottom-bar {
|
.dino-call-window .call-bottom-bar {
|
||||||
background: linear-gradient(rgba(0,0,0,0), rgba(0,0,0,0.3));
|
background: linear-gradient(rgba(0,0,0,0), rgba(0,0,0,0.3));
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dino-call-window .video-placeholder-box {
|
.dino-call-window {
|
||||||
background-color: #212121;
|
background-color: #212121;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dino-call-window .participant-name {
|
||||||
|
color: white;
|
||||||
|
text-shadow: black;
|
||||||
|
}
|
||||||
|
|
||||||
.dino-call-window .text-no-controls {
|
.dino-call-window .text-no-controls {
|
||||||
|
color: black;
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dino-call-window .own-video {
|
||||||
|
box-shadow: 0 0 2px 0 rgba(0,0,0,0.5);
|
||||||
|
}
|
|
@ -209,18 +209,24 @@ public class Dino.Ui.Application : Gtk.Application, Dino.Application {
|
||||||
SimpleAction accept_call_action = new SimpleAction("accept-call", VariantType.INT32);
|
SimpleAction accept_call_action = new SimpleAction("accept-call", VariantType.INT32);
|
||||||
accept_call_action.activate.connect((variant) => {
|
accept_call_action.activate.connect((variant) => {
|
||||||
Call? call = stream_interactor.get_module(CallStore.IDENTITY).get_call_by_id(variant.get_int32());
|
Call? call = stream_interactor.get_module(CallStore.IDENTITY).get_call_by_id(variant.get_int32());
|
||||||
stream_interactor.get_module(Calls.IDENTITY).accept_call(call);
|
CallState? call_state = stream_interactor.get_module(Calls.IDENTITY).call_states[call];
|
||||||
|
if (call_state == null) return;
|
||||||
|
|
||||||
|
call_state.accept();
|
||||||
|
|
||||||
var call_window = new CallWindow();
|
var call_window = new CallWindow();
|
||||||
call_window.controller = new CallWindowController(call_window, call, stream_interactor);
|
call_window.controller = new CallWindowController(call_window, call_state, stream_interactor);
|
||||||
call_window.present();
|
call_window.present();
|
||||||
});
|
});
|
||||||
add_action(accept_call_action);
|
add_action(accept_call_action);
|
||||||
|
|
||||||
SimpleAction deny_call_action = new SimpleAction("deny-call", VariantType.INT32);
|
SimpleAction deny_call_action = new SimpleAction("reject-call", VariantType.INT32);
|
||||||
deny_call_action.activate.connect((variant) => {
|
deny_call_action.activate.connect((variant) => {
|
||||||
Call? call = stream_interactor.get_module(CallStore.IDENTITY).get_call_by_id(variant.get_int32());
|
Call? call = stream_interactor.get_module(CallStore.IDENTITY).get_call_by_id(variant.get_int32());
|
||||||
stream_interactor.get_module(Calls.IDENTITY).reject_call(call);
|
CallState? call_state = stream_interactor.get_module(Calls.IDENTITY).call_states[call];
|
||||||
|
if (call_state == null) return;
|
||||||
|
|
||||||
|
call_state.reject();
|
||||||
});
|
});
|
||||||
add_action(deny_call_action);
|
add_action(deny_call_action);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ public class AvatarImage : Misc {
|
||||||
public int height { get; set; default = 35; }
|
public int height { get; set; default = 35; }
|
||||||
public int width { get; set; default = 35; }
|
public int width { get; set; default = 35; }
|
||||||
public bool allow_gray { get; set; default = true; }
|
public bool allow_gray { get; set; default = true; }
|
||||||
|
public bool force_gray { get; set; default = false; }
|
||||||
public StreamInteractor? stream_interactor { get; set; }
|
public StreamInteractor? stream_interactor { get; set; }
|
||||||
public AvatarManager? avatar_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(AvatarManager.IDENTITY); } }
|
public AvatarManager? avatar_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(AvatarManager.IDENTITY); } }
|
||||||
public MucManager? muc_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(MucManager.IDENTITY); } }
|
public MucManager? muc_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(MucManager.IDENTITY); } }
|
||||||
|
@ -78,14 +79,14 @@ public class AvatarImage : Misc {
|
||||||
string user_color = Util.get_avatar_hex_color(stream_interactor, account, conversation.counterpart, conversation);
|
string user_color = Util.get_avatar_hex_color(stream_interactor, account, conversation.counterpart, conversation);
|
||||||
if (avatar_manager.has_avatar_cached(account, conversation.counterpart)) {
|
if (avatar_manager.has_avatar_cached(account, conversation.counterpart)) {
|
||||||
drawer = new AvatarDrawer().tile(avatar_manager.get_cached_avatar(account, conversation.counterpart), "#", user_color);
|
drawer = new AvatarDrawer().tile(avatar_manager.get_cached_avatar(account, conversation.counterpart), "#", user_color);
|
||||||
if (allow_gray && (!is_self_online() || !is_counterpart_online())) drawer.grayscale();
|
if (force_gray || allow_gray && (!is_self_online() || !is_counterpart_online())) drawer.grayscale();
|
||||||
} else {
|
} else {
|
||||||
Gee.List<Jid>? occupants = muc_manager.get_other_offline_members(conversation.counterpart, account);
|
Gee.List<Jid>? occupants = muc_manager.get_other_offline_members(conversation.counterpart, account);
|
||||||
if (muc_manager.is_private_room(account, conversation.counterpart) && occupants != null && occupants.size > 0) {
|
if (muc_manager.is_private_room(account, conversation.counterpart) && occupants != null && occupants.size > 0) {
|
||||||
jids = occupants.to_array();
|
jids = occupants.to_array();
|
||||||
} else {
|
} else {
|
||||||
drawer = new AvatarDrawer().tile(null, "#", user_color);
|
drawer = new AvatarDrawer().tile(null, "#", user_color);
|
||||||
if (allow_gray && (!is_self_online() || !is_counterpart_online())) drawer.grayscale();
|
if (force_gray || allow_gray && (!is_self_online() || !is_counterpart_online())) drawer.grayscale();
|
||||||
}
|
}
|
||||||
try_load_avatar_async(conversation.counterpart);
|
try_load_avatar_async(conversation.counterpart);
|
||||||
}
|
}
|
||||||
|
@ -116,7 +117,7 @@ public class AvatarImage : Misc {
|
||||||
if (jids.length > 4) {
|
if (jids.length > 4) {
|
||||||
drawer.plus();
|
drawer.plus();
|
||||||
}
|
}
|
||||||
if (allow_gray && (!is_self_online() || !is_counterpart_online())) drawer.grayscale();
|
if (force_gray || allow_gray && (!is_self_online() || !is_counterpart_online())) drawer.grayscale();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -25,17 +25,12 @@ public class Dino.Ui.CallBottomBar : Gtk.Box {
|
||||||
private MenuButton video_settings_button = new MenuButton() { halign=Align.END, valign=Align.END };
|
private MenuButton video_settings_button = new MenuButton() { halign=Align.END, valign=Align.END };
|
||||||
public VideoSettingsPopover? video_settings_popover;
|
public VideoSettingsPopover? video_settings_popover;
|
||||||
|
|
||||||
public CallEntryptionButton encryption_button = new CallEntryptionButton() { relief=ReliefStyle.NONE, height_request=30, width_request=30, margin_start=20, margin_bottom=25, halign=Align.START, valign=Align.END };
|
|
||||||
|
|
||||||
private Label label = new Label("") { margin=20, halign=Align.CENTER, valign=Align.CENTER, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, hexpand=true, visible=true };
|
private Label label = new Label("") { margin=20, halign=Align.CENTER, valign=Align.CENTER, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, hexpand=true, visible=true };
|
||||||
private Stack stack = new Stack() { visible=true };
|
private Stack stack = new Stack() { visible=true };
|
||||||
|
|
||||||
public CallBottomBar() {
|
public CallBottomBar() {
|
||||||
Object(orientation:Orientation.HORIZONTAL, spacing:0);
|
Object(orientation:Orientation.HORIZONTAL, spacing:0);
|
||||||
|
|
||||||
Overlay default_control = new Overlay() { visible=true };
|
|
||||||
default_control.add_overlay(encryption_button);
|
|
||||||
|
|
||||||
Box main_buttons = new Box(Orientation.HORIZONTAL, 20) { margin_start=40, margin_end=40, margin=20, halign=Align.CENTER, hexpand=true, visible=true };
|
Box main_buttons = new Box(Orientation.HORIZONTAL, 20) { margin_start=40, margin_end=40, margin=20, halign=Align.CENTER, hexpand=true, visible=true };
|
||||||
|
|
||||||
audio_button.add(audio_image);
|
audio_button.add(audio_image);
|
||||||
|
@ -66,11 +61,9 @@ public class Dino.Ui.CallBottomBar : Gtk.Box {
|
||||||
button_hang.clicked.connect(() => hang_up());
|
button_hang.clicked.connect(() => hang_up());
|
||||||
main_buttons.add(button_hang);
|
main_buttons.add(button_hang);
|
||||||
|
|
||||||
default_control.add(main_buttons);
|
|
||||||
|
|
||||||
label.get_style_context().add_class("text-no-controls");
|
label.get_style_context().add_class("text-no-controls");
|
||||||
|
|
||||||
stack.add_named(default_control, "control-buttons");
|
stack.add_named(main_buttons, "control-buttons");
|
||||||
stack.add_named(label, "label");
|
stack.add_named(label, "label");
|
||||||
this.add(stack);
|
this.add(stack);
|
||||||
|
|
||||||
|
@ -159,6 +152,6 @@ public class Dino.Ui.CallBottomBar : Gtk.Box {
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool is_menu_active() {
|
public bool is_menu_active() {
|
||||||
return video_settings_button.active || audio_settings_button.active || encryption_button.active;
|
return video_settings_button.active || audio_settings_button.active;
|
||||||
}
|
}
|
||||||
}
|
}
|
84
main/src/ui/call_window/call_connection_details_window.vala
Normal file
84
main/src/ui/call_window/call_connection_details_window.vala
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
using Gtk;
|
||||||
|
|
||||||
|
namespace Dino.Ui {
|
||||||
|
|
||||||
|
public class CallConnectionDetailsWindow : Gtk.Window {
|
||||||
|
|
||||||
|
public Grid grid = new Grid() { column_spacing=5, margin=10, halign=Align.CENTER, valign=Align.CENTER, visible=true };
|
||||||
|
|
||||||
|
public Label audio_rtp_ready = new Label("?") { xalign=0, visible=true };
|
||||||
|
public Label audio_rtcp_ready = new Label("?") { xalign=0, visible=true };
|
||||||
|
public Label audio_sent_bps = new Label("?") { xalign=0, visible=true };
|
||||||
|
public Label audio_recv_bps = new Label("?") { xalign=0, visible=true };
|
||||||
|
public Label audio_codec = new Label("?") { xalign=0, visible=true };
|
||||||
|
|
||||||
|
public Label video_rtp_ready = new Label("") { xalign=0, visible=true };
|
||||||
|
public Label video_rtcp_ready = new Label("") { xalign=0, visible=true };
|
||||||
|
public Label video_sent_bps = new Label("") { xalign=0, visible=true };
|
||||||
|
public Label video_recv_bps = new Label("") { xalign=0, visible=true };
|
||||||
|
public Label video_codec = new Label("") { xalign=0, visible=true };
|
||||||
|
|
||||||
|
private int row_at = 0;
|
||||||
|
private bool video_added = false;
|
||||||
|
private PeerInfo? prev_peer_info = null;
|
||||||
|
|
||||||
|
public CallConnectionDetailsWindow() {
|
||||||
|
grid.attach(new Label("<b>Audio</b>") { use_markup=true, xalign=0, visible=true }, 0, row_at++, 1, 1);
|
||||||
|
put_row("RTP");
|
||||||
|
grid.attach(audio_rtp_ready, 1, row_at++, 1, 1);
|
||||||
|
put_row("RTCP");
|
||||||
|
grid.attach(audio_rtcp_ready, 1, row_at++, 1, 1);
|
||||||
|
put_row("Sent bp/s");
|
||||||
|
grid.attach(audio_sent_bps, 1, row_at++, 1, 1);
|
||||||
|
put_row("Received bp/s");
|
||||||
|
grid.attach(audio_recv_bps, 1, row_at++, 1, 1);
|
||||||
|
put_row("Codec");
|
||||||
|
grid.attach(audio_codec, 1, row_at++, 1, 1);
|
||||||
|
|
||||||
|
this.child = grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void put_row(string label) {
|
||||||
|
grid.attach(new Label(label) { xalign=0, visible=true }, 0, row_at, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update_content(PeerInfo peer_info) {
|
||||||
|
audio_rtp_ready.label = peer_info.audio_rtp_ready.to_string();
|
||||||
|
audio_rtcp_ready.label = peer_info.audio_rtcp_ready.to_string();
|
||||||
|
audio_codec.label = peer_info.audio_codec + " " + peer_info.audio_clockrate.to_string();
|
||||||
|
|
||||||
|
video_rtp_ready.label = peer_info.video_rtp_ready.to_string();
|
||||||
|
video_rtcp_ready.label = peer_info.video_rtcp_ready.to_string();
|
||||||
|
video_codec.label = peer_info.video_codec;
|
||||||
|
|
||||||
|
if (peer_info.video_content_exists) add_video_widgets();
|
||||||
|
|
||||||
|
if (prev_peer_info != null) {
|
||||||
|
audio_sent_bps.label = (peer_info.audio_bytes_sent - prev_peer_info.audio_bytes_sent).to_string();
|
||||||
|
audio_recv_bps.label = (peer_info.audio_bytes_received - prev_peer_info.audio_bytes_received).to_string();
|
||||||
|
video_sent_bps.label = (peer_info.video_bytes_sent - prev_peer_info.video_bytes_sent).to_string();
|
||||||
|
video_recv_bps.label = (peer_info.video_bytes_received - prev_peer_info.video_bytes_received).to_string();
|
||||||
|
}
|
||||||
|
prev_peer_info = peer_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add_video_widgets() {
|
||||||
|
if (video_added) return;
|
||||||
|
|
||||||
|
grid.attach(new Label("<b>Video</b>") { use_markup=true, xalign=0, visible=true }, 0, row_at++, 1, 1);
|
||||||
|
put_row("RTP");
|
||||||
|
grid.attach(video_rtp_ready, 1, row_at++, 1, 1);
|
||||||
|
put_row("RTCP");
|
||||||
|
grid.attach(video_rtcp_ready, 1, row_at++, 1, 1);
|
||||||
|
put_row("Sent bp/s");
|
||||||
|
grid.attach(video_sent_bps, 1, row_at++, 1, 1);
|
||||||
|
put_row("Received bp/s");
|
||||||
|
grid.attach(video_recv_bps, 1, row_at++, 1, 1);
|
||||||
|
put_row("Codec");
|
||||||
|
grid.attach(video_codec, 1, row_at++, 1, 1);
|
||||||
|
|
||||||
|
video_added = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,19 +2,21 @@ using Dino.Entities;
|
||||||
using Gtk;
|
using Gtk;
|
||||||
using Pango;
|
using Pango;
|
||||||
|
|
||||||
public class Dino.Ui.CallEntryptionButton : MenuButton {
|
public class Dino.Ui.CallEncryptionButton : MenuButton {
|
||||||
|
|
||||||
private Image encryption_image = new Image.from_icon_name("changes-allow-symbolic", IconSize.BUTTON) { visible=true };
|
private Image encryption_image = new Image.from_icon_name("", IconSize.BUTTON) { visible=true };
|
||||||
|
private bool has_been_set = false;
|
||||||
|
public bool controls_active { get; set; default=false; }
|
||||||
|
|
||||||
construct {
|
public CallEncryptionButton() {
|
||||||
|
this.opacity = 0;
|
||||||
add(encryption_image);
|
add(encryption_image);
|
||||||
get_style_context().add_class("encryption-box");
|
|
||||||
this.set_popover(popover);
|
this.set_popover(popover);
|
||||||
|
|
||||||
|
this.notify["controls-active"].connect(update_opacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_icon(bool encrypted, string? icon_name) {
|
public void set_icon(bool encrypted, string? icon_name) {
|
||||||
this.visible = true;
|
|
||||||
|
|
||||||
if (encrypted) {
|
if (encrypted) {
|
||||||
encryption_image.set_from_icon_name(icon_name ?? "changes-prevent-symbolic", IconSize.BUTTON);
|
encryption_image.set_from_icon_name(icon_name ?? "changes-prevent-symbolic", IconSize.BUTTON);
|
||||||
get_style_context().remove_class("unencrypted");
|
get_style_context().remove_class("unencrypted");
|
||||||
|
@ -22,6 +24,8 @@ public class Dino.Ui.CallEntryptionButton : MenuButton {
|
||||||
encryption_image.set_from_icon_name(icon_name ?? "changes-allow-symbolic", IconSize.BUTTON);
|
encryption_image.set_from_icon_name(icon_name ?? "changes-allow-symbolic", IconSize.BUTTON);
|
||||||
get_style_context().add_class("unencrypted");
|
get_style_context().add_class("unencrypted");
|
||||||
}
|
}
|
||||||
|
has_been_set = true;
|
||||||
|
update_opacity();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_info(string? title, bool show_keys, Xmpp.Xep.Jingle.ContentEncryption? audio_encryption, Xmpp.Xep.Jingle.ContentEncryption? video_encryption) {
|
public void set_info(string? title, bool show_keys, Xmpp.Xep.Jingle.ContentEncryption? audio_encryption, Xmpp.Xep.Jingle.ContentEncryption? video_encryption) {
|
||||||
|
@ -51,6 +55,10 @@ public class Dino.Ui.CallEntryptionButton : MenuButton {
|
||||||
popover.add(box);
|
popover.add(box);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void update_opacity() {
|
||||||
|
this.opacity = controls_active && has_been_set ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
private Grid create_media_encryption_grid(Xmpp.Xep.Jingle.ContentEncryption? encryption) {
|
private Grid create_media_encryption_grid(Xmpp.Xep.Jingle.ContentEncryption? encryption) {
|
||||||
Grid ret = new Grid() { row_spacing=3, column_spacing=5, visible=true };
|
Grid ret = new Grid() { row_spacing=3, column_spacing=5, visible=true };
|
||||||
if (encryption.peer_key.length > 0) {
|
if (encryption.peer_key.length > 0) {
|
||||||
|
|
|
@ -1,87 +1,155 @@
|
||||||
|
using Gee;
|
||||||
|
using Xmpp;
|
||||||
using Dino.Entities;
|
using Dino.Entities;
|
||||||
using Gtk;
|
using Gtk;
|
||||||
|
|
||||||
namespace Dino.Ui {
|
namespace Dino.Ui {
|
||||||
|
|
||||||
public class CallWindow : Gtk.Window {
|
public class CallWindow : Gtk.Window {
|
||||||
public string counterpart_display_name { get; set; }
|
|
||||||
|
|
||||||
// TODO should find another place for this
|
public signal void menu_dump_dot();
|
||||||
|
|
||||||
public CallWindowController controller;
|
public CallWindowController controller;
|
||||||
|
|
||||||
public Overlay overlay = new Overlay() { visible=true };
|
public Overlay overlay = new Overlay() { visible=true };
|
||||||
|
public Grid grid = new Grid() { visible=true };
|
||||||
public EventBox event_box = new EventBox() { 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() { show_close_button=true, visible=true };
|
public HeaderBar header_bar = new HeaderBar() { valign=Align.START, halign=Align.END, show_close_button=true, visible=true };
|
||||||
public Revealer header_bar_revealer = new Revealer() { valign=Align.START, transition_type=RevealerTransitionType.CROSSFADE, transition_duration=200, visible=true };
|
public Revealer header_bar_revealer = new Revealer() { halign=Align.END, valign=Align.START, transition_type=RevealerTransitionType.CROSSFADE, transition_duration=200, visible=true };
|
||||||
public Stack stack = new Stack() { visible=true };
|
public Box own_video_box = new Box(Orientation.HORIZONTAL, 0) { halign=Align.END, valign=Align.END, visible=true };
|
||||||
public Box own_video_box = new Box(Orientation.HORIZONTAL, 0) { expand=true, 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 };
|
||||||
private Widget? own_video = null;
|
private Widget? own_video = null;
|
||||||
private Box? own_video_border = new Box(Orientation.HORIZONTAL, 0) { expand=true }; // hack to draw a border around our own video, since we apparently can't draw a border around the Gst widget
|
private HashMap<string, ParticipantWidget> participant_widgets = new HashMap<string, ParticipantWidget>();
|
||||||
|
private ArrayList<string> participants = new ArrayList<string>();
|
||||||
|
|
||||||
private int own_video_width = 150;
|
private int own_video_width = 150;
|
||||||
private int own_video_height = 100;
|
private int own_video_height = 100;
|
||||||
|
|
||||||
private bool hide_controll_elements = false;
|
private bool hide_control_elements = false;
|
||||||
private uint hide_controll_handler = 0;
|
private uint hide_control_handler = 0;
|
||||||
private Widget? main_widget = null;
|
public bool controls_active { get; set; default=true; }
|
||||||
|
|
||||||
construct {
|
construct {
|
||||||
|
Util.force_css(header_bar, "* { background: none; border: 0; border-radius: 0; }");
|
||||||
header_bar.get_style_context().add_class("call-header-bar");
|
header_bar.get_style_context().add_class("call-header-bar");
|
||||||
header_bar_revealer.add(header_bar);
|
header_bar_revealer.add(header_bar);
|
||||||
|
bottom_bar_revealer.add(bottom_bar);
|
||||||
|
invite_button.get_style_context().add_class("black-element");
|
||||||
|
invite_button_revealer.add(invite_button);
|
||||||
|
own_video_box.get_style_context().add_class("own-video");
|
||||||
|
|
||||||
this.get_style_context().add_class("dino-call-window");
|
this.get_style_context().add_class("dino-call-window");
|
||||||
|
|
||||||
bottom_bar_revealer.add(bottom_bar);
|
overlay.add(grid);
|
||||||
|
|
||||||
overlay.add_overlay(own_video_box);
|
overlay.add_overlay(own_video_box);
|
||||||
overlay.add_overlay(own_video_border);
|
|
||||||
overlay.add_overlay(bottom_bar_revealer);
|
overlay.add_overlay(bottom_bar_revealer);
|
||||||
overlay.add_overlay(header_bar_revealer);
|
overlay.add_overlay(header_bar_revealer);
|
||||||
|
overlay.add_overlay(invite_button_revealer);
|
||||||
|
overlay.get_child_position.connect(on_get_child_position);
|
||||||
|
|
||||||
event_box.add(overlay);
|
event_box.add(overlay);
|
||||||
add(event_box);
|
add(event_box);
|
||||||
|
|
||||||
Util.force_css(own_video_border, "* { border: 1px solid #616161; background-color: transparent; }");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CallWindow() {
|
public CallWindow() {
|
||||||
event_box.events |= Gdk.EventMask.POINTER_MOTION_MASK;
|
this.bind_property("controls-active", bottom_bar_revealer, "reveal-child", BindingFlags.SYNC_CREATE);
|
||||||
event_box.events |= Gdk.EventMask.ENTER_NOTIFY_MASK;
|
this.bind_property("controls-active", header_bar_revealer, "reveal-child", BindingFlags.SYNC_CREATE);
|
||||||
event_box.events |= Gdk.EventMask.LEAVE_NOTIFY_MASK;
|
this.bind_property("controls-active", invite_button_revealer, "reveal-child", BindingFlags.SYNC_CREATE);
|
||||||
|
|
||||||
this.bind_property("counterpart-display-name", header_bar, "title", BindingFlags.SYNC_CREATE);
|
|
||||||
this.bind_property("counterpart-display-name", bottom_bar, "counterpart-display-name", BindingFlags.SYNC_CREATE);
|
|
||||||
|
|
||||||
event_box.motion_notify_event.connect(reveal_control_elements);
|
event_box.motion_notify_event.connect(reveal_control_elements);
|
||||||
event_box.enter_notify_event.connect(reveal_control_elements);
|
event_box.enter_notify_event.connect(reveal_control_elements);
|
||||||
event_box.leave_notify_event.connect(reveal_control_elements);
|
event_box.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(update_own_video_position);
|
|
||||||
|
this.configure_event.connect(reposition_participant_widgets);
|
||||||
|
|
||||||
this.set_titlebar(new OutsideHeaderBar(this.header_bar) { visible=true });
|
this.set_titlebar(new OutsideHeaderBar(this.header_bar) { visible=true });
|
||||||
|
|
||||||
reveal_control_elements();
|
reveal_control_elements();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_video_fallback(StreamInteractor stream_interactor, Conversation conversation) {
|
public void add_participant(string participant, ParticipantWidget participant_widget) {
|
||||||
hide_controll_elements = false;
|
participant_widget.visible = true;
|
||||||
|
this.bind_property("controls-active", participant_widget, "controls-active", BindingFlags.SYNC_CREATE);
|
||||||
|
this.bind_property("controls-active", participant_widget.encryption_button, "controls-active", BindingFlags.SYNC_CREATE);
|
||||||
|
|
||||||
Box box = new Box(Orientation.HORIZONTAL, 0) { visible=true };
|
participants.add(participant);
|
||||||
box.get_style_context().add_class("video-placeholder-box");
|
participant_widgets[participant] = participant_widget;
|
||||||
AvatarImage avatar = new AvatarImage() { hexpand=true, vexpand=true, halign=Align.CENTER, valign=Align.CENTER, height=100, width=100, visible=true };
|
grid.attach(participant_widget, 0, 0);
|
||||||
avatar.set_conversation(stream_interactor, conversation);
|
|
||||||
box.add(avatar);
|
|
||||||
|
|
||||||
set_new_main_widget(box);
|
reposition_participant_widgets();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_video(Widget widget) {
|
public void remove_participant(string participant) {
|
||||||
hide_controll_elements = true;
|
participants.remove(participant);
|
||||||
|
grid.remove(participant_widgets[participant]);
|
||||||
|
participant_widgets.unset(participant);
|
||||||
|
|
||||||
widget.visible = true;
|
reposition_participant_widgets();
|
||||||
set_new_main_widget(widget);
|
}
|
||||||
|
|
||||||
|
public void set_video(string participant, Widget widget) {
|
||||||
|
participant_widgets[participant].set_video(widget);
|
||||||
|
hide_control_elements = true;
|
||||||
|
timeout_hide_control_elements();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set_placeholder(string participant, Conversation? conversation, StreamInteractor stream_interactor) {
|
||||||
|
participant_widgets[participant].set_placeholder(conversation, stream_interactor);
|
||||||
|
hide_control_elements = false;
|
||||||
|
foreach (ParticipantWidget participant_widget in participant_widgets.values) {
|
||||||
|
if (participant_widget.shows_video) {
|
||||||
|
hide_control_elements = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hide_control_elements) {
|
||||||
|
reveal_control_elements();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool reposition_participant_widgets() {
|
||||||
|
int width, height;
|
||||||
|
this.get_size(out width,out height);
|
||||||
|
reposition_participant_widgets_rec(participants, width, height, 0, 0, 0, 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reposition_participant_widgets_rec(ArrayList<string> participants, int width, int height, int margin_top, int margin_right, int margin_bottom, int margin_left) {
|
||||||
|
if (participants.size == 0) return;
|
||||||
|
|
||||||
|
if (participants.size == 1) {
|
||||||
|
participant_widgets[participants[0]].margin_top = margin_top;
|
||||||
|
participant_widgets[participants[0]].margin_end = margin_right;
|
||||||
|
participant_widgets[participants[0]].margin_bottom = margin_bottom;
|
||||||
|
participant_widgets[participants[0]].margin_start = margin_left;
|
||||||
|
|
||||||
|
participant_widgets[participants[0]].on_lowest_row_changed(margin_bottom == 0);
|
||||||
|
participant_widgets[participants[0]].on_highest_row_changed(margin_top == 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<string> first_part = new ArrayList<string>();
|
||||||
|
ArrayList<string> last_part = new ArrayList<string>();
|
||||||
|
|
||||||
|
for (int i = 0; i < participants.size; i++) {
|
||||||
|
if (i < Math.ceil((double)participants.size / (double)2)) {
|
||||||
|
first_part.add(participants[i]);
|
||||||
|
} else {
|
||||||
|
last_part.add(participants[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width > height) {
|
||||||
|
reposition_participant_widgets_rec(first_part, width / 2, height, margin_top, margin_right + width / 2, margin_bottom, margin_left);
|
||||||
|
reposition_participant_widgets_rec(last_part, width / 2, height, margin_top, margin_right, margin_bottom, margin_left + width / 2);
|
||||||
|
} else {
|
||||||
|
reposition_participant_widgets_rec(first_part, width, height / 2, margin_top, margin_right, margin_bottom + height / 2, margin_left);
|
||||||
|
reposition_participant_widgets_rec(last_part, width, height / 2, margin_top + height / 2, margin_right, margin_bottom, margin_left);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_own_video(Widget? widget_) {
|
public void set_own_video(Widget? widget_) {
|
||||||
|
@ -92,13 +160,7 @@ namespace Dino.Ui {
|
||||||
own_video = new Box(Orientation.HORIZONTAL, 0) { expand=true };
|
own_video = new Box(Orientation.HORIZONTAL, 0) { expand=true };
|
||||||
}
|
}
|
||||||
own_video.visible = true;
|
own_video.visible = true;
|
||||||
own_video.width_request = 150;
|
|
||||||
own_video.height_request = 100;
|
|
||||||
own_video_box.add(own_video);
|
own_video_box.add(own_video);
|
||||||
|
|
||||||
own_video_border.visible = true;
|
|
||||||
|
|
||||||
update_own_video_position();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_own_video_ratio(int width, int height) {
|
public void set_own_video_ratio(int width, int height) {
|
||||||
|
@ -109,78 +171,25 @@ namespace Dino.Ui {
|
||||||
this.own_video_width = width * 100 / height;
|
this.own_video_width = width * 100 / height;
|
||||||
this.own_video_height = 100;
|
this.own_video_height = 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
own_video.width_request = own_video_width;
|
|
||||||
own_video.height_request = own_video_height;
|
|
||||||
|
|
||||||
update_own_video_position();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unset_own_video() {
|
public void unset_own_video() {
|
||||||
own_video_box.foreach((widget) => { own_video_box.remove(widget); });
|
own_video_box.foreach((widget) => { own_video_box.remove(widget); });
|
||||||
|
|
||||||
own_video_border.visible = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void set_test_video() {
|
public void set_status(string participant_id, string state) {
|
||||||
hide_controll_elements = true;
|
participant_widgets[participant_id].set_status(state);
|
||||||
|
|
||||||
var pipeline = new Gst.Pipeline(null);
|
|
||||||
var src = Gst.ElementFactory.make("videotestsrc", null);
|
|
||||||
pipeline.add(src);
|
|
||||||
Gst.Video.Sink sink = (Gst.Video.Sink) Gst.ElementFactory.make("gtksink", null);
|
|
||||||
Gtk.Widget widget;
|
|
||||||
sink.get("widget", out widget);
|
|
||||||
widget.unparent();
|
|
||||||
pipeline.add(sink);
|
|
||||||
src.link(sink);
|
|
||||||
widget.visible = true;
|
|
||||||
|
|
||||||
pipeline.set_state(Gst.State.PLAYING);
|
|
||||||
|
|
||||||
sink.get_static_pad("sink").notify["caps"].connect(() => {
|
|
||||||
int width, height;
|
|
||||||
sink.get_static_pad("sink").caps.get_structure(0).get_int("width", out width);
|
|
||||||
sink.get_static_pad("sink").caps.get_structure(0).get_int("height", out height);
|
|
||||||
widget.width_request = width;
|
|
||||||
widget.height_request = height;
|
|
||||||
});
|
|
||||||
|
|
||||||
set_new_main_widget(widget);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void set_new_main_widget(Widget widget) {
|
public void show_counterpart_ended(string who_terminated, string? reason_name, string? reason_text) {
|
||||||
if (main_widget != null) overlay.remove(main_widget);
|
hide_control_elements = false;
|
||||||
overlay.add(widget);
|
|
||||||
main_widget = widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void set_status(string state) {
|
|
||||||
switch (state) {
|
|
||||||
case "requested":
|
|
||||||
header_bar.subtitle = _("Calling…");
|
|
||||||
break;
|
|
||||||
case "ringing":
|
|
||||||
header_bar.subtitle = _("Ringing…");
|
|
||||||
break;
|
|
||||||
case "establishing":
|
|
||||||
header_bar.subtitle = _("Connecting…");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
header_bar.subtitle = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void show_counterpart_ended(string? reason_name, string? reason_text) {
|
|
||||||
hide_controll_elements = false;
|
|
||||||
reveal_control_elements();
|
reveal_control_elements();
|
||||||
|
|
||||||
string text = "";
|
string text = "";
|
||||||
if (reason_name == Xmpp.Xep.Jingle.ReasonElement.SUCCESS) {
|
if (reason_name == Xmpp.Xep.Jingle.ReasonElement.SUCCESS) {
|
||||||
text = _("%s ended the call").printf(counterpart_display_name);
|
text = _("%s ended the call").printf(who_terminated);
|
||||||
} else if (reason_name == Xmpp.Xep.Jingle.ReasonElement.DECLINE || reason_name == Xmpp.Xep.Jingle.ReasonElement.BUSY) {
|
} else if (reason_name == Xmpp.Xep.Jingle.ReasonElement.DECLINE || reason_name == Xmpp.Xep.Jingle.ReasonElement.BUSY) {
|
||||||
text = _("%s declined the call").printf(counterpart_display_name);
|
text = _("%s declined the call").printf(who_terminated);
|
||||||
} else {
|
} else {
|
||||||
text = "The call has been terminated: " + (reason_name ?? "") + " " + (reason_text ?? "");
|
text = "The call has been terminated: " + (reason_name ?? "") + " " + (reason_text ?? "");
|
||||||
}
|
}
|
||||||
|
@ -188,48 +197,53 @@ namespace Dino.Ui {
|
||||||
bottom_bar.show_counterpart_ended(text);
|
bottom_bar.show_counterpart_ended(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool reveal_control_elements() {
|
private bool reveal_control_elements() {
|
||||||
if (!bottom_bar_revealer.child_revealed) {
|
if (!bottom_bar_revealer.child_revealed) {
|
||||||
bottom_bar_revealer.set_reveal_child(true);
|
controls_active = true;
|
||||||
header_bar_revealer.set_reveal_child(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hide_controll_handler != 0) {
|
timeout_hide_control_elements();
|
||||||
Source.remove(hide_controll_handler);
|
|
||||||
hide_controll_handler = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hide_controll_elements) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
hide_controll_handler = Timeout.add_seconds(3, () => {
|
private void timeout_hide_control_elements() {
|
||||||
if (!hide_controll_elements) {
|
if (hide_control_handler != 0) {
|
||||||
|
Source.remove(hide_control_handler);
|
||||||
|
hide_control_handler = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hide_control_elements) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hide_control_handler = Timeout.add_seconds(3, () => {
|
||||||
|
if (!hide_control_elements) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bottom_bar.is_menu_active()) {
|
if (bottom_bar.is_menu_active()) {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
header_bar_revealer.set_reveal_child(false);
|
controls_active = false;
|
||||||
bottom_bar_revealer.set_reveal_child(false);
|
|
||||||
hide_controll_handler = 0;
|
hide_control_handler = 0;
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool update_own_video_position() {
|
private bool on_get_child_position(Widget widget, out Gdk.Rectangle allocation) {
|
||||||
if (own_video == null) return false;
|
if (widget == own_video_box) {
|
||||||
|
|
||||||
int width, height;
|
int width, height;
|
||||||
this.get_size(out width,out height);
|
this.get_size(out width,out height);
|
||||||
|
|
||||||
own_video.margin_end = own_video.margin_bottom = own_video_border.margin_end = own_video_border.margin_bottom = 20;
|
allocation = Gdk.Rectangle();
|
||||||
own_video.margin_start = own_video_border.margin_start = width - own_video_width - 20;
|
allocation.width = own_video_width;
|
||||||
own_video.margin_top = own_video_border.margin_top = height - own_video_height - 20;
|
allocation.height = own_video_height;
|
||||||
|
allocation.x = width - own_video_width - 20;
|
||||||
|
allocation.y = height - own_video_height - 20;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
using Xmpp;
|
||||||
|
using Gee;
|
||||||
using Dino.Entities;
|
using Dino.Entities;
|
||||||
using Gtk;
|
using Gtk;
|
||||||
|
|
||||||
|
@ -5,80 +7,178 @@ public class Dino.Ui.CallWindowController : Object {
|
||||||
|
|
||||||
private CallWindow call_window;
|
private CallWindow call_window;
|
||||||
private Call call;
|
private Call call;
|
||||||
private Conversation conversation;
|
private CallState call_state;
|
||||||
private StreamInteractor stream_interactor;
|
private StreamInteractor stream_interactor;
|
||||||
private Calls calls;
|
private Calls calls;
|
||||||
private Plugins.VideoCallPlugin call_plugin = Dino.Application.get_default().plugin_registry.video_call_plugin;
|
private Plugins.VideoCallPlugin call_plugin = Dino.Application.get_default().plugin_registry.video_call_plugin;
|
||||||
|
|
||||||
private Plugins.VideoCallWidget? own_video = null;
|
private Plugins.VideoCallWidget? own_video = null;
|
||||||
private Plugins.VideoCallWidget? counterpart_video = null;
|
private HashMap<string, Plugins.VideoCallWidget> participant_videos = new HashMap<string, Plugins.VideoCallWidget>();
|
||||||
|
private HashMap<string, ParticipantWidget> participant_widgets = new HashMap<string, ParticipantWidget>();
|
||||||
|
private HashMap<string, PeerState> peer_states = new HashMap<string, PeerState>();
|
||||||
private int window_height = -1;
|
private int window_height = -1;
|
||||||
private int window_width = -1;
|
private int window_width = -1;
|
||||||
private bool window_size_changed = false;
|
private bool window_size_changed = false;
|
||||||
|
private ulong[] call_window_handler_ids = new ulong[0];
|
||||||
|
private ulong[] bottom_bar_handler_ids = new ulong[0];
|
||||||
|
private ulong[] invite_handler_ids = new ulong[0];
|
||||||
|
|
||||||
public CallWindowController(CallWindow call_window, Call call, StreamInteractor stream_interactor) {
|
public CallWindowController(CallWindow call_window, CallState call_state, StreamInteractor stream_interactor) {
|
||||||
this.call_window = call_window;
|
this.call_window = call_window;
|
||||||
this.call = call;
|
this.call = call_state.call;
|
||||||
|
this.call_state = call_state;
|
||||||
this.stream_interactor = stream_interactor;
|
this.stream_interactor = stream_interactor;
|
||||||
|
|
||||||
this.calls = stream_interactor.get_module(Calls.IDENTITY);
|
this.calls = stream_interactor.get_module(Calls.IDENTITY);
|
||||||
this.conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(call.counterpart.bare_jid, call.account, Conversation.Type.CHAT);
|
|
||||||
this.own_video = call_plugin.create_widget(Plugins.WidgetType.GTK);
|
this.own_video = call_plugin.create_widget(Plugins.WidgetType.GTK);
|
||||||
this.counterpart_video = call_plugin.create_widget(Plugins.WidgetType.GTK);
|
|
||||||
|
|
||||||
call_window.counterpart_display_name = Util.get_conversation_display_name(stream_interactor, conversation);
|
|
||||||
call_window.set_default_size(704, 528); // 640x480 * 1.1
|
call_window.set_default_size(704, 528); // 640x480 * 1.1
|
||||||
call_window.set_video_fallback(stream_interactor, conversation);
|
|
||||||
|
|
||||||
this.call_window.bottom_bar.video_enabled = calls.should_we_send_video(call);
|
this.call_window.bottom_bar.video_enabled = call_state.should_we_send_video();
|
||||||
|
|
||||||
if (call.direction == Call.DIRECTION_INCOMING) {
|
call_state.terminated.connect((who_terminated, reason_name, reason_text) => {
|
||||||
call_window.set_status("establishing");
|
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(who_terminated.bare_jid, call.account, Conversation.Type.CHAT);
|
||||||
} else {
|
string display_name = conversation != null ? Util.get_conversation_display_name(stream_interactor, conversation) : who_terminated.bare_jid.to_string();
|
||||||
call_window.set_status("requested");
|
|
||||||
|
call_window.show_counterpart_ended(display_name, reason_name, reason_text);
|
||||||
|
Timeout.add_seconds(3, () => {
|
||||||
|
call_window.close();
|
||||||
|
call_window.destroy();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
call_state.peer_joined.connect((jid, peer_state) => {
|
||||||
|
connect_peer_signals(peer_state);
|
||||||
|
add_new_participant(peer_state.internal_id, peer_state.jid);
|
||||||
|
});
|
||||||
|
call_state.peer_left.connect((jid, peer_state, reason_name, reason_text) => {
|
||||||
|
remove_participant(peer_state.internal_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (PeerState peer_state in call_state.peers.values) {
|
||||||
|
connect_peer_signals(peer_state);
|
||||||
|
add_new_participant(peer_state.internal_id, peer_state.jid);
|
||||||
}
|
}
|
||||||
|
|
||||||
call_window.bottom_bar.hang_up.connect(() => {
|
// Call window signals
|
||||||
calls.end_call(conversation, call);
|
|
||||||
|
bottom_bar_handler_ids += call_window.bottom_bar.hang_up.connect(() => {
|
||||||
|
call_state.end();
|
||||||
call_window.close();
|
call_window.close();
|
||||||
call_window.destroy();
|
call_window.destroy();
|
||||||
this.dispose();
|
this.dispose();
|
||||||
});
|
});
|
||||||
call_window.destroy.connect(() => {
|
call_window_handler_ids += call_window.destroy.connect(() => {
|
||||||
calls.end_call(conversation, call);
|
call_state.end();
|
||||||
this.dispose();
|
this.dispose();
|
||||||
});
|
});
|
||||||
|
bottom_bar_handler_ids += call_window.bottom_bar.notify["audio-enabled"].connect(() => {
|
||||||
call_window.bottom_bar.notify["audio-enabled"].connect(() => {
|
call_state.mute_own_audio(!call_window.bottom_bar.audio_enabled);
|
||||||
calls.mute_own_audio(call, !call_window.bottom_bar.audio_enabled);
|
|
||||||
});
|
});
|
||||||
call_window.bottom_bar.notify["video-enabled"].connect(() => {
|
bottom_bar_handler_ids += call_window.bottom_bar.notify["video-enabled"].connect(() => {
|
||||||
calls.mute_own_video(call, !call_window.bottom_bar.video_enabled);
|
call_state.mute_own_video(!call_window.bottom_bar.video_enabled);
|
||||||
update_own_video();
|
update_own_video();
|
||||||
});
|
});
|
||||||
|
call_window_handler_ids += call_window.configure_event.connect((event) => {
|
||||||
|
if (window_width == -1 || window_height == -1) return false;
|
||||||
|
int current_height = this.call_window.get_allocated_height();
|
||||||
|
int current_width = this.call_window.get_allocated_width();
|
||||||
|
if (window_width != current_width || window_height != current_height) {
|
||||||
|
debug("Call window size changed by user. Disabling auto window-to-video size adaptation. %i->%i x %i->%i", window_width, current_width, window_height, current_height);
|
||||||
|
window_size_changed = true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
call_window_handler_ids += call_window.realize.connect(() => {
|
||||||
|
capture_window_size();
|
||||||
|
});
|
||||||
|
invite_handler_ids += call_window.invite_button.clicked.connect(() => {
|
||||||
|
Gee.List<Account> acc_list = new ArrayList<Account>(Account.equals_func);
|
||||||
|
acc_list.add(call.account);
|
||||||
|
SelectContactDialog add_chat_dialog = new SelectContactDialog(stream_interactor, acc_list);
|
||||||
|
add_chat_dialog.set_transient_for((Window) call_window.get_toplevel());
|
||||||
|
add_chat_dialog.title = _("Invite to Call");
|
||||||
|
add_chat_dialog.ok_button.label = _("Invite");
|
||||||
|
add_chat_dialog.selected.connect((account, jid) => {
|
||||||
|
call_state.invite_to_call.begin(jid);
|
||||||
|
});
|
||||||
|
add_chat_dialog.present();
|
||||||
|
});
|
||||||
|
|
||||||
calls.counterpart_sends_video_updated.connect((call, mute) => {
|
calls.conference_info_received.connect((call, conference_info) => {
|
||||||
if (!this.call.equals(call)) return;
|
if (!this.call.equals(call)) return;
|
||||||
|
|
||||||
|
var participants = new ArrayList<string>();
|
||||||
|
participants.add_all(participant_videos.keys);
|
||||||
|
foreach (string participant in participants) {
|
||||||
|
remove_participant(participant);
|
||||||
|
}
|
||||||
|
foreach (Jid participant in conference_info.users.keys) {
|
||||||
|
add_new_participant(participant.to_string(), participant);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
own_video.resolution_changed.connect((width, height) => {
|
||||||
|
if (width == 0 || height == 0) return;
|
||||||
|
call_window.set_own_video_ratio((int)width, (int)height);
|
||||||
|
});
|
||||||
|
|
||||||
|
call_window.menu_dump_dot.connect(() => { call_plugin.dump_dot(); });
|
||||||
|
|
||||||
|
update_own_video();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connect_peer_signals(PeerState peer_state) {
|
||||||
|
string peer_id = peer_state.internal_id;
|
||||||
|
Jid peer_jid = peer_state.jid;
|
||||||
|
peer_states[peer_id] = peer_state;
|
||||||
|
|
||||||
|
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_plugin.devices_changed.connect((media, incoming) => {
|
||||||
|
if (media == "audio") update_audio_device_choices();
|
||||||
|
if (media == "video") update_video_device_choices();
|
||||||
|
});
|
||||||
|
|
||||||
|
update_audio_device_choices();
|
||||||
|
update_video_device_choices();
|
||||||
|
} else if (participant_widgets.size >= 1) {
|
||||||
|
call_window.invite_button_revealer.visible = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
peer_state.counterpart_sends_video_updated.connect((mute) => {
|
||||||
if (mute) {
|
if (mute) {
|
||||||
call_window.set_video_fallback(stream_interactor, conversation);
|
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(peer_jid.bare_jid, call.account, Conversation.Type.CHAT);
|
||||||
counterpart_video.detach();
|
call_window.set_placeholder(peer_id, conversation, stream_interactor);
|
||||||
|
participant_videos[peer_id].detach();
|
||||||
} else {
|
} else {
|
||||||
if (!(counterpart_video is Widget)) return;
|
if (!(participant_videos[peer_id] is Widget)) return;
|
||||||
Widget widget = (Widget) counterpart_video;
|
Widget widget = (Widget) participant_videos[peer_id];
|
||||||
call_window.set_video(widget);
|
call_window.set_video(peer_id, widget);
|
||||||
counterpart_video.display_stream(calls.get_video_stream(call));
|
participant_videos[peer_id].display_stream(peer_state.get_video_stream(call), peer_jid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
calls.info_received.connect((call, session_info) => {
|
peer_state.info_received.connect((session_info) => {
|
||||||
if (!this.call.equals(call)) return;
|
|
||||||
if (session_info == Xmpp.Xep.JingleRtp.CallSessionInfo.RINGING) {
|
if (session_info == Xmpp.Xep.JingleRtp.CallSessionInfo.RINGING) {
|
||||||
call_window.set_status("ringing");
|
call_window.set_status(peer_state.internal_id, "ringing");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
calls.encryption_updated.connect((call, audio_encryption, video_encryption, same) => {
|
peer_state.encryption_updated.connect((audio_encryption, video_encryption, same) => {
|
||||||
if (!this.call.equals(call)) return;
|
update_encryption_indicator(participant_widgets[peer_id].encryption_button, audio_encryption, video_encryption, same);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_encryption_indicator(CallEncryptionButton encryption_button, Xep.Jingle.ContentEncryption? audio_encryption, Xep.Jingle.ContentEncryption? video_encryption, bool same) {
|
||||||
string? title = null;
|
string? title = null;
|
||||||
string? icon_name = null;
|
string? icon_name = null;
|
||||||
bool show_keys = true;
|
bool show_keys = true;
|
||||||
|
@ -92,16 +192,42 @@ public class Dino.Ui.CallWindowController : Object {
|
||||||
show_keys = encryption_widgets.show_keys();
|
show_keys = encryption_widgets.show_keys();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
call_window.bottom_bar.encryption_button.set_info(title, show_keys, audio_encryption, same ? null :video_encryption);
|
|
||||||
call_window.bottom_bar.encryption_button.set_icon(audio_encryption != null, icon_name);
|
|
||||||
});
|
|
||||||
|
|
||||||
own_video.resolution_changed.connect((width, height) => {
|
encryption_button.set_info(title, show_keys, audio_encryption, same ? null : video_encryption);
|
||||||
if (width == 0 || height == 0) return;
|
encryption_button.set_icon(audio_encryption != null, icon_name);
|
||||||
call_window.set_own_video_ratio((int)width, (int)height);
|
}
|
||||||
|
|
||||||
|
private void add_new_participant(string participant_id, Jid jid) {
|
||||||
|
if (participant_widgets.has_key(participant_id)) {
|
||||||
|
warning("[%s] Attempted to add same participant twice: %s", call.account.bare_jid.to_string(), jid.to_string());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debug("[%s] Call window controller | Add participant: %s", call.account.bare_jid.to_string(), jid.to_string());
|
||||||
|
|
||||||
|
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid.bare_jid, call.account, Conversation.Type.CHAT);
|
||||||
|
string participant_name = conversation != null ? Util.get_conversation_display_name(stream_interactor, conversation) : jid.bare_jid.to_string();
|
||||||
|
|
||||||
|
ParticipantWidget participant_widget = new ParticipantWidget(participant_name);
|
||||||
|
participant_widget.menu_button.clicked.connect((event) => {
|
||||||
|
var conn_details_window = new CallConnectionDetailsWindow() { title=participant_name, visible=true };
|
||||||
|
conn_details_window.update_content(peer_states[participant_id].get_info());
|
||||||
|
uint timeout_handle_id = Timeout.add_seconds(1, () => {
|
||||||
|
conn_details_window.update_content(peer_states[participant_id].get_info());
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
counterpart_video.resolution_changed.connect((width, height) => {
|
conn_details_window.set_transient_for(call_window);
|
||||||
if (window_size_changed) return;
|
conn_details_window.destroy.connect(() => Source.remove(timeout_handle_id));
|
||||||
|
conn_details_window.present();
|
||||||
|
this.call_window.destroy.connect(() => conn_details_window.close() );
|
||||||
|
});
|
||||||
|
participant_widgets[participant_id] = participant_widget;
|
||||||
|
|
||||||
|
call_window.add_participant(participant_id, participant_widget);
|
||||||
|
|
||||||
|
participant_videos[participant_id] = call_plugin.create_widget(Plugins.WidgetType.GTK);
|
||||||
|
|
||||||
|
participant_videos[participant_id].resolution_changed.connect((width, height) => {
|
||||||
|
if (window_size_changed || participant_widgets.size > 1) return;
|
||||||
if (width == 0 || height == 0) return;
|
if (width == 0 || height == 0) return;
|
||||||
if (width > height) {
|
if (width > height) {
|
||||||
call_window.resize(704, (int) (height * 704 / width));
|
call_window.resize(704, (int) (height * 704 / width));
|
||||||
|
@ -110,24 +236,22 @@ public class Dino.Ui.CallWindowController : Object {
|
||||||
}
|
}
|
||||||
capture_window_size();
|
capture_window_size();
|
||||||
});
|
});
|
||||||
call_window.configure_event.connect((event) => {
|
|
||||||
if (window_width == -1 || window_height == -1) return false;
|
participant_widget.set_placeholder(conversation, stream_interactor);
|
||||||
int current_height = this.call_window.get_allocated_height();
|
if (call.direction == Call.DIRECTION_INCOMING) {
|
||||||
int current_width = this.call_window.get_allocated_width();
|
call_window.set_status(participant_id, "establishing");
|
||||||
if (window_width != current_width || window_height != current_height) {
|
} else {
|
||||||
debug("Call window size changed by user. Disabling auto window-to-video size adaptation. %i->%i x %i->%i", window_width, current_width, window_height, current_height);
|
call_window.set_status(participant_id, "requested");
|
||||||
window_size_changed = true;
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
});
|
|
||||||
call_window.realize.connect(() => {
|
|
||||||
capture_window_size();
|
|
||||||
});
|
|
||||||
|
|
||||||
call.notify["state"].connect(on_call_state_changed);
|
private void remove_participant(string participant_id) {
|
||||||
calls.call_terminated.connect(on_call_terminated);
|
if (peer_states.has_key(participant_id)) debug(@"[%s] Call window controller | Remove participant: %s", call.account.bare_jid.to_string(), peer_states[participant_id].jid.to_string());
|
||||||
|
|
||||||
update_own_video();
|
participant_videos.unset(participant_id);
|
||||||
|
participant_widgets.unset(participant_id);
|
||||||
|
peer_states.unset(participant_id);
|
||||||
|
call_window.remove_participant(participant_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void capture_window_size() {
|
private void capture_window_size() {
|
||||||
|
@ -137,33 +261,6 @@ public class Dino.Ui.CallWindowController : Object {
|
||||||
this.window_width = this.call_window.get_allocated_width();
|
this.window_width = this.call_window.get_allocated_width();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void on_call_state_changed() {
|
|
||||||
if (call.state == Call.State.IN_PROGRESS) {
|
|
||||||
call_window.set_status("");
|
|
||||||
call_plugin.devices_changed.connect((media, incoming) => {
|
|
||||||
if (media == "audio") update_audio_device_choices();
|
|
||||||
if (media == "video") update_video_device_choices();
|
|
||||||
});
|
|
||||||
|
|
||||||
update_audio_device_choices();
|
|
||||||
update_video_device_choices();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void on_call_terminated(Call call, string? reason_name, string? reason_text) {
|
|
||||||
call_window.show_counterpart_ended(reason_name, reason_text);
|
|
||||||
Timeout.add_seconds(3, () => {
|
|
||||||
call.notify["state"].disconnect(on_call_state_changed);
|
|
||||||
calls.call_terminated.disconnect(on_call_terminated);
|
|
||||||
|
|
||||||
|
|
||||||
call_window.close();
|
|
||||||
call_window.destroy();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update_audio_device_choices() {
|
private void update_audio_device_choices() {
|
||||||
if (call_plugin.get_devices("audio", true).size == 0 || call_plugin.get_devices("audio", false).size == 0) {
|
if (call_plugin.get_devices("audio", true).size == 0 || call_plugin.get_devices("audio", false).size == 0) {
|
||||||
call_window.bottom_bar.show_audio_device_error();
|
call_window.bottom_bar.show_audio_device_error();
|
||||||
|
@ -190,13 +287,13 @@ public class Dino.Ui.CallWindowController : Object {
|
||||||
});*/
|
});*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update_current_audio_device(AudioSettingsPopover audio_settings_popover) {
|
/*private void update_current_audio_device(AudioSettingsPopover audio_settings_popover) {
|
||||||
Xmpp.Xep.JingleRtp.Stream stream = calls.get_audio_stream(call);
|
Xmpp.Xep.JingleRtp.Stream stream = calls.get_audio_stream(call);
|
||||||
if (stream != null) {
|
if (stream != null) {
|
||||||
audio_settings_popover.current_microphone_device = call_plugin.get_device(stream, false);
|
audio_settings_popover.current_microphone_device = call_plugin.get_device(stream, false);
|
||||||
audio_settings_popover.current_speaker_device = call_plugin.get_device(stream, true);
|
audio_settings_popover.current_speaker_device = call_plugin.get_device(stream, true);
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
private void update_video_device_choices() {
|
private void update_video_device_choices() {
|
||||||
int device_count = call_plugin.get_devices("video", false).size;
|
int device_count = call_plugin.get_devices("video", false).size;
|
||||||
|
@ -223,12 +320,37 @@ public class Dino.Ui.CallWindowController : Object {
|
||||||
});*/
|
});*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update_current_video_device(VideoSettingsPopover video_settings_popover) {
|
public void add_test_video() {
|
||||||
|
var pipeline = new Gst.Pipeline(null);
|
||||||
|
var src = Gst.ElementFactory.make("videotestsrc", null);
|
||||||
|
pipeline.add(src);
|
||||||
|
Gst.Video.Sink sink = (Gst.Video.Sink) Gst.ElementFactory.make("gtksink", null);
|
||||||
|
Gtk.Widget widget;
|
||||||
|
sink.get("widget", out widget);
|
||||||
|
widget.unparent();
|
||||||
|
pipeline.add(sink);
|
||||||
|
src.link(sink);
|
||||||
|
widget.visible = true;
|
||||||
|
|
||||||
|
pipeline.set_state(Gst.State.PLAYING);
|
||||||
|
|
||||||
|
sink.get_static_pad("sink").notify["caps"].connect(() => {
|
||||||
|
int width, height;
|
||||||
|
sink.get_static_pad("sink").caps.get_structure(0).get_int("width", out width);
|
||||||
|
sink.get_static_pad("sink").caps.get_structure(0).get_int("height", out height);
|
||||||
|
widget.width_request = width;
|
||||||
|
widget.height_request = height;
|
||||||
|
});
|
||||||
|
|
||||||
|
// call_window.set_participant_video(Xmpp.random_uuid(), widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*private void update_current_video_device(VideoSettingsPopover video_settings_popover) {
|
||||||
Xmpp.Xep.JingleRtp.Stream stream = calls.get_video_stream(call);
|
Xmpp.Xep.JingleRtp.Stream stream = calls.get_video_stream(call);
|
||||||
if (stream != null) {
|
if (stream != null) {
|
||||||
video_settings_popover.current_device = call_plugin.get_device(stream, false);
|
video_settings_popover.current_device = call_plugin.get_device(stream, false);
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
private void update_own_video() {
|
private void update_own_video() {
|
||||||
if (this.call_window.bottom_bar.video_enabled) {
|
if (this.call_window.bottom_bar.video_enabled) {
|
||||||
|
@ -247,8 +369,12 @@ public class Dino.Ui.CallWindowController : Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void dispose() {
|
public override void dispose() {
|
||||||
|
foreach (ulong handler_id in call_window_handler_ids) call_window.disconnect(handler_id);
|
||||||
|
foreach (ulong handler_id in bottom_bar_handler_ids) call_window.bottom_bar.disconnect(handler_id);
|
||||||
|
foreach (ulong handler_id in invite_handler_ids) call_window.invite_button.disconnect(handler_id);
|
||||||
|
|
||||||
|
call_window_handler_ids = bottom_bar_handler_ids = invite_handler_ids = new ulong[0];
|
||||||
|
|
||||||
base.dispose();
|
base.dispose();
|
||||||
call.notify["state"].disconnect(on_call_state_changed);
|
|
||||||
calls.call_terminated.disconnect(on_call_terminated);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
129
main/src/ui/call_window/participant_widget.vala
Normal file
129
main/src/ui/call_window/participant_widget.vala
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
using Pango;
|
||||||
|
using Gee;
|
||||||
|
using Xmpp;
|
||||||
|
using Dino.Entities;
|
||||||
|
using Gtk;
|
||||||
|
|
||||||
|
namespace Dino.Ui {
|
||||||
|
|
||||||
|
public class ParticipantWidget : Gtk.Overlay {
|
||||||
|
|
||||||
|
public Widget main_widget;
|
||||||
|
public Box outer_box = new Box(Orientation.HORIZONTAL, 0) { valign=Align.START, visible=true };
|
||||||
|
public Box inner_box = new Box(Orientation.HORIZONTAL, 0) { margin_start=5, margin_top=5, hexpand=true, visible=true };
|
||||||
|
public Box title_box = new Box(Orientation.VERTICAL, 0) { valign=Align.CENTER, hexpand=true, visible=true };
|
||||||
|
public CallEncryptionButton encryption_button = new CallEncryptionButton() { opacity=0, relief=ReliefStyle.NONE, height_request=30, width_request=30, margin_end=5, visible=true };
|
||||||
|
public Label status_label = new Label("") { ellipsize=EllipsizeMode.MIDDLE };
|
||||||
|
public Label name_label = new Label("") { ellipsize=EllipsizeMode.MIDDLE, visible=true };
|
||||||
|
public Button menu_button = new Button.from_icon_name("view-more-horizontal-symbolic") { relief=ReliefStyle.NONE, visible=true };
|
||||||
|
public bool shows_video = false;
|
||||||
|
public string? participant_name;
|
||||||
|
|
||||||
|
bool is_highest_row = false;
|
||||||
|
bool is_lowest_row = false;
|
||||||
|
public bool controls_active { get; set; }
|
||||||
|
|
||||||
|
public ParticipantWidget(string participant_name) {
|
||||||
|
this.participant_name = participant_name;
|
||||||
|
name_label.label = participant_name;
|
||||||
|
|
||||||
|
name_label.attributes = new AttrList();
|
||||||
|
name_label.attributes.filter((attr) => attr.equal(attr_weight_new(Weight.BOLD)));
|
||||||
|
|
||||||
|
name_label.attributes = new AttrList();
|
||||||
|
name_label.attributes.filter((attr) => attr.equal(attr_scale_new(0.9)));
|
||||||
|
status_label.get_style_context().add_class("dim-label");
|
||||||
|
|
||||||
|
Util.force_css(outer_box, "* { color: white; text-shadow: 1px 1px black; }");
|
||||||
|
menu_button.get_style_context().add_class("participant-title-button");
|
||||||
|
encryption_button.get_style_context().add_class("participant-title-button");
|
||||||
|
|
||||||
|
title_box.add(name_label);
|
||||||
|
title_box.add(status_label);
|
||||||
|
|
||||||
|
outer_box.add(inner_box);
|
||||||
|
|
||||||
|
inner_box.add(menu_button);
|
||||||
|
inner_box.add(encryption_button);
|
||||||
|
inner_box.add(title_box);
|
||||||
|
inner_box.add(new Button.from_icon_name("go-up-symbolic") { opacity=0, visible=true });
|
||||||
|
inner_box.add(new Button.from_icon_name("go-up-symbolic") { opacity=0, visible=true });
|
||||||
|
|
||||||
|
this.add_overlay(outer_box);
|
||||||
|
|
||||||
|
this.notify["controls-active"].connect(reveal_or_hide_controls);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void on_show_names_changed(bool show) {
|
||||||
|
name_label.visible = show;
|
||||||
|
reveal_or_hide_controls();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void on_highest_row_changed(bool is_highest) {
|
||||||
|
is_highest_row = is_highest;
|
||||||
|
reveal_or_hide_controls();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void on_lowest_row_changed(bool is_lowest) {
|
||||||
|
is_lowest_row = is_lowest;
|
||||||
|
reveal_or_hide_controls();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set_video(Widget widget) {
|
||||||
|
shows_video = true;
|
||||||
|
widget.visible = true;
|
||||||
|
set_participant_widget(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set_placeholder(Conversation? conversation, StreamInteractor stream_interactor) {
|
||||||
|
shows_video = false;
|
||||||
|
Box box = new Box(Orientation.HORIZONTAL, 0) { visible=true };
|
||||||
|
box.get_style_context().add_class("video-placeholder-box");
|
||||||
|
AvatarImage avatar = new AvatarImage() { allow_gray=false, hexpand=true, vexpand=true, halign=Align.CENTER, valign=Align.CENTER, height=100, width=100, visible=true };
|
||||||
|
if (conversation != null) {
|
||||||
|
avatar.set_conversation(stream_interactor, conversation);
|
||||||
|
} else {
|
||||||
|
avatar.set_text("?", false);
|
||||||
|
}
|
||||||
|
box.add(avatar);
|
||||||
|
|
||||||
|
set_participant_widget(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void set_participant_widget(Widget widget) {
|
||||||
|
widget.expand = true;
|
||||||
|
if (main_widget != null) this.remove(main_widget);
|
||||||
|
main_widget = widget;
|
||||||
|
this.add(main_widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set_status(string state) {
|
||||||
|
status_label.visible = true;
|
||||||
|
|
||||||
|
if (state == "requested") {
|
||||||
|
status_label.label = _("Calling…");
|
||||||
|
} else if (state == "ringing") {
|
||||||
|
status_label.label = _("Ringing…");
|
||||||
|
} else if (state == "establishing") {
|
||||||
|
status_label.label = _("Connecting…");
|
||||||
|
} else {
|
||||||
|
status_label.visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reveal_or_hide_controls() {
|
||||||
|
if (controls_active && name_label.visible) {
|
||||||
|
title_box.opacity = 1;
|
||||||
|
menu_button.opacity = 1;
|
||||||
|
} else {
|
||||||
|
title_box.opacity = 0;
|
||||||
|
menu_button.opacity = 0;
|
||||||
|
}
|
||||||
|
if (is_highest_row && controls_active) {
|
||||||
|
outer_box.get_style_context().add_class("call-header-bar");
|
||||||
|
} else {
|
||||||
|
outer_box.get_style_context().remove_class("call-header-bar");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,7 @@ public class MucConfigFormProvider : Plugins.ContactDetailsProvider, Object {
|
||||||
contact_details.save.connect(() => {
|
contact_details.save.connect(() => {
|
||||||
// Only send the config form if something was changed
|
// Only send the config form if something was changed
|
||||||
if (config_backup != data_form.stanza_node.to_string()) {
|
if (config_backup != data_form.stanza_node.to_string()) {
|
||||||
stream_interactor.get_module(MucManager.IDENTITY).set_config_form(conversation.account, conversation.counterpart, data_form);
|
stream_interactor.get_module(MucManager.IDENTITY).set_config_form.begin(conversation.account, conversation.counterpart, data_form);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,6 +2,7 @@ using Gee;
|
||||||
using Gdk;
|
using Gdk;
|
||||||
using Gtk;
|
using Gtk;
|
||||||
using Pango;
|
using Pango;
|
||||||
|
using Xmpp;
|
||||||
|
|
||||||
using Dino.Entities;
|
using Dino.Entities;
|
||||||
|
|
||||||
|
@ -18,7 +19,8 @@ namespace Dino.Ui {
|
||||||
|
|
||||||
public override Object? get_widget(Plugins.WidgetType type) {
|
public override Object? get_widget(Plugins.WidgetType type) {
|
||||||
CallItem call_item = content_item as CallItem;
|
CallItem call_item = content_item as CallItem;
|
||||||
return new CallWidget(stream_interactor, call_item.call, call_item.conversation) { visible=true };
|
CallState? call_state = stream_interactor.get_module(Calls.IDENTITY).call_states[call_item.call];
|
||||||
|
return new CallWidget(stream_interactor, call_item.call, call_state, call_item.conversation) { visible=true };
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Gee.List<Plugins.MessageAction>? get_item_actions(Plugins.WidgetType type) { return null; }
|
public override Gee.List<Plugins.MessageAction>? get_item_actions(Plugins.WidgetType type) { return null; }
|
||||||
|
@ -31,10 +33,14 @@ namespace Dino.Ui {
|
||||||
[GtkChild] public unowned Label title_label;
|
[GtkChild] public unowned Label title_label;
|
||||||
[GtkChild] public unowned Label subtitle_label;
|
[GtkChild] public unowned Label subtitle_label;
|
||||||
[GtkChild] public unowned Revealer incoming_call_revealer;
|
[GtkChild] public unowned Revealer incoming_call_revealer;
|
||||||
|
[GtkChild] public unowned Box outer_additional_box;
|
||||||
|
[GtkChild] public unowned Box incoming_call_box;
|
||||||
|
[GtkChild] public unowned Box multiparty_peer_box;
|
||||||
[GtkChild] public unowned Button accept_call_button;
|
[GtkChild] public unowned Button accept_call_button;
|
||||||
[GtkChild] public unowned Button reject_call_button;
|
[GtkChild] public unowned Button reject_call_button;
|
||||||
|
|
||||||
private StreamInteractor stream_interactor;
|
private StreamInteractor stream_interactor;
|
||||||
|
private CallState call_manager;
|
||||||
private Call call;
|
private Call call;
|
||||||
private Conversation conversation;
|
private Conversation conversation;
|
||||||
public Call.State call_state { get; set; } // needs to be public for binding
|
public Call.State call_state { get; set; } // needs to be public for binding
|
||||||
|
@ -45,8 +51,10 @@ namespace Dino.Ui {
|
||||||
size_request_mode = SizeRequestMode.HEIGHT_FOR_WIDTH;
|
size_request_mode = SizeRequestMode.HEIGHT_FOR_WIDTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CallWidget(StreamInteractor stream_interactor, Call call, Conversation conversation) {
|
/** @param call_state Null if it's an old call and we can't interact with it anymore */
|
||||||
|
public CallWidget(StreamInteractor stream_interactor, Call call, CallState? call_state, Conversation conversation) {
|
||||||
this.stream_interactor = stream_interactor;
|
this.stream_interactor = stream_interactor;
|
||||||
|
this.call_manager = call_state;
|
||||||
this.call = call;
|
this.call = call;
|
||||||
this.conversation = conversation;
|
this.conversation = conversation;
|
||||||
|
|
||||||
|
@ -57,36 +65,63 @@ namespace Dino.Ui {
|
||||||
});
|
});
|
||||||
|
|
||||||
call.bind_property("state", this, "call-state");
|
call.bind_property("state", this, "call-state");
|
||||||
this.notify["call-state"].connect(update_widget);
|
this.notify["call-state"].connect(update_call_state);
|
||||||
|
|
||||||
|
if (call_manager != null && (call.state == Call.State.ESTABLISHING || call.state == Call.State.IN_PROGRESS)) {
|
||||||
|
call_manager.peer_joined.connect(update_counterparts);
|
||||||
|
}
|
||||||
|
|
||||||
accept_call_button.clicked.connect(() => {
|
accept_call_button.clicked.connect(() => {
|
||||||
stream_interactor.get_module(Calls.IDENTITY).accept_call(call);
|
call_manager.accept();
|
||||||
|
|
||||||
var call_window = new CallWindow();
|
var call_window = new CallWindow();
|
||||||
call_window.controller = new CallWindowController(call_window, call, stream_interactor);
|
call_window.controller = new CallWindowController(call_window, call_state, stream_interactor);
|
||||||
call_window.present();
|
call_window.present();
|
||||||
});
|
});
|
||||||
|
|
||||||
reject_call_button.clicked.connect(() => {
|
reject_call_button.clicked.connect(call_manager.reject);
|
||||||
stream_interactor.get_module(Calls.IDENTITY).reject_call(call);
|
|
||||||
});
|
|
||||||
|
|
||||||
update_widget();
|
update_call_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update_widget() {
|
private void update_counterparts() {
|
||||||
|
if (call.state != Call.State.IN_PROGRESS && call.state != Call.State.ENDED) return;
|
||||||
|
if (call.counterparts.size <= 1) return;
|
||||||
|
|
||||||
|
multiparty_peer_box.foreach((widget) => { multiparty_peer_box.remove(widget); });
|
||||||
|
|
||||||
|
foreach (Jid counterpart in call.counterparts) {
|
||||||
|
AvatarImage image = new AvatarImage() { force_gray=true, margin_top=2, visible=true };
|
||||||
|
image.set_conversation_participant(stream_interactor, conversation, counterpart.bare_jid);
|
||||||
|
multiparty_peer_box.add(image);
|
||||||
|
}
|
||||||
|
AvatarImage image2 = new AvatarImage() { force_gray=true, margin_top=2, visible=true };
|
||||||
|
image2.set_conversation_participant(stream_interactor, conversation, call.account.bare_jid);
|
||||||
|
multiparty_peer_box.add(image2);
|
||||||
|
|
||||||
|
outer_additional_box.get_style_context().add_class("multiparty-participants");
|
||||||
|
|
||||||
|
multiparty_peer_box.visible = true;
|
||||||
|
incoming_call_box.visible = false;
|
||||||
|
incoming_call_revealer.reveal_child = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update_call_state() {
|
||||||
incoming_call_revealer.reveal_child = false;
|
incoming_call_revealer.reveal_child = false;
|
||||||
incoming_call_revealer.get_style_context().remove_class("incoming");
|
incoming_call_revealer.get_style_context().remove_class("incoming");
|
||||||
|
outer_additional_box.get_style_context().remove_class("incoming-call-box");
|
||||||
|
|
||||||
switch (call.state) {
|
switch (call.state) {
|
||||||
case Call.State.RINGING:
|
case Call.State.RINGING:
|
||||||
image.set_from_icon_name("dino-phone-ring-symbolic", IconSize.LARGE_TOOLBAR);
|
image.set_from_icon_name("dino-phone-ring-symbolic", IconSize.LARGE_TOOLBAR);
|
||||||
if (call.direction == Call.DIRECTION_INCOMING) {
|
if (call.direction == Call.DIRECTION_INCOMING) {
|
||||||
bool video = stream_interactor.get_module(Calls.IDENTITY).should_we_send_video(call);
|
bool video = call_manager.should_we_send_video();
|
||||||
title_label.label = video ? _("Incoming video call") : _("Incoming call");
|
title_label.label = video ? _("Incoming video call") : _("Incoming call");
|
||||||
subtitle_label.label = "Ring ring…!";
|
subtitle_label.label = "Ring ring…!";
|
||||||
|
incoming_call_box.visible = true;
|
||||||
incoming_call_revealer.reveal_child = true;
|
incoming_call_revealer.reveal_child = true;
|
||||||
incoming_call_revealer.get_style_context().add_class("incoming");
|
incoming_call_revealer.get_style_context().add_class("incoming");
|
||||||
|
outer_additional_box.get_style_context().add_class("incoming-call-box");
|
||||||
} else {
|
} else {
|
||||||
title_label.label = _("Calling…");
|
title_label.label = _("Calling…");
|
||||||
subtitle_label.label = "Ring ring…?";
|
subtitle_label.label = "Ring ring…?";
|
||||||
|
@ -100,9 +135,11 @@ namespace Dino.Ui {
|
||||||
subtitle_label.label = _("Started %s ago").printf(duration);
|
subtitle_label.label = _("Started %s ago").printf(duration);
|
||||||
|
|
||||||
time_update_handler_id = Timeout.add_seconds(get_next_time_change() + 1, () => {
|
time_update_handler_id = Timeout.add_seconds(get_next_time_change() + 1, () => {
|
||||||
|
if (time_update_handler_id != 0) {
|
||||||
Source.remove(time_update_handler_id);
|
Source.remove(time_update_handler_id);
|
||||||
time_update_handler_id = 0;
|
time_update_handler_id = 0;
|
||||||
update_widget();
|
update_call_state();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -128,7 +165,7 @@ namespace Dino.Ui {
|
||||||
if (call.direction == Call.DIRECTION_INCOMING) {
|
if (call.direction == Call.DIRECTION_INCOMING) {
|
||||||
subtitle_label.label = _("You missed this call");
|
subtitle_label.label = _("You missed this call");
|
||||||
} else {
|
} else {
|
||||||
string who = Util.get_participant_display_name(stream_interactor, conversation, call.to);
|
string who = Util.get_conversation_display_name(stream_interactor, conversation);
|
||||||
subtitle_label.label = _("%s missed this call").printf(who);
|
subtitle_label.label = _("%s missed this call").printf(who);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -138,7 +175,7 @@ namespace Dino.Ui {
|
||||||
if (call.direction == Call.DIRECTION_INCOMING) {
|
if (call.direction == Call.DIRECTION_INCOMING) {
|
||||||
subtitle_label.label = _("You declined this call");
|
subtitle_label.label = _("You declined this call");
|
||||||
} else {
|
} else {
|
||||||
string who = Util.get_participant_display_name(stream_interactor, conversation, call.to);
|
string who = Util.get_conversation_display_name(stream_interactor, conversation);
|
||||||
subtitle_label.label = _("%s declined this call").printf(who);
|
subtitle_label.label = _("%s declined this call").printf(who);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -148,6 +185,8 @@ namespace Dino.Ui {
|
||||||
subtitle_label.label = "Call failed to establish";
|
subtitle_label.label = "Call failed to establish";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_counterparts();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string get_duration_string(TimeSpan duration) {
|
private string get_duration_string(TimeSpan duration) {
|
||||||
|
@ -201,6 +240,9 @@ namespace Dino.Ui {
|
||||||
Source.remove(time_update_handler_id);
|
Source.remove(time_update_handler_id);
|
||||||
time_update_handler_id = 0;
|
time_update_handler_id = 0;
|
||||||
}
|
}
|
||||||
|
if (call_manager != null) {
|
||||||
|
call_manager.peer_joined.disconnect(update_counterparts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,7 +114,6 @@ public class ConversationItemSkeleton : EventBox {
|
||||||
[GtkTemplate (ui = "/im/dino/Dino/conversation_content_view/item_metadata_header.ui")]
|
[GtkTemplate (ui = "/im/dino/Dino/conversation_content_view/item_metadata_header.ui")]
|
||||||
public class ItemMetaDataHeader : Box {
|
public class ItemMetaDataHeader : Box {
|
||||||
[GtkChild] public unowned Label name_label;
|
[GtkChild] public unowned Label name_label;
|
||||||
[GtkChild] public unowned Label dot_label;
|
|
||||||
[GtkChild] public unowned Label time_label;
|
[GtkChild] public unowned Label time_label;
|
||||||
public Image received_image = new Image() { opacity=0.4 };
|
public Image received_image = new Image() { opacity=0.4 };
|
||||||
public Widget? encryption_image = null;
|
public Widget? encryption_image = null;
|
||||||
|
|
|
@ -47,16 +47,16 @@ namespace Dino.Ui {
|
||||||
Box box = new Box(Orientation.VERTICAL, 0) { margin=10, visible=true };
|
Box box = new Box(Orientation.VERTICAL, 0) { margin=10, visible=true };
|
||||||
audio_button.clicked.connect(() => {
|
audio_button.clicked.connect(() => {
|
||||||
stream_interactor.get_module(Calls.IDENTITY).initiate_call.begin(conversation, false, (_, res) => {
|
stream_interactor.get_module(Calls.IDENTITY).initiate_call.begin(conversation, false, (_, res) => {
|
||||||
Call call = stream_interactor.get_module(Calls.IDENTITY).initiate_call.end(res);
|
CallState call_state = stream_interactor.get_module(Calls.IDENTITY).initiate_call.end(res);
|
||||||
open_call_window(call);
|
open_call_window(call_state);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
box.add(audio_button);
|
box.add(audio_button);
|
||||||
|
|
||||||
video_button.clicked.connect(() => {
|
video_button.clicked.connect(() => {
|
||||||
stream_interactor.get_module(Calls.IDENTITY).initiate_call.begin(conversation, true, (_, res) => {
|
stream_interactor.get_module(Calls.IDENTITY).initiate_call.begin(conversation, true, (_, res) => {
|
||||||
Call call = stream_interactor.get_module(Calls.IDENTITY).initiate_call.end(res);
|
CallState call_state = stream_interactor.get_module(Calls.IDENTITY).initiate_call.end(res);
|
||||||
open_call_window(call);
|
open_call_window(call_state);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
box.add(video_button);
|
box.add(video_button);
|
||||||
|
@ -68,7 +68,7 @@ namespace Dino.Ui {
|
||||||
popover_menu.visible = true;
|
popover_menu.visible = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
stream_interactor.get_module(Calls.IDENTITY).call_incoming.connect((call, conversation) => {
|
stream_interactor.get_module(Calls.IDENTITY).call_incoming.connect((call, state,conversation) => {
|
||||||
update_button_state();
|
update_button_state();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -76,6 +76,7 @@ namespace Dino.Ui {
|
||||||
update_button_state();
|
update_button_state();
|
||||||
});
|
});
|
||||||
stream_interactor.get_module(PresenceManager.IDENTITY).show_received.connect((jid, account) => {
|
stream_interactor.get_module(PresenceManager.IDENTITY).show_received.connect((jid, account) => {
|
||||||
|
if (this.conversation == null) return;
|
||||||
if (this.conversation.counterpart.equals_bare(jid) && this.conversation.account.equals(account)) {
|
if (this.conversation.counterpart.equals_bare(jid) && this.conversation.account.equals(account)) {
|
||||||
update_visibility.begin();
|
update_visibility.begin();
|
||||||
}
|
}
|
||||||
|
@ -83,11 +84,14 @@ namespace Dino.Ui {
|
||||||
stream_interactor.connection_manager.connection_state_changed.connect((account, state) => {
|
stream_interactor.connection_manager.connection_state_changed.connect((account, state) => {
|
||||||
update_visibility.begin();
|
update_visibility.begin();
|
||||||
});
|
});
|
||||||
|
Dino.Application.get_default().plugin_registry.video_call_plugin.devices_changed.connect((media, incoming) => {
|
||||||
|
update_visibility.begin();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void open_call_window(Call call) {
|
private void open_call_window(CallState call_state) {
|
||||||
var call_window = new CallWindow();
|
var call_window = new CallWindow();
|
||||||
var call_controller = new CallWindowController(call_window, call, stream_interactor);
|
var call_controller = new CallWindowController(call_window, call_state, stream_interactor);
|
||||||
call_window.controller = call_controller;
|
call_window.controller = call_controller;
|
||||||
call_window.present();
|
call_window.present();
|
||||||
|
|
||||||
|
@ -102,17 +106,15 @@ namespace Dino.Ui {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update_button_state() {
|
private void update_button_state() {
|
||||||
Jid? call_counterpart = stream_interactor.get_module(Calls.IDENTITY).is_call_in_progress();
|
this.sensitive = !stream_interactor.get_module(Calls.IDENTITY).is_call_in_progress();
|
||||||
this.sensitive = call_counterpart == null;
|
|
||||||
|
|
||||||
if (call_counterpart != null && call_counterpart.equals_bare(conversation.counterpart)) {
|
|
||||||
this.set_image(new Gtk.Image.from_icon_name("dino-phone-in-talk-symbolic", Gtk.IconSize.MENU) { visible=true });
|
|
||||||
} else {
|
|
||||||
this.set_image(new Gtk.Image.from_icon_name("dino-phone-symbolic", Gtk.IconSize.MENU) { visible=true });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void update_visibility() {
|
private async void update_visibility() {
|
||||||
|
if (conversation == null) {
|
||||||
|
visible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (conversation.type_ == Conversation.Type.CHAT) {
|
if (conversation.type_ == Conversation.Type.CHAT) {
|
||||||
Conversation conv_bak = conversation;
|
Conversation conv_bak = conversation;
|
||||||
bool audio_works = yield stream_interactor.get_module(Calls.IDENTITY).can_do_audio_calls_async(conversation);
|
bool audio_works = yield stream_interactor.get_module(Calls.IDENTITY).can_do_audio_calls_async(conversation);
|
||||||
|
|
|
@ -123,6 +123,7 @@ public class Dino.Ui.FreeDesktopNotifier : NotificationProvider, Object {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void notify_call(Call call, Conversation conversation, bool video, string conversation_display_name) {
|
public async void notify_call(Call call, Conversation conversation, bool video, string conversation_display_name) {
|
||||||
|
debug("[%s] Call notification", call.account.bare_jid.to_string());
|
||||||
string summary = Markup.escape_text(conversation_display_name);
|
string summary = Markup.escape_text(conversation_display_name);
|
||||||
string body = video ? _("Incoming video call") : _("Incoming call");
|
string body = video ? _("Incoming video call") : _("Incoming call");
|
||||||
|
|
||||||
|
@ -140,7 +141,7 @@ public class Dino.Ui.FreeDesktopNotifier : NotificationProvider, Object {
|
||||||
GLib.Application.get_default().activate_action("open-conversation", new Variant.int32(conversation.id));
|
GLib.Application.get_default().activate_action("open-conversation", new Variant.int32(conversation.id));
|
||||||
});
|
});
|
||||||
add_action_listener(notification_id, "reject", () => {
|
add_action_listener(notification_id, "reject", () => {
|
||||||
GLib.Application.get_default().activate_action("deny-call", new Variant.int32(call.id));
|
GLib.Application.get_default().activate_action("reject-call", new Variant.int32(call.id));
|
||||||
});
|
});
|
||||||
add_action_listener(notification_id, "accept", () => {
|
add_action_listener(notification_id, "accept", () => {
|
||||||
GLib.Application.get_default().activate_action("accept-call", new Variant.int32(call.id));
|
GLib.Application.get_default().activate_action("accept-call", new Variant.int32(call.id));
|
||||||
|
|
|
@ -74,7 +74,7 @@ namespace Dino.Ui {
|
||||||
notification.set_icon(new ThemedIcon.from_names(new string[] {"call-start-symbolic"}));
|
notification.set_icon(new ThemedIcon.from_names(new string[] {"call-start-symbolic"}));
|
||||||
|
|
||||||
notification.set_default_action_and_target_value("app.open-conversation", new Variant.int32(conversation.id));
|
notification.set_default_action_and_target_value("app.open-conversation", new Variant.int32(conversation.id));
|
||||||
notification.add_button_with_target_value(_("Deny"), "app.deny-call", new Variant.int32(call.id));
|
notification.add_button_with_target_value(_("Reject"), "app.reject-call", new Variant.int32(call.id));
|
||||||
notification.add_button_with_target_value(_("Accept"), "app.accept-call", new Variant.int32(call.id));
|
notification.add_button_with_target_value(_("Accept"), "app.accept-call", new Variant.int32(call.id));
|
||||||
|
|
||||||
GLib.Application.get_default().send_notification(call.id.to_string(), notification);
|
GLib.Application.get_default().send_notification(call.id.to_string(), notification);
|
||||||
|
|
Loading…
Reference in a new issue