From 21ae42762d8a57da5cb1ec40b46e7510fc3121ad Mon Sep 17 00:00:00 2001 From: fiaxh Date: Thu, 20 Jun 2024 12:05:20 +0200 Subject: Redesign and rewrite accounts and settings dialog into a combined one --- plugins/omemo/CMakeLists.txt | 14 +- plugins/omemo/data/encryption_preferences_entry.ui | 81 +++++ plugins/omemo/data/gresource.xml | 1 + plugins/omemo/meson.build | 3 +- plugins/omemo/src/plugin.vala | 4 +- plugins/omemo/src/ui/account_settings_entry.vala | 58 ---- .../omemo/src/ui/encryption_preferences_entry.vala | 336 +++++++++++++++++++++ plugins/omemo/src/ui/util.vala | 46 +-- plugins/openpgp/CMakeLists.txt | 26 +- plugins/openpgp/data/account_settings_item.ui | 31 -- plugins/openpgp/meson.build | 2 +- plugins/openpgp/src/account_settings_entry.vala | 163 ---------- .../openpgp/src/encryption_preferences_entry.vala | 86 ++++++ plugins/openpgp/src/plugin.vala | 4 +- 14 files changed, 543 insertions(+), 312 deletions(-) create mode 100644 plugins/omemo/data/encryption_preferences_entry.ui delete mode 100644 plugins/omemo/src/ui/account_settings_entry.vala create mode 100644 plugins/omemo/src/ui/encryption_preferences_entry.vala delete mode 100644 plugins/openpgp/data/account_settings_item.ui delete mode 100644 plugins/openpgp/src/account_settings_entry.vala create mode 100644 plugins/openpgp/src/encryption_preferences_entry.vala (limited to 'plugins') diff --git a/plugins/omemo/CMakeLists.txt b/plugins/omemo/CMakeLists.txt index 7ecaa0b8..9e290390 100644 --- a/plugins/omemo/CMakeLists.txt +++ b/plugins/omemo/CMakeLists.txt @@ -3,8 +3,10 @@ find_package(Gettext) include(${GETTEXT_USE_FILE}) gettext_compile(${GETTEXT_PACKAGE} SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/po TARGET_NAME ${GETTEXT_PACKAGE}-translations) +find_package(Adwaita REQUIRED) find_package(Qrencode REQUIRED) find_packages(OMEMO_PACKAGES REQUIRED + Adwaita Gee GLib GModule @@ -19,6 +21,7 @@ find_package(SignalProtocol 2.3.2 REQUIRED) set(RESOURCE_LIST contact_details_dialog.ui + encryption_preferences_entry.ui manage_key_dialog.ui ) @@ -31,6 +34,13 @@ compile_gresources( PREFIX /im/dino/Dino/omemo SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data ) +set(OMEMO_DEFINITIONS) +if(Adwaita_VERSION VERSION_GREATER_EQUAL "1.2") + set(OMEMO_DEFINITIONS ${OMEMO_DEFINITIONS} Adw_1_2) +endif() + +message(STATUS ${Adwaita_VERSION}) +message(STATUS ${Adw_1_2}) vala_precompile(OMEMO_VALA_C SOURCES @@ -65,7 +75,6 @@ SOURCES src/signal/store.vala src/signal/util.vala - src/ui/account_settings_entry.vala src/ui/bad_messages_populator.vala src/ui/call_encryption_entry.vala src/ui/contact_details_provider.vala @@ -73,6 +82,7 @@ SOURCES src/ui/device_notification_populator.vala src/ui/own_notifications.vala src/ui/encryption_list_entry.vala + src/ui/encryption_preferences_entry.vala src/ui/manage_key_dialog.vala src/ui/util.vala CUSTOM_VAPIS @@ -86,6 +96,8 @@ PACKAGES ${OMEMO_PACKAGES} GRESOURCES ${OMEMO_GRESOURCES_XML} +DEFINITIONS + ${OMEMO_DEFINITIONS} GENERATE_VAPI omemo GENERATE_HEADER diff --git a/plugins/omemo/data/encryption_preferences_entry.ui b/plugins/omemo/data/encryption_preferences_entry.ui new file mode 100644 index 00000000..7ca26224 --- /dev/null +++ b/plugins/omemo/data/encryption_preferences_entry.ui @@ -0,0 +1,81 @@ + + + + + + left + + + 10 + 10 + 10 + 10 + + + False + + + + + + diff --git a/plugins/omemo/data/gresource.xml b/plugins/omemo/data/gresource.xml index 616dcdc1..673c3df5 100644 --- a/plugins/omemo/data/gresource.xml +++ b/plugins/omemo/data/gresource.xml @@ -2,6 +2,7 @@ contact_details_dialog.ui + encryption_preferences_entry.ui manage_key_dialog.ui diff --git a/plugins/omemo/meson.build b/plugins/omemo/meson.build index 57eec2ce..05d7c265 100644 --- a/plugins/omemo/meson.build +++ b/plugins/omemo/meson.build @@ -1,5 +1,6 @@ subdir('po') dependencies = [ + dep_libadwaita, dep_crypto_vala, dep_dino, dep_gee, @@ -40,13 +41,13 @@ sources = files( 'src/signal/store.vala', 'src/signal/util.vala', 'src/trust_level.vala', - 'src/ui/account_settings_entry.vala', 'src/ui/bad_messages_populator.vala', 'src/ui/call_encryption_entry.vala', 'src/ui/contact_details_dialog.vala', 'src/ui/contact_details_provider.vala', 'src/ui/device_notification_populator.vala', 'src/ui/encryption_list_entry.vala', + 'src/ui/encryption_preferences_entry.vala', 'src/ui/manage_key_dialog.vala', 'src/ui/own_notifications.vala', 'src/ui/util.vala', diff --git a/plugins/omemo/src/plugin.vala b/plugins/omemo/src/plugin.vala index 643428a8..dfbe0780 100644 --- a/plugins/omemo/src/plugin.vala +++ b/plugins/omemo/src/plugin.vala @@ -30,7 +30,6 @@ public class Plugin : RootInterface, Object { public Dino.Application app; public Database db; public EncryptionListEntry list_entry; - public AccountSettingsEntry settings_entry; public ContactDetailsProvider contact_details_provider; public DeviceNotificationPopulator device_notification_populator; public OwnNotifications own_notifications; @@ -43,13 +42,12 @@ public class Plugin : RootInterface, Object { this.app = app; this.db = new Database(Path.build_filename(Application.get_storage_dir(), "omemo.db")); this.list_entry = new EncryptionListEntry(this); - this.settings_entry = new AccountSettingsEntry(this); this.contact_details_provider = new ContactDetailsProvider(this); this.device_notification_populator = new DeviceNotificationPopulator(this, this.app.stream_interactor); this.trust_manager = new TrustManager(this.app.stream_interactor, this.db); this.app.plugin_registry.register_encryption_list_entry(list_entry); - this.app.plugin_registry.register_account_settings_entry(settings_entry); + this.app.plugin_registry.register_encryption_preferences_entry(new OmemoPreferencesEntry(this)); this.app.plugin_registry.register_contact_details_entry(contact_details_provider); this.app.plugin_registry.register_notification_populator(device_notification_populator); this.app.plugin_registry.register_conversation_addition_populator(new BadMessagesPopulator(this.app.stream_interactor, this)); diff --git a/plugins/omemo/src/ui/account_settings_entry.vala b/plugins/omemo/src/ui/account_settings_entry.vala deleted file mode 100644 index 8736260b..00000000 --- a/plugins/omemo/src/ui/account_settings_entry.vala +++ /dev/null @@ -1,58 +0,0 @@ -using Dino.Entities; -using Gtk; - -namespace Dino.Plugins.Omemo { - -public class AccountSettingsEntry : Plugins.AccountSettingsEntry { - private Plugin plugin; - private Account account; - - private Box box = new Box(Orientation.HORIZONTAL, 0); - private Label fingerprint = new Label("...") { xalign=0 }; - private Button btn = new Button.from_icon_name("view-list-symbolic") { has_frame=false, valign=Align.CENTER, visible=false }; - - public override string id { get { return "omemo_identity_key"; }} - - public override string name { get { return "OMEMO"; }} - - public AccountSettingsEntry(Plugin plugin) { - this.plugin = plugin; - - Border border = new Button().get_style_context().get_padding(); - fingerprint.margin_top = border.top + 1; - fingerprint.margin_start = border.left + 1; - fingerprint.visible = true; - box.append(fingerprint); - - btn.clicked.connect(() => { - activated(); - ContactDetailsDialog dialog = new ContactDetailsDialog(plugin, account, account.bare_jid); - dialog.set_transient_for((Window) box.get_root()); - dialog.present(); - }); - // TODO expand=false? - box.append(btn); - } - - public override Object? get_widget(WidgetType type) { - if (type != WidgetType.GTK4) return null; - return box; - } - - public override 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%s".printf(_("Own fingerprint"), _("Will be generated on first connection"))); - } else { - string res = fingerprint_markup(fingerprint_from_base64(((!)row)[plugin.db.identity.identity_key_public_base64])); - fingerprint.set_markup("%s\n%s".printf(_("Own fingerprint"), res)); - btn.visible = true; - } - } - - public override void deactivate() { } -} - -} \ No newline at end of file diff --git a/plugins/omemo/src/ui/encryption_preferences_entry.vala b/plugins/omemo/src/ui/encryption_preferences_entry.vala new file mode 100644 index 00000000..7997f04d --- /dev/null +++ b/plugins/omemo/src/ui/encryption_preferences_entry.vala @@ -0,0 +1,336 @@ +using Qlite; +using Qrencode; +using Gee; +using Xmpp; +using Dino.Entities; +using Gtk; + +namespace Dino.Plugins.Omemo { + +public class OmemoPreferencesEntry : Plugins.EncryptionPreferencesEntry { + + OmemoPreferencesWidget widget; + Plugin plugin; + + public OmemoPreferencesEntry(Plugin plugin) { + this.plugin = plugin; + } + + public override Object? get_widget(Account account, WidgetType type) { + if (type != WidgetType.GTK4) return null; + var widget = new OmemoPreferencesWidget(plugin); + widget.set_account(account); + return widget; + } + + public override string id { get { return "omemo_preferences_entryption"; }} +} + +[GtkTemplate (ui = "/im/dino/Dino/omemo/encryption_preferences_entry.ui")] +public class OmemoPreferencesWidget : Adw.PreferencesGroup { + private Plugin plugin; + private Account account; + private Jid jid; + private int identity_id = 0; + private Signal.Store store; + private Set displayed_ids = new HashSet(); + + [GtkChild] private unowned Adw.ActionRow automatically_accept_new_row; + [GtkChild] private Switch automatically_accept_new_switch; + [GtkChild] private unowned Adw.ActionRow encrypt_by_default_row; + [GtkChild] private Switch encrypt_by_default_switch; + [GtkChild] private unowned Label new_keys_label; + + [GtkChild] private unowned Adw.PreferencesGroup keys_preferences_group; + [GtkChild] private unowned ListBox new_keys_listbox; + [GtkChild] private unowned Picture qrcode_picture; + [GtkChild] private unowned Popover qrcode_popover; + + private ArrayList keys_preferences_group_children = new ArrayList(); + + construct { + // If we set the strings in the .ui file, they don't get translated + encrypt_by_default_row.title = _("OMEMO by default"); + encrypt_by_default_row.subtitle = _("Enable OMEMO encryption for new conversations"); + automatically_accept_new_row.title = _("Encrypt to new devices"); + automatically_accept_new_row.subtitle = _("Automatically encrypt to new devices from this contact."); + new_keys_label.label = _("New keys"); + } + + public OmemoPreferencesWidget(Plugin plugin) { + this.plugin = plugin; + this.account = account; + this.jid = jid; + } + + public void set_account(Account account) { + this.account = account; + this.jid = account.bare_jid; + + automatically_accept_new_switch.set_active(plugin.db.trust.get_blind_trust(identity_id, jid.bare_jid.to_string(), true)); + automatically_accept_new_switch.state_set.connect(on_auto_accept_toggled); + + encrypt_by_default_switch.set_active(plugin.app.settings.get_default_encryption(account) != Encryption.NONE); + encrypt_by_default_switch.state_set.connect(on_omemo_by_default_toggled); + + identity_id = plugin.db.identity.get_id(account.id); + if (identity_id < 0) return; + Dino.Application? app = Application.get_default() as Dino.Application; + if (app != null) { + store = app.stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).store; + } + + redraw_key_list(); + + // Check for unknown devices + fetch_unknown_bundles(); + } + + private void redraw_key_list() { + // Remove current widgets + foreach (var widget in keys_preferences_group_children) { + keys_preferences_group.remove(widget); + } + keys_preferences_group_children.clear(); + + // 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)) { + automatically_accept_new_row.subtitle = _("New encryption keys from your other devices will be accepted automatically."); + add_own_fingerprint(); + } + + //Show the normal devicelist + var own_id = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id]; + foreach (Row device in plugin.db.identity_meta.get_known_devices(identity_id, jid.to_string())) { + if(jid.equals(account.bare_jid) && device[plugin.db.identity_meta.device_id] == own_id) { + // If this is our own account, don't show this device twice (did it separately already) + continue; + } + add_fingerprint(device, (TrustLevel) device[plugin.db.identity_meta.trust_level]); + } + + //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); + } + } + + private static string escape_for_iri_path_segment(string s) { + // from RFC 3986, 2.2. Reserved Characters: + string SUB_DELIMS = "!$&'()*+,;="; + // from RFC 3986, 3.3. Path (pchar without unreserved and pct-encoded): + string ALLOWED_RESERVED_CHARS = SUB_DELIMS + ":@"; + return GLib.Uri.escape_string(s, ALLOWED_RESERVED_CHARS, true); + } + + private void fetch_unknown_bundles() { + Dino.Application app = Application.get_default() as Dino.Application; + XmppStream? stream = app.stream_interactor.get_stream(account); + if (stream == null) return; + StreamModule? module = stream.get_module(StreamModule.IDENTITY); + if (module == null) return; + module.bundle_fetched.connect_after((bundle_jid, device_id, bundle) => { + if (bundle_jid.equals(jid) && !displayed_ids.contains(device_id)) { + redraw_key_list(); + } + }); + foreach (Row device in plugin.db.identity_meta.get_unknown_devices(identity_id, jid.to_string())) { + try { + module.fetch_bundle(stream, new Jid(device[plugin.db.identity_meta.address_name]), device[plugin.db.identity_meta.device_id], false); + } catch (InvalidJidError e) { + warning("Ignoring device with invalid Jid: %s", e.message); + } + } + } + + private void add_own_fingerprint() { + 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); + + var own_action_box = new Box(Orientation.HORIZONTAL, 6); + var show_qrcode_button = new MenuButton() { icon_name="dino-qr-code-symbolic", valign=Align.CENTER }; + own_action_box.append(show_qrcode_button); + var copy_button = new Button() { icon_name="edit-copy-symbolic", valign=Align.CENTER }; + copy_button.clicked.connect(() => { copy_button.get_clipboard().set_text(fingerprint); }); + own_action_box.append(copy_button); + + Adw.ActionRow action_row = new Adw.ActionRow(); + + action_row.title = "This device"; + action_row.subtitle = format_fingerprint(fingerprint_from_base64(own_b64)); + action_row.add_suffix(own_action_box); +#if Adw_1_2 + action_row.use_markup = true; + action_row.subtitle = fingerprint_markup(fingerprint_from_base64(own_b64)); +#endif + add_key_row(action_row); + + // Create and set QR code popover + int sid = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id]; + var iri_query = @"omemo-sid-$(sid)=$(fingerprint)"; +#if GLIB_2_66 && VALA_0_50 + string iri = GLib.Uri.join(UriFlags.NONE, "xmpp", null, null, 0, jid.to_string(), iri_query, null); +#else + var iri_path_seg = escape_for_iri_path_segment(jid.to_string()); + var iri = @"xmpp:$(iri_path_seg)?$(iri_query)"; +#endif + + const int QUIET_ZONE_MODULES = 4; // MUST be at least 4 + const int MODULE_SIZE_PX = 4; // arbitrary + var qr_paintable = new QRcode(iri, 2) + .to_paintable(MODULE_SIZE_PX * qrcode_picture.scale_factor); + qrcode_picture.paintable = qr_paintable; + qrcode_picture.margin_top = qrcode_picture.margin_end = + qrcode_picture.margin_bottom = qrcode_picture.margin_start = QUIET_ZONE_MODULES * MODULE_SIZE_PX; + qrcode_popover.add_css_class("qrcode-container"); + + show_qrcode_button.popover = qrcode_popover; + } + + private void add_fingerprint(Row device, TrustLevel trust) { + string key_base64 = device[plugin.db.identity_meta.identity_key_public_base64]; + bool key_active = device[plugin.db.identity_meta.now_active]; + if (store != null) { + try { + Signal.Address address = new Signal.Address(jid.to_string(), device[plugin.db.identity_meta.device_id]); + Signal.SessionRecord? session = null; + if (store.contains_session(address)) { + session = store.load_session(address); + string session_key_base64 = Base64.encode(session.state.remote_identity_key.serialize()); + if (key_base64 != session_key_base64) { + critical("Session and database identity key mismatch!"); + key_base64 = session_key_base64; + } + } + } catch (Error e) { + print("Error while reading session store: %s", e.message); + } + } + + if (device[plugin.db.identity_meta.now_active]) { + Adw.ActionRow action_row = new Adw.ActionRow(); + action_row.activated.connect(() => { + 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_root()); + manage_dialog.present(); + manage_dialog.response.connect((response) => { + update_stored_trust(response, updated_device); + redraw_key_list(); + }); + }); + action_row.activatable = true; + action_row.title = "Other device"; + action_row.subtitle = format_fingerprint(fingerprint_from_base64(key_base64)); + string trust_str = _("Accepted"); + switch(trust) { + case TrustLevel.UNTRUSTED: + trust_str = _("Rejected"); + break; + case TrustLevel.VERIFIED: + trust_str = _("Verified"); + break; + } + + action_row.add_suffix(new Label(trust_str)); +#if Adw_1_2 + action_row.use_markup = true; + action_row.subtitle = fingerprint_markup(fingerprint_from_base64(key_base64)); +#endif + add_key_row(action_row); + } + displayed_ids.add(device[plugin.db.identity_meta.device_id]); + } + + private bool on_auto_accept_toggled(bool active) { + plugin.trust_manager.set_blind_trust(account, jid, active); + + if (active) { + int identity_id = plugin.db.identity.get_id(account.id); + if (identity_id < 0) return 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], TrustLevel.TRUSTED); + add_fingerprint(device, TrustLevel.TRUSTED); + } + } + return false; + } + + private bool on_omemo_by_default_toggled(bool active) { + var encryption_value = active ? Encryption.OMEMO : Encryption.NONE; + plugin.app.settings.set_default_encryption(account, encryption_value); + return false; + } + + private void update_stored_trust(int response, Row device) { + switch (response) { + case TrustLevel.TRUSTED: + plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.TRUSTED); + break; + case TrustLevel.UNTRUSTED: + plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.UNTRUSTED); + break; + case TrustLevel.VERIFIED: + plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.VERIFIED); + plugin.trust_manager.set_blind_trust(account, jid, false); + automatically_accept_new_switch.set_active(false); + break; + } + } + + private void add_new_fingerprint(Row device) { + Adw.ActionRow action_row = new Adw.ActionRow(); + action_row.title = _("New device"); + action_row.subtitle = format_fingerprint(fingerprint_from_base64(device[plugin.db.identity_meta.identity_key_public_base64])); + +#if Adw_1_2 + action_row.use_markup = true; + action_row.subtitle = fingerprint_markup(fingerprint_from_base64(device[plugin.db.identity_meta.identity_key_public_base64])); +#endif + + Button accept_button = new Button() { visible = true, valign = Align.CENTER, hexpand = true }; + accept_button.set_icon_name("emblem-ok-symbolic"); // using .image = sets .image-button. Together with .suggested/destructive action that breaks the button Adwaita + accept_button.add_css_class("suggested-action"); + accept_button.tooltip_text = _("Accept key"); + + Button reject_button = new Button() { visible = true, valign = Align.CENTER, hexpand = true }; + reject_button.set_icon_name("action-unavailable-symbolic"); + reject_button.add_css_class("destructive-action"); + reject_button.tooltip_text = _("Reject key"); + + accept_button.clicked.connect(() => { + plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.TRUSTED); + add_fingerprint(device, TrustLevel.TRUSTED); + remove_key_row(action_row); + }); + + reject_button.clicked.connect(() => { + plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.UNTRUSTED); + add_fingerprint(device, TrustLevel.UNTRUSTED); + remove_key_row(action_row); + }); + + Box control_box = new Box(Gtk.Orientation.HORIZONTAL, 0) { visible = true, hexpand = true }; + control_box.append(accept_button); + control_box.append(reject_button); + control_box.add_css_class("linked"); // .linked: Visually link the accept / reject buttons + + action_row.add_suffix(control_box); + + add_key_row(action_row); + displayed_ids.add(device[plugin.db.identity_meta.device_id]); + } + + private void add_key_row(Adw.PreferencesRow widget) { + keys_preferences_group.add(widget); + keys_preferences_group_children.add(widget); + } + + private void remove_key_row(Adw.PreferencesRow widget) { + keys_preferences_group.remove(widget); + keys_preferences_group_children.remove(widget); + } +} +} \ No newline at end of file diff --git a/plugins/omemo/src/ui/util.vala b/plugins/omemo/src/ui/util.vala index cf61ed82..e250ff4d 100644 --- a/plugins/omemo/src/ui/util.vala +++ b/plugins/omemo/src/ui/util.vala @@ -17,46 +17,24 @@ public static string fingerprint_from_base64(string b64) { } public static string fingerprint_markup(string s) { + return "" + format_fingerprint(s) + ""; +} + +public static string format_fingerprint(string s) { string markup = ""; for (int i = 0; i < s.length; i += 4) { string four_chars = s.substring(i, 4).down(); - int raw = (int) from_hex(four_chars); - 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 += @"$four_chars"; - if (i % 8 == 4 && i % 32 != 28) markup += " "; + markup += four_chars; + if (i % 16 == 12 && i % 32 != 28) { + markup += " "; + } + if (i % 8 == 4 && i % 16 != 12) { + markup += "\u00a0"; // Non-breaking space + } } - - return "" + markup + ""; + return markup; } } diff --git a/plugins/openpgp/CMakeLists.txt b/plugins/openpgp/CMakeLists.txt index 6ed7bf53..d2ac6d73 100644 --- a/plugins/openpgp/CMakeLists.txt +++ b/plugins/openpgp/CMakeLists.txt @@ -7,6 +7,7 @@ gettext_compile(${GETTEXT_PACKAGE} SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/po TAR find_packages(OPENPGP_PACKAGES REQUIRED + Adwaita Gee GLib GModule @@ -14,19 +15,10 @@ find_packages(OPENPGP_PACKAGES REQUIRED GTK4 ) -set(RESOURCE_LIST - account_settings_item.ui -) - -compile_gresources( - OPENPGP_GRESOURCES_TARGET - OPENPGP_GRESOURCES_XML - TARGET ${CMAKE_CURRENT_BINARY_DIR}/resources/resources.c - TYPE EMBED_C - RESOURCES ${RESOURCE_LIST} - PREFIX /im/dino/Dino/openpgp - SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data -) +set(OPENPGP_DEFINITIONS) +if(Adwaita_VERSION VERSION_GREATER_EQUAL "1.4") + set(OPENPGP_DEFINITIONS ${OPENPGP_DEFINITIONS} Adw_1_4) +endif() vala_precompile(OPENPGP_VALA_C SOURCES @@ -35,10 +27,10 @@ SOURCES src/file_transfer/file_decryptor.vala src/file_transfer/file_encryptor.vala - src/account_settings_entry.vala src/contact_details_provider.vala src/database.vala src/encryption_list_entry.vala + src/encryption_preferences_entry.vala src/manager.vala src/plugin.vala src/register_plugin.vala @@ -53,12 +45,12 @@ CUSTOM_VAPIS ${CMAKE_BINARY_DIR}/exports/dino.vapi PACKAGES ${OPENPGP_PACKAGES} -GRESOURCES - ${OPENPGP_GRESOURCES_XML} +DEFINITIONS + ${OPENPGP_DEFINITIONS} ) add_definitions(${VALA_CFLAGS} -DG_LOG_DOMAIN="OpenPGP" -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\") -add_library(openpgp SHARED ${OPENPGP_VALA_C} ${OPENPGP_GRESOURCES_TARGET} src/gpgme_fix.c) +add_library(openpgp SHARED ${OPENPGP_VALA_C} src/gpgme_fix.c) add_dependencies(openpgp ${GETTEXT_PACKAGE}-translations) target_include_directories(openpgp PRIVATE src) target_link_libraries(openpgp libdino gpgme ${OPENPGP_PACKAGES}) diff --git a/plugins/openpgp/data/account_settings_item.ui b/plugins/openpgp/data/account_settings_item.ui deleted file mode 100644 index 56808be0..00000000 --- a/plugins/openpgp/data/account_settings_item.ui +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - label - - - 0 - 0 - - - 0 - - - - - - - - - entry - - - 1 - - - - - - \ No newline at end of file diff --git a/plugins/openpgp/meson.build b/plugins/openpgp/meson.build index 806494f2..ec6f5c0e 100644 --- a/plugins/openpgp/meson.build +++ b/plugins/openpgp/meson.build @@ -1,5 +1,6 @@ subdir('po') dependencies = [ + dep_libadwaita, dep_dino, dep_gee, dep_glib, @@ -10,7 +11,6 @@ dependencies = [ dep_xmpp_vala, ] sources = files( - 'src/account_settings_entry.vala', 'src/contact_details_provider.vala', 'src/database.vala', 'src/encryption_list_entry.vala', diff --git a/plugins/openpgp/src/account_settings_entry.vala b/plugins/openpgp/src/account_settings_entry.vala deleted file mode 100644 index 7c99942f..00000000 --- a/plugins/openpgp/src/account_settings_entry.vala +++ /dev/null @@ -1,163 +0,0 @@ -using Dino.Entities; -using Gtk; - -namespace Dino.Plugins.OpenPgp { - -public class AccountSettingsEntry : Plugins.AccountSettingsEntry { - - private Label label; - private Button button; - private ComboBox combobox; - private Stack stack; - - private Plugin plugin; - private Account current_account; - private Gee.List keys = null; - private Gtk.ListStore list_store = new Gtk.ListStore(2, typeof(string), typeof(string?)); - - public override string id { get { return "pgp_key_picker"; }} - - public override string name { get { return "OpenPGP"; }} - - public AccountSettingsEntry(Plugin plugin) { - this.plugin = plugin; - - Builder builder = new Builder.from_resource("/im/dino/Dino/openpgp/account_settings_item.ui"); - stack = (Stack) builder.get_object("stack"); - label = (Label) builder.get_object("label"); - button = (Button) builder.get_object("button"); - combobox = (ComboBox) builder.get_object("combobox"); - - CellRendererText renderer = new CellRendererText(); - renderer.set_padding(0, 0); - combobox.pack_start(renderer, true); - combobox.add_attribute(renderer, "markup", 0); - combobox.set_model(list_store); - - button.clicked.connect(on_button_clicked); - combobox.changed.connect(key_changed); - } - - public override void deactivate() { - stack.set_visible_child_name("label"); - } - - public override void set_account(Account account) { - set_account_.begin(account); - } - - private async void set_account_(Account account) { - this.current_account = account; - if (keys == null) { - yield fetch_keys(); - populate_list_store(); - } - activate_current_account(); - } - - private void on_button_clicked() { - activated(); - stack.set_visible_child_name("entry"); - combobox.grab_focus(); - combobox.popup(); - } - - private void activate_current_account() { - combobox.changed.disconnect(key_changed); - if (keys == null) { - label.set_markup(build_markup_string(_("Key publishing disabled"), _("Error in GnuPG"))); - return; - } - if (keys.size == 0) { - label.set_markup(build_markup_string(_("Key publishing disabled"), _("No keys available. Generate one!"))); - return; - } - - string? account_key = plugin.db.get_account_key(current_account); - int activate_index = 0; - for (int i = 0; i < keys.size; i++) { - GPG.Key key = keys[i]; - if (key.fpr == account_key) { - activate_index = i + 1; - } - } - combobox.active = activate_index; - - TreeIter selected; - combobox.get_active_iter(out selected); - set_label_active(selected); - - combobox.changed.connect(key_changed); - } - - private void populate_list_store() { - if (keys == null || keys.size == 0) { - return; - } - - list_store.clear(); - TreeIter iter; - list_store.append(out iter); - list_store.set(iter, 0, build_markup_string(_("Key publishing disabled"), _("Select key") + " \n "), 1, ""); - for (int i = 0; i < keys.size; i++) { - list_store.append(out iter); - list_store.set(iter, 0, @"$(Markup.escape_text(keys[i].uids[0].uid))\n$(markup_colorize_id(keys[i].fpr, true)) "); - list_store.set(iter, 1, keys[i].fpr); - if (keys[i].fpr == plugin.db.get_account_key(current_account)) { - set_label_active(iter, i + 1); - } - } - button.sensitive = true; - } - - private async void fetch_keys() { - label.set_markup(build_markup_string(_("Loading…"), _("Querying GnuPG"))); - - SourceFunc callback = fetch_keys.callback; - new Thread (null, () => { // Querying GnuPG might take some time - try { - keys = GPGHelper.get_keylist(null, true); - } catch (Error e) { - warning(e.message); - } - Idle.add((owned)callback); - return null; - }); - yield; - } - - private void set_label_active(TreeIter iter, int i = -1) { - Value text; - list_store.get_value(iter, 0, out text); - label.set_markup((string) text); - if (i != -1) combobox.active = i; - } - - private void key_changed() { - TreeIter selected; - bool iter_valid = combobox.get_active_iter(out selected); - if (iter_valid) { - Value key_value; - list_store.get_value(selected, 1, out key_value); - string? key_id = key_value as string; - if (key_id != null) { - if (plugin.modules.has_key(current_account)) { - plugin.modules[current_account].set_private_key_id(key_id); - } - plugin.db.set_account_key(current_account, key_id); - } - set_label_active(selected); - deactivate(); - } - } - - private string build_markup_string(string primary, string secondary) { - return @"$(Markup.escape_text(primary))\n$secondary"; - } - - public override Object? get_widget(WidgetType type) { - if (type != WidgetType.GTK4) return null; - return stack; - } -} -} \ No newline at end of file diff --git a/plugins/openpgp/src/encryption_preferences_entry.vala b/plugins/openpgp/src/encryption_preferences_entry.vala new file mode 100644 index 00000000..4620e173 --- /dev/null +++ b/plugins/openpgp/src/encryption_preferences_entry.vala @@ -0,0 +1,86 @@ +using Adw; +using Dino.Entities; +using Gtk; + +namespace Dino.Plugins.OpenPgp { + + public class PgpPreferencesEntry : Plugins.EncryptionPreferencesEntry { + + private Plugin plugin; + + public PgpPreferencesEntry(Plugin plugin) { + this.plugin = plugin; + } + + public override Object? get_widget(Account account, WidgetType type) { + if (type != WidgetType.GTK4) return null; + StringList string_list = new StringList(null); + string_list.append(_("Querying GnuPG")); + + Adw.PreferencesGroup preferences_group = new Adw.PreferencesGroup() { title="OpenPGP" }; + populate_string_list.begin(account, preferences_group); + + return preferences_group; + } + + public override string id { get { return "pgp_preferences_encryption"; }} + + private async void populate_string_list(Account account, Adw.PreferencesGroup preferences_group) { + var keys = yield get_pgp_keys(); + + if (keys == null) { + preferences_group.add(new Adw.ActionRow() { title="Announce key", subtitle="Error in GnuPG" }); + return; + } + if (keys.size == 0) { + preferences_group.add(new Adw.ActionRow() { title="Announce key", subtitle="No keys available. Generate one!" }); + return; + } + + StringList string_list = new StringList(null); +#if Adw_1_4 + var drop_down = new Adw.ComboRow() { title = "Announce key" }; + drop_down.model = string_list; + preferences_group.add(drop_down); +#else + var view = new Adw.ActionRow() { title = "Announce key" }; + var drop_down = new DropDown(string_list, null) { valign = Align.CENTER }; + view.activatable_widget = drop_down; + view.add_suffix(drop_down); + preferences_group.add(view); +#endif + + string_list.append(_("Disabled")); + for (int i = 0; i < keys.size; i++) { + string_list.append(@"$(keys[i].uids[0].uid)\n$(keys[i].fpr.substring(24, 16))"); + if (keys[i].fpr == plugin.db.get_account_key(account)) { + drop_down.selected = i + 1; + } + } + + drop_down.notify["selected"].connect(() => { + var key_id = drop_down.selected == 0 ? "" : keys[(int)drop_down.selected - 1].fpr; + if (plugin.modules.has_key(account)) { + plugin.modules[account].set_private_key_id(key_id); + } + plugin.db.set_account_key(account, key_id); + }); + } + + private static async Gee.List get_pgp_keys() { + Gee.List keys = null; + SourceFunc callback = get_pgp_keys.callback; + new Thread (null, () => { // Querying GnuPG might take some time + try { + keys = GPGHelper.get_keylist(null, true); + } catch (Error e) { + warning(e.message); + } + Idle.add((owned)callback); + return null; + }); + yield; + return keys; + } + } +} \ No newline at end of file diff --git a/plugins/openpgp/src/plugin.vala b/plugins/openpgp/src/plugin.vala index 324b8652..463058f3 100644 --- a/plugins/openpgp/src/plugin.vala +++ b/plugins/openpgp/src/plugin.vala @@ -13,18 +13,16 @@ public class Plugin : Plugins.RootInterface, Object { public HashMap modules = new HashMap(Account.hash_func, Account.equals_func); private EncryptionListEntry list_entry; - private AccountSettingsEntry settings_entry; private ContactDetailsProvider contact_details_provider; public void registered(Dino.Application app) { this.app = app; this.db = new Database(Path.build_filename(Application.get_storage_dir(), "pgp.db")); this.list_entry = new EncryptionListEntry(app.stream_interactor, db); - this.settings_entry = new AccountSettingsEntry(this); this.contact_details_provider = new ContactDetailsProvider(app.stream_interactor); app.plugin_registry.register_encryption_list_entry(list_entry); - app.plugin_registry.register_account_settings_entry(settings_entry); + app.plugin_registry.register_encryption_preferences_entry(new PgpPreferencesEntry(this)); app.plugin_registry.register_contact_details_entry(contact_details_provider); app.stream_interactor.module_manager.initialize_account_modules.connect(on_initialize_account_modules); -- cgit v1.2.3-54-g00ecf