aboutsummaryrefslogtreecommitdiff
path: root/plugins/omemo/src/ui
diff options
context:
space:
mode:
authorfiaxh <git@lightrise.org>2019-05-11 09:39:02 -0600
committerfiaxh <git@lightrise.org>2019-07-08 18:46:30 +0200
commit837de4063dbe398735a5b1d35bde1821c177b555 (patch)
tree81c46027a32a9ff14f2c4932260ac97d3574b848 /plugins/omemo/src/ui
parent701175fcd3b39aff46f52627af74b4de29363058 (diff)
downloaddino-837de4063dbe398735a5b1d35bde1821c177b555.tar.gz
dino-837de4063dbe398735a5b1d35bde1821c177b555.zip
OMEMO: Move files to fitting subdirectory
Diffstat (limited to 'plugins/omemo/src/ui')
-rw-r--r--plugins/omemo/src/ui/account_settings_entry.vala26
-rw-r--r--plugins/omemo/src/ui/account_settings_widget.vala54
-rw-r--r--plugins/omemo/src/ui/contact_details_dialog.vala253
-rw-r--r--plugins/omemo/src/ui/contact_details_provider.vala48
-rw-r--r--plugins/omemo/src/ui/device_notification_populator.vala99
-rw-r--r--plugins/omemo/src/ui/encryption_list_entry.vala23
-rw-r--r--plugins/omemo/src/ui/manage_key_dialog.vala166
-rw-r--r--plugins/omemo/src/ui/own_notifications.vala42
-rw-r--r--plugins/omemo/src/ui/util.vala60
9 files changed, 771 insertions, 0 deletions
diff --git a/plugins/omemo/src/ui/account_settings_entry.vala b/plugins/omemo/src/ui/account_settings_entry.vala
new file mode 100644
index 00000000..3866febe
--- /dev/null
+++ b/plugins/omemo/src/ui/account_settings_entry.vala
@@ -0,0 +1,26 @@
+namespace Dino.Plugins.Omemo {
+
+public class AccountSettingsEntry : Plugins.AccountSettingsEntry {
+ private Plugin plugin;
+
+ public AccountSettingsEntry(Plugin plugin) {
+ this.plugin = plugin;
+ }
+
+ public override string id { get {
+ return "omemo_identity_key";
+ }}
+
+ public override string name { get {
+ return "OMEMO";
+ }}
+
+ public override Plugins.AccountSettingsWidget? get_widget(WidgetType type) {
+ if (type == WidgetType.GTK) {
+ return new AccountSettingWidget(plugin);
+ }
+ return null;
+ }
+}
+
+} \ No newline at end of file
diff --git a/plugins/omemo/src/ui/account_settings_widget.vala b/plugins/omemo/src/ui/account_settings_widget.vala
new file mode 100644
index 00000000..6148da56
--- /dev/null
+++ b/plugins/omemo/src/ui/account_settings_widget.vala
@@ -0,0 +1,54 @@
+using Gtk;
+using Dino.Entities;
+
+namespace Dino.Plugins.Omemo {
+
+public class AccountSettingWidget : Plugins.AccountSettingsWidget, Box {
+ private Plugin plugin;
+ private Label fingerprint;
+ private Account account;
+ private Button btn;
+
+ public AccountSettingWidget(Plugin plugin) {
+ this.plugin = plugin;
+
+ fingerprint = new Label("...");
+ fingerprint.xalign = 0;
+ Border border = new Button().get_style_context().get_padding(StateFlags.NORMAL);
+ fingerprint.margin_top = border.top + 1;
+ fingerprint.margin_start = border.left + 1;
+ fingerprint.visible = true;
+ pack_start(fingerprint);
+
+ btn = new Button();
+ btn.image = new Image.from_icon_name("view-list-symbolic", IconSize.BUTTON);
+ btn.relief = ReliefStyle.NONE;
+ btn.visible = false;
+ btn.valign = Align.CENTER;
+ btn.clicked.connect(() => {
+ activated();
+ ContactDetailsDialog dialog = new ContactDetailsDialog(plugin, account, account.bare_jid);
+ dialog.set_transient_for((Window) get_toplevel());
+ dialog.present();
+ });
+ pack_start(btn, false);
+ }
+
+ public void set_account(Account account) {
+ this.account = account;
+ btn.visible = false;
+ Qlite.Row? row = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id).inner;
+ if (row == null) {
+ fingerprint.set_markup("%s\n<span font='8'>%s</span>".printf(_("Own fingerprint"), _("Will be generated on first connect")));
+ } else {
+ string res = fingerprint_markup(fingerprint_from_base64(((!)row)[plugin.db.identity.identity_key_public_base64]));
+ fingerprint.set_markup("%s\n<span font_family='monospace' font='8'>%s</span>".printf(_("Own fingerprint"), res));
+ btn.visible = true;
+ }
+ }
+
+ public void deactivate() {
+ }
+}
+
+}
diff --git a/plugins/omemo/src/ui/contact_details_dialog.vala b/plugins/omemo/src/ui/contact_details_dialog.vala
new file mode 100644
index 00000000..6899acf6
--- /dev/null
+++ b/plugins/omemo/src/ui/contact_details_dialog.vala
@@ -0,0 +1,253 @@
+using Gtk;
+using Xmpp;
+using Gee;
+using Qlite;
+using Dino.Entities;
+using Qrencode;
+using Gdk;
+
+namespace Dino.Plugins.Omemo {
+
+[GtkTemplate (ui = "/im/dino/Dino/omemo/contact_details_dialog.ui")]
+public class ContactDetailsDialog : Gtk.Dialog {
+
+ private Plugin plugin;
+ private Account account;
+ private Jid jid;
+ private bool own = false;
+ private int own_id = 0;
+
+ [GtkChild] private Label automatically_accept_new_descr;
+ [GtkChild] private Box own_fingerprint_container;
+ [GtkChild] private Label own_fingerprint_label;
+ [GtkChild] private Box new_keys_container;
+ [GtkChild] private ListBox new_keys_listbox;
+ [GtkChild] private Box keys_container;
+ [GtkChild] private ListBox keys_listbox;
+ [GtkChild] private Switch auto_accept_switch;
+ [GtkChild] private Button copy_button;
+ [GtkChild] private Button show_qrcode_button;
+ [GtkChild] private Image qrcode_image;
+ [GtkChild] private Popover qrcode_popover;
+
+ public ContactDetailsDialog(Plugin plugin, Account account, Jid jid) {
+ Object(use_header_bar : Environment.get_variable("GTK_CSD") != "0" ? 1 : 0);
+ this.plugin = plugin;
+ this.account = account;
+ this.jid = jid;
+
+ if (Environment.get_variable("GTK_CSD") != "0") {
+ (get_header_bar() as HeaderBar).set_subtitle(jid.bare_jid.to_string());
+ }
+
+ int identity_id = plugin.db.identity.get_id(account.id);
+ if (identity_id < 0) return;
+
+ // Dialog opened from the account settings menu
+ // Show the fingerprint for this device separately with buttons for a qrcode and to copy
+ if(jid.equals(account.bare_jid)) {
+ own = true;
+ own_id = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id];
+
+ automatically_accept_new_descr.label = _("When you add new encryption keys to your account, automatically accept them.");
+
+ own_fingerprint_container.visible = true;
+
+ string own_b64 = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.identity_key_public_base64];
+ string fingerprint = fingerprint_from_base64(own_b64);
+ own_fingerprint_label.set_markup(fingerprint_markup(fingerprint));
+
+ copy_button.clicked.connect(() => {Clipboard.get_default(get_display()).set_text(fingerprint, fingerprint.length);});
+
+ int sid = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id];
+ Pixbuf qr_pixbuf = new QRcode(@"xmpp:$(account.bare_jid)?omemo-sid-$(sid)=$(fingerprint)", 2).to_pixbuf();
+ qr_pixbuf = qr_pixbuf.scale_simple(150, 150, InterpType.NEAREST);
+
+ Pixbuf pixbuf = new Pixbuf(
+ qr_pixbuf.colorspace,
+ qr_pixbuf.has_alpha,
+ qr_pixbuf.bits_per_sample,
+ 170,
+ 170
+ );
+ pixbuf.fill(uint32.MAX);
+ qr_pixbuf.copy_area(0, 0, 150, 150, pixbuf, 10, 10);
+
+ qrcode_image.set_from_pixbuf(pixbuf);
+ show_qrcode_button.clicked.connect(qrcode_popover.popup);
+ }
+
+ new_keys_listbox.set_header_func(header_function);
+
+ keys_listbox.set_header_func(header_function);
+
+ //Show any new devices for which the user must decide whether to accept or reject
+ foreach (Row device in plugin.db.identity_meta.get_new_devices(identity_id, jid.to_string())) {
+ add_new_fingerprint(device);
+ }
+
+ //Show the normal devicelist
+ foreach (Row device in plugin.db.identity_meta.get_known_devices(identity_id, jid.to_string())) {
+ if(own && device[plugin.db.identity_meta.device_id] == own_id) {
+ continue;
+ }
+ add_fingerprint(device, (Database.IdentityMetaTable.TrustLevel) device[plugin.db.identity_meta.trust_level]);
+ }
+
+ auto_accept_switch.set_active(plugin.db.trust.get_blind_trust(identity_id, jid.bare_jid.to_string()));
+
+ auto_accept_switch.state_set.connect((active) => {
+ plugin.trust_manager.set_blind_trust(account, jid, active);
+
+ if (active) {
+ new_keys_container.visible = false;
+
+ foreach (Row device in plugin.db.identity_meta.get_new_devices(identity_id, jid.to_string())) {
+ plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], Database.IdentityMetaTable.TrustLevel.TRUSTED);
+ add_fingerprint(device, Database.IdentityMetaTable.TrustLevel.TRUSTED);
+ }
+ }
+
+ return false;
+ });
+
+ }
+
+ private void header_function(ListBoxRow row, ListBoxRow? before) {
+ if (row.get_header() == null && before != null) {
+ row.set_header(new Separator(Orientation.HORIZONTAL));
+ }
+ }
+
+ private void set_row(int trust, bool now_active, Image img, Label status_lbl, Label lbl, ListBoxRow lbr){
+ switch(trust) {
+ case Database.IdentityMetaTable.TrustLevel.TRUSTED:
+ img.icon_name = "emblem-ok-symbolic";
+ status_lbl.set_markup("<span color='#1A63D9'>%s</span>".printf(_("Accepted")));
+ lbl.get_style_context().remove_class("dim-label");
+ break;
+ case Database.IdentityMetaTable.TrustLevel.UNTRUSTED:
+ img.icon_name = "action-unavailable-symbolic";
+ status_lbl.set_markup("<span color='#D91900'>%s</span>".printf(_("Rejected")));
+ lbl.get_style_context().add_class("dim-label");
+ break;
+ case Database.IdentityMetaTable.TrustLevel.VERIFIED:
+ img.icon_name = "security-high-symbolic";
+ status_lbl.set_markup("<span color='#1A63D9'>%s</span>".printf(_("Verified")));
+ lbl.get_style_context().remove_class("dim-label");
+ break;
+ }
+
+ if (!now_active) {
+ img.icon_name = "appointment-missed-symbolic";
+ status_lbl.set_markup("<span color='#8b8e8f'>%s</span>".printf(_("Unused")));
+ lbr.activatable = false;
+ }
+ }
+
+ private void add_fingerprint(Row device, Database.IdentityMetaTable.TrustLevel trust) {
+ keys_container.visible = true;
+
+ ListBoxRow lbr = new ListBoxRow() { visible = true, activatable = true, hexpand = true };
+ Box box = new Box(Gtk.Orientation.HORIZONTAL, 40) { visible = true, margin_start = 20, margin_end = 20, margin_top = 14, margin_bottom = 14, hexpand = true };
+
+ Box status_box = new Box(Gtk.Orientation.HORIZONTAL, 5) { visible = true, hexpand = true };
+ Label status_lbl = new Label(null) { visible = true, hexpand = true, xalign = 0 };
+
+ Image img = new Image() { visible = true, halign = Align.END, icon_size = IconSize.BUTTON };
+
+ string res = fingerprint_markup(fingerprint_from_base64(device[plugin.db.identity_meta.identity_key_public_base64]));
+ Label lbl = new Label(res)
+ { use_markup=true, justify=Justification.RIGHT, visible=true, halign = Align.START, valign = Align.CENTER, hexpand = false };
+
+ set_row(trust, device[plugin.db.identity_meta.now_active], img, status_lbl, lbl, lbr);
+
+ box.add(lbl);
+ box.add(status_box);
+
+ status_box.add(status_lbl);
+ status_box.add(img);
+
+ lbr.add(box);
+ keys_listbox.add(lbr);
+
+ //Row clicked - pull the most up to date device info from the database and show the manage window
+ keys_listbox.row_activated.connect((row) => {
+ if(row == lbr) {
+ Row updated_device = plugin.db.identity_meta.get_device(device[plugin.db.identity_meta.identity_id], device[plugin.db.identity_meta.address_name], device[plugin.db.identity_meta.device_id]);
+ ManageKeyDialog manage_dialog = new ManageKeyDialog(updated_device, plugin.db);
+ manage_dialog.set_transient_for((Gtk.Window) get_toplevel());
+ manage_dialog.present();
+ manage_dialog.response.connect((response) => {
+ set_row(response, device[plugin.db.identity_meta.now_active], img, status_lbl, lbl, lbr);
+ update_device(response, device);
+ });
+ }
+ });
+ }
+
+ private void update_device(int response, Row device){
+ switch (response) {
+ case Database.IdentityMetaTable.TrustLevel.TRUSTED:
+ plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], Database.IdentityMetaTable.TrustLevel.TRUSTED);
+ break;
+ case Database.IdentityMetaTable.TrustLevel.UNTRUSTED:
+ plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], Database.IdentityMetaTable.TrustLevel.UNTRUSTED);
+ break;
+ case Database.IdentityMetaTable.TrustLevel.VERIFIED:
+ plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], Database.IdentityMetaTable.TrustLevel.VERIFIED);
+ plugin.trust_manager.set_blind_trust(account, jid, false);
+ auto_accept_switch.set_active(false);
+ break;
+ }
+ }
+
+ private void add_new_fingerprint(Row device){
+ new_keys_container.visible = true;
+
+ ListBoxRow lbr = new ListBoxRow() { visible = true, activatable = false, hexpand = true };
+ Box box = new Box(Gtk.Orientation.HORIZONTAL, 40) { visible = true, margin_start = 20, margin_end = 20, margin_top = 14, margin_bottom = 14, hexpand = true };
+
+ Box control_box = new Box(Gtk.Orientation.HORIZONTAL, 0) { visible = true, hexpand = true };
+
+ Button yes_button = new Button() { visible = true, valign = Align.CENTER, hexpand = true };
+ yes_button.image = new Image.from_icon_name("emblem-ok-symbolic", IconSize.BUTTON);
+ yes_button.get_style_context().add_class("suggested-action");
+
+ Button no_button = new Button() { visible = true, valign = Align.CENTER, hexpand = true };
+ no_button.image = new Image.from_icon_name("action-unavailable-symbolic", IconSize.BUTTON);
+ no_button.get_style_context().add_class("destructive-action");
+
+ yes_button.clicked.connect(() => {
+ plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], Database.IdentityMetaTable.TrustLevel.TRUSTED);
+ add_fingerprint(device, Database.IdentityMetaTable.TrustLevel.TRUSTED);
+ new_keys_listbox.remove(lbr);
+ if (new_keys_listbox.get_children().length() < 1) new_keys_container.visible = false;
+ });
+
+ no_button.clicked.connect(() => {
+ plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], Database.IdentityMetaTable.TrustLevel.UNTRUSTED);
+ add_fingerprint(device, Database.IdentityMetaTable.TrustLevel.UNTRUSTED);
+ new_keys_listbox.remove(lbr);
+ if (new_keys_listbox.get_children().length() < 1) new_keys_container.visible = false;
+ });
+
+ string res = fingerprint_markup(fingerprint_from_base64(device[plugin.db.identity_meta.identity_key_public_base64]));
+ Label lbl = new Label(res)
+ { use_markup=true, justify=Justification.RIGHT, visible=true, halign = Align.START, valign = Align.CENTER, hexpand = false };
+
+
+ box.add(lbl);
+
+ control_box.add(yes_button);
+ control_box.add(no_button);
+ control_box.get_style_context().add_class("linked");
+
+ box.add(control_box);
+
+ lbr.add(box);
+ new_keys_listbox.add(lbr);
+ }
+}
+
+}
diff --git a/plugins/omemo/src/ui/contact_details_provider.vala b/plugins/omemo/src/ui/contact_details_provider.vala
new file mode 100644
index 00000000..7250d135
--- /dev/null
+++ b/plugins/omemo/src/ui/contact_details_provider.vala
@@ -0,0 +1,48 @@
+using Gtk;
+using Gee;
+using Qlite;
+using Dino.Entities;
+
+namespace Dino.Plugins.Omemo {
+
+public class ContactDetailsProvider : Plugins.ContactDetailsProvider, Object {
+ public string id { get { return "omemo_info"; } }
+
+ private Plugin plugin;
+
+ public ContactDetailsProvider(Plugin plugin) {
+ this.plugin = plugin;
+ }
+
+ public void populate(Conversation conversation, Plugins.ContactDetails contact_details, WidgetType type) {
+ if (conversation.type_ == Conversation.Type.CHAT && type == WidgetType.GTK) {
+
+ int identity_id = plugin.db.identity.get_id(conversation.account.id);
+ if (identity_id < 0) return;
+
+ int i = 0;
+ foreach (Row row in plugin.db.identity_meta.with_address(identity_id, conversation.counterpart.to_string())) {
+ if (row[plugin.db.identity_meta.identity_key_public_base64] != null) {
+ i++;
+ }
+ }
+
+ if (i > 0) {
+ Button btn = new Button.from_icon_name("view-list-symbolic") { visible = true, valign = Align.CENTER, relief = ReliefStyle.NONE };
+ btn.clicked.connect(() => {
+ btn.activate();
+ ContactDetailsDialog dialog = new ContactDetailsDialog(plugin, conversation.account, conversation.counterpart);
+ dialog.set_transient_for((Window) btn.get_toplevel());
+ dialog.response.connect((response_type) => {
+ plugin.device_notification_populator.should_hide();
+ });
+ dialog.present();
+ });
+
+ contact_details.add(_("Encryption"), "OMEMO", n("%d OMEMO device", "%d OMEMO devices", i).printf(i), btn);
+ }
+ }
+ }
+}
+
+}
diff --git a/plugins/omemo/src/ui/device_notification_populator.vala b/plugins/omemo/src/ui/device_notification_populator.vala
new file mode 100644
index 00000000..5b47611c
--- /dev/null
+++ b/plugins/omemo/src/ui/device_notification_populator.vala
@@ -0,0 +1,99 @@
+using Dino.Entities;
+using Xmpp;
+using Gtk;
+
+namespace Dino.Plugins.Omemo {
+
+public class DeviceNotificationPopulator : NotificationPopulator, Object {
+
+ public string id { get { return "device_notification"; } }
+
+ private StreamInteractor? stream_interactor;
+ private Plugin plugin;
+ private Conversation? current_conversation;
+ private NotificationCollection? notification_collection;
+ private ConversationNotification notification;
+
+ public DeviceNotificationPopulator(Plugin plugin, StreamInteractor stream_interactor) {
+ this.stream_interactor = stream_interactor;
+ this.plugin = plugin;
+
+ stream_interactor.account_added.connect(on_account_added);
+ }
+
+ public bool has_new_devices(Jid jid) {
+ int identity_id = plugin.db.identity.get_id(current_conversation.account.id);
+ if (identity_id < 0) return false;
+ return plugin.db.identity_meta.get_new_devices(identity_id, jid.bare_jid.to_string()).count() > 0;
+ }
+
+ public void init(Conversation conversation, NotificationCollection notification_collection, Plugins.WidgetType type) {
+ current_conversation = conversation;
+ this.notification_collection = notification_collection;
+ if (has_new_devices(conversation.counterpart) && conversation.type_ == Conversation.Type.CHAT) {
+ display_notification();
+ }
+ }
+
+ public void close(Conversation conversation) {
+ notification = null;
+ }
+
+ private void display_notification() {
+ if (notification == null) {
+ notification = new ConversationNotification(plugin, current_conversation.account, current_conversation.counterpart);
+ notification.should_hide.connect(should_hide);
+ notification_collection.add_meta_notification(notification);
+ }
+ }
+
+ public void should_hide() {
+ if (!has_new_devices(current_conversation.counterpart) && notification != null){
+ notification_collection.remove_meta_notification(notification);
+ notification = null;
+ }
+ }
+
+ private void on_account_added(Account account) {
+ stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).bundle_fetched.connect_after((jid, device_id, bundle) => {
+ if (current_conversation != null && jid.equals(current_conversation.counterpart) && has_new_devices(current_conversation.counterpart)) {
+ display_notification();
+ }
+ });
+ }
+}
+
+private class ConversationNotification : MetaConversationNotification {
+ private Widget widget;
+ private Plugin plugin;
+ private Jid jid;
+ private Account account;
+ public signal void should_hide();
+
+ public ConversationNotification(Plugin plugin, Account account, Jid jid) {
+ this.plugin = plugin;
+ this.jid = jid;
+ this.account = account;
+
+ Box box = new Box(Orientation.HORIZONTAL, 5) { visible=true };
+ Button manage_button = new Button() { label=_("Manage"), visible=true };
+ manage_button.clicked.connect(() => {
+ manage_button.activate();
+ ContactDetailsDialog dialog = new ContactDetailsDialog(plugin, account, jid);
+ dialog.set_transient_for((Window) manage_button.get_toplevel());
+ dialog.response.connect((response_type) => {
+ should_hide();
+ });
+ dialog.present();
+ });
+ box.add(new Label(_("This contact has new devices")) { margin_end=10, visible=true });
+ box.add(manage_button);
+ widget = box;
+ }
+
+ public override Object? get_widget(WidgetType type) {
+ return widget;
+ }
+}
+
+}
diff --git a/plugins/omemo/src/ui/encryption_list_entry.vala b/plugins/omemo/src/ui/encryption_list_entry.vala
new file mode 100644
index 00000000..2e8905e2
--- /dev/null
+++ b/plugins/omemo/src/ui/encryption_list_entry.vala
@@ -0,0 +1,23 @@
+namespace Dino.Plugins.Omemo {
+
+public class EncryptionListEntry : Plugins.EncryptionListEntry, Object {
+ private Plugin plugin;
+
+ public EncryptionListEntry(Plugin plugin) {
+ this.plugin = plugin;
+ }
+
+ public Entities.Encryption encryption { get {
+ return Entities.Encryption.OMEMO;
+ }}
+
+ public string name { get {
+ return "OMEMO";
+ }}
+
+ public bool can_encrypt(Entities.Conversation conversation) {
+ return plugin.app.stream_interactor.get_module(Manager.IDENTITY).can_encrypt(conversation);
+ }
+}
+
+}
diff --git a/plugins/omemo/src/ui/manage_key_dialog.vala b/plugins/omemo/src/ui/manage_key_dialog.vala
new file mode 100644
index 00000000..87d43de8
--- /dev/null
+++ b/plugins/omemo/src/ui/manage_key_dialog.vala
@@ -0,0 +1,166 @@
+using Gtk;
+using Qlite;
+
+namespace Dino.Plugins.Omemo {
+
+[GtkTemplate (ui = "/im/dino/Dino/omemo/manage_key_dialog.ui")]
+public class ManageKeyDialog : Gtk.Dialog {
+
+ [GtkChild] private Stack manage_stack;
+
+ [GtkChild] private Button cancel_button;
+ [GtkChild] private Button ok_button;
+
+ [GtkChild] private Label main_desc_label;
+ [GtkChild] private ListBox main_action_list;
+
+ [GtkChild] private Image confirm_image;
+ [GtkChild] private Label confirm_title_label;
+ [GtkChild] private Label confirm_desc_label;
+
+ [GtkChild] private Label verify_label;
+ [GtkChild] private Button verify_yes_button;
+ [GtkChild] private Button verify_no_button;
+
+ private Row device;
+ private Database db;
+
+ private bool return_to_main;
+ private int current_response;
+
+ public ManageKeyDialog(Row device, Database db) {
+ Object(use_header_bar : Environment.get_variable("GTK_CSD") != "0" ? 1 : 0);
+
+ this.device = device;
+ this.db = db;
+
+ setup_main_screen();
+ setup_verify_screen();
+
+ cancel_button.clicked.connect(handle_cancel);
+ ok_button.clicked.connect(() => {
+ response(current_response);
+ close();
+ });
+
+ verify_yes_button.clicked.connect(() => {
+ confirm_image.set_from_icon_name("security-high-symbolic", IconSize.DIALOG);
+ confirm_title_label.label = _("Verify key");
+ confirm_desc_label.set_markup(_("Once confirmed, any future messages sent by %s using this key will be highlighted accordingly in the chat window.").printf(@"<b>$(device[db.identity_meta.address_name])</b>"));
+ manage_stack.set_visible_child_name("confirm");
+ ok_button.sensitive = true;
+ return_to_main = false;
+ current_response = Database.IdentityMetaTable.TrustLevel.VERIFIED;
+ });
+
+ verify_no_button.clicked.connect(() => {
+ return_to_main = false;
+ confirm_image.set_from_icon_name("dialog-warning-symbolic", IconSize.DIALOG);
+ confirm_title_label.label = _("Fingerprints do not match");
+ confirm_desc_label.set_markup(_("Please verify that you are comparing the correct fingerprint. If fingerprints do not match, %s's account may be compromised and you should consider rejecting this key.").printf(@"<b>$(device[db.identity_meta.address_name])</b>"));
+ manage_stack.set_visible_child_name("confirm");
+ });
+ }
+
+ private void handle_cancel() {
+ if (manage_stack.get_visible_child_name() == "main") close();
+
+ if (manage_stack.get_visible_child_name() == "verify") {
+ manage_stack.set_visible_child_name("main");
+ cancel_button.label = _("Cancel");
+ }
+
+ if (manage_stack.get_visible_child_name() == "confirm") {
+ if (return_to_main) {
+ manage_stack.set_visible_child_name("main");
+ cancel_button.label = _("Cancel");
+ } else {
+ manage_stack.set_visible_child_name("verify");
+ }
+ }
+
+ ok_button.sensitive = false;
+ }
+
+ private Box make_action_box(string title, string desc){
+ Box box = new Box(Orientation.VERTICAL, 0) { visible = true, margin_start = 20, margin_end = 20, margin_top = 14, margin_bottom = 14 };
+ Label lbl_title = new Label(title) { visible = true, halign = Align.START };
+ Label lbl_desc = new Label(desc) { visible = true, xalign = 0, wrap = true, max_width_chars = 40 };
+
+ Pango.AttrList title_attrs = new Pango.AttrList();
+ title_attrs.insert(Pango.attr_scale_new(1.1));
+ lbl_title.attributes = title_attrs;
+ Pango.AttrList desc_attrs = new Pango.AttrList();
+ desc_attrs.insert(Pango.attr_scale_new(0.8));
+ lbl_desc.attributes = desc_attrs;
+ lbl_desc.get_style_context().add_class("dim-label");
+
+ box.add(lbl_title);
+ box.add(lbl_desc);
+
+ return box;
+ }
+
+ private void setup_main_screen() {
+ main_action_list.set_header_func((row, before_row) => {
+ if (row.get_header() == null && before_row != null) {
+ row.set_header(new Separator(Orientation.HORIZONTAL));
+ }
+ });
+
+ ListBoxRow verify_row = new ListBoxRow() { visible = true };
+ verify_row.add(make_action_box(_("Verify key fingerprint"), _("Compare this key's fingerprint with the fingerprint displayed on the contact's device.")));
+ ListBoxRow reject_row = new ListBoxRow() { visible = true };
+ reject_row.add(make_action_box(_("Reject key"), _("Stop accepting this key during communication with its associated contact.")));
+ ListBoxRow accept_row = new ListBoxRow() {visible = true };
+ accept_row.add(make_action_box(_("Accept key"), _("Start accepting this key during communication with its associated contact")));
+
+ switch((Database.IdentityMetaTable.TrustLevel) device[db.identity_meta.trust_level]) {
+ case Database.IdentityMetaTable.TrustLevel.TRUSTED:
+ main_desc_label.set_markup(_("This key is currently %s.").printf("<span color='#1A63D9'>"+_("accepted")+"</span>")+" "+_("This means it can be used by %s to receive and send messages.").printf(@"<b>$(device[db.identity_meta.address_name])</b>"));
+ main_action_list.add(verify_row);
+ main_action_list.add(reject_row);
+ break;
+ case Database.IdentityMetaTable.TrustLevel.VERIFIED:
+ main_desc_label.set_markup(_("This key is currently %s.").printf("<span color='#1A63D9'>"+_("verified")+"</span>")+" "+_("This means it can be used by %s to receive and send messages.") + " " + _("Additionally it has been verified to match the key on the contact's device.").printf(@"<b>$(device[db.identity_meta.address_name])</b>"));
+ main_action_list.add(reject_row);
+ break;
+ case Database.IdentityMetaTable.TrustLevel.UNTRUSTED:
+ main_desc_label.set_markup(_("This key is currently %s.").printf("<span color='#D91900'>"+_("rejected")+"</span>")+" "+_("This means it cannot be used by %s to receive messages, and any messages sent by it will be ignored.").printf(@"<b>$(device[db.identity_meta.address_name])</b>"));
+ main_action_list.add(accept_row);
+ break;
+ }
+
+ //Row clicked - go to appropriate screen
+ main_action_list.row_activated.connect((row) => {
+ if(row == verify_row) {
+ manage_stack.set_visible_child_name("verify");
+ } else if (row == reject_row) {
+ confirm_image.set_from_icon_name("action-unavailable-symbolic", IconSize.DIALOG);
+ confirm_title_label.label = _("Reject key");
+ confirm_desc_label.set_markup(_("Once confirmed, any future messages sent by %s using this key will be ignored and none of your messages will be readable using this key.").printf(@"<b>$(device[db.identity_meta.address_name])</b>"));
+ manage_stack.set_visible_child_name("confirm");
+ ok_button.sensitive = true;
+ return_to_main = true;
+ current_response = Database.IdentityMetaTable.TrustLevel.UNTRUSTED;
+ } else if (row == accept_row) {
+ confirm_image.set_from_icon_name("emblem-ok-symbolic", IconSize.DIALOG);
+ confirm_title_label.label = _("Accept key");
+ confirm_desc_label.set_markup(_("Once confirmed this key will be usable by %s to receive and send messages.").printf(@"<b>$(device[db.identity_meta.address_name])</b>"));
+ manage_stack.set_visible_child_name("confirm");
+ ok_button.sensitive = true;
+ return_to_main = true;
+ current_response = Database.IdentityMetaTable.TrustLevel.TRUSTED;
+ }
+ cancel_button.label = _("Back");
+ });
+
+ manage_stack.set_visible_child_name("main");
+ }
+
+ private void setup_verify_screen() {
+ verify_label.set_markup(fingerprint_markup(fingerprint_from_base64(device[db.identity_meta.identity_key_public_base64])));
+ }
+}
+
+}
diff --git a/plugins/omemo/src/ui/own_notifications.vala b/plugins/omemo/src/ui/own_notifications.vala
new file mode 100644
index 00000000..f882d03a
--- /dev/null
+++ b/plugins/omemo/src/ui/own_notifications.vala
@@ -0,0 +1,42 @@
+using Dino.Entities;
+using Xmpp;
+using Gtk;
+
+namespace Dino.Plugins.Omemo {
+
+public class OwnNotifications {
+
+ private StreamInteractor stream_interactor;
+ private Plugin plugin;
+ private Account account;
+
+ public OwnNotifications (Plugin plugin, StreamInteractor stream_interactor, Account account) {
+ this.stream_interactor = (!)stream_interactor;
+ this.plugin = plugin;
+ this.account = account;
+ stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).bundle_fetched.connect_after((jid, device_id, bundle) => {
+ if (jid.equals(account.bare_jid) && has_new_devices(account.bare_jid)) {
+ display_notification();
+ }
+ });
+
+ if (has_new_devices(account.bare_jid)) {
+ display_notification();
+ }
+ }
+
+ public bool has_new_devices(Jid jid) {
+ int identity_id = plugin.db.identity.get_id(account.id);
+ if (identity_id < 0) return false;
+
+ return plugin.db.identity_meta.get_new_devices(identity_id, jid.bare_jid.to_string()).count() > 0;
+ }
+
+ private void display_notification() {
+ Notification notification = new Notification(_("OMEMO trust decision required"));
+ notification.set_default_action_and_target_value("app.own-keys", new Variant.int32(account.id));
+ notification.set_body(_("Did you add a new device for account %s?").printf(@"$(account.bare_jid.to_string())"));
+ plugin.app.send_notification(account.id.to_string()+"-new-device", notification);
+ }
+}
+}
diff --git a/plugins/omemo/src/ui/util.vala b/plugins/omemo/src/ui/util.vala
new file mode 100644
index 00000000..88d30b3b
--- /dev/null
+++ b/plugins/omemo/src/ui/util.vala
@@ -0,0 +1,60 @@
+namespace Dino.Plugins.Omemo {
+
+public static string fingerprint_from_base64(string b64) {
+ uint8[] arr = Base64.decode(b64);
+
+ arr = arr[1:arr.length];
+ string s = "";
+ foreach (uint8 i in arr) {
+ string tmp = i.to_string("%x");
+ if (tmp.length == 1) tmp = "0" + tmp;
+ s = s + tmp;
+ }
+
+ return s;
+}
+
+public static string fingerprint_markup(string s) {
+ string markup = "";
+ for (int i = 0; i < s.length; i += 4) {
+ string four_chars = s.substring(i, 4).down();
+
+ int raw = (int) four_chars.to_long(null, 16);
+ uint8[] bytes = {(uint8) ((raw >> 8) & 0xff - 128), (uint8) (raw & 0xff - 128)};
+
+ Checksum checksum = new Checksum(ChecksumType.SHA1);
+ checksum.update(bytes, bytes.length);
+ uint8[] digest = new uint8[20];
+ size_t len = 20;
+ checksum.get_digest(digest, ref len);
+
+ uint8 r = digest[0];
+ uint8 g = digest[1];
+ uint8 b = digest[2];
+
+ if (r == 0 && g == 0 && b == 0) r = g = b = 1;
+
+ double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b;
+
+ if (brightness < 80) {
+ double factor = 80.0 / brightness;
+ r = uint8.min(255, (uint8) (r * factor));
+ g = uint8.min(255, (uint8) (g * factor));
+ b = uint8.min(255, (uint8) (b * factor));
+
+ } else if (brightness > 180) {
+ double factor = 180.0 / brightness;
+ r = (uint8) (r * factor);
+ g = (uint8) (g * factor);
+ b = (uint8) (b * factor);
+ }
+
+ if (i % 32 == 0 && i != 0) markup += "\n";
+ markup += @"<span foreground=\"$("#%02x%02x%02x".printf(r, g, b))\">$four_chars</span>";
+ if (i % 8 == 4 && i % 32 != 28) markup += " ";
+ }
+
+ return "<span font_family='monospace' font='8'>" + markup + "</span>";
+}
+
+} \ No newline at end of file