[seahorse/nielsdg/gtk4] Port to GTK4




commit f9b6023f187d7e3fcae436fccc7b023dc0bfcd15
Author: Niels De Graef <nielsdegraef gmail com>
Date:   Fri Jul 1 10:40:42 2022 +0200

    Port to GTK4

 common/add-keyserver-dialog.vala           |   3 +-
 common/backend.vala                        |  33 +-
 common/catalog.vala                        |  44 +-
 common/config.vapi                         |  38 +-
 common/datepicker.vala                     |  26 +-
 common/deletable.vala                      |   4 +-
 common/delete-dialog.vala                  | 143 ++---
 common/deleter.vala                        |  20 +-
 common/exportable.vala                     | 447 +++++++-------
 common/icons.vala                          |  44 --
 common/interaction.vala                    |   4 +-
 common/item-list.vala                      | 226 -------
 common/meson.build                         |  11 +-
 common/passphrase-prompt.vala              | 107 ++--
 common/place.vala                          |  10 +-
 common/prefs-keyservers.vala               |  42 +-
 common/prefs.vala                          |   2 +-
 common/registry.vala                       |  94 ---
 common/renderer.vala                       |  29 +
 common/seahorse-prefs-keyservers.ui        | 105 ++--
 common/util.vala                           |  43 +-
 common/viewable.vala                       |   3 +-
 data/seahorse.gresource.xml                |   5 +-
 gkr/gkr-backend.vala                       | 388 ++++++------
 gkr/gkr-dialogs.vala                       |  93 ++-
 gkr/gkr-item-add.vala                      |  60 +-
 gkr/gkr-item-properties.vala               |  82 ++-
 gkr/gkr-item.vala                          |  10 +-
 gkr/gkr-keyring-add.vala                   |   9 +-
 gkr/gkr-keyring-properties.vala            |   3 -
 gkr/gkr-keyring.vala                       | 300 +++++-----
 gkr/gkr-password-entry.vala                |  51 --
 gkr/meson.build                            |   6 +-
 gkr/seahorse-gkr-add-item.ui               | 114 +---
 gkr/seahorse-gkr-add-keyring.ui            |  89 +--
 gkr/seahorse-gkr-item-properties.ui        | 116 +---
 gkr/seahorse-gkr-keyring.ui                | 114 +---
 libseahorse/meson.build                    |   2 +-
 libseahorse/seahorse-progress.c            |   2 +-
 meson.build                                |   9 +-
 pgp/meson.build                            |   3 +-
 pgp/seahorse-combo-keys.c                  | 362 ------------
 pgp/seahorse-combo-keys.h                  |  46 --
 pgp/seahorse-gpgme-add-subkey.ui           | 157 ++---
 pgp/seahorse-gpgme-add-uid.c               |  63 +-
 pgp/seahorse-gpgme-add-uid.ui              | 130 +---
 pgp/seahorse-gpgme-dialogs.h               |   3 -
 pgp/seahorse-gpgme-expires-dialog.c        |  21 +-
 pgp/seahorse-gpgme-expires-dialog.ui       |  24 +-
 pgp/seahorse-gpgme-generate-dialog.c       | 202 ++++---
 pgp/seahorse-gpgme-generate-dialog.ui      | 337 +++--------
 pgp/seahorse-gpgme-key-deleter.c           |   2 +-
 pgp/seahorse-gpgme-key-op.c                |   6 -
 pgp/seahorse-gpgme-keyring.c               | 134 ++---
 pgp/seahorse-gpgme-photos.c                | 129 ++--
 pgp/seahorse-gpgme-revoke-dialog.c         |   6 +-
 pgp/seahorse-gpgme-revoke-dialog.ui        |  88 +--
 pgp/seahorse-gpgme-sign-dialog.c           |  55 +-
 pgp/seahorse-gpgme-sign-dialog.ui          | 349 ++++-------
 pgp/seahorse-hkp-source.c                  |  23 +-
 pgp/seahorse-keyserver-results.c           | 103 +---
 pgp/seahorse-keyserver-results.ui          |  13 -
 pgp/seahorse-keyserver-search.c            |  23 +-
 pgp/seahorse-keyserver-search.ui           |  42 +-
 pgp/seahorse-keyserver-sync.c              |  39 +-
 pgp/seahorse-keyserver-sync.h              |   6 +-
 pgp/seahorse-keyserver-sync.ui             |  93 +--
 pgp/seahorse-ldap-source.c                 |  44 +-
 pgp/seahorse-pgp-actions.c                 | 101 ++--
 pgp/seahorse-pgp-backend.c                 |  48 +-
 pgp/seahorse-pgp-backend.h                 |  16 +-
 pgp/seahorse-pgp-dialogs.h                 |   9 -
 pgp/seahorse-pgp-key-properties.c          | 684 +++++----------------
 pgp/seahorse-pgp-key-properties.h          |  32 +
 pgp/seahorse-pgp-key-properties.ui         | 322 ++++++++++
 pgp/seahorse-pgp-key.c                     |  16 +-
 pgp/seahorse-pgp-keysets.c                 |  74 +--
 pgp/seahorse-pgp-keysets.h                 |  14 +-
 pgp/seahorse-pgp-private-key-properties.ui | 521 ----------------
 pgp/seahorse-pgp-public-key-properties.ui  | 485 ---------------
 pgp/seahorse-pgp-subkey-list-box-row.ui    |  98 ++-
 pgp/seahorse-pgp-subkey-list-box.c         | 167 ++++--
 pgp/seahorse-pgp-subkey-list-box.h         |   4 +-
 pgp/seahorse-pgp-uid-list-box-row.ui       |  20 +-
 pgp/seahorse-pgp-uid-list-box.c            | 156 +++--
 pgp/seahorse-pgp-uid-list-box.h            |   4 +-
 pgp/seahorse-revoke.ui                     |  99 ----
 pgp/seahorse-server-source.c               | 192 +++---
 pgp/seahorse-server-source.h               |  72 ++-
 pgp/seahorse-transfer.c                    |   7 +-
 pgp/seahorse-unknown-source.c              | 198 +++----
 pgp/seahorse-unknown-source.h              |  25 +-
 pgp/seahorse-unknown.c                     | 101 +++-
 pgp/seahorse-unknown.h                     |  31 +-
 pgp/test-gpgme-backend.c                   |   4 +-
 pkcs11/certificate-der-exporter.vala       |   2 +-
 pkcs11/meson.build                         |   2 +-
 pkcs11/pkcs11-certificate.vala             | 418 +++++++------
 pkcs11/pkcs11-deleter.vala                 |  12 +-
 pkcs11/pkcs11-generate.vala                |  68 +--
 pkcs11/pkcs11-key-deleter.vala             |   2 +-
 pkcs11/pkcs11-private-key.vala             | 197 +++----
 pkcs11/pkcs11-properties.vala              |  64 +-
 pkcs11/pkcs11-request.vala                 | 225 ++++---
 pkcs11/pkcs11-token-filter.vala            |  63 ++
 pkcs11/pkcs11-token.vala                   | 917 ++++++++++++++---------------
 pkcs11/seahorse-pkcs11-backend.c           | 463 +++++++--------
 pkcs11/seahorse-pkcs11-backend.h           |  14 +-
 pkcs11/seahorse-pkcs11-generate.ui         | 211 ++-----
 pkcs11/seahorse-pkcs11-properties.ui       |  16 +-
 src/application.vala                       |  20 +-
 src/import-dialog.vala                     | 102 ++--
 src/key-manager-filter.vala                | 125 ++++
 src/key-manager-item-row.vala              |  11 +-
 src/key-manager.vala                       | 397 +++++++------
 src/main.vala                              |   6 +-
 src/meson.build                            |   5 +-
 src/seahorse-key-manager.ui                | 738 ++++++++++-------------
 src/search-provider.vala                   |  57 +-
 src/sidebar.vala                           | 443 ++++----------
 ssh/actions.vala                           |  19 +-
 ssh/backend.vala                           |  18 +-
 ssh/deleter.vala                           |   2 +-
 ssh/exporter.vala                          |   4 +-
 ssh/generate.vala                          |  20 +-
 ssh/key-length-chooser.vala                |  37 +-
 ssh/key-properties.vala                    |  53 +-
 ssh/key.vala                               |   4 +-
 ssh/meson.build                            |   9 +-
 ssh/operation.vala                         |   5 -
 ssh/seahorse-ssh-askpass.c                 | 210 +++----
 ssh/seahorse-ssh-generate.ui               | 224 ++-----
 ssh/seahorse-ssh-key-properties.ui         | 247 ++------
 ssh/seahorse-ssh-upload.ui                 | 146 +----
 ssh/source.vala                            |  53 +-
 ssh/upload.vala                            |  35 +-
 136 files changed, 5517 insertions(+), 9081 deletions(-)
---
diff --git a/common/add-keyserver-dialog.vala b/common/add-keyserver-dialog.vala
index 3437d285..55b4fbbb 100644
--- a/common/add-keyserver-dialog.vala
+++ b/common/add-keyserver-dialog.vala
@@ -32,7 +32,6 @@ public class Seahorse.AddKeyserverDialog : Gtk.Dialog {
             title: _("Add Key Server"),
             transient_for: parent,
             modal: true,
-            window_position: Gtk.WindowPosition.CENTER_ON_PARENT,
             default_width: 400,
             use_header_bar: 1
         );
@@ -47,7 +46,7 @@ public class Seahorse.AddKeyserverDialog : Gtk.Dialog {
             GLib.critical("%s", err.message);
         }
         Gtk.Box content = (Gtk.Box) builder.get_object("add-keyserver");
-        get_content_area().add(content);
+        set_child(content);
         this.keyserver_host = (Gtk.Entry) builder.get_object("keyserver-host");
         this.keyserver_port = (Gtk.Entry) builder.get_object("keyserver-port");
         this.keyserver_type = (Gtk.ComboBoxText) builder.get_object("keyserver-type");
diff --git a/common/backend.vala b/common/backend.vala
index ec0508cc..504e753b 100644
--- a/common/backend.vala
+++ b/common/backend.vala
@@ -16,24 +16,27 @@
  * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
-namespace Seahorse {
+public interface Seahorse.Backend : GLib.ListModel {
 
-public interface Backend : Gcr.Collection {
-       public abstract string name { get; }
-       public abstract string label { get; }
-       public abstract string description { get; }
-       public abstract ActionGroup actions { owned get; }
-       public abstract bool loaded { get; }
+    public abstract string name { get; }
+    public abstract string label { get; }
+    public abstract string description { get; }
+    public abstract ActionGroup actions { owned get; }
+    public abstract bool loaded { get; }
 
-       public abstract Place? lookup_place(string uri);
+    public abstract Place? lookup_place(string uri);
 
-       public void register() {
-               Registry.register_object(this, "backend");
-       }
+    private static GLib.ListStore backends;
 
-       public static GLib.List<Backend> get_registered() {
-               return (GLib.List<Seahorse.Backend>)Registry.object_instances("backend");
-       }
-}
+    public void register() {
+        if (backends == null)
+            backends = new GLib.ListStore(typeof(Backend));
+        backends.append(this);
+    }
 
+    public static GLib.ListModel get_registered() {
+        if (backends == null)
+            backends = new GLib.ListStore(typeof(Backend));
+        return backends;
+    }
 }
diff --git a/common/catalog.vala b/common/catalog.vala
index 2cff02e5..25ad5ba2 100644
--- a/common/catalog.vala
+++ b/common/catalog.vala
@@ -20,16 +20,14 @@
 
 namespace Seahorse {
 
-public abstract class Catalog : Gtk.ApplicationWindow {
+public abstract class Catalog : Adw.ApplicationWindow {
 
     /* Set by the derived classes */
     public string ui_name { construct; get; }
 
     protected MenuModel context_menu;
-    private bool _disposed;
     private GLib.Settings _settings;
 
-    public abstract GLib.List<weak Backend> get_backends();
     public abstract GLib.List<GLib.Object> get_selected_objects();
 
     private const ActionEntry[] ACTION_ENTRIES = {
@@ -46,7 +44,7 @@ public abstract class Catalog : Gtk.ApplicationWindow {
         var width = this._settings.get_int("width");
         var height = this._settings.get_int("height");
         if (width > 0 && height > 0)
-            this.resize (width, height);
+            set_default_size (width, height);
 
         Gtk.Builder builder = new Gtk.Builder.from_resource(
             "/org/gnome/Seahorse/seahorse-%s-widgets.ui".printf(this.ui_name)
@@ -56,17 +54,11 @@ public abstract class Catalog : Gtk.ApplicationWindow {
         add_action_entries (ACTION_ENTRIES, this);
     }
 
-    public override void dispose() {
-        if (!this._disposed) {
-            this._disposed = true;
+       public override bool close_request() {
+        this._settings.set_int("width", this.default_width);
+        this._settings.set_int("height", this.default_height);
 
-            int width, height;
-            this.get_size(out width, out height);
-            this._settings.set_int("width", width);
-            this._settings.set_int("height", height);
-        }
-
-        base.dispose();
+        return base.close_request();
     }
 
     public virtual signal void selection_changed() {
@@ -97,13 +89,20 @@ public abstract class Catalog : Gtk.ApplicationWindow {
     }
 
     public void show_context_menu(Gdk.Event? event) {
-        Gtk.Menu menu = new Gtk.Menu.from_model(this.context_menu);
+        var menu = new Gtk.PopoverMenu.from_model(this.context_menu);
         menu.insert_action_group("win", this);
-        foreach (weak Backend backend in get_backends()) {
-            ActionGroup actions = backend.actions;
-            menu.insert_action_group(actions.prefix, actions);
+        var backends = Backend.get_registered();
+        for (uint i = 0; i < backends.get_n_items(); i++) {
+            var backend = (Backend) backends.get_item(i);
+            menu.insert_action_group(backend.actions.prefix, backend.actions);
         }
-        menu.popup_at_pointer(event);
+
+        menu.set_parent(this);
+               if (event != null) {
+                       double x, y;
+                       event.get_position(out x, out y);
+            menu.set_pointing_to({ (int) x, (int) y, 1, 1 });
+               }
         menu.show();
     }
 
@@ -140,10 +139,13 @@ public abstract class Catalog : Gtk.ApplicationWindow {
             return;
         }
 
+               var val = Value(typeof(string));
+               val.set_string((string) output);
+
         /* TODO: Print message if only partially exported */
 
-        var board = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
-        board.set_text ((string)output, output.length);
+        var board = get_clipboard();
+        board.set_value(val);
     }
 }
 
diff --git a/common/config.vapi b/common/config.vapi
index 469481b3..3e637b11 100644
--- a/common/config.vapi
+++ b/common/config.vapi
@@ -5,23 +5,23 @@ namespace Config
 
     public const string PROFILE;
 
-       public const string PKGDATADIR;
+    public const string PKGDATADIR;
 
-       public const string EXECDIR;
-       public const string LOCALEDIR;
+    public const string EXECDIR;
+    public const string LOCALEDIR;
 
-       public const string VERSION;
-       public const string PACKAGE;
-       public const string PACKAGE_STRING;
-       public const string GETTEXT_PACKAGE;
+    public const string VERSION;
+    public const string PACKAGE;
+    public const string PACKAGE_STRING;
+    public const string GETTEXT_PACKAGE;
 
-       public const string SSH_PATH;
-       public const string SSH_KEYGEN_PATH;
+    public const string SSH_PATH;
+    public const string SSH_KEYGEN_PATH;
 
-       public const string GNUPG;
-       public const int GPG_MAJOR;
-       public const int GPG_MINOR;
-       public const int GPG_MICRO;
+    public const string GNUPG;
+    public const int GPG_MAJOR;
+    public const int GPG_MINOR;
+    public const int GPG_MICRO;
 }
 
 /*
@@ -43,11 +43,11 @@ public static GLib.EqualFunc<ulong?> ulong_equal;
 
 [CCode (cheader_filename = "libseahorse/seahorse-progress.h")]
 namespace Progress {
-       public void show(GLib.Cancellable? cancellable, string title, bool delayed);
+    public void show(GLib.Cancellable? cancellable, string title, bool delayed);
 }
 
 [CCode (cheader_filename = "pgp/seahorse-pgp-backend.h")]
-public class Pgp.Backend : GLib.Object, Gcr.Collection, Place {
+public class Pgp.Backend : GLib.Object, GLib.ListModel, Place {
     public static void initialize(string? gpg_homedir);
     public static unowned Pgp.Backend get();
 
@@ -57,7 +57,7 @@ public class Pgp.Backend : GLib.Object, Gcr.Collection, Place {
 }
 
 [CCode (cheader_filename = "pgp/seahorse-server-source.h")]
-public class ServerSource : GLib.Object, Gcr.Collection, Place {
+public class ServerSource : GLib.Object, GLib.ListModel, Place {
 }
 
 #if WITH_LDAP
@@ -79,8 +79,8 @@ public static bool hkp_is_valid_uri(string uri);
 #endif // WITH_HKP
 
 [CCode (cheader_filename = "pkcs11/seahorse-pkcs11-backend.h")]
-public class Pkcs11.Backend {
-       public static void initialize();
-       public static Gcr.Collection get_writable_tokens(Pkcs11.Backend? self, ulong with_mechanism);
+public class Pkcs11.Backend : GLib.Object, GLib.ListModel {
+    public static void initialize();
+    public static unowned Pkcs11.Backend get();
 }
 }
diff --git a/common/datepicker.vala b/common/datepicker.vala
index 2183492c..832e8328 100644
--- a/common/datepicker.vala
+++ b/common/datepicker.vala
@@ -37,43 +37,39 @@ public class Seahorse.DatePicker : Gtk.Box {
 
           this._datetime = value;
           this.date_entry.text = value.format("%F");
-          // Note: GtkCalendar's months are [0,11] while GDateTime uses [1,12]
-          this.calendar.select_month(value.get_month() - 1, value.get_year());
-          this.calendar.select_day(value.get_day_of_month());
+          this.calendar.select_day (value);
        }
     }
 
     construct {
         this.orientation = Gtk.Orientation.HORIZONTAL;
-        get_style_context().add_class("linked");
+        add_css_class("linked");
 
         // The entry for manual editing
         this.date_entry = new Gtk.Entry();
-        this.date_entry.visible = true;
         this.date_entry.max_length = 10;
         this.date_entry.max_width_chars = 10;
         this.date_entry.tooltip_text = _("Enter the date directly");
         this.date_entry.activate.connect(on_date_entry_activated);
-        add(this.date_entry);
+        append(this.date_entry);
 
         // The button with a popover
         var calendar_button = new Gtk.MenuButton();
-        calendar_button.visible = true;
         calendar_button.tooltip_text = _("Select the date from a calendar");
-        add(calendar_button);
+        append(calendar_button);
 
         // The popover that contains the calendar
-        this.calendar_popover = new Gtk.Popover(calendar_button);
+        this.calendar_popover = new Gtk.Popover();
         calendar_button.popover = this.calendar_popover;
 
         // The calendar
         this.calendar = new Gtk.Calendar();
-        this.calendar.visible = true;
         this.calendar.show_day_names = true;
         this.calendar.show_heading = true;
         this.calendar.day_selected.connect(on_calendar_day_selected);
-        this.calendar.day_selected_double_click.connect(on_calendar_day_selected_double_click);
-        this.calendar_popover.add(this.calendar);
+               // XXX
+        // this.calendar.day_selected_double_click.connect(on_calendar_day_selected_double_click);
+        this.calendar_popover.set_child(this.calendar);
     }
 
     [CCode (type="GtkWidget*")]
@@ -97,11 +93,7 @@ public class Seahorse.DatePicker : Gtk.Box {
     }
 
     private void on_calendar_day_selected(Gtk.Calendar calendar) {
-        uint y, m, d;
-
-        calendar.get_date(out y, out m, out d);
-        // Note: GtkCalendar's months are [0,11] while GDateTime uses [1,12]
-        this.datetime = new DateTime.utc((int) y, (int) m + 1, (int) d, 0, 0, 0);
+        this.datetime = calendar.get_date().to_utc();
     }
 
     private void on_calendar_day_selected_double_click(Gtk.Calendar calendar) {
diff --git a/common/deletable.vala b/common/deletable.vala
index 58b78e6d..18e884cd 100644
--- a/common/deletable.vala
+++ b/common/deletable.vala
@@ -62,7 +62,9 @@ public interface Deletable : GLib.Object {
                        }
 
                        /* Now show a prompt choosing between the exporters */
-                       var ret = deleter.prompt(parent);
+                       // XXX
+                       // var ret = deleter.prompt(parent);
+                       var ret = false;
                        if (!ret)
                                break;
 
diff --git a/common/delete-dialog.vala b/common/delete-dialog.vala
index 013c81fd..137cac6a 100644
--- a/common/delete-dialog.vala
+++ b/common/delete-dialog.vala
@@ -19,93 +19,76 @@
  * Author: Stef Walter <stefw collabora co uk>
  */
 
-namespace Seahorse {
+public class Seahorse.DeleteDialog : Adw.MessageDialog {
 
-public class DeleteDialog : Gtk.MessageDialog {
-       private Gtk.ToggleButton _check;
-       private bool _check_require;
+    private Gtk.CheckButton _check;
+    private bool _check_require;
 
-       public string? check_label {
-               get {
-                       if (_check.get_visible())
-                               return _check.get_label();
-                       return null;
-               }
-               set {
-                       if (value == null) {
-                               _check.hide();
-                               value = "";
-                       } else {
-                               _check.show();
-                       }
-                       _check.set_label(value);
-               }
-       }
+    public string? check_label {
+        get {
+            if (_check.get_visible())
+                return _check.get_label();
+            return null;
+        }
+        set {
+            if (value == null) {
+                _check.hide();
+                value = "";
+            } else {
+                _check.show();
+            }
+            _check.set_label(value);
+        }
+    }
 
-       public bool check_value {
-               get {
-                       if (_check.get_visible())
-                               return _check.get_active();
-                       return false;
-               }
-               set {
-                       _check.set_active(value);
-               }
-       }
+    public bool check_value {
+        get {
+            if (_check.get_visible())
+                return _check.get_active();
+            return false;
+        }
+        set {
+            _check.set_active(value);
+        }
+    }
 
-       public bool check_require {
-               get {
-                       return _check_require;
-               }
-               set {
-                       _check_require = value;
-                       update_response_buttons();
-               }
-       }
+    public bool check_require {
+        get {
+            return _check_require;
+        }
+        set {
+            _check_require = value;
+            update_response_buttons();
+        }
+    }
 
-       [CCode (type = "GtkDialog*")]
-       public DeleteDialog(Gtk.Window? parent,
-                           string format,
-                           ...) {
-               GLib.Object(
-                       message_type: Gtk.MessageType.QUESTION,
-                       transient_for: parent,
-                       text: format.vprintf(va_list())
-               );
-       }
+    public DeleteDialog(Gtk.Window? parent,
+                        string format,
+                        ...) {
+        GLib.Object(
+            transient_for: parent,
+            body: format.vprintf(va_list())
+        );
+    }
 
-       construct {
-               set_modal(true);
-               set_destroy_with_parent(true);
+    construct {
+        set_modal(true);
+        set_destroy_with_parent(true);
 
-               _check = new Gtk.CheckButton();
-               ((Gtk.Container)get_message_area()).add(_check);
-               _check.toggled.connect((toggle) => {
-                       update_response_buttons();
-               });
+        this._check = new Gtk.CheckButton();
+        this._check.hide();
+        set_extra_child(this._check);
+        this._check.toggled.connect((toggle) => {
+            update_response_buttons();
+        });
 
-               var cancel = new Gtk.Button.with_mnemonic(_("_Cancel"));
-               add_action_widget(cancel, Gtk.ResponseType.CANCEL);
-               cancel.show();
-
-               var delet = new Gtk.Button.with_mnemonic(_("_Delete"));
-               delet.get_style_context().add_class("destructive-action");
-               add_action_widget(delet, Gtk.ResponseType.OK);
-               delet.show();
-       }
-
-       private void update_response_buttons() {
-               set_response_sensitive(Gtk.ResponseType.OK,
-                                      !_check_require || _check.get_active());
-       }
-
-       public static bool prompt (Gtk.Window? parent, string text) {
-               Gtk.Dialog? dialog = new DeleteDialog(parent, "%s", text);
-               var response = dialog.run();
-               dialog.destroy();
-               return (response == Gtk.ResponseType.OK);
-       }
-
-}
+        add_response("cancel", _("_Cancel"));
+        add_response("delete", _("_Delete"));
+        set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE);
+    }
 
+    private void update_response_buttons() {
+        set_response_enabled("delete",
+                             !_check_require || _check.get_active());
+    }
 }
diff --git a/common/deleter.vala b/common/deleter.vala
index 1c50cdaf..4f3e6d0e 100644
--- a/common/deleter.vala
+++ b/common/deleter.vala
@@ -19,23 +19,13 @@
  * Author: Stef Walter <stefw collabora co uk>
  */
 
-namespace Seahorse {
+public abstract class Seahorse.Deleter : GLib.Object {
 
-public abstract class Deleter : GLib.Object {
-       public abstract Gtk.Dialog create_confirm(Gtk.Window? parent);
+    public abstract Gtk.Window create_confirm(Gtk.Window? parent);
 
-       public abstract unowned GLib.List<GLib.Object> get_objects();
+    public abstract unowned GLib.List<GLib.Object> get_objects();
 
-       public abstract bool add_object (GLib.Object obj);
-
-       public abstract async bool delete(GLib.Cancellable? cancellable) throws GLib.Error;
-
-       public bool prompt(Gtk.Window? parent) {
-               var prompt = this.create_confirm(parent);
-               int res = prompt.run();
-               prompt.destroy();
-               return res == Gtk.ResponseType.OK || res == Gtk.ResponseType.ACCEPT;
-       }
-}
+    public abstract bool add_object(GLib.Object obj);
 
+    public abstract async bool delete(GLib.Cancellable? cancellable) throws GLib.Error;
 }
diff --git a/common/exportable.vala b/common/exportable.vala
index 8ed2db46..adbcd370 100644
--- a/common/exportable.vala
+++ b/common/exportable.vala
@@ -18,224 +18,231 @@
  * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
-namespace Seahorse {
-
-public interface Exportable : GLib.Object {
-       public abstract bool exportable { get; }
-
-       public abstract GLib.List<Exporter> create_exporters(ExporterType type);
-
-       public static bool can_export(GLib.Object object) {
-               if (object is Exportable)
-                       return ((Exportable)object).exportable;
-               return false;
-       }
-
-       public static int export_to_directory_wait(GLib.List<GLib.Object> objects,
-                                                  string directory) throws GLib.Error {
-               var loop = new GLib.MainLoop (null, false);
-               GLib.AsyncResult? result = null;
-               int count = 0;
-
-               foreach (var object in objects) {
-                       if (!Exportable.can_export(object))
-                               continue;
-
-                       var exporters = ((Exportable)object).create_exporters(ExporterType.ANY);
-                       if (exporters == null)
-                               continue;
-
-                       var exporter = exporters.data;
-                       string filename = GLib.Path.build_filename (directory, exporter.filename, null);
-                       var file = GLib.File.new_for_uri(filename);
-
-                       exporter.export_to_file.begin(file, false, null, (obj, res) => {
-                               result = res;
-                               loop.quit();
-                       });
-
-                       loop.run();
-
-                       exporter.export_to_file.end(result);
-
-                       count++;
-               }
-
-               return count;
-       }
-
-       public static uint export_to_text_wait(GLib.List<GLib.Object> objects,
-                                              [CCode (array_length_type = "size_t")] out uint8[] output) 
throws GLib.Error {
-               GLib.List<Exporter> exporters = null;
-
-               foreach (var object in objects) {
-                       if (!Exportable.can_export(object))
-                               continue;
-
-                       /* If we've already found exporters, then add to those */
-                       if (exporters != null) {
-                               foreach (var exporter in exporters)
-                                       exporter.add_object(object);
-
-                       /* Otherwise try and create new exporters for this object */
-                       } else {
-                               exporters = ((Exportable)object).create_exporters (ExporterType.TEXTUAL);
-                       }
-               }
-
-               /* Find the exporter than has the most objects */
-               Exporter? chosen = null;
-               uint total = 0;
-               foreach (var exporter in exporters) {
-                       uint count = exporter.get_objects().length();
-                       if (count > total) {
-                               total = count;
-                               chosen = exporter;
-                       }
-               }
-
-               if (chosen != null) {
-                       var loop = new GLib.MainLoop (null, false);
-                       GLib.AsyncResult? result = null;
-
-                       chosen.export.begin(null, (obj, res) => {
-                               result = res;
-                               loop.quit();
-                       });
-
-                       loop.run();
-
-                       output = chosen.export.end(result);
-                       return total;
-               }
-
-               output = { };
-               return 0;
-       }
-
-       public static int export_to_prompt_wait(GLib.List<GLib.Object> objects,
-                                               Gtk.Window? parent) throws GLib.Error {
-               int count = 0;
-
-               var pending = new GLib.GenericSet<weak GLib.Object>(GLib.direct_hash, GLib.direct_equal);
-               foreach (var object in objects)
-                       pending.add(object);
-
-               foreach (var object in objects) {
-                       if (!pending.contains(object))
-                               continue;
-
-                       if (!Exportable.can_export(object)) {
-                               pending.remove(object);
-                               continue;
-                       }
-
-                       var exporters = ((Exportable)object).create_exporters(ExporterType.ANY);
-                       if (exporters == null)
-                               continue;
-
-                       foreach (var x in objects) {
-                               if (x == object)
-                                       continue;
-                               if (pending.contains(x)) {
-                                       foreach (var exporter in exporters)
-                                               exporter.add_object(x);
-                               }
-                       }
-
-                       string? directory = null;
-                       GLib.File? file;
-                       Exporter? exporter;
-
-                       /* Now show a prompt choosing between the exporters */
-                       bool ret = Exportable.prompt(exporters, parent, ref directory,
-                                                    out file, out exporter);
-                       if (!ret)
-                               break;
-
-                       var loop = new GLib.MainLoop(null, false);
-                       GLib.AsyncResult? result = null;
-
-                       exporter.export_to_file.begin(file, true, null, (obj, res) => {
-                               result = res;
-                               loop.quit();
-                       });
-
-                       loop.run();
-
-                       exporter.export_to_file.end(result);
-                       foreach (var e in exporter.get_objects()) {
-                               pending.remove(e);
-                               count++;
-                       }
-               }
-
-               return count;
-       }
-
-       private static string calculate_basename(GLib.File file,
-                                                string extension) {
-               var basename = file.get_basename();
-               var dot = basename.last_index_of_char('.');
-               if (dot != -1)
-                       basename = basename.substring(0, dot);
-               return "%s%s".printf(basename, extension);
-       }
-
-       public static bool prompt(GLib.List<Exporter> exporters,
-                                 Gtk.Window? parent,
-                                 ref string? directory,
-                                 out GLib.File chosen_file,
-                                 out Exporter chosen_exporter) {
-               var chooser = new Gtk.FileChooserNative(null, parent, Gtk.FileChooserAction.SAVE,
-                                                       _("Export"), _("_Cancel"));
-
-               chooser.set_local_only(false);
-               chooser.set_do_overwrite_confirmation(true);
-
-               if (directory != null)
-                       chooser.set_current_folder(directory);
-
-               Gtk.FileFilter? first = null;
-               var filters = new GLib.HashTable<Gtk.FileFilter, Exporter>(GLib.direct_hash, 
GLib.direct_equal);
-               foreach (var exporter in exporters) {
-                       var filter = exporter.file_filter;
-                       filters.replace(filter, exporter);
-                       chooser.add_filter(filter);
-                       if (first == null)
-                               first = filter;
-               }
-
-               chooser.notify.connect((obj, prop) => {
-                       var exporter = filters.lookup(chooser.get_filter());
-                       var name = exporter.filename;
-                       var dot = name.last_index_of_char('.');
-                       if (dot != -1) {
-                               var file = chooser.get_file();
-                               if (file != null) {
-                                       var basename = calculate_basename(file, name.substring(dot));
-                                       chooser.set_current_name(basename);
-                               } else {
-                                       chooser.set_current_name(name);
-                               }
-                       }
-               });
-
-               chooser.set_filter(first);
-
-               if (chooser.run() == Gtk.ResponseType.ACCEPT) {
-                       chosen_file = chooser.get_file();
-                       chosen_exporter = filters.lookup(chooser.get_filter());
-                       directory = chooser.get_current_folder();
-                       chooser.destroy();
-                       return true;
-               }
-
-               chosen_file = null;
-               chosen_exporter = null;
-               chooser.destroy();
-               return false;
-       }
-}
-
+public interface Seahorse.Exportable : GLib.Object {
+    public abstract bool exportable { get; }
+
+    public abstract GLib.List<Exporter> create_exporters(ExporterType type);
+
+    public static bool can_export(GLib.Object object) {
+        if (object is Exportable)
+            return ((Exportable)object).exportable;
+        return false;
+    }
+
+    public static int export_to_directory_wait(GLib.List<GLib.Object> objects,
+                                               string directory) throws GLib.Error {
+        var loop = new GLib.MainLoop (null, false);
+        GLib.AsyncResult? result = null;
+        int count = 0;
+
+        foreach (var object in objects) {
+            if (!Exportable.can_export(object))
+                continue;
+
+            var exporters = ((Exportable)object).create_exporters(ExporterType.ANY);
+            if (exporters == null)
+                continue;
+
+            var exporter = exporters.data;
+            string filename = GLib.Path.build_filename (directory, exporter.filename, null);
+            var file = GLib.File.new_for_uri(filename);
+
+            exporter.export_to_file.begin(file, false, null, (obj, res) => {
+                result = res;
+                loop.quit();
+            });
+
+            loop.run();
+
+            exporter.export_to_file.end(result);
+
+            count++;
+        }
+
+        return count;
+    }
+
+    public static uint export_to_text_wait(GLib.List<GLib.Object> objects,
+                                           [CCode (array_length_type = "size_t")] out uint8[] output) throws 
GLib.Error {
+        GLib.List<Exporter> exporters = null;
+
+        foreach (var object in objects) {
+            if (!Exportable.can_export(object))
+                continue;
+
+            /* If we've already found exporters, then add to those */
+            if (exporters != null) {
+                foreach (var exporter in exporters)
+                    exporter.add_object(object);
+
+            /* Otherwise try and create new exporters for this object */
+            } else {
+                exporters = ((Exportable)object).create_exporters (ExporterType.TEXTUAL);
+            }
+        }
+
+        /* Find the exporter than has the most objects */
+        Exporter? chosen = null;
+        uint total = 0;
+        foreach (var exporter in exporters) {
+            uint count = exporter.get_objects().length();
+            if (count > total) {
+                total = count;
+                chosen = exporter;
+            }
+        }
+
+        if (chosen != null) {
+            var loop = new GLib.MainLoop (null, false);
+            GLib.AsyncResult? result = null;
+
+            chosen.export.begin(null, (obj, res) => {
+                result = res;
+                loop.quit();
+            });
+
+            loop.run();
+
+            output = chosen.export.end(result);
+            return total;
+        }
+
+        output = { };
+        return 0;
+    }
+
+    public static int export_to_prompt_wait(GLib.List<GLib.Object> objects,
+                                            Gtk.Window? parent) throws GLib.Error {
+        int count = 0;
+
+        var pending = new GLib.GenericSet<weak GLib.Object>(GLib.direct_hash, GLib.direct_equal);
+        foreach (var object in objects)
+            pending.add(object);
+
+        foreach (var object in objects) {
+            if (!pending.contains(object))
+                continue;
+
+            if (!Exportable.can_export(object)) {
+                pending.remove(object);
+                continue;
+            }
+
+            var exporters = ((Exportable)object).create_exporters(ExporterType.ANY);
+            if (exporters == null)
+                continue;
+
+            foreach (var x in objects) {
+                if (x == object)
+                    continue;
+                if (pending.contains(x)) {
+                    foreach (var exporter in exporters)
+                        exporter.add_object(x);
+                }
+            }
+
+            string? directory = null;
+            GLib.File? file;
+            Exporter? exporter;
+
+            /* Now show a prompt choosing between the exporters */
+            bool ret = Exportable.prompt(exporters, parent, ref directory,
+                                         out file, out exporter);
+            if (!ret)
+                break;
+
+            var loop = new GLib.MainLoop(null, false);
+            GLib.AsyncResult? result = null;
+
+            exporter.export_to_file.begin(file, true, null, (obj, res) => {
+                result = res;
+                loop.quit();
+            });
+
+            loop.run();
+
+            exporter.export_to_file.end(result);
+            foreach (var e in exporter.get_objects()) {
+                pending.remove(e);
+                count++;
+            }
+        }
+
+        return count;
+    }
+
+    private static string calculate_basename(GLib.File file,
+                                             string extension) {
+        var basename = file.get_basename();
+        var dot = basename.last_index_of_char('.');
+        if (dot != -1)
+            basename = basename.substring(0, dot);
+        return "%s%s".printf(basename, extension);
+    }
+
+       /* XXX */
+    public static bool prompt(GLib.List<Exporter> exporters,
+                              Gtk.Window? parent,
+                              ref string? directory,
+                              out GLib.File chosen_file,
+                              out Exporter chosen_exporter) {
+        var chooser = new Gtk.FileChooserNative(null, parent, Gtk.FileChooserAction.SAVE,
+                                                _("Export"), _("_Cancel"));
+
+        if (directory != null)
+            chooser.set_current_folder(File.new_for_path(directory));
+
+        Gtk.FileFilter? first = null;
+        var filters = new GLib.HashTable<Gtk.FileFilter, Exporter>(GLib.direct_hash, GLib.direct_equal);
+        foreach (var exporter in exporters) {
+            var filter = exporter.file_filter;
+            filters.replace(filter, exporter);
+            chooser.add_filter(filter);
+            if (first == null)
+                first = filter;
+        }
+
+        chooser.notify.connect((obj, prop) => {
+            var exporter = filters.lookup(chooser.get_filter());
+            var name = exporter.filename;
+            var dot = name.last_index_of_char('.');
+            if (dot != -1) {
+                var file = chooser.get_file();
+                if (file != null) {
+                    var basename = calculate_basename(file, name.substring(dot));
+                    chooser.set_current_name(basename);
+                } else {
+                    chooser.set_current_name(name);
+                }
+            }
+        });
+
+        chooser.set_filter(first);
+
+        // XXX check
+        int response = Gtk.ResponseType.CANCEL;
+        bool got_response = false;
+        chooser.response.connect((response) => {
+            got_response = true;
+        });
+
+        chooser.modal = true;
+        var main_context = MainContext.default();
+        while (!got_response) {
+            main_context.iteration(true);
+        }
+
+        if (response == Gtk.ResponseType.ACCEPT) {
+            chosen_file = chooser.get_file();
+            chosen_exporter = filters.lookup(chooser.get_filter());
+            directory = chooser.get_current_folder().get_path();
+            chooser.destroy();
+            return true;
+        }
+
+        chosen_file = null;
+        chosen_exporter = null;
+        chooser.destroy();
+        return false;
+    }
 }
diff --git a/common/interaction.vala b/common/interaction.vala
index 1e5f0f3e..cd50d520 100644
--- a/common/interaction.vala
+++ b/common/interaction.vala
@@ -45,7 +45,9 @@ public class Seahorse.Interaction : GLib.TlsInteraction {
         if (this.parent != null)
             dialog.transient_for = this.parent;
 
-        int response = dialog.run();
+               // XXX
+        // int response = dialog.run();
+        int response = Gtk.ResponseType.CANCEL;
 
         if (response == Gtk.ResponseType.ACCEPT)
             password.set_value_full((uint8[])gcr_secure_memory_strdup(dialog.get_text()),
diff --git a/common/meson.build b/common/meson.build
index 47a41175..3eca8de3 100644
--- a/common/meson.build
+++ b/common/meson.build
@@ -10,16 +10,14 @@ common_sources = files(
   'deleter.vala',
   'exportable.vala',
   'exporter.vala',
-  'icons.vala',
   'interaction.vala',
-  'item-list.vala',
   'lockable.vala',
   'object.vala',
   'passphrase-prompt.vala',
   'pgp-settings.vala',
   'place.vala',
   'prefs.vala',
-  'registry.vala',
+  'renderer.vala',
   'server-category.vala',
   'types.vala',
   'util.vala',
@@ -29,10 +27,9 @@ common_sources = files(
 
 common_deps = [
   glib_deps,
-  gtk,
-  gcr,
-  gcr_ui,
-  libhandy_dep,
+  gtk4_dep,
+  gcr4_dep,
+  libadwaita_dep,
   config,
 ]
 
diff --git a/common/passphrase-prompt.vala b/common/passphrase-prompt.vala
index 373a9b8f..a225fe73 100644
--- a/common/passphrase-prompt.vala
+++ b/common/passphrase-prompt.vala
@@ -25,13 +25,9 @@ public const int SEAHORSE_PASS_BAD = 0x00000001;
 public const int SEAHORSE_PASS_NEW = 0x01000000;
 
 public class Seahorse.PassphrasePrompt : Gtk.Dialog {
-    // gnome hig small space in pixels
-    private const int HIG_SMALL = 6;
-    // gnome hig large space in pixels
-    private const int HIG_LARGE = 12;
 
-    private Gtk.Entry secure_entry;
-    private Gtk.Entry? confirm_entry;
+    private Gtk.PasswordEntry pass_entry;
+    private Gtk.PasswordEntry? confirm_entry;
     private Gtk.CheckButton? check_option;
 
 #if ! _DEBUG
@@ -45,42 +41,39 @@ public class Seahorse.PassphrasePrompt : Gtk.Dialog {
             icon_name: "dialog-password-symbolic"
         );
 
-        Gtk.Box wvbox = new Gtk.Box(Gtk.Orientation.VERTICAL, HIG_LARGE * 2);
-        get_content_area().add(wvbox);
-        wvbox.set_border_width(HIG_LARGE);
+        Gtk.Box wvbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 24);
+        set_child(wvbox);
 
-        Gtk.Box chbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, HIG_LARGE);
-        wvbox.pack_start (chbox, false, false);
+        Gtk.Box chbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 12);
+        wvbox.append(chbox);
 
         // The image
-        Gtk.Image img = new Gtk.Image.from_icon_name("dialog-password-symbolic", Gtk.IconSize.DIALOG);
-        img.set_alignment(0.0f, 0.0f);
-        chbox.pack_start(img, false, false);
+        Gtk.Image img = new Gtk.Image.from_icon_name("dialog-password-symbolic");
+        chbox.append(img);
 
-        Gtk.Box box = new Gtk.Box(Gtk.Orientation.VERTICAL, HIG_SMALL);
-        chbox.pack_start (box);
+        Gtk.Box box = new Gtk.Box(Gtk.Orientation.VERTICAL, 6);
+        chbox.append(box);
 
         // The description text
         if (description != null) {
             Gtk.Label desc_label = new Gtk.Label(utf8_validate (description));
-            desc_label.set_alignment(0.0f, 0.5f);
-            desc_label.set_line_wrap(true);
-            box.pack_start(desc_label, true, false);
+            desc_label.xalign = 0;
+            desc_label.wrap = true;
+            box.append(desc_label);
         }
 
         Gtk.Grid grid = new Gtk.Grid();
-        grid.set_row_spacing(HIG_SMALL);
-        grid.set_column_spacing(HIG_LARGE);
-        box.pack_start(grid, false, false);
+        grid.set_row_spacing(6);
+        grid.set_column_spacing(12);
+        box.append(grid);
 
         // The first entry (if we have one)
         if (confirm) {
             Gtk.Label prompt_label = new Gtk.Label(utf8_validate (prompt));
-            prompt_label.set_alignment(0.0f, 0.5f);
+            prompt_label.xalign = 0;
             grid.attach(prompt_label, 0, 0);
 
-            this.confirm_entry = new Gtk.Entry.with_buffer(new Gcr.SecureEntryBuffer());
-            this.confirm_entry.set_visibility(false);
+            this.confirm_entry = new Gtk.PasswordEntry();
             this.confirm_entry.set_size_request(200, -1);
             this.confirm_entry.activate.connect(confirm_callback);
             this.confirm_entry.changed.connect(entry_changed);
@@ -90,21 +83,20 @@ public class Seahorse.PassphrasePrompt : Gtk.Dialog {
 
         // The second and main entry
         Gtk.Label confirm_label = new Gtk.Label(utf8_validate (confirm? _("Confirm:") : prompt));
-        confirm_label.set_alignment(0.0f, 0.5f);
+        confirm_label.xalign = 0;
         grid.attach(confirm_label, 0, 1);
 
-        this.secure_entry = new Gtk.Entry.with_buffer(new Gcr.SecureEntryBuffer());
-        this.secure_entry.set_size_request(200, -1);
-        this.secure_entry.set_visibility(false);
-        this.secure_entry.activate.connect(() => {
+        this.pass_entry = new Gtk.PasswordEntry();
+        this.pass_entry.set_size_request(200, -1);
+        this.pass_entry.activate.connect(() => {
             if (get_widget_for_response(Gtk.ResponseType.ACCEPT).sensitive)
                 response(Gtk.ResponseType.ACCEPT);
         });
-        grid.attach(secure_entry, 1, 1);
+        grid.attach(pass_entry, 1, 1);
         if (confirm)
-            this.secure_entry.changed.connect(entry_changed);
+            this.pass_entry.changed.connect(entry_changed);
         else
-            this.secure_entry.grab_focus();
+            this.pass_entry.grab_focus();
 
         // The checkbox
         if (check != null) {
@@ -112,28 +104,20 @@ public class Seahorse.PassphrasePrompt : Gtk.Dialog {
             grid.attach(this.check_option, 1, 2);
         }
 
-        grid.show_all();
-
         Gtk.Button cancel_button = new Gtk.Button.with_mnemonic(_("_Cancel"));
         add_action_widget(cancel_button, Gtk.ResponseType.REJECT);
-        cancel_button.set_can_default(true);
 
         Gtk.Button ok_button = new Gtk.Button.with_mnemonic(_("_OK"));
         add_action_widget(ok_button, Gtk.ResponseType.ACCEPT);
-        ok_button.set_can_default(true);
-        ok_button.grab_default();
+        set_default_widget(ok_button);
 
-        // Signals
-        this.map_event.connect(grab_keyboard);
-        this.unmap_event.connect(ungrab_keyboard);
-        this.window_state_event.connect(window_state_changed);
-        this.key_press_event.connect(key_press);
+        // Signals XXX
+        // this.map_event.connect(grab_keyboard);
+        // this.unmap_event.connect(ungrab_keyboard);
+        // this.window_state_event.connect(window_state_changed);
+        // this.key_press_event.connect(key_press);
 
-        set_position(Gtk.WindowPosition.CENTER);
         set_resizable(false);
-        set_keep_above(true);
-        show_all();
-        get_window().focus(Gdk.CURRENT_TIME);
 
         if (confirm)
             entry_changed (null);
@@ -146,7 +130,7 @@ public class Seahorse.PassphrasePrompt : Gtk.Dialog {
     }
 
     public string get_text() {
-        return this.secure_entry.text;
+        return this.pass_entry.text;
     }
 
     public bool checked() {
@@ -174,7 +158,9 @@ public class Seahorse.PassphrasePrompt : Gtk.Dialog {
         return result;
     }
 
-    private bool key_press (Gtk.Widget widget, Gdk.EventKey event) {
+    //XXX
+#if 0
+    private bool key_press(Gtk.EventControllerKey controller, uint keyval, uint keycode, Gdk.ModifierType 
state) {
         // Close the dialog when hitting "Esc".
         if (event.keyval == Gdk.Key.Escape) {
             response(Gtk.ResponseType.REJECT);
@@ -213,22 +199,12 @@ public class Seahorse.PassphrasePrompt : Gtk.Dialog {
             Gdk.Seat seat = display.get_default_seat();
 
             seat.ungrab();
-               }
+        }
         this.keyboard_grabbed = false;
 #endif
         return false;
     }
 
-    /* When enter is pressed in the confirm entry, move */
-    private void confirm_callback(Gtk.Widget widget) {
-        this.secure_entry.grab_focus();
-    }
-
-    private void entry_changed (Gtk.Editable? editable) {
-        set_response_sensitive(Gtk.ResponseType.ACCEPT,
-                               this.secure_entry.text == this.confirm_entry.text);
-    }
-
     private bool window_state_changed (Gtk.Widget win, Gdk.EventWindowState event) {
         Gdk.WindowState state = win.get_window().get_state();
 
@@ -242,5 +218,16 @@ public class Seahorse.PassphrasePrompt : Gtk.Dialog {
 
         return false;
     }
+#endif
+
+    /* When enter is pressed in the confirm entry, move */
+    private void confirm_callback(Gtk.Widget widget) {
+        this.pass_entry.grab_focus();
+    }
+
+    private void entry_changed (Gtk.Editable? editable) {
+        set_response_sensitive(Gtk.ResponseType.ACCEPT,
+                               this.pass_entry.text == this.confirm_entry.text);
+    }
 
 }
diff --git a/common/place.vala b/common/place.vala
index 3beee304..95066da0 100644
--- a/common/place.vala
+++ b/common/place.vala
@@ -22,7 +22,7 @@
  * A SeahorsePlace is a collection of objects (passwords/keys/certificates/...).
  * An example of this is a keyring.
  */
-public interface Seahorse.Place : Gcr.Collection {
+public interface Seahorse.Place : GLib.ListModel {
 
     /**
      * We generally divide a SeahorsePlace in some high level categories.
@@ -51,16 +51,8 @@ public interface Seahorse.Place : Gcr.Collection {
     public abstract string label { owned get; set; }
     public abstract string description { owned get; }
     public abstract string uri { owned get; }
-    public abstract Icon icon { owned get; }
     public abstract Category category { owned get; }
 
-    /**
-     * In some cases, we do not want to show the Place in the sidebar
-     * if it's empty (for example p11-kit's System Trust), while we do
-     * want this for others (like libsecret keyrings).
-     */
-    public abstract bool show_if_empty { get; }
-
     /**
      * Returns the {@link GLib.Action}s that are defined for this Place,
      * or null if none.
diff --git a/common/prefs-keyservers.vala b/common/prefs-keyservers.vala
index adc1ed4d..564aebd8 100644
--- a/common/prefs-keyservers.vala
+++ b/common/prefs-keyservers.vala
@@ -19,10 +19,8 @@
  */
 
 [GtkTemplate (ui = "/org/gnome/Seahorse/seahorse-prefs-keyservers.ui")]
-public class Seahorse.PrefsKeyservers : Hdy.PreferencesPage {
+public class Seahorse.PrefsKeyservers : Adw.PreferencesPage {
 
-    [GtkChild]
-    public unowned Gtk.Grid keyserver_tab;
     [GtkChild]
     private unowned Gtk.ListBox keyservers_list;
     [GtkChild]
@@ -42,8 +40,7 @@ public class Seahorse.PrefsKeyservers : Hdy.PreferencesPage {
 
         KeyserverControl skc = new KeyserverControl("server-publish-to",
                                                     _("None: Don’t publish keys"));
-        this.keyserver_publish.add(skc);
-        this.keyserver_publish.show_all();
+        this.keyserver_publish.append(skc);
 
         this.keyserver_publish_to_label.set_mnemonic_widget(skc);
 
@@ -63,35 +60,32 @@ public class Seahorse.PrefsKeyservers : Hdy.PreferencesPage {
             this.server = server;
 
             var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 2);
-            box.margin = 6;
-            add(box);
+            // box.margin = 6; XXX
+            set_child(box);
 
             this.label = new Gtk.Label(server.uri);
-            box.pack_start(this.label, false);
+            box.append(this.label);
 
             this.entry = new Gtk.Entry();
-            this.entry.set_no_show_all(true);
             this.entry.hide();
             this.entry.activate.connect(on_entry_activated);
-            box.pack_start(this.entry);
+            box.append(this.entry);
 
             var remove_button = new Gtk.Button.from_icon_name("list-remove-symbolic");
             remove_button.get_style_context().add_class("flat");
             remove_button.clicked.connect(on_remove_button_clicked);
-            box.pack_end(remove_button, false);
+            box.append(remove_button);
 
             var edit_button = new Gtk.Button.from_icon_name("document-edit-symbolic");
             edit_button.get_style_context().add_class("flat");
             edit_button.clicked.connect(on_edit_button_clicked);
-            box.pack_end(edit_button, false);
-
-            show_all();
+            box.append(edit_button);
         }
 
         private void on_edit_button_clicked(Gtk.Button edit_button) {
             this.label.hide();
             this.entry.set_text(this.label.get_text());
-            this.entry.show_now();
+            this.entry.show();
             this.entry.grab_focus();
         }
 
@@ -124,15 +118,17 @@ public class Seahorse.PrefsKeyservers : Hdy.PreferencesPage {
 
     [GtkCallback]
     private void on_add_button_clicked(Gtk.Button button) {
-        AddKeyserverDialog dialog = new AddKeyserverDialog(get_toplevel() as Gtk.Window);
-
-        if (dialog.run() == Gtk.ResponseType.OK) {
-            string? result = dialog.calculate_keyserver_uri();
+        AddKeyserverDialog dialog = new AddKeyserverDialog(get_root() as Gtk.Window);
 
-            if (result != null)
-                Pgp.Backend.get().add_remote(result, true);
-        }
+        dialog.response.connect((response) => {
+            if (response == Gtk.ResponseType.OK) {
+                string? result = dialog.calculate_keyserver_uri();
+                if (result != null)
+                    Pgp.Backend.get().add_remote(result, true);
+            }
 
-        dialog.destroy();
+            dialog.destroy();
+        });
+        dialog.present();
     }
 }
diff --git a/common/prefs.vala b/common/prefs.vala
index cd0c8e74..caef69d0 100644
--- a/common/prefs.vala
+++ b/common/prefs.vala
@@ -18,7 +18,7 @@
  * <http://www.gnu.org/licenses/>.
  */
 
-public class Seahorse.Prefs : Hdy.PreferencesWindow {
+public class Seahorse.Prefs : Adw.PreferencesWindow {
 
     /**
      * Create a new preferences window.
diff --git a/common/renderer.vala b/common/renderer.vala
new file mode 100644
index 00000000..56633c93
--- /dev/null
+++ b/common/renderer.vala
@@ -0,0 +1,29 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2022 Niels De Graef <nielsdegraef gmail com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Niels De Graef <nielsdegraef gmail com>
+ */
+
+public interface Seahorse.Renderer : GLib.Object {
+
+    public abstract Gtk.Widget create_widget();
+
+    public static Renderer? new_for_parsed(Gcr.Parsed parsed) {
+        return null;
+    }
+}
diff --git a/common/seahorse-prefs-keyservers.ui b/common/seahorse-prefs-keyservers.ui
index 419ab9cf..41bd4f40 100644
--- a/common/seahorse-prefs-keyservers.ui
+++ b/common/seahorse-prefs-keyservers.ui
@@ -1,44 +1,29 @@
 <?xml version="1.0"?>
 <interface>
-  <requires lib="gtk+" version="3.24"/>
-  <template class="SeahorsePrefsKeyservers" parent="HdyPreferencesPage">
-    <property name="visible">True</property>
+  <template class="SeahorsePrefsKeyservers" parent="AdwPreferencesPage">
     <property name="title" translatable="yes">Keyservers</property>
     <child>
-      <object class="HdyPreferencesGroup">
-        <property name="visible">True</property>
+      <object class="AdwPreferencesGroup">
         <property name="title" translatable="yes">Keyservers</property>
+        <child type="header-suffix">
+          <object class="GtkButton" id="keyserver_add_button">
+            <property name="halign">end</property>
+            <property name="label" translatable="yes">Add keyserver</property>
+            <signal name="clicked" handler="on_add_button_clicked"/>
+          </object>
+        </child>
         <child>
-          <object class="GtkBox">
-            <property name="visible">True</property>
-            <property name="orientation">vertical</property>
-            <property name="spacing">6</property>
-            <child>
-              <object class="GtkScrolledWindow">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="hscrollbar_policy">never</property>
-                <property name="vscrollbar_policy">automatic</property>
-                <property name="propagate-natural-height">True</property>
-                <property name="max-content-height">300</property>
-                <child>
-                  <object class="GtkListBox" id="keyservers_list">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="hexpand">True</property>
-                    <style>
-                      <class name="content"/>
-                    </style>
-                  </object>
-                </child>
-              </object>
-            </child>
+          <object class="GtkScrolledWindow">
+            <property name="hscrollbar_policy">never</property>
+            <property name="vscrollbar_policy">automatic</property>
+            <property name="propagate-natural-height">True</property>
+            <property name="max-content-height">300</property>
             <child>
-              <object class="GtkButton" id="keyserver_add_button">
-                <property name="visible">True</property>
-                <property name="halign">end</property>
-                <property name="label" translatable="yes">Add keyserver</property>
-                <signal name="clicked" handler="on_add_button_clicked"/>
+              <object class="GtkListBox" id="keyservers_list">
+                <property name="hexpand">True</property>
+                <style>
+                  <class name="boxed-list"/>
+                </style>
               </object>
             </child>
           </object>
@@ -46,68 +31,56 @@
       </object>
     </child>
     <child>
-      <object class="HdyPreferencesGroup">
-        <property name="visible">True</property>
+      <object class="AdwPreferencesGroup">
         <property name="title" translatable="yes">Key Synchronization</property>
         <child>
-          <object class="GtkGrid" id="keyserver_tab">
-            <property name="visible">True</property>
+          <object class="GtkGrid">
             <property name="column_spacing">12</property>
             <property name="row_spacing">12</property>
             <child>
               <object class="GtkLabel" id="keyserver_publish_to_label">
-                <property name="visible">True</property>
                 <property name="xalign">0</property>
                 <property name="label" translatable="yes">_Publish keys to:</property>
                 <property name="use_underline">True</property>
+                <layout>
+                  <property name="row">0</property>
+                  <property name="column">0</property>
+                </layout>
               </object>
-              <packing>
-                <property name="top_attach">0</property>
-                <property name="left_attach">0</property>
-              </packing>
             </child>
             <child>
               <object class="GtkBox" id="keyserver_publish">
-                <property name="visible">True</property>
                 <property name="orientation">vertical</property>
                 <child>
                   <placeholder/>
                 </child>
+                <layout>
+                  <property name="row">0</property>
+                  <property name="column">1</property>
+                </layout>
               </object>
-              <packing>
-                <property name="top_attach">0</property>
-                <property name="left_attach">1</property>
-              </packing>
             </child>
             <child>
               <object class="GtkCheckButton" id="auto_retrieve">
                 <property name="label" translatable="yes">Automatically retrieve keys from _key 
servers</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
                 <property name="use_underline">True</property>
-                <property name="draw_indicator">True</property>
+                <layout>
+                  <property name="row">1</property>
+                  <property name="column">0</property>
+                  <property name="column-span">2</property>
+                </layout>
               </object>
-              <packing>
-                <property name="top_attach">1</property>
-                <property name="left_attach">0</property>
-                <property name="width">2</property>
-              </packing>
             </child>
             <child>
               <object class="GtkCheckButton" id="auto_sync">
                 <property name="label" translatable="yes">Automatically synchronize _modified keys with key 
servers</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
                 <property name="use_underline">True</property>
-                <property name="draw_indicator">True</property>
+                <layout>
+                  <property name="row">2</property>
+                  <property name="column">0</property>
+                  <property name="column-span">2</property>
+                </layout>
               </object>
-              <packing>
-                <property name="top_attach">2</property>
-                <property name="left_attach">0</property>
-                <property name="width">2</property>
-              </packing>
             </child>
           </object>
         </child>
diff --git a/common/util.vala b/common/util.vala
index 06b78bec..ad033412 100644
--- a/common/util.vala
+++ b/common/util.vala
@@ -21,32 +21,31 @@
 
 namespace Seahorse.Util {
 
-       public void show_error (Gtk.Widget? parent,
-                               string? heading,
-                               string? message) {
-               Gtk.Window? window = null;
+    public void show_error (Gtk.Widget? parent,
+                            string? heading,
+                            string? message) {
+        Gtk.Window? window = null;
 
-               if (message == null)
-                       message = "";
+        if (message == null)
+            message = "";
 
-               if (parent != null) {
-                       if (!(parent is Gtk.Window))
-                               parent = parent.get_toplevel();
-                       if (parent is Gtk.Window)
-                               window = (Gtk.Window)parent;
-               }
+        if (parent != null && !(parent is Gtk.Window)) {
+            parent = parent.get_root() as Gtk.Window;
+        }
 
-               var dialog = new Gtk.MessageDialog(window, Gtk.DialogFlags.MODAL,
-                                                  Gtk.MessageType.ERROR,
-                                                  Gtk.ButtonsType.CLOSE, "");
-               if (heading == null)
-                       dialog.set("text", message);
-               else
-                       dialog.set("text", heading, "secondary-text", message);
+        var dialog = new Gtk.MessageDialog(window, Gtk.DialogFlags.MODAL,
+                                           Gtk.MessageType.ERROR,
+                                           Gtk.ButtonsType.CLOSE, "");
+        if (heading == null)
+            dialog.set("text", message);
+        else
+            dialog.set("text", heading, "secondary-text", message);
 
-               dialog.run();
-               dialog.destroy();
-       }
+        dialog.response.connect(() => {
+            dialog.destroy();
+        });
+        dialog.show();
+    }
 
     public void toggle_action (GLib.SimpleAction action,
                                GLib.Variant?     variant,
diff --git a/common/viewable.vala b/common/viewable.vala
index da5a79fa..73917416 100644
--- a/common/viewable.vala
+++ b/common/viewable.vala
@@ -44,8 +44,9 @@ public interface Viewable : GLib.Object {
                                return false;
 
                        object.set_data("viewable-window", window);
-                       window.destroy.connect(() => {
+                       window.close_request.connect(() => {
                                object.set_data_full("viewable-window", null, null);
+                return false;
                        });
                }
 
diff --git a/data/seahorse.gresource.xml b/data/seahorse.gresource.xml
index ec3b45e2..bf6cd96b 100644
--- a/data/seahorse.gresource.xml
+++ b/data/seahorse.gresource.xml
@@ -31,13 +31,12 @@
     <file alias="seahorse-gpgme-add-uid.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-gpgme-add-uid.ui</file>
     <file alias="seahorse-gpgme-expires-dialog.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-gpgme-expires-dialog.ui</file>
     <file alias="seahorse-gpgme-generate-dialog.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-gpgme-generate-dialog.ui</file>
-    <file alias="seahorse-gpgme-revoke-dialog.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-revoke.ui</file>
+    <file alias="seahorse-gpgme-revoke-dialog.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-gpgme-revoke-dialog.ui</file>
     <file alias="seahorse-gpgme-sign-dialog.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-gpgme-sign-dialog.ui</file>
     <file alias="seahorse-keyserver-results.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-keyserver-results.ui</file>
     <file alias="seahorse-keyserver-search.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-keyserver-search.ui</file>
     <file alias="seahorse-keyserver-sync.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-keyserver-sync.ui</file>
-    <file alias="seahorse-pgp-private-key-properties.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-pgp-private-key-properties.ui</file>
-    <file alias="seahorse-pgp-public-key-properties.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-pgp-public-key-properties.ui</file>
+    <file alias="seahorse-pgp-key-properties.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-pgp-key-properties.ui</file>
     <file alias="seahorse-pgp-subkey-list-box-row.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-pgp-subkey-list-box-row.ui</file>
     <file alias="seahorse-pgp-uid-list-box-row.ui" 
preprocess="xml-stripblanks">../pgp/seahorse-pgp-uid-list-box-row.ui</file>
 
diff --git a/gkr/gkr-backend.vala b/gkr/gkr-backend.vala
index 6f9b3fa5..8c43b5bb 100644
--- a/gkr/gkr-backend.vala
+++ b/gkr/gkr-backend.vala
@@ -17,170 +17,160 @@
  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
-namespace Seahorse {
-namespace Gkr {
+namespace Seahorse.Gkr {
 
 private class MyService : Secret.Service {
-       public override GLib.Type get_collection_gtype() {
-               return typeof(Keyring);
-       }
+    public override GLib.Type get_collection_gtype() {
+        return typeof(Keyring);
+    }
 
-       public override GLib.Type get_item_gtype() {
-               return typeof(Item);
-       }
+    public override GLib.Type get_item_gtype() {
+        return typeof(Item);
+    }
 }
 
-public class Backend: GLib.Object , Gcr.Collection, Seahorse.Backend {
-       public string name {
-               get { return NAME; }
-       }
+public class Backend: GLib.Object, GLib.ListModel, Seahorse.Backend {
 
-       public string label {
-               get { return _("Passwords"); }
-       }
+    public string name {
+        get { return NAME; }
+    }
 
-       public string description {
-               get { return _("Stored personal passwords, credentials and secrets"); }
-       }
+    public string label {
+        get { return _("Passwords"); }
+    }
+
+    public string description {
+        get { return _("Stored personal passwords, credentials and secrets"); }
+    }
 
     public ActionGroup actions {
         owned get { return this._actions; }
     }
 
-       public GLib.HashTable<string, string> aliases {
-               get { return this._aliases; }
-       }
-
-       private bool _loaded;
-       public bool loaded {
-               get { return this._loaded; }
-       }
-
-       public Secret.Service? service {
-               get { return this._service; }
-       }
-
-       private static Backend? _instance = null;
-       private Secret.Service _service;
-       private GLib.HashTable<string, Keyring> _keyrings;
-       private GLib.HashTable<string, string> _aliases;
-       private ActionGroup _actions;
-
-       construct {
-               return_val_if_fail(_instance == null, null);
-               Backend._instance = this;
-
-               this._actions = BackendActions.instance(this);
-               this._keyrings = new GLib.HashTable<string, Keyring>(GLib.str_hash, GLib.str_equal);
-               this._aliases = new GLib.HashTable<string, string>(GLib.str_hash, GLib.str_equal);
-
-               Secret.Service.open.begin(typeof(MyService), null,
-                                         Secret.ServiceFlags.OPEN_SESSION, null, (obj, res) => {
-                       try {
-                               this._service = Secret.Service.open.end(res);
-                               this._service.notify["collections"].connect((obj, pspec) => {
-                                       refresh_collections();
-                               });
-                               this._service.load_collections.begin(null, (obj, res) => {
-                                       try {
-                                               this._service.load_collections.end(res);
-                                               refresh_collections();
-                                       } catch (GLib.Error e) {
-                                               warning("couldn't load all secret collections: %s", 
e.message);
-                                       }
-                               });
-                               refresh_aliases();
-                       } catch (GLib.Error err) {
-                               GLib.warning("couldn't connect to secret service: %s", err.message);
-                       }
-                       notify_property("service");
-               });
-       }
-
-       public override void dispose() {
-               this._aliases.remove_all();
-               this._keyrings.remove_all();
-               base.dispose();
-       }
-
-       public void refresh_collections() {
-               var seen = new GLib.GenericSet<string>(GLib.str_hash, GLib.str_equal);
-               var keyrings = this._service.get_collections();
-
-               string object_path;
-               foreach (var keyring in keyrings) {
-                       object_path = keyring.get_object_path();
-
-                       /* Don't list the session keyring */
-                       if (this._aliases.lookup("session") == object_path)
-                               continue;
-
-                       var uri = "secret-service://%s".printf(object_path);
-                       seen.add(uri);
-                       if (this._keyrings.lookup(uri) == null) {
-                               this._keyrings.insert(uri, (Keyring)keyring);
-                               emit_added(keyring);
-                       }
-               }
-
-               /* Remove any that we didn't find */
-               var iter = GLib.HashTableIter<string, Keyring>(this._keyrings);
-               string uri;
-               while (iter.next(out uri, null)) {
-                       if (!seen.contains(uri)) {
-                               var keyring = this._keyrings.lookup(uri);
-                               iter.remove();
-                               emit_removed(keyring);
-                       }
-               }
-
-               if (!_loaded) {
-                       _loaded = true;
-                       notify_property("loaded");
-               }
-       }
-
-       public uint get_length() {
-               return this._keyrings.size();
-       }
-
-       public GLib.List<weak GLib.Object> get_objects() {
-               return get_keyrings();
-       }
-
-    public bool contains(GLib.Object object) {
-        var keyring = object as Gkr.Keyring;
-        if (keyring == null)
-            return false;
-
-        return this._keyrings.lookup(keyring.uri) == keyring;
+    public GLib.HashTable<string, string> aliases {
+        get { return this._aliases; }
+    }
+
+    private bool _loaded;
+    public bool loaded {
+        get { return this._loaded; }
+    }
+
+    public Secret.Service? service {
+        get { return this._service; }
+    }
+
+    private static Backend? _instance = null;
+    private Secret.Service _service;
+    private GenericArray<Gkr.Keyring> keyrings;
+    private GLib.HashTable<string, string> _aliases;
+    private ActionGroup _actions;
+
+    construct {
+        return_val_if_fail(_instance == null, null);
+        Backend._instance = this;
+
+        this._actions = BackendActions.instance(this);
+        this.keyrings = new GenericArray<Keyring>();
+        this._aliases = new GLib.HashTable<string, string>(GLib.str_hash, GLib.str_equal);
+
+        Secret.Service.open.begin(typeof(MyService), null,
+                                  Secret.ServiceFlags.OPEN_SESSION, null, (obj, res) => {
+            try {
+                this._service = Secret.Service.open.end(res);
+                this._service.notify["collections"].connect((obj, pspec) => {
+                    refresh_collections();
+                });
+                this._service.load_collections.begin(null, (obj, res) => {
+                    try {
+                        this._service.load_collections.end(res);
+                        refresh_collections();
+                    } catch (GLib.Error e) {
+                        warning("couldn't load all secret collections: %s", e.message);
+                    }
+                });
+                refresh_aliases();
+            } catch (GLib.Error err) {
+                GLib.warning("couldn't connect to secret service: %s", err.message);
+            }
+            notify_property("service");
+        });
+    }
+
+    public Seahorse.Place? lookup_place(string uri) {
+        for (uint i = 0; i < this.keyrings.length; i++) {
+            if (this.keyrings[i].uri == uri)
+                return this.keyrings[i];
+        }
+        return null;
+    }
+
+    public void refresh_collections() {
+        var seen = new GLib.GenericSet<string>(GLib.str_hash, GLib.str_equal);
+        var keyrings = this._service.get_collections();
+
+        string object_path;
+        foreach (var keyring in keyrings) {
+            object_path = keyring.get_object_path();
+
+            /* Don't list the session keyring */
+            if (this._aliases.lookup("session") == object_path)
+                continue;
+
+            var uri = "secret-service://%s".printf(object_path);
+            seen.add(uri);
+            if (lookup_place(uri) == null) {
+                this.keyrings.add((Keyring) keyring);
+                items_changed(this.keyrings.length - 1, 0, 1);
+            }
+        }
+
+        /* Remove any that we didn't find */
+        for (uint i = 0; i < this.keyrings.length; i++) {
+            if (!seen.contains(this.keyrings[i].uri)) {
+                this.keyrings.remove_index(i);
+                items_changed(i, 1, 0);
+                i--;
+            }
+        }
+
+        if (!_loaded) {
+            _loaded = true;
+            notify_property("loaded");
+        }
+    }
+
+    public GLib.Type get_item_type() {
+        return typeof(Gkr.Keyring);
     }
 
-       public Place? lookup_place(string uri) {
-               return this._keyrings.lookup(uri);
-       }
+    public uint get_n_items() {
+        return this.keyrings.length;
+    }
 
-       public static void initialize() {
-               return_if_fail(Backend._instance == null);
-               (new Backend()).register();
-               return_if_fail(Backend._instance != null);
-       }
+    public GLib.Object? get_item(uint position) {
+        if (position >= this.keyrings.length)
+            return null;
+        return this.keyrings[position];
+    }
 
-       public static Backend instance() {
-               return_val_if_fail(Backend._instance != null, null);
-               return Backend._instance;
-       }
+    public static void initialize() {
+        return_if_fail(Backend._instance == null);
+        (new Backend()).register();
+        return_if_fail(Backend._instance != null);
+    }
 
-       public GLib.List<unowned Keyring> get_keyrings() {
-               return this._keyrings.get_values();
-       }
+    public static Backend instance() {
+        return_val_if_fail(Backend._instance != null, null);
+        return Backend._instance;
+    }
 
-       private async void read_alias(string name) {
-               if (this._service == null)
-                       return;
+    private async void read_alias(string name) {
+        if (this._service == null)
+            return;
 
-               try {
-                   var object_path = this._service.read_alias_dbus_path_sync(name);
+        try {
+            var object_path = this._service.read_alias_dbus_path_sync(name);
             if (object_path != null) {
                 this._aliases[name] = object_path;
                 notify_property("aliases");
@@ -188,29 +178,30 @@ public class Backend: GLib.Object , Gcr.Collection, Seahorse.Backend {
         } catch (GLib.Error err) {
             warning("Couldn't read secret service alias %s: %s", name, err.message);
         }
-       }
-
-       private void refresh_aliases() {
-               read_alias.begin ("default");
-               read_alias.begin ("session");
-               read_alias.begin ("login");
-       }
-
-       public void refresh() {
-               refresh_aliases();
-               refresh_collections();
-       }
-       public bool has_alias(string alias,
-                             Keyring keyring) {
-               string object_path = keyring.get_object_path();
-               return this._aliases.lookup(alias) == object_path;
-       }
+    }
+
+    private void refresh_aliases() {
+        read_alias.begin ("default");
+        read_alias.begin ("session");
+        read_alias.begin ("login");
+    }
+
+    public void refresh() {
+        refresh_aliases();
+        refresh_collections();
+    }
+
+    public bool has_alias(string alias,
+                          Keyring keyring) {
+        string object_path = keyring.get_object_path();
+        return this._aliases.lookup(alias) == object_path;
+    }
 }
 
 public class BackendActions : Seahorse.ActionGroup {
-       public Backend backend { construct; get; }
-       private static WeakRef _instance;
-       private bool _initialized;
+    public Backend backend { construct; get; }
+    private static WeakRef _instance;
+    private bool _initialized;
 
     private const ActionEntry[] BACKEND_ACTIONS = {
         { "keyring-new",      on_new_keyring },
@@ -218,23 +209,23 @@ public class BackendActions : Seahorse.ActionGroup {
         { "copy-secret",      on_copy_secret },
     };
 
-       construct {
-               this._initialized = false;
+    construct {
+        this._initialized = false;
 
-               this.backend.notify.connect_after((pspec) => {
-                       if (pspec.name == "service")
-                               return;
-                       if (this._initialized)
-                               return;
-                       if (this.backend.service == null)
-                                       return;
+        this.backend.notify.connect_after((pspec) => {
+            if (pspec.name == "service")
+                return;
+            if (this._initialized)
+                return;
+            if (this.backend.service == null)
+                    return;
 
-                       this._initialized = true;
+            this._initialized = true;
             add_action_entries(BACKEND_ACTIONS, this);
-               });
+        });
 
-               this.backend.notify_property("service");
-       }
+        this.backend.notify_property("service");
+    }
 
     private BackendActions(Backend backend) {
         GLib.Object(
@@ -245,20 +236,22 @@ public class BackendActions : Seahorse.ActionGroup {
 
     private void on_new_keyring(SimpleAction action, Variant? param) {
         var dialog = new KeyringAdd(this.catalog);
-
-        int response = dialog.run();
-        if (response == Gtk.ResponseType.ACCEPT)
-            this.catalog.activate_action("focus-place", "secret-service");
-        dialog.destroy();
+        dialog.response.connect((response) => {
+            if (response == Gtk.ResponseType.ACCEPT)
+                this.catalog.activate_action("focus-place", "secret-service");
+            dialog.destroy();
+        });
+        dialog.show();
     }
 
     private void on_new_item(SimpleAction action, Variant? param) {
         var dialog = new ItemAdd(this.catalog);
-
-        int response = dialog.run();
-        if (response == Gtk.ResponseType.ACCEPT)
-            this.catalog.activate_action("focus-place", "secret-service");
-        dialog.destroy();
+        dialog.response.connect((response) => {
+            if (response == Gtk.ResponseType.ACCEPT)
+                this.catalog.activate_action("focus-place", "secret-service");
+            dialog.destroy();
+        });
+        dialog.show();
     }
 
     private void on_copy_secret(SimpleAction action, Variant? param) {
@@ -271,7 +264,7 @@ public class BackendActions : Seahorse.ActionGroup {
         var selected_item = selected.data as Gkr.Item;
         return_if_fail (selected_item != null);
 
-        var clipboard = Gtk.Clipboard.get_default(this.catalog.get_display());
+        var clipboard = this.catalog.get_clipboard();
         selected_item.copy_secret_to_clipboard.begin(clipboard, (obj, res) => {
             try {
                 selected_item.copy_secret_to_clipboard.end(res);
@@ -292,15 +285,14 @@ public class BackendActions : Seahorse.ActionGroup {
         ((SimpleAction) lookup_action("copy-secret")).set_enabled(can_copy_secret);
     }
 
-       public static ActionGroup instance(Backend backend) {
-               BackendActions? actions = (BackendActions?)_instance.get();
-               if (actions != null)
-                       return actions;
-               actions = new BackendActions(backend);
-               _instance.set(actions);
-               return actions;
-       }
+    public static ActionGroup instance(Backend backend) {
+        BackendActions? actions = (BackendActions?)_instance.get();
+        if (actions != null)
+            return actions;
+        actions = new BackendActions(backend);
+        _instance.set(actions);
+        return actions;
+    }
 }
 
 }
-}
diff --git a/gkr/gkr-dialogs.vala b/gkr/gkr-dialogs.vala
index d70f41b0..d202846b 100644
--- a/gkr/gkr-dialogs.vala
+++ b/gkr/gkr-dialogs.vala
@@ -17,67 +17,56 @@
  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
-namespace Seahorse {
-namespace Gkr {
+namespace Seahorse.Gkr.Dialog {
 
-public class Dialog {
+private void update_wait_cursor(Gtk.Widget widget) {
+    GLib.Cancellable? cancellable = widget.get_data("gkr-request");
 
-       private static void update_wait_cursor(Gtk.Widget widget) {
-               GLib.Cancellable? cancellable = widget.get_data("gkr-request");
+    // No request active?
+    if (cancellable == null) {
+        widget.set_cursor(null);
+        return;
+    }
 
-               /* No request active? */
-               if (cancellable == null) {
-                       widget.get_window().set_cursor(null);
-                       return;
-               }
+    // Get the wait cursor. Create a new one and cache it on the widget
+    Gdk.Cursor? cursor = widget.get_data("wait-cursor");
+    if (cursor == null) {
+        cursor = new Gdk.Cursor.from_name ("wait", null);
+        widget.set_data("wait-cursor", cursor);
+    }
 
-               /*
-                * Get the wait cursor. Create a new one and cache it on the widget
-                * if first time.
-                */
-               Gdk.Cursor? cursor = widget.get_data("wait-cursor");
-               if (cursor == null) {
-                        cursor = new Gdk.Cursor.from_name (Gdk.Display.get_default(), "wait");
-                       widget.set_data("wait-cursor", cursor);
-               }
-
-               /* Indicate that we're loading stuff */
-               widget.get_window().set_cursor(cursor);
-       }
-
-       public static GLib.Cancellable begin_request(Gtk.Widget dialog) {
-               /* Cancel any old operation going on */
-               complete_request (dialog, true);
+    // Indicate that we're loading stuff
+    widget.set_cursor(cursor);
+}
 
-               /*
-                * Start the operation and tie it to the widget so that it will get
-                * cancelled if the widget is destroyed before the operation is complete
-                */
-               var cancellable = new GLib.Cancellable ();
-               dialog.set_data_full ("gkr-request", cancellable.ref(), (data) => {
-                       GLib.Cancellable? c = (GLib.Cancellable?)data;
-                       c.cancel();
-                       c.unref();
-               });
+public GLib.Cancellable begin_request(Gtk.Widget dialog) {
+    // Cancel any old operation going on
+    complete_request (dialog, true);
 
-               if (dialog.get_realized())
-                       update_wait_cursor (dialog);
-               else
-                       dialog.realize.connect(update_wait_cursor);
+    // Start the operation and tie it to the widget so that it will get
+    // cancelled if the widget is destroyed before the operation is complete
+    var cancellable = new GLib.Cancellable ();
+    dialog.set_data_full ("gkr-request", cancellable.ref(), (data) => {
+        GLib.Cancellable? c = (GLib.Cancellable?)data;
+        c.cancel();
+        c.unref();
+    });
 
-               dialog.set_sensitive(false);
-               return cancellable;
-       }
+    if (dialog.get_realized())
+        update_wait_cursor (dialog);
+    else
+        dialog.realize.connect(update_wait_cursor);
 
-       public static void complete_request(Gtk.Widget dialog, bool cancel) {
-               GLib.Cancellable? cancellable = dialog.steal_data ("gkr-request");
-               if (cancellable != null && cancel)
-                       cancellable.cancel();
-               if (dialog.get_realized())
-                       update_wait_cursor (dialog);
-               dialog.set_sensitive(true);
-       }
+    dialog.set_sensitive(false);
+    return cancellable;
 }
 
+public void complete_request(Gtk.Widget dialog, bool cancel) {
+    GLib.Cancellable? cancellable = dialog.steal_data ("gkr-request");
+    if (cancellable != null && cancel)
+        cancellable.cancel();
+    if (dialog.get_realized())
+        update_wait_cursor (dialog);
+    dialog.set_sensitive(true);
 }
 }
diff --git a/gkr/gkr-item-add.vala b/gkr/gkr-item-add.vala
index aad437c9..60eaf7d3 100644
--- a/gkr/gkr-item-add.vala
+++ b/gkr/gkr-item-add.vala
@@ -20,12 +20,11 @@
 [GtkTemplate (ui = "/org/gnome/Seahorse/seahorse-gkr-add-item.ui")]
 public class Seahorse.Gkr.ItemAdd : Gtk.Dialog {
     [GtkChild]
-    private unowned Gtk.ComboBox item_keyring_combo;
+    private unowned Adw.ComboRow keyring_row;
     [GtkChild]
-    private unowned Gtk.Container password_area;
-    private Gtk.Entry password_entry;
+    private unowned Gtk.PasswordEntry password_entry;
     [GtkChild]
-    private unowned Gtk.Entry item_entry;
+    private unowned Adw.EntryRow description_row;
     [GtkChild]
     private unowned Gtk.LevelBar password_strength_bar;
     [GtkChild]
@@ -34,30 +33,20 @@ public class Seahorse.Gkr.ItemAdd : Gtk.Dialog {
     private PasswordQuality.Settings pwquality = new PasswordQuality.Settings();
 
     construct {
-        // Load up a list of all the keyrings, and select the default
-        var store = new Gtk.ListStore(2, typeof(string), typeof(Secret.Collection));
-        this.item_keyring_combo.set_model(store);
-
-        var cell = new Gtk.CellRendererText();
-        this.item_keyring_combo.pack_start(cell, true);
-        this.item_keyring_combo.add_attribute(cell, "text", 0);
-
-        foreach (var keyring in Backend.instance().get_keyrings()) {
-            Gtk.TreeIter iter;
-            store.append(out iter);
-            store.set(iter, 0, keyring.label,
-                            1, keyring);
+        // Set the list of all keyrings as model, and select the default
+        var model = Gkr.Backend.instance();
+        this.keyring_row.set_model(model);
+
+        for (uint i = 0; i < model.get_n_items(); i++) {
+            var keyring = (Gkr.Keyring) model.get_item(i);
             if (keyring.is_default)
-                this.item_keyring_combo.set_active_iter(iter);
+                this.keyring_row.set_selected(i);
         }
 
+        this.response.connect(on_response);
         set_response_sensitive(Gtk.ResponseType.ACCEPT, false);
 
-        this.password_entry = new PasswordEntry();
-        this.password_entry.visibility = false;
         this.password_entry.changed.connect(on_password_entry_changed);
-        this.password_area.add(this.password_entry);
-        this.password_entry.show();
     }
 
     public ItemAdd(Gtk.Window? parent) {
@@ -68,8 +57,8 @@ public class Seahorse.Gkr.ItemAdd : Gtk.Dialog {
     }
 
     [GtkCallback]
-    private void on_add_item_entry_changed (Gtk.Editable entry) {
-        set_response_sensitive(Gtk.ResponseType.ACCEPT, this.item_entry.text != "");
+    private void on_description_row_changed (Gtk.Editable editable) {
+        set_response_sensitive(Gtk.ResponseType.ACCEPT, editable.text != "");
     }
 
     private void on_password_entry_changed (Gtk.Editable entry) {
@@ -87,27 +76,18 @@ public class Seahorse.Gkr.ItemAdd : Gtk.Dialog {
         this.password_strength_bar.value = ((score / 25) + 1).clamp(1, 5);
     }
 
-    public override void response(int resp) {
+    private void on_response(int resp) {
         if (resp != Gtk.ResponseType.ACCEPT)
             return;
 
-        Gtk.TreeIter iter;
-        if (!this.item_keyring_combo.get_active_iter(out iter))
-            return;
-
-        Secret.Collection collection;
-        this.item_keyring_combo.model.get(iter, 1, out collection, -1);
-
-        var keyring = (Keyring) collection;
+        var keyring = (Keyring) this.keyring_row.selected_item;
         var cancellable = new Cancellable();
         var interaction = new Interaction(this);
-        var item_buffer = this.item_entry.buffer;
-        var secret_buffer = this.password_entry.buffer;
 
         keyring.unlock.begin(interaction, cancellable, (obj, res) => {
             try {
                 if (keyring.unlock.end(res)) {
-                    create_secret(item_buffer, secret_buffer, collection);
+                    create_secret(this.description_row.text, this.password_entry.text, keyring);
                 }
             } catch (Error e) {
                 Util.show_error(this, _("Couldn’t unlock"), e.message);
@@ -115,10 +95,10 @@ public class Seahorse.Gkr.ItemAdd : Gtk.Dialog {
         });
     }
 
-    private void create_secret(Gtk.EntryBuffer item_buffer,
-                               Gtk.EntryBuffer secret_buffer,
+    private void create_secret(string item,
+                               string secret,
                                Secret.Collection collection) {
-        var secret = new Secret.Value(secret_buffer.text, -1, "text/plain");
+        var secret_val = new Secret.Value(secret, -1, "text/plain");
         var cancellable = Dialog.begin_request(this);
         var attributes = new HashTable<string, string>(GLib.str_hash, GLib.str_equal);
 
@@ -126,7 +106,7 @@ public class Seahorse.Gkr.ItemAdd : Gtk.Dialog {
         var schema = new Secret.Schema("org.gnome.keyring.Note", Secret.SchemaFlags.NONE);
 
         Secret.Item.create.begin(collection, schema, attributes,
-                                 item_buffer.text, secret, Secret.ItemCreateFlags.NONE,
+                                 item, secret_val, Secret.ItemCreateFlags.NONE,
                                  cancellable, (obj, res) => {
             try {
                 /* Clear the operation without cancelling it since it is complete */
diff --git a/gkr/gkr-item-properties.vala b/gkr/gkr-item-properties.vala
index 08578270..85b83c59 100644
--- a/gkr/gkr-item-properties.vala
+++ b/gkr/gkr-item-properties.vala
@@ -23,27 +23,29 @@ public class Seahorse.Gkr.ItemProperties : Gtk.Dialog {
     public Item item { construct; get; }
 
     [GtkChild]
-    private unowned Gtk.Entry description_field;
+    private unowned Adw.WindowTitle window_title;
+    [GtkChild]
+    private unowned Adw.EntryRow description_field;
     private bool description_has_changed;
     [GtkChild]
     private unowned Gtk.Label use_field;
     [GtkChild]
     private unowned Gtk.Label type_field;
     [GtkChild]
-    private unowned Hdy.PreferencesGroup details_group;
+    private unowned Adw.PreferencesGroup details_group;
     [GtkChild]
     private unowned Gtk.ListBox details_box;
     [GtkChild]
-    private unowned Hdy.ActionRow server_row;
+    private unowned Adw.ActionRow server_row;
     [GtkChild]
     private unowned Gtk.Label server_field;
     [GtkChild]
-    private unowned Hdy.ActionRow login_row;
+    private unowned Adw.ActionRow login_row;
     [GtkChild]
     private unowned Gtk.Label login_field;
     [GtkChild]
-    private unowned Gtk.Box password_box_area;
-    private PasswordEntry password_entry;
+    private unowned Gtk.PasswordEntry password_entry;
+    private string original_password = "";
 
     construct {
         // Setup the label properly
@@ -54,8 +56,7 @@ public class Seahorse.Gkr.ItemProperties : Gtk.Dialog {
         });
 
         /* Window title */
-        var headerbar = (Gtk.HeaderBar) this.get_header_bar();
-        this.item.bind_property("label", headerbar, "subtitle",
+        this.item.bind_property("label", this.window_title, "subtitle",
                                 GLib.BindingFlags.SYNC_CREATE);
 
         /* Update as appropriate */
@@ -77,9 +78,7 @@ public class Seahorse.Gkr.ItemProperties : Gtk.Dialog {
             }
         });
 
-        // Create the password entry
-        this.password_entry = new PasswordEntry();
-        this.password_box_area.pack_start(this.password_entry, true, true, 0);
+        // fill the password entry
         fetch_password();
 
         // Sensitivity of the password entry
@@ -97,7 +96,8 @@ public class Seahorse.Gkr.ItemProperties : Gtk.Dialog {
 
     public override void response(int response) {
         // In case of changes: ask for confirmation
-        if (!this.password_entry.has_changed && !this.description_has_changed) {
+        if (this.password_entry.text == this.original_password
+            && !this.description_has_changed) {
             destroy();
             return;
         }
@@ -106,7 +106,7 @@ public class Seahorse.Gkr.ItemProperties : Gtk.Dialog {
                                            Gtk.ButtonsType.OK_CANCEL, _("Save changes for this item?"));
         dialog.response.connect((resp) => {
             if (resp == Gtk.ResponseType.OK) {
-                if (this.password_entry.has_changed)
+                if (this.password_entry.text != this.original_password)
                     save_password.begin();
                 if (this.description_has_changed)
                     save_description.begin();
@@ -114,7 +114,7 @@ public class Seahorse.Gkr.ItemProperties : Gtk.Dialog {
 
             dialog.destroy();
         });
-        dialog.run();
+        dialog.show();
     }
 
     private void update_use() {
@@ -189,7 +189,7 @@ public class Seahorse.Gkr.ItemProperties : Gtk.Dialog {
 
             any_details = true;
 
-            var row = new Hdy.ActionRow();
+            var row = new Adw.ActionRow();
             row.title = key;
             row.can_focus = false;
 
@@ -199,9 +199,8 @@ public class Seahorse.Gkr.ItemProperties : Gtk.Dialog {
             label.wrap = true;
             label.wrap_mode = Pango.WrapMode.WORD_CHAR;
             label.max_width_chars = 32;
-            row.add(label);
+            row.add_suffix(label);
 
-            row.show_all();
             this.details_box.insert(row, -1);
         }
 
@@ -222,14 +221,9 @@ public class Seahorse.Gkr.ItemProperties : Gtk.Dialog {
     private void fetch_password() {
         var secret = this.item.get_secret();
         if (secret != null) {
-            unowned string? password = secret.get_text();
-            if (password != null) {
-                this.password_entry.set_initial_password(password);
-                return;
-            }
+            this.original_password = secret.get_text() ?? "";
+            this.password_entry.text = this.original_password;
         }
-
-        this.password_entry.set_initial_password("");
     }
 
     private async void save_description() {
@@ -244,31 +238,33 @@ public class Seahorse.Gkr.ItemProperties : Gtk.Dialog {
 
     [GtkCallback]
     private void on_copy_button_clicked() {
-        var clipboard = Gtk.Clipboard.get_default(this.get_display());
-        this.item.copy_secret_to_clipboard.begin(clipboard);
+        this.item.copy_secret_to_clipboard.begin(get_clipboard());
     }
 
     [GtkCallback]
     private void on_delete_button_clicked() {
         var deleter = this.item.create_deleter();
-        var ret = deleter.prompt(this);
-
-        if (!ret)
-            return;
-
-        deleter.delete.begin(null, (obj, res) => {
-            try {
-                deleter.delete.end(res);
-                this.destroy();
-            } catch (GLib.Error e) {
-                var dialog = new Gtk.MessageDialog(this,
-                    Gtk.DialogFlags.MODAL,
-                    Gtk.MessageType.ERROR,
-                    Gtk.ButtonsType.OK,
-                    _("Error deleting the password."));
-                dialog.run();
-                dialog.destroy();
+        var prompt = deleter.create_confirm(this);
+        //XXX
+        ((Gtk.Dialog) prompt).response.connect((response) => {
+            if (response != Gtk.ResponseType.ACCEPT) {
+                prompt.destroy();
+                return;
             }
+            prompt.destroy();
+
+            deleter.delete.begin(null, (obj, res) => {
+                try {
+                    deleter.delete.end(res);
+                    this.destroy();
+                } catch (GLib.Error e) {
+                    var dialog = new Gtk.MessageDialog(this,
+                        Gtk.DialogFlags.MODAL,
+                        Gtk.MessageType.ERROR,
+                        Gtk.ButtonsType.OK,
+                        _("Error deleting the password."));
+                }
+            });
         });
     }
 }
diff --git a/gkr/gkr-item.vala b/gkr/gkr-item.vala
index def1c5b2..3126e63c 100644
--- a/gkr/gkr-item.vala
+++ b/gkr/gkr-item.vala
@@ -69,7 +69,9 @@ public class Item : Secret.Item, Deletable, Viewable {
     public GLib.Icon icon {
         owned get {
             ensure_item_info();
-            return this._info.icon ?? new GLib.ThemedIcon (ICON_PASSWORD);
+            // XXX
+            // return this._info.icon ?? new GLib.ThemedIcon (ICON_PASSWORD);
+            return this._info.icon ?? new GLib.ThemedIcon ("dialog-password-symbolic");
         }
     }
 
@@ -246,7 +248,7 @@ public class Item : Secret.Item, Deletable, Viewable {
         return true;
     }
 
-    public async void copy_secret_to_clipboard(Gtk.Clipboard clipboard) throws GLib.Error {
+    public async void copy_secret_to_clipboard(Gdk.Clipboard clipboard) throws GLib.Error {
         if (this._item_secret == null)
             yield load_item_secret();
 
@@ -259,7 +261,7 @@ public class Item : Secret.Item, Deletable, Viewable {
         if (password == null)
             return;
 
-        clipboard.set_text(password, -1);
+        clipboard.set_text(password);
         debug("Succesfully copied secret to clipboard");
     }
 }
@@ -334,7 +336,7 @@ private unowned string map_item_type_to_specific(string? item_type,
 class ItemDeleter : Deleter {
     private GLib.List<Item> _items;
 
-    public override Gtk.Dialog create_confirm(Gtk.Window? parent) {
+    public override Gtk.Window create_confirm(Gtk.Window? parent) {
         var num = this._items.length();
         if (num == 1) {
             var label = ((Secret.Item)_items.data).label;
diff --git a/gkr/gkr-keyring-add.vala b/gkr/gkr-keyring-add.vala
index 1dd94e86..df9d2b75 100644
--- a/gkr/gkr-keyring-add.vala
+++ b/gkr/gkr-keyring-add.vala
@@ -19,8 +19,9 @@
 
 [GtkTemplate (ui = "/org/gnome/Seahorse/seahorse-gkr-add-keyring.ui")]
 public class Seahorse.Gkr.KeyringAdd : Gtk.Dialog {
+
     [GtkChild]
-    private unowned Gtk.Entry name_entry;
+    private unowned Adw.EntryRow name_row;
 
     construct {
         set_response_sensitive(Gtk.ResponseType.ACCEPT, false);
@@ -41,7 +42,7 @@ public class Seahorse.Gkr.KeyringAdd : Gtk.Dialog {
 
         var cancellable = Dialog.begin_request(this);
         var service = Backend.instance().service;
-        Secret.Collection.create.begin(service, this.name_entry.text, null, 0,
+        Secret.Collection.create.begin(service, this.name_row.text, null, 0,
                                        cancellable, (obj, res) => {
             /* Clear the operation without cancelling it since it is complete */
             Dialog.complete_request(this, false);
@@ -57,7 +58,7 @@ public class Seahorse.Gkr.KeyringAdd : Gtk.Dialog {
     }
 
     [GtkCallback]
-    private void on_name_entry_changed(Gtk.Editable editable) {
-        set_response_sensitive(Gtk.ResponseType.ACCEPT, this.name_entry.text != "");
+    private void on_name_row_changed(Gtk.Editable editable) {
+        set_response_sensitive(Gtk.ResponseType.ACCEPT, this.name_row.text != "");
     }
 }
diff --git a/gkr/gkr-keyring-properties.vala b/gkr/gkr-keyring-properties.vala
index a7f97e5d..e2c73250 100644
--- a/gkr/gkr-keyring-properties.vala
+++ b/gkr/gkr-keyring-properties.vala
@@ -27,8 +27,6 @@ public class Seahorse.Gkr.KeyringProperties : Gtk.Dialog {
     [GtkChild]
     private unowned Gtk.Label created_label;
     [GtkChild]
-    private unowned Gtk.Image keyring_image;
-    [GtkChild]
     private unowned Gtk.Button set_default_button;
     [GtkChild]
     private unowned Gtk.LockButton lock_button;
@@ -38,7 +36,6 @@ public class Seahorse.Gkr.KeyringProperties : Gtk.Dialog {
 
         this.keyring.bind_property("label", this.header, "subtitle", GLib.BindingFlags.SYNC_CREATE);
         this.keyring.bind_property("label", this.name_label, "label", GLib.BindingFlags.SYNC_CREATE);
-        this.keyring.bind_property("icon", this.keyring_image, "gicon", GLib.BindingFlags.SYNC_CREATE);
 
         // The date field
         set_created(this.keyring.created);
diff --git a/gkr/gkr-keyring.vala b/gkr/gkr-keyring.vala
index a7c8113b..69c70fc0 100644
--- a/gkr/gkr-keyring.vala
+++ b/gkr/gkr-keyring.vala
@@ -18,10 +18,9 @@
  * License along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
-namespace Seahorse {
-namespace Gkr {
+namespace Seahorse.Gkr {
 
-public class Keyring : Secret.Collection, Gcr.Collection, Place, Deletable, Lockable, Viewable {
+public class Keyring : Secret.Collection, GLib.ListModel, Place, Deletable, Lockable, Viewable {
 
     private const ActionEntry[] KEYRING_ACTIONS = {
         { "set-default",     on_action_set_default },
@@ -43,10 +42,6 @@ public class Keyring : Secret.Collection, Gcr.Collection, Place, Deletable, Lock
                }
        }
 
-       public GLib.Icon icon {
-               owned get { return new GLib.ThemedIcon("folder"); }
-       }
-
     public Place.Category category {
         get { return Place.Category.PASSWORDS; }
     }
@@ -65,10 +60,6 @@ public class Keyring : Secret.Collection, Gcr.Collection, Place, Deletable, Lock
         owned get { return this._menu_model; }
     }
 
-    public bool show_if_empty {
-        get { return true; }
-    }
-
        public bool is_default {
                get { return Backend.instance().has_alias ("default", this); }
        }
@@ -85,10 +76,9 @@ public class Keyring : Secret.Collection, Gcr.Collection, Place, Deletable, Lock
                get { return true; }
        }
 
-       private GLib.HashTable<string, Item> _items;
+    private GenericArray<Gkr.Item> _items = new GenericArray<Gkr.Item>();
 
        construct {
-               this._items = new GLib.HashTable<string, Item>(GLib.str_hash, GLib.str_equal);
                this.notify.connect((pspec) => {
                        if (pspec.name == "items" || pspec.name == "locked")
                                refresh_collection();
@@ -102,87 +92,100 @@ public class Keyring : Secret.Collection, Gcr.Collection, Place, Deletable, Lock
         this._menu_model = create_menu_model();
        }
 
-       public uint get_length() {
-               return _items.size();
-       }
+    public GLib.Type get_item_type() {
+        return typeof(Gkr.Item);
+    }
 
-       public GLib.List<weak GLib.Object> get_objects() {
-               return _items.get_values();
-       }
+    public uint get_n_items() {
+        return this._items.length;
+    }
 
-       public bool contains(GLib.Object obj) {
-               if (obj is Item)
-                       return _items.lookup(((Item)obj).get_object_path()) != null;
-               return false;
-       }
+    public GLib.Object? get_item(uint index) {
+        if (index >= this._items.length)
+            return null;
+        return this._items[index];
+    }
+
+    private Gkr.Item? lookup_by_path(string object_path) {
+        for (uint i = 0; i < this._items.length; i++) {
+            if (object_path == this._items[i].get_object_path())
+                return this._items[i];
+        }
+        return null;
+    }
 
        public Gtk.Window? create_viewer(Gtk.Window? parent) {
                return new KeyringProperties(this, parent);
        }
 
-       public Deleter create_deleter() {
-               return new KeyringDeleter(this);
-       }
-
-       public async bool lock(GLib.TlsInteraction? interaction,
-                       GLib.Cancellable? cancellable) throws GLib.Error {
-               var objects = new GLib.List<GLib.DBusProxy>();
-               objects.prepend(this);
-
-               var service = get_service();
-               GLib.List<GLib.DBusProxy> locked;
-               yield service.lock(objects, cancellable, out locked);
-               refresh_collection ();
-               return locked.length() > 0;
-       }
-
-       public async bool unlock(GLib.TlsInteraction? interaction,
-                                GLib.Cancellable? cancellable) throws GLib.Error {
-               var objects = new GLib.List<GLib.DBusProxy>();
-               objects.prepend(this);
-
-               var service = get_service();
-               GLib.List<GLib.DBusProxy> unlocked;
-               yield service.unlock(objects, cancellable, out unlocked);
-               refresh_collection ();
-               return unlocked.length() > 0;
-       }
-
-       public async bool load(GLib.Cancellable? cancellable) throws GLib.Error {
-               refresh_collection();
-               return true;
-       }
-
-       private void refresh_collection() {
-               var seen = new GLib.GenericSet<string>(GLib.str_hash, GLib.str_equal);
+    public Deleter create_deleter() {
+        return new KeyringDeleter(this);
+    }
 
-               GLib.List<Secret.Item> items = null;
-               if (!get_locked())
-                       items = get_items();
+    public async bool lock(GLib.TlsInteraction? interaction,
+            GLib.Cancellable? cancellable) throws GLib.Error {
+        var objects = new GLib.List<GLib.DBusProxy>();
+        objects.prepend(this);
+
+        var service = get_service();
+        GLib.List<GLib.DBusProxy> locked;
+        yield service.lock(objects, cancellable, out locked);
+        refresh_collection ();
+        notify_property("lockable");
+        notify_property("unlockable");
+        return locked.length() > 0;
+    }
 
-               foreach (var item in items) {
-                       var object_path = item.get_object_path();
-                       seen.add(object_path);
+    public async bool unlock(GLib.TlsInteraction? interaction,
+                             GLib.Cancellable? cancellable) throws GLib.Error {
+        var objects = new GLib.List<GLib.DBusProxy>();
+        objects.prepend(this);
+
+        var service = get_service();
+        GLib.List<GLib.DBusProxy> unlocked;
+        yield service.unlock(objects, cancellable, out unlocked);
+        refresh_collection ();
+        notify_property("lockable");
+        notify_property("unlockable");
+        return unlocked.length() > 0;
+    }
 
-                       if (_items.lookup(object_path) == null) {
-                               item.set("place", this);
-                               _items.insert(object_path, (Item)item);
-                               emit_added(item);
-                       }
-               }
+    public async bool load(GLib.Cancellable? cancellable) throws GLib.Error {
+        refresh_collection();
+        return true;
+    }
 
-               /* Remove any that we didn't find */
-               var iter = GLib.HashTableIter<string, Item>(_items);
-               string object_path;
-               while (iter.next (out object_path, null)) {
-                       if (!seen.contains(object_path)) {
-                               var item = _items.lookup(object_path);
-                               item.set("place", null);
-                               iter.remove();
-                               emit_removed (item);
-                       }
-               }
-       }
+    private void refresh_collection() {
+        var seen = new GLib.GenericSet<string>(GLib.str_hash, GLib.str_equal);
+
+        GLib.List<Secret.Item> items = null;
+        if (!get_locked())
+            items = get_items();
+
+        // NOTE: this is not _items
+        foreach (var item in items) {
+            unowned var object_path = item.get_object_path();
+            seen.add(object_path);
+
+            if (lookup_by_path(object_path) == null) {
+                item.set("place", this);
+                this._items.add((Item) item);
+                items_changed(this._items.length - 1, 0, 1);
+            }
+        }
+
+        /* Remove any that we didn't find */
+        for (uint i = 0; i < this._items.length; i++) {
+            unowned var object_path = this._items[i].get_object_path();
+            if (!seen.contains(object_path)) {
+                var item = lookup_by_path(object_path);
+                item.set("place", null);
+                this._items.remove(item);
+                items_changed(i, 1, 0);
+                i--;
+            }
+        }
+    }
 
     public void on_action_set_default(SimpleAction action, Variant? param) {
         set_as_default();
@@ -190,16 +193,16 @@ public class Keyring : Secret.Collection, Gcr.Collection, Place, Deletable, Lock
 
     public void set_as_default() {
         var parent = null;
-               var service = this.service;
-
-               service.set_alias.begin("default", this, null, (obj, res) => {
-                       try {
-                               service.set_alias.end(res);
-                               Backend.instance().refresh();
-                       } catch (GLib.Error err) {
-                               Util.show_error(parent, _("Couldn’t set default keyring"), err.message);
-                       }
-               });
+        var service = this.service;
+
+        service.set_alias.begin("default", this, null, (obj, res) => {
+            try {
+                service.set_alias.end(res);
+                Backend.instance().refresh();
+            } catch (GLib.Error err) {
+                Util.show_error(parent, _("Couldn’t set default keyring"), err.message);
+            }
+        });
     }
 
     public void on_action_change_password(SimpleAction action, Variant? param) {
@@ -208,30 +211,30 @@ public class Keyring : Secret.Collection, Gcr.Collection, Place, Deletable, Lock
 
     public void change_password() {
         var parent = null;
-               var service = this.service;
-               service.get_connection().call.begin(service.get_name(),
-                                                   service.get_object_path(),
-                                                   
"org.gnome.keyring.InternalUnsupportedGuiltRiddenInterface",
-                                                   "ChangeWithPrompt",
-                                                   new GLib.Variant("(o)", this.get_object_path()),
-                                                   new GLib.VariantType("(o)"),
-                                                   GLib.DBusCallFlags.NONE, -1, null, (obj, res) => {
-                       try {
-                               var retval = service.get_connection().call.end(res);
-                               string prompt_path;
-                               retval.get("(o)", out prompt_path);
-                               service.prompt_at_dbus_path.begin(prompt_path.dup(), null, null, (obj, res) 
=> {
-                                       try {
-                                               service.prompt_at_dbus_path.end(res);
-                                       } catch (GLib.Error err) {
-                                               Util.show_error(parent, _("Couldn’t change keyring 
password"), err.message);
-                                       }
-                                       Backend.instance().refresh();
-                               });
-                       } catch (GLib.Error err) {
-                               Util.show_error(parent, _("Couldn’t change keyring password"), err.message);
-                       }
-               });
+        var service = this.service;
+        service.get_connection().call.begin(service.get_name(),
+                                            service.get_object_path(),
+                                            "org.gnome.keyring.InternalUnsupportedGuiltRiddenInterface",
+                                            "ChangeWithPrompt",
+                                            new GLib.Variant("(o)", this.get_object_path()),
+                                            new GLib.VariantType("(o)"),
+                                            GLib.DBusCallFlags.NONE, -1, null, (obj, res) => {
+            try {
+                var retval = service.get_connection().call.end(res);
+                string prompt_path;
+                retval.get("(o)", out prompt_path);
+                service.prompt_at_dbus_path.begin(prompt_path.dup(), null, null, (obj, res) => {
+                    try {
+                        service.prompt_at_dbus_path.end(res);
+                    } catch (GLib.Error err) {
+                        Util.show_error(parent, _("Couldn’t change keyring password"), err.message);
+                    }
+                    Backend.instance().refresh();
+                });
+            } catch (GLib.Error err) {
+                Util.show_error(parent, _("Couldn’t change keyring password"), err.message);
+            }
+        });
     }
 
     private SimpleActionGroup create_actions() {
@@ -255,45 +258,44 @@ public class Keyring : Secret.Collection, Gcr.Collection, Place, Deletable, Lock
 }
 
 class KeyringDeleter : Deleter {
-       private Keyring? _keyring;
-       private GLib.List<GLib.Object> _objects;
+    private Keyring? _keyring;
+    private GLib.List<GLib.Object> _objects;
 
-       public override Gtk.Dialog create_confirm(Gtk.Window? parent) {
-               var dialog = new DeleteDialog(parent,
-                                             _("Are you sure you want to delete the password keyring “%s”?"),
-                                             this._keyring.label);
+    public override Gtk.Window create_confirm(Gtk.Window? parent) {
+        var dialog = new DeleteDialog(parent,
+                                      _("Are you sure you want to delete the password keyring “%s”?"),
+                                      this._keyring.label);
 
-               dialog.check_label = _("I understand that all items will be permanently deleted.");
-               dialog.check_require = true;
+        dialog.check_label = _("I understand that all items will be permanently deleted.");
+        dialog.check_require = true;
 
-               return dialog;
-       }
+        return dialog;
+    }
 
-       public KeyringDeleter(Keyring keyring) {
-               if (!add_object(keyring))
-                       GLib.assert_not_reached();
-       }
+    public KeyringDeleter(Keyring keyring) {
+        if (!add_object(keyring))
+            GLib.assert_not_reached();
+    }
 
-       public override unowned GLib.List<GLib.Object> get_objects() {
-               return this._objects;
-       }
+    public override unowned GLib.List<GLib.Object> get_objects() {
+        return this._objects;
+    }
 
-       public override bool add_object (GLib.Object obj) {
-               if (this._keyring != null)
-                       return false;
-               if (obj is Keyring) {
-                       this._keyring = (Keyring)obj;
-                       this._objects.append(obj);
-                       return true;
-               }
-               return false;
-       }
+    public override bool add_object (GLib.Object obj) {
+        if (this._keyring != null)
+            return false;
+        if (obj is Keyring) {
+            this._keyring = (Keyring)obj;
+            this._objects.append(obj);
+            return true;
+        }
+        return false;
+    }
 
-       public override async bool delete(GLib.Cancellable? cancellable) throws GLib.Error {
-               yield this._keyring.delete(cancellable);
-               return true;
-       }
+    public override async bool delete(GLib.Cancellable? cancellable) throws GLib.Error {
+        yield this._keyring.delete(cancellable);
+        return true;
+    }
 }
 
 }
-}
diff --git a/gkr/meson.build b/gkr/meson.build
index b530fb8d..479b7418 100644
--- a/gkr/meson.build
+++ b/gkr/meson.build
@@ -10,14 +10,12 @@ gkr_sources = files(
   'gkr-keyring-properties.vala',
   'gkr-keyring.vala',
   'gkr-module.vala',
-  'gkr-password-entry.vala',
 )
 
 gkr_dependencies = [
   glib_deps,
-  gtk,
-  gcr,
-  gcr_ui,
+  gtk4_dep,
+  gcr4_dep,
   libsecret,
   libpwquality,
   common_dep,
diff --git a/gkr/seahorse-gkr-add-item.ui b/gkr/seahorse-gkr-add-item.ui
index 93336f59..b2b223cd 100644
--- a/gkr/seahorse-gkr-add-item.ui
+++ b/gkr/seahorse-gkr-add-item.ui
@@ -1,110 +1,62 @@
 <?xml version="1.0"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
   <template class="SeahorseGkrItemAdd" parent="GtkDialog">
     <property name="title" translatable="yes">Add Password</property>
     <property name="modal">True</property>
-    <property name="window-position">center-on-parent</property>
-    <property name="border_width">5</property>
-    <child internal-child="vbox">
+
+    <child internal-child="content_area">
       <object class="GtkBox">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
-        <property name="border_width">5</property>
         <property name="spacing">6</property>
+        <property name="margin-top">12</property>
+        <property name="margin-bottom">12</property>
+        <property name="margin-start">12</property>
+        <property name="margin-end">12</property>
         <child>
-          <object class="GtkGrid">
-            <property name="visible">True</property>
-            <property name="column_spacing">12</property>
-            <property name="row_spacing">6</property>
+          <object class="AdwPreferencesGroup">
             <child>
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="xalign">0</property>
-                <property name="label" translatable="yes">_Keyring:</property>
+              <object class="AdwComboRow" id="keyring_row">
+                <property name="title" translatable="yes">_Keyring</property>
                 <property name="use_underline">True</property>
+                <property name="expression">
+                  <lookup type="SeahorseGkrKeyring" name="label"/>
+                </property>
               </object>
-              <packing>
-                <property name="top_attach">0</property>
-                <property name="left_attach">0</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkComboBox" id="item_keyring_combo">
-                <property name="visible">True</property>
-              </object>
-              <packing>
-                <property name="top_attach">0</property>
-                <property name="left_attach">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="xalign">0</property>
-                <property name="label" translatable="yes">_Description:</property>
+              <object class="AdwEntryRow" id="description_row">
+                <property name="title" translatable="yes">_Description</property>
                 <property name="use_underline">True</property>
+                <signal name="changed" handler="on_description_row_changed"/>
               </object>
-              <packing>
-                <property name="top_attach">1</property>
-                <property name="left_attach">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkEntry" id="item_entry">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="invisible_char">&#x2022;</property>
-                <property name="activates_default">True</property>
-                <property name="width_chars">16</property>
-                <signal name="changed" handler="on_add_item_entry_changed"/>
-              </object>
-              <packing>
-                <property name="top_attach">1</property>
-                <property name="left_attach">1</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="xalign">0</property>
-                <property name="label" translatable="yes">_Password:</property>
+              <object class="AdwActionRow">
+                <property name="title" translatable="yes">_Password</property>
                 <property name="use_underline">True</property>
-              </object>
-              <packing>
-                <property name="top_attach">2</property>
-                <property name="left_attach">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkBox" id="password_area">
-                <property name="visible">True</property>
-                <child>
-                  <placeholder/>
+                <child type="suffix">
+                  <object class="GtkPasswordEntry" id="password_entry">
+                    <property name="valign">center</property>
+                  </object>
                 </child>
               </object>
-              <packing>
-                <property name="top_attach">2</property>
-                <property name="left_attach">1</property>
-              </packing>
             </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
             <child>
               <object class="GtkImage" id="password_strength_icon">
-                <property name="visible">True</property>
                 <property name="halign">end</property>
                 <property name="icon-name">dialog-warning-symbolic</property>
                 <style>
                   <class name="dim-label"/>
                 </style>
               </object>
-              <packing>
-                <property name="top_attach">3</property>
-                <property name="left_attach">0</property>
-              </packing>
             </child>
             <child>
               <object class="GtkLevelBar" id="password_strength_bar">
-                <property name="visible">True</property>
                 <property name="valign">start</property>
                 <property name="min_value">0</property>
                 <property name="max_value">5</property>
@@ -117,26 +69,22 @@
                   <offset name="strength-high" value="5"/>
                 </offsets>
               </object>
-              <packing>
-                <property name="top_attach">3</property>
-                <property name="left_attach">1</property>
-              </packing>
             </child>
           </object>
         </child>
       </object>
     </child>
+
     <child type="action">
       <object class="GtkButton" id="cancel_button">
-        <property name="visible">True</property>
-        <property name="label" translatable="yes">Cancel</property>
+        <property name="label" translatable="yes">_Cancel</property>
+        <property name="use-underline">True</property>
       </object>
     </child>
     <child type="action">
       <object class="GtkButton" id="ok_button">
-        <property name="visible">True</property>
-        <property name="can-default">True</property>
-        <property name="label" translatable="yes">Add</property>
+        <property name="label" translatable="yes">_Add</property>
+        <property name="use-underline">True</property>
       </object>
     </child>
     <action-widgets>
diff --git a/gkr/seahorse-gkr-add-keyring.ui b/gkr/seahorse-gkr-add-keyring.ui
index 523ac62d..eb1b4015 100644
--- a/gkr/seahorse-gkr-add-keyring.ui
+++ b/gkr/seahorse-gkr-add-keyring.ui
@@ -1,92 +1,45 @@
 <?xml version="1.0"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
   <template class="SeahorseGkrKeyringAdd" parent="GtkDialog">
     <property name="modal">True</property>
     <property name="title" translatable="yes">Add password keyring</property>
-    <property name="window-position">center-on-parent</property>
-    <property name="border_width">5</property>
-    <child internal-child="vbox">
+    <child internal-child="content_area">
       <object class="GtkBox">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
-        <property name="spacing">2</property>
+        <property name="spacing">6</property>
+        <property name="margin-top">12</property>
+        <property name="margin-bottom">12</property>
+        <property name="margin-start">12</property>
+        <property name="margin-end">12</property>
         <child>
-          <object class="GtkBox">
-            <property name="visible">True</property>
-            <property name="orientation">vertical</property>
-            <property name="border_width">5</property>
-            <property name="spacing">6</property>
-            <child>
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="xalign">0</property>
-                <property name="label" translatable="yes">Please choose a name for the new keyring. You will 
be prompted for an unlock password.</property>
-                <property name="wrap">True</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
+          <object class="GtkLabel">
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">Please choose a name for the new keyring. You will be 
prompted for an unlock password.</property>
+            <property name="wrap">True</property>
+          </object>
+        </child>
+        <child>
+          <object class="AdwPreferencesGroup">
             <child>
-              <object class="GtkBox">
-                <property name="visible">True</property>
-                <property name="orientation">horizontal</property>
-                <property name="spacing">12</property>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="xalign">0</property>
-                    <property name="label" translatable="yes">New Keyring Name:</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkEntry" id="name_entry">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="has_focus">True</property>
-                    <property name="max_length">32</property>
-                    <property name="hexpand">True</property>
-                    <property name="invisible_char">&#x25CF;</property>
-                    <property name="activates_default">True</property>
-                    <property name="width_chars">16</property>
-                    <signal name="changed" handler="on_name_entry_changed"/>
-                  </object>
-                  <packing>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
+              <object class="AdwEntryRow" id="name_row">
+                <property name="title" translatable="yes">New Keyring Name:</property>
+                <signal name="changed" handler="on_name_row_changed"/>
               </object>
-              <packing>
-                <property name="position">1</property>
-              </packing>
             </child>
           </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="position">1</property>
-          </packing>
         </child>
       </object>
     </child>
     <child type="action">
       <object class="GtkButton" id="cancel_button">
-        <property name="visible">True</property>
-        <property name="label" translatable="yes">Cancel</property>
+        <property name="label" translatable="yes">_Cancel</property>
+        <property name="use-underline">True</property>
       </object>
     </child>
     <child type="action">
       <object class="GtkButton" id="ok_button">
-        <property name="visible">True</property>
-        <property name="can-default">True</property>
-        <property name="label" translatable="yes">Add</property>
+        <property name="label" translatable="yes">_Add</property>
+        <property name="use-underline">True</property>
       </object>
     </child>
     <action-widgets>
diff --git a/gkr/seahorse-gkr-item-properties.ui b/gkr/seahorse-gkr-item-properties.ui
index 32e46c9d..063b3b62 100644
--- a/gkr/seahorse-gkr-item-properties.ui
+++ b/gkr/seahorse-gkr-item-properties.ui
@@ -1,89 +1,58 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
   <template class="SeahorseGkrItemProperties" parent="GtkDialog">
     <property name="width_request">400</property>
     <property name="height_request">400</property>
-    <property name="can_focus">False</property>
-    <property name="title" translatable="yes">Item Properties</property>
-    <property name="window_position">center-on-parent</property>
-    <property name="type_hint">dialog</property>
-    <child>
-      <placeholder/>
+    <child type="titlebar">
+      <object class="GtkHeaderBar">
+        <property name="show-title-buttons">True</property>
+        <property name="title-widget">
+          <object class="AdwWindowTitle" id="window_title">
+            <property name="title" translatable="yes">Item Properties</property>
+          </object>
+        </property>
+      </object>
     </child>
-    <child internal-child="vbox">
+    <child internal-child="content_area">
       <object class="GtkBox">
-        <property name="halign">fill</property>
         <property name="orientation">vertical</property>
-        <property name="border_width">0</property>
-        <child internal-child="action_area">
-          <object class="GtkButtonBox">
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
-            <property name="position">2</property>
-          </packing>
-        </child>
+        <property name="margin-top">12</property>
+        <property name="margin-bottom">12</property>
+        <property name="margin-start">12</property>
+        <property name="margin-end">12</property>
         <child>
           <object class="GtkScrolledWindow">
-            <property name="visible">True</property>
             <property name="hscrollbar_policy">never</property>
             <property name="vscrollbar_policy">automatic</property>
             <property name="propagate_natural_height">True</property>
             <property name="propagate_natural_width">True</property>
             <child>
               <object class="GtkBox">
-                <property name="visible">True</property>
                 <property name="orientation">vertical</property>
-                <property name="margin">18</property>
                 <property name="spacing">12</property>
                 <child>
-                  <object class="GtkListBox">
-                    <property name="visible">True</property>
-                    <property name="selection_mode">none</property>
-                    <style>
-                      <class name="content"/>
-                    </style>
+                  <object class="AdwPreferencesGroup">
                     <child>
-                      <object class="HdyActionRow">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
+                      <object class="AdwEntryRow" id="description_field">
                         <property name="title" translatable="yes">Description</property>
-                        <property name="activatable_widget">description_field</property>
-                        <child>
-                          <object class="GtkEntry" id="description_field">
-                            <property name="visible">True</property>
-                            <property name="valign">center</property>
-                            <property name="can_focus">True</property>
-                            <property name="max_width_chars">32</property>
-                          </object>
-                        </child>
                       </object>
                     </child>
                     <child>
-                      <object class="HdyActionRow">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
+                      <object class="AdwActionRow">
                         <property name="title" translatable="yes">Password</property>
                         <child>
-                          <object class="GtkBox" id="password_box_area">
-                            <property name="visible">True</property>
+                          <object class="GtkBox">
                             <property name="valign">center</property>
+                            <child>
+                              <object class="GtkPasswordEntry" id="password_entry">
+                              </object>
+                            </child>
                             <child>
                               <object class="GtkButton">
                                 <property name="label" translatable="yes">Copy</property>
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
                                 <property name="receives_default">True</property>
                                 <signal name="clicked" handler="on_copy_button_clicked" swapped="no"/>
                               </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">True</property>
-                                <property name="pack_type">end</property>
-                                <property name="position">1</property>
-                              </packing>
                             </child>
                             <style>
                               <class name="linked"/>
@@ -93,72 +62,49 @@
                       </object>
                     </child>
                     <child>
-                      <object class="HdyActionRow">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
+                      <object class="AdwActionRow">
                         <property name="title" translatable="yes" comments="To translators: This is the noun 
not the verb.">Use</property>
                         <child>
                           <object class="GtkLabel" id="use_field">
-                            <property name="visible">True</property>
-                            <property name="can_focus">True</property>
                           </object>
                         </child>
                       </object>
                     </child>
                     <child>
-                      <object class="HdyActionRow">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
+                      <object class="AdwActionRow">
                         <property name="title" translatable="yes">Type</property>
                         <child>
                           <object class="GtkLabel" id="type_field">
-                            <property name="visible">True</property>
-                            <property name="can_focus">True</property>
                           </object>
                         </child>
                       </object>
                     </child>
                     <child>
-                      <object class="HdyActionRow" id="server_row">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
+                      <object class="AdwActionRow" id="server_row">
                         <property name="title" translatable="yes">Server</property>
                         <child>
                           <object class="GtkLabel" id="server_field">
-                            <property name="visible">True</property>
-                            <property name="can_focus">True</property>
                           </object>
                         </child>
                       </object>
                     </child>
                     <child>
-                      <object class="HdyActionRow" id="login_row">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
+                      <object class="AdwActionRow" id="login_row">
                         <property name="title" translatable="yes">Login</property>
                         <child>
                           <object class="GtkLabel" id="login_field">
-                            <property name="visible">True</property>
-                            <property name="can_focus">True</property>
                           </object>
                         </child>
                       </object>
                     </child>
                   </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">0</property>
-                  </packing>
                 </child>
                 <child>
-                  <object class="HdyPreferencesGroup" id="details_group">
+                  <object class="AdwPreferencesGroup" id="details_group">
                     <property name="title" translatable="yes">Details</property>
                     <child>
                       <object class="GtkListBox" id="details_box">
-                        <property name="visible">True</property>
                         <property name="selection_mode">none</property>
-                        <property name="can_focus">False</property>
                         <style>
                           <class name="content"/>
                         </style>
@@ -169,8 +115,6 @@
                 <child>
                   <object class="GtkButton">
                     <property name="label" translatable="yes">Delete Password</property>
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
                     <property name="receives_default">True</property>
                     <property name="halign">end</property>
                     <signal name="clicked" handler="on_delete_button_clicked" swapped="no"/>
@@ -178,12 +122,6 @@
                       <class name="destructive-action"/>
                     </style>
                   </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="pack_type">end</property>
-                    <property name="position">1</property>
-                  </packing>
                 </child>
               </object>
             </child>
diff --git a/gkr/seahorse-gkr-keyring.ui b/gkr/seahorse-gkr-keyring.ui
index c9ff8c75..665ce2d8 100644
--- a/gkr/seahorse-gkr-keyring.ui
+++ b/gkr/seahorse-gkr-keyring.ui
@@ -1,130 +1,68 @@
 <?xml version="1.0"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
   <template class="SeahorseGkrKeyringProperties" parent="GtkDialog">
     <child internal-child="headerbar">
       <object class="GtkHeaderBar" id="header">
-        <property name="visible">True</property>
-        <property name="show_close_button">True</property>
-        <property name="title" translatable="yes">Keyring properties</property>
+        <property name="show-title-buttons">True</property>
+        <property name="title-widget">
+          <object class="AdwWindowTitle">
+            <property name="title" translatable="yes">Keyring properties</property>
+          </object>
+        </property>
         <child>
           <object class="GtkLockButton" id="lock_button">
-            <property name="visible">True</property>
-            <property name="tooltip_lock">Keyring is unlocked</property>
-            <property name="tooltip_unlock">Keyring is locked</property>
+            <property name="tooltip_lock" translatable="yes">Keyring is unlocked</property>
+            <property name="tooltip_unlock" translatable="yes">Keyring is locked</property>
             <property name="tooltip_not_authorized"></property>
           </object>
         </child>
       </object>
     </child>
-    <child internal-child="vbox">
+    <child internal-child="content_area">
       <object class="GtkBox">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
-        <property name="border_width">0</property>
         <property name="width_request">300</property>
+        <property name="margin-start">18</property>
+        <property name="margin-end">18</property>
+        <property name="margin-top">18</property>
+        <property name="margin-bottom">18</property>
         <child>
-          <object class="GtkGrid">
-            <property name="visible">True</property>
-            <property name="margin">18</property>
-            <property name="column_spacing">18</property>
-            <property name="row_spacing">6</property>
-            <property name="vexpand">True</property>
-            <child>
-              <object class="GtkImage" id="keyring_image">
-                <property name="visible">True</property>
-                <property name="halign">center</property>
-                <property name="valign">start</property>
-                <property name="margin_end">12</property>
-                <property name="icon-size">6</property>
-              </object>
-              <packing>
-                <property name="top_attach">0</property>
-                <property name="left_attach">0</property>
-                <property name="height">2</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="halign">end</property>
-                <property name="valign">start</property>
-                <property name="label" translatable="yes">Name</property>
-                <style>
-                  <class name="dim-label"/>
-                </style>
-              </object>
-              <packing>
-                <property name="top_attach">0</property>
-                <property name="left_attach">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel" id="name_label">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="halign">start</property>
-                <property name="valign">start</property>
-                <property name="label">keyring name</property>
-              </object>
-              <packing>
-                <property name="top_attach">0</property>
-                <property name="left_attach">2</property>
-              </packing>
-            </child>
+          <object class="AdwPreferencesGroup">
             <child>
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="halign">end</property>
-                <property name="valign">start</property>
-                <property name="label" translatable="yes">Created on</property>
-                <style>
-                  <class name="dim-label"/>
-                </style>
+              <object class="AdwActionRow">
+                <property name="title" translatable="yes">Name</property>
+                <child type="suffix">
+                  <object class="GtkLabel" id="name_label">
+                  </object>
+                </child>
               </object>
-              <packing>
-                <property name="top_attach">1</property>
-                <property name="left_attach">1</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkLabel" id="created_label">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="halign">start</property>
-                <property name="valign">start</property>
+              <object class="AdwActionRow">
+                <property name="title" translatable="yes">Created on</property>
+                <child type="suffix">
+                  <object class="GtkLabel" id="created_label">
+                  </object>
+                </child>
               </object>
-              <packing>
-                <property name="top_attach">1</property>
-                <property name="left_attach">2</property>
-              </packing>
             </child>
           </object>
         </child>
         <child>
           <object class="GtkActionBar">
-            <property name="visible">True</property>
             <child>
               <object class="GtkButton" id="change_pw_button">
-                <property name="visible">True</property>
                 <property name="label" translatable="yes">Change password</property>
                 <signal name="clicked" handler="on_change_pw_button_clicked"/>
               </object>
             </child>
             <child>
               <object class="GtkButton" id="set_default_button">
-                <property name="visible">True</property>
                 <property name="label" translatable="yes">Set as default</property>
                 <signal name="clicked" handler="on_set_default_button_clicked"/>
               </object>
-              <packing>
-                <property name="pack_type">end</property>
-              </packing>
             </child>
           </object>
-          <packing>
-            <property name="pack_type">end</property>
-          </packing>
         </child>
       </object>
     </child>
diff --git a/libseahorse/meson.build b/libseahorse/meson.build
index 97708275..82c5a98a 100644
--- a/libseahorse/meson.build
+++ b/libseahorse/meson.build
@@ -5,7 +5,7 @@ libseahorse_sources = files(
 
 libseahorse_deps = [
   glib_deps,
-  gcr,
+  gcr4_dep,
   libsecret,
   common_dep,
 ]
diff --git a/libseahorse/seahorse-progress.c b/libseahorse/seahorse-progress.c
index 5226ba53..f289e3d1 100644
--- a/libseahorse/seahorse-progress.c
+++ b/libseahorse/seahorse-progress.c
@@ -136,7 +136,7 @@ tracked_task_free (void *data)
     g_free (task->notice);
     g_clear_object (&task->builder);
     if (task->dialog)
-      gtk_widget_destroy (task->dialog);
+      gtk_window_destroy (GTK_WINDOW (task->dialog));
     g_free (task);
 }
 
diff --git a/meson.build b/meson.build
index 0e3c7b16..0e0c9bd1 100644
--- a/meson.build
+++ b/meson.build
@@ -36,10 +36,10 @@ glib_deps = [
   dependency('gio-unix-2.0',version: '>=' + min_glib_version),
   dependency('gmodule-2.0', version: '>=' + min_glib_version),
 ]
-gtk = dependency('gtk+-3.0', version: '>= 3.24.0')
-libhandy_dep = dependency('libhandy-1', version: '>= 1.6.0')
-gcr = dependency('gcr-3',       version: '>=' + min_gcr_version)
-gcr_ui = dependency('gcr-ui-3', version: '>=' + min_gcr_version)
+gtk4_dep = dependency('gtk4', version: '>= 4.6')
+libadwaita_dep = dependency('libadwaita-1', version: '>= 1.0')
+gcr4_dep = dependency('gcr-4', version: '>=' + min_gcr_version)
+# gcr_ui = dependency('gcr-ui-3', version: '>=' + min_gcr_version)
 libsecret = dependency('libsecret-1', version: '>= 0.16')
 libpwquality = dependency('pwquality')
 posix = valac.find_library('posix')
@@ -63,6 +63,7 @@ if get_option('pgp-support')
   endif
 endif
 
+# XXX
 pkcs11_dep = valac.find_library('pkcs11', required: get_option('pkcs11-support'))
 if get_option('pkcs11-support') and not pkcs11_dep.found()
   error('Required library "pkcs11" not found (needed for PKCS#11 support)')
diff --git a/pgp/meson.build b/pgp/meson.build
index 1d4b6db9..c1f53967 100644
--- a/pgp/meson.build
+++ b/pgp/meson.build
@@ -1,5 +1,4 @@
 pgp_sources = files(
-  'seahorse-combo-keys.c',
   'seahorse-discovery.c',
   'seahorse-gpgme.c',
   'seahorse-gpgme-add-subkey.c',
@@ -39,7 +38,7 @@ pgp_sources = files(
 pgp_dependencies = [
   config,
   glib_deps,
-  gcr,
+  gcr4_dep,
   gpgme_dep,
   common_dep,
   libseahorse_dep,
diff --git a/pgp/seahorse-gpgme-add-subkey.ui b/pgp/seahorse-gpgme-add-subkey.ui
index c8efa24e..80f7d7c2 100644
--- a/pgp/seahorse-gpgme-add-subkey.ui
+++ b/pgp/seahorse-gpgme-add-subkey.ui
@@ -1,6 +1,5 @@
 <?xml version="1.0"?>
 <interface>
-  <requires lib="gtk+" version="3.24"/>
   <object class="GtkAdjustment" id="length_spinner_adjustment">
     <property name="value">1024</property>
     <property name="lower">768</property>
@@ -8,80 +7,48 @@
     <property name="step_increment">1</property>
     <property name="page_increment">128</property>
   </object>
+
   <template class="SeahorseGpgmeAddSubkey" parent="GtkDialog">
-    <property name="visible">True</property>
-    <property name="border_width">6</property>
-    <child internal-child="vbox">
+    <child internal-child="content_area">
       <object class="GtkBox">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
-        <property name="spacing">2</property>
+        <property name="margin-top">12</property>
+        <property name="margin-bottom">12</property>
+        <property name="margin-start">12</property>
+        <property name="margin-end">12</property>
         <child>
-          <object class="GtkGrid">
-            <property name="visible">True</property>
-            <property name="column-spacing">12</property>
-            <property name="row-spacing">6</property>
+          <object class="AdwPreferencesGroup">
             <child>
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="xalign">1</property>
-                <property name="label" translatable="yes">Key _Type</property>
-                <property name="use_underline">True</property>
-                <property name="mnemonic_widget">type_combo</property>
-                <style>
-                  <class name="dim-label"/>
-                </style>
-              </object>
-              <packing>
-                <property name="top_attach">0</property>
-                <property name="left_attach">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkComboBox" id="type_combo">
-                <property name="visible">True</property>
-                <signal name="changed" handler="handler_gpgme_add_subkey_type_changed"/>
-              </object>
-              <packing>
-                <property name="top_attach">0</property>
-                <property name="left_attach">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="xalign">1</property>
-                <property name="label" translatable="yes">Key _Length</property>
-                <property name="use_underline">True</property>
-                <property name="mnemonic_widget">length_spinner</property>
-                <style>
-                  <class name="dim-label"/>
-                </style>
+              <object class="AdwActionRow">
+                <property name="title" translatable="yes">Key _Type</property>
+                <property name="use-underline">True</property>
+                <child type="suffix">
+                  <object class="GtkComboBox" id="type_combo">
+                    <property name="valign">center</property>
+                    <signal name="changed" handler="handler_gpgme_add_subkey_type_changed"/>
+                  </object>
+                </child>
               </object>
-              <packing>
-                <property name="top_attach">1</property>
-                <property name="left_attach">0</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkSpinButton" id="length_spinner">
-                <property name="width_request">80</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="tooltip_text" translatable="yes">Length of Key</property>
-                <property name="adjustment">length_spinner_adjustment</property>
-                <property name="climb_rate">128</property>
-                <property name="snap_to_ticks">True</property>
-                <property name="numeric">True</property>
+              <object class="AdwActionRow">
+                <property name="title" translatable="yes">Key _Length</property>
+                <property name="use-underline">True</property>
+                <child type="suffix">
+                  <object class="GtkSpinButton" id="length_spinner">
+                    <property name="width-request">80</property>
+                    <property name="tooltip-text" translatable="yes">Length of Key</property>
+                    <property name="adjustment">length_spinner_adjustment</property>
+                    <property name="climb-rate">128</property>
+                    <property name="snap-to-ticks">True</property>
+                    <property name="numeric">True</property>
+                    <property name="valign">center</property>
+                  </object>
+                </child>
               </object>
-              <packing>
-                <property name="top_attach">1</property>
-                <property name="left_attach">1</property>
-              </packing>
             </child>
             <child>
               <object class="GtkLabel">
-                <property name="visible">True</property>
                 <property name="xalign">1</property>
                 <property name="label" translatable="yes">Expiration Date</property>
                 <property name="mnemonic-widget">expires_datepicker</property>
@@ -89,74 +56,42 @@
                   <class name="dim-label"/>
                 </style>
               </object>
-              <packing>
-                <property name="top_attach">2</property>
-                <property name="left_attach">0</property>
-              </packing>
             </child>
             <child>
               <object class="SeahorseDatePicker" id="expires_datepicker">
-                <property name="visible">True</property>
                 <property name="sensitive">False</property>
               </object>
-              <packing>
-                <property name="top_attach">2</property>
-                <property name="left_attach">1</property>
-              </packing>
             </child>
             <child>
               <object class="GtkCheckButton" id="never_expires_check">
                 <property name="label" translatable="yes">Never E_xpires</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">False</property>
                 <property name="tooltip_text" translatable="yes">If key never expires</property>
                 <property name="use_underline">True</property>
                 <property name="active">True</property>
-                <property name="draw_indicator">True</property>
                 <signal name="toggled" handler="on_gpgme_add_subkey_never_expires_toggled"/>
               </object>
-              <packing>
-                <property name="top_attach">2</property>
-                <property name="left_attach">2</property>
-              </packing>
-            </child>
-          </object>
-        </child>
-        <child internal-child="action_area">
-          <object class="GtkButtonBox">
-            <property name="visible">True</property>
-            <property name="orientation">horizontal</property>
-            <property name="layout_style">end</property>
-            <child>
-              <object class="GtkButton" id="cancelbutton1">
-                <property name="label">gtk-cancel</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="receives_default">False</property>
-                <property name="use_stock">True</property>
-              </object>
-            </child>
-            <child>
-              <object class="GtkButton" id="okbutton1">
-                <property name="label">gtk-ok</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="has_default">True</property>
-                <property name="receives_default">False</property>
-                <property name="tooltip_text" translatable="yes">Generate a new subkey</property>
-                <property name="use_stock">True</property>
-              </object>
             </child>
           </object>
         </child>
       </object>
     </child>
+
+    <child type="action">
+      <object class="GtkButton" id="cancel_button">
+        <property name="use-underline">True</property>
+        <property name="label" translatable="yes">_Cancel</property>
+      </object>
+    </child>
+    <child type="action">
+      <object class="GtkButton" id="ok_button">
+        <property name="label" translatable="yes">_Generate</property>
+        <property name="use-underline">True</property>
+        <property name="tooltip_text" translatable="yes">Generate a new subkey</property>
+      </object>
+    </child>
     <action-widgets>
-      <action-widget response="-6">cancelbutton1</action-widget>
-      <action-widget response="-5">okbutton1</action-widget>
+      <action-widget response="-6">cancel_button</action-widget>
+      <action-widget response="-5" default="true">ok_button</action-widget>
     </action-widgets>
   </template>
 </interface>
diff --git a/pgp/seahorse-gpgme-add-uid.c b/pgp/seahorse-gpgme-add-uid.c
index ed9b70a2..92097900 100644
--- a/pgp/seahorse-gpgme-add-uid.c
+++ b/pgp/seahorse-gpgme-add-uid.c
@@ -36,9 +36,9 @@ struct _SeahorseGpgmeAddUid {
 
     SeahorseGpgmeKey *key;
 
-    GtkWidget *name_entry;
-    GtkWidget *email_entry;
-    GtkWidget *comment_entry;
+    GtkWidget *name_row;
+    GtkWidget *email_row;
+    GtkWidget *comment_row;
 };
 
 enum {
@@ -51,18 +51,18 @@ G_DEFINE_TYPE (SeahorseGpgmeAddUid, seahorse_gpgme_add_uid, GTK_TYPE_DIALOG)
 static gboolean
 check_name_input (SeahorseGpgmeAddUid *self)
 {
-    const gchar *name;
+    const char *name;
 
-    name = gtk_entry_get_text (GTK_ENTRY (self->name_entry));
+    name = gtk_editable_get_text (GTK_EDITABLE (self->name_row));
     return strlen (name) >= 5;
 }
 
 static gboolean
 check_email_input (SeahorseGpgmeAddUid *self)
 {
-    const gchar *email;
+    const char *email;
 
-    email = gtk_entry_get_text (GTK_ENTRY (self->email_entry));
+    email = gtk_editable_get_text (GTK_EDITABLE (self->email_row));
     return strlen (email) == 0 || g_pattern_match_simple ("?*@?*", email);
 }
 
@@ -76,14 +76,14 @@ check_ok (SeahorseGpgmeAddUid *self)
 }
 
 static void
-on_name_entry_changed (GtkEditable *editable, gpointer user_data)
+on_name_row_changed (GtkEditable *editable, gpointer user_data)
 {
     SeahorseGpgmeAddUid *self = SEAHORSE_GPGME_ADD_UID (user_data);
     check_ok (self);
 }
 
 static void
-on_email_entry_changed (GtkEditable *editable, gpointer user_data)
+on_email_row_changed (GtkEditable *editable, gpointer user_data)
 {
     SeahorseGpgmeAddUid *self = SEAHORSE_GPGME_ADD_UID (user_data);
     check_ok (self);
@@ -174,12 +174,12 @@ seahorse_gpgme_add_uid_class_init (SeahorseGpgmeAddUidClass *klass)
 
     gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/Seahorse/seahorse-gpgme-add-uid.ui");
 
-    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeAddUid, name_entry);
-    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeAddUid, email_entry);
-    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeAddUid, comment_entry);
+    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeAddUid, name_row);
+    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeAddUid, email_row);
+    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeAddUid, comment_row);
 
-    gtk_widget_class_bind_template_callback (widget_class, on_name_entry_changed);
-    gtk_widget_class_bind_template_callback (widget_class, on_email_entry_changed);
+    gtk_widget_class_bind_template_callback (widget_class, on_name_row_changed);
+    gtk_widget_class_bind_template_callback (widget_class, on_email_row_changed);
 }
 
 /**
@@ -220,32 +220,39 @@ on_gpgme_key_op_uid_added (GObject *source, GAsyncResult *result, gpointer user_
     }
 
     seahorse_gpgme_key_refresh (key);
-    gtk_widget_destroy (GTK_WIDGET (g_steal_pointer (&dialog)));
+    gtk_window_destroy (GTK_WINDOW (g_steal_pointer (&dialog)));
 }
 
-void
-seahorse_gpgme_add_uid_run_dialog (SeahorseGpgmeKey *pkey, GtkWindow *parent)
+static void
+on_response (GtkDialog *dialg, int response, gpointer user_data)
 {
     g_autoptr(SeahorseGpgmeAddUid) dialog = NULL;
-    GtkResponseType response;
-    const gchar *name, *email, *comment;
-
-    g_return_if_fail (SEAHORSE_GPGME_IS_KEY (pkey));
+    const char *name, *email, *comment;
 
-    dialog = seahorse_gpgme_add_uid_new (pkey, parent);
-    response = gtk_dialog_run (GTK_DIALOG (dialog));
     if (response != GTK_RESPONSE_OK) {
-        gtk_widget_destroy (GTK_WIDGET (g_steal_pointer (&dialog)));
+        gtk_window_destroy (GTK_WINDOW (g_steal_pointer (&dialog)));
         return;
     }
 
-    name = gtk_entry_get_text (GTK_ENTRY (dialog->name_entry));
-    email = gtk_entry_get_text (GTK_ENTRY (dialog->email_entry));
-    comment = gtk_entry_get_text (GTK_ENTRY (dialog->comment_entry));
+    name = gtk_editable_get_text (GTK_EDITABLE (dialog->name_row));
+    email = gtk_editable_get_text (GTK_EDITABLE (dialog->email_row));
+    comment = gtk_editable_get_text (GTK_EDITABLE (dialog->comment_row));
 
-    seahorse_gpgme_key_op_add_uid_async (pkey,
+    seahorse_gpgme_key_op_add_uid_async (dialog->key,
                                          name, email, comment,
                                          NULL,
                                          on_gpgme_key_op_uid_added,
                                          g_steal_pointer (&dialog));
 }
+
+void
+seahorse_gpgme_add_uid_run_dialog (SeahorseGpgmeKey *pkey, GtkWindow *parent)
+{
+    g_autoptr(SeahorseGpgmeAddUid) dialog = NULL;
+
+    g_return_if_fail (SEAHORSE_GPGME_IS_KEY (pkey));
+
+    dialog = seahorse_gpgme_add_uid_new (pkey, parent);
+    g_signal_connect (dialog, "response", G_CALLBACK (on_response), NULL);
+    gtk_window_present (GTK_WINDOW (g_steal_pointer (&dialog)));
+}
diff --git a/pgp/seahorse-gpgme-add-uid.ui b/pgp/seahorse-gpgme-add-uid.ui
index 30f73fdd..2ad989c3 100644
--- a/pgp/seahorse-gpgme-add-uid.ui
+++ b/pgp/seahorse-gpgme-add-uid.ui
@@ -1,142 +1,64 @@
 <?xml version="1.0"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
   <template class="SeahorseGpgmeAddUid" parent="GtkDialog">
-    <property name="visible">True</property>
     <property name="default-width">400</property>
-    <property name="border_width">5</property>
     <property name="title" translatable="yes">Add User ID</property>
-    <child internal-child="vbox">
-      <object class="GtkBox" id="dialog-vbox1">
-        <property name="visible">True</property>
+
+    <child internal-child="content_area">
+      <object class="GtkBox">
         <property name="orientation">vertical</property>
-        <property name="spacing">2</property>
+        <property name="margin-top">12</property>
+        <property name="margin-bottom">12</property>
+        <property name="margin-start">12</property>
+        <property name="margin-end">12</property>
         <child>
-          <object class="GtkGrid">
-            <property name="visible">True</property>
-            <property name="margin">6</property>
-            <property name="row_spacing">6</property>
-            <property name="column_spacing">6</property>
+          <object class="AdwPreferencesGroup">
             <child>
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="halign">end</property>
-                <property name="label" translatable="yes" comments="Full name of the key, usually the name 
of the user.">Full _Name</property>
-                <property name="use_underline">True</property>
-                <property name="mnemonic_widget">name_entry</property>
-                <style>
-                  <class name="dim-label"/>
-                </style>
+              <object class="AdwEntryRow" id="name_row">
+                <property name="title" translatable="yes" comments="Full name of the key, usually the name 
of the user.">Full _Name</property>
+                <property name="use-underline">True</property>
+                <property name="tooltip-text" translatable="yes">Must be at least 5 characters 
long</property>
+                <signal name="changed" handler="on_name_row_changed"/>
               </object>
-              <packing>
-                <property name="top_attach">0</property>
-                <property name="left_attach">0</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkEntry" id="name_entry">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="has_focus">True</property>
-                <property name="hexpand">True</property>
-                <property name="tooltip_text" translatable="yes">Must be at least 5 characters 
long</property>
-                <property name="activates_default">True</property>
-                <signal name="changed" handler="on_name_entry_changed"/>
+              <object class="AdwEntryRow" id="email_row">
+                <property name="title" translatable="yes">_Email Address</property>
+                <property name="use-underline">True</property>
+                <property name="tooltip-text" translatable="yes">Optional email address</property>
+                <property name="input-purpose">email</property>
+                <signal name="changed" handler="on_email_row_changed"/>
               </object>
-              <packing>
-                <property name="top_attach">0</property>
-                <property name="left_attach">1</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="halign">end</property>
-                <property name="label" translatable="yes">_Email Address</property>
-                <property name="use_underline">True</property>
-                <property name="mnemonic_widget">email_entry</property>
-                <style>
-                  <class name="dim-label"/>
-                </style>
+              <object class="AdwEntryRow" id="comment_row">
+                <property name="title" translatable="yes">Key Co_mment</property>
+                <property name="use-underline">True</property>
+                <property name="tooltip-text" translatable="yes">Optional comment describing key</property>
               </object>
-              <packing>
-                <property name="top_attach">1</property>
-                <property name="left_attach">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkEntry" id="email_entry">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="hexpand">True</property>
-                <property name="tooltip_text" translatable="yes">Optional email address</property>
-                <property name="activates_default">True</property>
-                <signal name="changed" handler="on_email_entry_changed"/>
-              </object>
-              <packing>
-                <property name="top_attach">1</property>
-                <property name="left_attach">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="halign">end</property>
-                <property name="label" translatable="yes">Key Co_mment</property>
-                <property name="use_underline">True</property>
-                <property name="mnemonic_widget">comment_entry</property>
-                <style>
-                  <class name="dim-label"/>
-                </style>
-              </object>
-              <packing>
-                <property name="top_attach">2</property>
-                <property name="left_attach">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkEntry" id="comment_entry">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="hexpand">True</property>
-                <property name="tooltip_text" translatable="yes">Optional comment describing key</property>
-                <property name="activates_default">True</property>
-              </object>
-              <packing>
-                <property name="top_attach">2</property>
-                <property name="left_attach">1</property>
-              </packing>
             </child>
           </object>
         </child>
       </object>
     </child>
+
     <child type="action">
       <object class="GtkButton" id="cancel_button">
-        <property name="visible">True</property>
         <property name="use-underline">True</property>
         <property name="label" translatable="yes">_Cancel</property>
-        <property name="can_focus">True</property>
-        <property name="can_default">True</property>
-        <property name="receives_default">False</property>
       </object>
     </child>
     <child type="action">
       <object class="GtkButton" id="ok_button">
-        <property name="visible">True</property>
-        <property name="label" translatable="yes">_OK</property>
+        <property name="label" translatable="yes">_Add</property>
         <property name="use-underline">True</property>
         <property name="sensitive">False</property>
-        <property name="can_focus">True</property>
-        <property name="can_default">True</property>
-        <property name="has_default">True</property>
-        <property name="receives_default">False</property>
         <property name="tooltip_text" translatable="yes">Create the new user ID</property>
       </object>
     </child>
     <action-widgets>
       <action-widget response="cancel">cancel_button</action-widget>
-      <action-widget response="ok">ok_button</action-widget>
+      <action-widget response="ok" default="true">ok_button</action-widget>
     </action-widgets>
   </template>
 </interface>
diff --git a/pgp/seahorse-gpgme-dialogs.h b/pgp/seahorse-gpgme-dialogs.h
index 3167d298..36de5103 100644
--- a/pgp/seahorse-gpgme-dialogs.h
+++ b/pgp/seahorse-gpgme-dialogs.h
@@ -58,6 +58,3 @@ void            seahorse_gpgme_expires_new          (SeahorseGpgmeSubkey *subkey
 gboolean        seahorse_gpgme_photo_add            (SeahorseGpgmeKey *pkey,
                                                      GtkWindow        *parent,
                                                      const char       *path);
-
-gboolean        seahorse_gpgme_photo_delete         (SeahorseGpgmePhoto *photo,
-                                                     GtkWindow *parent);
diff --git a/pgp/seahorse-gpgme-expires-dialog.c b/pgp/seahorse-gpgme-expires-dialog.c
index a9acebe3..7a7ec90e 100644
--- a/pgp/seahorse-gpgme-expires-dialog.c
+++ b/pgp/seahorse-gpgme-expires-dialog.c
@@ -58,13 +58,11 @@ seahorse_gpgme_expires_dialog_response (GtkDialog *dialog, int response)
     if (response != GTK_RESPONSE_OK)
         return;
 
-    if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->never_expires_check))) {
-        unsigned int y, m, d;
+    if (!gtk_check_button_get_active (GTK_CHECK_BUTTON (self->never_expires_check))) {
         g_autoptr(GDateTime) now = NULL;
 
-        gtk_calendar_get_date (GTK_CALENDAR (self->calendar), &y, &m, &d);
-        expires = g_date_time_new_utc (y, m + 1, d, 0, 0, 0);
-        now = g_date_time_new_now_utc ();
+        expires = gtk_calendar_get_date (GTK_CALENDAR (self->calendar));
+        now = g_date_time_new_now_local ();
 
         if (g_date_time_compare (expires, now) <= 0) {
             seahorse_util_show_error (self->calendar, _("Invalid expiry date"),
@@ -91,7 +89,7 @@ on_gpgme_expire_toggled (GtkWidget *widget,
     SeahorseGpgmeExpiresDialog *self = SEAHORSE_GPGME_EXPIRES_DIALOG (user_data);
 
     gtk_widget_set_sensitive (self->calendar,
-                              !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->never_expires_check)));
+                              !gtk_check_button_get_active (GTK_CHECK_BUTTON (self->never_expires_check)));
 }
 
 static void
@@ -157,17 +155,12 @@ seahorse_gpgme_expires_dialog_constructed (GObject *obj)
 
     expires = seahorse_pgp_subkey_get_expires (SEAHORSE_PGP_SUBKEY (self->subkey));
     if (expires == NULL) {
-        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->never_expires_check), TRUE);
+        gtk_check_button_set_active (GTK_CHECK_BUTTON (self->never_expires_check), TRUE);
         gtk_widget_set_sensitive (self->calendar, FALSE);
     } else {
-        int y, m, d;
-
-        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->never_expires_check), FALSE);
+        gtk_check_button_set_active (GTK_CHECK_BUTTON (self->never_expires_check), FALSE);
         gtk_widget_set_sensitive (self->calendar, TRUE);
-
-        g_date_time_get_ymd (expires, &y, &m, &d);
-        gtk_calendar_select_month (GTK_CALENDAR (self->calendar), m - 1, y);
-        gtk_calendar_select_day (GTK_CALENDAR (self->calendar), d);
+        gtk_calendar_select_day (GTK_CALENDAR (self->calendar), expires);
     }
 }
 
diff --git a/pgp/seahorse-gpgme-expires-dialog.ui b/pgp/seahorse-gpgme-expires-dialog.ui
index 2d73d897..961127a8 100644
--- a/pgp/seahorse-gpgme-expires-dialog.ui
+++ b/pgp/seahorse-gpgme-expires-dialog.ui
@@ -1,50 +1,36 @@
 <?xml version="1.0"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
   <template class="SeahorseGpgmeExpiresDialog" parent="GtkDialog">
-    <property name="visible">True</property>
-    <property name="border-width">12</property>
     <property name="width-request">400</property>
     <property name="modal">True</property>
-    <child internal-child="vbox">
+
+    <child internal-child="content_area">
       <object class="GtkBox" id="all-controls">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
         <property name="spacing">6</property>
         <child>
           <object class="GtkCheckButton" id="never_expires_check">
             <property name="label" translatable="yes">_Never expires</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
             <property name="receives_default">False</property>
             <property name="use_underline">True</property>
-            <property name="draw_indicator">True</property>
             <signal name="toggled" handler="on_gpgme_expire_toggled"/>
           </object>
         </child>
         <child>
           <object class="GtkCalendar" id="calendar">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
           </object>
         </child>
       </object>
     </child>
+
     <child type="action">
       <object class="GtkButton" id="cancel_button">
-        <property name="label">gtk-cancel</property>
-        <property name="visible">True</property>
-        <property name="can_default">True</property>
-        <property name="receives_default">False</property>
-        <property name="use_stock">True</property>
+        <property name="label" translatable="yes">_Cancel</property>
+        <property name="use_underline">True</property>
       </object>
     </child>
     <child type="action">
       <object class="GtkButton" id="ok_button">
-        <property name="visible">True</property>
-        <property name="can_default">True</property>
-        <property name="has_default">True</property>
-        <property name="receives_default">False</property>
         <property name="label" translatable="yes">C_hange</property>
         <property name="use_underline">True</property>
       </object>
diff --git a/pgp/seahorse-gpgme-generate-dialog.c b/pgp/seahorse-gpgme-generate-dialog.c
index d522502b..81db167d 100644
--- a/pgp/seahorse-gpgme-generate-dialog.c
+++ b/pgp/seahorse-gpgme-generate-dialog.c
@@ -49,14 +49,16 @@ struct _SeahorseGpgmeGenerateDialog {
 
     SeahorseGpgmeKeyring *keyring;
 
-    GtkWidget *name_entry;
-    GtkWidget *email_entry;
-    GtkWidget *comment_entry;
+    GtkWidget *name_row;
+    GtkWidget *name_row_warning;
+    GtkWidget *email_row;
+    GtkWidget *comment_row;
 
-    GtkWidget *algorithm_choice;
-    GtkWidget *bits_entry;
+    GtkWidget *algorithm_row;
+    GtkWidget *bits_spin;
 
-    GtkWidget *expires_check;
+    GtkWidget *expires_switch;
+    GtkWidget *expires_date_row;
     GtkWidget *expires_datepicker;
 };
 
@@ -104,6 +106,65 @@ on_generate_key_complete (GObject *source,
                                     g_variant_new_string ("gnupg"));
 }
 
+typedef struct _GenerateClosure {
+    SeahorseGpgmeKeyring *keyring;
+    char *name;
+    char *email;
+    char *comment;
+    unsigned int type;
+    unsigned int bits;
+    GDateTime *expires;
+    GtkWindow *parent;
+} GenerateClosure;
+
+static void
+generate_closure_free (void *data)
+{
+    GenerateClosure *closure = data;
+    g_clear_object (&closure->keyring);
+    g_clear_pointer (&closure->name, g_free);
+    g_clear_pointer (&closure->email, g_free);
+    g_clear_pointer (&closure->comment, g_free);
+    g_clear_object (&closure->expires);
+    g_clear_object (&closure->parent);
+}
+
+static void
+on_pass_prompt_response (GtkDialog *dialog, int response, void *user_data)
+{
+    SeahorsePassphrasePrompt *pdialog = SEAHORSE_PASSPHRASE_PROMPT (dialog);
+    GenerateClosure *closure = user_data;
+    const char *pass;
+    g_autoptr(GCancellable) cancellable = NULL;
+    const char *notice;
+
+    if (response == GTK_RESPONSE_ACCEPT) {
+        pass = seahorse_passphrase_prompt_get_text (pdialog);
+        cancellable = g_cancellable_new ();
+        seahorse_gpgme_key_op_generate_async (closure->keyring,
+                                              closure->name,
+                                              closure->email,
+                                              closure->comment,
+                                              pass,
+                                              closure->type,
+                                              closure->bits,
+                                              closure->expires,
+                                              cancellable, on_generate_key_complete,
+                                              closure->parent);
+
+        /* Has line breaks because GtkLabel is completely broken WRT wrapping */
+        notice = _("When creating a key we need to generate a lot of\n"
+                   "random data and we need you to help. It’s a good\n"
+                   "idea to perform some other action like typing on\n"
+                   "the keyboard, moving the mouse, using applications.\n"
+                   "This gives the system the random data that it needs.");
+        seahorse_progress_show_with_notice (cancellable, _("Generating key"), notice, FALSE);
+    }
+
+    gtk_window_destroy (GTK_WINDOW (dialog));
+    generate_closure_free (closure);
+}
+
 /**
  * gpgme_generate_key:
  * @sksrc: the seahorse source
@@ -129,34 +190,26 @@ seahorse_gpgme_generate_key (SeahorseGpgmeKeyring *keyring,
                              GDateTime           *expires,
                              GtkWindow           *parent)
 {
-    GCancellable *cancellable;
-    const gchar *pass;
     SeahorsePassphrasePrompt *dialog;
-    const gchar *notice;
+    GenerateClosure *closure;
 
     dialog = seahorse_passphrase_prompt_show_dialog (_("Passphrase for New PGP Key"),
-                                              _("Enter the passphrase for your new key twice."),
-                                              NULL, NULL, TRUE);
+                                                     _("Enter the passphrase for your new key twice."),
+                                                     NULL, NULL, TRUE);
     gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
-    if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
-        pass = seahorse_passphrase_prompt_get_text (dialog);
-        cancellable = g_cancellable_new ();
-        seahorse_gpgme_key_op_generate_async (keyring, name, email, comment,
-                                              pass, type, bits, expires,
-                                              cancellable, on_generate_key_complete,
-                                              parent);
 
-        /* Has line breaks because GtkLabel is completely broken WRT wrapping */
-        notice = _("When creating a key we need to generate a lot of\n"
-                   "random data and we need you to help. It’s a good\n"
-                   "idea to perform some other action like typing on\n"
-                   "the keyboard, moving the mouse, using applications.\n"
-                   "This gives the system the random data that it needs.");
-        seahorse_progress_show_with_notice (cancellable, _("Generating key"), notice, FALSE);
-        g_object_unref (cancellable);
-    }
-
-    gtk_widget_destroy (GTK_WIDGET (dialog));
+    closure = g_new0 (GenerateClosure, 1);
+    closure->keyring = g_object_ref (keyring);
+    closure->name = g_strdup (name);
+    closure->email = g_strdup (email);
+    closure->comment = g_strdup (comment);
+    closure->type = type;
+    closure->bits = bits;
+    closure->expires = expires? g_object_ref (expires) : NULL;
+    closure->parent = parent? g_object_ref (parent) : NULL;
+
+    g_signal_connect (dialog, "response", G_CALLBACK (on_pass_prompt_response), closure);
+    gtk_window_present (GTK_WINDOW (dialog));
 }
 
 /* If the name has more than 5 characters, this sets the ok button sensitive */
@@ -169,53 +222,35 @@ on_gpgme_generate_entry_changed (GtkEditable *editable,
     gboolean name_long_enough;
 
     /* A 5 character name is required */
-    name = g_strdup (gtk_entry_get_text (GTK_ENTRY (self->name_entry)));
+    name = g_strdup (gtk_editable_get_text (GTK_EDITABLE (self->name_row)));
     name_long_enough = name && strlen (g_strstrip (name)) >= 5;
 
     /* If not, show the user and disable the create button */
-    if (!name_long_enough) {
-        g_object_set (self->name_entry,
-            "secondary-icon-name", "dialog-warning-symbolic",
-            "secondary-icon-tooltip-text", _("Name must be at least 5 characters long."),
-            NULL);
-    } else {
-        g_object_set (self->name_entry, "secondary-icon-name", NULL, NULL);
-    }
-
+    gtk_widget_set_visible (self->name_row_warning, !name_long_enough);
     gtk_dialog_set_response_sensitive (GTK_DIALOG (self), GTK_RESPONSE_OK, name_long_enough);
 }
 
-/* Handles the expires toggle button feedback */
-static void
-on_gpgme_generate_expires_toggled (GtkToggleButton *button,
-                                   gpointer user_data)
-{
-    SeahorseGpgmeGenerateDialog *self = SEAHORSE_GPGME_GENERATE_DIALOG (user_data);
-
-    gtk_widget_set_sensitive (self->expires_datepicker,
-                              !gtk_toggle_button_get_active (button));
-}
-
 /* Changes the bit range depending on the algorithm set */
 static void
-on_gpgme_generate_algorithm_changed (GtkComboBox *combo,
-                                     gpointer user_data)
+on_algo_row_notify_selected (GObject    *object,
+                             GParamSpec *pspec,
+                             void       *user_data)
 {
     SeahorseGpgmeGenerateDialog *self = SEAHORSE_GPGME_GENERATE_DIALOG (user_data);
-    int sel;
+    unsigned int sel;
 
-    sel = gtk_combo_box_get_active (combo);
-    g_assert (sel < (int)G_N_ELEMENTS (available_algorithms));
+    sel = adw_combo_row_get_selected (ADW_COMBO_ROW (self->algorithm_row));
+    g_assert (sel < G_N_ELEMENTS (available_algorithms));
 
-    gtk_spin_button_set_range (GTK_SPIN_BUTTON (self->bits_entry),
+    gtk_spin_button_set_range (GTK_SPIN_BUTTON (self->bits_spin),
                                available_algorithms[sel].min,
                                available_algorithms[sel].max);
 
     /* Set sane default key length */
     if (available_algorithms[sel].def > available_algorithms[sel].max)
-        gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->bits_entry), available_algorithms[sel].max);
+        gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->bits_spin), available_algorithms[sel].max);
     else
-        gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->bits_entry), available_algorithms[sel].def);
+        gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->bits_spin), available_algorithms[sel].def);
 }
 
 static void
@@ -224,7 +259,7 @@ seahorse_gpgme_generate_dialog_response (GtkDialog *dialog, int response)
     SeahorseGpgmeGenerateDialog *self = SEAHORSE_GPGME_GENERATE_DIALOG (dialog);
     g_autofree char *name = NULL;
     const char *email, *comment;
-    int sel;
+    guint sel;
     guint type;
     g_autoptr(GDateTime) expires = NULL;
     guint bits;
@@ -233,28 +268,28 @@ seahorse_gpgme_generate_dialog_response (GtkDialog *dialog, int response)
         return;
 
     /* Make sure the name is the right length. Should've been checked earlier */
-    name = g_strdup (gtk_entry_get_text (GTK_ENTRY (self->name_entry)));
+    name = g_strdup (gtk_editable_get_text (GTK_EDITABLE (self->name_row)));
     g_return_if_fail (name);
     name = g_strstrip (name);
     g_return_if_fail (strlen(name) >= 5);
 
-    email = gtk_entry_get_text (GTK_ENTRY (self->email_entry));
-    comment = gtk_entry_get_text (GTK_ENTRY (self->comment_entry));
+    email = gtk_editable_get_text (GTK_EDITABLE (self->email_row));
+    comment = gtk_editable_get_text (GTK_EDITABLE (self->comment_row));
 
     /* The algorithm */
-    sel = gtk_combo_box_get_active (GTK_COMBO_BOX (self->algorithm_choice));
+    sel = adw_combo_row_get_selected (ADW_COMBO_ROW (self->algorithm_row));
     g_assert (sel <= (int) G_N_ELEMENTS(available_algorithms));
     type = available_algorithms[sel].type;
 
     /* The number of bits */
-    bits = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (self->bits_entry));
+    bits = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (self->bits_spin));
     if (bits < available_algorithms[sel].min || bits > available_algorithms[sel].max) {
         bits = available_algorithms[sel].def;
         g_message ("invalid key size: %s defaulting to %u", available_algorithms[sel].desc, bits);
     }
 
     /* The expiry */
-    if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->expires_check)))
+    if (!gtk_switch_get_active (GTK_SWITCH (self->expires_switch)))
         expires = seahorse_date_picker_get_datetime (SEAHORSE_DATE_PICKER (self->expires_datepicker));
 
     /* Less confusing with less on the screen */
@@ -315,21 +350,19 @@ seahorse_gpgme_generate_dialog_finalize (GObject *obj)
 static void
 seahorse_gpgme_generate_dialog_init (SeahorseGpgmeGenerateDialog *self)
 {
-    g_autoptr (GDateTime) now = NULL;
-    g_autoptr (GDateTime) next_year = NULL;
-    guint i;
+    g_autoptr(GDateTime) now = NULL;
+    g_autoptr(GDateTime) next_year = NULL;
+    g_autoptr(GtkStringList) algos = NULL;
 
     gtk_widget_init_template (GTK_WIDGET (self));
 
     /* The algorithms */
-    gtk_combo_box_text_remove (GTK_COMBO_BOX_TEXT (self->algorithm_choice), 0);
-    for (i = 0; i < G_N_ELEMENTS(available_algorithms); i++) {
-        gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (self->algorithm_choice),
-                                        _(available_algorithms[i].desc));
-    }
-    gtk_combo_box_set_active (GTK_COMBO_BOX (self->algorithm_choice), 0);
-    on_gpgme_generate_algorithm_changed (GTK_COMBO_BOX (self->algorithm_choice),
-                                         self);
+    algos = gtk_string_list_new (NULL);
+    for (guint i = 0; i < G_N_ELEMENTS (available_algorithms); i++)
+        gtk_string_list_append (algos, _(available_algorithms[i].desc));
+    adw_combo_row_set_model (ADW_COMBO_ROW (self->algorithm_row),
+                             G_LIST_MODEL (algos));
+    on_algo_row_notify_selected (G_OBJECT (self->algorithm_row), NULL, self);
 
     /* Default expiry date */
     now = g_date_time_new_now_utc ();
@@ -362,16 +395,17 @@ seahorse_gpgme_generate_dialog_class_init (SeahorseGpgmeGenerateDialogClass *kla
     g_object_class_install_properties (gobject_class, N_PROPS, obj_props);
 
     gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/Seahorse/seahorse-gpgme-generate-dialog.ui");
-    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeGenerateDialog, name_entry);
-    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeGenerateDialog, email_entry);
-    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeGenerateDialog, comment_entry);
-    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeGenerateDialog, algorithm_choice);
-    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeGenerateDialog, bits_entry);
+    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeGenerateDialog, name_row);
+    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeGenerateDialog, name_row_warning);
+    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeGenerateDialog, email_row);
+    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeGenerateDialog, comment_row);
+    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeGenerateDialog, algorithm_row);
+    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeGenerateDialog, bits_spin);
+    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeGenerateDialog, expires_date_row);
     gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeGenerateDialog, expires_datepicker);
-    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeGenerateDialog, expires_check);
+    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeGenerateDialog, expires_switch);
     gtk_widget_class_bind_template_callback (widget_class, on_gpgme_generate_entry_changed);
-    gtk_widget_class_bind_template_callback (widget_class, on_gpgme_generate_expires_toggled);
-    gtk_widget_class_bind_template_callback (widget_class, on_gpgme_generate_algorithm_changed);
+    gtk_widget_class_bind_template_callback (widget_class, on_algo_row_notify_selected);
 
     dialog_class->response = seahorse_gpgme_generate_dialog_response;
 }
diff --git a/pgp/seahorse-gpgme-generate-dialog.ui b/pgp/seahorse-gpgme-generate-dialog.ui
index 35380ce3..ef243540 100644
--- a/pgp/seahorse-gpgme-generate-dialog.ui
+++ b/pgp/seahorse-gpgme-generate-dialog.ui
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
   <object class="GtkAdjustment" id="adjustment1">
     <property name="lower">512</property>
     <property name="upper">8192</property>
@@ -8,272 +7,104 @@
     <property name="step_increment">512</property>
     <property name="page_increment">1</property>
   </object>
-  <object class="GtkListStore" id="model1">
-    <columns>
-      <!-- column-name gchararray -->
-      <column type="gchararray"/>
-    </columns>
-  </object>
+
   <template class="SeahorseGpgmeGenerateDialog" parent="GtkDialog">
     <property name="title" translatable="yes">New PGP key</property>
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="border_width">5</property>
     <property name="resizable">False</property>
     <property name="modal">True</property>
-    <child internal-child="vbox">
+    <child internal-child="content_area">
       <object class="GtkBox">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="orientation">vertical</property>
-        <property name="spacing">2</property>
+        <property name="spacing">12</property>
+        <property name="margin-top">12</property>
+        <property name="margin-bottom">12</property>
+        <property name="margin-start">12</property>
+        <property name="margin-end">12</property>
+        <child>
+          <object class="GtkLabel">
+            <property name="xalign">0</property>
+            <property name="yalign">0</property>
+            <property name="label" translatable="yes">A PGP key allows you to encrypt email or files to 
other people.</property>
+          </object>
+        </child>
         <child>
-          <object class="GtkBox">
-            <property name="visible">True</property>
-            <property name="orientation">vertical</property>
-            <property name="can_focus">False</property>
-            <property name="spacing">12</property>
-            <property name="margin">12</property>
+          <object class="AdwPreferencesGroup">
             <child>
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="xalign">0</property>
-                <property name="yalign">0</property>
-                <property name="label" translatable="yes">A PGP key allows you to encrypt email or files to 
other people.</property>
+              <object class="AdwEntryRow" id="name_row">
+                <property name="title" translatable="yes" comments="Full name of the key, usually the name 
of the user.">Full _Name</property>
+                <property name="use-underline">True</property>
+                <property name="input-purpose">name</property>
+                <signal name="changed" handler="on_gpgme_generate_entry_changed"/>
+                <child type="suffix">
+                  <object class="GtkImage" id="name_row_warning">
+                    <property name="icon-name">dialog-warning-symbolic</property>
+                    <property name="tooltip-text" translatable="yes">Name must be at least 5 characters 
long.</property>
+                  </object>
+                </child>
               </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">0</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkGrid">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="column_spacing">12</property>
-                <property name="row_spacing">6</property>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">end</property>
-                    <property name="label" translatable="yes" comments="Full name of the key, usually the 
name of the user.">Full _Name</property>
-                    <property name="use_underline">True</property>
-                    <property name="mnemonic_widget">name_entry</property>
-                    <style>
-                      <class name="dim-label"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="top_attach">0</property>
-                    <property name="left_attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkEntry" id="name_entry">
-                    <property name="width_request">180</property>
-                    <property name="visible">True</property>
-                    <property name="input-purpose">name</property>
-                    <property name="activates_default">True</property>
-                    <signal name="changed" handler="on_gpgme_generate_entry_changed" swapped="no"/>
-                  </object>
-                  <packing>
-                    <property name="top_attach">0</property>
-                    <property name="left_attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="email_label">
-                    <property name="visible">True</property>
-                    <property name="halign">end</property>
-                    <property name="can_focus">False</property>
-                    <property name="label" translatable="yes">_Email Address</property>
-                    <property name="use_underline">True</property>
-                    <property name="mnemonic_widget">email_entry</property>
-                    <style>
-                      <class name="dim-label"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="top_attach">1</property>
-                    <property name="left_attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkEntry" id="email_entry">
-                    <property name="width_request">180</property>
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="input-purpose">email</property>
-                    <property name="activates_default">True</property>
-                    <signal name="changed" handler="on_gpgme_generate_entry_changed" swapped="no"/>
-                  </object>
-                  <packing>
-                    <property name="top_attach">1</property>
-                    <property name="left_attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="margin_top">18</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="label" translatable="yes">_Advanced key options</property>
-                    <property name="use_underline">True</property>
-                  </object>
-                  <packing>
-                    <property name="top_attach">2</property>
-                    <property name="left_attach">0</property>
-                    <property name="width">2</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="halign">end</property>
-                    <property name="can_focus">False</property>
-                    <property name="label" translatable="yes">_Comment</property>
-                    <property name="use_underline">True</property>
-                    <property name="mnemonic_widget">comment_entry</property>
-                    <style>
-                      <class name="dim-label"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="top_attach">3</property>
-                    <property name="left_attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkEntry" id="comment_entry">
-                    <property name="width_request">180</property>
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="invisible_char">●</property>
-                    <property name="activates_default">True</property>
-                    <signal name="changed" handler="on_gpgme_generate_entry_changed" swapped="no"/>
-                  </object>
-                  <packing>
-                    <property name="top_attach">3</property>
-                    <property name="left_attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="label49">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">end</property>
-                    <property name="label" translatable="yes">Encryption _Type</property>
-                    <property name="use_underline">True</property>
-                    <style>
-                      <class name="dim-label"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="top_attach">4</property>
-                    <property name="left_attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkComboBoxText" id="algorithm_choice">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="entry_text_column">0</property>
-                    <property name="id_column">1</property>
-                    <signal name="changed" handler="on_gpgme_generate_algorithm_changed" swapped="no"/>
-                  </object>
-                  <packing>
-                    <property name="top_attach">4</property>
-                    <property name="left_attach">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="label50">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">end</property>
-                    <property name="label" translatable="yes">Key _Strength (bits)</property>
-                    <property name="use_underline">True</property>
-                    <style>
-                      <class name="dim-label"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="top_attach">5</property>
-                    <property name="left_attach">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkSpinButton" id="bits_entry">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="halign">start</property>
+              <object class="AdwEntryRow" id="email_row">
+                <property name="title" translatable="yes">_Email Address</property>
+                <property name="use-underline">True</property>
+                <property name="input-purpose">email</property>
+                <signal name="changed" handler="on_gpgme_generate_entry_changed"/>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="AdwPreferencesGroup">
+            <property name="title" translatable="yes">Advanced key options</property>
+            <child>
+              <object class="AdwEntryRow" id="comment_row">
+                <property name="title" translatable="yes">_Comment</property>
+                <property name="use-underline">True</property>
+                <signal name="changed" handler="on_gpgme_generate_entry_changed"/>
+              </object>
+            </child>
+            <child>
+              <object class="AdwComboRow" id="algorithm_row">
+                <property name="title" translatable="yes">Encryption _Type</property>
+                <property name="use-underline">True</property>
+                <signal name="notify::selected" handler="on_algo_row_notify_selected"/>
+              </object>
+            </child>
+            <child>
+              <object class="AdwActionRow" id="bits_row">
+                <property name="title" translatable="yes">Key _Strength (bits)</property>
+                <property name="use-underline">True</property>
+                <child type="suffix">
+                  <object class="GtkSpinButton" id="bits_spin">
+                    <property name="valign">center</property>
                     <property name="adjustment">adjustment1</property>
                     <property name="climb_rate">1</property>
                     <property name="numeric">True</property>
                   </object>
-                  <packing>
-                    <property name="top_attach">5</property>
-                    <property name="left_attach">1</property>
-                  </packing>
                 </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">end</property>
-                    <property name="label" translatable="yes">E_xpiration Date</property>
-                    <property name="use_underline">True</property>
-                    <property name="mnemonic-widget">expires_datepicker</property>
-                    <style>
-                      <class name="dim-label"/>
-                    </style>
+              </object>
+            </child>
+            <child>
+              <object class="AdwActionRow">
+                <property name="title" translatable="yes">Ne_ver Expires</property>
+                <property name="use-underline">True</property>
+                <child type="suffix">
+                  <object class="GtkSwitch" id="expires_switch">
+                    <property name="valign">center</property>
+                    <property name="active">True</property>
                   </object>
-                  <packing>
-                    <property name="top_attach">6</property>
-                    <property name="left_attach">0</property>
-                  </packing>
                 </child>
-                <child>
-                  <object class="GtkBox">
-                    <property name="visible">True</property>
-                    <property name="orientation">horizontal</property>
-                    <property name="can_focus">False</property>
-                    <property name="spacing">12</property>
-                    <child>
-                      <object class="SeahorseDatePicker" id="expires_datepicker">
-                        <property name="visible">True</property>
-                        <property name="sensitive">False</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkCheckButton" id="expires_check">
-                        <property name="label" translatable="yes">Ne_ver Expires</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="receives_default">False</property>
-                        <property name="use_action_appearance">False</property>
-                        <property name="use_underline">True</property>
-                        <property name="active">True</property>
-                        <property name="draw_indicator">True</property>
-                        <signal name="toggled" handler="on_gpgme_generate_expires_toggled" swapped="no"/>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">False</property>
-                        <property name="pack_type">end</property>
-                        <property name="position">1</property>
-                      </packing>
-                    </child>
+              </object>
+            </child>
+            <child>
+              <object class="AdwActionRow" id="expires_date_row">
+                <property name="title" translatable="yes">E_xpiration Date</property>
+                <property name="use-underline">True</property>
+                <property name="sensitive" bind-source="expires_switch" bind-property="active" 
bind-flags="bidirectional|sync-create|invert-boolean" />
+                <child type="suffix">
+                  <object class="SeahorseDatePicker" id="expires_datepicker">
+                    <property name="valign">center</property>
                   </object>
-                  <packing>
-                    <property name="top_attach">6</property>
-                    <property name="left_attach">1</property>
-                  </packing>
                 </child>
               </object>
             </child>
@@ -281,25 +112,17 @@
         </child>
       </object>
     </child>
+
     <child type="action">
       <object class="GtkButton" id="cancelbutton1">
         <property name="label" translatable="yes">_Cancel</property>
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="can_default">True</property>
-        <property name="receives_default">False</property>
         <property name="use_underline">True</property>
       </object>
     </child>
     <child type="action">
       <object class="GtkButton" id="ok">
         <property name="label" translatable="yes">C_reate</property>
-        <property name="visible">True</property>
         <property name="sensitive">False</property>
-        <property name="can_focus">True</property>
-        <property name="can_default">True</property>
-        <property name="has_default">True</property>
-        <property name="receives_default">False</property>
         <property name="tooltip_text" translatable="yes">Generate a new key</property>
         <property name="use_underline">True</property>
         <style>
diff --git a/pgp/seahorse-gpgme-key-deleter.c b/pgp/seahorse-gpgme-key-deleter.c
index 61d52c31..39226001 100644
--- a/pgp/seahorse-gpgme-key-deleter.c
+++ b/pgp/seahorse-gpgme-key-deleter.c
@@ -62,7 +62,7 @@ seahorse_gpgme_key_deleter_finalize (GObject *obj)
     G_OBJECT_CLASS (seahorse_gpgme_key_deleter_parent_class)->finalize (obj);
 }
 
-static GtkDialog *
+static GtkWindow *
 seahorse_gpgme_key_deleter_create_confirm (SeahorseDeleter *deleter,
                                            GtkWindow *parent)
 {
diff --git a/pgp/seahorse-gpgme-key-op.c b/pgp/seahorse-gpgme-key-op.c
index 52aaac1a..2b56f746 100644
--- a/pgp/seahorse-gpgme-key-op.c
+++ b/pgp/seahorse-gpgme-key-op.c
@@ -2357,12 +2357,6 @@ photoid_load_transit (unsigned int   current_state,
 
             g_unlink (parm->output_file);
 
-            /* Load a 'missing' icon */
-            if (!pixbuf) {
-                pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
-                                                   "gnome-unknown", 48, 0, NULL);
-            }
-
             seahorse_pgp_photo_set_pixbuf (SEAHORSE_PGP_PHOTO (photo), pixbuf);
             g_object_unref (pixbuf);
         }
diff --git a/pgp/seahorse-gpgme-keyring.c b/pgp/seahorse-gpgme-keyring.c
index eceb7107..8097cab3 100644
--- a/pgp/seahorse-gpgme-keyring.c
+++ b/pgp/seahorse-gpgme-keyring.c
@@ -51,7 +51,7 @@
 struct _SeahorseGpgmeKeyring {
     GObject parent_instance;
 
-    GHashTable *keys;
+    GPtrArray *keys;
     unsigned int scheduled_refresh;         /* Source for refresh timeout */
     GFileMonitor *monitor_handle;           /* For monitoring the .gnupg directory */
     GList *orphan_secret;                   /* Orphan secret keys */
@@ -62,22 +62,20 @@ enum {
     PROP_0,
     PROP_LABEL,
     PROP_DESCRIPTION,
-    PROP_ICON,
     PROP_CATEGORY,
     PROP_URI,
     PROP_ACTIONS,
     PROP_ACTION_PREFIX,
     PROP_MENU_MODEL,
-    PROP_SHOW_IF_EMPTY,
     N_PROPS
 };
 
 static void     seahorse_gpgme_keyring_place_iface        (SeahorsePlaceIface *iface);
 
-static void     seahorse_gpgme_keyring_collection_iface   (GcrCollectionIface *iface);
+static void     seahorse_gpgme_keyring_list_model_iface   (GListModelInterface *iface);
 
 G_DEFINE_TYPE_WITH_CODE (SeahorseGpgmeKeyring, seahorse_gpgme_keyring, G_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (GCR_TYPE_COLLECTION, 
seahorse_gpgme_keyring_collection_iface);
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, seahorse_gpgme_keyring_list_model_iface);
                          G_IMPLEMENT_INTERFACE (SEAHORSE_TYPE_PLACE, seahorse_gpgme_keyring_place_iface);
 );
 
@@ -130,6 +128,8 @@ passphrase_get (void       *hook,
                                                      NULL,
                                                      confirm);
 
+       // XXX
+#if 0
     switch (gtk_dialog_run (GTK_DIALOG (dialog))) {
     case GTK_RESPONSE_ACCEPT:
         pass = seahorse_passphrase_prompt_get_text (dialog);
@@ -142,6 +142,7 @@ passphrase_get (void       *hook,
     };
 
     gtk_widget_destroy (GTK_WIDGET (dialog));
+#endif
     return err;
 }
 
@@ -229,8 +230,8 @@ add_key_to_context (SeahorseGpgmeKeyring *self,
         pkey = seahorse_gpgme_key_new (SEAHORSE_PLACE (self), key, NULL);
 
     /* Add to context */
-    g_hash_table_insert (self->keys, g_strdup (keyid), pkey);
-    gcr_collection_emit_added (GCR_COLLECTION (self), G_OBJECT (pkey));
+    g_ptr_array_add (self->keys, pkey);
+    g_list_model_items_changed (G_LIST_MODEL (self), self->keys->len - 1, 0, 1);
 
     return pkey;
 }
@@ -243,7 +244,7 @@ remove_key (SeahorseGpgmeKeyring *self,
 {
     SeahorseGpgmeKey *key;
 
-    key = g_hash_table_lookup (self->keys, keyid);
+    key = seahorse_gpgme_keyring_lookup (self, keyid);
     if (key != NULL)
         seahorse_gpgme_keyring_remove_key (self, key);
 }
@@ -255,7 +256,6 @@ on_idle_list_batch_of_keys (void *data)
     GTask *task = G_TASK (data);
     keyring_list_closure *closure = g_task_get_task_data (task);
     SeahorseGpgmeKey *pkey;
-    GHashTableIter iter;
     gpgme_key_t key;
     unsigned int batch;
     g_autofree char *detail = NULL;
@@ -266,6 +266,7 @@ on_idle_list_batch_of_keys (void *data)
 
     while (batch-- > 0) {
         if (!GPG_IS_OK (gpgme_op_keylist_next (closure->gctx, &key))) {
+            GHashTableIter iter;
 
             gpgme_op_keylist_end (closure->gctx);
 
@@ -328,7 +329,6 @@ seahorse_gpgme_keyring_list_async (SeahorseGpgmeKeyring *self,
     keyring_list_closure *closure;
     SeahorseObject *object;
     gpgme_error_t gerr = 0;
-    GHashTableIter iter;
     g_autoptr(GError) error = NULL;
 
     task = g_task_new (self, cancellable, callback, user_data);
@@ -358,16 +358,19 @@ seahorse_gpgme_keyring_list_async (SeahorseGpgmeKeyring *self,
 
     /* Loading all the keys? */
     if (patterns == NULL) {
-        char *keyid;
-
         closure->checks = g_hash_table_new_full (seahorse_pgp_keyid_hash,
                                                  seahorse_pgp_keyid_equal,
                                                  g_free, NULL);
-        g_hash_table_iter_init (&iter, self->keys);
-        while (g_hash_table_iter_next (&iter, (void **) &keyid, (void **) &object)) {
-            if ((secret && seahorse_object_get_usage (object) == SEAHORSE_USAGE_PRIVATE_KEY) ||
-                (!secret && seahorse_object_get_usage (object) == SEAHORSE_USAGE_PUBLIC_KEY)) {
-                keyid = g_strdup (keyid);
+        for (unsigned int i = 0; i < self->keys->len; i++) {
+            SeahorsePgpKey *key = g_ptr_array_index (self->keys, i);
+            SeahorseUsage usage;
+
+            usage = seahorse_object_get_usage (SEAHORSE_OBJECT (key));
+            if ((secret && usage == SEAHORSE_USAGE_PRIVATE_KEY) ||
+                (!secret && usage == SEAHORSE_USAGE_PUBLIC_KEY)) {
+                char *keyid;
+
+                keyid = (char*) seahorse_pgp_key_get_keyid (key);
                 g_hash_table_insert (closure->checks, keyid, keyid);
             }
         }
@@ -511,7 +514,15 @@ seahorse_gpgme_keyring_lookup (SeahorseGpgmeKeyring *self,
     g_return_val_if_fail (SEAHORSE_IS_GPGME_KEYRING (self), NULL);
     g_return_val_if_fail (keyid != NULL, NULL);
 
-    return g_hash_table_lookup (self->keys, keyid);
+    for (unsigned int i = 0; i < self->keys->len; i++) {
+        SeahorseGpgmeKey *pkey = g_ptr_array_index (self->keys, i);
+        const char *pkeyid;
+
+        pkeyid = seahorse_pgp_key_get_keyid (SEAHORSE_PGP_KEY (pkey));
+        if (seahorse_pgp_keyid_equal (keyid, pkeyid))
+            return pkey;
+    }
+    return NULL;
 }
 
 void
@@ -519,16 +530,19 @@ seahorse_gpgme_keyring_remove_key (SeahorseGpgmeKeyring *self,
                                    SeahorseGpgmeKey *key)
 {
     const char *keyid;
+    gboolean found;
+    unsigned int pos;
 
     g_return_if_fail (SEAHORSE_IS_GPGME_KEYRING (self));
     g_return_if_fail (SEAHORSE_GPGME_IS_KEY (key));
 
     keyid = seahorse_pgp_key_get_keyid (SEAHORSE_PGP_KEY (key));
-    g_return_if_fail (g_hash_table_lookup (self->keys, keyid) == key);
+    found = g_ptr_array_find (self->keys, key, &pos);
+    g_return_if_fail (found);
 
     g_object_ref (key);
-    g_hash_table_remove (self->keys, keyid);
-    gcr_collection_emit_removed (GCR_COLLECTION (self), G_OBJECT (key));
+    g_ptr_array_remove_index (self->keys, pos);
+    g_list_model_items_changed (G_LIST_MODEL (self), pos, 1, 0);
     g_object_unref (key);
 
 }
@@ -583,16 +597,16 @@ on_keyring_import_loaded (GObject      *source,
     g_autoptr(GList) keys = NULL;
 
     for (unsigned int i = 0; closure->patterns[i] != NULL; i++) {
-        SeahorseObject *object;
+        SeahorseGpgmeKey *key;
 
-        object = g_hash_table_lookup (closure->keyring->keys, closure->patterns[i]);
-        if (object == NULL) {
+        key = seahorse_gpgme_keyring_lookup (closure->keyring, closure->patterns[i]);
+        if (key == NULL) {
             g_warning ("imported key but then couldn't find it in keyring: %s",
                        closure->patterns[i]);
             continue;
         }
 
-        keys = g_list_prepend (keys, object);
+        keys = g_list_prepend (keys, key);
     }
 
     seahorse_progress_end (g_task_get_cancellable (task), task);
@@ -747,9 +761,7 @@ seahorse_gpgme_keyring_init (SeahorseGpgmeKeyring *self)
     g_autoptr(GFile) file = NULL;
     g_autoptr(GError) err = NULL;
 
-    self->keys = g_hash_table_new_full (seahorse_pgp_keyid_hash,
-                                        seahorse_pgp_keyid_equal,
-                                        g_free, g_object_unref);
+    self->keys = g_ptr_array_new_with_free_func (g_object_unref);
 
     self->scheduled_refresh = 0;
     self->monitor_handle = NULL;
@@ -796,12 +808,6 @@ seahorse_gpgme_keyring_get_description (SeahorsePlace *place)
     return g_strdup (_("GnuPG: default keyring directory"));
 }
 
-static GIcon *
-seahorse_gpgme_keyring_get_icon (SeahorsePlace *place)
-{
-    return g_themed_icon_new (GCR_ICON_GNUPG);
-}
-
 static SeahorsePlaceCategory
 seahorse_gpgme_keyring_get_category (SeahorsePlace *place)
 {
@@ -835,12 +841,6 @@ seahorse_gpgme_keyring_get_uri (SeahorsePlace *place)
     return g_strdup ("gnupg://");
 }
 
-static gboolean
-seahorse_gpgme_keyring_get_show_if_empty (SeahorsePlace *place)
-{
-    return TRUE;
-}
-
 static void
 seahorse_gpgme_keyring_get_property (GObject     *obj,
                                      unsigned int prop_id,
@@ -856,9 +856,6 @@ seahorse_gpgme_keyring_get_property (GObject     *obj,
     case PROP_DESCRIPTION:
         g_value_take_string (value, seahorse_gpgme_keyring_get_description (place));
         break;
-    case PROP_ICON:
-        g_value_take_object (value, seahorse_gpgme_keyring_get_icon (place));
-        break;
     case PROP_CATEGORY:
         g_value_set_enum (value, seahorse_gpgme_keyring_get_category (place));
         break;
@@ -874,9 +871,6 @@ seahorse_gpgme_keyring_get_property (GObject     *obj,
     case PROP_MENU_MODEL:
         g_value_take_object (value, seahorse_gpgme_keyring_get_menu_model (place));
         break;
-    case PROP_SHOW_IF_EMPTY:
-        g_value_set_boolean (value, TRUE);
-        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
         break;
@@ -906,8 +900,6 @@ seahorse_gpgme_keyring_dispose (GObject *object)
 {
     SeahorseGpgmeKeyring *self = SEAHORSE_GPGME_KEYRING (object);
 
-    g_hash_table_remove_all (self->keys);
-
     cancel_scheduled_refresh (self);
     g_clear_object (&self->monitor_handle);
 
@@ -924,7 +916,7 @@ seahorse_gpgme_keyring_finalize (GObject *object)
     SeahorseGpgmeKeyring *self = SEAHORSE_GPGME_KEYRING (object);
 
     g_clear_object (&self->actions);
-    g_hash_table_destroy (self->keys);
+    g_ptr_array_unref (self->keys);
 
     /* All monitoring and scheduling should be done */
     g_assert (self->scheduled_refresh == 0);
@@ -953,12 +945,10 @@ seahorse_gpgme_keyring_class_init (SeahorseGpgmeKeyringClass *klass)
     g_object_class_override_property (gobject_class, PROP_LABEL, "label");
     g_object_class_override_property (gobject_class, PROP_DESCRIPTION, "description");
     g_object_class_override_property (gobject_class, PROP_URI, "uri");
-    g_object_class_override_property (gobject_class, PROP_ICON, "icon");
     g_object_class_override_property (gobject_class, PROP_CATEGORY, "category");
     g_object_class_override_property (gobject_class, PROP_ACTIONS, "actions");
     g_object_class_override_property (gobject_class, PROP_ACTION_PREFIX, "action-prefix");
     g_object_class_override_property (gobject_class, PROP_MENU_MODEL, "menu-model");
-    g_object_class_override_property (gobject_class, PROP_SHOW_IF_EMPTY, "show-if-empty");
 }
 
 static void
@@ -970,48 +960,42 @@ seahorse_gpgme_keyring_place_iface (SeahorsePlaceIface *iface)
     iface->get_action_prefix = seahorse_gpgme_keyring_get_action_prefix;
     iface->get_menu_model = seahorse_gpgme_keyring_get_menu_model;
     iface->get_description = seahorse_gpgme_keyring_get_description;
-    iface->get_icon = seahorse_gpgme_keyring_get_icon;
     iface->get_category = seahorse_gpgme_keyring_get_category;
     iface->get_label = seahorse_gpgme_keyring_get_label;
     iface->set_label = seahorse_gpgme_keyring_set_label;
     iface->get_uri = seahorse_gpgme_keyring_get_uri;
-    iface->get_show_if_empty = seahorse_gpgme_keyring_get_show_if_empty;
 }
 
-static unsigned int
-seahorse_gpgme_keyring_get_length (GcrCollection *collection)
+static GType
+seahorse_gpgme_keyring_get_item_type (GListModel *list)
 {
-    SeahorseGpgmeKeyring *self = SEAHORSE_GPGME_KEYRING (collection);
-    return g_hash_table_size (self->keys);
+    return SEAHORSE_GPGME_TYPE_KEY;
 }
 
-static GList *
-seahorse_gpgme_keyring_get_objects (GcrCollection *collection)
+static unsigned int
+seahorse_gpgme_keyring_get_n_items (GListModel *list)
 {
-    SeahorseGpgmeKeyring *self = SEAHORSE_GPGME_KEYRING (collection);
-    return g_hash_table_get_values (self->keys);
+    SeahorseGpgmeKeyring *self = SEAHORSE_GPGME_KEYRING (list);
+    return self->keys->len;
 }
 
-static gboolean
-seahorse_gpgme_keyring_contains (GcrCollection *collection,
-                                 GObject *object)
+static void *
+seahorse_gpgme_keyring_get_item (GListModel   *list,
+                                 unsigned int  index)
 {
-    SeahorseGpgmeKeyring *self = SEAHORSE_GPGME_KEYRING (collection);
-    const char *keyid;
-
-    if (!SEAHORSE_GPGME_IS_KEY (object))
-        return FALSE;
+    SeahorseGpgmeKeyring *self = SEAHORSE_GPGME_KEYRING (list);
 
-    keyid = seahorse_pgp_key_get_keyid (SEAHORSE_PGP_KEY (object));
-    return g_hash_table_lookup (self->keys, keyid) == object;
+    if (index >= self->keys->len)
+        return NULL;
+    return g_object_ref (g_ptr_array_index (self->keys, index));
 }
 
 static void
-seahorse_gpgme_keyring_collection_iface (GcrCollectionIface *iface)
+seahorse_gpgme_keyring_list_model_iface (GListModelInterface *iface)
 {
-    iface->get_objects = seahorse_gpgme_keyring_get_objects;
-    iface->get_length = seahorse_gpgme_keyring_get_length;
-    iface->contains = seahorse_gpgme_keyring_contains;
+    iface->get_item_type = seahorse_gpgme_keyring_get_item_type;
+    iface->get_n_items = seahorse_gpgme_keyring_get_n_items;
+    iface->get_item = seahorse_gpgme_keyring_get_item;
 }
 
 /**
diff --git a/pgp/seahorse-gpgme-photos.c b/pgp/seahorse-gpgme-photos.c
index 24bb517a..eb316b85 100644
--- a/pgp/seahorse-gpgme-photos.c
+++ b/pgp/seahorse-gpgme-photos.c
@@ -38,6 +38,8 @@
 #define LARGE_WIDTH      240
 #define LARGE_HEIGHT     288
 
+// XXX
+#if 0
 static gboolean
 calc_scale (int *width, int *height)
 {
@@ -238,98 +240,69 @@ add_image_files (GtkWidget *dialog)
     gtk_file_filter_add_pattern (filter, "*");
     gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
 }
-
+#endif
 
 gboolean
 seahorse_gpgme_photo_add (SeahorseGpgmeKey *pkey,
                           GtkWindow *parent,
-                          const gchar *path)
-{
-       gchar *filename = NULL;
-       gchar *tempfile = NULL;
-       GError *error = NULL;
-       gpgme_error_t gerr;
-       GtkWidget *chooser;
-       gboolean res = TRUE;
-
-       g_return_val_if_fail (SEAHORSE_GPGME_IS_KEY (pkey), FALSE);
-
-       if (NULL == path) {
-               chooser = gtk_file_chooser_dialog_new (_("Choose Photo to Add to Key"), parent,
-                                                     GTK_FILE_CHOOSER_ACTION_OPEN,
-                                                     _("_Cancel"), GTK_RESPONSE_CANCEL,
-                                                     _("_Open"), GTK_RESPONSE_ACCEPT,
-                                                     NULL);
-
-               gtk_dialog_set_default_response (GTK_DIALOG (chooser), GTK_RESPONSE_ACCEPT);
-               gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (chooser), TRUE);
-               add_image_files (chooser);
-
-               if (gtk_dialog_run (GTK_DIALOG (chooser)) == GTK_RESPONSE_ACCEPT)
-                       filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (chooser));
-
-               gtk_widget_destroy (chooser);
-
-               if (!filename)
-                       return FALSE;
-       } else {
-               filename = g_strdup (path);
-       }
-
-       if (!prepare_photo_id (parent, filename, &tempfile, &error)) {
-               seahorse_util_handle_error (&error, NULL, _("Couldn’t prepare photo"));
-               return FALSE;
-       }
-
-       gerr = seahorse_gpgme_key_op_photo_add (pkey, tempfile ? tempfile : filename);
-       if (!GPG_IS_OK (gerr)) {
-
-               /* A special error value set by seahorse_key_op_photoid_add to
-                  denote an invalid format file */
-               if (gerr == GPG_E (GPG_ERR_USER_1))
-                       seahorse_util_show_error (NULL, _("Couldn’t add photo"),
-                                                 _("The file could not be loaded. It may be in an invalid 
format."));
-               else
-                       seahorse_gpgme_handle_error (gerr, _("Couldn’t add photo"));
-               res = FALSE;
-       }
-
-       g_free (filename);
-       if (tempfile) {
-               unlink (tempfile);
-               g_free (tempfile);
-       }
-
-       return res;
-}
-
-gboolean
-seahorse_gpgme_photo_delete (SeahorseGpgmePhoto *photo, GtkWindow *parent)
+                          const char *path)
 {
+#if 0
+    g_autofree char *filename = NULL;
+    char *tempfile = NULL;
+    GError *error = NULL;
     gpgme_error_t gerr;
-    GtkWidget *dlg;
-    int response;
+    GtkWidget *chooser;
+    gboolean res = TRUE;
 
-    g_return_val_if_fail (SEAHORSE_IS_GPGME_PHOTO (photo), FALSE);
+    g_return_val_if_fail (SEAHORSE_GPGME_IS_KEY (pkey), FALSE);
 
-    dlg = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
-                                  GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
-                                  _("Are you sure you want to remove the current photo from your key?"));
+    if (NULL == path) {
+        chooser = gtk_file_chooser_dialog_new (_("Choose Photo to Add to Key"), parent,
+                                              GTK_FILE_CHOOSER_ACTION_OPEN,
+                                              _("_Cancel"), GTK_RESPONSE_CANCEL,
+                                              _("_Open"), GTK_RESPONSE_ACCEPT,
+                                              NULL);
 
-    gtk_dialog_add_button (GTK_DIALOG (dlg), _("_Delete"), GTK_RESPONSE_ACCEPT);
-    gtk_dialog_add_button (GTK_DIALOG (dlg), _("_Cancel"), GTK_RESPONSE_REJECT);
+        gtk_dialog_set_default_response (GTK_DIALOG (chooser), GTK_RESPONSE_ACCEPT);
+        gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (chooser), TRUE);
+        add_image_files (chooser);
 
-    response = gtk_dialog_run (GTK_DIALOG (dlg));
-    gtk_widget_destroy (dlg);
+        if (gtk_dialog_run (GTK_DIALOG (chooser)) == GTK_RESPONSE_ACCEPT)
+            filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (chooser));
 
-    if (response != GTK_RESPONSE_ACCEPT)
+        gtk_widget_destroy (chooser);
+
+        if (!filename)
+            return FALSE;
+    } else {
+        filename = g_strdup (path);
+    }
+
+    if (!prepare_photo_id (parent, filename, &tempfile, &error)) {
+        seahorse_util_handle_error (&error, NULL, _("Couldn’t prepare photo"));
         return FALSE;
+    }
 
-    gerr = seahorse_gpgme_key_op_photo_delete (photo);
+    gerr = seahorse_gpgme_key_op_photo_add (pkey, tempfile ? tempfile : filename);
     if (!GPG_IS_OK (gerr)) {
-           seahorse_gpgme_handle_error (gerr, _("Couldn’t delete photo"));
-        return FALSE;
+
+        /* A special error value set by seahorse_key_op_photoid_add to
+           denote an invalid format file */
+        if (gerr == GPG_E (GPG_ERR_USER_1))
+            seahorse_util_show_error (NULL, _("Couldn’t add photo"),
+                                      _("The file could not be loaded. It may be in an invalid format."));
+        else
+            seahorse_gpgme_handle_error (gerr, _("Couldn’t add photo"));
+        res = FALSE;
     }
 
-    return TRUE;
+    if (tempfile) {
+        unlink (tempfile);
+        g_free (tempfile);
+    }
+
+    return res;
+#endif
+       return FALSE;
 }
diff --git a/pgp/seahorse-gpgme-revoke-dialog.c b/pgp/seahorse-gpgme-revoke-dialog.c
index aa98c832..193e4219 100644
--- a/pgp/seahorse-gpgme-revoke-dialog.c
+++ b/pgp/seahorse-gpgme-revoke-dialog.c
@@ -42,7 +42,7 @@ struct _SeahorseGpgmeRevokeDialog {
     SeahorseGpgmeSubkey *subkey;
 
     GtkWidget *reason_combo;
-    GtkWidget *description_entry;
+    GtkWidget *description_row;
 };
 
 enum {
@@ -74,7 +74,7 @@ on_gpgme_revoke_ok_clicked (GtkButton *button,
     reason = g_value_get_int (&value);
     g_value_unset (&value);
 
-    description = gtk_entry_get_text (GTK_ENTRY (self->description_entry));
+    description = gtk_editable_get_text (GTK_EDITABLE (self->description_row));
 
     err = seahorse_gpgme_key_op_revoke_subkey (self->subkey, reason, description);
     if (!GPG_IS_OK (err))
@@ -222,7 +222,7 @@ seahorse_gpgme_revoke_dialog_class_init (SeahorseGpgmeRevokeDialogClass *klass)
                                           reason_combo);
     gtk_widget_class_bind_template_child (widget_class,
                                           SeahorseGpgmeRevokeDialog,
-                                          description_entry);
+                                          description_row);
     gtk_widget_class_bind_template_callback (widget_class,
                                              on_gpgme_revoke_ok_clicked);
 }
diff --git a/pgp/seahorse-gpgme-revoke-dialog.ui b/pgp/seahorse-gpgme-revoke-dialog.ui
index d126328f..c1377bc7 100644
--- a/pgp/seahorse-gpgme-revoke-dialog.ui
+++ b/pgp/seahorse-gpgme-revoke-dialog.ui
@@ -1,100 +1,52 @@
 <?xml version="1.0"?>
 <interface>
-  <requires lib="gtk+" version="3.24"/>
   <template class="SeahorseGpgmeRevokeDialog" parent="GtkDialog">
-    <property name="visible">True</property>
-    <property name="border_width">5</property>
-    <child internal-child="vbox">
+
+    <child internal-child="content_area">
       <object class="GtkBox">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
         <child>
-          <object class="GtkGrid">
-            <property name="visible">True</property>
-            <property name="margin">6</property>
-            <property name="column_spacing">12</property>
-            <property name="row_spacing">6</property>
+          <object class="AdwPreferencesGroup">
             <child>
-              <object class="GtkLabel" id="reason_label">
-                <property name="visible">True</property>
-                <property name="xalign">0</property>
-                <property name="label" translatable="yes">_Reason:</property>
-                <property name="tooltip_text" translatable="yes">Reason for revoking the key</property>
-                <property name="use_underline">True</property>
-                <property name="mnemonic_widget">reason_combo</property>
+              <object class="AdwActionRow">
+                <property name="title" translatable="yes">_Reason</property>
+                <property name="use-underline">True</property>
+                <property name="tooltip-text" translatable="yes">Reason for revoking the key</property>
+                <child>
+                  <object class="GtkComboBox" id="reason_combo">
+                  </object>
+                </child>
               </object>
-              <packing>
-                <property name="top_attach">0</property>
-                <property name="left_attach">0</property>
-              </packing>
             </child>
             <child>
-              <object class="GtkComboBox" id="reason_combo">
-                <property name="visible">True</property>
+              <object class="AdwEntryRow" id="description_row">
+                <property name="title" translatable="yes">_Description</property>
+                <property name="use-underline">True</property>
+                <property name="tooltip-text" translatable="yes">Optional description of 
revocation</property>
               </object>
-              <packing>
-                <property name="top_attach">0</property>
-                <property name="left_attach">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel" id="desription_label">
-                <property name="visible">True</property>
-                <property name="xalign">0</property>
-                <property name="label" translatable="yes">_Description:</property>
-                <property name="use_underline">True</property>
-                <property name="mnemonic_widget">description</property>
-              </object>
-              <packing>
-                <property name="top_attach">1</property>
-                <property name="left_attach">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkEntry" id="description">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="tooltip_text" translatable="yes">Optional description of 
revocation</property>
-                <property name="activates_default">True</property>
-              </object>
-              <packing>
-                <property name="left_attach">1</property>
-                <property name="top_attach">1</property>
-              </packing>
             </child>
           </object>
-          <packing>
-            <property name="position">1</property>
-          </packing>
         </child>
       </object>
     </child>
+
     <child type="action">
       <object class="GtkButton" id="cancelbutton">
         <property name="label" translatable="yes">_Cancel</property>
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="can_default">True</property>
-        <property name="receives_default">False</property>
-        <signal name="clicked" handler="on_widget_closed"/>
+        <property name="use-underline">True</property>
       </object>
     </child>
     <child type="action">
       <object class="GtkButton" id="okbutton">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="can_default">True</property>
-        <property name="has_default">True</property>
-        <property name="receives_default">False</property>
-        <property name="tooltip_text" translatable="yes">Revoke key</property>
+        <property name="tooltip-text" translatable="yes">Revoke key</property>
         <property name="label" translatable="yes">Re_voke</property>
-        <property name="use_underline">True</property>
+        <property name="use-underline">True</property>
         <signal name="clicked" handler="on_gpgme_revoke_ok_clicked"/>
       </object>
     </child>
     <action-widgets>
       <action-widget response="-6">cancelbutton</action-widget>
-      <action-widget response="-5">okbutton</action-widget>
+      <action-widget response="-5" default="true">okbutton</action-widget>
     </action-widgets>
   </template>
 </interface>
diff --git a/pgp/seahorse-gpgme-sign-dialog.c b/pgp/seahorse-gpgme-sign-dialog.c
index 1059f29e..912653fc 100644
--- a/pgp/seahorse-gpgme-sign-dialog.c
+++ b/pgp/seahorse-gpgme-sign-dialog.c
@@ -20,7 +20,6 @@
 
 #include "config.h"
 
-#include "seahorse-combo-keys.h"
 #include "seahorse-gpgme-sign-dialog.h"
 #include "seahorse-gpgme-key-op.h"
 #include "seahorse-pgp-keysets.h"
@@ -49,7 +48,7 @@ struct _SeahorseGpgmeSignDialog {
     GtkWidget *sign_option_local;
     GtkWidget *sign_option_revocable;
 
-    GtkWidget *signer_select;
+    GtkWidget *signer_row;
     GtkWidget *signer_frame;
 };
 
@@ -64,14 +63,16 @@ G_DEFINE_TYPE (SeahorseGpgmeSignDialog, seahorse_gpgme_sign_dialog, GTK_TYPE_DIA
 
 
 static void
-on_collection_changed (GcrCollection *collection,
-                       GObject *object,
+on_collection_changed (GListModel *model,
+                       unsigned int position,
+                       unsigned int removed,
+                       unsigned int added,
                        gpointer user_data)
 {
     SeahorseGpgmeSignDialog *self = SEAHORSE_GPGME_SIGN_DIALOG (user_data);
 
     gtk_widget_set_visible (self->signer_frame,
-                            gcr_collection_get_length (collection) > 1);
+                            g_list_model_get_n_items (model) > 1);
 }
 
 static void
@@ -82,13 +83,13 @@ on_gpgme_sign_choice_toggled (GtkToggleButton *toggle,
 
     /* Figure out choice */
     gtk_widget_set_visible (self->sign_display_not,
-                            gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->sign_choice_not)));
+                            gtk_check_button_get_active (GTK_CHECK_BUTTON (self->sign_choice_not)));
 
     gtk_widget_set_visible (self->sign_display_casual,
-                            gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->sign_choice_casual)));
+                            gtk_check_button_get_active (GTK_CHECK_BUTTON (self->sign_choice_casual)));
 
     gtk_widget_set_visible (self->sign_display_careful,
-                            gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->sign_choice_careful)));
+                            gtk_check_button_get_active (GTK_CHECK_BUTTON (self->sign_choice_careful)));
 }
 
 static void
@@ -105,13 +106,13 @@ seahorse_gpgme_sign_dialog_response (GtkDialog *dialog, int response)
 
     /* Figure out choice */
     check = SIGN_CHECK_NO_ANSWER;
-    if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->sign_choice_not)))
+    if (gtk_check_button_get_active (GTK_CHECK_BUTTON (self->sign_choice_not)))
         check = SIGN_CHECK_NONE;
     else {
-        if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->sign_choice_casual)))
+        if (gtk_check_button_get_active (GTK_CHECK_BUTTON (self->sign_choice_casual)))
             check = SIGN_CHECK_CASUAL;
         else {
-            if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->sign_choice_careful)))
+            if (gtk_check_button_get_active (GTK_CHECK_BUTTON (self->sign_choice_careful)))
                 check = SIGN_CHECK_CAREFUL;
         }
     }
@@ -125,7 +126,7 @@ seahorse_gpgme_sign_dialog_response (GtkDialog *dialog, int response)
         options |= SIGN_NO_REVOKE;
 
     /* Signer */
-    signer = seahorse_combo_keys_get_active (GTK_COMBO_BOX (self->signer_select));
+    signer = adw_combo_row_get_selected_item (ADW_COMBO_ROW (self->signer_row));
 
     g_assert (!signer || (SEAHORSE_GPGME_IS_KEY (signer) &&
                           seahorse_object_get_usage (SEAHORSE_OBJECT (signer)) == 
SEAHORSE_USAGE_PRIVATE_KEY));
@@ -145,8 +146,8 @@ seahorse_gpgme_sign_dialog_response (GtkDialog *dialog, int response)
             w = gtk_message_dialog_new (GTK_WINDOW (self), GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, 
GTK_BUTTONS_CLOSE,
                                         _("This key was already signed by\n“%s”"),
                                         seahorse_object_get_label (SEAHORSE_OBJECT (signer)));
-            gtk_dialog_run (GTK_DIALOG (w));
-            gtk_widget_destroy (w);
+            g_signal_connect (dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
+            gtk_window_present (GTK_WINDOW (dialog));
         } else
             seahorse_gpgme_handle_error (err, _("Couldn’t sign key"));
     }
@@ -203,13 +204,11 @@ static void
 seahorse_gpgme_sign_dialog_constructed (GObject *obj)
 {
     SeahorseGpgmeSignDialog *self = SEAHORSE_GPGME_SIGN_DIALOG (obj);
-    g_autofree char *userid = NULL;
 
     G_OBJECT_CLASS (seahorse_gpgme_sign_dialog_parent_class)->constructed (obj);
 
-    userid = g_markup_printf_escaped("<i>%s</i>",
-                                     seahorse_object_get_label (self->to_sign));
-    gtk_label_set_markup (GTK_LABEL (self->to_sign_name_label), userid);
+    gtk_label_set_text (GTK_LABEL (self->to_sign_name_label),
+                        seahorse_object_get_label (self->to_sign));
 
     /* Initial choice */
     on_gpgme_sign_choice_toggled (NULL, self);
@@ -258,7 +257,7 @@ seahorse_gpgme_sign_dialog_class_init (SeahorseGpgmeSignDialogClass *klass)
     gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeSignDialog, sign_option_local);
     gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeSignDialog, sign_option_revocable);
     gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeSignDialog, signer_frame);
-    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeSignDialog, signer_select);
+    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeSignDialog, signer_row);
     gtk_widget_class_bind_template_callback (widget_class, on_gpgme_sign_choice_toggled);
 
     dialog_class->response = seahorse_gpgme_sign_dialog_response;
@@ -268,14 +267,14 @@ SeahorseGpgmeSignDialog *
 seahorse_gpgme_sign_dialog_new (SeahorseObject *to_sign)
 {
     g_autoptr(SeahorseGpgmeSignDialog) self = NULL;
-    GcrCollection *collection;
+    g_autoptr(GListModel) model = NULL;
 
     g_return_val_if_fail (SEAHORSE_GPGME_IS_KEY (to_sign) ||
                           SEAHORSE_GPGME_IS_UID (to_sign), NULL);
 
     /* If no signing keys then we can't sign */
-    collection = seahorse_keyset_pgp_signers_new ();
-    if (gcr_collection_get_length (collection) == 0) {
+    model = seahorse_keyset_pgp_signers_new ();
+    if (g_list_model_get_n_items (model) == 0) {
         /* TODO: We should be giving an error message that allows them to
            generate or import a key */
         seahorse_util_show_error (NULL, _("No keys usable for signing"),
@@ -289,17 +288,13 @@ seahorse_gpgme_sign_dialog_new (SeahorseObject *to_sign)
                          NULL);
 
     /* Signature area */
-    g_signal_connect_object (collection, "added",
+    g_signal_connect_object (model, "model",
                              G_CALLBACK (on_collection_changed), self, 0);
-    g_signal_connect_object (collection, "removed",
-                             G_CALLBACK (on_collection_changed), self, 0);
-    on_collection_changed (collection, NULL, self);
+    on_collection_changed (model, 0, 0, 0, self);
 
     /* Signer box */
-    seahorse_combo_keys_attach (GTK_COMBO_BOX (self->signer_select),
-                                collection, NULL);
-
-    g_object_unref (collection);
+    /* XXX I think we still need to set a factory here? */
+    adw_combo_row_set_model (ADW_COMBO_ROW (self->signer_row), model);
 
     return g_steal_pointer (&self);
 }
diff --git a/pgp/seahorse-gpgme-sign-dialog.ui b/pgp/seahorse-gpgme-sign-dialog.ui
index cf03894d..774d880b 100644
--- a/pgp/seahorse-gpgme-sign-dialog.ui
+++ b/pgp/seahorse-gpgme-sign-dialog.ui
@@ -1,25 +1,19 @@
 <?xml version="1.0"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
   <template class="SeahorseGpgmeSignDialog" parent="GtkDialog">
-    <property name="visible">True</property>
     <property name="title" translatable="yes">Sign Key</property>
-    <property name="resizable">False</property>
     <property name="use_header_bar">1</property>
-    <child internal-child="vbox">
+
+    <child internal-child="content_area">
       <object class="GtkBox">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
-        <property name="border_width">12</property>
         <property name="spacing">18</property>
         <child>
           <object class="GtkBox">
-            <property name="visible">True</property>
             <property name="orientation">vertical</property>
             <property name="spacing">6</property>
             <child>
               <object class="GtkLabel">
-                <property name="visible">True</property>
                 <property name="xalign">0</property>
                 <property name="yalign">0</property>
                 <property name="label" translatable="yes">By signing you indicate your trust that this key 
belongs to:</property>
@@ -27,7 +21,6 @@
             </child>
             <child>
               <object class="GtkLabel" id="to_sign_name_label">
-                <property name="visible">True</property>
                 <property name="yalign">0</property>
                 <property name="label" translatable="yes">Key Name</property>
                 <property name="use_markup">True</property>
@@ -40,13 +33,11 @@
           </object>
         </child>
         <child>
-          <object class="GtkBox" id="vbox6">
-            <property name="visible">True</property>
+          <object class="GtkBox">
             <property name="orientation">vertical</property>
             <property name="spacing">12</property>
             <child>
               <object class="GtkLabel" id="label8">
-                <property name="visible">True</property>
                 <property name="xalign">0</property>
                 <property name="yalign">0</property>
                 <property name="label" translatable="yes">How carefully have you checked this key?</property>
@@ -57,133 +48,93 @@
             </child>
             <child>
               <object class="GtkBox">
-                <property name="visible">True</property>
                 <property name="orientation">vertical</property>
                 <property name="spacing">6</property>
                 <property name="margin-start">12</property>
                 <child>
                   <object class="GtkBox">
-                    <property name="visible">True</property>
+                    <property name="orientation">horizontal</property>
+                    <property name="spacing">12</property>
+                    <property name="homogeneous">True</property>
+                    <child>
+                      <object class="GtkCheckButton" id="sign_choice_not">
+                        <property name="label" translatable="yes">_Not at all</property>
+                        <property name="receives_default">False</property>
+                        <property name="use_underline">True</property>
+                        <signal name="toggled" handler="on_gpgme_sign_choice_toggled"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkCheckButton" id="sign_choice_casual">
+                        <property name="label" translatable="yes">_Casually</property>
+                        <property name="receives_default">False</property>
+                        <property name="use_underline">True</property>
+                        <property name="group">sign_choice_not</property>
+                        <signal name="toggled" handler="on_gpgme_sign_choice_toggled"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkCheckButton" id="sign_choice_careful">
+                        <property name="label" translatable="yes">_Very Carefully</property>
+                        <property name="receives_default">False</property>
+                        <property name="use_underline">True</property>
+                        <property name="group">sign_choice_not</property>
+                        <signal name="toggled" handler="on_gpgme_sign_choice_toggled"/>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkBox">
                     <property name="orientation">vertical</property>
-                    <property name="spacing">6</property>
                     <child>
-                      <object class="GtkBox">
-                        <property name="visible">True</property>
-                        <property name="orientation">horizontal</property>
-                        <property name="spacing">12</property>
-                        <property name="homogeneous">True</property>
-                        <child>
-                          <object class="GtkRadioButton" id="sign_choice_not">
-                            <property name="label" translatable="yes">_Not at all</property>
-                            <property name="visible">True</property>
-                            <property name="can_focus">True</property>
-                            <property name="receives_default">False</property>
-                            <property name="use_underline">True</property>
-                            <property name="draw_indicator">True</property>
-                            <signal name="toggled" handler="on_gpgme_sign_choice_toggled"/>
-                          </object>
-                        </child>
-                        <child>
-                          <object class="GtkRadioButton" id="sign_choice_casual">
-                            <property name="label" translatable="yes">_Casually</property>
-                            <property name="visible">True</property>
-                            <property name="can_focus">True</property>
-                            <property name="receives_default">False</property>
-                            <property name="use_underline">True</property>
-                            <property name="draw_indicator">True</property>
-                            <property name="group">sign_choice_not</property>
-                            <signal name="toggled" handler="on_gpgme_sign_choice_toggled"/>
-                          </object>
-                        </child>
-                        <child>
-                          <object class="GtkRadioButton" id="sign_choice_careful">
-                            <property name="label" translatable="yes">_Very Carefully</property>
-                            <property name="visible">True</property>
-                            <property name="can_focus">True</property>
-                            <property name="receives_default">False</property>
-                            <property name="use_underline">True</property>
-                            <property name="draw_indicator">True</property>
-                            <property name="group">sign_choice_not</property>
-                            <signal name="toggled" handler="on_gpgme_sign_choice_toggled"/>
-                          </object>
-                        </child>
+                      <object class="GtkLabel" id="sign_display_not">
+                        <property name="xalign">0</property>
+                        <property name="yalign">0</property>
+                        <property name="label" translatable="yes">&lt;i&gt;Not at all:&lt;/i&gt; means you 
believe the key is owned by the person who claims to own it, but you could not or did not verify this to be a 
fact.</property>
+                        <property name="use_markup">True</property>
+                        <property name="justify">fill</property>
+                        <property name="wrap">True</property>
+                        <property name="max_width_chars">75</property>
+                        <property name="selectable">True</property>
                       </object>
                     </child>
                     <child>
-                      <object class="GtkAlignment" id="alignment2">
-                        <property name="visible">True</property>
-                        <property name="xalign">1</property>
-                        <property name="yalign">1</property>
-                        <property name="left_padding">18</property>
+                      <object class="GtkLabel" id="sign_display_casual">
+                        <property name="xalign">0</property>
+                        <property name="yalign">0</property>
+                        <property name="label" translatable="yes">&lt;i&gt;Casually:&lt;/i&gt; means you 
have done a casual verification that the key is owned by the person who claims to own it. For example, you 
could read the key fingerprint to the owner over the phone.</property>
+                        <property name="use_markup">True</property>
+                        <property name="justify">fill</property>
+                        <property name="wrap">True</property>
+                        <property name="max_width_chars">75</property>
+                        <property name="selectable">True</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="sign_display_careful">
+                        <property name="spacing">6</property>
+                        <property name="orientation">vertical</property>
                         <child>
-                          <object class="GtkBox" id="vbox12">
-                            <property name="visible">True</property>
-                            <property name="orientation">vertical</property>
-                            <child>
-                              <object class="GtkLabel" id="sign_display_not">
-                                <property name="can_focus">True</property>
-                                <property name="xalign">0</property>
-                                <property name="yalign">0</property>
-                                <property name="label" translatable="yes">&lt;i&gt;Not at all:&lt;/i&gt; 
means you believe the key is owned by the person who claims to own it, but you could not or did not verify 
this to be a fact.</property>
-                                <property name="use_markup">True</property>
-                                <property name="justify">fill</property>
-                                <property name="wrap">True</property>
-                                <property name="max_width_chars">75</property>
-                                <property name="selectable">True</property>
-                              </object>
-                            </child>
-                            <child>
-                              <object class="GtkLabel" id="sign_display_casual">
-                                <property name="can_focus">True</property>
-                                <property name="xalign">0</property>
-                                <property name="yalign">0</property>
-                                <property name="label" translatable="yes">&lt;i&gt;Casually:&lt;/i&gt; means 
you have done a casual verification that the key is owned by the person who claims to own it. For example, 
you could read the key fingerprint to the owner over the phone.</property>
-                                <property name="use_markup">True</property>
-                                <property name="justify">fill</property>
-                                <property name="wrap">True</property>
-                                <property name="max_width_chars">75</property>
-                                <property name="selectable">True</property>
-                              </object>
-                            </child>
-                            <child>
-                              <object class="GtkBox" id="sign_display_careful">
-                                <property name="spacing">6</property>
-                                <property name="orientation">vertical</property>
-                                <child>
-                                  <object class="GtkEventBox" id="eventbox1">
-                                    <property name="visible">True</property>
-                                    <child>
-                                      <object class="GtkLabel" id="label7">
-                                        <property name="visible">True</property>
-                                        <property name="can_focus">True</property>
-                                        <property name="xalign">0</property>
-                                        <property name="yalign">0</property>
-                                        <property name="label" translatable="yes">&lt;i&gt;Very 
Carefully:&lt;/i&gt; Select this only if you are absolutely sure that this key is genuine.</property>
-                                        <property name="use_markup">True</property>
-                                        <property name="justify">fill</property>
-                                        <property name="wrap">True</property>
-                                        <property name="max_width_chars">75</property>
-                                        <property name="selectable">True</property>
-                                      </object>
-                                    </child>
-                                  </object>
-                                </child>
-                                <child>
-                                  <object class="GtkLabel" id="label12">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">True</property>
-                                    <property name="xalign">0</property>
-                                    <property name="yalign">0</property>
-                                    <property name="label" translatable="yes">You could use a hard to forge 
photo identification (such as a passport) to personally check that the name on the key is correct. You should 
have also used email to check that the email address belongs to the owner.</property>
-                                    <property name="use_markup">True</property>
-                                    <property name="justify">fill</property>
-                                    <property name="wrap">True</property>
-                                    <property name="max_width_chars">75</property>
-                                    <property name="selectable">True</property>
-                                  </object>
-                                </child>
-                              </object>
-                            </child>
+                          <object class="GtkLabel" id="label7">
+                            <property name="xalign">0</property>
+                            <property name="yalign">0</property>
+                            <property name="label" translatable="yes">&lt;i&gt;Very Carefully:&lt;/i&gt; 
Select this only if you are absolutely sure that this key is genuine.</property>
+                            <property name="use_markup">True</property>
+                            <property name="justify">fill</property>
+                            <property name="wrap">True</property>
+                            <property name="max_width_chars">75</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="label12">
+                            <property name="xalign">0</property>
+                            <property name="yalign">0</property>
+                            <property name="label" translatable="yes">You could use a hard to forge photo 
identification (such as a passport) to personally check that the name on the key is correct. You should have 
also used email to check that the email address belongs to the owner.</property>
+                            <property name="use_markup">True</property>
+                            <property name="justify">fill</property>
+                            <property name="wrap">True</property>
+                            <property name="max_width_chars">75</property>
                           </object>
                         </child>
                       </object>
@@ -195,145 +146,63 @@
           </object>
         </child>
         <child>
-          <object class="GtkBox">
-            <property name="visible">True</property>
-            <property name="orientation">vertical</property>
-            <property name="spacing">12</property>
-            <child>
-              <object class="GtkLabel" id="label9">
-                <property name="visible">True</property>
-                <property name="xalign">0</property>
-                <property name="label" translatable="yes">How others will see this signature:</property>
-                <attributes>
-                 <attribute name="weight" value="bold"/>
-                </attributes>
-              </object>
-            </child>
+          <object class="AdwPreferencesGroup">
+            <property name="title" translatable="yes">How others will see this signature</property>
             <child>
-              <object class="GtkGrid">
-                <property name="visible">True</property>
-                <property name="row-spacing">12</property>
-                <property name="column-spacing">12</property>
-                <property name="margin-start">12</property>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="xalign">0</property>
-                    <property name="label" translatable="yes">_Others may not see this signature</property>
-                    <property name="tooltip_text">If signature is local to the key ring and won't be 
exported with the key</property>
-                    <property name="use_underline">True</property>
-                    <property name="mnemonic-widget">sign_option_local</property>
-                  </object>
-                  <packing>
-                    <property name="top_attach">0</property>
-                    <property name="left_attach">0</property>
-                  </packing>
-                </child>
-                <child>
+              <object class="AdwActionRow">
+                <property name="title" translatable="yes">_Others may not see this signature</property>
+                <property name="use-underline">True</property>
+                <property name="tooltip-text">If signature is local to the key ring and won't be exported 
with the key</property>
+                <child type="suffix">
                   <object class="GtkSwitch" id="sign_option_local">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="receives_default">False</property>
-                    <property name="tooltip_text">If signature is local to the key ring and won't be 
exported with the key</property>
+                    <property name="tooltip-text">If signature is local to the key ring and won't be 
exported with the key</property>
+                    <property name="valign">center</property>
                   </object>
-                  <packing>
-                    <property name="top_attach">0</property>
-                    <property name="left_attach">1</property>
-                  </packing>
                 </child>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <property name="xalign">0</property>
-                    <property name="label" translatable="yes">I can _revoke this signature at a later 
date.</property>
-                    <property name="tooltip_text">If signature can be revoked</property>
-                    <property name="use_underline">True</property>
-                    <property name="mnemonic-widget">sign_option_revocable</property>
-                  </object>
-                  <packing>
-                    <property name="top_attach">1</property>
-                    <property name="left_attach">0</property>
-                  </packing>
-                </child>
-                <child>
+              </object>
+            </child>
+            <child>
+              <object class="AdwActionRow">
+                <property name="title" translatable="yes">I can _revoke this signature at a later 
date.</property>
+                <property name="tooltip-text">If signature can be revoked</property>
+                <property name="use-underline">True</property>
+                <child type="suffix">
                   <object class="GtkSwitch" id="sign_option_revocable">
-                    <property name="visible">True</property>
-                    <property name="tooltip_text">If signature can be revoked</property>
+                    <property name="tooltip-text">If signature can be revoked</property>
                     <property name="active">True</property>
+                    <property name="valign">center</property>
                   </object>
-                  <packing>
-                    <property name="top_attach">1</property>
-                    <property name="left_attach">1</property>
-                  </packing>
                 </child>
               </object>
             </child>
           </object>
         </child>
         <child>
-          <object class="GtkBox" id="signer_frame">
-            <property name="visible">True</property>
-            <property name="orientation">vertical</property>
-            <property name="spacing">12</property>
+          <object class="AdwPreferencesGroup" id="signer_frame">
             <child>
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="xalign">0</property>
-                <property name="label" translatable="yes">Sign key as:</property>
-                <attributes>
-                 <attribute name="weight" value="bold"/>
-                </attributes>
-              </object>
-            </child>
-            <child>
-              <object class="GtkAlignment" id="alignment7">
-                <property name="visible">True</property>
-                <property name="left_padding">12</property>
-                <child>
-                  <object class="GtkBox" id="hbox2">
-                    <property name="visible">True</property>
-                    <property name="orientation">horizontal</property>
-                    <property name="spacing">6</property>
-                    <child>
-                      <object class="GtkLabel" id="label11">
-                        <property name="visible">True</property>
-                        <property name="label" translatable="yes">_Signer:</property>
-                        <property name="use_underline">True</property>
-                        <property name="mnemonic_widget">signer_select</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkComboBox" id="signer_select">
-                        <property name="visible">True</property>
-                      </object>
-                    </child>
-                  </object>
-                </child>
+              <object class="AdwComboRow" id="signer_row">
+                <property name="title" translatable="yes">_Sign key as</property>
+                <property name="use-underline">True</property>
+                <property name="expression">
+                  <lookup type="SeahorseGpgmeKey" name="label"/>
+                </property>
               </object>
             </child>
           </object>
         </child>
       </object>
     </child>
+
     <child type="action">
       <object class="GtkButton" id="cancel_button">
-        <property name="label">gtk-cancel</property>
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="can_default">True</property>
-        <property name="receives_default">False</property>
-        <property name="use_stock">True</property>
+        <property name="label" translatable="yes">_Cancel</property>
+        <property name="use-underline">True</property>
       </object>
     </child>
     <child type="action">
       <object class="GtkButton" id="ok_button">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="can_default">True</property>
-        <property name="has_default">True</property>
-        <property name="receives_default">False</property>
         <property name="label" translatable="yes">_Sign</property>
-        <property name="use_underline">True</property>
+        <property name="use-underline">True</property>
       </object>
     </child>
     <action-widgets>
diff --git a/pgp/seahorse-hkp-source.c b/pgp/seahorse-hkp-source.c
index 1e906f90..6cedd023 100644
--- a/pgp/seahorse-hkp-source.c
+++ b/pgp/seahorse-hkp-source.c
@@ -480,7 +480,7 @@ typedef struct {
     SoupMessage *message;
     GString *response;
     int requests;
-    GcrSimpleCollection *results;
+    GListStore *results;
 } SearchClosure;
 
 static void
@@ -522,7 +522,7 @@ on_search_message_complete (GObject *object,
     keys = seahorse_hkp_parse_lookup_response (closure->response->str);
     for (GList *l = keys; l; l = g_list_next (l)) {
         g_object_set (l->data, "place", closure->source, NULL);
-        gcr_simple_collection_add (closure->results, l->data);
+        g_list_store_append (closure->results, l->data);
     }
     g_task_return_boolean (task, TRUE);
 }
@@ -547,7 +547,7 @@ is_hex_keyid (const char *match)
 static void
 seahorse_hkp_source_search_async (SeahorseServerSource *source,
                                   const char           *match,
-                                  GcrSimpleCollection  *results,
+                                  GListStore           *results,
                                   GCancellable         *cancellable,
                                   GAsyncReadyCallback   callback,
                                   void                 *user_data)
@@ -761,7 +761,6 @@ seahorse_hkp_source_import_finish (SeahorseServerSource *source,
 typedef struct {
     SeahorseHKPSource *source;
     GString *data;
-    gsize data_len;
     SoupSession *session;
     SoupMessage *message;
     int requests;
@@ -817,10 +816,10 @@ on_export_message_complete (GObject *object,
     closure->requests--;
 
     if (closure->requests == 0) {
-        closure->data_len = closure->data->len;
-        g_task_return_pointer (task,
-                               g_string_free (g_steal_pointer (&closure->data), FALSE),
-                               g_free);
+        g_autoptr(GBytes) result = NULL;
+
+        result = g_string_free_to_bytes (g_steal_pointer (&closure->data));
+        g_task_return_pointer (task, g_steal_pointer (&result), g_bytes_unref);
     }
 }
 
@@ -891,19 +890,13 @@ seahorse_hkp_source_export_async (SeahorseServerSource *source,
                                closure->session, NULL);
 }
 
-static void *
+static GBytes *
 seahorse_hkp_source_export_finish (SeahorseServerSource *source,
                                    GAsyncResult *result,
-                                   gsize *size,
                                    GError **error)
 {
-    ExportClosure *closure;
-
-    g_return_val_if_fail (size != NULL, NULL);
     g_return_val_if_fail (g_task_is_valid (result, source), NULL);
 
-    closure = g_task_get_task_data (G_TASK (result));
-    *size = closure->data_len;
     return g_task_propagate_pointer (G_TASK (result), error);
 }
 
diff --git a/pgp/seahorse-keyserver-results.c b/pgp/seahorse-keyserver-results.c
index 2c03b087..baa6ed8d 100644
--- a/pgp/seahorse-keyserver-results.c
+++ b/pgp/seahorse-keyserver-results.c
@@ -35,16 +35,18 @@
 #include <string.h>
 
 #define SEAHORSE_TYPE_KEYSERVER_RESULTS_ROW (seahorse_keyserver_results_row_get_type ())
-G_DECLARE_FINAL_TYPE (SeahorseKeyserverResultsRow, seahorse_keyserver_results_row, SEAHORSE, 
KEYSERVER_RESULTS_ROW, GtkListBoxRow)
+G_DECLARE_FINAL_TYPE (SeahorseKeyserverResultsRow, seahorse_keyserver_results_row,
+                      SEAHORSE, KEYSERVER_RESULTS_ROW,
+                      AdwActionRow)
 
 struct _SeahorseKeyserverResultsRow {
-    GtkListBoxRow parent;
+    AdwActionRow parent;
 
     GObject *key;
     GtkButton *import_button;
 };
 
-G_DEFINE_TYPE (SeahorseKeyserverResultsRow, seahorse_keyserver_results_row, GTK_TYPE_LIST_BOX_ROW);
+G_DEFINE_TYPE (SeahorseKeyserverResultsRow, seahorse_keyserver_results_row, ADW_TYPE_ACTION_ROW);
 
 static void
 on_import_complete (GObject *source, GAsyncResult *result, gpointer user_data)
@@ -52,9 +54,8 @@ on_import_complete (GObject *source, GAsyncResult *result, gpointer user_data)
     SeahorsePgpBackend *backend = SEAHORSE_PGP_BACKEND (source);
     g_autoptr(SeahorseKeyserverResultsRow) row =
         SEAHORSE_KEYSERVER_RESULTS_ROW (user_data);
-    const gchar *result_icon_name;
-    g_autoptr(GtkWidget) result_icon = NULL;
-    g_autofree gchar *result_tooltip = NULL;
+    const char *result_icon_name;
+    g_autofree char *result_tooltip = NULL;
     g_autoptr(GError) err = NULL;
 
     if (!seahorse_pgp_backend_transfer_finish (backend, result, &err)) {
@@ -66,9 +67,7 @@ on_import_complete (GObject *source, GAsyncResult *result, gpointer user_data)
         result_tooltip = g_strdup (_("Key import succeeded"));
     }
 
-    result_icon = gtk_image_new_from_icon_name (result_icon_name,
-                                                GTK_ICON_SIZE_BUTTON);
-    gtk_button_set_image (row->import_button, g_steal_pointer (&result_icon));
+    gtk_button_set_icon_name (row->import_button, result_icon_name);
     gtk_widget_set_tooltip_text (GTK_WIDGET (row->import_button),
                                  result_tooltip);
 }
@@ -87,7 +86,7 @@ on_import_button_clicked (GtkButton *import_button, gpointer user_data)
     gtk_widget_set_sensitive (GTK_WIDGET (import_button), FALSE);
     spinner = gtk_spinner_new ();
     gtk_spinner_start (GTK_SPINNER (spinner));
-    gtk_button_set_image (import_button, g_steal_pointer (&spinner));
+    gtk_button_set_child (import_button, g_steal_pointer (&spinner));
 
     /* Now import the key */
     keys = g_list_append (keys, row->key);
@@ -110,53 +109,39 @@ seahorse_keyserver_results_row_init (SeahorseKeyserverResultsRow *row)
 {
 }
 
-static SeahorseKeyserverResultsRow*
+static SeahorseKeyserverResultsRow *
 seahorse_keyserver_results_row_new (GObject *item)
 {
     g_autoptr(SeahorseKeyserverResultsRow) row = NULL;
-    g_autoptr(GtkWidget) grid = NULL;
-    g_autoptr(GtkWidget) label = NULL;
-    g_autoptr(GtkWidget) import_button = NULL;
-    gchar *item_label;
+    GtkWidget *import_button = NULL;
+    g_autofree char *item_label = NULL;
     gboolean item_exportable;
 
-    g_object_get (item, "markup", &item_label, "exportable", &item_exportable,
+    g_object_get (item,
+                  "markup", &item_label,
+                  "exportable", &item_exportable,
                   NULL);
 
     row = g_object_new (SEAHORSE_TYPE_KEYSERVER_RESULTS_ROW, NULL);
     gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (row), FALSE);
     gtk_widget_set_sensitive (GTK_WIDGET (row), item_exportable);
-    gtk_widget_show (GTK_WIDGET (row));
     row->key = item;
 
-    grid = gtk_grid_new ();
-    g_object_set (grid, "margin", 6, NULL);
-    gtk_widget_show (grid);
+    adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), item_label);
 
-    label = gtk_label_new (item_label);
-    gtk_widget_set_hexpand (label, TRUE);
-    gtk_label_set_xalign (GTK_LABEL (label), 0);
-    gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
-    gtk_widget_show (label);
-    gtk_grid_attach (GTK_GRID (grid), g_steal_pointer (&label), 0, 0, 1, 1);
-
-    import_button = gtk_button_new_from_icon_name ("document-save-symbolic",
-                                                   GTK_ICON_SIZE_BUTTON);
+    import_button = gtk_button_new_from_icon_name ("document-save-symbolic");
     row->import_button = GTK_BUTTON (import_button);
     g_signal_connect_object (import_button, "clicked",
                              G_CALLBACK (on_import_button_clicked), row, 0);
-    gtk_widget_set_visible (import_button, TRUE);
-    gtk_widget_set_valign (import_button, GTK_ALIGN_START);
-    gtk_widget_set_halign (import_button, GTK_ALIGN_END);
-    gtk_style_context_add_class (gtk_widget_get_style_context (import_button),
-                                 "flat");
-    if (item_exportable)
+    gtk_widget_set_valign (import_button, GTK_ALIGN_CENTER);
+    gtk_widget_add_css_class (import_button, "flat");
+    if (item_exportable) {
         gtk_widget_set_tooltip_text (import_button, _("Import"));
-    else
+    } else {
         gtk_widget_set_tooltip_text (import_button, _("Can’t import key"));
-    gtk_grid_attach (GTK_GRID (grid), g_steal_pointer (&import_button), 1, 0, 1, 1);
-
-    gtk_container_add (GTK_CONTAINER (row), g_steal_pointer (&grid));
+        gtk_widget_set_sensitive (import_button, FALSE);
+    }
+    adw_action_row_add_suffix (ADW_ACTION_ROW (row), import_button);
 
     return g_steal_pointer (&row);
 }
@@ -173,7 +158,7 @@ struct _SeahorseKeyserverResults {
     GtkBuilder *builder;
 
     char *search_string;
-    GcrSimpleCollection *collection;
+    GListStore *collection;
     GtkListBox *key_list;
 };
 
@@ -190,27 +175,10 @@ on_row_activated (GtkListBox *key_list, GtkListBoxRow *row, gpointer user_data)
     seahorse_viewable_view (_row->key, GTK_WINDOW (self));
 }
 
-static void
-on_item_added (GcrCollection *collection, GObject *item, gpointer user_data)
-{
-    SeahorseKeyserverResults *self = SEAHORSE_KEYSERVER_RESULTS (user_data);
-    g_autoptr(SeahorseKeyserverResultsRow) row = NULL;
-
-    g_return_if_fail (G_IS_OBJECT (item));
-
-    row = seahorse_keyserver_results_row_new (item);
-    gtk_list_box_insert (self->key_list,
-                         GTK_WIDGET (g_steal_pointer (&row)),
-                         -1);
-}
-
-static gboolean
-on_delete_event (GtkWidget* widget, GdkEvent* event, gpointer user_data)
+static GtkWidget *
+create_row_for_key (void *item, gpointer user_data)
 {
-    SeahorseKeyserverResults *self = SEAHORSE_KEYSERVER_RESULTS (user_data);
-
-    gtk_widget_destroy (GTK_WIDGET (self));
-    return TRUE;
+    return seahorse_keyserver_results_row_new ((GObject *) item);
 }
 
 static void
@@ -232,22 +200,15 @@ seahorse_keyserver_results_constructed (GObject *obj)
     gtk_window_set_title (window, title);
     gtk_widget_set_visible (GTK_WIDGET (window), TRUE);
 
-    g_signal_connect (window, "delete-event",
-                      G_CALLBACK (on_delete_event), self);
-
     self->builder = gtk_builder_new_from_resource ("/org/gnome/Seahorse/seahorse-keyserver-results.ui");
-    gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (self))),
-                       GTK_WIDGET (gtk_builder_get_object (self->builder, "keyserver-results")));
+    gtk_window_set_child (GTK_WINDOW (self),
+                          GTK_WIDGET (gtk_builder_get_object (self->builder, "keyserver-results")));
 
     /* init key list */
     self->key_list = GTK_LIST_BOX (gtk_builder_get_object (self->builder, "key_list"));
     g_signal_connect_object (self->key_list, "row-activated",
                              G_CALLBACK (on_row_activated), self, 0);
-    gtk_widget_show (GTK_WIDGET (self->key_list));
-
-    /* Make sure the listbox gets updated with the collection */
-    g_signal_connect_object (self->collection, "added",
-                             G_CALLBACK (on_item_added), self, 0);
+    gtk_list_box_bind_model (self->key_list, G_LIST_MODEL (self->collection), create_row_for_key, self, 
NULL);
 
     /* Set focus to the current key list */
     gtk_widget_grab_focus (GTK_WIDGET (self->key_list));
@@ -256,7 +217,7 @@ seahorse_keyserver_results_constructed (GObject *obj)
 static void
 seahorse_keyserver_results_init (SeahorseKeyserverResults *self)
 {
-    self->collection = GCR_SIMPLE_COLLECTION (gcr_simple_collection_new ());
+    self->collection = g_list_store_new (SEAHORSE_PGP_TYPE_KEY);
 }
 
 static void
diff --git a/pgp/seahorse-keyserver-results.ui b/pgp/seahorse-keyserver-results.ui
index 4e8cea41..bf218328 100644
--- a/pgp/seahorse-keyserver-results.ui
+++ b/pgp/seahorse-keyserver-results.ui
@@ -1,33 +1,23 @@
 <?xml version="1.0"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
   <!-- interface-naming-policy toplevel-contextual -->
       <object class="GtkBox" id="keyserver-results">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
         <property name="spacing">12</property>
         <child>
           <object class="GtkLabel">
-            <property name="visible">True</property>
             <property name="wrap">True</property>
             <property name="max-width-chars">40</property>
-            <property name="margin">24</property>
-            <property name="margin-bottom">0</property>
             <property name="label" translatable="yes">Double click on a key to inspect it, or click the 
import button to import it into your local keyring.</property>
           </object>
         </child>
         <child>
           <object class="GtkScrolledWindow" id="scrolledwindow1">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
             <property name="hscrollbar_policy">never</property>
-            <property name="margin">24</property>
             <property name="margin-top">0</property>
             <property name="margin-bottom">0</property>
             <child>
               <object class="GtkListBox" id="key_list">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
                 <property name="vexpand">True</property>
                 <property name="activate_on_single_click">False</property>
                 <style>
@@ -39,18 +29,15 @@
         </child>
         <child>
           <object class="GtkBox" id="hbox1">
-            <property name="visible">True</property>
             <property name="orientation">horizontal</property>
             <child>
               <object class="GtkProgressBar" id="progress-bar">
-                <property name="visible">True</property>
                 <property name="hexpand">True</property>
                 <property name="pulse_step">0.10000000149</property>
               </object>
             </child>
             <child>
               <object class="GtkStatusbar" id="status">
-                <property name="visible">True</property>
               </object>
             </child>
           </object>
diff --git a/pgp/seahorse-keyserver-search.c b/pgp/seahorse-keyserver-search.c
index 077b0f34..93fd73dd 100644
--- a/pgp/seahorse-keyserver-search.c
+++ b/pgp/seahorse-keyserver-search.c
@@ -72,7 +72,7 @@ seahorse_keyserver_search_get_search_text (SeahorseKeyserverSearch *self)
 {
     g_return_val_if_fail (SEAHORSE_IS_KEYSERVER_SEARCH (self), NULL);
 
-    return g_strdup (gtk_entry_get_text (GTK_ENTRY (self->search_entry)));
+    return g_strdup (gtk_editable_get_text (GTK_EDITABLE (self->search_entry)));
 }
 
 /* Extracts data, stores it in settings and starts a search using the entered
@@ -148,33 +148,22 @@ create_row_for_server_source (gpointer item,
     SeahorseServerSource *ssrc = SEAHORSE_SERVER_SOURCE (item);
     g_autofree char *uri = NULL;
     GtkWidget *row;
-    GtkWidget *grid;
-    GtkWidget *label;
     GtkWidget *check;
     gboolean is_selected;
 
-    row = gtk_list_box_row_new ();
+    row = adw_action_row_new ();
     gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (row), FALSE);
     g_object_set_data (G_OBJECT (row), "keyserver-uri", ssrc);
 
-    grid = gtk_grid_new ();
-    g_object_set (grid, "margin", 6, NULL);
-    gtk_container_add (GTK_CONTAINER (row), grid);
-
     uri = seahorse_place_get_uri (SEAHORSE_PLACE (ssrc));
-    label = gtk_label_new (uri);
-    gtk_widget_set_hexpand (label, TRUE);
-    gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1);
+    adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), uri);
 
-    check = gtk_image_new_from_icon_name ("emblem-ok-symbolic",
-                                          GTK_ICON_SIZE_BUTTON);
+    check = gtk_image_new_from_icon_name ("emblem-ok-symbolic");
     is_selected = g_ptr_array_find (self->selected_servers, ssrc, NULL);
     gtk_widget_set_visible (check, is_selected);
-    gtk_grid_attach (GTK_GRID (grid), check, 1, 0, 1, 1);
+    adw_action_row_add_suffix (ADW_ACTION_ROW (row), check);
     g_object_set_data (G_OBJECT (row), "check", check);
 
-    gtk_widget_show_all (row);
-
     return row;
 }
 
@@ -223,7 +212,7 @@ seahorse_keyserver_search_init (SeahorseKeyserverSearch *self)
 
     search_text = seahorse_app_settings_get_last_search_text (app_settings);
     if (search_text != NULL) {
-        gtk_entry_set_text (GTK_ENTRY (self->search_entry), search_text);
+        gtk_editable_set_text (GTK_EDITABLE (self->search_entry), search_text);
         gtk_editable_select_region (GTK_EDITABLE (self->search_entry), 0, -1);
     }
 
diff --git a/pgp/seahorse-keyserver-search.ui b/pgp/seahorse-keyserver-search.ui
index 557ade9c..c3fd6988 100644
--- a/pgp/seahorse-keyserver-search.ui
+++ b/pgp/seahorse-keyserver-search.ui
@@ -1,32 +1,27 @@
 <?xml version="1.0"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
   <template class="SeahorseKeyserverSearch" parent="GtkDialog">
-    <property name="visible">True</property>
-    <property name="border_width">18</property>
     <property name="use_header_bar">1</property>
     <property name="title" translatable="yes">Find Remote Keys</property>
-    <child internal-child="vbox">
+    <child internal-child="content_area">
       <object class="GtkBox">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
-        <property name="border_width">6</property>
         <property name="spacing">12</property>
+        <property name="margin-top">12</property>
+        <property name="margin-bottom">12</property>
+        <property name="margin-start">12</property>
+        <property name="margin-end">12</property>
         <child>
           <object class="GtkBox">
-            <property name="visible">True</property>
             <property name="orientation">horizontal</property>
             <property name="spacing">12</property>
             <child>
               <object class="GtkImage">
-                <property name="visible">True</property>
                 <property name="icon-name">find-location-symbolic</property>
-                <property name="icon-size">5</property>
               </object>
             </child>
             <child>
               <object class="GtkLabel" id="publish-message">
-                <property name="visible">True</property>
                 <property name="xalign">0</property>
                 <property name="label" translatable="yes">This will find keys for others on the Internet. 
These keys can then be imported into your local key ring.</property>
                 <property name="max_width_chars">60</property>
@@ -37,24 +32,18 @@
         </child>
         <child>
           <object class="GtkBox">
-            <property name="visible">True</property>
             <property name="orientation">horizontal</property>
             <property name="spacing">12</property>
             <child>
               <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="label" translatable="yes">_Search for keys containing: </property>
+                <property name="label" translatable="yes">_Search for keys containing:</property>
                 <property name="use_underline">True</property>
                 <property name="mnemonic_widget">search_entry</property>
               </object>
             </child>
             <child>
               <object class="GtkEntry" id="search_entry">
-                <property name="visible">True</property>
                 <property name="hexpand">True</property>
-                <property name="can_focus">True</property>
-                <property name="has_focus">True</property>
-                <accelerator key="Return" signal="activate"/>
                 <signal name="changed" handler="on_keyserver_search_control_changed"/>
                 <signal name="activate" handler="on_keyserver_search_ok_clicked"/>
               </object>
@@ -63,7 +52,6 @@
         </child>
         <child>
           <object class="GtkLabel">
-            <property name="visible">True</property>
             <property name="halign">start</property>
             <property name="label" translatable="yes">Where to search:</property>
             <property name="use_markup">True</property>
@@ -75,19 +63,16 @@
         </child>
         <child>
           <object class="GtkScrolledWindow">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
             <property name="hscrollbar_policy">never</property>
             <property name="vscrollbar_policy">automatic</property>
             <property name="propagate-natural-height">True</property>
             <property name="max-content-height">250</property>
             <child>
               <object class="GtkListBox" id="key_server_list">
-                <property name="visible">True</property>
                 <property name="margin-start">18</property>
                 <property name="margin-end">18</property>
                 <style>
-                  <class name="content"/>
+                  <class name="boxed-list"/>
                 </style>
               </object>
             </child>
@@ -95,22 +80,15 @@
         </child>
       </object>
     </child>
+
     <child type="action">
       <object class="GtkButton" id="cancelbutton">
-        <property name="label" translatable="yes">Cancel</property>
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="can_default">True</property>
-        <property name="receives_default">False</property>
+        <property name="label" translatable="yes">_Cancel</property>
+        <property name="use_underline">True</property>
       </object>
     </child>
     <child type="action">
       <object class="GtkButton" id="searchbutton">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="can_default">True</property>
-        <property name="has_default">True</property>
-        <property name="receives_default">False</property>
         <property name="label" translatable="yes">_Search</property>
         <property name="use_underline">True</property>
         <signal name="clicked" handler="on_keyserver_search_ok_clicked"/>
diff --git a/pgp/seahorse-keyserver-sync.c b/pgp/seahorse-keyserver-sync.c
index facb49df..210c04b3 100644
--- a/pgp/seahorse-keyserver-sync.c
+++ b/pgp/seahorse-keyserver-sync.c
@@ -35,7 +35,7 @@
 struct _SeahorseKeyserverSync {
     GtkDialog parent_instance;
 
-    GList *keys;
+    GListModel *keys;
 
     GtkWidget *detail_message;
     GtkWidget *publish_message;
@@ -118,10 +118,8 @@ static void
 on_sync_ok_clicked (GtkButton *button, gpointer user_data)
 {
     SeahorseKeyserverSync *self = SEAHORSE_KEYSERVER_SYNC (user_data);
-    g_autoptr(GList) keys = NULL;
 
-    keys = g_list_copy (self->keys);
-    seahorse_keyserver_sync_do_sync (keys);
+    seahorse_keyserver_sync_do_sync (self->keys);
 }
 
 static void
@@ -144,7 +142,7 @@ seahorse_keyserver_sync_get_property (GObject    *object,
 
     switch (prop_id) {
     case PROP_KEYS:
-        g_value_set_pointer (value, self->keys);
+        g_value_set_object (value, self->keys);
         break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -162,8 +160,7 @@ seahorse_keyserver_sync_set_property (GObject      *object,
 
     switch (prop_id) {
     case PROP_KEYS:
-        g_list_free (self->keys);
-        self->keys = g_list_copy (g_value_get_pointer (value));
+        g_set_object (&self->keys, g_value_get_object (value));
         break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -176,7 +173,7 @@ seahorse_keyserver_sync_finalize (GObject *obj)
 {
     SeahorseKeyserverSync *self = SEAHORSE_KEYSERVER_SYNC (obj);
 
-    g_clear_pointer (&self->keys, g_list_free);
+    g_clear_object (&self->keys);
 
     G_OBJECT_CLASS (seahorse_keyserver_sync_parent_class)->finalize (obj);
 }
@@ -192,7 +189,7 @@ seahorse_keyserver_sync_constructed (GObject *obj)
     G_OBJECT_CLASS (seahorse_keyserver_sync_parent_class)->constructed (obj);
 
     /* The details message */
-    n_keys = g_list_length (self->keys);
+    n_keys = g_list_model_get_n_items (self->keys);
     t = g_strdup_printf (ngettext ("<b>%d key is selected for synchronizing</b>",
                                    "<b>%d keys are selected for synchronizing</b>",
                                    n_keys),
@@ -225,9 +222,8 @@ seahorse_keyserver_sync_class_init (SeahorseKeyserverSyncClass *klass)
     gobject_class->finalize = seahorse_keyserver_sync_finalize;
 
     g_object_class_install_property (gobject_class, PROP_KEYS,
-        g_param_spec_pointer ("keys", "Keys",
-                              "A GList of keys which should be synced",
-                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+        g_param_spec_object ("keys", NULL, NULL, G_TYPE_LIST_MODEL,
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
 
     gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/Seahorse/seahorse-keyserver-sync.ui");
 
@@ -247,7 +243,7 @@ seahorse_keyserver_sync_class_init (SeahorseKeyserverSyncClass *klass)
  * Non-interactively synchronizes the given @keys with the chosen keyserver.
  */
 void
-seahorse_keyserver_sync_do_sync (GList *keys)
+seahorse_keyserver_sync_do_sync (GListModel *keys)
 {
     SeahorseServerSource *source;
     SeahorseGpgmeKeyring *keyring;
@@ -258,16 +254,17 @@ seahorse_keyserver_sync_do_sync (GList *keys)
     g_auto(GStrv) keyservers = NULL;
     g_autoptr(GPtrArray) keyids = NULL;
 
-    if (!keys)
-        return;
+    g_return_if_fail (G_IS_LIST_MODEL (keys));
 
     keyring = seahorse_pgp_backend_get_default_keyring (NULL);
     pgp_settings = seahorse_pgp_settings_instance ();
     cancellable = g_cancellable_new ();
 
     keyids = g_ptr_array_new ();
-    for (GList *l = keys; l != NULL; l = g_list_next (l))
-        g_ptr_array_add (keyids, (char *) seahorse_pgp_key_get_keyid (l->data));
+    for (unsigned int i = 0; i < g_list_model_get_n_items (keys); i++) {
+        g_autoptr(SeahorsePgpKey) key = g_list_model_get_item (keys, i);
+        g_ptr_array_add (keyids, (char *) seahorse_pgp_key_get_keyid (key));
+    }
     g_ptr_array_add (keyids, NULL);
 
     /* And now synchronizing keys from the servers */
@@ -305,11 +302,11 @@ seahorse_keyserver_sync_do_sync (GList *keys)
 }
 
 SeahorseKeyserverSync *
-seahorse_keyserver_sync_new (GList     *keys,
-                             GtkWindow *parent)
+seahorse_keyserver_sync_new (GListModel *keys,
+                             GtkWindow  *parent)
 {
-    g_return_val_if_fail (keys, NULL);
-    g_return_val_if_fail (keys->data, NULL);
+    g_return_val_if_fail (G_IS_LIST_MODEL (keys), NULL);
+    g_return_val_if_fail (g_list_model_get_n_items (keys) > 0, NULL);
     g_return_val_if_fail (!parent || GTK_IS_WINDOW (parent), NULL);
 
     return g_object_new (SEAHORSE_TYPE_KEYSERVER_SYNC,
diff --git a/pgp/seahorse-keyserver-sync.h b/pgp/seahorse-keyserver-sync.h
index 6b6679d7..983dd664 100644
--- a/pgp/seahorse-keyserver-sync.h
+++ b/pgp/seahorse-keyserver-sync.h
@@ -27,7 +27,7 @@ G_DECLARE_FINAL_TYPE (SeahorseKeyserverSync, seahorse_keyserver_sync,
                       SEAHORSE, KEYSERVER_SYNC,
                       GtkDialog)
 
-SeahorseKeyserverSync *    seahorse_keyserver_sync_new          (GList     *keys,
-                                                                 GtkWindow *parent);
+SeahorseKeyserverSync *    seahorse_keyserver_sync_new          (GListModel *keys,
+                                                                 GtkWindow  *parent);
 
-void                       seahorse_keyserver_sync_do_sync      (GList *keys);
+void                       seahorse_keyserver_sync_do_sync      (GListModel *keys);
diff --git a/pgp/seahorse-keyserver-sync.ui b/pgp/seahorse-keyserver-sync.ui
index 70e52582..2b7a2748 100644
--- a/pgp/seahorse-keyserver-sync.ui
+++ b/pgp/seahorse-keyserver-sync.ui
@@ -1,105 +1,66 @@
 <?xml version="1.0"?>
 <interface>
-  <requires lib="gtk+" version="3.24"/>
   <template class="SeahorseKeyserverSync" parent="GtkDialog">
     <property name="title" translatable="yes">Sync Keys</property>
-    <property name="border_width">6</property>
     <property name="use_header_bar">1</property>
-    <child internal-child="vbox">
+
+    <child internal-child="content_area">
       <object class="GtkBox">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
-        <property name="border_width">5</property>
         <property name="spacing">12</property>
         <child>
-          <object class="GtkBox">
-            <property name="visible">True</property>
-            <property name="orientation">horizontal</property>
-            <property name="spacing">12</property>
-            <child>
-              <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="icon-name">network-transmit-receive-symbolic</property>
-                <property name="icon-size">5</property>
-              </object>
-            </child>
-            <child>
-              <object class="GtkBox">
-                <property name="visible">True</property>
-                <property name="orientation">vertical</property>
-                <property name="spacing">12</property>
-                <child>
-                  <object class="GtkLabel" id="publish_message">
-                    <property name="visible">True</property>
-                    <property name="xalign">0</property>
-                    <property name="yalign">0</property>
-                    <property name="label" translatable="yes">This will publish the keys in your key ring so 
they’re available for others to use. You’ll also get any changes others have made since you received their 
keys.</property>
-                    <property name="max-width-chars">80</property>
-                    <property name="wrap">True</property>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="sync_message">
-                    <property name="xalign">0</property>
-                    <property name="yalign">0</property>
-                    <property name="label" translatable="yes">This will retrieve any changes others have 
made since you received their keys. No key server has been chosen for publishing, so your keys will not be 
made available to others.</property>
-                    <property name="wrap">True</property>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="detail_message">
-                    <property name="visible">True</property>
-                    <property name="xalign">0</property>
-                    <property name="yalign">0</property>
-                    <property name="label"></property>
-                    <property name="use_markup">True</property>
-                    <property name="wrap">True</property>
-                  </object>
-                </child>
-              </object>
-            </child>
+          <object class="GtkLabel" id="publish_message">
+            <property name="xalign">0</property>
+            <property name="yalign">0</property>
+            <property name="label" translatable="yes">This will publish the keys in your key ring so they’re 
available for others to use. You’ll also get any changes others have made since you received their 
keys.</property>
+            <property name="max-width-chars">80</property>
+            <property name="wrap">True</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="sync_message">
+            <property name="xalign">0</property>
+            <property name="yalign">0</property>
+            <property name="label" translatable="yes">This will retrieve any changes others have made since 
you received their keys. No key server has been chosen for publishing, so your keys will not be made 
available to others.</property>
+            <property name="wrap">True</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="detail_message">
+            <property name="xalign">0</property>
+            <property name="yalign">0</property>
+            <property name="label"></property>
+            <property name="use_markup">True</property>
+            <property name="wrap">True</property>
           </object>
         </child>
         <child>
           <object class="GtkButton" id="configure">
             <property name="label" translatable="yes">_Key Servers</property>
-            <property name="visible">True</property>
             <property name="halign">end</property>
-            <property name="can_focus">True</property>
-            <property name="can_default">True</property>
-            <property name="receives_default">False</property>
             <property name="use_underline">True</property>
             <signal name="clicked" handler="on_sync_configure_clicked"/>
           </object>
         </child>
       </object>
     </child>
+
     <child type="action">
       <object class="GtkButton" id="cancel_button">
         <property name="label" translatable="yes">_Cancel</property>
-        <property name="visible">True</property>
         <property name="use-underline">True</property>
-        <property name="can_focus">True</property>
-        <property name="can_default">True</property>
-        <property name="receives_default">False</property>
       </object>
     </child>
     <child type="action">
       <object class="GtkButton" id="sync_button">
         <property name="label" translatable="yes">_Sync</property>
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="has_focus">True</property>
-        <property name="can_default">True</property>
-        <property name="has_default">True</property>
-        <property name="receives_default">False</property>
         <property name="use_underline">True</property>
         <signal name="clicked" handler="on_sync_ok_clicked"/>
       </object>
     </child>
     <action-widgets>
       <action-widget response="cancel">cancel_button</action-widget>
-      <action-widget response="accept">sync_button</action-widget>
+      <action-widget response="accept" default="true">sync_button</action-widget>
     </action-widgets>
   </template>
 </interface>
diff --git a/pgp/seahorse-ldap-source.c b/pgp/seahorse-ldap-source.c
index fc17fe40..df4e8377 100644
--- a/pgp/seahorse-ldap-source.c
+++ b/pgp/seahorse-ldap-source.c
@@ -689,7 +689,7 @@ seahorse_ldap_source_init (SeahorseLDAPSource *self)
 typedef struct {
     char *filter;
     LDAP *ldap;
-    GcrSimpleCollection *results;
+    GListStore *results;
 } SearchClosure;
 
 static void
@@ -718,9 +718,9 @@ static const char *PGP_ATTRIBUTES[] = {
 /* Add a key to the key source from an LDAP entry */
 static void
 search_parse_key_from_ldap_entry (SeahorseLDAPSource *self,
-                                  GcrSimpleCollection *results,
-                                  LDAP *ldap,
-                                  LDAPMessage *res)
+                                  GListStore         *results,
+                                  LDAP               *ldap,
+                                  LDAPMessage        *res)
 {
     const char *algo;
     long int timestamp;
@@ -791,7 +791,7 @@ search_parse_key_from_ldap_entry (SeahorseLDAPSource *self,
                       NULL);
 
         seahorse_pgp_key_realize (key);
-        gcr_simple_collection_add (results, G_OBJECT (key));
+        g_list_store_append (results, key);
     }
 }
 
@@ -893,11 +893,11 @@ on_search_connect_completed (GObject *source,
 
 static void
 seahorse_ldap_source_search_async (SeahorseServerSource *source,
-                                   const char *match,
-                                   GcrSimpleCollection *results,
-                                   GCancellable *cancellable,
-                                   GAsyncReadyCallback callback,
-                                   gpointer user_data)
+                                   const char           *match,
+                                   GListStore           *results,
+                                   GCancellable         *cancellable,
+                                   GAsyncReadyCallback   callback,
+                                   void                 *user_data)
 {
     SeahorseLDAPSource *self = SEAHORSE_LDAP_SOURCE (source);
     SearchClosure *closure;
@@ -1270,11 +1270,11 @@ on_export_connect_completed (GObject *source,
 }
 
 static void
-seahorse_ldap_source_export_async (SeahorseServerSource *source,
-                                   const char **keyids,
-                                   GCancellable *cancellable,
-                                   GAsyncReadyCallback callback,
-                                   gpointer user_data)
+seahorse_ldap_source_export_async (SeahorseServerSource  *source,
+                                   const char           **keyids,
+                                   GCancellable          *cancellable,
+                                   GAsyncReadyCallback    callback,
+                                   void                  *user_data)
 {
     SeahorseLDAPSource *self = SEAHORSE_LDAP_SOURCE (source);
     ExportClosure *closure;
@@ -1300,26 +1300,20 @@ seahorse_ldap_source_export_async (SeahorseServerSource *source,
                                         g_steal_pointer (&task));
 }
 
-static gpointer
+static GBytes *
 seahorse_ldap_source_export_finish (SeahorseServerSource *source,
-                                    GAsyncResult *result,
-                                    gsize *size,
-                                    GError **error)
+                                    GAsyncResult         *result,
+                                    GError              **error)
 {
     ExportClosure *closure;
-    gpointer output;
 
-    g_return_val_if_fail (size != NULL, NULL);
     g_return_val_if_fail (g_task_is_valid (result, source), NULL);
 
     if (!g_task_propagate_boolean (G_TASK (result), error))
         return NULL;
 
     closure = g_task_get_task_data (G_TASK (result));
-    *size = closure->data->len;
-    output = g_string_free (closure->data, FALSE);
-    closure->data = NULL;
-    return output;
+    return g_string_free_to_bytes (g_steal_pointer (&closure->data));
 }
 
 /* Initialize the basic class stuff */
diff --git a/pgp/seahorse-pgp-actions.c b/pgp/seahorse-pgp-actions.c
index 22c3ad55..2cc8ab4c 100644
--- a/pgp/seahorse-pgp-actions.c
+++ b/pgp/seahorse-pgp-actions.c
@@ -59,39 +59,48 @@ G_DEFINE_TYPE (SeahorsePgpBackendActions, seahorse_pgp_backend_actions, SEAHORSE
 
 #ifdef WITH_KEYSERVER
 
+static void
+on_keyserver_search_response (GtkDialog *dialog, int response, void *user_data)
+{
+    SeahorseKeyserverSearch *sdialog = SEAHORSE_KEYSERVER_SEARCH (dialog);
+    SeahorseCatalog *catalog = user_data;
+    g_autofree char *search_text = NULL;
+
+    /* If the user pressed "Search", make it happen */
+    if (response == GTK_RESPONSE_ACCEPT) {
+      search_text = seahorse_keyserver_search_get_search_text (sdialog);
+      /* Get search text and save it for next time */
+      g_return_if_fail (search_text && *search_text);
+
+      seahorse_app_settings_set_last_search_text (seahorse_app_settings_instance (),
+                                                  search_text);
+      seahorse_keyserver_results_show (search_text, GTK_WINDOW (catalog));
+    }
+
+    gtk_window_destroy (GTK_WINDOW (dialog));
+    g_clear_object (&catalog);
+}
+
 static void
 on_remote_find (GSimpleAction *action,
                 GVariant *param,
                 gpointer user_data)
 {
-  SeahorseActionGroup *actions = SEAHORSE_ACTION_GROUP (user_data);
-  SeahorseCatalog *catalog = NULL;
-  g_autoptr(SeahorseKeyserverSearch) search_dialog = NULL;
-  int response;
-  g_autofree gchar *search_text = NULL;
-
-  /* Make a new "Find remote keys" dialog */
-  catalog = seahorse_action_group_get_catalog (actions);
-  search_dialog = seahorse_keyserver_search_new (GTK_WINDOW (catalog));
-
-  /* Run it and get the search text */
-  response = gtk_dialog_run (GTK_DIALOG (search_dialog));
-  search_text = seahorse_keyserver_search_get_search_text (search_dialog);
-
-  /* We can safely destroy it */
-  gtk_widget_destroy (GTK_WIDGET (g_steal_pointer (&search_dialog)));
-
-  /* If the user pressed "Search", make it happen */
-  if (response == GTK_RESPONSE_ACCEPT) {
-    /* Get search text and save it for next time */
-    g_return_if_fail (search_text && *search_text);
-
-    seahorse_app_settings_set_last_search_text (seahorse_app_settings_instance (),
-                                                search_text);
-    seahorse_keyserver_results_show (search_text, GTK_WINDOW (catalog));
-  }
-
-  g_clear_object (&catalog);
+    SeahorseActionGroup *actions = SEAHORSE_ACTION_GROUP (user_data);
+    SeahorseCatalog *catalog = NULL;
+    SeahorseKeyserverSearch *search_dialog = NULL;
+
+    /* Make a new "Find remote keys" dialog */
+    catalog = seahorse_action_group_get_catalog (actions);
+    search_dialog = seahorse_keyserver_search_new (GTK_WINDOW (catalog));
+
+    /* Run it and get the search text */
+    g_signal_connect (search_dialog, "response",
+                      G_CALLBACK (on_keyserver_search_response),
+                      g_steal_pointer (&catalog));
+    gtk_window_present (GTK_WINDOW (search_dialog));
+
+    g_clear_object (&catalog);
 }
 
 static void
@@ -101,30 +110,32 @@ on_remote_sync (GSimpleAction *action,
 {
     SeahorseActionGroup *actions = SEAHORSE_ACTION_GROUP (user_data);
     g_autoptr(SeahorseCatalog) catalog = NULL;
-    g_autoptr(GList) keys = NULL;
+    g_autoptr(GListModel) keys = NULL;
     SeahorseKeyserverSync *dialog = NULL;
 
     catalog = seahorse_action_group_get_catalog (actions);
     if (catalog != NULL) {
         g_autoptr(GList) objects = NULL;
 
+        keys = g_list_store_new (SEAHORSE_PGP_TYPE_KEY);
         objects = seahorse_catalog_get_selected_objects (catalog);
         for (GList *l = objects; l != NULL; l = g_list_next (l)) {
             if (SEAHORSE_PGP_IS_KEY (l->data))
-                keys = g_list_prepend (keys, l->data);
+                g_list_store_append (keys, l->data);
         }
     }
 
-    if (keys == NULL) {
+    if (keys == NULL || g_list_model_get_n_items (keys) == 0) {
         SeahorseGpgmeKeyring *keyring;
 
+        g_clear_object (&keys);
         keyring = seahorse_pgp_backend_get_default_keyring (NULL);
-        keys = gcr_collection_get_objects (GCR_COLLECTION (keyring));
+        keys = g_object_ref (G_LIST_MODEL (keyring));
     }
 
     dialog = seahorse_keyserver_sync_new (keys, GTK_WINDOW (catalog));
-    gtk_dialog_run (GTK_DIALOG (dialog));
-    gtk_widget_destroy (GTK_WIDGET (dialog));
+    g_signal_connect (dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
+    gtk_window_present (GTK_WINDOW (dialog));
 }
 
 #endif /* WITH_KEYSERVER */
@@ -134,21 +145,21 @@ on_pgp_generate_key (GSimpleAction *action,
                      GVariant *param,
                      gpointer user_data)
 {
-  SeahorseActionGroup *actions = SEAHORSE_ACTION_GROUP (user_data);
-  SeahorseGpgmeKeyring* keyring;
-  SeahorseCatalog *catalog;
-  GtkDialog *dialog;
+    SeahorseActionGroup *actions = SEAHORSE_ACTION_GROUP (user_data);
+    SeahorseGpgmeKeyring* keyring;
+    SeahorseCatalog *catalog;
+    GtkDialog *dialog;
 
-  keyring = seahorse_pgp_backend_get_default_keyring (NULL);
-  g_return_if_fail (keyring != NULL);
+    keyring = seahorse_pgp_backend_get_default_keyring (NULL);
+    g_return_if_fail (keyring != NULL);
 
-  catalog = seahorse_action_group_get_catalog (actions);
+    catalog = seahorse_action_group_get_catalog (actions);
 
-  dialog = seahorse_gpgme_generate_dialog_new (keyring, GTK_WINDOW (catalog));
-  gtk_dialog_run (GTK_DIALOG (dialog));
-  gtk_widget_destroy (GTK_WIDGET (dialog));
+    dialog = seahorse_gpgme_generate_dialog_new (keyring, GTK_WINDOW (catalog));
+    g_signal_connect (dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
+    gtk_window_present (GTK_WINDOW (dialog));
 
-  g_clear_object (&catalog);
+    g_clear_object (&catalog);
 }
 
 static const GActionEntry ACTION_ENTRIES[] = {
diff --git a/pgp/seahorse-pgp-backend.c b/pgp/seahorse-pgp-backend.c
index 1ffd889a..f93427a8 100644
--- a/pgp/seahorse-pgp-backend.c
+++ b/pgp/seahorse-pgp-backend.c
@@ -68,10 +68,10 @@ static GParamSpec *obj_props[N_PROPS] = { NULL, };
 
 static void         seahorse_pgp_backend_iface            (SeahorseBackendIface *iface);
 
-static void         seahorse_pgp_backend_collection_init  (GcrCollectionIface *iface);
+static void         seahorse_pgp_backend_list_model_init  (GListModelInterface *iface);
 
 G_DEFINE_TYPE_WITH_CODE (SeahorsePgpBackend, seahorse_pgp_backend, G_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (GCR_TYPE_COLLECTION, seahorse_pgp_backend_collection_init);
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, seahorse_pgp_backend_list_model_init);
                          G_IMPLEMENT_INTERFACE (SEAHORSE_TYPE_BACKEND, seahorse_pgp_backend_iface);
 );
 
@@ -326,33 +326,32 @@ seahorse_pgp_backend_class_init (SeahorsePgpBackendClass *klass)
     g_object_class_override_property (gobject_class, PROP_LOADED, "loaded");
 }
 
-static guint
-seahorse_pgp_backend_get_length (GcrCollection *collection)
+static GType
+seahorse_pgp_backend_get_item_type (GListModel *model)
 {
-       return 1;
+    return SEAHORSE_TYPE_GPGME_KEYRING;
 }
 
-static GList *
-seahorse_pgp_backend_get_objects (GcrCollection *collection)
+static unsigned int
+seahorse_pgp_backend_get_n_items (GListModel *model)
 {
-       SeahorsePgpBackend *self = SEAHORSE_PGP_BACKEND (collection);
-       return g_list_append (NULL, self->keyring);
+    return 1;
 }
 
-static gboolean
-seahorse_pgp_backend_contains (GcrCollection *collection,
-                               GObject *object)
+static void *
+seahorse_pgp_backend_get_item (GListModel   *model,
+                               unsigned int  position)
 {
-       SeahorsePgpBackend *self = SEAHORSE_PGP_BACKEND (collection);
-       return G_OBJECT (self->keyring) == object;
+    SeahorsePgpBackend *self = SEAHORSE_PGP_BACKEND (model);
+    return (position == 0)? g_object_ref (self->keyring) : NULL;
 }
 
 static void
-seahorse_pgp_backend_collection_init (GcrCollectionIface *iface)
+seahorse_pgp_backend_list_model_init (GListModelInterface *iface)
 {
-       iface->contains = seahorse_pgp_backend_contains;
-       iface->get_length = seahorse_pgp_backend_get_length;
-       iface->get_objects = seahorse_pgp_backend_get_objects;
+    iface->get_item_type = seahorse_pgp_backend_get_item_type;
+    iface->get_n_items = seahorse_pgp_backend_get_n_items;
+    iface->get_item = seahorse_pgp_backend_get_item;
 }
 
 static SeahorsePlace *
@@ -574,8 +573,8 @@ on_source_search_ready (GObject *source,
 
 void
 seahorse_pgp_backend_search_remote_async (SeahorsePgpBackend *self,
-                                          const gchar *search,
-                                          GcrSimpleCollection *results,
+                                          const char *search,
+                                          GListStore *results,
                                           GCancellable *cancellable,
                                           GAsyncReadyCallback callback,
                                           gpointer user_data)
@@ -585,8 +584,9 @@ seahorse_pgp_backend_search_remote_async (SeahorsePgpBackend *self,
     g_autoptr(GHashTable) servers = NULL;
     g_auto(GStrv) names = NULL;
 
-    self = self ? self : seahorse_pgp_backend_get ();
     g_return_if_fail (SEAHORSE_PGP_IS_BACKEND (self));
+    g_return_if_fail (search);
+    g_return_if_fail (G_IS_LIST_STORE (results));
 
     /* Get a list of all selected key servers */
     names = seahorse_app_settings_get_last_search_servers (seahorse_app_settings_instance ());
@@ -670,7 +670,7 @@ on_source_transfer_ready (GObject *source,
 
 void
 seahorse_pgp_backend_transfer_async (SeahorsePgpBackend *self,
-                                     GList *keys,
+                                     GListModel *keys,
                                      SeahorsePlace *to,
                                      GCancellable *cancellable,
                                      GAsyncReadyCallback callback,
@@ -681,7 +681,6 @@ seahorse_pgp_backend_transfer_async (SeahorsePgpBackend *self,
     g_autoptr(GTask) task = NULL;
     SeahorsePlace *from;
 
-    self = self ? self : seahorse_pgp_backend_get ();
     g_return_if_fail (SEAHORSE_PGP_IS_BACKEND (self));
     g_return_if_fail (SEAHORSE_IS_PLACE (to));
 
@@ -689,6 +688,8 @@ seahorse_pgp_backend_transfer_async (SeahorsePgpBackend *self,
     closure = g_new0 (transfer_closure, 1);
     g_task_set_task_data (task, closure, transfer_closure_free);
 
+       // XXX
+#if 0
     keys = g_list_copy (keys);
     /* Sort by key place */
     keys = seahorse_util_objects_sort_by_place (keys);
@@ -717,6 +718,7 @@ seahorse_pgp_backend_transfer_async (SeahorsePgpBackend *self,
         g_list_free (keys);
         keys = next;
     }
+#endif
 
     if (closure->num_transfers == 0)
         g_task_return_boolean (task, TRUE);
diff --git a/pgp/seahorse-pgp-backend.h b/pgp/seahorse-pgp-backend.h
index 5f6a369a..fd570a1d 100644
--- a/pgp/seahorse-pgp-backend.h
+++ b/pgp/seahorse-pgp-backend.h
@@ -68,8 +68,8 @@ void                   seahorse_pgp_backend_remove_remote        (SeahorsePgpBac
                                                                   const char         *uri);
 
 void                   seahorse_pgp_backend_search_remote_async  (SeahorsePgpBackend *self,
-                                                                  const gchar *search,
-                                                                  GcrSimpleCollection *results,
+                                                                  const char *search,
+                                                                  GListStore *results,
                                                                   GCancellable *cancellable,
                                                                   GAsyncReadyCallback callback,
                                                                   gpointer user_data);
@@ -78,12 +78,12 @@ gboolean               seahorse_pgp_backend_search_remote_finish (SeahorsePgpBac
                                                                   GAsyncResult *result,
                                                                   GError **error);
 
-void                   seahorse_pgp_backend_transfer_async       (SeahorsePgpBackend *self,
-                                                                  GList *keys,
-                                                                  SeahorsePlace *to,
-                                                                  GCancellable *cancellable,
-                                                                  GAsyncReadyCallback callback,
-                                                                  gpointer user_data);
+void                   seahorse_pgp_backend_transfer_async       (SeahorsePgpBackend  *self,
+                                                                  GListModel          *keys,
+                                                                  SeahorsePlace       *to,
+                                                                  GCancellable        *cancellable,
+                                                                  GAsyncReadyCallback  callback,
+                                                                  void                *user_data);
 
 gboolean               seahorse_pgp_backend_transfer_finish      (SeahorsePgpBackend *self,
                                                                   GAsyncResult *result,
diff --git a/pgp/seahorse-pgp-dialogs.h b/pgp/seahorse-pgp-dialogs.h
index 5e062adb..db627906 100644
--- a/pgp/seahorse-pgp-dialogs.h
+++ b/pgp/seahorse-pgp-dialogs.h
@@ -29,12 +29,3 @@
 #include "pgp/seahorse-pgp-key.h"
 
 SeahorsePgpKey* seahorse_signer_get                 (GtkWindow *parent);
-
-
-#define SEAHORSE_PGP_TYPE_KEY_PROPERTIES (seahorse_pgp_key_properties_get_type ())
-G_DECLARE_FINAL_TYPE (SeahorsePgpKeyProperties, seahorse_pgp_key_properties,
-                      SEAHORSE_PGP, KEY_PROPERTIES,
-                      GtkDialog);
-
-GtkWindow *     seahorse_pgp_key_properties_new    (SeahorsePgpKey *pkey,
-                                                    GtkWindow *parent);
diff --git a/pgp/seahorse-pgp-key-properties.c b/pgp/seahorse-pgp-key-properties.c
index 4c308195..d8456a3f 100644
--- a/pgp/seahorse-pgp-key-properties.c
+++ b/pgp/seahorse-pgp-key-properties.c
@@ -23,8 +23,7 @@
 
 #include "config.h"
 
-#include "seahorse-pgp-dialogs.h"
-#include "seahorse-gpgme-add-subkey.h"
+#include "seahorse-pgp-key-properties.h"
 #include "seahorse-gpgme-add-uid.h"
 #include "seahorse-gpgme-dialogs.h"
 #include "seahorse-gpgme-expires-dialog.h"
@@ -52,322 +51,46 @@
 
 #include <string.h>
 
-#define PUBLIC_KEY_PROPERTIES_UI "/org/gnome/Seahorse/seahorse-pgp-public-key-properties.ui"
-#define PRIVATE_KEY_PROPERTIES_UI  "/org/gnome/Seahorse/seahorse-pgp-private-key-properties.ui"
-
 enum {
     PROP_0,
     PROP_KEY,
+    N_PROPS
 };
+static GParamSpec *properties[N_PROPS] = { NULL, };
 
 struct _SeahorsePgpKeyProperties {
-    GtkDialog parent_instance;
+    GtkWindow parent_instance;
 
     SeahorsePgpKey *key;
 
-    GSimpleActionGroup *action_group;
-
     /* Common widgets */
+    GtkWidget *window_title;
+
     GtkWidget *revoked_area;
     GtkWidget *expired_area;
     GtkWidget *expired_message;
 
-    GtkImage *photoid;
-    GtkEventBox *photo_event_box;
-
     GtkWidget *name_label;
     GtkWidget *email_label;
     GtkWidget *comment_label;
     GtkWidget *keyid_label;
     GtkWidget *fingerprint_label;
     GtkWidget *expires_label;
-    GtkWidget *photo_previous_button;
-    GtkWidget *photo_next_button;
     GtkWidget *ownertrust_combobox;
     GtkWidget *uids_container;
     GtkWidget *subkeys_container;
 
     /* Private key widgets */
-    GMenuModel *actions_menu;
-    GtkWidget *owner_photo_frame;
-    GtkWidget *owner_photo_add_button;
-    GtkWidget *owner_photo_delete_button;
-    GtkWidget *owner_photo_primary_button;
+    GtkWidget *menu_button;
 
     /* Public key widgets */
     GtkWidget *indicate_trust_row;
     GtkWidget *trust_page;
     GtkWidget *trust_sign_row;
-    GtkWidget *trust_revoke_row;
     GtkWidget *trust_marginal_switch;
 };
 
-G_DEFINE_TYPE (SeahorsePgpKeyProperties, seahorse_pgp_key_properties, GTK_TYPE_DIALOG)
-
-static void
-set_action_enabled (SeahorsePgpKeyProperties *self,
-                    const char               *action_str,
-                    gboolean                  enabled)
-{
-    GAction *action;
-
-    action = g_action_map_lookup_action (G_ACTION_MAP (self->action_group),
-                                         action_str);
-    g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
-}
-
-static void
-on_uids_add (GSimpleAction *action, GVariant *param, void *user_data)
-{
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
-    g_return_if_fail (SEAHORSE_GPGME_IS_KEY (self->key));
-    seahorse_gpgme_add_uid_run_dialog (SEAHORSE_GPGME_KEY (self->key),
-                                       GTK_WINDOW (self));
-}
-
-/* drag-n-drop uri data */
-enum {
-    TARGET_URI,
-};
-
-static GtkTargetEntry target_list[] = {
-    { "text/uri-list", 0, TARGET_URI } };
-
-static void
-on_pgp_owner_photo_drag_received (GtkWidget        *widget,
-                                  GdkDragContext   *context,
-                                  int               x,
-                                  int               y,
-                                  GtkSelectionData *sel_data,
-                                  unsigned int      target_type,
-                                  unsigned int      time,
-                                  void             *user_data)
-{
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
-    gboolean dnd_success = FALSE;
-    int len = 0;
-
-    g_return_if_fail (SEAHORSE_GPGME_IS_KEY (self->key));
-
-    /*
-     * This needs to be improved, support should be added for remote images
-     * and there has to be a better way to get rid of the trailing \r\n appended
-     * to the end of the path after the call to g_filename_from_uri
-     */
-    if ((sel_data != NULL) && (gtk_selection_data_get_length (sel_data) >= 0)) {
-        g_auto(GStrv) uri_list = NULL;
-
-        g_return_if_fail (target_type == TARGET_URI);
-
-        uri_list = gtk_selection_data_get_uris (sel_data);
-        while (uri_list && uri_list[len]) {
-            g_autofree char *uri = NULL;
-
-            uri = g_filename_from_uri (uri_list[len], NULL, NULL);
-            if (!uri)
-                continue;
-
-            dnd_success = seahorse_gpgme_photo_add (SEAHORSE_GPGME_KEY (self->key),
-                                                    GTK_WINDOW (self), uri);
-            if (!dnd_success)
-                break;
-            len++;
-        }
-    }
-
-    gtk_drag_finish (context, dnd_success, FALSE, time);
-}
-
-static void
-on_photos_add (GSimpleAction *action, GVariant *param, void *user_data)
-{
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
-
-    g_return_if_fail (SEAHORSE_GPGME_IS_KEY (self->key));
-
-    if (seahorse_gpgme_photo_add (SEAHORSE_GPGME_KEY (self->key),
-                                  GTK_WINDOW (self), NULL))
-        g_object_set_data (G_OBJECT (self), "current-photoid", NULL);
-}
-
-static void
-on_photos_delete (GSimpleAction *action, GVariant *param, void *user_data)
-{
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
-    SeahorseGpgmePhoto *photo;
-
-    photo = g_object_get_data (G_OBJECT (self), "current-photoid");
-    g_return_if_fail (SEAHORSE_IS_GPGME_PHOTO (photo));
-
-    if (seahorse_gpgme_key_op_photo_delete (photo))
-        g_object_set_data (G_OBJECT (self), "current-photoid", NULL);
-}
-
-static void
-on_photos_make_primary (GSimpleAction *action, GVariant *param, void *user_data)
-{
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
-    gpgme_error_t gerr;
-    SeahorseGpgmePhoto *photo;
-
-    photo = g_object_get_data (G_OBJECT (self), "current-photoid");
-    g_return_if_fail (SEAHORSE_IS_GPGME_PHOTO (photo));
-
-    gerr = seahorse_gpgme_key_op_photo_primary (photo);
-    if (!GPG_IS_OK (gerr))
-        seahorse_gpgme_handle_error (gerr, _("Couldn’t change primary photo"));
-}
-
-static void
-set_photoid_state (SeahorsePgpKeyProperties *self)
-{
-    SeahorseUsage etype;
-    SeahorsePgpPhoto *photo;
-    gboolean is_gpgme;
-    GdkPixbuf *pixbuf;
-    GListModel *photos;
-    unsigned int n_photos;
-    g_autoptr(SeahorsePgpPhoto) first_photo = NULL;
-    g_autoptr(SeahorsePgpPhoto) last_photo = NULL;
-
-    etype = seahorse_object_get_usage (SEAHORSE_OBJECT (self->key));
-    photos = seahorse_pgp_key_get_photos (self->key);
-    n_photos = g_list_model_get_n_items (photos);
-
-    photo = g_object_get_data (G_OBJECT (self), "current-photoid");
-    g_return_if_fail (!photo || SEAHORSE_PGP_IS_PHOTO (photo));
-    is_gpgme = SEAHORSE_GPGME_IS_KEY (self->key);
-
-    if (etype == SEAHORSE_USAGE_PRIVATE_KEY) {
-        gtk_widget_set_visible (self->owner_photo_add_button, is_gpgme);
-        gtk_widget_set_visible (self->owner_photo_primary_button,
-                                is_gpgme && n_photos> 1);
-        gtk_widget_set_visible (self->owner_photo_delete_button,
-                                is_gpgme && photo);
-    }
-
-    /* Display both of these when there is more than one photo id */
-    gtk_widget_set_visible (self->photo_previous_button, n_photos > 1);
-    gtk_widget_set_visible (self->photo_next_button, n_photos > 1);
-
-    /* Change sensitivity if first/last photo id */
-    first_photo = g_list_model_get_item (photos, 0);
-    last_photo = g_list_model_get_item (photos, MAX (n_photos - 1, 0));
-    set_action_enabled (self, "photos.previous",
-                        photo && photo != first_photo);
-    set_action_enabled (self, "photos.next",
-                        photo && photo != last_photo);
-
-    pixbuf = photo ? seahorse_pgp_photo_get_pixbuf (photo) : NULL;
-    if (pixbuf)
-        gtk_image_set_from_pixbuf (self->photoid, pixbuf);
-}
-
-static void
-do_photo_id (SeahorsePgpKeyProperties *self)
-{
-    GListModel *photos;
-    g_autoptr(SeahorsePgpPhoto) first_photo = NULL;
-
-    photos = seahorse_pgp_key_get_photos (self->key);
-    first_photo = g_list_model_get_item (photos, 0);
-    if (first_photo)
-        g_object_set_data_full (G_OBJECT (self), "current-photoid",
-                                g_steal_pointer (&first_photo), g_object_unref);
-    else
-        g_object_set_data (G_OBJECT (self), "current-photoid", NULL);
-
-    set_photoid_state (self);
-}
-
-static unsigned int
-find_photo_index (GListModel *photos, SeahorsePgpPhoto *photo)
-{
-    for (unsigned int i = 0; i < g_list_model_get_n_items (photos); i++) {
-        g_autoptr(SeahorsePgpPhoto) foto = g_list_model_get_item (photos, i);
-
-        if (photo == foto)
-            return i;
-    }
-
-    g_return_val_if_reached (0);
-}
-
-static void
-on_photos_next (GSimpleAction *action, GVariant *param, void *user_data)
-{
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
-    SeahorsePgpPhoto *photo;
-    GListModel *photos;
-
-    photos = seahorse_pgp_key_get_photos (self->key);
-    photo = g_object_get_data (G_OBJECT (self), "current-photoid");
-    if (photo) {
-        unsigned int index;
-        g_autoptr(SeahorsePgpPhoto) next = NULL;
-
-        index = find_photo_index (photos, photo);
-        next = g_list_model_get_item (photos, index + 1);
-        if (next)
-            g_object_set_data_full (G_OBJECT (self), "current-photoid",
-                                    g_steal_pointer (&next),
-                                    g_object_unref);
-    }
-
-    set_photoid_state (self);
-}
-
-static void
-on_photos_previous (GSimpleAction *action, GVariant *param, void *user_data)
-{
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
-    SeahorsePgpPhoto *photo;
-    GListModel *photos;
-
-    photos = seahorse_pgp_key_get_photos (self->key);
-    photo = g_object_get_data (G_OBJECT (self), "current-photoid");
-    if (photo) {
-        unsigned int index;
-
-        index = find_photo_index (photos, photo);
-        if (index > 0) {
-            g_autoptr(SeahorsePgpPhoto) prev = NULL;
-
-            prev = g_list_model_get_item (photos, index - 1);
-            g_object_set_data_full (G_OBJECT (self), "current-photoid",
-                                    g_steal_pointer (&prev),
-                                    g_object_unref);
-        }
-    }
-
-    set_photoid_state (self);
-}
-
-static void
-on_pgp_owner_photoid_button (GtkWidget *widget,
-                             GdkEvent  *event,
-                             void      *user_data)
-{
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
-    const char *action_str;
-
-    if (event->type != GDK_SCROLL)
-        return;
-
-    switch (((GdkEventScroll *) event)->direction) {
-    case GDK_SCROLL_UP:
-        action_str = "photos.previous";
-        break;
-    case GDK_SCROLL_DOWN:
-        action_str = "photos.next";
-        break;
-    default:
-        return;
-    }
-
-    g_action_group_activate_action (G_ACTION_GROUP (self->action_group),
-                                    action_str, NULL);
-}
+G_DEFINE_TYPE (SeahorsePgpKeyProperties, seahorse_pgp_key_properties, GTK_TYPE_WINDOW)
 
 static void
 on_gpgme_key_change_pass_done (GObject      *source,
@@ -388,9 +111,9 @@ on_gpgme_key_change_pass_done (GObject      *source,
 }
 
 static void
-on_change_password (GSimpleAction *action, GVariant *param, void *user_data)
+on_change_password (GtkWidget* widget, const char *action_name, GVariant *param)
 {
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
+    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (widget);
     SeahorseUsage usage;
 
     usage = seahorse_object_get_usage (SEAHORSE_OBJECT (self->key));
@@ -403,42 +126,6 @@ on_change_password (GSimpleAction *action, GVariant *param, void *user_data)
                                              g_object_ref (self));
 }
 
-static void
-setup_titlebar (SeahorsePgpKeyProperties *self)
-{
-    const char *name;
-    GtkWidget *headerbar;
-    GtkWidget *menu_icon;
-    GtkWidget *menu_button;
-    g_autofree char *title = NULL;
-
-    name = seahorse_pgp_key_get_primary_name (self->key);
-
-    headerbar = gtk_header_bar_new ();
-    gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (headerbar), TRUE);
-    gtk_widget_show (headerbar);
-
-    menu_button = gtk_menu_button_new ();
-    menu_icon = gtk_image_new_from_icon_name ("open-menu-symbolic", GTK_ICON_SIZE_BUTTON);
-    gtk_button_set_image (GTK_BUTTON (menu_button), menu_icon);
-    gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (menu_button), self->actions_menu);
-
-    gtk_header_bar_pack_start (GTK_HEADER_BAR (headerbar), menu_button);
-
-    if (seahorse_pgp_key_is_private_key (self->key)) {
-        /* Translators: the 1st part of the title is the owner's name */
-        title = g_strdup_printf (_("%s — Private key"), name);
-        gtk_widget_show (menu_button);
-    } else {
-        /* Translators: the 1st part of the title is the owner's name */
-        title = g_strdup_printf (_("%s — Public key"), name);
-        gtk_widget_hide (menu_button);
-    }
-    gtk_header_bar_set_title (GTK_HEADER_BAR (headerbar), title);
-
-    gtk_window_set_titlebar (GTK_WINDOW (self), headerbar);
-}
-
 static void
 do_owner (SeahorsePgpKeyProperties *self)
 {
@@ -521,9 +208,6 @@ do_owner (SeahorsePgpKeyProperties *self)
     else
         expires_str = g_strdup (C_("Expires", "Never"));
     gtk_label_set_text (GTK_LABEL (self->expires_label), expires_str);
-
-    setup_titlebar (self);
-    do_photo_id (self);
 }
 
 /* trust combo box list */
@@ -538,49 +222,6 @@ const GType trust_columns[] = {
     G_TYPE_INT      /* validity */
 };
 
-static void
-on_add_subkey_completed (GObject *object, GAsyncResult *res, void *user_data)
-{
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
-    g_autoptr(GError) error = NULL;
-
-    if (!seahorse_gpgme_key_op_add_subkey_finish (SEAHORSE_GPGME_KEY (self->key),
-                                                  res, &error)) {
-        seahorse_util_handle_error (&error, self, NULL);
-    }
-}
-
-static void
-on_subkeys_add (GSimpleAction *action, GVariant *param, void *user_data)
-{
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
-    SeahorseGpgmeAddSubkey *dialog;
-    int response;
-    SeahorseKeyEncType type;
-    unsigned int length;
-    GDateTime *expires;
-
-    g_return_if_fail (SEAHORSE_GPGME_IS_KEY (self->key));
-
-    dialog = seahorse_gpgme_add_subkey_new (SEAHORSE_GPGME_KEY (self->key),
-                                            GTK_WINDOW (self));
-
-    response = gtk_dialog_run (GTK_DIALOG (dialog));
-    if (response != GTK_RESPONSE_OK) {
-        gtk_widget_destroy (GTK_WIDGET (dialog));
-        return;
-    }
-
-    length = seahorse_gpgme_add_subkey_get_keysize (dialog);
-    type = seahorse_gpgme_add_subkey_get_active_type (dialog);
-    expires = seahorse_gpgme_add_subkey_get_expires (dialog);
-    seahorse_gpgme_key_op_add_subkey_async (SEAHORSE_GPGME_KEY (self->key),
-                                            type, length, expires, NULL,
-                                            on_add_subkey_completed, self);
-
-    gtk_widget_destroy (GTK_WIDGET (dialog));
-}
-
 static void
 on_pgp_details_trust_changed (GtkComboBox *selection, void *user_data)
 {
@@ -621,11 +262,11 @@ on_export_complete (GObject *source, GAsyncResult *result, void *user_data)
 static void
 export_key_to_file (SeahorsePgpKeyProperties *self, gboolean secret)
 {
-    GList *exporters = NULL;
+    g_autolist(SeahorseExporter) exporters = NULL;
     GtkWindow *window;
     g_autofree char *directory = NULL;
     g_autoptr(GFile) file = NULL;
-    SeahorseExporter *exporter = NULL;
+    g_autoptr(SeahorseExporter) exporter = NULL;
 
     exporters = g_list_append (exporters,
                                seahorse_gpgme_exporter_new (G_OBJECT (self->key), TRUE, secret));
@@ -635,31 +276,28 @@ export_key_to_file (SeahorsePgpKeyProperties *self, gboolean secret)
         seahorse_exporter_export_to_file (exporter, file, TRUE, NULL,
                                           on_export_complete, g_object_ref (window));
     }
-
-    g_clear_object (&exporter);
-    g_list_free_full (exporters, g_object_unref);
 }
 
 static void
-on_export_secret (GSimpleAction *action, GVariant *param, void *user_data)
+on_export_secret (GtkWidget* widget, const char *action_name, GVariant *param)
 {
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
+    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (widget);
 
     export_key_to_file (self, TRUE);
 }
 
 static void
-on_export_public (GSimpleAction *action, GVariant *param, void *user_data)
+on_export_public (GtkWidget* widget, const char *action_name, GVariant *param)
 {
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
+    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (widget);
 
     export_key_to_file (self, FALSE);
 }
 
 static void
-on_change_expires (GSimpleAction *action, GVariant *param, void *user_data)
+on_change_expires (GtkWidget* widget, const char *action_name, GVariant *param)
 {
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
+    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (widget);
     GListModel *subkeys;
     g_autoptr(SeahorseGpgmeSubkey) subkey = NULL;
     GtkDialog *dialog;
@@ -671,8 +309,8 @@ on_change_expires (GSimpleAction *action, GVariant *param, void *user_data)
     g_return_if_fail (subkey);
 
     dialog = seahorse_gpgme_expires_dialog_new (subkey, GTK_WINDOW (self));
-    gtk_dialog_run (dialog);
-    gtk_widget_destroy (GTK_WIDGET (dialog));
+    g_signal_connect (dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
+    gtk_window_present (GTK_WINDOW (dialog));
 }
 
 static void
@@ -761,9 +399,9 @@ do_details (SeahorsePgpKeyProperties *self)
 }
 
 static void
-on_trust_marginal_changed (GSimpleAction *action,
-                           GVariant      *new_state,
-                           void          *user_data)
+on_trust_marginal_switch_notify_active (GObject    *object,
+                                        GParamSpec *new_state,
+                                        void       *user_data)
 {
     SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
     SeahorseValidity trust;
@@ -771,9 +409,8 @@ on_trust_marginal_changed (GSimpleAction *action,
 
     g_return_if_fail (SEAHORSE_GPGME_IS_KEY (self->key));
 
-    trust = g_variant_get_boolean (new_state) ?
+    trust = gtk_switch_get_active (GTK_SWITCH (object)) ?
             SEAHORSE_VALIDITY_MARGINAL : SEAHORSE_VALIDITY_UNKNOWN;
-    g_simple_action_set_state (action, new_state);
 
     if (seahorse_pgp_key_get_trust (self->key) != trust) {
         err = seahorse_gpgme_key_op_set_trust (SEAHORSE_GPGME_KEY (self->key), trust);
@@ -784,17 +421,16 @@ on_trust_marginal_changed (GSimpleAction *action,
 
 /* Add a signature */
 static void
-on_sign_key (GSimpleAction *action, GVariant *param, void *user_data)
+on_sign_key (GtkWidget* widget, const char *action_name, GVariant *param)
 {
-    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
+    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (widget);
     SeahorseGpgmeSignDialog *dialog;
 
     g_return_if_fail (SEAHORSE_GPGME_IS_KEY (self->key));
 
     dialog = seahorse_gpgme_sign_dialog_new (SEAHORSE_OBJECT (self->key));
-
-    gtk_dialog_run (GTK_DIALOG (dialog));
-    gtk_widget_destroy (GTK_WIDGET (dialog));
+    g_signal_connect (dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
+    gtk_window_present (GTK_WINDOW (dialog));
 }
 
 static gboolean
@@ -822,7 +458,6 @@ static void
 do_trust (SeahorsePgpKeyProperties *self)
 {
     gboolean sigpersonal;
-    GAction *trust_action;
 
     if (seahorse_object_get_usage (SEAHORSE_OBJECT (self->key)) != SEAHORSE_USAGE_PUBLIC_KEY)
         return;
@@ -832,7 +467,6 @@ do_trust (SeahorsePgpKeyProperties *self)
         gtk_widget_set_visible (self->trust_marginal_switch, TRUE);
         gtk_widget_set_sensitive (self->trust_marginal_switch, FALSE);
         gtk_widget_set_visible (self->trust_sign_row, FALSE);
-        gtk_widget_set_visible (self->trust_revoke_row, FALSE);
 
     /* Local keys */
     } else {
@@ -865,45 +499,19 @@ do_trust (SeahorsePgpKeyProperties *self)
 
         /* Managed and unmanaged areas */
         gtk_widget_set_visible (self->trust_marginal_switch, managed);
-        trust_action = g_action_map_lookup_action (G_ACTION_MAP (self->action_group),
-                                                   "trust-marginal");
 
         /* Managed check boxes */
         if (managed) {
-            GVariant *state;
-            state = g_variant_new_boolean (trust != SEAHORSE_VALIDITY_UNKNOWN);
-            g_simple_action_set_state (G_SIMPLE_ACTION (trust_action), state);
+            gtk_switch_set_active (GTK_SWITCH (self->trust_marginal_switch),
+                                   (trust != SEAHORSE_VALIDITY_UNKNOWN));
         }
 
         /* Signing and revoking */
         sigpersonal = key_have_signatures (self->key, SKEY_PGPSIG_PERSONAL);
         gtk_widget_set_visible (self->trust_sign_row, !sigpersonal);
-        gtk_widget_set_visible (self->trust_revoke_row, sigpersonal);
     }
 }
 
-static const GActionEntry PRIVATE_KEY_ACTIONS[] = {
-    { "change-password",  on_change_password  },
-    { "change-expires",   on_change_expires   },
-    { "export-secret",    on_export_secret    },
-    { "export-public",    on_export_public    },
-    { "uids.add",         on_uids_add         },
-    { "subkeys.add",      on_subkeys_add      },
-    { "photos.add",           on_photos_add           },
-    { "photos.delete",        on_photos_delete        },
-    { "photos.previous",      on_photos_previous      },
-    { "photos.next",          on_photos_next          },
-    { "photos.make-primary",  on_photos_make_primary  },
-};
-
-static const GActionEntry PUBLIC_KEY_ACTIONS[] = {
-    { "sign-key",         on_sign_key  },
-    /* { "revoke-key",    on_revoke_key  },  TODO */
-    { "trust-marginal",   seahorse_util_toggle_action, NULL, "false", on_trust_marginal_changed },
-    { "photos.previous",  on_photos_previous      },
-    { "photos.next",      on_photos_next          },
-};
-
 static void
 key_notify (GObject *object, GParamSpec *pspec, void *user_data)
 {
@@ -914,77 +522,12 @@ key_notify (GObject *object, GParamSpec *pspec, void *user_data)
     do_details (self);
 }
 
-static void
-get_common_widgets (SeahorsePgpKeyProperties *self, GtkBuilder *builder)
-{
-    GtkWidget *uids_listbox, *subkeys_listbox;
-
-    self->name_label = GTK_WIDGET (gtk_builder_get_object (builder, "name_label"));
-    self->email_label = GTK_WIDGET (gtk_builder_get_object (builder, "email_label"));
-    self->comment_label = GTK_WIDGET (gtk_builder_get_object (builder, "comment_label"));
-    self->keyid_label = GTK_WIDGET (gtk_builder_get_object (builder, "keyid_label"));
-    self->fingerprint_label = GTK_WIDGET (gtk_builder_get_object (builder, "fingerprint_label"));
-    self->expires_label = GTK_WIDGET (gtk_builder_get_object (builder, "expires_label"));
-    self->photo_previous_button = GTK_WIDGET (gtk_builder_get_object (builder, "photo_previous_button"));
-    self->photo_next_button = GTK_WIDGET (gtk_builder_get_object (builder, "photo_next_button"));
-    self->revoked_area = GTK_WIDGET (gtk_builder_get_object (builder, "revoked_area"));
-    self->expired_area = GTK_WIDGET (gtk_builder_get_object (builder, "expired_area"));
-    self->expired_message = GTK_WIDGET (gtk_builder_get_object (builder, "expired_message"));
-    self->photoid = GTK_IMAGE (gtk_builder_get_object (builder, "photoid"));
-    self->photo_event_box = GTK_EVENT_BOX (gtk_builder_get_object (builder, "photo-event-box"));
-    self->ownertrust_combobox = GTK_WIDGET (gtk_builder_get_object (builder, "ownertrust_combobox"));
-    self->uids_container = GTK_WIDGET (gtk_builder_get_object (builder, "uids_container"));
-    self->subkeys_container = GTK_WIDGET (gtk_builder_get_object (builder, "subkeys_container"));
-
-    g_signal_connect_object (self->photo_event_box, "scroll-event",
-                             G_CALLBACK (on_pgp_owner_photoid_button),
-                             self, 0);
-    g_signal_connect_object (self->ownertrust_combobox, "changed",
-                             G_CALLBACK (on_pgp_details_trust_changed),
-                             self, 0);
-
-    uids_listbox = seahorse_pgp_uid_list_box_new (self->key);
-    gtk_widget_show (uids_listbox);
-    gtk_container_add (GTK_CONTAINER (self->uids_container),
-                       uids_listbox);
-
-    subkeys_listbox = seahorse_pgp_subkey_list_box_new (self->key);
-    gtk_widget_show (subkeys_listbox);
-    gtk_container_add (GTK_CONTAINER (self->subkeys_container),
-                       subkeys_listbox);
-}
-
 static void
 create_public_key_dialog (SeahorsePgpKeyProperties *self)
 {
-    g_autoptr(GtkBuilder) builder = NULL;
-    GtkWidget *content_area, *content;
     const char *user;
     g_autofree char *sign_text = NULL;
-    g_autofree char *revoke_text = NULL;
-
-    builder = gtk_builder_new_from_resource (PUBLIC_KEY_PROPERTIES_UI);
-    gtk_builder_connect_signals (builder, self);
-
-    content_area = gtk_dialog_get_content_area (GTK_DIALOG (self));
-    content = GTK_WIDGET (gtk_builder_get_object (builder, "window_content"));
-    gtk_container_add (GTK_CONTAINER (content_area), content);
-
-    g_action_map_add_action_entries (G_ACTION_MAP (self->action_group),
-                                     PUBLIC_KEY_ACTIONS,
-                                     G_N_ELEMENTS (PUBLIC_KEY_ACTIONS),
-                                     self);
-    gtk_widget_insert_action_group (GTK_WIDGET (self),
-                                    "props",
-                                    G_ACTION_GROUP (self->action_group));
-
-    get_common_widgets (self, builder);
-
-    self->trust_page = GTK_WIDGET (gtk_builder_get_object (builder, "trust-page"));
-    self->indicate_trust_row = GTK_WIDGET (gtk_builder_get_object (builder, "indicate_trust_row"));
-    self->trust_sign_row = GTK_WIDGET (gtk_builder_get_object (builder, "trust_sign_row"));
-    self->trust_revoke_row = GTK_WIDGET (gtk_builder_get_object (builder, "trust_revoke_row"));
-    self->trust_marginal_switch = GTK_WIDGET (gtk_builder_get_object (builder, "trust-marginal-switch"));
+    g_autofree char *sign_text_esc = NULL;
 
     setup_trust_combobox (self);
     do_owner (self);
@@ -995,56 +538,18 @@ create_public_key_dialog (SeahorsePgpKeyProperties *self)
     user = seahorse_object_get_label (SEAHORSE_OBJECT (self->key));
 
     sign_text = g_strdup_printf(_("I believe “%s” is the owner of this key"),
-                               user);
-    hdy_action_row_set_subtitle (HDY_ACTION_ROW (self->trust_sign_row), sign_text);
-    hdy_action_row_set_subtitle_lines (HDY_ACTION_ROW (self->trust_sign_row), 0);
-
-    revoke_text = g_strdup_printf(_("I no longer trust that “%s” owns this key"),
-                                  user);
-    hdy_action_row_set_subtitle (HDY_ACTION_ROW (self->trust_revoke_row), revoke_text);
-    hdy_action_row_set_subtitle_lines (HDY_ACTION_ROW (self->trust_revoke_row), 0);
+                                user);
+    sign_text_esc = g_markup_escape_text (sign_text, -1);
+    adw_action_row_set_subtitle (ADW_ACTION_ROW (self->trust_sign_row), sign_text_esc);
+    adw_action_row_set_subtitle_lines (ADW_ACTION_ROW (self->trust_sign_row), 0);
 }
 
 static void
 create_private_key_dialog (SeahorsePgpKeyProperties *self)
 {
-    g_autoptr(GtkBuilder) builder = NULL;
-    GtkWidget *content_area, *content;
-
-    builder = gtk_builder_new_from_resource (PRIVATE_KEY_PROPERTIES_UI);
-
-    content_area = gtk_dialog_get_content_area (GTK_DIALOG (self));
-    content = GTK_WIDGET (gtk_builder_get_object (builder, "window_content"));
-    self->actions_menu = G_MENU_MODEL (gtk_builder_get_object (builder, "actions_menu"));
-    gtk_container_add (GTK_CONTAINER (content_area), content);
-
-    g_action_map_add_action_entries (G_ACTION_MAP (self->action_group),
-                                     PRIVATE_KEY_ACTIONS,
-                                     G_N_ELEMENTS (PRIVATE_KEY_ACTIONS),
-                                     self);
-    gtk_widget_insert_action_group (GTK_WIDGET (self),
-                                    "props",
-                                    G_ACTION_GROUP (self->action_group));
-
-    get_common_widgets (self, builder);
-
-    self->owner_photo_frame = GTK_WIDGET (gtk_builder_get_object (builder, "owner-photo-frame"));
-    self->owner_photo_add_button = GTK_WIDGET (gtk_builder_get_object (builder, "owner-photo-add-button"));
-    self->owner_photo_delete_button = GTK_WIDGET (gtk_builder_get_object (builder, 
"owner-photo-delete-button"));
-    self->owner_photo_primary_button = GTK_WIDGET (gtk_builder_get_object (builder, 
"owner-photo-primary-button"));
-
     setup_trust_combobox (self);
     do_owner (self);
     do_details (self);
-
-    /* Allow DnD on the photo frame */
-    g_signal_connect_object (self->owner_photo_frame, "drag-data-received",
-                             G_CALLBACK (on_pgp_owner_photo_drag_received),
-                             self, 0);
-    gtk_drag_dest_set (self->owner_photo_frame,
-                       GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT | GTK_DEST_DEFAULT_DROP,
-                       target_list, G_N_ELEMENTS (target_list),
-                       GDK_ACTION_COPY);
 }
 
 static void
@@ -1075,8 +580,7 @@ seahorse_pgp_key_properties_set_property (GObject      *object,
 
     switch (prop_id) {
     case PROP_KEY:
-        g_clear_object (&self->key);
-        self->key = g_value_dup_object (value);
+        g_set_object (&self->key, g_value_get_object (value));
         break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -1090,7 +594,6 @@ seahorse_pgp_key_properties_finalize (GObject *obj)
     SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (obj);
 
     g_clear_object (&self->key);
-    g_clear_object (&self->action_group);
 
     G_OBJECT_CLASS (seahorse_pgp_key_properties_parent_class)->finalize (obj);
 }
@@ -1100,43 +603,140 @@ seahorse_pgp_key_properties_constructed (GObject *obj)
 {
     SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (obj);
     SeahorseUsage usage;
+    GtkWidget *uids_listbox, *subkeys_listbox;
+    const char *name;
+    g_autofree char *title = NULL;
+    gboolean is_public_key;
 
     G_OBJECT_CLASS (seahorse_pgp_key_properties_parent_class)->constructed (obj);
 
     usage = seahorse_object_get_usage (SEAHORSE_OBJECT (self->key));
-    if (usage == SEAHORSE_USAGE_PUBLIC_KEY)
+    is_public_key = (usage == SEAHORSE_USAGE_PUBLIC_KEY);
+    if (is_public_key)
         create_public_key_dialog (self);
     else
         create_private_key_dialog (self);
 
+    uids_listbox = seahorse_pgp_uid_list_box_new (self->key);
+    gtk_box_append (GTK_BOX (self->uids_container), uids_listbox);
+
+    subkeys_listbox = seahorse_pgp_subkey_list_box_new (self->key);
+    gtk_box_append (GTK_BOX (self->subkeys_container), subkeys_listbox);
+
+    /* Some trust rows are only make sense for public keys */
+    gtk_widget_set_visible (self->trust_page, is_public_key);
+    gtk_widget_set_visible (self->indicate_trust_row, is_public_key);
+    gtk_widget_set_visible (self->trust_sign_row, is_public_key);
+    gtk_widget_set_visible (self->trust_marginal_switch, is_public_key);
+
     g_signal_connect_object (self->key, "notify",
                              G_CALLBACK (key_notify), self, 0);
+
+    /* Titlebar */
+    name = seahorse_pgp_key_get_primary_name (self->key);
+
+    /* Translators: the 1st part of the title is the owner's name */
+    title = (!is_public_key)? g_strdup_printf (_("%s — Public key"), name)
+                            : g_strdup_printf (_("%s — Private key"), name);
+    gtk_widget_set_visible (self->menu_button, !is_public_key);
+    adw_window_title_set_title (ADW_WINDOW_TITLE (self->window_title), title);
 }
 
 static void
 seahorse_pgp_key_properties_init (SeahorsePgpKeyProperties *self)
 {
-    self->action_group = g_simple_action_group_new ();
-
-    gtk_window_set_default_size (GTK_WINDOW (self), 300, 600);
-    gtk_container_set_border_width (GTK_CONTAINER (self), 0);
+    gtk_widget_init_template (GTK_WIDGET (self));
 }
 
 static void
 seahorse_pgp_key_properties_class_init (SeahorsePgpKeyPropertiesClass *klass)
 {
     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 
     gobject_class->constructed = seahorse_pgp_key_properties_constructed;
     gobject_class->get_property = seahorse_pgp_key_properties_get_property;
     gobject_class->set_property = seahorse_pgp_key_properties_set_property;
     gobject_class->finalize = seahorse_pgp_key_properties_finalize;
 
-    g_object_class_install_property (gobject_class, PROP_KEY,
-        g_param_spec_object ("key", "PGP key",
-                             "The PGP key of which we're showing the details",
+    properties[PROP_KEY] =
+        g_param_spec_object ("key", NULL, NULL,
                              SEAHORSE_PGP_TYPE_KEY,
-                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+    g_object_class_install_properties (gobject_class, N_PROPS, properties);
+
+    gtk_widget_class_set_template_from_resource (widget_class,
+                                                 "/org/gnome/Seahorse/seahorse-pgp-key-properties.ui");
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          window_title);
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          menu_button);
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          name_label);
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          email_label);
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          comment_label);
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          keyid_label);
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          fingerprint_label);
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          expires_label);
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          revoked_area);
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          expired_area);
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          expired_message);
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          ownertrust_combobox);
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          uids_container);
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          subkeys_container);
+
+    /* public keys only */
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          trust_page);
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          indicate_trust_row);
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          trust_sign_row);
+    gtk_widget_class_bind_template_child (widget_class,
+                                          SeahorsePgpKeyProperties,
+                                          trust_marginal_switch);
+
+    gtk_widget_class_bind_template_callback (widget_class,
+                                             on_pgp_details_trust_changed);
+    gtk_widget_class_bind_template_callback (widget_class,
+                                             on_trust_marginal_switch_notify_active);
+
+    /* ACTIONS */
+    /* Private keys only */
+    gtk_widget_class_install_action (widget_class, "change-password", NULL, on_change_password);
+    gtk_widget_class_install_action (widget_class, "change-expires", NULL, on_change_expires);
+    gtk_widget_class_install_action (widget_class, "export-secret", NULL, on_export_secret);
+    gtk_widget_class_install_action (widget_class, "export-public", NULL, on_export_public);
+    /* Public keys only */
+    gtk_widget_class_install_action (widget_class, "sign-key", NULL, on_sign_key);
 }
 
 GtkWindow *
diff --git a/pgp/seahorse-pgp-key-properties.h b/pgp/seahorse-pgp-key-properties.h
new file mode 100644
index 00000000..125be448
--- /dev/null
+++ b/pgp/seahorse-pgp-key-properties.h
@@ -0,0 +1,32 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2022 Niels De Graef
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "pgp/seahorse-pgp-key.h"
+
+#define SEAHORSE_PGP_TYPE_KEY_PROPERTIES (seahorse_pgp_key_properties_get_type ())
+G_DECLARE_FINAL_TYPE (SeahorsePgpKeyProperties, seahorse_pgp_key_properties,
+                      SEAHORSE_PGP, KEY_PROPERTIES,
+                      GtkWindow);
+
+GtkWindow *     seahorse_pgp_key_properties_new    (SeahorsePgpKey *pkey,
+                                                    GtkWindow *parent);
diff --git a/pgp/seahorse-pgp-key-properties.ui b/pgp/seahorse-pgp-key-properties.ui
new file mode 100644
index 00000000..ec01961d
--- /dev/null
+++ b/pgp/seahorse-pgp-key-properties.ui
@@ -0,0 +1,322 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <menu id="actions_menu">
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">Change _passphrase</attribute>
+        <attribute name="action">change-password</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">Export _public key</attribute>
+        <attribute name="action">export-public</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">Export _secret key</attribute>
+        <attribute name="action">export-secret</attribute>
+      </item>
+    </section>
+  </menu>
+
+  <template class="SeahorsePgpKeyProperties" parent="GtkWindow">
+    <property name="default-width">300</property>
+    <property name="default-height">600</property>
+
+    <child type="titlebar">
+      <object class="GtkHeaderBar">
+        <property name="show-title-buttons">True</property>
+        <property name="title-widget">
+          <object class="AdwWindowTitle" id="window_title">
+          </object>
+        </property>
+        <child type="start">
+          <object class="GtkMenuButton" id="menu_button">
+            <property name="icon-name">open-menu-symbolic</property>
+            <property name="menu-model">actions_menu</property>
+          </object>
+        </child>
+      </object>
+    </child>
+
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkInfoBar" id="revoked_area">
+            <property name="message-type">error</property>
+            <child>
+              <object class="GtkLabel">
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">The owner of the key revoked the key. It should no 
longer be used.</property>
+                <property name="margin-start">12</property>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkInfoBar" id="expired_area">
+            <property name="message-type">error</property>
+            <child>
+              <object class="GtkLabel" id="expired_message">
+                <property name="xalign">0</property>
+                <property name="margin-start">12</property>
+                <property name="label">This key expired on (placeholder)</property>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow">
+            <property name="propagate-natural-height">True</property>
+            <property name="vscrollbar-policy">automatic</property>
+            <property name="hscrollbar-policy">never</property>
+            <child>
+              <object class="AdwClamp">
+                <property name="margin-top">12</property>
+                <property name="margin-bottom">12</property>
+                <property name="margin-start">12</property>
+                <property name="margin-end">12</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">18</property>
+                    <child>
+                      <object class="GtkGrid">
+                        <property name="column_spacing">12</property>
+                        <property name="row_spacing">6</property>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="orientation">vertical</property>
+                            <property name="spacing">3</property>
+                            <property name="margin-bottom">12</property>
+                            <child>
+                              <object class="GtkLabel" id="name_label">
+                                <property name="halign">start</property>
+                                <property name="selectable">True</property>
+                                <attributes>
+                                  <attribute name="scale" value="1.75"/>
+                                </attributes>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="email_label">
+                                <property name="halign">start</property>
+                                <property name="selectable">True</property>
+                              </object>
+                            </child>
+                            <layout>
+                              <property name="row">0</property>
+                              <property name="column">0</property>
+                              <property name="column-span">2</property>
+                            </layout>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="visible" bind-source="comment_label" bind-property="visible" 
bind-flags="sync-create" />
+                            <property name="xalign">1</property>
+                            <property name="label" translatable="yes">Comment</property>
+                            <style>
+                              <class name="dim-label"/>
+                            </style>
+                            <layout>
+                              <property name="row">1</property>
+                              <property name="column">0</property>
+                            </layout>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="comment_label">
+                            <property name="xalign">0</property>
+                            <property name="selectable">True</property>
+                            <layout>
+                              <property name="row">1</property>
+                              <property name="column">1</property>
+                            </layout>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="xalign">1</property>
+                            <property name="yalign">0</property>
+                            <property name="label" translatable="yes">Key ID</property>
+                            <style>
+                              <class name="dim-label"/>
+                            </style>
+                            <layout>
+                              <property name="row">2</property>
+                              <property name="column">0</property>
+                            </layout>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="keyid_label">
+                            <property name="xalign">0</property>
+                            <property name="selectable">True</property>
+                            <layout>
+                              <property name="row">2</property>
+                              <property name="column">1</property>
+                            </layout>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="xalign">1</property>
+                            <property name="yalign">0</property>
+                            <property name="label" translatable="yes">Fingerprint</property>
+                            <style>
+                              <class name="dim-label"/>
+                            </style>
+                            <layout>
+                              <property name="row">3</property>
+                              <property name="column">0</property>
+                            </layout>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel" id="fingerprint_label">
+                            <property name="xalign">0</property>
+                            <property name="selectable">True</property>
+                            <property name="wrap">True</property>
+                            <property name="wrap-mode">word</property>
+                            <property name="width-chars">24</property>
+                            <property name="max-width-chars">24</property>
+                            <property name="lines">2</property>
+                            <layout>
+                              <property name="row">3</property>
+                              <property name="column">1</property>
+                            </layout>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="xalign">1</property>
+                            <property name="yalign">0</property>
+                            <property name="label" translatable="yes">Expires</property>
+                            <style>
+                              <class name="dim-label"/>
+                            </style>
+                            <layout>
+                              <property name="row">4</property>
+                              <property name="column">0</property>
+                            </layout>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkLabel" id="expires_label">
+                                <property name="xalign">0</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkButton" id="details_expires_button">
+                                <property name="action-name">change-expires</property>
+                                <child>
+                                  <object class="GtkImage">
+                                    <property name="icon_name">x-office-calendar-symbolic</property>
+                                  </object>
+                                </child>
+                                <style>
+                                  <class name="flat"/>
+                                </style>
+                              </object>
+                            </child>
+                            <layout>
+                              <property name="row">4</property>
+                              <property name="column">1</property>
+                            </layout>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="uids_container">
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="subkeys_container">
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="trust_page">
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">24</property>
+                        <child>
+                          <object class="AdwPreferencesGroup">
+                            <property name="title" translatable="yes">Trust</property>
+                            <child>
+                              <object class="AdwActionRow">
+                                <property name="title" translatable="yes">Signature trust</property>
+                                <property name="subtitle" translatable="yes">I trust signatures from this 
key on other keys</property>
+                                <child>
+                                  <object class="GtkSwitch" id="trust_marginal_switch">
+                                    <property name="halign">end</property>
+                                    <property name="valign">center</property>
+                                    <signal name="notify::active" 
handler="on_trust_marginal_switch_notify_active"/>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="AdwActionRow" id="trust_sign_row">
+                                <property name="title" translatable="yes">Sign key</property>
+                                <child>
+                                  <object class="GtkButton" id="sign-button">
+                                    <property name="halign">end</property>
+                                    <property name="valign">center</property>
+                                    <property name="label" translatable="yes">_Sign key</property>
+                                    <property name="use_underline">True</property>
+                                    <property name="action-name">sign-key</property>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="AdwActionRow" id="trust_revoke_row">
+                                <property name="title" translatable="yes">Revoke key signature</property>
+                                <child>
+                                  <object class="GtkButton" id="revoke-button">
+                                    <property name="halign">end</property>
+                                    <property name="valign">center</property>
+                                    <property name="label" translatable="yes">_Revoke</property>
+                                    <property name="use_underline">True</property>
+                                    <property name="action-name">revoke-key</property>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="AdwActionRow" id="indicate_trust_row">
+                                <property name="title" translatable="yes">Owner trust</property>
+                                <property name="subtitle" translatable="yes">Give a trust level to the owner 
of this key</property>
+                                <child>
+                                  <object class="GtkComboBox" id="ownertrust_combobox">
+                                    <property name="valign">center</property>
+                                    <signal name="changed" handler="on_pgp_details_trust_changed"/>
+                                    <child>
+                                      <object class="GtkCellRendererText"/>
+                                      <attributes>
+                                        <attribute name="text">0</attribute>
+                                      </attributes>
+                                    </child>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/pgp/seahorse-pgp-key.c b/pgp/seahorse-pgp-key.c
index e1871f97..5154d4f9 100644
--- a/pgp/seahorse-pgp-key.c
+++ b/pgp/seahorse-pgp-key.c
@@ -253,13 +253,15 @@ seahorse_pgp_key_realize (SeahorsePgpKey *self)
     g_object_get (self, "usage", &usage, NULL);
 
     /* The type */
-    if (usage == SEAHORSE_USAGE_PRIVATE_KEY) {
-        icon_name = GCR_ICON_KEY_PAIR;
-    } else {
-        icon_name = GCR_ICON_KEY;
-        if (usage == SEAHORSE_USAGE_NONE)
-            g_object_set (self, "usage", SEAHORSE_USAGE_PUBLIC_KEY, NULL);
-    }
+    // XXX
+    icon_name = "missing-icon";
+    /* if (usage == SEAHORSE_USAGE_PRIVATE_KEY) { */
+    /*     icon_name = GCR_ICON_KEY_PAIR; */
+    /* } else { */
+    /*     icon_name = GCR_ICON_KEY; */
+    /*     if (usage == SEAHORSE_USAGE_NONE) */
+    /*         g_object_set (self, "usage", SEAHORSE_USAGE_PUBLIC_KEY, NULL); */
+    /* } */
 
     icon = g_themed_icon_new (icon_name);
     g_object_set (self,
diff --git a/pgp/seahorse-pgp-keysets.c b/pgp/seahorse-pgp-keysets.c
index b2703ea8..35007586 100644
--- a/pgp/seahorse-pgp-keysets.c
+++ b/pgp/seahorse-pgp-keysets.c
@@ -35,53 +35,55 @@
 static void
 on_settings_default_key_changed (GSettings *settings, const gchar *key, gpointer user_data)
 {
-       /* Default key changed, refresh */
-       gcr_filter_collection_refilter (GCR_FILTER_COLLECTION (user_data));
+    GtkFilter *filter = GTK_FILTER (user_data);
+
+    /* Default key changed, refresh */
+    gtk_filter_changed (filter, GTK_FILTER_CHANGE_DIFFERENT);
 }
 
 static gboolean
 pgp_signers_match (GObject *obj,
                    gpointer data)
 {
-       SeahorsePgpKey *defkey;
-       SeahorseUsage usage;
-       SeahorseFlags flags;
-
-       if (!SEAHORSE_GPGME_IS_KEY (obj))
-               return FALSE;
-
-       g_object_get (obj, "usage", &usage, "object-flags", &flags, NULL);
-       if (usage != SEAHORSE_USAGE_PRIVATE_KEY)
-               return FALSE;
-       if (!(flags & SEAHORSE_FLAG_CAN_SIGN))
-               return FALSE;
-       if (flags & (SEAHORSE_FLAG_EXPIRED | SEAHORSE_FLAG_REVOKED | SEAHORSE_FLAG_DISABLED))
-               return FALSE;
-
-       defkey = seahorse_pgp_backend_get_default_key (NULL);
-
-       /* Default key overrides all, and becomes the only signer available*/
-       if (defkey != NULL &&
-           g_strcmp0 (seahorse_pgp_key_get_keyid (defkey),
-                      seahorse_pgp_key_get_keyid (SEAHORSE_PGP_KEY (obj))) != 0)
-               return FALSE;
-
-       return TRUE;
+    SeahorsePgpKey *defkey;
+    SeahorseUsage usage;
+    SeahorseFlags flags;
+
+    if (!SEAHORSE_GPGME_IS_KEY (obj))
+        return FALSE;
+
+    g_object_get (obj, "usage", &usage, "object-flags", &flags, NULL);
+    if (usage != SEAHORSE_USAGE_PRIVATE_KEY)
+        return FALSE;
+    if (!(flags & SEAHORSE_FLAG_CAN_SIGN))
+        return FALSE;
+    if (flags & (SEAHORSE_FLAG_EXPIRED | SEAHORSE_FLAG_REVOKED | SEAHORSE_FLAG_DISABLED))
+        return FALSE;
+
+    defkey = seahorse_pgp_backend_get_default_key (NULL);
+
+    /* Default key overrides all, and becomes the only signer available*/
+    if (defkey != NULL &&
+        g_strcmp0 (seahorse_pgp_key_get_keyid (defkey),
+                   seahorse_pgp_key_get_keyid (SEAHORSE_PGP_KEY (obj))) != 0)
+        return FALSE;
+
+    return TRUE;
 }
 
-GcrCollection *
+GListModel *
 seahorse_keyset_pgp_signers_new (void)
 {
-       GcrCollection *collection;
-       SeahorseGpgmeKeyring *keyring;
+    SeahorseGpgmeKeyring *keyring;
+    g_autoptr(GtkCustomFilter) filter = NULL;
+    GtkFilterListModel *model;
 
-       keyring = seahorse_pgp_backend_get_default_keyring (NULL);
-       collection = gcr_filter_collection_new_with_callback (GCR_COLLECTION (keyring),
-                       pgp_signers_match,
-                       NULL, NULL);
+    keyring = seahorse_pgp_backend_get_default_keyring (NULL);
+    filter = gtk_custom_filter_new (pgp_signers_match, NULL, NULL);
+    model = gtk_filter_list_model_new (G_LIST_MODEL (keyring), g_steal_pointer (&filter));
 
-       g_signal_connect_object (seahorse_pgp_settings_instance (), "changed::default-key",
-                                G_CALLBACK (on_settings_default_key_changed), collection, 0);
+    g_signal_connect_object (seahorse_pgp_settings_instance (), "changed::default-key",
+                             G_CALLBACK (on_settings_default_key_changed), filter, 0);
 
-       return collection;
+    return G_LIST_MODEL (model);
 }
diff --git a/pgp/seahorse-pgp-keysets.h b/pgp/seahorse-pgp-keysets.h
index 65bfe522..dc31149f 100644
--- a/pgp/seahorse-pgp-keysets.h
+++ b/pgp/seahorse-pgp-keysets.h
@@ -2,6 +2,7 @@
  * Seahorse
  *
  * Copyright (C) 2008 Stefan Walter
+ * Copyright (C) 2022 Niels De Graef
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as
@@ -18,15 +19,8 @@
  * <http://www.gnu.org/licenses/>.
  */
 
-#ifndef SEAPGPKEYSETS_H_
-#define SEAPGPKEYSETS_H_
+#pragma once
 
-#include <gcr/gcr.h>
+#include <gio/gio.h>
 
-/* -----------------------------------------------------------------------------
- * SOME COMMON KEYSETS
- */
-
-GcrCollection *     seahorse_keyset_pgp_signers_new     (void);
-
-#endif /*SEAPGPKEYSETS_H_*/
+GListModel *     seahorse_keyset_pgp_signers_new     (void);
diff --git a/pgp/seahorse-pgp-subkey-list-box-row.ui b/pgp/seahorse-pgp-subkey-list-box-row.ui
index 1caa65a6..47980611 100644
--- a/pgp/seahorse-pgp-subkey-list-box-row.ui
+++ b/pgp/seahorse-pgp-subkey-list-box-row.ui
@@ -17,159 +17,137 @@
       </item>
     </section>
   </menu>
-  <template class="SeahorsePgpSubkeyListBoxRow" parent="HdyExpanderRow">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
+  <template class="SeahorsePgpSubkeyListBoxRow" parent="AdwExpanderRow">
     <child type="action">
       <object class="GtkImage" id="status_box">
-        <property name="visible">True</property>
         <property name="icon-name">dialog-warning-symbolic</property>
         <style>
           <class name="pgp-subkey-status-box"/>
         </style>
       </object>
-      <packing>
-        <property name="top_attach">0</property>
-        <property name="left_attach">2</property>
-      </packing>
     </child>
     <child>
       <object class="GtkGrid">
-        <property name="visible">True</property>
-        <property name="margin">12</property>
         <property name="row-spacing">12</property>
         <property name="column-spacing">12</property>
         <child>
           <object class="GtkLabel">
-            <property name="visible">True</property>
             <property name="xalign">1</property>
             <property name="label" translatable="yes">Usages</property>
             <style>
               <class name="dim-label"/>
             </style>
+            <layout>
+              <property name="row">0</property>
+              <property name="column">0</property>
+            </layout>
           </object>
-          <packing>
-            <property name="top_attach">0</property>
-            <property name="left_attach">0</property>
-          </packing>
         </child>
         <child>
           <object class="GtkBox" id="usages_box">
-            <property name="visible">True</property>
             <property name="spacing">3</property>
             <property name="orientation">horizontal</property>
+            <layout>
+              <property name="row">0</property>
+              <property name="column">1</property>
+            </layout>
           </object>
-          <packing>
-            <property name="top_attach">0</property>
-            <property name="left_attach">1</property>
-          </packing>
         </child>
         <child>
           <object class="GtkLabel">
-            <property name="visible">True</property>
             <property name="xalign">1</property>
             <property name="label" translatable="yes">Fingerprint</property>
             <style>
               <class name="dim-label"/>
             </style>
+            <layout>
+              <property name="row">1</property>
+              <property name="column">0</property>
+            </layout>
           </object>
-          <packing>
-            <property name="top_attach">1</property>
-            <property name="left_attach">0</property>
-          </packing>
         </child>
         <child>
           <object class="GtkLabel" id="fingerprint_label">
-            <property name="visible">True</property>
             <property name="selectable">True</property>
             <property name="xalign">0</property>
+            <layout>
+              <property name="row">1</property>
+              <property name="column">1</property>
+            </layout>
           </object>
-          <packing>
-            <property name="top_attach">1</property>
-            <property name="left_attach">1</property>
-          </packing>
         </child>
         <child>
           <object class="GtkLabel">
-            <property name="visible">True</property>
             <property name="xalign">1</property>
             <property name="label" translatable="yes">Type</property>
             <style>
               <class name="dim-label"/>
             </style>
+            <layout>
+              <property name="row">2</property>
+              <property name="column">0</property>
+            </layout>
           </object>
-          <packing>
-            <property name="top_attach">2</property>
-            <property name="left_attach">0</property>
-          </packing>
         </child>
         <child>
           <object class="GtkLabel" id="algo_label">
-            <property name="visible">True</property>
             <property name="xalign">0</property>
+            <layout>
+              <property name="row">2</property>
+              <property name="column">1</property>
+            </layout>
           </object>
-          <packing>
-            <property name="top_attach">2</property>
-            <property name="left_attach">1</property>
-          </packing>
         </child>
         <child>
           <object class="GtkLabel">
-            <property name="visible">True</property>
             <property name="xalign">1</property>
             <property name="label" translatable="yes">Created</property>
             <style>
               <class name="dim-label"/>
             </style>
+            <layout>
+              <property name="row">3</property>
+              <property name="column">0</property>
+            </layout>
           </object>
-          <packing>
-            <property name="top_attach">3</property>
-            <property name="left_attach">0</property>
-          </packing>
         </child>
         <child>
           <object class="GtkLabel" id="created_label">
-            <property name="visible">True</property>
             <property name="xalign">0</property>
+            <layout>
+              <property name="row">3</property>
+              <property name="column">1</property>
+            </layout>
           </object>
-          <packing>
-            <property name="top_attach">3</property>
-            <property name="left_attach">1</property>
-          </packing>
         </child>
         <child>
           <object class="GtkBox" id="action_box">
-            <property name="visible">True</property>
             <property name="orientation">horizontal</property>
             <property name="spacing">6</property>
-            <property name="margin">6</property>
             <child>
               <object class="GtkButton">
-                <property name="visible">True</property>
                 <property name="label" translatable="yes">Change expiry date</property>
                 <property name="action-name">subkey.change-expires</property>
               </object>
             </child>
             <child>
               <object class="GtkButton">
-                <property name="visible">True</property>
                 <property name="label" translatable="yes">Revoke</property>
                 <property name="action-name">subkey.revoke</property>
               </object>
             </child>
             <child>
               <object class="GtkButton">
-                <property name="visible">True</property>
                 <property name="label" translatable="yes">Delete</property>
                 <property name="action-name">subkey.delete</property>
               </object>
             </child>
+            <layout>
+              <property name="row">4</property>
+              <property name="column">0</property>
+              <property name="column-span">2</property>
+            </layout>
           </object>
-          <packing>
-            <property name="top_attach">4</property>
-            <property name="left_attach">0</property>
-            <property name="width">2</property>
-          </packing>
         </child>
       </object>
     </child>
diff --git a/pgp/seahorse-pgp-subkey-list-box.c b/pgp/seahorse-pgp-subkey-list-box.c
index 4ee2dc45..831b46f7 100644
--- a/pgp/seahorse-pgp-subkey-list-box.c
+++ b/pgp/seahorse-pgp-subkey-list-box.c
@@ -19,6 +19,7 @@
 
 #include "config.h"
 
+#include "seahorse-gpgme-add-subkey.h"
 #include "seahorse-gpgme-expires-dialog.h"
 #include "seahorse-gpgme-key-op.h"
 #include "seahorse-gpgme-revoke-dialog.h"
@@ -29,9 +30,11 @@
 #include <glib/gi18n.h>
 
 struct _SeahorsePgpSubkeyListBox {
-    GtkListBox parent_instance;
+    AdwPreferencesGroup parent_instance;
 
     SeahorsePgpKey *key;
+
+    GPtrArray *rows;
 };
 
 enum {
@@ -41,7 +44,59 @@ enum {
 };
 static GParamSpec *obj_props[N_PROPS] = { NULL, };
 
-G_DEFINE_TYPE (SeahorsePgpSubkeyListBox, seahorse_pgp_subkey_list_box, GTK_TYPE_LIST_BOX)
+G_DEFINE_TYPE (SeahorsePgpSubkeyListBox, seahorse_pgp_subkey_list_box, ADW_TYPE_PREFERENCES_GROUP)
+
+static void
+on_add_subkey_completed (GObject *object, GAsyncResult *res, void *user_data)
+{
+    SeahorsePgpSubkeyListBox *self = SEAHORSE_PGP_SUBKEY_LIST_BOX (user_data);
+    g_autoptr(GError) error = NULL;
+
+    if (!seahorse_gpgme_key_op_add_subkey_finish (SEAHORSE_GPGME_KEY (self->key),
+                                                  res, &error)) {
+        seahorse_util_handle_error (&error, self, NULL);
+    }
+}
+
+static void
+on_add_subkey_response (GtkDialog *dialog, int response, void *user_data)
+{
+    SeahorsePgpSubkeyListBox *self = SEAHORSE_PGP_SUBKEY_LIST_BOX (user_data);
+    SeahorseGpgmeAddSubkey *add_dialog = SEAHORSE_GPGME_ADD_SUBKEY (dialog);
+    SeahorseKeyEncType type;
+    unsigned int length;
+    GDateTime *expires;
+
+    if (response != GTK_RESPONSE_OK) {
+        gtk_window_destroy (GTK_WINDOW (add_dialog));
+        return;
+    }
+
+    length = seahorse_gpgme_add_subkey_get_keysize (add_dialog);
+    type = seahorse_gpgme_add_subkey_get_active_type (add_dialog);
+    expires = seahorse_gpgme_add_subkey_get_expires (add_dialog);
+    seahorse_gpgme_key_op_add_subkey_async (SEAHORSE_GPGME_KEY (self->key),
+                                            type, length, expires, NULL,
+                                            on_add_subkey_completed, self);
+
+    gtk_window_destroy (GTK_WINDOW (add_dialog));
+}
+
+static void
+on_add_subkey_clicked (GtkButton *widget, void *user_data)
+{
+    SeahorsePgpSubkeyListBox *self = SEAHORSE_PGP_SUBKEY_LIST_BOX (user_data);
+    GtkRoot *root;
+    SeahorseGpgmeAddSubkey *dialog;
+
+    g_return_if_fail (SEAHORSE_GPGME_IS_KEY (self->key));
+
+    root = gtk_widget_get_root (widget);
+    dialog = seahorse_gpgme_add_subkey_new (SEAHORSE_GPGME_KEY (self->key),
+                                            GTK_WINDOW (root));
+    g_signal_connect (dialog, "response", G_CALLBACK (on_add_subkey_response), self);
+    gtk_window_present (GTK_WINDOW (dialog));
+}
 
 static void
 seahorse_pgp_subkey_list_box_set_property (GObject      *object,
@@ -53,7 +108,7 @@ seahorse_pgp_subkey_list_box_set_property (GObject      *object,
 
     switch (prop_id) {
     case PROP_KEY:
-        g_set_object (&self->key, g_value_dup_object (value));
+        g_set_object (&self->key, g_value_get_object (value));
         break;
     }
 }
@@ -73,38 +128,82 @@ seahorse_pgp_subkey_list_box_get_property (GObject      *object,
     }
 }
 
-static GtkWidget *
-create_subkey_row (void *item,
-                   void *user_data)
+static void
+on_key_subkeys_changed (GListModel *subkeys,
+                        unsigned int position,
+                        unsigned int removed,
+                        unsigned int added,
+                        void *user_data)
 {
-    SeahorsePgpSubkey *subkey = SEAHORSE_PGP_SUBKEY (item);
+    SeahorsePgpSubkeyListBox *self = SEAHORSE_PGP_SUBKEY_LIST_BOX (user_data);
 
-    return g_object_new (SEAHORSE_PGP_TYPE_SUBKEY_LIST_BOX_ROW,
-                         "subkey", subkey,
-                         NULL);
+    for (unsigned int i = 0; i < position + removed; i++) {
+        GtkWidget *row = g_ptr_array_index (self->rows, i);
+
+        adw_preferences_group_remove (ADW_PREFERENCES_GROUP (self),
+                                      row);
+        g_ptr_array_remove_index (self->rows, i);
+    }
+
+    for (unsigned int i = 0; i < position + added; i++) {
+        GtkWidget *row;
+        g_autoptr(SeahorsePgpSubkey) subkey = NULL;
+
+        subkey = g_list_model_get_item (subkeys, i);
+        row = g_object_new (SEAHORSE_PGP_TYPE_SUBKEY_LIST_BOX_ROW,
+                            "subkey", subkey,
+                            NULL);
+        g_ptr_array_insert (self->rows, i, row);
+        adw_preferences_group_add (ADW_PREFERENCES_GROUP (self),
+                                   row);
+    }
 }
 
 static void
 seahorse_pgp_subkey_list_box_constructed (GObject *obj)
 {
     SeahorsePgpSubkeyListBox *self = SEAHORSE_PGP_SUBKEY_LIST_BOX (obj);
+    GListModel *subkeys;
+    SeahorseUsage usage;
 
     G_OBJECT_CLASS (seahorse_pgp_subkey_list_box_parent_class)->constructed (obj);
 
-    gtk_list_box_bind_model (GTK_LIST_BOX (self),
-                             seahorse_pgp_key_get_subkeys (self->key),
-                             create_subkey_row,
+    /* Setup the rows for each subkey */
+    subkeys = seahorse_pgp_key_get_subkeys (self->key);
+    g_signal_connect_object (subkeys,
+                             "items-changed",
+                             G_CALLBACK (on_key_subkeys_changed),
                              self,
-                             NULL);
+                             0);
+    on_key_subkeys_changed (subkeys, 0, 0, g_list_model_get_n_items (subkeys), self);
+
+    /* If applicable, add a button to add a new subkey too */
+    usage = seahorse_object_get_usage (SEAHORSE_OBJECT (self->key));
+    if (usage == SEAHORSE_USAGE_PRIVATE_KEY) {
+        GtkWidget *button_content;
+        GtkWidget *button;
+
+        button_content = adw_button_content_new ();
+        adw_button_content_set_icon_name (ADW_BUTTON_CONTENT (button_content),
+                                          "list-add-symbolic");
+        adw_button_content_set_label (ADW_BUTTON_CONTENT (button_content),
+                                      _("Add subkey"));
+
+        button = gtk_button_new ();
+        gtk_button_set_child (GTK_BUTTON (button), button_content);
+        gtk_widget_add_css_class (button, "flat");
+        g_signal_connect (button, "clicked", G_CALLBACK (on_add_subkey_clicked), self);
+        adw_preferences_group_set_header_suffix (ADW_PREFERENCES_GROUP (self),
+                                                 button);
+    }
 }
 
 static void
 seahorse_pgp_subkey_list_box_init (SeahorsePgpSubkeyListBox *self)
 {
-    GtkStyleContext *style_context;
+    self->rows = g_ptr_array_new ();
 
-    style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
-    gtk_style_context_add_class (style_context, "content");
+    adw_preferences_group_set_title (ADW_PREFERENCES_GROUP (self), _("Subkeys"));
 }
 
 static void
@@ -112,6 +211,7 @@ seahorse_pgp_subkey_list_box_finalize (GObject *obj)
 {
     SeahorsePgpSubkeyListBox *self = SEAHORSE_PGP_SUBKEY_LIST_BOX (obj);
 
+    g_clear_pointer (&self->rows, g_ptr_array_unref);
     g_clear_object (&self->key);
 
     G_OBJECT_CLASS (seahorse_pgp_subkey_list_box_parent_class)->finalize (obj);
@@ -139,7 +239,6 @@ GtkWidget *
 seahorse_pgp_subkey_list_box_new (SeahorsePgpKey *key)
 {
     return g_object_new (SEAHORSE_PGP_TYPE_SUBKEY_LIST_BOX,
-                         "selection-mode", GTK_SELECTION_NONE,
                          "key", key,
                          NULL);
 }
@@ -154,7 +253,7 @@ seahorse_pgp_subkey_list_box_get_key (SeahorsePgpSubkeyListBox *self)
 /* SeahorsePhpSubkeyListBoxRow */
 
 struct _SeahorsePgpSubkeyListBoxRow {
-    HdyExpanderRow parent_instance;
+    AdwExpanderRow parent_instance;
 
     SeahorsePgpSubkey *subkey;
 
@@ -175,14 +274,14 @@ enum {
 };
 static GParamSpec *row_props[ROW_N_PROPS] = { NULL, };
 
-G_DEFINE_TYPE (SeahorsePgpSubkeyListBoxRow, seahorse_pgp_subkey_list_box_row, HDY_TYPE_EXPANDER_ROW)
+G_DEFINE_TYPE (SeahorsePgpSubkeyListBoxRow, seahorse_pgp_subkey_list_box_row, ADW_TYPE_EXPANDER_ROW)
 
 static GtkWindow *
-get_toplevel_window (SeahorsePgpSubkeyListBoxRow *row)
+get_root (SeahorsePgpSubkeyListBoxRow *row)
 {
     GtkWidget *toplevel = NULL;
 
-    toplevel = gtk_widget_get_toplevel (GTK_WIDGET (row));
+    toplevel = gtk_widget_get_root (GTK_WIDGET (row));
     if (GTK_IS_WINDOW (toplevel))
         return GTK_WINDOW (toplevel);
 
@@ -202,12 +301,15 @@ on_subkey_delete (GSimpleAction *action, GVariant *param, void *user_data)
     fingerprint = seahorse_pgp_subkey_get_fingerprint (row->subkey);
     message = g_strdup_printf (_("Are you sure you want to permanently delete subkey %s?"), fingerprint);
 
-    if (!seahorse_delete_dialog_prompt (get_toplevel_window (row), message))
+    // XXX
+#if 0
+    if (!seahorse_delete_dialog_prompt (get_root (row), message))
         return;
 
     err = seahorse_gpgme_key_op_del_subkey (SEAHORSE_GPGME_SUBKEY (row->subkey));
     if (!GPG_IS_OK (err))
         seahorse_gpgme_handle_error (err, _("Couldn’t delete subkey"));
+#endif
 }
 
 static void
@@ -219,9 +321,9 @@ on_subkey_revoke (GSimpleAction *action, GVariant *param, void *user_data)
     g_return_if_fail (SEAHORSE_GPGME_IS_SUBKEY (row->subkey));
 
     dialog = seahorse_gpgme_revoke_dialog_new (SEAHORSE_GPGME_SUBKEY (row->subkey),
-                                               get_toplevel_window (row));
-    gtk_dialog_run (GTK_DIALOG (dialog));
-    gtk_widget_destroy (dialog);
+                                               get_root (row));
+    g_signal_connect (dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
+    gtk_window_present (GTK_WINDOW (dialog));
 }
 
 static void
@@ -233,9 +335,9 @@ on_subkey_change_expires (GSimpleAction *action, GVariant *param, void *user_dat
     g_return_if_fail (SEAHORSE_GPGME_IS_SUBKEY (row->subkey));
 
     dialog = seahorse_gpgme_expires_dialog_new (SEAHORSE_GPGME_SUBKEY (row->subkey),
-                                                get_toplevel_window (row));
-    gtk_dialog_run (dialog);
-    gtk_widget_destroy (GTK_WIDGET (dialog));
+                                                get_root (row));
+    g_signal_connect (dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
+    gtk_window_present (GTK_WINDOW (dialog));
 }
 
 static const GActionEntry SUBKEY_ACTIONS[] = {
@@ -276,12 +378,12 @@ update_row (SeahorsePgpSubkeyListBoxRow *row)
     }
 
     /* Set the key id */
-    hdy_preferences_row_set_title (HDY_PREFERENCES_ROW (row),
+    adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row),
                                    seahorse_pgp_subkey_get_keyid (row->subkey));
     expires = seahorse_pgp_subkey_get_expires (row->subkey);
     expires_str = expires? g_date_time_format (expires, "Expires on %x")
                          : g_strdup (_("Never expires"));
-    hdy_expander_row_set_subtitle (HDY_EXPANDER_ROW (row), expires_str);
+    adw_expander_row_set_subtitle (ADW_EXPANDER_ROW (row), expires_str);
 
     /* Add the usage tags */
     usages = seahorse_pgp_subkey_get_usages (row->subkey, &descriptions);
@@ -291,8 +393,7 @@ update_row (SeahorsePgpSubkeyListBoxRow *row)
         label = gtk_label_new (usages[i]);
         gtk_widget_set_tooltip_text (label, descriptions[i]);
         gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
-        gtk_widget_show (label);
-        gtk_box_pack_start (GTK_BOX (row->usages_box), label, FALSE, FALSE, 0);
+        gtk_box_append (GTK_BOX (row->usages_box), label);
         gtk_style_context_add_class (gtk_widget_get_style_context (label),
                                      "pgp-subkey-usage-label");
     }
diff --git a/pgp/seahorse-pgp-subkey-list-box.h b/pgp/seahorse-pgp-subkey-list-box.h
index 589ac98e..a306de7d 100644
--- a/pgp/seahorse-pgp-subkey-list-box.h
+++ b/pgp/seahorse-pgp-subkey-list-box.h
@@ -28,7 +28,7 @@
 #define SEAHORSE_PGP_TYPE_SUBKEY_LIST_BOX (seahorse_pgp_subkey_list_box_get_type ())
 G_DECLARE_FINAL_TYPE (SeahorsePgpSubkeyListBox, seahorse_pgp_subkey_list_box,
                       SEAHORSE_PGP, SUBKEY_LIST_BOX,
-                      GtkListBox)
+                      AdwPreferencesGroup)
 
 GtkWidget *          seahorse_pgp_subkey_list_box_new                (SeahorsePgpKey *key);
 
@@ -37,6 +37,6 @@ SeahorsePgpKey *     seahorse_pgp_subkey_list_box_get_key            (SeahorsePg
 #define SEAHORSE_PGP_TYPE_SUBKEY_LIST_BOX_ROW (seahorse_pgp_subkey_list_box_row_get_type ())
 G_DECLARE_FINAL_TYPE (SeahorsePgpSubkeyListBoxRow, seahorse_pgp_subkey_list_box_row,
                       SEAHORSE_PGP, SUBKEY_LIST_BOX_ROW,
-                      HdyExpanderRow)
+                      AdwExpanderRow)
 
 SeahorsePgpSubkey *   seahorse_pgp_subkey_list_box_row_get_subkey    (SeahorsePgpSubkeyListBoxRow *self);
diff --git a/pgp/seahorse-pgp-uid-list-box-row.ui b/pgp/seahorse-pgp-uid-list-box-row.ui
index 3b9c313e..3e8c8e40 100644
--- a/pgp/seahorse-pgp-uid-list-box-row.ui
+++ b/pgp/seahorse-pgp-uid-list-box-row.ui
@@ -17,19 +17,16 @@
       </item>
     </section>
   </menu>
-  <template class="SeahorsePgpUidListBoxRow" parent="HdyExpanderRow">
-    <property name="visible">True</property>
+  <template class="SeahorsePgpUidListBoxRow" parent="AdwExpanderRow">
     <property name="focus-on-click">False</property>
     <child type="action">
       <object class="GtkMenuButton" id="actions_button">
-        <property name="visible">True</property>
         <property name="valign">center</property>
         <property name="halign">end</property>
         <property name="margin-start">6</property>
         <property name="menu-model">actions_menu</property>
         <child>
           <object class="GtkImage">
-            <property name="visible">True</property>
             <property name="icon-name">open-menu-symbolic</property>
           </object>
         </child>
@@ -40,25 +37,19 @@
     </child>
     <child type="prefix">
       <object class="GtkImage" id="avatar">
-        <property name="visible">True</property>
         <property name="icon-name">avatar-default-symbolic</property>
-        <property name="icon-size">5</property>
       </object>
     </child>
     <child>
       <object class="GtkBox">
-        <property name="visible">True</property>
         <property name="spacing">12</property>
-        <property name="margin">18</property>
         <property name="orientation">vertical</property>
         <child>
           <object class="GtkBox">
-            <property name="visible">True</property>
             <property name="spacing">6</property>
             <property name="orientation">horizontal</property>
             <child>
               <object class="GtkLabel" id="signed_by_label">
-                <property name="visible">True</property>
                 <property name="xalign">0</property>
                 <property name="valign">center</property>
                 <property name="label" translatable="yes">Signatures</property>
@@ -72,31 +63,22 @@
             </child>
             <child>
               <object class="GtkSwitch" id="trusted_switch">
-                <property name="visible">True</property>
                 <property name="action-name">uid.only-trusted</property>
                 <property name="tooltip-text" translatable="yes">Only display the signatures of people I 
trust</property>
               </object>
-              <packing>
-                <property name="pack_type">end</property>
-              </packing>
             </child>
             <child>
               <object class="GtkLabel">
-                <property name="visible">True</property>
                 <property name="xalign">1</property>
                 <property name="label" translatable="yes">Only trusted</property>
                 <property name="tooltip-text" translatable="yes">Only display the signatures of people I 
trust</property>
                 <property name="mnemonic-widget">trusted_switch</property>
               </object>
-              <packing>
-                <property name="pack_type">end</property>
-              </packing>
             </child>
           </object>
         </child>
         <child>
           <object class="GtkListBox" id="signatures_list">
-            <property name="visible">True</property>
             <property name="selection-mode">none</property>
           </object>
         </child>
diff --git a/pgp/seahorse-pgp-uid-list-box.c b/pgp/seahorse-pgp-uid-list-box.c
index de5fab10..d885786d 100644
--- a/pgp/seahorse-pgp-uid-list-box.c
+++ b/pgp/seahorse-pgp-uid-list-box.c
@@ -33,9 +33,11 @@
 /* ListBox object */
 
 struct _SeahorsePgpUidListBox {
-    GtkListBox parent_instance;
+    AdwPreferencesGroup parent_instance;
 
     SeahorsePgpKey *key;
+
+    GPtrArray *rows;
 };
 
 enum {
@@ -45,39 +47,108 @@ enum {
 };
 static GParamSpec *obj_props[N_PROPS] = { NULL, };
 
-G_DEFINE_TYPE (SeahorsePgpUidListBox, seahorse_pgp_uid_list_box, GTK_TYPE_LIST_BOX);
+G_DEFINE_TYPE (SeahorsePgpUidListBox, seahorse_pgp_uid_list_box, ADW_TYPE_PREFERENCES_GROUP);
 
-static GtkWidget *
-create_row_for_uid (void *item, void *user_data)
+static void
+on_add_uid_clicked (GtkButton *widget, void *user_data)
 {
-    g_return_val_if_fail (SEAHORSE_PGP_IS_UID (item), NULL);
+    SeahorsePgpUidListBox *self = SEAHORSE_PGP_UID_LIST_BOX (user_data);
+    GtkRoot *root;
 
-    return g_object_new (SEAHORSE_PGP_TYPE_UID_LIST_BOX_ROW,
-                         "uid", SEAHORSE_PGP_UID (item),
-                         NULL);
+    g_return_if_fail (SEAHORSE_GPGME_IS_KEY (self->key));
+
+    root = gtk_widget_get_root (widget);
+    seahorse_gpgme_add_uid_run_dialog (SEAHORSE_GPGME_KEY (self->key),
+                                       GTK_WINDOW (root));
+}
+
+static void
+on_key_uids_changed (GListModel *uids,
+                     unsigned int position,
+                     unsigned int removed,
+                     unsigned int added,
+                     void *user_data)
+{
+    SeahorsePgpUidListBox *self = SEAHORSE_PGP_UID_LIST_BOX (user_data);
+
+    for (unsigned int i = 0; i < position + removed; i++) {
+        GtkWidget *row = g_ptr_array_index (self->rows, i);
+
+        adw_preferences_group_remove (ADW_PREFERENCES_GROUP (self),
+                                      row);
+        g_ptr_array_remove_index (self->rows, i);
+    }
+
+    for (unsigned int i = 0; i < position + added; i++) {
+        GtkWidget *row;
+        g_autoptr(SeahorsePgpUid) uid = NULL;
+
+        uid = g_list_model_get_item (uids, i);
+        row = g_object_new (SEAHORSE_PGP_TYPE_UID_LIST_BOX_ROW,
+                            "uid", uid,
+                            NULL);
+        g_ptr_array_insert (self->rows, i, row);
+        adw_preferences_group_add (ADW_PREFERENCES_GROUP (self),
+                                   row);
+    }
 }
 
 static void
 seahorse_pgp_uid_list_box_constructed (GObject *object)
 {
     SeahorsePgpUidListBox *self = SEAHORSE_PGP_UID_LIST_BOX (object);
+    GListModel *uids;
+    SeahorseUsage usage;
 
     G_OBJECT_CLASS (seahorse_pgp_uid_list_box_parent_class)->constructed (object);
 
-    gtk_list_box_bind_model (GTK_LIST_BOX (self),
-                             seahorse_pgp_key_get_uids (self->key),
-                             create_row_for_uid,
+    /* Setup the rows for each uid */
+    uids = seahorse_pgp_key_get_uids (self->key);
+    g_signal_connect_object (uids,
+                             "items-changed",
+                             G_CALLBACK (on_key_uids_changed),
                              self,
-                             NULL);
+                             0);
+    on_key_uids_changed (uids, 0, 0, g_list_model_get_n_items (uids), self);
+
+    /* If applicable, add a button to add a new uid too */
+    usage = seahorse_object_get_usage (SEAHORSE_OBJECT (self->key));
+    if (usage == SEAHORSE_USAGE_PRIVATE_KEY) {
+        GtkWidget *button_content;
+        GtkWidget *button;
+
+        button_content = adw_button_content_new ();
+        adw_button_content_set_icon_name (ADW_BUTTON_CONTENT (button_content),
+                                          "list-add-symbolic");
+        adw_button_content_set_label (ADW_BUTTON_CONTENT (button_content),
+                                      _("Add user ID"));
+
+        button = gtk_button_new ();
+        gtk_button_set_child (GTK_BUTTON (button), button_content);
+        gtk_widget_add_css_class (button, "flat");
+        g_signal_connect (button, "clicked", G_CALLBACK (on_add_uid_clicked), self);
+        adw_preferences_group_set_header_suffix (ADW_PREFERENCES_GROUP (self),
+                                                 button);
+    }
 }
 
 static void
 seahorse_pgp_uid_list_box_init (SeahorsePgpUidListBox *self)
 {
-    GtkStyleContext *style_context;
+    self->rows = g_ptr_array_new ();
+
+    adw_preferences_group_set_title (ADW_PREFERENCES_GROUP (self), _("User IDs"));
+}
+
+static void
+seahorse_pgp_uid_list_box_finalize (GObject *obj)
+{
+    SeahorsePgpUidListBox *self = SEAHORSE_PGP_UID_LIST_BOX (obj);
+
+    g_clear_pointer (&self->rows, g_ptr_array_unref);
+    g_clear_object (&self->key);
 
-    style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
-    gtk_style_context_add_class (style_context, "content");
+    G_OBJECT_CLASS (seahorse_pgp_uid_list_box_parent_class)->finalize (obj);
 }
 
 static void
@@ -107,7 +178,7 @@ seahorse_pgp_uid_list_box_set_property (GObject      *object,
 
     switch (prop_id) {
     case PROP_KEY:
-        self->key = g_value_get_object (value);
+        g_set_object (&self->key, g_value_get_object (value));
         break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -122,6 +193,7 @@ seahorse_pgp_uid_list_box_class_init (SeahorsePgpUidListBoxClass *klass)
     gobject_class->set_property = seahorse_pgp_uid_list_box_set_property;
     gobject_class->get_property = seahorse_pgp_uid_list_box_get_property;
     gobject_class->constructed = seahorse_pgp_uid_list_box_constructed;
+    gobject_class->finalize = seahorse_pgp_uid_list_box_finalize;
 
     obj_props[PROP_KEY] =
         g_param_spec_object ("key", "PGP key", "The key to list the UIDs for",
@@ -147,14 +219,13 @@ seahorse_pgp_uid_list_box_new (SeahorsePgpKey *key)
     /* XXX We should store the key and connect to ::notify */
     return g_object_new (SEAHORSE_PGP_TYPE_UID_LIST_BOX,
                          "key", key,
-                         "selection-mode", GTK_SELECTION_NONE,
                          NULL);
 }
 
 /* Row object */
 
 struct _SeahorsePgpUidListBoxRow {
-    HdyExpanderRow parent_instance;
+    AdwExpanderRow parent_instance;
 
     SeahorsePgpUid *uid;
 
@@ -172,7 +243,7 @@ enum {
     ROW_N_PROPS
 };
 
-G_DEFINE_TYPE (SeahorsePgpUidListBoxRow, seahorse_pgp_uid_list_box_row, HDY_TYPE_EXPANDER_ROW);
+G_DEFINE_TYPE (SeahorsePgpUidListBoxRow, seahorse_pgp_uid_list_box_row, ADW_TYPE_EXPANDER_ROW);
 
 static void
 update_actions (SeahorsePgpUidListBoxRow *row)
@@ -246,26 +317,36 @@ on_only_trusted_changed (GSimpleAction *action,
     gtk_widget_set_visible (row->signatures_list, n_shown > 0);
 }
 
+static void
+on_uid_delete_dialog_response (GtkDialog *dialog, int response, void *user_data)
+{
+    SeahorsePgpUidListBoxRow *row = SEAHORSE_PGP_UID_LIST_BOX_ROW (user_data);
+    gpgme_error_t gerr;
+
+    gerr = seahorse_gpgme_key_op_del_uid (SEAHORSE_GPGME_UID (row->uid));
+    if (!GPG_IS_OK (gerr))
+        seahorse_gpgme_handle_error (gerr, _("Couldn’t delete user ID"));
+
+    gtk_window_destroy (GTK_WINDOW (dialog));
+}
+
 static void
 on_uid_delete (GSimpleAction *action, GVariant *param, void *user_data)
 {
     SeahorsePgpUidListBoxRow *row = SEAHORSE_PGP_UID_LIST_BOX_ROW (user_data);
     GtkWidget *window;
     g_autofree char *message = NULL;
-    gpgme_error_t gerr;
+    GtkWidget *dialog;
 
     g_return_if_fail (SEAHORSE_GPGME_IS_UID (row->uid));
 
-    window = gtk_widget_get_toplevel (GTK_WIDGET (row));
+    window = GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (row)));
     message = g_strdup_printf (_("Are you sure you want to permanently delete the “%s” user ID?"),
                                seahorse_object_get_label (SEAHORSE_OBJECT (row->uid)));
 
-    if (!seahorse_delete_dialog_prompt (GTK_WINDOW (window), message))
-        return;
-
-    gerr = seahorse_gpgme_key_op_del_uid (SEAHORSE_GPGME_UID (row->uid));
-    if (!GPG_IS_OK (gerr))
-        seahorse_gpgme_handle_error (gerr, _("Couldn’t delete user ID"));
+    dialog = seahorse_delete_dialog_new (window, "%s", message);
+    g_signal_connect (dialog, "response", G_CALLBACK (on_uid_delete_dialog_response), row);
+    gtk_window_present (GTK_WINDOW (dialog));
 }
 
 static void
@@ -292,7 +373,7 @@ on_uid_make_primary (GSimpleAction *action, GVariant *param, void *user_data)
 
     /* Don't pass the row itself as user_data, as that might be destroyed as
      * part of the GListModel shuffle */
-    toplevel = gtk_widget_get_toplevel (GTK_WIDGET (row));
+    toplevel = GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (row)));
 
     seahorse_gpgme_key_op_make_primary_async (SEAHORSE_GPGME_UID (row->uid),
                                               NULL,
@@ -309,8 +390,8 @@ on_uid_sign (GSimpleAction *action, GVariant *param, void *user_data)
     g_return_if_fail (SEAHORSE_GPGME_IS_UID (row->uid));
 
     dialog = seahorse_gpgme_sign_dialog_new (SEAHORSE_OBJECT (row->uid));
-    gtk_dialog_run (GTK_DIALOG (dialog));
-    gtk_widget_destroy (GTK_WIDGET (dialog));
+    g_signal_connect (dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
+    gtk_window_present (GTK_WINDOW (dialog));
 }
 
 static const GActionEntry UID_ACTION_ENTRIES[] = {
@@ -335,13 +416,11 @@ create_row_for_signature (void *item, void *user_data)
 
     sig_row = gtk_list_box_row_new ();
     box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
-    gtk_widget_show (box);
-    gtk_container_add (GTK_CONTAINER (sig_row), box);
+    gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (sig_row), box);
 
     sig_keyid = seahorse_pgp_signature_get_keyid (signature);
     keyid_label = gtk_label_new (sig_keyid);
-    gtk_widget_show (keyid_label);
-    gtk_box_pack_start (GTK_BOX (box), keyid_label, FALSE, FALSE, 0);
+    gtk_box_append (GTK_BOX (box), keyid_label);
 
     for (GList *l = row->discovered_keys; l; l = g_list_next (l)) {
         if (SEAHORSE_PGP_IS_KEY (l->data)) {
@@ -373,8 +452,7 @@ create_row_for_signature (void *item, void *user_data)
     }
 
     signer_label = gtk_label_new (signer_name);
-    gtk_widget_show (signer_label);
-    gtk_box_pack_start (GTK_BOX (box), signer_label, FALSE, FALSE, 0);
+    gtk_box_append (GTK_BOX (box), signer_label);
 
     return sig_row;
 }
@@ -394,7 +472,7 @@ on_row_expanded (GObject *object,
 
     /* Lazily discover keys by only loading when user actually expands the row
      * (showing the signatures) and not reloading if already done earlier */
-    expanded = hdy_expander_row_get_expanded (HDY_EXPANDER_ROW (row));
+    expanded = adw_expander_row_get_expanded (ADW_EXPANDER_ROW (row));
     if (!expanded || row->discovered_keys)
         return;
 
@@ -445,12 +523,12 @@ update_row (SeahorsePgpUidListBoxRow *row)
     comment = seahorse_pgp_uid_get_comment (row->uid);
     if (comment && *comment)
         g_string_append_printf (title, " (%s)", comment);
-    hdy_preferences_row_set_title (HDY_PREFERENCES_ROW (row), title->str);
+    adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), title->str);
 
     /* Make a linkified version the email as subtitle */
     email = seahorse_pgp_uid_get_email (row->uid);
     if (email && *email)
-        hdy_expander_row_set_subtitle (HDY_EXPANDER_ROW (row), email);
+        adw_expander_row_set_subtitle (ADW_EXPANDER_ROW (row), email);
 
     /* Actions */
     gtk_widget_set_visible (row->actions_button, is_editable);
diff --git a/pgp/seahorse-pgp-uid-list-box.h b/pgp/seahorse-pgp-uid-list-box.h
index 9079ad4b..9b4fb235 100644
--- a/pgp/seahorse-pgp-uid-list-box.h
+++ b/pgp/seahorse-pgp-uid-list-box.h
@@ -26,11 +26,11 @@
 #define SEAHORSE_PGP_TYPE_UID_LIST_BOX (seahorse_pgp_uid_list_box_get_type ())
 G_DECLARE_FINAL_TYPE (SeahorsePgpUidListBox, seahorse_pgp_uid_list_box,
                       SEAHORSE_PGP, UID_LIST_BOX,
-                      GtkListBox)
+                      AdwPreferencesGroup)
 
 #define SEAHORSE_PGP_TYPE_UID_LIST_BOX_ROW (seahorse_pgp_uid_list_box_row_get_type ())
 G_DECLARE_FINAL_TYPE (SeahorsePgpUidListBoxRow, seahorse_pgp_uid_list_box_row,
                       SEAHORSE_PGP, UID_LIST_BOX_ROW,
-                      HdyExpanderRow)
+                      AdwExpanderRow)
 
 GtkWidget * seahorse_pgp_uid_list_box_new (SeahorsePgpKey *key);
diff --git a/pgp/seahorse-server-source.c b/pgp/seahorse-server-source.c
index e3170f26..e2ddbc72 100644
--- a/pgp/seahorse-server-source.c
+++ b/pgp/seahorse-server-source.c
@@ -46,13 +46,11 @@ enum {
     PROP_0,
     PROP_LABEL,
     PROP_DESCRIPTION,
-    PROP_ICON,
     PROP_CATEGORY,
     PROP_URI,
     PROP_ACTIONS,
     PROP_ACTION_PREFIX,
     PROP_MENU_MODEL,
-    PROP_SHOW_IF_EMPTY,
     N_PROPS
 };
 
@@ -65,13 +63,13 @@ typedef struct _SeahorseServerSourcePrivate {
     gchar *uri;
 } SeahorseServerSourcePrivate;
 
-static void      seahorse_server_source_collection_init    (GcrCollectionIface *iface);
+static void      seahorse_server_source_list_model_init    (GListModelInterface *iface);
 
 static void      seahorse_server_source_place_iface        (SeahorsePlaceIface *iface);
 
 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (SeahorseServerSource, seahorse_server_source, G_TYPE_OBJECT,
                          G_ADD_PRIVATE (SeahorseServerSource)
-                         G_IMPLEMENT_INTERFACE (GCR_TYPE_COLLECTION, seahorse_server_source_collection_init);
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, seahorse_server_source_list_model_init);
                          G_IMPLEMENT_INTERFACE (SEAHORSE_TYPE_PLACE, seahorse_server_source_place_iface);
 );
 
@@ -91,14 +89,12 @@ seahorse_server_source_class_init (SeahorseServerSourceClass *klass)
     gobject_class->set_property = seahorse_server_set_property;
     gobject_class->get_property = seahorse_server_get_property;
 
-       g_object_class_override_property (gobject_class, PROP_LABEL, "label");
-       g_object_class_override_property (gobject_class, PROP_DESCRIPTION, "description");
-    g_object_class_override_property (gobject_class, PROP_ICON, "icon");
+    g_object_class_override_property (gobject_class, PROP_LABEL, "label");
+    g_object_class_override_property (gobject_class, PROP_DESCRIPTION, "description");
     g_object_class_override_property (gobject_class, PROP_CATEGORY, "category");
-       g_object_class_override_property (gobject_class, PROP_ACTIONS, "actions");
-       g_object_class_override_property (gobject_class, PROP_ACTION_PREFIX, "action-prefix");
-       g_object_class_override_property (gobject_class, PROP_MENU_MODEL, "menu-model");
-    g_object_class_override_property (gobject_class, PROP_SHOW_IF_EMPTY, "show-if-empty");
+    g_object_class_override_property (gobject_class, PROP_ACTIONS, "actions");
+    g_object_class_override_property (gobject_class, PROP_ACTION_PREFIX, "action-prefix");
+    g_object_class_override_property (gobject_class, PROP_MENU_MODEL, "menu-model");
 
     g_object_class_install_property (gobject_class, PROP_URI,
             g_param_spec_string ("uri", "Key Server URI",
@@ -130,7 +126,7 @@ seahorse_server_source_load (SeahorsePlace *self,
                               GAsyncReadyCallback callback,
                               gpointer user_data)
 {
-       g_return_if_reached ();
+    g_return_if_reached ();
 }
 
 static gboolean
@@ -138,7 +134,7 @@ seahorse_server_source_load_finish (SeahorsePlace *self,
                                      GAsyncResult *res,
                                      GError **error)
 {
-       g_return_val_if_reached (FALSE);
+    g_return_val_if_reached (FALSE);
 }
 
 static gchar *
@@ -148,7 +144,7 @@ seahorse_server_source_get_label (SeahorsePlace* self)
     SeahorseServerSourcePrivate *priv =
         seahorse_server_source_get_instance_private (ssrc);
 
-       return g_strdup (priv->server);
+    return g_strdup (priv->server);
 }
 
 static void
@@ -163,7 +159,7 @@ seahorse_server_source_get_description (SeahorsePlace* self)
     SeahorseServerSourcePrivate *priv =
         seahorse_server_source_get_instance_private (ssrc);
 
-       return g_strdup (priv->uri);
+    return g_strdup (priv->uri);
 }
 
 static gchar *
@@ -173,13 +169,7 @@ seahorse_server_source_get_uri (SeahorsePlace* self)
     SeahorseServerSourcePrivate *priv =
         seahorse_server_source_get_instance_private (ssrc);
 
-       return g_strdup (priv->uri);
-}
-
-static GIcon *
-seahorse_server_source_get_icon (SeahorsePlace* self)
-{
-       return g_themed_icon_new (NULL);
+    return g_strdup (priv->uri);
 }
 
 static SeahorsePlaceCategory
@@ -206,12 +196,6 @@ seahorse_server_source_get_menu_model (SeahorsePlace* self)
     return NULL;
 }
 
-static gboolean
-seahorse_server_source_get_show_if_empty (SeahorsePlace *place)
-{
-    return TRUE;
-}
-
 static void
 seahorse_server_source_place_iface (SeahorsePlaceIface *iface)
 {
@@ -221,12 +205,10 @@ seahorse_server_source_place_iface (SeahorsePlaceIface *iface)
     iface->get_action_prefix = seahorse_server_source_get_action_prefix;
     iface->get_menu_model = seahorse_server_source_get_menu_model;
     iface->get_description = seahorse_server_source_get_description;
-    iface->get_icon = seahorse_server_source_get_icon;
     iface->get_category = seahorse_server_source_get_category;
     iface->get_label = seahorse_server_source_get_label;
     iface->set_label = seahorse_server_source_set_label;
     iface->get_uri = seahorse_server_source_get_uri;
-    iface->get_show_if_empty = seahorse_server_source_get_show_if_empty;
 }
 
 static void
@@ -258,21 +240,18 @@ seahorse_server_get_property (GObject *obj,
                               GValue *value,
                               GParamSpec *pspec)
 {
-       SeahorseServerSource *self = SEAHORSE_SERVER_SOURCE (obj);
-       SeahorsePlace *place = SEAHORSE_PLACE (self);
-
-       switch (prop_id) {
-       case PROP_LABEL:
-               g_value_take_string (value, seahorse_server_source_get_label (place));
-               break;
-       case PROP_DESCRIPTION:
-               g_value_take_string (value, seahorse_server_source_get_description (place));
-               break;
-       case PROP_URI:
-               g_value_take_string (value, seahorse_server_source_get_uri (place));
-               break;
-    case PROP_ICON:
-        g_value_take_object (value, seahorse_server_source_get_icon (place));
+    SeahorseServerSource *self = SEAHORSE_SERVER_SOURCE (obj);
+    SeahorsePlace *place = SEAHORSE_PLACE (self);
+
+    switch (prop_id) {
+    case PROP_LABEL:
+        g_value_take_string (value, seahorse_server_source_get_label (place));
+        break;
+    case PROP_DESCRIPTION:
+        g_value_take_string (value, seahorse_server_source_get_description (place));
+        break;
+    case PROP_URI:
+        g_value_take_string (value, seahorse_server_source_get_uri (place));
         break;
     case PROP_CATEGORY:
         g_value_set_enum (value, seahorse_server_source_get_category (place));
@@ -286,57 +265,56 @@ seahorse_server_get_property (GObject *obj,
     case PROP_MENU_MODEL:
         g_value_set_object (value, seahorse_server_source_get_menu_model (place));
         break;
-    case PROP_SHOW_IF_EMPTY:
-        g_value_set_boolean (value, TRUE);
-        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
         break;
     }
 }
 
-static guint
-seahorse_server_source_get_length (GcrCollection *collection)
+static unsigned int
+seahorse_server_source_get_n_items (GListModel *list)
 {
-       return 0;
+    return 0;
 }
 
-static GList *
-seahorse_server_source_get_objects (GcrCollection *collection)
+static void *
+seahorse_server_source_get_item (GListModel *list,
+                                 unsigned int pos)
 {
-       return NULL;
+    return NULL;
 }
 
-static gboolean
-seahorse_server_source_contains (GcrCollection *collection,
-                                 GObject *object)
+static GType
+seahorse_server_source_get_item_type (GListModel *list)
 {
-       return FALSE;
+    return G_TYPE_OBJECT;
 }
 
 static void
-seahorse_server_source_collection_init (GcrCollectionIface *iface)
+seahorse_server_source_list_model_init (GListModelInterface *iface)
 {
-       /* This is implemented because SeahorseSource requires it */
-       iface->get_length = seahorse_server_source_get_length;
-       iface->get_objects = seahorse_server_source_get_objects;
-       iface->contains = seahorse_server_source_contains;
+    /* This is implemented because SeahorseSource requires it */
+    iface->get_n_items = seahorse_server_source_get_n_items;
+    iface->get_item = seahorse_server_source_get_item;
+    iface->get_item_type = seahorse_server_source_get_item_type;
 }
 
 void
 seahorse_server_source_search_async (SeahorseServerSource *self,
-                                     const gchar *match,
-                                     GcrSimpleCollection *results,
-                                     GCancellable *cancellable,
-                                     GAsyncReadyCallback callback,
-                                     gpointer user_data)
+                                     const char           *match,
+                                     GListStore           *results,
+                                     GCancellable         *cancellable,
+                                     GAsyncReadyCallback   callback,
+                                     gpointer              user_data)
 {
-       g_return_if_fail (SEAHORSE_IS_SERVER_SOURCE (self));
-       g_return_if_fail (match != NULL);
-       g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
-       g_return_if_fail (SEAHORSE_SERVER_SOURCE_GET_CLASS (self)->search_async);
-       SEAHORSE_SERVER_SOURCE_GET_CLASS (self)->search_async (self, match, results,
-                                                              cancellable, callback, user_data);
+    g_return_if_fail (SEAHORSE_IS_SERVER_SOURCE (self));
+    g_return_if_fail (match != NULL);
+    g_return_if_fail (G_IS_LIST_STORE (results));
+    g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+    g_return_if_fail (SEAHORSE_SERVER_SOURCE_GET_CLASS (self)->search_async);
+
+    SEAHORSE_SERVER_SOURCE_GET_CLASS (self)->search_async (self, match, results,
+                                                           cancellable, callback, user_data);
 }
 
 gboolean
@@ -344,47 +322,45 @@ seahorse_server_source_search_finish (SeahorseServerSource *self,
                                       GAsyncResult *result,
                                       GError **error)
 {
-       g_return_val_if_fail (SEAHORSE_IS_SERVER_SOURCE (self), FALSE);
-       g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
-       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-       g_return_val_if_fail (SEAHORSE_SERVER_SOURCE_GET_CLASS (self)->search_finish, FALSE);
-       return SEAHORSE_SERVER_SOURCE_GET_CLASS (self)->search_finish (self, result, error);
+    g_return_val_if_fail (SEAHORSE_IS_SERVER_SOURCE (self), FALSE);
+    g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+    g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+    g_return_val_if_fail (SEAHORSE_SERVER_SOURCE_GET_CLASS (self)->search_finish, FALSE);
+
+    return SEAHORSE_SERVER_SOURCE_GET_CLASS (self)->search_finish (self, result, error);
 }
 
 void
 seahorse_server_source_export_async (SeahorseServerSource *self,
-                                     const gchar **keyids,
+                                     const char **keyids,
                                      GCancellable *cancellable,
                                      GAsyncReadyCallback callback,
                                      gpointer user_data)
 {
-       SeahorseServerSourceClass *klass;
+    SeahorseServerSourceClass *klass;
 
-       g_return_if_fail (SEAHORSE_IS_SERVER_SOURCE (self));
-       g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+    g_return_if_fail (SEAHORSE_IS_SERVER_SOURCE (self));
+    g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
 
-       klass = SEAHORSE_SERVER_SOURCE_GET_CLASS (self);
-       g_return_if_fail (klass->export_async);
-       (klass->export_async) (self, keyids, cancellable, callback, user_data);
+    klass = SEAHORSE_SERVER_SOURCE_GET_CLASS (self);
+    g_return_if_fail (klass->export_async);
+    (klass->export_async) (self, keyids, cancellable, callback, user_data);
 }
 
-gpointer
+GBytes *
 seahorse_server_source_export_finish (SeahorseServerSource *self,
                                       GAsyncResult *result,
-                                      gsize *size,
                                       GError **error)
 {
-       SeahorseServerSourceClass *klass;
+    SeahorseServerSourceClass *klass;
 
-       g_return_val_if_fail (SEAHORSE_IS_SERVER_SOURCE (self), NULL);
-       g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
-       g_return_val_if_fail (size != NULL, NULL);
-       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+    g_return_val_if_fail (SEAHORSE_IS_SERVER_SOURCE (self), NULL);
+    g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+    g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
-       klass = SEAHORSE_SERVER_SOURCE_GET_CLASS (self);
-       g_return_val_if_fail (klass->export_async != NULL, NULL);
-       g_return_val_if_fail (klass->export_finish != NULL, NULL);
-       return (klass->export_finish) (self, result, size, error);
+    klass = SEAHORSE_SERVER_SOURCE_GET_CLASS (self);
+    g_return_val_if_fail (klass->export_finish != NULL, NULL);
+    return (klass->export_finish) (self, result, error);
 }
 
 void
@@ -394,12 +370,12 @@ seahorse_server_source_import_async (SeahorseServerSource *source,
                                      GAsyncReadyCallback callback,
                                      gpointer user_data)
 {
-       g_return_if_fail (SEAHORSE_IS_SERVER_SOURCE (source));
-       g_return_if_fail (G_IS_INPUT_STREAM (input));
-       g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
-       g_return_if_fail (SEAHORSE_SERVER_SOURCE_GET_CLASS (source)->import_async);
-       SEAHORSE_SERVER_SOURCE_GET_CLASS (source)->import_async (source, input, cancellable,
-                                                                callback, user_data);
+    g_return_if_fail (SEAHORSE_IS_SERVER_SOURCE (source));
+    g_return_if_fail (G_IS_INPUT_STREAM (input));
+    g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+    g_return_if_fail (SEAHORSE_SERVER_SOURCE_GET_CLASS (source)->import_async);
+    SEAHORSE_SERVER_SOURCE_GET_CLASS (source)->import_async (source, input, cancellable,
+                                                             callback, user_data);
 }
 
 GList *
@@ -407,9 +383,9 @@ seahorse_server_source_import_finish (SeahorseServerSource *source,
                                       GAsyncResult *result,
                                       GError **error)
 {
-       g_return_val_if_fail (SEAHORSE_IS_SERVER_SOURCE (source), NULL);
-       g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
-       g_return_val_if_fail (error == NULL || *error == NULL, NULL);
-       g_return_val_if_fail (SEAHORSE_SERVER_SOURCE_GET_CLASS (source)->import_finish, NULL);
-       return SEAHORSE_SERVER_SOURCE_GET_CLASS (source)->import_finish (source, result, error);
+    g_return_val_if_fail (SEAHORSE_IS_SERVER_SOURCE (source), NULL);
+    g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+    g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+    g_return_val_if_fail (SEAHORSE_SERVER_SOURCE_GET_CLASS (source)->import_finish, NULL);
+    return SEAHORSE_SERVER_SOURCE_GET_CLASS (source)->import_finish (source, result, error);
 }
diff --git a/pgp/seahorse-server-source.h b/pgp/seahorse-server-source.h
index 9d009b41..fada5a45 100644
--- a/pgp/seahorse-server-source.h
+++ b/pgp/seahorse-server-source.h
@@ -46,46 +46,45 @@ G_DECLARE_DERIVABLE_TYPE (SeahorseServerSource, seahorse_server_source,
                           GObject)
 
 struct _SeahorseServerSourceClass {
-       GObjectClass parent_class;
-
-       void            (* import_async)         (SeahorseServerSource *source,
-                                                 GInputStream *input,
-                                                 GCancellable *cancellable,
-                                                 GAsyncReadyCallback callback,
-                                                 gpointer user_data);
-
-       GList *         (* import_finish)        (SeahorseServerSource *source,
-                                                 GAsyncResult *result,
-                                                 GError **error);
-
-       void            (*export_async)          (SeahorseServerSource *source,
-                                                 const gchar **keyids,
-                                                 GCancellable *cancellable,
-                                                 GAsyncReadyCallback callback,
-                                                 gpointer user_data);
-
-       gpointer        (*export_finish)         (SeahorseServerSource *source,
-                                                 GAsyncResult *result,
-                                                 gsize *size,
-                                                 GError **error);
-
-       void            (*search_async)          (SeahorseServerSource *source,
-                                                 const gchar *match,
-                                                 GcrSimpleCollection *results,
-                                                 GCancellable *cancellable,
-                                                 GAsyncReadyCallback callback,
-                                                 gpointer user_data);
-
-       gboolean        (*search_finish)         (SeahorseServerSource *source,
-                                                 GAsyncResult *result,
-                                                 GError **error);
+    GObjectClass parent_class;
+
+    void            (*import_async)          (SeahorseServerSource *source,
+                                              GInputStream *input,
+                                              GCancellable *cancellable,
+                                              GAsyncReadyCallback callback,
+                                              gpointer user_data);
+
+    GList *         (*import_finish)         (SeahorseServerSource *source,
+                                              GAsyncResult *result,
+                                              GError **error);
+
+    void            (*export_async)          (SeahorseServerSource *source,
+                                              const gchar **keyids,
+                                              GCancellable *cancellable,
+                                              GAsyncReadyCallback callback,
+                                              gpointer user_data);
+
+    GBytes *        (*export_finish)         (SeahorseServerSource *source,
+                                              GAsyncResult *result,
+                                              GError **error);
+
+    void            (*search_async)          (SeahorseServerSource *source,
+                                              const char *match,
+                                              GListStore *results,
+                                              GCancellable *cancellable,
+                                              GAsyncReadyCallback callback,
+                                              gpointer user_data);
+
+    gboolean        (*search_finish)         (SeahorseServerSource *source,
+                                              GAsyncResult *result,
+                                              GError **error);
 };
 
 SeahorseServerSource*  seahorse_server_source_new              (const char *uri);
 
 void                   seahorse_server_source_search_async     (SeahorseServerSource *self,
-                                                                const gchar *match,
-                                                                GcrSimpleCollection *results,
+                                                                const char *match,
+                                                                GListStore *results,
                                                                 GCancellable *cancellable,
                                                                 GAsyncReadyCallback callback,
                                                                 gpointer user_data);
@@ -110,7 +109,6 @@ void                   seahorse_server_source_export_async     (SeahorseServerSo
                                                                 GAsyncReadyCallback callback,
                                                                 gpointer user_data);
 
-gpointer               seahorse_server_source_export_finish    (SeahorseServerSource *self,
+GBytes *               seahorse_server_source_export_finish    (SeahorseServerSource *self,
                                                                 GAsyncResult *result,
-                                                                gsize *size,
                                                                 GError **error);
diff --git a/pgp/seahorse-transfer.c b/pgp/seahorse-transfer.c
index c7e91851..00f0f60c 100644
--- a/pgp/seahorse-transfer.c
+++ b/pgp/seahorse-transfer.c
@@ -104,8 +104,11 @@ on_source_export_ready (GObject *object,
     seahorse_progress_end (cancellable, &closure->from);
 
     if (SEAHORSE_IS_SERVER_SOURCE (closure->from)) {
-        stream_data = seahorse_server_source_export_finish (SEAHORSE_SERVER_SOURCE (object),
-                                                            result, &stream_size, &error);
+        GBytes *stream_bytes;
+
+        stream_bytes = seahorse_server_source_export_finish (SEAHORSE_SERVER_SOURCE (object),
+                                                             result, &error);
+        stream_data = g_bytes_unref_to_data (stream_bytes, &stream_size);
 
     } else if (SEAHORSE_IS_GPGME_KEYRING (closure->from)) {
         stream_data = seahorse_exporter_export_finish (SEAHORSE_EXPORTER (object), result,
diff --git a/pgp/seahorse-unknown-source.c b/pgp/seahorse-unknown-source.c
index a4f4a308..c22bbddd 100644
--- a/pgp/seahorse-unknown-source.c
+++ b/pgp/seahorse-unknown-source.c
@@ -23,9 +23,8 @@
 #include "seahorse-unknown-source.h"
 
 #include "seahorse-pgp-key.h"
-#include "seahorse-unknown.h"
 
-#include <gcr/gcr-base.h>
+#include <gcr/gcr.h>
 
 #include <glib/gi18n.h>
 
@@ -33,40 +32,49 @@ enum {
     PROP_0,
     PROP_LABEL,
     PROP_DESCRIPTION,
-    PROP_ICON,
     PROP_CATEGORY,
     PROP_URI,
     PROP_ACTIONS,
     PROP_ACTION_PREFIX,
     PROP_MENU_MODEL,
-    PROP_SHOW_IF_EMPTY,
     N_PROPS
 };
 
 struct _SeahorseUnknownSource {
-       GObject parent;
-       GHashTable *keys;
-};
+    GObject parent;
 
-struct _SeahorseUnknownSourceClass {
-       GObjectClass parent_class;
+    GPtrArray *keys;
 };
 
-static void      seahorse_unknown_source_collection_iface      (GcrCollectionIface *iface);
+static void      seahorse_unknown_source_list_model_iface      (GListModelInterface *iface);
 
 static void      seahorse_unknown_source_place_iface           (SeahorsePlaceIface *iface);
 
 G_DEFINE_TYPE_WITH_CODE (SeahorseUnknownSource, seahorse_unknown_source, G_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (GCR_TYPE_COLLECTION, 
seahorse_unknown_source_collection_iface);
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, seahorse_unknown_source_list_model_iface);
                          G_IMPLEMENT_INTERFACE (SEAHORSE_TYPE_PLACE, seahorse_unknown_source_place_iface);
 );
 
 static void
 seahorse_unknown_source_init (SeahorseUnknownSource *self)
 {
-       self->keys = g_hash_table_new_full (seahorse_pgp_keyid_hash,
-                                           seahorse_pgp_keyid_equal,
-                                           g_free, g_object_unref);
+    self->keys = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+static SeahorseUnknown *
+seahorse_unknown_lookup_by_keyid (SeahorseUnknownSource *self,
+                                  const char *keyid)
+{
+    for (unsigned int i = 0; i < self->keys->len; i++) {
+        SeahorseUnknown *unknown = g_ptr_array_index (self->keys, i);
+        const char *unknown_keyid;
+
+        unknown_keyid = seahorse_unknown_get_keyid (unknown);
+        if (seahorse_pgp_keyid_equal (keyid, unknown_keyid))
+            return unknown;
+    }
+
+    return NULL;
 }
 
 static void
@@ -75,7 +83,7 @@ seahorse_unknown_source_load (SeahorsePlace *self,
                               GAsyncReadyCallback callback,
                               gpointer user_data)
 {
-       g_return_if_reached ();
+    g_return_if_reached ();
 }
 
 static gboolean
@@ -83,13 +91,13 @@ seahorse_unknown_source_load_finish (SeahorsePlace *self,
                                      GAsyncResult *res,
                                      GError **error)
 {
-       g_return_val_if_reached (FALSE);
+    g_return_val_if_reached (FALSE);
 }
 
 static gchar *
 seahorse_unknown_source_get_label (SeahorsePlace* self)
 {
-       return g_strdup ("");
+    return g_strdup ("");
 }
 
 static void
@@ -100,19 +108,13 @@ seahorse_unknown_source_set_label (SeahorsePlace *self, const char *label)
 static gchar *
 seahorse_unknown_source_get_description (SeahorsePlace* self)
 {
-       return NULL;
+    return NULL;
 }
 
 static gchar *
 seahorse_unknown_source_get_uri (SeahorsePlace* self)
 {
-       return NULL;
-}
-
-static GIcon *
-seahorse_unknown_source_get_icon (SeahorsePlace* self)
-{
-       return NULL;
+    return NULL;
 }
 
 static SeahorsePlaceCategory
@@ -124,7 +126,7 @@ seahorse_unknown_source_get_category (SeahorsePlace *place)
 static GActionGroup *
 seahorse_unknown_source_get_actions (SeahorsePlace* self)
 {
-       return NULL;
+    return NULL;
 }
 
 static const gchar *
@@ -136,13 +138,7 @@ seahorse_unknown_source_get_action_prefix (SeahorsePlace* self)
 static GMenuModel *
 seahorse_unknown_source_get_menu_model (SeahorsePlace* self)
 {
-       return NULL;
-}
-
-static gboolean
-seahorse_unknown_source_get_show_if_empty (SeahorsePlace *place)
-{
-    return TRUE;
+    return NULL;
 }
 
 static void
@@ -151,20 +147,17 @@ seahorse_unknown_source_get_property (GObject *obj,
                                       GValue *value,
                                       GParamSpec *pspec)
 {
-       SeahorsePlace *place = SEAHORSE_PLACE (obj);
-
-       switch (prop_id) {
-       case PROP_LABEL:
-               g_value_take_string (value, seahorse_unknown_source_get_label (place));
-               break;
-       case PROP_DESCRIPTION:
-               g_value_take_string (value, seahorse_unknown_source_get_description (place));
-               break;
-       case PROP_URI:
-               g_value_take_string (value, seahorse_unknown_source_get_uri (place));
-               break;
-    case PROP_ICON:
-        g_value_take_object (value, seahorse_unknown_source_get_icon (place));
+    SeahorsePlace *place = SEAHORSE_PLACE (obj);
+
+    switch (prop_id) {
+    case PROP_LABEL:
+        g_value_take_string (value, seahorse_unknown_source_get_label (place));
+        break;
+    case PROP_DESCRIPTION:
+        g_value_take_string (value, seahorse_unknown_source_get_description (place));
+        break;
+    case PROP_URI:
+        g_value_take_string (value, seahorse_unknown_source_get_uri (place));
         break;
     case PROP_CATEGORY:
         g_value_set_enum (value, seahorse_unknown_source_get_category (place));
@@ -178,9 +171,6 @@ seahorse_unknown_source_get_property (GObject *obj,
     case PROP_MENU_MODEL:
         g_value_take_object (value, seahorse_unknown_source_get_menu_model (place));
         break;
-    case PROP_SHOW_IF_EMPTY:
-        g_value_set_boolean (value, TRUE);
-        break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
         break;
@@ -193,77 +183,78 @@ seahorse_unknown_source_set_property (GObject *obj,
                                       const GValue *value,
                                       GParamSpec *pspec)
 {
-       SeahorsePlace *place = SEAHORSE_PLACE (obj);
-
-       switch (prop_id) {
-       case PROP_LABEL:
-               seahorse_unknown_source_set_label (place, g_value_get_boxed (value));
-               break;
-       default:
-               G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
-               break;
-       }
+    SeahorsePlace *place = SEAHORSE_PLACE (obj);
+
+    switch (prop_id) {
+    case PROP_LABEL:
+        seahorse_unknown_source_set_label (place, g_value_get_boxed (value));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+        break;
+    }
 }
 
 static void
 seahorse_unknown_source_finalize (GObject *obj)
 {
-       SeahorseUnknownSource *self = SEAHORSE_UNKNOWN_SOURCE (obj);
+    SeahorseUnknownSource *self = SEAHORSE_UNKNOWN_SOURCE (obj);
 
-       g_hash_table_destroy (self->keys);
+    g_ptr_array_unref (self->keys);
 
-       G_OBJECT_CLASS (seahorse_unknown_source_parent_class)->finalize (obj);
+    G_OBJECT_CLASS (seahorse_unknown_source_parent_class)->finalize (obj);
 }
 
 static void
 seahorse_unknown_source_class_init (SeahorseUnknownSourceClass *klass)
 {
-       GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 
-       gobject_class->get_property = seahorse_unknown_source_get_property;
-       gobject_class->set_property = seahorse_unknown_source_set_property;
-       gobject_class->finalize = seahorse_unknown_source_finalize;
+    gobject_class->get_property = seahorse_unknown_source_get_property;
+    gobject_class->set_property = seahorse_unknown_source_set_property;
+    gobject_class->finalize = seahorse_unknown_source_finalize;
 
     g_object_class_override_property (gobject_class, PROP_LABEL, "label");
     g_object_class_override_property (gobject_class, PROP_DESCRIPTION, "description");
-    g_object_class_override_property (gobject_class, PROP_ICON, "icon");
     g_object_class_override_property (gobject_class, PROP_CATEGORY, "category");
     g_object_class_override_property (gobject_class, PROP_ACTIONS, "actions");
     g_object_class_override_property (gobject_class, PROP_ACTION_PREFIX, "action-prefix");
     g_object_class_override_property (gobject_class, PROP_MENU_MODEL, "menu-model");
     g_object_class_override_property (gobject_class, PROP_URI, "uri");
-    g_object_class_override_property (gobject_class, PROP_SHOW_IF_EMPTY, "show-if-empty");
 }
 
-static guint
-seahorse_unknown_source_get_length (GcrCollection *collection)
+static unsigned int
+seahorse_unknown_source_get_n_items (GListModel *list)
 {
-       SeahorseUnknownSource *self = SEAHORSE_UNKNOWN_SOURCE (collection);
-       return g_hash_table_size (self->keys);
+    SeahorseUnknownSource *self = SEAHORSE_UNKNOWN_SOURCE (list);
+
+    return self->keys->len;
 }
 
-static GList *
-seahorse_unknown_source_get_objects (GcrCollection *collection)
+static void *
+seahorse_unknown_source_get_item (GListModel *list,
+                                  unsigned int pos)
 {
-       SeahorseUnknownSource *self = SEAHORSE_UNKNOWN_SOURCE (collection);
-       return g_hash_table_get_values (self->keys);
+    SeahorseUnknownSource *self = SEAHORSE_UNKNOWN_SOURCE (list);
+
+    if (pos >= self->keys->len)
+        return NULL;
+
+    return g_object_ref (g_ptr_array_index (self->keys, pos));
 }
 
-static gboolean
-seahorse_unknown_source_contains (GcrCollection *collection,
-                                  GObject *object)
+static GType
+seahorse_unknown_source_get_item_type (GListModel *list)
 {
-       SeahorseUnknownSource *self = SEAHORSE_UNKNOWN_SOURCE (collection);
-       const gchar *identifier = seahorse_object_get_identifier (SEAHORSE_OBJECT (object));
-       return g_hash_table_lookup (self->keys, identifier) == object;
+    return SEAHORSE_TYPE_UNKNOWN;
 }
 
 static void
-seahorse_unknown_source_collection_iface (GcrCollectionIface *iface)
+seahorse_unknown_source_list_model_iface (GListModelInterface *iface)
 {
-       iface->contains = seahorse_unknown_source_contains;
-       iface->get_length = seahorse_unknown_source_get_length;
-       iface->get_objects = seahorse_unknown_source_get_objects;
+    iface->get_n_items = seahorse_unknown_source_get_n_items;
+    iface->get_item = seahorse_unknown_source_get_item;
+    iface->get_item_type = seahorse_unknown_source_get_item_type;
 }
 
 static void
@@ -274,45 +265,44 @@ seahorse_unknown_source_place_iface (SeahorsePlaceIface *iface)
     iface->get_actions = seahorse_unknown_source_get_actions;
     iface->get_menu_model = seahorse_unknown_source_get_menu_model;
     iface->get_description = seahorse_unknown_source_get_description;
-    iface->get_icon = seahorse_unknown_source_get_icon;
     iface->get_category = seahorse_unknown_source_get_category;
     iface->get_label = seahorse_unknown_source_get_label;
     iface->set_label = seahorse_unknown_source_set_label;
     iface->get_uri = seahorse_unknown_source_get_uri;
-    iface->get_show_if_empty = seahorse_unknown_source_get_show_if_empty;
 }
 
 SeahorseUnknownSource*
 seahorse_unknown_source_new (void)
 {
-       return g_object_new (SEAHORSE_TYPE_UNKNOWN_SOURCE, NULL);
+    return g_object_new (SEAHORSE_TYPE_UNKNOWN_SOURCE, NULL);
 }
 
 static void
 on_cancellable_gone (gpointer user_data,
                      GObject *where_the_object_was)
 {
-       /* TODO: Change the icon */
+    /* TODO: Change the icon */
 }
 
-SeahorseObject *
+SeahorseUnknown *
 seahorse_unknown_source_add_object (SeahorseUnknownSource *self,
-                                    const gchar *keyid,
+                                    const char *keyid,
                                     GCancellable *cancellable)
 {
-       SeahorseObject *object;
+    SeahorseUnknown *unknown;
 
-       g_return_val_if_fail (keyid != NULL, NULL);
-       g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL);
+    g_return_val_if_fail (SEAHORSE_IS_UNKNOWN_SOURCE (self), NULL);
+    g_return_val_if_fail (keyid != NULL, NULL);
+    g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL);
 
-       object = g_hash_table_lookup (self->keys, keyid);
-       if (object == NULL) {
-               object = SEAHORSE_OBJECT (seahorse_unknown_new (self, keyid, NULL));
-               g_hash_table_insert (self->keys, g_strdup (keyid), object);
-       }
+    unknown = seahorse_unknown_lookup_by_keyid (self, keyid);
+    if (unknown == NULL) {
+        unknown = seahorse_unknown_new (self, keyid, NULL);
+        g_ptr_array_add (self->keys, unknown);
+    }
 
-       if (cancellable)
-               g_object_weak_ref (G_OBJECT (cancellable), on_cancellable_gone, object);
+    if (cancellable)
+        g_object_weak_ref (G_OBJECT (cancellable), on_cancellable_gone, unknown);
 
-       return object;
+    return unknown;
 }
diff --git a/pgp/seahorse-unknown-source.h b/pgp/seahorse-unknown-source.h
index a9f6a1b2..b242adc5 100644
--- a/pgp/seahorse-unknown-source.h
+++ b/pgp/seahorse-unknown-source.h
@@ -21,22 +21,15 @@
 #pragma once
 
 #include "seahorse-common.h"
+#include "seahorse-unknown.h"
 
-#define SEAHORSE_TYPE_UNKNOWN_SOURCE            (seahorse_unknown_source_get_type ())
-#define SEAHORSE_UNKNOWN_SOURCE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
SEAHORSE_TYPE_UNKNOWN_SOURCE, SeahorseUnknownSource))
-#define SEAHORSE_UNKNOWN_SOURCE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), 
SEAHORSE_TYPE_UNKNOWN_SOURCE, SeahorseUnknownSourceClass))
-#define SEAHORSE_IS_UNKNOWN_SOURCE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
SEAHORSE_TYPE_UNKNOWN_SOURCE))
-#define SEAHORSE_IS_UNKNOWN_SOURCE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
SEAHORSE_TYPE_UNKNOWN_SOURCE))
-#define SEAHORSE_UNKNOWN_SOURCE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), 
SEAHORSE_TYPE_UNKNOWN_SOURCE, SeahorseUnknownSourceClass))
+#define SEAHORSE_TYPE_UNKNOWN_SOURCE (seahorse_unknown_source_get_type ())
+G_DECLARE_FINAL_TYPE (SeahorseUnknownSource, seahorse_unknown_source,
+                      SEAHORSE, UNKNOWN_SOURCE,
+                      GObject)
 
-typedef struct _SeahorseUnknownSource SeahorseUnknownSource;
-typedef struct _SeahorseUnknownSourceClass SeahorseUnknownSourceClass;
-typedef struct _SeahorseUnknownSourcePrivate SeahorseUnknownSourcePrivate;
+SeahorseUnknownSource *   seahorse_unknown_source_new           (void);
 
-GType                    seahorse_unknown_source_get_type      (void);
-
-SeahorseUnknownSource*   seahorse_unknown_source_new           (void);
-
-SeahorseObject*          seahorse_unknown_source_add_object    (SeahorseUnknownSource *self,
-                                                                const gchar *keyid,
-                                                                GCancellable *cancellable);
+SeahorseUnknown *         seahorse_unknown_source_add_object    (SeahorseUnknownSource *self,
+                                                                 const char *keyid,
+                                                                 GCancellable *cancellable);
diff --git a/pgp/seahorse-unknown.c b/pgp/seahorse-unknown.c
index 319620e4..9f99de1b 100644
--- a/pgp/seahorse-unknown.c
+++ b/pgp/seahorse-unknown.c
@@ -25,43 +25,104 @@
 
 #include <glib/gi18n.h>
 
+struct _SeahorseUnknown {
+    SeahorseObject parent;
+
+    char *keyid;
+};
+
+enum {
+    PROP_0,
+    PROP_KEYID,
+    N_PROPS
+};
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
 G_DEFINE_TYPE (SeahorseUnknown, seahorse_unknown, SEAHORSE_TYPE_OBJECT);
 
-/* -----------------------------------------------------------------------------
- * OBJECT
- */
+static void
+seahorse_unknown_get_property (GObject *object,
+                               unsigned int prop_id,
+                               GValue *value,
+                               GParamSpec *pspec)
+{
+    SeahorseUnknown *self = SEAHORSE_UNKNOWN (object);
 
+    switch (prop_id) {
+    case PROP_KEYID:
+        g_value_set_string (value, seahorse_unknown_get_keyid (self));
+        break;
+    }
+}
 
 static void
-seahorse_unknown_init (SeahorseUnknown *self)
+seahorse_unknown_set_property (GObject *object, guint prop_id, const GValue *value,
+                                 GParamSpec *pspec)
 {
+    SeahorseUnknown *self = SEAHORSE_UNKNOWN (object);
 
+    switch (prop_id) {
+    case PROP_KEYID:
+        g_clear_pointer (&self->keyid, g_free);
+        self->keyid = g_value_dup_string (value);
+        break;
+    }
 }
 
 static void
-seahorse_unknown_class_init (SeahorseUnknownClass *klass)
+seahorse_unknown_object_finalize (GObject *obj)
 {
+    SeahorseUnknown *self = SEAHORSE_UNKNOWN (obj);
 
+    g_clear_pointer (&self->keyid, g_free);
+
+    G_OBJECT_CLASS (seahorse_unknown_parent_class)->finalize (G_OBJECT (self));
 }
 
-/* -----------------------------------------------------------------------------
- * PUBLIC METHODS
- */
+static void
+seahorse_unknown_init (SeahorseUnknown *self)
+{
+}
+
+static void
+seahorse_unknown_class_init (SeahorseUnknownClass *klass)
+{
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+    gobject_class->finalize = seahorse_unknown_object_finalize;
+    gobject_class->set_property = seahorse_unknown_set_property;
+    gobject_class->get_property = seahorse_unknown_get_property;
+
+    properties[PROP_KEYID] =
+        g_param_spec_string ("keyid", NULL, NULL, NULL,
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
 
-SeahorseUnknown*
+    g_object_class_install_properties (gobject_class, N_PROPS, properties);
+}
+
+SeahorseUnknown *
 seahorse_unknown_new (SeahorseUnknownSource *source,
-                      const gchar *keyid,
-                      const gchar *display)
+                      const char *keyid,
+                      const char *display)
 {
-       const char *identifier;
+    const char *identifier;
+
+    if (!display)
+        display = _("Unavailable");
+    identifier = seahorse_pgp_key_calc_identifier (keyid);
 
-       if (!display)
-               display = _("Unavailable");
-       identifier = seahorse_pgp_key_calc_identifier (keyid);
+    return g_object_new (SEAHORSE_TYPE_UNKNOWN,
+                         "place", source,
+                         "label", display,
+                         "identifier", identifier,
+                         "keyid", keyid,
+                         NULL);
+}
+
+const char *
+seahorse_unknown_get_keyid (SeahorseUnknown *self)
+{
+    g_return_val_if_fail (SEAHORSE_IS_UNKNOWN (self), NULL);
 
-       return g_object_new (SEAHORSE_TYPE_UNKNOWN,
-                            "place", source,
-                            "label", display,
-                            "identifier", identifier,
-                            NULL);
+    return self->keyid;
 }
diff --git a/pgp/seahorse-unknown.h b/pgp/seahorse-unknown.h
index 304b2398..92609f4a 100644
--- a/pgp/seahorse-unknown.h
+++ b/pgp/seahorse-unknown.h
@@ -22,31 +22,16 @@
 #include <gtk/gtk.h>
 
 #include "seahorse-common.h"
-#include "seahorse-unknown-source.h"
 
-#define SEAHORSE_TYPE_UNKNOWN            (seahorse_unknown_get_type ())
-#define SEAHORSE_UNKNOWN(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), SEAHORSE_TYPE_UNKNOWN, 
SeahorseUnknown))
-#define SEAHORSE_UNKNOWN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), SEAHORSE_TYPE_UNKNOWN, 
SeahorseUnknownClass))
-#define SEAHORSE_IS_UNKNOWN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SEAHORSE_TYPE_UNKNOWN))
-#define SEAHORSE_IS_UNKNOWN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SEAHORSE_TYPE_UNKNOWN))
-#define SEAHORSE_UNKNOWN_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), SEAHORSE_TYPE_UNKNOWN, 
SeahorseUnknownClass))
+/* Solve a circular include */
+typedef struct _SeahorseUnknownSource SeahorseUnknownSource;
 
-typedef struct _SeahorseUnknown SeahorseUnknown;
-typedef struct _SeahorseUnknownClass SeahorseUnknownClass;
 
-struct _SeahorseUnknown {
-    SeahorseObject parent;
+#define SEAHORSE_TYPE_UNKNOWN (seahorse_unknown_get_type ())
+G_DECLARE_FINAL_TYPE (SeahorseUnknown, seahorse_unknown, SEAHORSE, UNKNOWN, SeahorseObject)
 
-    /*< public >*/
-    gchar *display;
-};
+SeahorseUnknown *    seahorse_unknown_new              (SeahorseUnknownSource *usrc,
+                                                        const char *keyid,
+                                                        const char *display);
 
-struct _SeahorseUnknownClass {
-    SeahorseObjectClass            parent_class;
-};
-
-GType                seahorse_unknown_get_type         (void);
-
-SeahorseUnknown*     seahorse_unknown_new              (SeahorseUnknownSource *usrc,
-                                                        const gchar *keyid,
-                                                        const gchar *display);
+const char *         seahorse_unknown_get_keyid        (SeahorseUnknown *self);
diff --git a/pgp/test-gpgme-backend.c b/pgp/test-gpgme-backend.c
index 64b6ce1a..5a24cf35 100644
--- a/pgp/test-gpgme-backend.c
+++ b/pgp/test-gpgme-backend.c
@@ -41,9 +41,9 @@ test_pgp_check_empty_keyring (PgpTestFixture *fixture,
     g_assert_nonnull (keyring);
     g_assert_true (SEAHORSE_IS_GPGME_KEYRING (keyring));
 
-    n_keys = gcr_collection_get_length (GCR_COLLECTION (keyring));
+    n_keys = g_list_model_get_n_items (G_LIST_MODEL (keyring));
     g_assert_cmpint (n_keys, ==, 0);
-    g_assert_null (gcr_collection_get_objects (GCR_COLLECTION (keyring)));
+    g_assert_null (g_list_model_get_item (G_LIST_MODEL (keyring), 0));
 }
 
 static void
diff --git a/pkcs11/certificate-der-exporter.vala b/pkcs11/certificate-der-exporter.vala
index 57e6ec25..9aaf1c41 100644
--- a/pkcs11/certificate-der-exporter.vala
+++ b/pkcs11/certificate-der-exporter.vala
@@ -54,7 +54,7 @@ public class CertificateDerExporter : GLib.Object, Exporter {
        public Gtk.FileFilter file_filter {
                owned get {
                        var filter = new Gtk.FileFilter();
-                       filter.set_name(_("Certificates (DER encoded)"));
+                       filter.name = _("Certificates (DER encoded)");
                        filter.add_mime_type ("application/pkix-cert");
                        filter.add_mime_type ("application/x-x509-ca-cert");
                        filter.add_mime_type ("application/x-x509-user-cert");
diff --git a/pkcs11/meson.build b/pkcs11/meson.build
index 342bc1c9..cac1bdba 100644
--- a/pkcs11/meson.build
+++ b/pkcs11/meson.build
@@ -9,6 +9,7 @@ pkcs11_sources = files(
   'pkcs11-properties.vala',
   'pkcs11-request.vala',
   'pkcs11-token.vala',
+  'pkcs11-token-filter.vala',
 
   'cryptoki.vapi',
   'seahorse-pkcs11-backend.c',
@@ -16,7 +17,6 @@ pkcs11_sources = files(
 
 pkcs11_deps = [
   glib_deps,
-  gcr_ui,
   pkcs11_dep,
   common_dep,
 ]
diff --git a/pkcs11/pkcs11-certificate.vala b/pkcs11/pkcs11-certificate.vala
index 03e626cf..ab75afe5 100644
--- a/pkcs11/pkcs11-certificate.vala
+++ b/pkcs11/pkcs11-certificate.vala
@@ -21,224 +21,202 @@
  * 02111-1307, USA.
  */
 
-namespace Seahorse {
-namespace Pkcs11 {
-
-public class Certificate : Gck.Object, Gcr.Comparable, Gcr.Certificate,
-                           Gck.ObjectCache, Deletable, Exportable, Viewable {
-       public Token? place {
-               owned get { return (Token?)this._token.get(); }
-               set { this._token.set(value); }
-       }
-
-       public Flags object_flags {
-               get { ensure_flags(); return this._flags; }
-       }
-
-       public Gtk.ActionGroup? actions {
-               get { return null; }
-       }
-
-       public PrivateKey? partner {
-               owned get { return (PrivateKey?)this._private_key.get(); }
-               set {
-                       this._private_key.set(value);
-                       this._icon = null;
-                       this.notify_property("partner");
-                       this.notify_property("icon");
-                       this.notify_property("description");
-               }
-       }
-
-       public Gck.Attributes attributes {
-               owned get { return this._attributes; }
-               set {
-                       this._attributes = value;
-                       this.notify_property("attributes");
-               }
-       }
-
-       public bool deletable {
-               get {
-                       var token = this.place;
-                       if (token == null)
-                               return false;
-                       return token.is_deletable(this);
-               }
-       }
-
-       public bool exportable {
-               get { return this._der != null; }
-       }
-
-       public GLib.Icon icon {
-               owned get {
-                       if (this._icon != null)
-                               return this._icon;
-                       var icon = new GLib.ThemedIcon(Gcr.ICON_CERTIFICATE);
-                       if (this._private_key.get() != null) {
-                               var eicon = new GLib.ThemedIcon (Gcr.ICON_KEY);
-                               var emblem = new GLib.Emblem (eicon);
-                               this._icon = new GLib.EmblemedIcon (icon, emblem);
-                       } else {
-                               this._icon = icon;
-                       }
-                       return this._icon;
-               }
-       }
-
-       public string description {
-               owned get {
-                       ensure_flags ();
-                       if (this._private_key.get() != null)
-                               return _("Personal certificate and key");
-                       if ((this._flags & Flags.PERSONAL) == Flags.PERSONAL)
-                               return _("Personal certificate");
-                       else
-                               return _("Certificate");
-               }
-       }
-
-       public string? label {
-               owned get { return get_subject_name(); }
-       }
-
-       public string? subject {
-               owned get { return get_subject_name(); }
-       }
-
-       public string? markup {
-               owned get { return get_markup_text(); }
-       }
-
-       public string? issuer {
-               owned get { return get_issuer_name(); }
-       }
-
-       public GLib.Date expiry {
-               owned get { return get_expiry_date(); }
-       }
-
-       private GLib.WeakRef _token;
-       private Gck.Attributes? _attributes;
-       private unowned Gck.Attribute? _der;
-       private GLib.WeakRef _private_key;
-       private GLib.Icon? _icon;
-       private Flags _flags;
-
-       private static uint8[] EMPTY = { };
-
-       construct {
-               this._flags = (Flags)uint.MAX;
-               this._der = null;
-               this._private_key = GLib.WeakRef(null);
-               this._token = GLib.WeakRef(null);
-
-               this.notify.connect((pspec) => {
-                       if (pspec.name != "attributes")
-                               return;
-                       if (this._attributes != null)
-                               this._der = this._attributes.find(CKA.VALUE);
-                       notify_property ("label");
-                       notify_property ("markup");
-                       notify_property ("subject");
-                       notify_property ("issuer");
-                       notify_property ("expiry");
-               });
-
-               if (this._attributes != null)
-                       this._der = this._attributes.find(CKA.VALUE);
-       }
-
-       public override void dispose() {
-               this.partner = null;
-               base.dispose();
-       }
-
-       public Gtk.Window? create_viewer(Gtk.Window? parent) {
-               var viewer = new Pkcs11.Properties(this, parent);
-               viewer.show();
-               return viewer;
-       }
-
-       public Seahorse.Deleter create_deleter() {
-               Seahorse.Deleter deleter;
-
-               PrivateKey? key = this.partner;
-               if (key == null) {
-                       deleter = new Pkcs11.Deleter(this);
-               } else {
-                       deleter = key.create_deleter();
-                       if (!deleter.add_object(this))
-                               GLib.return_val_if_reached(null);
-               }
-
-               return deleter;
-       }
-
-       public GLib.List<Exporter> create_exporters(ExporterType type) {
-               var exporters = new GLib.List<Exporter>();
-
-               if (this.exportable) {
-                       var exporter = new CertificateDerExporter(this);
-                       exporters.append(exporter);
-               }
-
-               return exporters;
-       }
-
-       public void fill(Gck.Attributes attributes) {
-               Gck.Builder builder = new Gck.Builder(Gck.BuilderFlags.NONE);
-
-               if (this._attributes != null)
-                       builder.add_all(this._attributes);
-               builder.set_all(attributes);
-               this._attributes = builder.steal();
-               this.notify_property("attributes");
-       }
-
-       [CCode (array_length_type = "gsize")]
-       public unowned uint8[] get_der_data() {
-               if (this._der == null)
-                       return EMPTY;
-               return this._der.get_data();
-       }
-
-       public int compare (Gcr.Comparable? other) {
-               if (other == null)
-                       return -1;
-               unowned uint8[] data1 = this.get_der_data();
-               unowned uint8[] data2 = ((Gcr.Certificate)other).get_der_data();
-               return Gcr.Comparable.memcmp(data1, data2);
-       }
-
-       private Flags calc_is_personal_and_trusted() {
-               ulong category = 0;
-               bool is_ca;
-
-               /* If a matching private key, then this is personal*/
-               if (this._private_key.get() != null)
-                       return Flags.PERSONAL | Flags.TRUSTED;
-
-               if (this._attributes != null &&
-                   this._attributes.find_ulong (CKA.CERTIFICATE_CATEGORY, out category)) {
-                       if (category == 2)
-                               return 0;
-                       else if (category == 1)
-                               return Flags.PERSONAL;
-               }
-
-               if (get_basic_constraints (out is_ca, null))
-                       return is_ca ? 0 : Flags.PERSONAL;
-
-               return Flags.PERSONAL;
-       }
-
-       private void ensure_flags() {
-               if (this._flags == uint.MAX)
-                       this._flags = Flags.EXPORTABLE | calc_is_personal_and_trusted ();
-       }
-}
-
-}
+public class Seahorse.Pkcs11.Certificate : Gck.Object, Gcr.Certificate,
+                                           Gck.ObjectCache, Deletable, Exportable, Viewable {
+    public Token? place {
+        owned get { return (Token?)this._token.get(); }
+        set { this._token.set(value); }
+    }
+
+    public Flags object_flags {
+        get { ensure_flags(); return this._flags; }
+    }
+
+    public PrivateKey? partner {
+        owned get { return (PrivateKey?)this._private_key.get(); }
+        set {
+            this._private_key.set(value);
+            this._icon = null;
+            this.notify_property("partner");
+            this.notify_property("icon");
+            this.notify_property("description");
+        }
+    }
+
+    public Gck.Attributes attributes {
+        owned get { return this._attributes; }
+        set {
+            this._attributes = value;
+            this.notify_property("attributes");
+        }
+    }
+
+    public bool deletable {
+        get {
+            var token = this.place;
+            if (token == null)
+                return false;
+            return token.is_deletable(this);
+        }
+    }
+
+    public bool exportable {
+        get { return this._der != null; }
+    }
+
+    public GLib.Icon icon {
+        owned get {
+            if (this._icon != null)
+                return this._icon;
+            //XXX
+            var icon = new GLib.ThemedIcon("application-certificate-symbolic");
+            if (this._private_key.get() != null) {
+            //     var eicon = new GLib.ThemedIcon (Gcr.ICON_KEY);
+            //     var emblem = new GLib.Emblem (eicon);
+            //     this._icon = new GLib.EmblemedIcon (icon, emblem);
+            } else {
+                this._icon = icon;
+            }
+            return this._icon;
+        }
+    }
+
+    public string description {
+        owned get {
+            ensure_flags ();
+            if (this._private_key.get() != null)
+                return _("Personal certificate and key");
+            if ((this._flags & Flags.PERSONAL) == Flags.PERSONAL)
+                return _("Personal certificate");
+            else
+                return _("Certificate");
+        }
+    }
+
+    public string? label {
+        owned get { return get_subject_name(); }
+    }
+
+    public string? subject_name {
+        owned get { return get_subject_name(); }
+    }
+
+    public string? issuer_name {
+        owned get { return get_issuer_name(); }
+    }
+
+    public GLib.DateTime? expiry_date {
+        owned get { return get_expiry_date(); }
+    }
+
+    private GLib.WeakRef _token;
+    private Gck.Attributes? _attributes;
+    private unowned Gck.Attribute? _der;
+    private GLib.WeakRef _private_key;
+    private GLib.Icon? _icon;
+    private Flags _flags;
+
+    private static uint8[] EMPTY = { };
+
+    construct {
+        this._flags = (Flags)uint.MAX;
+        this._der = null;
+        this._private_key = GLib.WeakRef(null);
+        this._token = GLib.WeakRef(null);
+
+        this.notify.connect((pspec) => {
+            if (pspec.name != "attributes")
+                return;
+            if (this._attributes != null)
+                this._der = this._attributes.find(CKA.VALUE);
+            notify_property ("label");
+            notify_property ("subject-name");
+            notify_property ("issuer-name");
+            notify_property ("expiry-date");
+        });
+
+        if (this._attributes != null)
+            this._der = this._attributes.find(CKA.VALUE);
+    }
+
+    public override void dispose() {
+        this.partner = null;
+        base.dispose();
+    }
+
+    public Gtk.Window? create_viewer(Gtk.Window? parent) {
+        var viewer = new Pkcs11.Properties(this, parent);
+        viewer.show();
+        return viewer;
+    }
+
+    public Seahorse.Deleter create_deleter() {
+        Seahorse.Deleter deleter;
+
+        PrivateKey? key = this.partner;
+        if (key == null) {
+            deleter = new Pkcs11.Deleter(this);
+        } else {
+            deleter = key.create_deleter();
+            if (!deleter.add_object(this))
+                GLib.return_val_if_reached(null);
+        }
+
+        return deleter;
+    }
+
+    public GLib.List<Exporter> create_exporters(ExporterType type) {
+        var exporters = new GLib.List<Exporter>();
+
+        if (this.exportable) {
+            var exporter = new CertificateDerExporter(this);
+            exporters.append(exporter);
+        }
+
+        return exporters;
+    }
+
+    public void fill(Gck.Attributes attributes) {
+        Gck.Builder builder = new Gck.Builder(Gck.BuilderFlags.NONE);
+
+        if (this._attributes != null)
+            builder.add_all(this._attributes);
+        builder.set_all(attributes);
+        this._attributes = builder.end();
+        this.notify_property("attributes");
+    }
+
+    [CCode (array_length_type = "gsize")]
+    public unowned uint8[] get_der_data() {
+        if (this._der == null)
+            return EMPTY;
+        return this._der.get_data();
+    }
+
+    private Flags calc_is_personal_and_trusted() {
+        ulong category = 0;
+        bool is_ca;
+
+        /* If a matching private key, then this is personal*/
+        if (this._private_key.get() != null)
+            return Flags.PERSONAL | Flags.TRUSTED;
+
+        if (this._attributes != null &&
+            this._attributes.find_ulong (CKA.CERTIFICATE_CATEGORY, out category)) {
+            if (category == 2)
+                return 0;
+            else if (category == 1)
+                return Flags.PERSONAL;
+        }
+
+        if (get_basic_constraints (out is_ca, null))
+            return is_ca ? 0 : Flags.PERSONAL;
+
+        return Flags.PERSONAL;
+    }
+
+    private void ensure_flags() {
+        if (this._flags == uint.MAX)
+            this._flags = Flags.EXPORTABLE | calc_is_personal_and_trusted ();
+    }
 }
diff --git a/pkcs11/pkcs11-deleter.vala b/pkcs11/pkcs11-deleter.vala
index 475b30c4..20e6571e 100644
--- a/pkcs11/pkcs11-deleter.vala
+++ b/pkcs11/pkcs11-deleter.vala
@@ -21,13 +21,11 @@
  * Author: Stef Walter <stefw redhat com>
  */
 
-namespace Seahorse {
-namespace Pkcs11 {
+public class Seahorse.Pkcs11.Deleter : Seahorse.Deleter {
 
-public class Deleter : Seahorse.Deleter {
        protected GLib.List<Gck.Object> objects;
 
-       public override Gtk.Dialog create_confirm(Gtk.Window? parent) {
+       public override Gtk.Window create_confirm(Gtk.Window? parent) {
                var num = this.objects.length();
                if (num == 1) {
                        string label;
@@ -69,7 +67,7 @@ public class Deleter : Seahorse.Deleter {
 
                        } catch (GLib.Error e) {
                                /* Ignore objects that have gone away */
-                               if (e.domain != Gck.Error.get_quark() ||
+                               if (e.domain != Gck.Error.quark() ||
                                    e.code != CKR.OBJECT_HANDLE_INVALID)
                                        throw e;
                        }
@@ -77,7 +75,3 @@ public class Deleter : Seahorse.Deleter {
                return true;
        }
 }
-
-
-}
-}
diff --git a/pkcs11/pkcs11-generate.vala b/pkcs11/pkcs11-generate.vala
index a7dbb499..11c78c7d 100644
--- a/pkcs11/pkcs11-generate.vala
+++ b/pkcs11/pkcs11-generate.vala
@@ -19,20 +19,16 @@
  * <http://www.gnu.org/licenses/>.
  */
 
-// FIXME: damn broken bindings
-extern Gcr.CollectionModel gcr_collection_model_new(Gcr.Collection collection,
-                                                    Gcr.CollectionModelMode mode, ...);
-
 [GtkTemplate (ui = "/org/gnome/Seahorse/seahorse-pkcs11-generate.ui")]
 public class Seahorse.Pkcs11.Generate : Gtk.Dialog {
 
     [GtkChild]
-    private unowned Gtk.Entry label_entry;
+    private unowned Adw.EntryRow label_row;
 
     private Pkcs11.Token? token;
     [GtkChild]
     private unowned Gtk.ComboBox token_box;
-    private Gcr.CollectionModel? token_model;
+    private GLib.ListModel token_model;
 
     private Gck.Mechanism? mechanism;
     private Gtk.ListStore? mechanism_store;
@@ -77,22 +73,26 @@ public class Seahorse.Pkcs11.Generate : Gtk.Dialog {
         this.mechanism_box.changed.connect(on_mechanism_changed);
 
         // The tokens
-        Gcr.Collection collection = Pkcs11.Backend.get_writable_tokens(null, 
Cryptoki.MechanismType.RSA_PKCS_KEY_PAIR_GEN);
-        this.token_model = gcr_collection_model_new(collection, Gcr.CollectionModelMode.LIST,
-                                                    "icon", typeof(Icon), "label", typeof(string),
-                                                    null);
-        this.token_model.set_sort_column_id(1, Gtk.SortType.ASCENDING);
-        this.token_box.set_model(this.token_model);
-        Gtk.CellRendererPixbuf icon_renderer = new Gtk.CellRendererPixbuf();
-        icon_renderer.stock_size = Gtk.IconSize.BUTTON;
-        this.token_box.pack_start(icon_renderer, false);
-        this.token_box.add_attribute(icon_renderer, "gicon", 0);
-        renderer = new Gtk.CellRendererText();
-        this.token_box.pack_start(renderer, true);
-        this.token_box.add_attribute(renderer, "text", 1);
-        this.token_box.changed.connect(on_token_changed);
-        if (collection.get_length() > 0)
-            this.token_box.active = 0;
+        var backend = Pkcs11.Backend.get();
+        var filter = new Pkcs11.TokenFilter();
+        filter.only_writable = true;
+        filter.mechanism = Cryptoki.MechanismType.RSA_PKCS_KEY_PAIR_GEN;
+        var model = new Gtk.FilterListModel(backend, filter);
+        // this.token_model = gcr_collection_model_new(collection, Gcr.CollectionModelMode.LIST,
+        //                                             "icon", typeof(Icon), "label", typeof(string),
+        //                                             null);
+        // this.token_model.set_sort_column_id(1, Gtk.SortType.ASCENDING);
+        // this.token_box.set_model(this.token_model);
+        // Gtk.CellRendererPixbuf icon_renderer = new Gtk.CellRendererPixbuf();
+        // icon_renderer.stock_size = Gtk.IconSize.BUTTON;
+        // this.token_box.pack_start(icon_renderer, false);
+        // this.token_box.add_attribute(icon_renderer, "gicon", 0);
+        // renderer = new Gtk.CellRendererText();
+        // this.token_box.pack_start(renderer, true);
+        // this.token_box.add_attribute(renderer, "text", 1);
+        // this.token_box.changed.connect(on_token_changed);
+        // if (collection.get_length() > 0)
+        //     this.token_box.active = 0;
 
         set_default_response (Gtk.ResponseType.OK);
 
@@ -103,17 +103,6 @@ public class Seahorse.Pkcs11.Generate : Gtk.Dialog {
         GLib.Object(transient_for: parent);
     }
 
-    ~Generate() {
-        this.token = null;
-        this.token_model = null;
-
-        this.mechanism = null;
-        this.mechanism_store = null;
-
-        this.cancellable = null;
-        this.pub_attrs = this.prv_attrs = null;
-    }
-
     private void update_response() {
         set_response_sensitive(Gtk.ResponseType.OK, this.token != null && this.mechanism != null);
     }
@@ -130,10 +119,11 @@ public class Seahorse.Pkcs11.Generate : Gtk.Dialog {
     private void on_token_changed(Gtk.ComboBox combo_box) {
         this.token = null;
 
+        // XXX
         Gtk.TreeIter iter;
-        if (combo_box.get_active_iter(out iter)) {
-            this.token = (Pkcs11.Token) this.token_model.object_for_iter(iter);
-        }
+        // if (combo_box.get_active_iter(out iter)) {
+        //     this.token = (Pkcs11.Token) this.token_model.object_for_iter(iter);
+        // }
 
         bool valid = this.mechanism_store.get_iter_first(out iter);
         if (this.token != null) {
@@ -248,7 +238,7 @@ public class Seahorse.Pkcs11.Generate : Gtk.Dialog {
         priva.add_boolean(Cryptoki.Attribute.PRIVATE, true);
         priva.add_boolean(Cryptoki.Attribute.SENSITIVE, true);
 
-        string label = this.label_entry.text;
+        string label = this.label_row.text;
         publi.add_string(Cryptoki.Attribute.LABEL, label);
         priva.add_string(Cryptoki.Attribute.LABEL, label);
 
@@ -267,8 +257,8 @@ public class Seahorse.Pkcs11.Generate : Gtk.Dialog {
             warning("currently no support for this mechanism");
         }
 
-        this.prv_attrs = priva.steal();
-        this.pub_attrs = publi.steal();
+        this.prv_attrs = priva.end();
+        this.pub_attrs = publi.end();
 
         publi.clear();
         priva.clear();
diff --git a/pkcs11/pkcs11-key-deleter.vala b/pkcs11/pkcs11-key-deleter.vala
index 3b34dbc6..efb7db30 100644
--- a/pkcs11/pkcs11-key-deleter.vala
+++ b/pkcs11/pkcs11-key-deleter.vala
@@ -29,7 +29,7 @@ class KeyDeleter : Deleter {
        private PrivateKey? _key;
        private string? _label;
 
-       public override Gtk.Dialog create_confirm(Gtk.Window? parent) {
+       public override Gtk.Window create_confirm(Gtk.Window? parent) {
                var dialog = new DeleteDialog(parent, _("Are you sure you want to permanently delete %s?"), 
this._label);
                dialog.check_label = _("I understand that this key will be permanently deleted.");
                dialog.check_require = true;
diff --git a/pkcs11/pkcs11-private-key.vala b/pkcs11/pkcs11-private-key.vala
index c6adcfa9..f8e5869c 100644
--- a/pkcs11/pkcs11-private-key.vala
+++ b/pkcs11/pkcs11-private-key.vala
@@ -26,106 +26,103 @@ namespace Pkcs11 {
 
 public class PrivateKey : Gck.Object, Gck.ObjectCache,
                           Deletable, Exportable, Viewable {
-       public Token? place {
-               owned get { return (Token?)this._token.get(); }
-               set { this._token.set(value); }
-       }
-
-       public Flags object_flags {
-               get { return Flags.PERSONAL; }
-       }
-
-       public Gtk.ActionGroup? actions {
-               get { return null; }
-       }
-
-       public Certificate? partner {
-               owned get { return (Certificate?)this._certificate.get(); }
-               set {
-                       this._certificate.set(value);
-                       notify_property("partner");
-                       notify_property("description");
-               }
-       }
-
-       public string? label {
-               owned get {
-                       if (this._attributes != null) {
-                               string label;
-                               if (this._attributes.find_string(CKA.LABEL, out label))
-                                       return label;
-                       }
-                       Certificate? cert = this.partner;
-                       if (cert != null)
-                               return cert.label;
-                       return _("Unnamed private key");
-               }
-       }
-
-       public string? markup {
-               owned get { return GLib.Markup.escape_text(this.label, -1); }
-       }
-
-       public string? description {
-               get { return _("Private key"); }
-       }
-
-       public GLib.Icon? icon {
-               get {
-                       if (this._icon == null)
-                               this._icon = new GLib.ThemedIcon(Gcr.ICON_KEY);
-                       return this._icon;
-               }
-       }
-
-       public Gck.Attributes attributes {
-               owned get { return this._attributes; }
-               set {
-                       this._attributes = value;
-                       notify_property("attributes");
-               }
-       }
-
-       public bool deletable {
-               get {
-                       Token ?token = this.place;
-                       return token == null ? false : token.is_deletable(this);
-               }
-       }
-
-       public bool exportable {
-               get { return false; }
-       }
-
-       private GLib.WeakRef _token;
-       private Gck.Attributes? _attributes;
-       private GLib.WeakRef _certificate;
-       private GLib.Icon? _icon;
-
-       public void fill(Gck.Attributes attributes) {
-               Gck.Builder builder = new Gck.Builder(Gck.BuilderFlags.NONE);
-               if (this._attributes != null)
-                       builder.add_all(this._attributes);
-               builder.set_all(attributes);
-               this._attributes = builder.steal();
-               notify_property("attributes");
-       }
-
-       public Seahorse.Deleter create_deleter() {
-               return new KeyDeleter(this);
-       }
-
-       public GLib.List<Exporter> create_exporters(ExporterType type) {
-               /* In the future we may exporters here, but for now no exporting */
-               var exporters = new GLib.List<Exporter>();
-               return exporters;
-       }
-
-       public Gtk.Window? create_viewer(Gtk.Window? parent) {
-               var viewer = new Pkcs11.Properties(this, parent);
-               viewer.show();
-               return viewer;
-       }
+    public Token? place {
+        owned get { return (Token?)this._token.get(); }
+        set { this._token.set(value); }
+    }
+
+    public Flags object_flags {
+        get { return Flags.PERSONAL; }
+    }
+
+    public Certificate? partner {
+        owned get { return (Certificate?)this._certificate.get(); }
+        set {
+            this._certificate.set(value);
+            notify_property("partner");
+            notify_property("description");
+        }
+    }
+
+    public string? label {
+        owned get {
+            if (this._attributes != null) {
+                string label;
+                if (this._attributes.find_string(CKA.LABEL, out label))
+                    return label;
+            }
+            Certificate? cert = this.partner;
+            if (cert != null)
+                return cert.label;
+            return _("Unnamed private key");
+        }
+    }
+
+    public string? markup {
+        owned get { return GLib.Markup.escape_text(this.label, -1); }
+    }
+
+    public string? description {
+        get { return _("Private key"); }
+    }
+
+    public GLib.Icon? icon {
+        get {
+            // XXX
+            // if (this._icon == null)
+            //     this._icon = new GLib.ThemedIcon(Gcr.ICON_KEY);
+            return this._icon;
+        }
+    }
+
+    public Gck.Attributes attributes {
+        owned get { return this._attributes; }
+        set {
+            this._attributes = value;
+            notify_property("attributes");
+        }
+    }
+
+    public bool deletable {
+        get {
+            Token ?token = this.place;
+            return token == null ? false : token.is_deletable(this);
+        }
+    }
+
+    public bool exportable {
+        get { return false; }
+    }
+
+    private GLib.WeakRef _token;
+    private Gck.Attributes? _attributes;
+    private GLib.WeakRef _certificate;
+    private GLib.Icon? _icon;
+
+    public void fill(Gck.Attributes attributes) {
+        Gck.Builder builder = new Gck.Builder(Gck.BuilderFlags.NONE);
+        if (this._attributes != null)
+            builder.add_all(this._attributes);
+        builder.set_all(attributes);
+        this._attributes = builder.end();
+        notify_property("attributes");
+    }
+
+    public Seahorse.Deleter create_deleter() {
+        return new KeyDeleter(this);
+    }
+
+    public GLib.List<Exporter> create_exporters(ExporterType type) {
+        /* In the future we may exporters here, but for now no exporting */
+        var exporters = new GLib.List<Exporter>();
+        return exporters;
+    }
+
+    public Gtk.Window? create_viewer(Gtk.Window? parent) {
+        var viewer = new Pkcs11.Properties(this, parent);
+        viewer.show();
+        return viewer;
+    }
 }
 
 }
diff --git a/pkcs11/pkcs11-properties.vala b/pkcs11/pkcs11-properties.vala
index e24c355b..d877ab3e 100644
--- a/pkcs11/pkcs11-properties.vala
+++ b/pkcs11/pkcs11-properties.vala
@@ -40,9 +40,10 @@ public class Seahorse.Pkcs11.Properties : Gtk.Dialog {
     [GtkChild]
     private unowned Gtk.Box content;
 
-    private Gcr.Viewer _viewer;
+    //XXX
+    // private Gcr.Viewer _viewer;
     private GLib.Cancellable _cancellable;
-    private Gck.Object _request_key;
+    private Pkcs11.PrivateKey _request_key;
 
     public Properties(Gck.Object object, Gtk.Window window) {
         GLib.Object(object: object, transient_for: window);
@@ -51,11 +52,11 @@ public class Seahorse.Pkcs11.Properties : Gtk.Dialog {
     construct {
         this._cancellable = new GLib.Cancellable();
 
-        this._viewer = Gcr.Viewer.new_scrolled();
-        this.content.pack_start(this._viewer);
-        this._viewer.set_hexpand(true);
-        this._viewer.set_vexpand(true);
-        this._viewer.show();
+        // this._viewer = Gcr.Viewer.new_scrolled();
+        // this.content.append(this._viewer);
+        // this._viewer.set_hexpand(true);
+        // this._viewer.set_vexpand(true);
+        // this._viewer.show();
 
         /* ... */
 
@@ -81,8 +82,6 @@ public class Seahorse.Pkcs11.Properties : Gtk.Dialog {
             exporters = ((Exportable)this.object).create_exporters(ExporterType.ANY);
 
         this.export_button.set_visible(exporters != null);
-
-        this._viewer.grab_focus();
     }
 
     public override void dispose() {
@@ -105,18 +104,21 @@ public class Seahorse.Pkcs11.Properties : Gtk.Dialog {
 
         object.get("label", &label, "attributes", &attributes);
         if (attributes != null) {
-            var renderer = Gcr.Renderer.create(label, attributes);
-            if (renderer != null) {
-                object.bind_property("label", renderer, "label",
-                                     GLib.BindingFlags.DEFAULT);
-                object.bind_property("attributes", renderer, "attributes",
-                                     GLib.BindingFlags.DEFAULT);
-
-                if (renderer.get_class().find_property("object") != null)
-                    renderer.set("object", object);
-
-                this._viewer.add_renderer(renderer);
-            }
+            // XXX
+            // var renderer = Gcr.Renderer.create(label, attributes);
+            // if (renderer != null) {
+            //     object.bind_property("label", renderer, "label",
+            //                          GLib.BindingFlags.DEFAULT);
+            //     object.bind_property("attributes", renderer, "attributes",
+            //                          GLib.BindingFlags.DEFAULT);
+
+            //     if (renderer.get_class().find_property("object") != null)
+            //         renderer.set("object", object);
+
+// #if 0
+            //     this._viewer.add_renderer(renderer);
+// #endif
+            // }
         }
     }
 
@@ -145,7 +147,15 @@ public class Seahorse.Pkcs11.Properties : Gtk.Dialog {
             deleter = new Deleter((Gck.Object)this.object);
         }
 
-        if (deleter.prompt(this)) {
+        var prompt = deleter.create_confirm(this);
+        //XXX
+        ((Gtk.Dialog) prompt).response.connect((response) => {
+            if (response != Gtk.ResponseType.ACCEPT) {
+                prompt.destroy();
+                return;
+            }
+            prompt.destroy();
+
             deleter.delete.begin(this._cancellable, (obj, res) => {
                 try {
                     if (deleter.delete.end(res))
@@ -154,12 +164,16 @@ public class Seahorse.Pkcs11.Properties : Gtk.Dialog {
                     Util.show_error(this, _("Couldn’t delete"), err.message);
                 }
             });
-        }
+        });
     }
 
     [GtkCallback]
     private void on_request_certificate_button_clicked(Gtk.Button request_button) {
-        Request.prompt(this, this._request_key);
+        var req_dialog = new Pkcs11.Request(this, this._request_key);
+        req_dialog.response.connect((resp) => {
+            req_dialog.destroy();
+        });
+        req_dialog.present();
     }
 
     private void check_certificate_request_capable(GLib.Object object) {
@@ -170,7 +184,7 @@ public class Seahorse.Pkcs11.Properties : Gtk.Dialog {
             try {
                 if (Gcr.CertificateRequest.capable_async.end(res)) {
                     this.request_certificate_button.set_visible(true);
-                    this._request_key = (PrivateKey)object;
+                    this._request_key = (Pkcs11.PrivateKey) object;
                 }
             } catch (GLib.Error err) {
                 GLib.message("couldn't check capabilities of private key: %s", err.message);
diff --git a/pkcs11/pkcs11-request.vala b/pkcs11/pkcs11-request.vala
index 688acd4d..f227aa1e 100644
--- a/pkcs11/pkcs11-request.vala
+++ b/pkcs11/pkcs11-request.vala
@@ -23,81 +23,76 @@
  * Stef Walter <stefw redhat com>
  */
 
-namespace Seahorse {
-namespace Pkcs11 {
-
-public class Request : Gtk.Dialog {
-       public PrivateKey private_key { construct; get; }
-
-       Gtk.Entry _name_entry;
-       uint8[] _encoded;
-
-       construct {
-               var builder = new Gtk.Builder();
-               var path = "/org/gnome/Seahorse/seahorse-pkcs11-request.ui";
-               try {
-                       builder.add_from_resource(path);
-               } catch (GLib.Error err) {
-                       GLib.warning("couldn't load ui file: %s", path);
-                       return;
-               }
-
-               this.set_resizable(false);
-               var content = this.get_content_area();
-               var widget = (Gtk.Widget)builder.get_object("pkcs11-request");
-               content.add(widget);
-               widget.show();
-
-               this._name_entry = (Gtk.Entry)builder.get_object("request-name");
-               this._name_entry.changed.connect(() => { update_response(); });
+public class Seahorse.Pkcs11.Request : Gtk.Dialog {
+
+    public Pkcs11.PrivateKey private_key { construct; get; }
+
+    private Gtk.Entry _name_entry;
+    private uint8[] _encoded;
+
+    construct {
+        var builder = new Gtk.Builder();
+        var path = "/org/gnome/Seahorse/seahorse-pkcs11-request.ui";
+        try {
+            builder.add_from_resource(path);
+        } catch (GLib.Error err) {
+            GLib.warning("couldn't load ui file: %s", path);
+            return;
+        }
+
+        this.set_resizable(false);
+        var content = this.get_content_area();
+        var widget = (Gtk.Widget)builder.get_object("pkcs11-request");
+        content.append(widget);
+
+        this._name_entry = (Gtk.Entry)builder.get_object("request-name");
+        this._name_entry.changed.connect(() => { update_response(); });
 
         // The buttons
         this.add_buttons(_("_Cancel"), Gtk.ResponseType.CANCEL,
                          _("Create"), Gtk.ResponseType.OK);
         this.set_default_response (Gtk.ResponseType.OK);
 
-               this.update_response ();
-
-               if (!(this.private_key is Gck.Object)) {
-                       GLib.critical("private key is not of type %s", typeof(Gck.Object).name());
-               }
-       }
-
-       public static void prompt(Gtk.Window? parent,
-                                 Gck.Object private_key) {
-               var dialog = (Request)GLib.Object.new(typeof(Request), transient_for: parent,
-                                                     private_key: private_key);
-               dialog.run();
-       }
-
-       public override void response(int response_id) {
-               if (response_id == Gtk.ResponseType.OK) {
-                       var interaction = new Interaction(this.transient_for);
-                       var session = this.private_key.get_session();
-                       session.set_interaction(interaction);
-
-                       var req = 
Gcr.CertificateRequest.prepare(Gcr.CertificateRequestFormat.CERTIFICATE_REQUEST_PKCS10,
-                                                                this.private_key);
-                       req.set_cn(this._name_entry.get_text());
-                       req.complete_async.begin(null, (obj, res) => {
-                               try {
-                                       req.complete_async.end(res);
-                                       this.save_certificate_request(req, this.transient_for);
-                               } catch (GLib.Error err) {
-                                       Util.show_error(this.transient_for, _("Couldn’t create certificate 
request"), err.message);
-                               }
-                       });
-
-                       this.hide();
-               }
-       }
-
-       private void update_response() {
-               string name = this._name_entry.get_text();
-               this.set_response_sensitive(Gtk.ResponseType.OK, name != "");
-       }
-
-       private static string BAD_FILENAME_CHARS = "/\\<>|?*";
+        this.update_response ();
+
+        if (!(this.private_key is Gck.Object)) {
+            GLib.critical("private key is not of type %s", typeof(Gck.Object).name());
+        }
+    }
+
+    public Request(Gtk.Window? parent,
+                   Pkcs11.PrivateKey private_key) {
+        GLib.Object(transient_for: parent, private_key: private_key);
+    }
+
+    public override void response(int response_id) {
+        if (response_id == Gtk.ResponseType.OK) {
+            var interaction = new Interaction(this.transient_for);
+            var session = this.private_key.get_session();
+            session.set_interaction(interaction);
+
+            var req = Gcr.CertificateRequest.prepare(Gcr.CertificateRequestFormat.CERTIFICATE_REQUEST_PKCS10,
+                                                     this.private_key);
+            req.set_cn(this._name_entry.get_text());
+            req.complete_async.begin(null, (obj, res) => {
+                try {
+                    req.complete_async.end(res);
+                    this.save_certificate_request(req, this.transient_for);
+                } catch (GLib.Error err) {
+                    Util.show_error(this.transient_for, _("Couldn’t create certificate request"), 
err.message);
+                }
+            });
+
+            this.hide();
+        }
+    }
+
+    private void update_response() {
+        string name = this._name_entry.get_text();
+        this.set_response_sensitive(Gtk.ResponseType.OK, name != "");
+    }
+
+    private static string BAD_FILENAME_CHARS = "/\\<>|?*";
 
     private void save_certificate_request(Gcr.CertificateRequest req,
                                           Gtk.Window? parent) {
@@ -105,53 +100,47 @@ public class Request : Gtk.Dialog {
                                                 parent, Gtk.FileChooserAction.SAVE,
                                                 _("_Save"), _("_Cancel"));
 
-               chooser.set_local_only(false);
-
-               var der_filter = new Gtk.FileFilter();
-               der_filter.set_name(_("Certificate request"));
-               der_filter.add_mime_type("application/pkcs10");
-               der_filter.add_pattern("*.p10");
-               der_filter.add_pattern("*.csr");
-               chooser.add_filter(der_filter);
-               chooser.set_filter(der_filter);
-
-               var pem_filter = new Gtk.FileFilter();
-               pem_filter.set_name(_("PEM encoded request"));
-               pem_filter.add_mime_type("application/pkcs10+pem");
-               pem_filter.add_pattern("*.pem");
-               chooser.add_filter(pem_filter);
-
-               string? label;
-               this.private_key.get("label", out label);
-               if (label == null || label == "")
-                       label = "Certificate Request";
-               var filename = label + ".csr";
-               filename = filename.delimit(BAD_FILENAME_CHARS, '_');
-               chooser.set_current_name(filename);
-
-               chooser.set_do_overwrite_confirmation(true);
-
-               var response = chooser.run();
-               if (response == Gtk.ResponseType.ACCEPT) {
-                       bool textual = chooser.get_filter() == pem_filter;
-                       this._encoded = req.encode(textual);
-
-                       var file = chooser.get_file();
-                       file.replace_contents_async.begin(this._encoded, null, false,
-                                                         GLib.FileCreateFlags.NONE,
-                                                         null, (obj, res) => {
-                               try {
-                                       string new_etag;
-                                       file.replace_contents_async.end(res, out new_etag);
-                               } catch (GLib.Error err) {
-                                       Util.show_error(parent, _("Couldn’t save certificate request"), 
err.message);
-                               }
-                       });
-               }
-
-               chooser.destroy();
-       }
-}
-
-}
+        var der_filter = new Gtk.FileFilter();
+        der_filter.name = _("Certificate request");
+        der_filter.add_mime_type("application/pkcs10");
+        der_filter.add_pattern("*.p10");
+        der_filter.add_pattern("*.csr");
+        chooser.add_filter(der_filter);
+        chooser.set_filter(der_filter);
+
+        var pem_filter = new Gtk.FileFilter();
+        pem_filter.name = _("PEM encoded request");
+        pem_filter.add_mime_type("application/pkcs10+pem");
+        pem_filter.add_pattern("*.pem");
+        chooser.add_filter(pem_filter);
+
+        string? label;
+        this.private_key.get("label", out label);
+        if (label == null || label == "")
+            label = "Certificate Request";
+        var filename = label + ".csr";
+        filename = filename.delimit(BAD_FILENAME_CHARS, '_');
+        chooser.set_current_name(filename);
+
+        chooser.response.connect((response) => {
+            if (response == Gtk.ResponseType.ACCEPT) {
+                bool textual = chooser.get_filter() == pem_filter;
+                this._encoded = req.encode(textual);
+
+                var file = chooser.get_file();
+                file.replace_contents_async.begin(this._encoded, null, false,
+                                                  GLib.FileCreateFlags.NONE,
+                                                  null, (obj, res) => {
+                    try {
+                        string new_etag;
+                        file.replace_contents_async.end(res, out new_etag);
+                    } catch (GLib.Error err) {
+                        Util.show_error(parent, _("Couldn’t save certificate request"), err.message);
+                    }
+                });
+            }
+
+            chooser.destroy();
+        });
+    }
 }
diff --git a/pkcs11/pkcs11-token-filter.vala b/pkcs11/pkcs11-token-filter.vala
new file mode 100644
index 00000000..ce53a1d5
--- /dev/null
+++ b/pkcs11/pkcs11-token-filter.vala
@@ -0,0 +1,63 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2022 Niels De Graef
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+public class Seahorse.Pkcs11.TokenFilter : Gtk.Filter {
+
+    /** If set, only match tokens that are writable  */
+    public bool only_writable {
+        get { return this._only_writable; }
+        set {
+            if (this._only_writable != value) {
+                this._only_writable = value;
+                changed(Gtk.FilterChange.DIFFERENT);
+            }
+        }
+    }
+    private bool _only_writable = false;
+
+    /** If not MAXUINT, only match token that have the mechanism  */
+    public uint mechanism {
+        get { return this._mechanism; }
+        set {
+            if (this._mechanism == value) 
+                return;
+
+            this._mechanism = value;
+            changed(Gtk.FilterChange.DIFFERENT);
+        }
+    }
+    public uint _mechanism = uint.MAX;
+
+    public override bool match (GLib.Object? item) {
+        var token = (Pkcs11.Token) item;
+        if (this.only_writable && (CKF.WRITE_PROTECTED & token.info.flags) != 0)
+            return false;
+
+        if (this.mechanism != uint.MAX && !token.has_mechanism(this.mechanism))
+            return false;
+
+        return true;
+    }
+
+    public override Gtk.FilterMatch get_strictness () {
+        return Gtk.FilterMatch.SOME;
+    }
+}
diff --git a/pkcs11/pkcs11-token.vala b/pkcs11/pkcs11-token.vala
index 28db57fc..a066e077 100644
--- a/pkcs11/pkcs11-token.vala
+++ b/pkcs11/pkcs11-token.vala
@@ -21,84 +21,72 @@
  * Boston, MA 02111-1307, USA.
  */
 
-namespace Seahorse {
-namespace Pkcs11 {
-
-public class Token : GLib.Object, Gcr.Collection, Place, Lockable {
-
-       public bool unlockable {
-               get {
-                       this.ensure_token_info();
-                       if ((this._info.flags & CKF.LOGIN_REQUIRED) == 0)
-                               return false;
-                       if ((this._info.flags & CKF.USER_PIN_INITIALIZED) == 0)
-                               return false;
-                       return !is_session_logged_in(this._session);
-               }
-       }
-
-       public bool lockable {
-               get {
-                       this.ensure_token_info();
-                       if ((this._info.flags & CKF.LOGIN_REQUIRED) == 0)
-                               return false;
-                       if ((this._info.flags & CKF.USER_PIN_INITIALIZED) == 0)
-                               return false;
-                       return is_session_logged_in(this._session);
-               }
-       }
-
-       public Gck.TokenInfo info {
-               get { return this.ensure_token_info(); }
-       }
-
-       public Gck.Session session {
-               get { return this._session; }
-               set {
-                       this._session = session;
-                       notify_property("session");
-                       notify_property("lockable");
-                       notify_property("unlockable");
-               }
-       }
-
-       public Gck.Slot slot {
-               get { return this._slot; }
-               construct { this._slot = value; }
-       }
-
-       public string label {
-               owned get {
-                       var token = this._slot.get_token_info();
-                       if (token == null)
-                               return C_("Label", "Unknown");
-                       return token.label;
-               }
-               set {
-               }
-       }
-
-       public string description {
-               owned get {
-                       var token = this._slot.get_token_info();
-                       if (token == null)
-                               return "";
-                       return token.manufacturer_id;
-               }
-       }
-
-       public string uri {
-               owned get { return this._uri; }
-       }
-
-       public GLib.Icon icon {
-               owned get {
-                       var token = this._slot.get_token_info();
-                       if (token == null)
-                               return new GLib.ThemedIcon("dialog-question");
-                       return Gcr.icon_for_token(token);
-               }
-       }
+public class Seahorse.Pkcs11.Token : GLib.Object, GLib.ListModel, Place, Lockable {
+
+    public bool unlockable {
+        get {
+            this.ensure_token_info();
+            if ((this._info.flags & CKF.LOGIN_REQUIRED) == 0)
+                return false;
+            if ((this._info.flags & CKF.USER_PIN_INITIALIZED) == 0)
+                return false;
+            return !is_session_logged_in(this._session);
+        }
+    }
+
+    public bool lockable {
+        get {
+            this.ensure_token_info();
+            if ((this._info.flags & CKF.LOGIN_REQUIRED) == 0)
+                return false;
+            if ((this._info.flags & CKF.USER_PIN_INITIALIZED) == 0)
+                return false;
+            return is_session_logged_in(this._session);
+        }
+    }
+
+    public Gck.TokenInfo info {
+        get { return this.ensure_token_info(); }
+    }
+
+    public Gck.Session session {
+        get { return this._session; }
+        set {
+            this._session = session;
+            notify_property("session");
+            notify_property("lockable");
+            notify_property("unlockable");
+        }
+    }
+
+    public Gck.Slot slot {
+        get { return this._slot; }
+        construct { this._slot = value; }
+    }
+
+    public string label {
+        owned get {
+            var token = this._slot.get_token_info();
+            if (token == null)
+                return C_("Label", "Unknown");
+            return token.label;
+        }
+        set {
+        }
+    }
+
+    public string description {
+        owned get {
+            var token = this._slot.get_token_info();
+            if (token == null)
+                return "";
+            return token.manufacturer_id;
+        }
+    }
+
+    public string uri {
+        owned get { return this._uri; }
+    }
 
     public Place.Category category {
         get { return Place.Category.CERTIFICATES; }
@@ -116,393 +104,390 @@ public class Token : GLib.Object, Gcr.Collection, Place, Lockable {
         owned get { return null; }
     }
 
-       public Flags object_flags {
-               get { return 0; }
-       }
-
-    public bool show_if_empty {
-        get { return false; }
-    }
-
-       public unowned GLib.Array<ulong> mechanisms {
-               get {
-                       if (this._mechanisms == null)
-                               this._mechanisms = this._slot.get_mechanisms();
-                       return this._mechanisms;
-               }
-       }
-
-       private Gck.Slot _slot;
-       private string _uri;
-       private Gck.TokenInfo? _info;
-       private GLib.Array<ulong> _mechanisms;
-       private Gck.Session? _session;
-       private GLib.HashTable<ulong?, GLib.Object> _object_for_handle;
-       private GLib.HashTable<Gck.Attribute, GLib.GenericArray<GLib.Object>> _objects_for_id;
-       private GLib.HashTable<GLib.Object, unowned Gck.Attribute> _id_for_object;
-       private GLib.HashTable<GLib.Object, GLib.Object> _objects_visible;
-
-       public Token(Gck.Slot slot) {
-               GLib.Object(
-                       slot: slot
-               );
-       }
-
-       construct {
-               this._object_for_handle = new GLib.HashTable<ulong?, GLib.Object>(ulong_hash, ulong_equal);
-               this._objects_for_id = new GLib.HashTable<Gck.Attribute, 
GLib.GenericArray<GLib.Object>>(Gck.Attribute.hash, Gck.Attribute.equal);
-               this._id_for_object = new GLib.HashTable<GLib.Object, unowned 
Gck.Attribute>(GLib.direct_hash, GLib.direct_equal);
-               this._objects_visible = new GLib.HashTable<GLib.Object, GLib.Object>(GLib.direct_hash, 
GLib.direct_equal);
-
-               /* TODO: Does this happen in the background? It really should. */
-               this.load.begin(null);
-
-               var data = new Gck.UriData();
-               this.ensure_token_info();
-               data.token_info = this._info;
-               this._uri = Gck.uri_build(data, Gck.UriFlags.FOR_TOKEN);
-       }
-
-       public override void dispose() {
-               this._slot = null;
-               this._session = null;
-       }
-
-       public async bool lock(GLib.TlsInteraction? interaction,
-                              GLib.Cancellable? cancellable) throws GLib.Error {
-               if (!is_session_logged_in(this._session))
-                       return true;
-
-               yield this._session.logout_async(cancellable);
-               return yield this.load(cancellable);
-       }
-
-       public async bool unlock(GLib.TlsInteraction? interaction,
-                                GLib.Cancellable? cancellable) throws GLib.Error {
-               if (is_session_logged_in (this._session))
-                       return true;
-               if (this._session != null) {
-                       return yield this._session.login_interactive_async(CKU.USER, interaction, 
cancellable);
-               } else {
-                       var options = calculate_session_options();
-                       this._session = yield this._slot.open_session_async(options | 
Gck.SessionOptions.LOGIN_USER,
-                                                                           cancellable);
-                       return true;
-               }
-       }
-
-       public bool contains (GLib.Object object) {
-               return this._objects_visible.lookup(object) != null;
-       }
-
-       public uint get_length() {
-               return this._objects_visible.size();
-       }
-
-       public GLib.List<weak GLib.Object> get_objects() {
-               return this._objects_visible.get_values();
-       }
-
-       public bool is_deletable(Gck.Object object) {
-               this.ensure_token_info();
-
-               if ((this._info.flags & CKF.WRITE_PROTECTED) == CKF.WRITE_PROTECTED)
-                       return false;
-
-               Gck.Attributes? attributes;
-               object.get("attributes", out attributes);
-
-               if (attributes != null) {
-                       bool ret = true;
-                       attributes.find_boolean(CKA.MODIFIABLE, out ret);
-                       return ret;
-               }
-
-               return false;
-       }
-
-       public void remove_object(Gck.Object object) {
-               GLib.List<Gck.Object> objects = null;
-               objects.append(object);
-               remove_objects(objects.copy());
-       }
-
-       public bool has_mechanism(ulong mechanism) {
-               return Gck.mechanisms_check(this.mechanisms, mechanism, Gck.INVALID);
-       }
-
-       private static bool is_session_logged_in(Gck.Session? session) {
-               if (session == null)
-                       return false;
-               var info = session.get_info();
-               return (info != null) &&
-                      (info.state == CKS.RW_USER_FUNCTIONS ||
-                       info.state == CKS.RO_USER_FUNCTIONS ||
-                       info.state == CKS.RW_SO_FUNCTIONS);
-       }
-
-       private unowned Gck.TokenInfo ensure_token_info() {
-               if (this._info == null)
-                       this.update_token_info();
-               return this._info;
-       }
-
-       private void update_token_info() {
-               var info = this._slot.get_token_info();
-               if (info != null) {
-                       this._info = info;
-                       this.notify_property("info");
-                       this.notify_property("lockable");
-                       this.notify_property("unlockable");
-               }
-       }
-
-       private void update_id_map(GLib.Object object,
-                                  Gck.Attribute* id) {
-               bool add = false;
-               bool remove = false;
-
-               var pid = this._id_for_object.lookup(object);
-               if (id == null) {
-                       if (pid != null) {
-                               id = pid;
-                               remove = true;
-                       }
-               } else {
-                       if (pid == null) {
-                               add = true;
-                       } else if (!id->equal(pid)) {
-                               remove = true;
-                               add = true;
-                       }
-               }
-
-               if (add) {
-                       unowned GLib.GenericArray<GLib.Object>? objects;
-                       objects = this._objects_for_id.lookup(id);
-                       if (objects == null) {
-                               var objs = new GLib.GenericArray<GLib.Object>();
-                               this._objects_for_id.insert(id, objs);
-                               objects = objs;
-                       }
-                       objects.add(object);
-                       this._id_for_object.insert(object, id);
-               }
-
-               /* Remove this object from the map */
-               if (remove) {
-                       if (!this._id_for_object.remove(object))
-                               GLib.assert_not_reached();
-                       var objects = this._objects_for_id.lookup(id);
-                       GLib.assert(objects != null);
-                       GLib.assert(objects.length > 0);
-                       if (objects.length == 1) {
-                               if (!this._objects_for_id.remove(id))
-                                       GLib.assert_not_reached();
-                       } else {
-                               if (!objects.remove(object))
-                                       GLib.assert_not_reached();
-                       }
-               }
-       }
-
-       private GLib.Object? lookup_id_map(GLib.Type object_type,
-                                          Gck.Attribute* id) {
-               if (id == null)
-                       return null;
-               var objects = this._objects_for_id.lookup(id);
-               if (objects == null)
-                       return null;
-               for (var i = 0; i < objects.length; i++) {
-                       if (objects[i].get_type().is_a(object_type))
-                               return objects[i];
-               }
-               return null;
-       }
-
-       private void update_visibility(GLib.List<GLib.Object> objects,
-                                      bool visible) {
-               foreach (var object in objects) {
-                       bool have = (this._objects_visible.lookup(object) != null);
-                       if (!have && visible) {
-                               this._objects_visible.insert(object, object);
-                               this.emit_added(object);
-                       } else if (have && !visible) {
-                               if (!this._objects_visible.remove(object))
-                                       GLib.assert_not_reached();
-                               this.emit_removed(object);
-                       }
-               }
-
-       }
-
-       private static bool make_certificate_key_pair(Certificate certificate,
-                                                     PrivateKey private_key) {
-               if (certificate.partner != null || private_key.partner != null)
-                       return false;
-               certificate.partner = private_key;
-               private_key.partner = certificate;
-               return true;
-       }
-
-       private static GLib.Object? break_certificate_key_pair(GLib.Object object) {
-               GLib.Object? pair = null;
-               if (object is Certificate) {
-                       var certificate = (Certificate)object;
-                       pair = certificate.partner;
-                       certificate.partner = null;
-               } else if (object is PrivateKey) {
-                       var private_key = (PrivateKey)object;
-                       pair = private_key.partner;
-                       private_key.partner = null;
-               }
-               return pair;
-       }
-
-       private void receive_objects(GLib.List<GLib.Object> objects) {
-               var show = new GLib.List<GLib.Object>();
-               var hide = new GLib.List<GLib.Object>();
-
-               foreach (var object in objects) {
-                       if (!(object is Gck.Object && object is Gck.ObjectCache))
-                               continue;
-                       var handle = ((Gck.Object)object).handle;
-                       var attrs = ((Gck.ObjectCache)object).attributes;
-
-                       var prev = this._object_for_handle.lookup(handle);
-                       if (prev == null) {
-                               this._object_for_handle.insert(handle, object);
-                               object.set("place", this);
-                       } else if (prev != object) {
-                               object.set("attributes", attrs);
-                               object = prev;
-                       }
-
-                       unowned Gck.Attribute? id = null;
-                       if (attrs != null)
-                               id = attrs.find(CKA.ID);
-                       this.update_id_map(object, id);
-
-                       if (object is Certificate) {
-                               var pair = this.lookup_id_map(typeof(PrivateKey), id);
-                               if (pair != null && make_certificate_key_pair((Certificate)object, 
(PrivateKey)pair))
-                                       hide.prepend(pair);
-                               show.prepend(object);
-                       } else if (object is PrivateKey) {
-                               var pair = this.lookup_id_map(typeof(Certificate), id);
-                               if (pair != null && make_certificate_key_pair((Certificate)pair, 
(PrivateKey)object))
-                                       hide.prepend(object);
-                               else
-                                       show.prepend(object);
-                       } else {
-                               show.prepend(object);
-                       }
-               }
-
-               update_visibility(hide, false);
-               update_visibility(show, true);
-       }
-
-       private void remove_objects(GLib.List<weak GLib.Object> objects) {
-               var depaired = new GLib.List<GLib.Object>();
-               var hide = new GLib.List<GLib.Object>();
-
-               foreach (var object in objects) {
-                       var pair = break_certificate_key_pair(object);
-                       if (pair != null)
-                               depaired.prepend(pair);
-                       update_id_map(object, null);
-                       hide.prepend(object);
-               }
-
-               /* Remove the ownership of these */
-               foreach (var object in objects) {
-                       var handle = ((Gck.Object)object).handle;
-                       object.set("place", null);
-                       this._object_for_handle.remove(handle);
-               }
-
-               update_visibility(hide, false);
-
-               /* Add everything that was paired */
-               receive_objects(depaired);
-       }
-
-       private Gck.SessionOptions calculate_session_options() {
-               this.ensure_token_info();
-               if ((this._info.flags & CKF.WRITE_PROTECTED) == CKF.WRITE_PROTECTED)
-                       return Gck.SessionOptions.READ_ONLY;
-               else
-                       return Gck.SessionOptions.READ_WRITE;
-       }
-
-       public async bool load(GLib.Cancellable? cancellable) throws GLib.Error {
-               var checks = new GLib.HashTable<ulong?, GLib.Object>(ulong_hash, ulong_equal);
-
-               /* Make note of all the objects that were there */
-               this.update_token_info();
-               foreach (var object in this.get_objects()) {
-                       var handle = ((Gck.Object)object).handle;
-                       checks.insert(handle, object);
-               }
-
-               if (this._session == null) {
-                       var options = this.calculate_session_options();
-                       this._session = yield this._slot.open_session_async(options, cancellable);
-               }
-
-               var builder = new Gck.Builder(Gck.BuilderFlags.NONE);
-               builder.add_boolean(CKA.TOKEN, true);
-               builder.add_ulong(CKA.CLASS, CKO.CERTIFICATE);
-
-               const ulong[] CERTIFICATE_ATTRS = {
-                       CKA.VALUE,
-                       CKA.ID,
-                       CKA.LABEL,
-                       CKA.CLASS,
-                       CKA.CERTIFICATE_CATEGORY,
-                       CKA.MODIFIABLE
-               };
-
-               var enumerator = this._session.enumerate_objects(builder.end());
-               enumerator.set_object_type(typeof(Certificate), CERTIFICATE_ATTRS);
-
-               builder = new Gck.Builder(Gck.BuilderFlags.NONE);
-               builder.add_boolean(CKA.TOKEN, true);
-               builder.add_ulong(CKA.CLASS, CKO.PRIVATE_KEY);
-
-               const ulong[] KEY_ATTRS = {
-                       CKA.MODULUS_BITS,
-                       CKA.ID,
-                       CKA.LABEL,
-                       CKA.CLASS,
-                       CKA.KEY_TYPE,
-                       CKA.MODIFIABLE,
-               };
-
-               var chained = this._session.enumerate_objects(builder.end());
-               chained.set_object_type(typeof(PrivateKey), KEY_ATTRS);
-               enumerator.set_chained(chained);
-
-               for (;;) {
-                       var objects = yield enumerator.next_async(16, cancellable);
-
-                       /* Otherwise we're done, remove everything not found */
-                       if (objects == null) {
-                               remove_objects(checks.get_values());
-                               return true;
-                       }
-
-                       this.receive_objects(objects);
-
-                       /* Remove all objects that were found from the check table */
-                       foreach (var object in objects) {
-                               var handle = ((Gck.Object)object).handle;
-                               checks.remove(handle);
-                       }
-               }
-       }
-}
+    public Flags object_flags {
+        get { return 0; }
+    }
 
-}
+    public unowned GLib.Array<ulong> mechanisms {
+        get {
+            if (this._mechanisms == null)
+                this._mechanisms = this._slot.get_mechanisms();
+            return this._mechanisms;
+        }
+    }
+
+    private Gck.Slot _slot;
+    private string _uri;
+    private Gck.TokenInfo? _info;
+    private GLib.Array<ulong> _mechanisms;
+    private Gck.Session? _session;
+    private GLib.HashTable<ulong?, GLib.Object> _object_for_handle;
+    private GLib.HashTable<Gck.Attribute, GLib.GenericArray<GLib.Object>> _objects_for_id;
+    private GLib.HashTable<GLib.Object, unowned Gck.Attribute> _id_for_object;
+
+    private GenericArray<GLib.Object> objects_visible;
+
+    public Token(Gck.Slot slot) {
+        GLib.Object(
+            slot: slot
+        );
+    }
+
+    construct {
+        this._object_for_handle = new GLib.HashTable<ulong?, GLib.Object>(ulong_hash, ulong_equal);
+        this._objects_for_id = new GLib.HashTable<Gck.Attribute, 
GLib.GenericArray<GLib.Object>>(Gck.Attribute.hash, Gck.Attribute.equal);
+        this._id_for_object = new GLib.HashTable<GLib.Object, unowned Gck.Attribute>(GLib.direct_hash, 
GLib.direct_equal);
+        this.objects_visible = new GenericArray<GLib.Object>();
+
+        /* TODO: Does this happen in the background? It really should. */
+        this.load.begin(null);
+
+        var data = new Gck.UriData();
+        this.ensure_token_info();
+        data.token_info = this._info;
+        this._uri = data.build(Gck.UriFlags.FOR_TOKEN);
+    }
+
+    public override void dispose() {
+        this._slot = null;
+        this._session = null;
+    }
+
+    public async bool lock(GLib.TlsInteraction? interaction,
+                           GLib.Cancellable? cancellable) throws GLib.Error {
+        if (!is_session_logged_in(this._session))
+            return true;
+
+        yield this._session.logout_async(cancellable);
+        return yield this.load(cancellable);
+    }
+
+    public async bool unlock(GLib.TlsInteraction? interaction,
+                             GLib.Cancellable? cancellable) throws GLib.Error {
+        if (is_session_logged_in (this._session))
+            return true;
+        if (this._session != null) {
+            return yield this._session.login_interactive_async(CKU.USER, interaction, cancellable);
+        } else {
+            var options = calculate_session_options();
+            this._session = yield this._slot.open_session_async(options | Gck.SessionOptions.LOGIN_USER,
+                                                                null,
+                                                                cancellable);
+            return true;
+        }
+    }
+
+    public GLib.Type get_item_type() {
+        return typeof(GLib.Object);
+    }
+
+    public uint get_n_items() {
+        return this.objects_visible.length;
+    }
+
+    public GLib.Object? get_item(uint i) {
+        if (i >= this.objects_visible.length)
+            return null;
+        return this.objects_visible[i];
+    }
+
+    public bool is_deletable(Gck.Object object) {
+        this.ensure_token_info();
+
+        if ((this._info.flags & CKF.WRITE_PROTECTED) == CKF.WRITE_PROTECTED)
+            return false;
+
+        Gck.Attributes? attributes;
+        object.get("attributes", out attributes);
+
+        if (attributes != null) {
+            bool ret = true;
+            attributes.find_boolean(CKA.MODIFIABLE, out ret);
+            return ret;
+        }
+
+        return false;
+    }
+
+    public void remove_object(Gck.Object object) {
+        GLib.List<Gck.Object> objects = null;
+        objects.append(object);
+        remove_objects(objects.copy());
+    }
+
+    public bool has_mechanism(ulong mechanism) {
+        return Gck.mechanisms_check(this.mechanisms, mechanism, Gck.INVALID);
+    }
+
+    private static bool is_session_logged_in(Gck.Session? session) {
+        if (session == null)
+            return false;
+        var info = session.get_info();
+        return (info != null) &&
+               (info.state == CKS.RW_USER_FUNCTIONS ||
+                info.state == CKS.RO_USER_FUNCTIONS ||
+                info.state == CKS.RW_SO_FUNCTIONS);
+    }
+
+    private unowned Gck.TokenInfo ensure_token_info() {
+        if (this._info == null)
+            this.update_token_info();
+        return this._info;
+    }
+
+    private void update_token_info() {
+        var info = this._slot.get_token_info();
+        if (info != null) {
+            this._info = info;
+            this.notify_property("info");
+            this.notify_property("lockable");
+            this.notify_property("unlockable");
+        }
+    }
+
+    private void update_id_map(GLib.Object object,
+                               Gck.Attribute* id) {
+        bool add = false;
+        bool remove = false;
+
+        var pid = this._id_for_object.lookup(object);
+        if (id == null) {
+            if (pid != null) {
+                id = pid;
+                remove = true;
+            }
+        } else {
+            if (pid == null) {
+                add = true;
+            } else if (!id->equal(pid)) {
+                remove = true;
+                add = true;
+            }
+        }
+
+        if (add) {
+            unowned GLib.GenericArray<GLib.Object>? objects;
+            objects = this._objects_for_id.lookup(id);
+            if (objects == null) {
+                var objs = new GLib.GenericArray<GLib.Object>();
+                this._objects_for_id.insert(id, objs);
+                objects = objs;
+            }
+            objects.add(object);
+            this._id_for_object.insert(object, id);
+        }
+
+        /* Remove this object from the map */
+        if (remove) {
+            if (!this._id_for_object.remove(object))
+                GLib.assert_not_reached();
+            var objects = this._objects_for_id.lookup(id);
+            GLib.assert(objects != null);
+            GLib.assert(objects.length > 0);
+            if (objects.length == 1) {
+                if (!this._objects_for_id.remove(id))
+                    GLib.assert_not_reached();
+            } else {
+                if (!objects.remove(object))
+                    GLib.assert_not_reached();
+            }
+        }
+    }
+
+    private GLib.Object? lookup_id_map(GLib.Type object_type,
+                                       Gck.Attribute* id) {
+        if (id == null)
+            return null;
+        var objects = this._objects_for_id.lookup(id);
+        if (objects == null)
+            return null;
+        for (var i = 0; i < objects.length; i++) {
+            if (objects[i].get_type().is_a(object_type))
+                return objects[i];
+        }
+        return null;
+    }
+
+    private void update_visibility(GLib.List<GLib.Object> objects,
+                                   bool visible) {
+        foreach (var object in objects) {
+            uint position;
+            bool have = this.objects_visible.find(object, out position);
+            if (!have && visible) {
+                this.objects_visible.add(object);
+                items_changed(this.objects_visible.length - 1, 0, 1);
+            } else if (have && !visible) {
+                this.objects_visible.remove_index(position);
+                items_changed(position, 1, 0);
+            }
+        }
+
+    }
+
+    private static bool make_certificate_key_pair(Certificate certificate,
+                                                  PrivateKey private_key) {
+        if (certificate.partner != null || private_key.partner != null)
+            return false;
+        certificate.partner = private_key;
+        private_key.partner = certificate;
+        return true;
+    }
+
+    private static GLib.Object? break_certificate_key_pair(GLib.Object object) {
+        GLib.Object? pair = null;
+        if (object is Certificate) {
+            var certificate = (Certificate)object;
+            pair = certificate.partner;
+            certificate.partner = null;
+        } else if (object is PrivateKey) {
+            var private_key = (PrivateKey)object;
+            pair = private_key.partner;
+            private_key.partner = null;
+        }
+        return pair;
+    }
+
+    private void receive_objects(GLib.List<GLib.Object> objects) {
+        var show = new GLib.List<GLib.Object>();
+        var hide = new GLib.List<GLib.Object>();
+
+        foreach (var object in objects) {
+            if (!(object is Gck.Object && object is Gck.ObjectCache))
+                continue;
+            var handle = ((Gck.Object)object).handle;
+            var attrs = ((Gck.ObjectCache)object).attributes;
+
+            var prev = this._object_for_handle.lookup(handle);
+            if (prev == null) {
+                this._object_for_handle.insert(handle, object);
+                object.set("place", this);
+            } else if (prev != object) {
+                object.set("attributes", attrs);
+                object = prev;
+            }
+
+            unowned Gck.Attribute? id = null;
+            if (attrs != null)
+                id = attrs.find(CKA.ID);
+            this.update_id_map(object, id);
+
+            if (object is Certificate) {
+                var pair = this.lookup_id_map(typeof(PrivateKey), id);
+                if (pair != null && make_certificate_key_pair((Certificate)object, (PrivateKey)pair))
+                    hide.prepend(pair);
+                show.prepend(object);
+            } else if (object is PrivateKey) {
+                var pair = this.lookup_id_map(typeof(Certificate), id);
+                if (pair != null && make_certificate_key_pair((Certificate)pair, (PrivateKey)object))
+                    hide.prepend(object);
+                else
+                    show.prepend(object);
+            } else {
+                show.prepend(object);
+            }
+        }
+
+        update_visibility(hide, false);
+        update_visibility(show, true);
+    }
+
+    private void remove_objects(GLib.List<weak GLib.Object> objects) {
+        var depaired = new GLib.List<GLib.Object>();
+        var hide = new GLib.List<GLib.Object>();
+
+        foreach (var object in objects) {
+            var pair = break_certificate_key_pair(object);
+            if (pair != null)
+                depaired.prepend(pair);
+            update_id_map(object, null);
+            hide.prepend(object);
+        }
+
+        /* Remove the ownership of these */
+        foreach (var object in objects) {
+            var handle = ((Gck.Object)object).handle;
+            object.set("place", null);
+            this._object_for_handle.remove(handle);
+        }
+
+        update_visibility(hide, false);
+
+        /* Add everything that was paired */
+        receive_objects(depaired);
+    }
+
+    private Gck.SessionOptions calculate_session_options() {
+        this.ensure_token_info();
+        if ((this._info.flags & CKF.WRITE_PROTECTED) == CKF.WRITE_PROTECTED)
+            return Gck.SessionOptions.READ_ONLY;
+        else
+            return Gck.SessionOptions.READ_WRITE;
+    }
+
+    public async bool load(GLib.Cancellable? cancellable) throws GLib.Error {
+        var checks = new GLib.HashTable<ulong?, GLib.Object>(ulong_hash, ulong_equal);
+
+        /* Make note of all the objects that were there */
+        this.update_token_info();
+        for (uint i = 0; i < get_n_items(); i++) {
+            var object = (Gck.Object) get_item(i);
+            checks.insert(object.handle, object);
+        }
+
+        if (this._session == null) {
+            var options = this.calculate_session_options();
+            this._session = yield this._slot.open_session_async(options, null, cancellable);
+        }
+
+        var builder = new Gck.Builder(Gck.BuilderFlags.NONE);
+        builder.add_boolean(CKA.TOKEN, true);
+        builder.add_ulong(CKA.CLASS, CKO.CERTIFICATE);
+
+        const ulong[] CERTIFICATE_ATTRS = {
+            CKA.VALUE,
+            CKA.ID,
+            CKA.LABEL,
+            CKA.CLASS,
+            CKA.CERTIFICATE_CATEGORY,
+            CKA.MODIFIABLE
+        };
+
+        var enumerator = this._session.enumerate_objects(builder.end());
+        enumerator.set_object_type(typeof(Certificate), CERTIFICATE_ATTRS);
+
+        builder = new Gck.Builder(Gck.BuilderFlags.NONE);
+        builder.add_boolean(CKA.TOKEN, true);
+        builder.add_ulong(CKA.CLASS, CKO.PRIVATE_KEY);
+
+        const ulong[] KEY_ATTRS = {
+            CKA.MODULUS_BITS,
+            CKA.ID,
+            CKA.LABEL,
+            CKA.CLASS,
+            CKA.KEY_TYPE,
+            CKA.MODIFIABLE,
+        };
+
+        var chained = this._session.enumerate_objects(builder.end());
+        chained.set_object_type(typeof(PrivateKey), KEY_ATTRS);
+        enumerator.set_chained(chained);
+
+        for (;;) {
+            var objects = yield enumerator.next_async(16, cancellable);
+
+            /* Otherwise we're done, remove everything not found */
+            if (objects == null) {
+                remove_objects(checks.get_values());
+                return true;
+            }
+
+            this.receive_objects(objects);
+
+            /* Remove all objects that were found from the check table */
+            foreach (var object in objects) {
+                var handle = ((Gck.Object)object).handle;
+                checks.remove(handle);
+            }
+        }
+    }
 }
diff --git a/pkcs11/seahorse-pkcs11-backend.c b/pkcs11/seahorse-pkcs11-backend.c
index b7ea21e1..297cb175 100644
--- a/pkcs11/seahorse-pkcs11-backend.c
+++ b/pkcs11/seahorse-pkcs11-backend.c
@@ -28,19 +28,17 @@
 
 #include "libseahorse/seahorse-util.h"
 
-#include <gcr/gcr-base.h>
-
 #include <gck/gck.h>
 
 #include <glib/gi18n.h>
 
 enum {
-       PROP_0,
-       PROP_NAME,
-       PROP_LABEL,
-       PROP_DESCRIPTION,
-       PROP_ACTIONS,
-       PROP_LOADED,
+    PROP_0,
+    PROP_NAME,
+    PROP_LABEL,
+    PROP_DESCRIPTION,
+    PROP_ACTIONS,
+    PROP_LOADED,
 };
 
 void  seahorse_pkcs11_backend_initialize (void);
@@ -48,22 +46,24 @@ void  seahorse_pkcs11_backend_initialize (void);
 static SeahorsePkcs11Backend *pkcs11_backend = NULL;
 
 struct _SeahorsePkcs11Backend {
-       GObject parent;
-       SeahorseActionGroup *actions;
-       GList *tokens;
-       GList *blacklist;
-       gboolean loaded;
+    GObject parent;
+    SeahorseActionGroup *actions;
+    GList *blacklist;
+    gboolean loaded;
+
+    GPtrArray *tokens;
+    GPtrArray *tokens_visible;
 };
 
 struct _SeahorsePkcs11BackendClass {
-       GObjectClass parent_class;
+    GObjectClass parent_class;
 };
 
 static const char *token_blacklist[] = {
-       "pkcs11:manufacturer=Gnome%20Keyring;serial=1:SSH:HOME",
-       "pkcs11:manufacturer=Gnome%20Keyring;serial=1:SECRET:MAIN",
-       "pkcs11:manufacturer=Mozilla%20Foundation;token=NSS%20Generic%20Crypto%20Services",
-       NULL
+    "pkcs11:manufacturer=Gnome%20Keyring;serial=1:SSH:HOME",
+    "pkcs11:manufacturer=Gnome%20Keyring;serial=1:SECRET:MAIN",
+    "pkcs11:manufacturer=Mozilla%20Foundation;token=NSS%20Generic%20Crypto%20Services",
+    NULL
 };
 
 static void
@@ -75,10 +75,10 @@ static const GActionEntry ACTION_ENTRIES[] = {
 
 static void         seahorse_pkcs11_backend_iface            (SeahorseBackendIface *iface);
 
-static void         seahorse_pkcs11_backend_collection_init  (GcrCollectionIface *iface);
+static void         seahorse_pkcs11_backend_list_model_init  (GListModelInterface *iface);
 
 G_DEFINE_TYPE_WITH_CODE (SeahorsePkcs11Backend, seahorse_pkcs11_backend, G_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (GCR_TYPE_COLLECTION, seahorse_pkcs11_backend_collection_init)
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, seahorse_pkcs11_backend_list_model_init)
                          G_IMPLEMENT_INTERFACE (SEAHORSE_TYPE_BACKEND, seahorse_pkcs11_backend_iface);
 );
 
@@ -97,21 +97,23 @@ init_actions (SeahorsePkcs11Backend *self)
 static void
 seahorse_pkcs11_backend_init (SeahorsePkcs11Backend *self)
 {
-       GError *error = NULL;
-       GckUriData *uri;
-       guint i;
-
-       g_return_if_fail (pkcs11_backend == NULL);
-       pkcs11_backend = self;
-
-       for (i = 0; token_blacklist[i] != NULL; i++) {
-               uri = gck_uri_parse (token_blacklist[i], GCK_URI_FOR_TOKEN | GCK_URI_FOR_MODULE, &error);
-               if (uri == NULL) {
-                       g_warning ("couldn't parse pkcs11 blacklist uri: %s", error->message);
-                       g_clear_error (&error);
-               }
-               self->blacklist = g_list_prepend (self->blacklist, uri);
-       }
+
+    g_return_if_fail (pkcs11_backend == NULL);
+    pkcs11_backend = self;
+
+    self->tokens = g_ptr_array_new_with_free_func (g_object_unref);
+    self->tokens_visible = g_ptr_array_new ();
+
+    for (unsigned int i = 0; token_blacklist[i] != NULL; i++) {
+        g_autoptr(GError) error = NULL;
+        GckUriData *uri;
+
+        uri = gck_uri_data_parse (token_blacklist[i], GCK_URI_FOR_TOKEN | GCK_URI_FOR_MODULE, &error);
+        if (uri == NULL) {
+            g_warning ("couldn't parse pkcs11 blacklist uri: %s", error->message);
+        }
+        self->blacklist = g_list_prepend (self->blacklist, uri);
+    }
 
     init_actions (self);
 }
@@ -121,24 +123,61 @@ is_token_usable (SeahorsePkcs11Backend *self,
                  GckSlot *slot,
                  GckTokenInfo *token)
 {
-       GList *l;
-
-       if (!(token->flags & CKF_TOKEN_INITIALIZED)) {
-               /* _gcr_debug ("token is not importable: %s: not initialized", token->label); */
-               return FALSE;
-       }
-       if ((token->flags & CKF_LOGIN_REQUIRED) &&
-           !(token->flags & CKF_USER_PIN_INITIALIZED)) {
-               /* _gcr_debug ("token is not importable: %s: user pin not initialized", token->label); */
-               return FALSE;
-       }
-
-       for (l = self->blacklist; l != NULL; l = g_list_next (l)) {
-               if (gck_slot_match (slot, l->data))
-                       return FALSE;
-       }
-
-       return TRUE;
+    if (!(token->flags & CKF_TOKEN_INITIALIZED)) {
+        /* _gcr_debug ("token is not importable: %s: not initialized", token->label); */
+        return FALSE;
+    }
+    if ((token->flags & CKF_LOGIN_REQUIRED) &&
+        !(token->flags & CKF_USER_PIN_INITIALIZED)) {
+        /* _gcr_debug ("token is not importable: %s: user pin not initialized", token->label); */
+        return FALSE;
+    }
+
+    for (GList *l = self->blacklist; l != NULL; l = g_list_next (l)) {
+        if (gck_slot_match (slot, l->data))
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+/* We only expose non-empty tokens */
+static void
+on_token_items_changed (GListModel *list,
+                        unsigned int position,
+                        unsigned int removed,
+                        unsigned int added,
+                        void *user_data)
+{
+    SeahorsePkcs11Token *token = SEAHORSE_PKCS11_TOKEN (list);
+    SeahorsePkcs11Backend *self = SEAHORSE_PKCS11_BACKEND (user_data);
+    unsigned int n_items;
+    gboolean was_empty, is_empty;
+
+    n_items = g_list_model_get_n_items (list);
+    is_empty = (n_items == 0);
+    was_empty = (n_items + removed - added == 0);
+
+    if (is_empty == was_empty)
+        return;
+
+    /* Token was empty, but no longer is: add to exposed items */
+    if (was_empty) {
+        g_return_if_fail (!g_ptr_array_find (self->tokens_visible, token, NULL));
+        g_ptr_array_add (self->tokens_visible, token);
+        g_list_model_items_changed (G_LIST_MODEL (self), self->tokens_visible->len - 1, 0, 1);
+    }
+
+    /* Token was not empty, but now is: remove from exposed items */
+    if (is_empty) {
+        gboolean found;
+        unsigned int pos;
+
+        found = g_ptr_array_find (self->tokens_visible, token, &pos);
+        g_return_if_fail (found);
+        g_ptr_array_remove_index (self->tokens_visible, pos);
+        g_list_model_items_changed (G_LIST_MODEL (self), pos, 1, 0);
+    }
 }
 
 static void
@@ -146,79 +185,79 @@ on_initialized_registered (GObject *unused,
                            GAsyncResult *result,
                            gpointer user_data)
 {
-       SeahorsePkcs11Backend *self = SEAHORSE_PKCS11_BACKEND (user_data);
-       SeahorsePlace *place;
-       GList *slots, *s;
-       GList *modules, *m;
-       GError *error = NULL;
-       GckTokenInfo *token;
-
-       modules = gck_modules_initialize_registered_finish (result, &error);
-       if (error != NULL) {
-               g_warning ("%s", error->message);
-               g_clear_error (&error);
-       }
-
-       for (m = modules; m != NULL; m = g_list_next (m)) {
-               slots = gck_module_get_slots (m->data, TRUE);
-               for (s = slots; s; s = g_list_next (s)) {
-                       token = gck_slot_get_token_info (s->data);
-                       if (token == NULL)
-                               continue;
-                       if (is_token_usable (self, s->data, token)) {
-                               place = SEAHORSE_PLACE (seahorse_pkcs11_token_new (s->data));
-                               self->tokens = g_list_append (self->tokens, place);
-                               gcr_collection_emit_added (GCR_COLLECTION (self), G_OBJECT (place));
-                       }
-                       gck_token_info_free (token);
-               }
-
-               /* These will have been refed by the source above */
-               gck_list_unref_free (slots);
-       }
-
-       self->loaded = TRUE;
-       g_object_notify (G_OBJECT (self), "loaded");
-
-       gck_list_unref_free (modules);
-       g_object_unref (self);
+    g_autoptr(SeahorsePkcs11Backend) self = SEAHORSE_PKCS11_BACKEND (user_data);
+    g_autolist(GckModule) modules = NULL;
+    g_autoptr(GError) error = NULL;
+
+    modules = gck_modules_initialize_registered_finish (result, &error);
+    if (error != NULL) {
+        g_warning ("%s", error->message);
+    }
+
+    for (GList *m = modules; m != NULL; m = g_list_next (m)) {
+        g_autolist(GckSlot) slots = NULL;
+
+        slots = gck_module_get_slots (m->data, TRUE);
+        for (GList *s = slots; s; s = g_list_next (s)) {
+            g_autoptr(GckTokenInfo) info = NULL;
+            SeahorsePkcs11Token *token;
+
+            info = gck_slot_get_token_info (s->data);
+            if (info == NULL || !is_token_usable (self, s->data, info))
+                continue;
+
+            token = seahorse_pkcs11_token_new (s->data);
+            g_ptr_array_add (self->tokens, token);
+
+            if (g_list_model_get_n_items (G_LIST_MODEL (token)) > 0) {
+                g_ptr_array_add (self->tokens_visible, token);
+                g_list_model_items_changed (G_LIST_MODEL (self),
+                                            self->tokens_visible->len - 1,
+                                            0, 1);
+            }
+            g_signal_connect (token, "items-changed", G_CALLBACK (on_token_items_changed), self);
+        }
+    }
+
+    self->loaded = TRUE;
+    g_object_notify (G_OBJECT (self), "loaded");
 }
 
 static void
 seahorse_pkcs11_backend_constructed (GObject *obj)
 {
-       SeahorsePkcs11Backend *self = SEAHORSE_PKCS11_BACKEND (obj);
+    SeahorsePkcs11Backend *self = SEAHORSE_PKCS11_BACKEND (obj);
 
-       G_OBJECT_CLASS (seahorse_pkcs11_backend_parent_class)->constructed (obj);
+    G_OBJECT_CLASS (seahorse_pkcs11_backend_parent_class)->constructed (obj);
 
-       gck_modules_initialize_registered_async (NULL, on_initialized_registered,
-                                                g_object_ref (self));
+    gck_modules_initialize_registered_async (NULL, on_initialized_registered,
+                                             g_object_ref (self));
 }
 
 static const gchar *
 seahorse_pkcs11_backend_get_name (SeahorseBackend *backend)
 {
-       return SEAHORSE_PKCS11_NAME;
+    return SEAHORSE_PKCS11_NAME;
 }
 
 static const gchar *
 seahorse_pkcs11_backend_get_label (SeahorseBackend *backend)
 {
-       return _("Certificates");
+    return _("Certificates");
 }
 
 static const gchar *
 seahorse_pkcs11_backend_get_description (SeahorseBackend *backend)
 {
-       return _("X.509 certificates and related keys");
+    return _("X.509 certificates and related keys");
 }
 
 static gboolean
 seahorse_pkcs11_backend_get_loaded (SeahorseBackend *backend)
 {
-       g_return_val_if_fail (SEAHORSE_IS_PKCS11_BACKEND (backend), FALSE);
+    g_return_val_if_fail (SEAHORSE_IS_PKCS11_BACKEND (backend), FALSE);
 
-       return SEAHORSE_PKCS11_BACKEND (backend)->loaded;
+    return SEAHORSE_PKCS11_BACKEND (backend)->loaded;
 }
 
 static SeahorseActionGroup *
@@ -233,197 +272,147 @@ seahorse_pkcs11_backend_get_property (GObject *obj,
                                       GValue *value,
                                       GParamSpec *pspec)
 {
-       SeahorseBackend *backend = SEAHORSE_BACKEND (obj);
-
-       switch (prop_id) {
-       case PROP_NAME:
-               g_value_set_string (value, seahorse_pkcs11_backend_get_name (backend));
-               break;
-       case PROP_LABEL:
-               g_value_set_string (value, seahorse_pkcs11_backend_get_label (backend));
-               break;
-       case PROP_DESCRIPTION:
-               g_value_set_string (value, seahorse_pkcs11_backend_get_description (backend));
-               break;
-       case PROP_ACTIONS:
-               g_value_take_object (value, seahorse_pkcs11_backend_get_actions (backend));
-               break;
-       case PROP_LOADED:
-               g_value_set_boolean (value, seahorse_pkcs11_backend_get_loaded (backend));
-               break;
-       default:
-               G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
-               break;
-       }
-}
-
-static void
-seahorse_pkcs11_backend_dispose (GObject *obj)
-{
-       SeahorsePkcs11Backend *self = SEAHORSE_PKCS11_BACKEND (obj);
-
-       g_list_free_full (self->tokens, g_object_unref);
-       self->tokens = NULL;
-
-       G_OBJECT_CLASS (seahorse_pkcs11_backend_parent_class)->dispose (obj);
+    SeahorseBackend *backend = SEAHORSE_BACKEND (obj);
+
+    switch (prop_id) {
+    case PROP_NAME:
+        g_value_set_string (value, seahorse_pkcs11_backend_get_name (backend));
+        break;
+    case PROP_LABEL:
+        g_value_set_string (value, seahorse_pkcs11_backend_get_label (backend));
+        break;
+    case PROP_DESCRIPTION:
+        g_value_set_string (value, seahorse_pkcs11_backend_get_description (backend));
+        break;
+    case PROP_ACTIONS:
+        g_value_take_object (value, seahorse_pkcs11_backend_get_actions (backend));
+        break;
+    case PROP_LOADED:
+        g_value_set_boolean (value, seahorse_pkcs11_backend_get_loaded (backend));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
+        break;
+    }
 }
 
 static void
 seahorse_pkcs11_backend_finalize (GObject *obj)
 {
-       SeahorsePkcs11Backend *self = SEAHORSE_PKCS11_BACKEND (obj);
+    SeahorsePkcs11Backend *self = SEAHORSE_PKCS11_BACKEND (obj);
 
-       g_list_free_full (self->blacklist, (GDestroyNotify)gck_uri_data_free);
+    g_list_free_full (self->blacklist, (GDestroyNotify)gck_uri_data_free);
     g_clear_object (&self->actions);
-       g_assert (self->tokens == NULL);
-       g_return_if_fail (pkcs11_backend == self);
-       pkcs11_backend = NULL;
+    g_clear_pointer (&self->tokens_visible, g_ptr_array_unref);
+    g_clear_pointer (&self->tokens, g_ptr_array_unref);
+    g_return_if_fail (pkcs11_backend == self);
+    pkcs11_backend = NULL;
 
-       G_OBJECT_CLASS (seahorse_pkcs11_backend_parent_class)->finalize (obj);
+    G_OBJECT_CLASS (seahorse_pkcs11_backend_parent_class)->finalize (obj);
 }
 
 static void
 seahorse_pkcs11_backend_class_init (SeahorsePkcs11BackendClass *klass)
 {
-       GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-
-       gobject_class->constructed = seahorse_pkcs11_backend_constructed;
-       gobject_class->dispose = seahorse_pkcs11_backend_dispose;
-       gobject_class->finalize = seahorse_pkcs11_backend_finalize;
-       gobject_class->get_property = seahorse_pkcs11_backend_get_property;
-
-       g_object_class_override_property (gobject_class, PROP_NAME, "name");
-       g_object_class_override_property (gobject_class, PROP_LABEL, "label");
-       g_object_class_override_property (gobject_class, PROP_DESCRIPTION, "description");
-       g_object_class_override_property (gobject_class, PROP_ACTIONS, "actions");
-       g_object_class_override_property (gobject_class, PROP_LOADED, "loaded");
+    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+    gobject_class->constructed = seahorse_pkcs11_backend_constructed;
+    gobject_class->finalize = seahorse_pkcs11_backend_finalize;
+    gobject_class->get_property = seahorse_pkcs11_backend_get_property;
+
+    g_object_class_override_property (gobject_class, PROP_NAME, "name");
+    g_object_class_override_property (gobject_class, PROP_LABEL, "label");
+    g_object_class_override_property (gobject_class, PROP_DESCRIPTION, "description");
+    g_object_class_override_property (gobject_class, PROP_ACTIONS, "actions");
+    g_object_class_override_property (gobject_class, PROP_LOADED, "loaded");
 }
 
-static guint
-seahorse_pkcs11_backend_get_length (GcrCollection *collection)
+static GType
+seahorse_pkcs11_backend_get_item_type (GListModel *model)
 {
-       SeahorsePkcs11Backend *self = SEAHORSE_PKCS11_BACKEND (collection);
-       return g_list_length (self->tokens);
+    return SEAHORSE_PKCS11_TYPE_TOKEN;
 }
 
-static GList *
-seahorse_pkcs11_backend_get_objects (GcrCollection *collection)
+static unsigned int
+seahorse_pkcs11_backend_get_n_items (GListModel *model)
 {
-       SeahorsePkcs11Backend *self = SEAHORSE_PKCS11_BACKEND (collection);
-       return g_list_copy (self->tokens);
+    SeahorsePkcs11Backend *self = SEAHORSE_PKCS11_BACKEND (model);
+    return self->tokens_visible->len;
 }
 
-static gboolean
-seahorse_pkcs11_backend_contains (GcrCollection *collection,
-                                  GObject *object)
+static void *
+seahorse_pkcs11_backend_get_item (GListModel   *model,
+                                  unsigned int  position)
 {
-       SeahorsePkcs11Backend *self = SEAHORSE_PKCS11_BACKEND (collection);
-       return g_list_find (self->tokens, object) != NULL;
+    SeahorsePkcs11Backend *self = SEAHORSE_PKCS11_BACKEND (model);
+
+    if (position >= self->tokens_visible->len)
+        return NULL;
+    return g_object_ref (g_ptr_array_index (self->tokens_visible, position));
 }
 
 static void
-seahorse_pkcs11_backend_collection_init (GcrCollectionIface *iface)
+seahorse_pkcs11_backend_list_model_init (GListModelInterface *iface)
 {
-       iface->contains = seahorse_pkcs11_backend_contains;
-       iface->get_length = seahorse_pkcs11_backend_get_length;
-       iface->get_objects = seahorse_pkcs11_backend_get_objects;
+    iface->get_item_type = seahorse_pkcs11_backend_get_item_type;
+    iface->get_n_items = seahorse_pkcs11_backend_get_n_items;
+    iface->get_item = seahorse_pkcs11_backend_get_item;
 }
 
 static SeahorsePlace *
 seahorse_pkcs11_backend_lookup_place (SeahorseBackend *backend,
                                       const gchar *uri)
 {
-       SeahorsePkcs11Backend *self = SEAHORSE_PKCS11_BACKEND (backend);
-       GckUriData *uri_data;
-       GList *l;
+    SeahorsePkcs11Backend *self = SEAHORSE_PKCS11_BACKEND (backend);
+    g_autoptr(GckUriData) uri_data = NULL;
+
+    if (!g_str_has_prefix (uri, "pkcs11:"))
+        return NULL;
 
-       if (!g_str_has_prefix (uri, "pkcs11:"))
-               return NULL;
+    uri_data = gck_uri_data_parse (uri, GCK_URI_FOR_TOKEN | GCK_URI_FOR_MODULE, NULL);
+    if (uri_data == NULL)
+        return NULL;
 
-       uri_data = gck_uri_parse (uri, GCK_URI_FOR_TOKEN | GCK_URI_FOR_MODULE, NULL);
-       if (uri_data == NULL)
-               return NULL;
+    for (unsigned int i = 0; i < self->tokens->len; i++) {
+        SeahorsePkcs11Token *token = g_ptr_array_index (self->tokens, i);
 
-       for (l = self->tokens; l != NULL; l = g_list_next (l)) {
-               if (gck_slot_match (seahorse_pkcs11_token_get_slot (l->data), uri_data))
-                       break;
-       }
+        if (gck_slot_match (seahorse_pkcs11_token_get_slot (token), uri_data))
+            return SEAHORSE_PLACE (token);
+    }
 
-       gck_uri_data_free (uri_data);
-       return l != NULL ? l->data : NULL;
+    return NULL;
 }
 
 static void
 seahorse_pkcs11_backend_iface (SeahorseBackendIface *iface)
 {
-       iface->lookup_place = seahorse_pkcs11_backend_lookup_place;
-       iface->get_actions = seahorse_pkcs11_backend_get_actions;
-       iface->get_description = seahorse_pkcs11_backend_get_description;
-       iface->get_label = seahorse_pkcs11_backend_get_label;
-       iface->get_name = seahorse_pkcs11_backend_get_name;
-       iface->get_loaded = seahorse_pkcs11_backend_get_loaded;
+    iface->lookup_place = seahorse_pkcs11_backend_lookup_place;
+    iface->get_actions = seahorse_pkcs11_backend_get_actions;
+    iface->get_description = seahorse_pkcs11_backend_get_description;
+    iface->get_label = seahorse_pkcs11_backend_get_label;
+    iface->get_name = seahorse_pkcs11_backend_get_name;
+    iface->get_loaded = seahorse_pkcs11_backend_get_loaded;
 }
 
 void
 seahorse_pkcs11_backend_initialize (void)
 {
-       SeahorsePkcs11Backend *self;
+    SeahorsePkcs11Backend *self;
 
-       g_return_if_fail (pkcs11_backend == NULL);
-       self = g_object_new (SEAHORSE_TYPE_PKCS11_BACKEND, NULL);
+    g_return_if_fail (pkcs11_backend == NULL);
+    self = g_object_new (SEAHORSE_TYPE_PKCS11_BACKEND, NULL);
 
-       seahorse_backend_register (SEAHORSE_BACKEND (self));
-       g_object_unref (self);
+    seahorse_backend_register (SEAHORSE_BACKEND (self));
+    g_object_unref (self);
 
-       g_return_if_fail (pkcs11_backend != NULL);
+    g_return_if_fail (pkcs11_backend != NULL);
 }
 
 SeahorsePkcs11Backend *
 seahorse_pkcs11_backend_get (void)
 {
-       g_return_val_if_fail (pkcs11_backend, NULL);
-       return pkcs11_backend;
-}
-
-static gboolean
-on_filter_writable (GObject *object,
-                    gpointer user_data)
-{
-       SeahorsePkcs11Token *token = SEAHORSE_PKCS11_TOKEN (object);
-       guint mechanism = GPOINTER_TO_UINT (user_data);
-       GckTokenInfo *info;
-
-       info = seahorse_pkcs11_token_get_info (token);
-       g_return_val_if_fail (info != NULL, FALSE);
-
-       if (info->flags & CKF_WRITE_PROTECTED)
-               return FALSE;
-
-       if (mechanism != G_MAXUINT) {
-               if (!seahorse_pkcs11_token_has_mechanism (token, (gulong)mechanism))
-                       return FALSE;
-       }
-
-       return TRUE;
-}
-
-GcrCollection *
-seahorse_pkcs11_backend_get_writable_tokens (SeahorsePkcs11Backend *self,
-                                             gulong with_mechanism)
-{
-       gpointer mechanism;
-
-       self = self ? self : seahorse_pkcs11_backend_get ();
-       g_return_val_if_fail (SEAHORSE_IS_PKCS11_BACKEND (self), NULL);
-
-       if (with_mechanism == GCK_INVALID)
-               mechanism = GUINT_TO_POINTER (G_MAXUINT);
-       else
-               mechanism = GUINT_TO_POINTER (with_mechanism);
-
-       return gcr_filter_collection_new_with_callback (GCR_COLLECTION (self),
-                                                       on_filter_writable,
-                                                       mechanism, NULL);
+    g_return_val_if_fail (pkcs11_backend, NULL);
+    return pkcs11_backend;
 }
 
 static void
@@ -437,7 +426,7 @@ on_generate_activate (GSimpleAction *action,
 
     catalog = seahorse_action_group_get_catalog (actions);
     dialog = seahorse_pkcs11_generate_new (GTK_WINDOW (catalog));
-    gtk_dialog_run (GTK_DIALOG (dialog));
-    gtk_widget_destroy (GTK_WIDGET (dialog));
+    g_signal_connect (dialog, "response", G_CALLBACK(gtk_window_destroy), NULL);
+    gtk_window_present (GTK_WINDOW (dialog));
     g_clear_object (&catalog);
 }
diff --git a/pkcs11/seahorse-pkcs11-backend.h b/pkcs11/seahorse-pkcs11-backend.h
index ddcfd2fc..ba9310a7 100644
--- a/pkcs11/seahorse-pkcs11-backend.h
+++ b/pkcs11/seahorse-pkcs11-backend.h
@@ -27,20 +27,8 @@
 #define SEAHORSE_PKCS11                         (g_quark_from_static_string (SEAHORSE_PKCS11_STR))
 
 #define SEAHORSE_TYPE_PKCS11_BACKEND            (seahorse_pkcs11_backend_get_type ())
-#define SEAHORSE_PKCS11_BACKEND(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
SEAHORSE_TYPE_PKCS11_BACKEND, SeahorsePkcs11Backend))
-#define SEAHORSE_PKCS11_BACKEND_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), 
SEAHORSE_TYPE_PKCS11_BACKEND, SeahorsePkcs11BackendClass))
-#define SEAHORSE_IS_PKCS11_BACKEND(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
SEAHORSE_TYPE_PKCS11_BACKEND))
-#define SEAHORSE_IS_PKCS11_BACKEND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
SEAHORSE_TYPE_PKCS11_BACKEND))
-#define SEAHORSE_PKCS11_BACKEND_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), 
SEAHORSE_TYPE_PKCS11_BACKEND, SeahorsePkcs11BackendClass))
-
-typedef struct _SeahorsePkcs11Backend SeahorsePkcs11Backend;
-typedef struct _SeahorsePkcs11BackendClass SeahorsePkcs11BackendClass;
-
-GType                    seahorse_pkcs11_backend_get_type      (void) G_GNUC_CONST;
+G_DECLARE_FINAL_TYPE (SeahorsePkcs11Backend, seahorse_pkcs11_backend, SEAHORSE, PKCS11_BACKEND, GObject)
 
 SeahorsePkcs11Backend *  seahorse_pkcs11_backend_get           (void);
 
-GcrCollection *          seahorse_pkcs11_backend_get_writable_tokens (SeahorsePkcs11Backend *self,
-                                                                      gulong with_mechanism);
-
 #endif /* SEAHORSE_PKCS11_BACKEND_H_ */
diff --git a/pkcs11/seahorse-pkcs11-generate.ui b/pkcs11/seahorse-pkcs11-generate.ui
index f240c722..3a203434 100644
--- a/pkcs11/seahorse-pkcs11-generate.ui
+++ b/pkcs11/seahorse-pkcs11-generate.ui
@@ -1,176 +1,66 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
   <template class="SeahorsePkcs11Generate" parent="GtkDialog">
     <property name="resizable">False</property>
     <property name="title" translatable="yes">New private key</property>
-    <child internal-child="vbox">
-      <object class="GtkBox">
-        <property name="visible">True</property>
+
+    <child internal-child="content_area">
+      <object class="GtkBox" id="vbox1">
         <property name="orientation">vertical</property>
+        <property name="spacing">12</property>
+        <property name="margin-top">12</property>
+        <property name="margin-bottom">12</property>
+        <property name="margin-start">12</property>
+        <property name="margin-end">12</property>
+        <child>
+          <object class="GtkLabel" id="label45">
+            <property name="xalign">0</property>
+            <property name="yalign">0</property>
+            <property name="label" translatable="yes">Create a new private key</property>
+          </object>
+        </child>
         <child>
-          <object class="GtkBox" id="pkcs11-generate">
-            <property name="visible">True</property>
-            <property name="orientation">horizontal</property>
-            <property name="can_focus">False</property>
-            <property name="border_width">7</property>
-            <property name="spacing">12</property>
+          <object class="AdwPreferencesGroup">
             <child>
-              <object class="GtkImage" id="key-image">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="yalign">0</property>
-                <property name="pixel_size">48</property>
-                <property name="icon_name">gcr-key-pair</property>
+              <object class="AdwEntryRow" id="label_row">
+                <property name="title" translatable="yes">Label</property>
               </object>
             </child>
             <child>
-              <object class="GtkBox" id="vbox1">
-                <property name="visible">True</property>
-                <property name="orientation">vertical</property>
-                <property name="can_focus">False</property>
-                <property name="spacing">12</property>
-                <child>
-                  <object class="GtkLabel" id="label45">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="xalign">0</property>
-                    <property name="yalign">0</property>
-                    <property name="label" translatable="yes">Create a new private key</property>
+              <object class="AdwActionRow">
+                <property name="title" translatable="yes">Stored at</property>
+                <child type="suffix">
+                  <object class="GtkComboBox" id="token_box">
+                    <property name="valign">center</property>
                   </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">False</property>
-                    <property name="position">0</property>
-                  </packing>
                 </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="AdwPreferencesGroup">
+            <property name="title" translatable="yes">Advanced key options</property>
+            <child type="suffix">
+              <object class="AdwActionRow">
+                <property name="title" translatable="yes">Key _Type</property>
+                <property name="use_underline">True</property>
                 <child>
-                  <object class="GtkGrid" id="grid2">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="row_spacing">6</property>
-                    <property name="column_spacing">12</property>
-                    <child>
-                      <object class="GtkLabel" id="label1">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="halign">end</property>
-                        <property name="label" translatable="yes">Label:</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">0</property>
-                        <property name="width">1</property>
-                        <property name="height">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label2">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">Stored at:</property>
-                        <property name="halign">end</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">0</property>
-                        <property name="top_attach">1</property>
-                        <property name="width">1</property>
-                        <property name="height">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEntry" id="label_entry">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="invisible_char">●</property>
-                        <property name="invisible_char_set">True</property>
-                        <property name="activates_default">True</property>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">0</property>
-                        <property name="width">1</property>
-                        <property name="height">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkComboBox" id="token_box">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                      </object>
-                      <packing>
-                        <property name="top_attach">1</property>
-                        <property name="left_attach">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label48">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="label" translatable="yes">_Advanced key options</property>
-                        <property name="use_underline">True</property>
-                        <property name="halign">start</property>
-                        <attributes>
-                         <attribute name="weight" value="bold"/>
-                        </attributes>
-                      </object>
-                      <packing>
-                        <property name="top_attach">2</property>
-                        <property name="left_attach">0</property>
-                        <property name="width">2</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label49">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="halign">end</property>
-                        <property name="label" translatable="yes">Key _Type:</property>
-                        <property name="use_underline">True</property>
-                      </object>
-                      <packing>
-                        <property name="top_attach">3</property>
-                        <property name="left_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkComboBox" id="mechanism_box">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                      </object>
-                      <packing>
-                        <property name="top_attach">3</property>
-                        <property name="left_attach">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label50">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="halign">end</property>
-                        <property name="label" translatable="yes">Key _Strength (bits):</property>
-                        <property name="use_underline">True</property>
-                      </object>
-                      <packing>
-                        <property name="top_attach">4</property>
-                        <property name="left_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkSpinButton" id="key_bits">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="invisible_char">●</property>
-                        <property name="invisible_char_set">True</property>
-                        <property name="climb_rate">64</property>
-                        <property name="numeric">True</property>
-                        <property name="activates_default">True</property>
-                      </object>
-                      <packing>
-                        <property name="top_attach">4</property>
-                        <property name="left_attach">1</property>
-                      </packing>
-                    </child>
+                  <object class="GtkComboBox" id="mechanism_box">
+                    <property name="valign">center</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="AdwActionRow">
+                <property name="title" translatable="yes">Key _Strength (bits)</property>
+                <property name="use_underline">True</property>
+                <child type="suffix">
+                  <object class="GtkSpinButton" id="key_bits">
+                    <property name="valign">center</property>
+                    <property name="climb-rate">64</property>
+                    <property name="numeric">True</property>
                   </object>
                 </child>
               </object>
@@ -182,14 +72,11 @@
 
     <child type="action">
       <object class="GtkButton" id="button_cancel">
-        <property name="visible">True</property>
         <property name="label" translatable="yes">Cancel</property>
       </object>
     </child>
     <child type="action">
       <object class="GtkButton" id="button_ok">
-        <property name="visible">True</property>
-        <property name="can-default">True</property>
         <property name="label" translatable="yes">Create</property>
       </object>
     </child>
diff --git a/pkcs11/seahorse-pkcs11-properties.ui b/pkcs11/seahorse-pkcs11-properties.ui
index 0c7b7838..711d1b3e 100644
--- a/pkcs11/seahorse-pkcs11-properties.ui
+++ b/pkcs11/seahorse-pkcs11-properties.ui
@@ -1,24 +1,19 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
   <template class="SeahorsePkcs11Properties" parent="GtkDialog">
     <property name="width_request">400</property>
     <property name="height_request">400</property>
-    <property name="can_focus">False</property>
-    <child internal-child="vbox">
+
+    <child internal-child="content_area">
       <object class="GtkBox" id="content">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
-        <property name="can_focus">False</property>
         <property name="spacing">12</property>
         <child>
           <object class="GtkBox">
-            <property name="visible">True</property>
             <property name="orientation">horizontal</property>
             <property name="spacing">6</property>
             <child>
               <object class="GtkButton" id="delete_button">
-                <property name="visible">True</property>
                 <property name="label" translatable="yes">Delete</property>
                 <property name="tooltip_text" translatable="yes">Delete this certificate or key</property>
                 <signal name="clicked" handler="on_delete_button_clicked"/>
@@ -29,7 +24,6 @@
             </child>
             <child>
               <object class="GtkButton" id="export_button">
-                <property name="visible">True</property>
                 <property name="label" translatable="yes">Export</property>
                 <property name="tooltip_text" translatable="yes">Export the certificate</property>
                 <signal name="clicked" handler="on_export_button_clicked"/>
@@ -42,14 +36,8 @@
                 <property name="tooltip_text" translatable="yes">Create a certificate request file for this 
key</property>
                 <signal name="clicked" handler="on_request_certificate_button_clicked"/>
               </object>
-              <packing>
-                <property name="pack_type">end</property>
-              </packing>
             </child>
           </object>
-          <packing>
-            <property name="pack_type">end</property>
-          </packing>
         </child>
       </object>
     </child>
diff --git a/src/application.vala b/src/application.vala
index 469844ad..088cd517 100644
--- a/src/application.vala
+++ b/src/application.vala
@@ -21,7 +21,7 @@
  * <http://www.gnu.org/licenses/>.
  */
 
-public class Seahorse.Application : Gtk.Application {
+public class Seahorse.Application : Adw.Application {
     private SearchProvider? search_provider;
     private uint search_provider_dbus_id = 0;
 
@@ -96,14 +96,6 @@ public class Seahorse.Application : Gtk.Application {
     public override void startup() {
         base.startup();
 
-        Hdy.init();
-
-        // Opt-in to Color Scheme user preference
-        Hdy.StyleManager.get_default ().color_scheme = Hdy.ColorScheme.PREFER_LIGHT;
-
-        // Insert Icons into Stock
-        icons_init();
-
         // Initialize the backends
         Gkr.Backend.initialize();
         Ssh.Backend.initialize();
@@ -177,19 +169,13 @@ public class Seahorse.Application : Gtk.Application {
         about.set_logo_icon_name(Config.APPLICATION_ID);
         about.set_website("https://wiki.gnome.org/Apps/Seahorse";);
         about.set_website_label(_("Seahorse Project Homepage"));
-
-        about.response.connect((response) => {
-            about.hide();
-        });
-
         about.set_transient_for(this.key_mgr);
-        about.run();
-        about.destroy();
+        about.show();
     }
 
     private void on_app_help(SimpleAction action, Variant? param) {
         try {
-          Gtk.show_uri_on_window(this.key_mgr, "help:seahorse", Gtk.get_current_event_time ());
+          Gtk.show_uri(this.key_mgr, "help:seahorse", Gdk.CURRENT_TIME);
         } catch (GLib.Error err) {
           warning("Error showing help: %s", err.message);
         }
diff --git a/src/import-dialog.vala b/src/import-dialog.vala
index d3536467..c430244d 100644
--- a/src/import-dialog.vala
+++ b/src/import-dialog.vala
@@ -20,61 +20,87 @@
  */
 
 public class Seahorse.ImportDialog : Gtk.Dialog {
+    //XXX
 
-    private Gcr.ViewerWidget viewer;
-    private Gcr.ImportButton import;
+    private Gcr.Parser parser = new Gcr.Parser();
+    // private Gcr.ViewerWidget viewer;
+    // private Gcr.ImportButton import;
 
-    public ImportDialog(Gtk.Window? parent) {
-        GLib.Object(
-            transient_for: parent,
-            title: _("Data to be imported"),
-            use_header_bar: 1
-        );
+    construct {
 
-        Gtk.Widget button = new Gtk.Button.with_mnemonic(_("_Cancel"));
-        button.show();
+        var button = new Gtk.Button.with_mnemonic(_("_Cancel"));
         add_action_widget(button, Gtk.ResponseType.CANCEL);
 
-        this.import = new Gcr.ImportButton(_("_Import"));
-        this.import.halign = Gtk.Align.END;
-        this.import.visible = true;
-        this.import.get_style_context().add_class("suggested-action");
-        this.import.importing.connect(() => this.viewer.clear_error());
-        this.import.imported.connect(on_import_button_imported);
-        ((Gtk.HeaderBar) get_header_bar()).pack_end(this.import);
+        var import_button = new Gtk.Button.with_mnemonic(_("_Import"));
+        add_action_widget(import_button, Gtk.ResponseType.ACCEPT);
+        set_response_sensitive(Gtk.ResponseType.ACCEPT, false);
 
-        this.viewer = new Gcr.ViewerWidget();
-        this.viewer.added.connect((v, r, parsed) => {
+        // this.viewer = new Gcr.ViewerWidget();
+        // this.viewer.added.connect((v, r, parsed) => {
+        //     debug("Parsed a '%s' with format %d", parsed.get_description(), parsed.get_format());
+        //     this.import.add_parsed(parsed);
+        // });
+        // this.viewer.show();
+        // ((Gtk.Box) get_content_area()).append(this.viewer);
+
+        this.parser.parsed.connect((parser) => {
+            var parsed = this.parser.get_parsed();
             debug("Parsed a '%s' with format %d", parsed.get_description(), parsed.get_format());
-            this.import.add_parsed(parsed);
+
+            // Try to render the parsed object
+            var renderer = Renderer.new_for_parsed(parsed);
+            if (renderer == null) {
+                // XXX show this in the UI
+                warning("Don't know how to render parsed object");
+                return;
+            }
+
+            var widget = renderer.create_widget();
+            set_child(widget);
+
+            // Check if we have any importers
+            var importers = Gcr.Importer.create_for_parsed(parsed);
+            set_response_sensitive(Gtk.ResponseType.ACCEPT, importers == null);
+        });
+        this.parser.authenticate.connect((parser, count) => {
         });
-        this.viewer.show();
-        ((Gtk.Box) get_content_area()).pack_end(this.viewer);
+    }
+
+    public ImportDialog(Gtk.Window? parent) {
+        GLib.Object(
+            transient_for: parent,
+            title: _("Data to be imported"),
+            use_header_bar: 1
+        );
     }
 
     public void add_uris(string[] uris) {
-        foreach (string uri in uris)
-            this.viewer.load_file(File.new_for_uri(uri));
+        // foreach (string uri in uris)
+        //     this.viewer.load_file(File.new_for_uri(uri));
     }
 
     public void add_text(string? display_name, string text) {
-        this.viewer.load_data(display_name, text.data);
+        try {
+            this.parser.parse_data(text.data);
+        } catch (Error err) {
+        }
+        // this.viewer.load_data(display_name, text.data);
     }
 
     private void on_import_button_imported(GLib.Object importer, Error? error) {
-        if (error == null) {
-            response(Gtk.ResponseType.OK);
-
-            string uri = ((Gcr.Importer) importer).uri;
-            foreach (Backend backend in Backend.get_registered()) {
-                Place? place = backend.lookup_place(uri);
-                if (place != null)
-                    place.load.begin(null);
-            }
+        // if (error == null) {
+        //     response(Gtk.ResponseType.OK);
 
-        } else {
-            if (!(error is GLib.IOError.CANCELLED))
-                this.viewer.show_error(_("Import failed"), error);
-        }
+        //     string uri = ((Gcr.Importer) importer).uri;
+        //     foreach (Backend backend in Backend.get_registered()) {
+        //         Place? place = backend.lookup_place(uri);
+        //         if (place != null)
+        //             place.load.begin(null);
+        //     }
+
+        // } else {
+        //     if (!(error is GLib.IOError.CANCELLED))
+        //         this.viewer.show_error(_("Import failed"), error);
+        // }
     }
 }
diff --git a/src/key-manager-filter.vala b/src/key-manager-filter.vala
new file mode 100644
index 00000000..006c753d
--- /dev/null
+++ b/src/key-manager-filter.vala
@@ -0,0 +1,125 @@
+/*
+ * Seahorse
+ *
+ * Copyright (C) 2022 Niels De Graef
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+public class Seahorse.KeyManagerFilter : Gtk.Filter {
+
+    public enum ShowFilter {
+        ANY,
+        PERSONAL,
+        TRUSTED;
+
+        public unowned string? to_string() {
+            switch (this) {
+                case ShowFilter.ANY:
+                    return "";
+                case ShowFilter.PERSONAL:
+                    return "personal";
+                case ShowFilter.TRUSTED:
+                    return "trusted";
+                default:
+                    assert_not_reached();
+            }
+        }
+
+        public static ShowFilter from_string(string? str) {
+            switch (str) {
+                case null:
+                case "":
+                case "any":
+                    return ShowFilter.ANY;
+                case "personal":
+                    return ShowFilter.PERSONAL;
+                case "trusted":
+                    return ShowFilter.TRUSTED;
+                default:
+                    critical ("Got unknown ShowFilter string: %s", str);
+                    assert_not_reached();
+            }
+        }
+    }
+
+    public ShowFilter show_filter {
+        get { return this._show_filter; }
+        set {
+            if (value != this._show_filter) {
+                this._show_filter = value;
+                changed(Gtk.FilterChange.DIFFERENT);
+            }
+        }
+    }
+    private ShowFilter _show_filter = ShowFilter.ANY;
+
+    public string filter_text {
+        get { return this._filter_text; }
+        set {
+            if (value.casefold() == this._filter_text)
+                return;
+            this._filter_text = value.casefold();
+            changed(Gtk.FilterChange.DIFFERENT);
+        }
+    }
+    private string _filter_text = "";
+
+    public override bool match(GLib.Object? item) {
+        return matches_showfilter(item)
+            && object_contains_filtered_text(item, this._filter_text);
+    }
+
+    private bool matches_showfilter(GLib.Object? obj) {
+        Flags obj_flags = Flags.NONE;
+        obj.get("object-flags", out obj_flags, null);
+
+        switch (this.show_filter) {
+            case ShowFilter.PERSONAL:
+                return Seahorse.Flags.PERSONAL in obj_flags;
+            case ShowFilter.TRUSTED:
+                return Seahorse.Flags.TRUSTED in obj_flags;
+            case ShowFilter.ANY:
+                return true;
+        }
+
+        return false;
+    }
+
+    // Search through row for text
+    private bool object_contains_filtered_text(GLib.Object? object, string? text) {
+        // Empty search text results in a match
+        if (text == null || text == "")
+            return true;
+
+        string? name = null;
+        object.get("label", out name, null);
+        if (name != null && (text in name.down()))
+            return true;
+
+        if (object.get_class().find_property("description") != null) {
+            string? description = null;
+            object.get("description", out description, null);
+            if (description != null && (text in description.down()))
+                return true;
+        }
+
+        return false;
+    }
+
+    public override Gtk.FilterMatch get_strictness () {
+        return Gtk.FilterMatch.SOME;
+    }
+}
diff --git a/src/key-manager-item-row.vala b/src/key-manager-item-row.vala
index 12b3f16e..a8e28139 100644
--- a/src/key-manager-item-row.vala
+++ b/src/key-manager-item-row.vala
@@ -29,21 +29,20 @@ public class Seahorse.KeyManagerItemRow : Gtk.ListBoxRow {
 
     construct {
         var grid = new Gtk.Grid();
-        grid.get_style_context().add_class("seahorse-item-listbox-row");
-        add(grid);
+        grid.add_css_class("seahorse-item-listbox-row");
+        set_child(grid);
 
         GLib.Icon? icon = null;
         object.get("icon", out icon);
         if (icon != null) {
-            var img = new Gtk.Image.from_gicon(icon, Gtk.IconSize.DND);
+            var img = new Gtk.Image.from_gicon(icon);
             img.margin_end = 12;
             img.pixel_size = 32;
             grid.attach(img, 0, 0, 1, 2);
         }
 
         var markup_label = new Gtk.Label(null);
-        object.bind_property("markup", markup_label, "label", BindingFlags.SYNC_CREATE);
-        markup_label.use_markup = true;
+        object.bind_property("label", markup_label, "label", BindingFlags.SYNC_CREATE);
         markup_label.halign = Gtk.Align.START;
         markup_label.xalign = 0.0f;
         markup_label.hexpand = true;
@@ -56,8 +55,6 @@ public class Seahorse.KeyManagerItemRow : Gtk.ListBoxRow {
         description_label.valign = Gtk.Align.START;
         description_label.get_style_context().add_class("seahorse-item-listbox-row-description");
         grid.attach(description_label, 2, 0);
-
-        show_all();
     }
 
     public KeyManagerItemRow(GLib.Object object) {
diff --git a/src/key-manager.vala b/src/key-manager.vala
index 559188f1..def65237 100644
--- a/src/key-manager.vala
+++ b/src/key-manager.vala
@@ -24,11 +24,7 @@
 public class Seahorse.KeyManager : Catalog {
 
     [GtkChild]
-    private unowned Hdy.Leaflet header;
-    [GtkChild]
-    private unowned Gtk.HeaderBar left_header;
-    [GtkChild]
-    private unowned Gtk.HeaderBar right_header;
+    private unowned Adw.HeaderBar right_header;
     [GtkChild]
     private unowned Gtk.Revealer back_revealer;
 
@@ -38,12 +34,20 @@ public class Seahorse.KeyManager : Catalog {
     private unowned Gtk.SearchEntry filter_entry;
 
     [GtkChild]
-    private unowned Hdy.Leaflet content_box;
+    private unowned Adw.Leaflet content_box;
     [GtkChild]
     private unowned Gtk.ScrolledWindow sidebar_area;
     private Sidebar sidebar;
+
     [GtkChild]
     private unowned Gtk.Stack content_stack;
+    [GtkChild]
+    private unowned Gtk.ScrolledWindow item_listbox_page;
+    [GtkChild]
+    private unowned Adw.StatusPage empty_state_page;
+    [GtkChild]
+    private unowned Adw.StatusPage locked_keyring_page;
+
     [GtkChild]
     private unowned Gtk.ListBox item_listbox;
 
@@ -52,23 +56,26 @@ public class Seahorse.KeyManager : Catalog {
     [GtkChild]
     private unowned Gtk.ToggleButton show_search_button;
 
-    private Seahorse.ItemList item_list;
-    private Gcr.Collection collection;
+    [GtkChild]
+    private unowned Gtk.DropTarget drop_target;
 
-    private GLib.Settings settings;
+    private KeyManagerFilter item_filter = new KeyManagerFilter();
+    private Gtk.FilterListModel item_list;
 
-    private enum DndTarget { // Drag 'n Drop target type
-        PLAIN,
-        URIS
-    }
+    private GLib.Settings settings;
 
     private const GLib.ActionEntry[] action_entries = {
-         { "new-item",         on_new_item            },
-         { "show-search",      on_show_search,        },
-         { "filter-items",     on_filter_items,       "s", "'any'" },
-         { "focus-place",      on_focus_place,        "s", "'secret-service'" },
-         { "import-file",      on_import_file          },
-         { "paste",            on_paste,               },
+         { "new-item", on_new_item },
+         { "show-search", on_show_search },
+         { "filter-items", on_filter_items, "s", "'any'" },
+         { "focus-place", on_focus_place, "s", "'secret-service'" },
+         { "import-file", on_import_file },
+         { "paste", on_paste },
+
+         { "lock-place", on_lock_place, "s" },
+         { "unlock-place", on_unlock_place, "s" },
+         { "delete-place", on_delete_place, "s" },
+         { "view-place", on_view_place, "s" },
     };
 
     public KeyManager(Application app) {
@@ -78,18 +85,21 @@ public class Seahorse.KeyManager : Catalog {
         );
         this.settings = new GLib.Settings("org.gnome.seahorse.manager");
 
-        this.collection = setup_sidebar();
+        setup_sidebar();
+
+        this.item_list = new Gtk.FilterListModel(null, this.item_filter);
+        this.sidebar.selection.bind_property("selected-item",
+                                             this.item_list, "model",
+                                             GLib.BindingFlags.SYNC_CREATE);
 
         load_css();
 
         // Add new item list and bind our listbox to it
-        this.item_list = new Seahorse.ItemList(this.collection);
         this.item_list.items_changed.connect((idx, removed, added) => check_empty_state());
         this.item_listbox.bind_model(this.item_list, (obj) => { return new KeyManagerItemRow(obj); });
         this.item_listbox.row_activated.connect(on_item_listbox_row_activated);
         this.item_listbox.selected_rows_changed.connect(on_item_listbox_selected_rows_changed);
-        this.item_listbox.popup_menu.connect(on_item_listbox_popup_menu);
-        this.item_listbox.button_press_event.connect(on_item_listbox_button_press_event);
+        // this.item_listbox.popup_menu.connect(on_item_listbox_popup_menu);
 
         init_actions();
 
@@ -100,12 +110,8 @@ public class Seahorse.KeyManager : Catalog {
         // For the filtering
         on_filter_changed(this.filter_entry);
 
-        // Setup drops
-        Gtk.drag_dest_set(this, Gtk.DestDefaults.ALL, {}, Gdk.DragAction.COPY);
-        Gtk.TargetList targets = new Gtk.TargetList(null);
-        targets.add_uri_targets(DndTarget.URIS);
-        targets.add_text_targets(DndTarget.PLAIN);
-        Gtk.drag_dest_set_target_list(this, targets);
+        // DnD
+        this.drop_target.on_drop.connect(on_drop);
 
         // In the beginning, nothing's selected, so show empty state
         check_empty_state();
@@ -117,17 +123,17 @@ public class Seahorse.KeyManager : Catalog {
 
     private void load_css() {
         Gtk.CssProvider provider = new Gtk.CssProvider();
-        Gtk.StyleContext.add_provider_for_screen(
-            Gdk.Display.get_default().get_default_screen(),
-            provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+        Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(),
+                                                  provider,
+                                                  Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
 
         provider.parsing_error.connect((section, error) => {
-            uint start = section.get_start_line();
-            uint end = section.get_end_line();
+            size_t start = section.get_start_location().lines + 1;
+            size_t end = section.get_end_location().lines + 1;
             if (start == end)
-                debug("Error parsing css on line %u: %s", start, error.message);
+                debug("Error parsing css on line %zu: %s", start, error.message);
             else
-                debug("Error parsing css on lines %u-%u: %s", start, end, error.message);
+                debug("Error parsing css on lines %zu-%zu: %s", start, end, error.message);
         });
 
         provider.load_from_resource("/org/gnome/Seahorse/seahorse.css");
@@ -147,34 +153,33 @@ public class Seahorse.KeyManager : Catalog {
         selection_changed();
     }
 
-    private bool on_item_listbox_button_press_event (Gdk.EventButton event) {
-        // Check for right click
-        if ((event.type == Gdk.EventType.BUTTON_PRESS) && (event.button == 3)) {
-            // Make sure that the right-clicked row is also selected
-            var row = this.item_listbox.get_row_at_y((int) event.y);
-            if (row != null) {
-                this.item_listbox.unselect_all();
-                this.item_listbox.select_row(row);
-            }
-
-            // Show context menu (unless no row was right clicked or nothing was selected)
-            var objects = get_selected_item_listbox_items();
-            debug("We have %u selected objects", objects.length());
-            if (objects != null)
-                show_context_menu(null);
-            return true;
+    [GtkCallback]
+    private void on_item_listbox_right_click(Gtk.GestureClick gesture,
+                                             int n_press,
+                                             double x,
+                                             double y) {
+        // Make sure that the right-clicked row is also selected
+        var row = this.item_listbox.get_row_at_y((int) y);
+        if (row != null) {
+            this.item_listbox.unselect_all();
+            this.item_listbox.select_row(row);
         }
 
-        return false;
-    }
-
-    private bool on_item_listbox_popup_menu(Gtk.Widget? listview) {
+        // Show context menu (unless no row was right clicked or nothing was selected)
         var objects = get_selected_item_listbox_items();
+        debug("We have %u selected objects", objects.length());
         if (objects != null)
             show_context_menu(null);
-        return false;
     }
 
+    // XXX
+    // private bool on_item_listbox_popup_menu(Gtk.Widget? listview) {
+    //     var objects = get_selected_item_listbox_items();
+    //     if (objects != null)
+    //         show_context_menu(null);
+    //     return false;
+    // }
+
     private GLib.List<weak GLib.Object> get_selected_item_listbox_items() {
         var rows = this.item_listbox.get_selected_rows();
         var objects = new GLib.List<weak GLib.Object>();
@@ -189,8 +194,11 @@ public class Seahorse.KeyManager : Catalog {
         base.selection_changed();
 
         var objects = get_selected_objects();
-        foreach (weak Backend backend in get_backends())
+        var backends = Backend.get_registered();
+        for (uint i = 0; i < backends.get_n_items(); i++) {
+            var backend = (Backend) backends.get_item(i);
             backend.actions.set_actions_for_selected_objects(objects);
+        }
     }
 
     private void check_empty_state() {
@@ -199,19 +207,19 @@ public class Seahorse.KeyManager : Catalog {
 
         this.show_search_button.sensitive = !empty;
         if (!empty) {
-            this.content_stack.visible_child_name = "item_listbox_page";
+            this.content_stack.visible_child = this.item_listbox_page;
             return;
         }
 
         // We have an empty page, that might still have 2 reasons:
         // - we really have no items in our collections
         // - we're dealing with a locked keyring
-        Place? place = this.sidebar.get_focused_place();
-        if (place != null && place is Lockable && ((Lockable) place).unlockable) {
-            this.content_stack.visible_child_name = "locked_keyring_page";
+        var place = (Place) this.sidebar.selection.selected_item;
+        if (place != null && (place is Lockable) && ((Lockable) place).unlockable) {
+            this.content_stack.visible_child = this.locked_keyring_page;
             return;
         }
-        this.content_stack.visible_child_name = "empty_state_page";
+        this.content_stack.visible_child = this.empty_state_page;
     }
 
     private void on_new_item(SimpleAction action, GLib.Variant? param) {
@@ -220,7 +228,7 @@ public class Seahorse.KeyManager : Catalog {
 
     [GtkCallback]
     private void on_filter_changed(Gtk.Editable entry) {
-        this.item_list.filter_text = this.filter_entry.text;
+        this.item_filter.filter_text = this.filter_entry.text;
     }
 
     [GtkCallback]
@@ -244,23 +252,17 @@ public class Seahorse.KeyManager : Catalog {
     }
 
     private void update_header() {
-        bool folded = this.content_box.folded;
-
-        this.left_header.show_close_button =
-            !folded || header.visible_child == left_header;
-
-        this.right_header.show_close_button =
-            !folded || header.visible_child == right_header;
-
-        this.back_revealer.reveal_child = this.back_revealer.visible =
-            folded && header.visible_child == right_header;
+        this.back_revealer.reveal_child = this.content_box.folded &&
+            this.content_box.visible_child_name == "item-list-pane";
     }
 
     public void import_files(string[]? uris) {
         ImportDialog dialog = new ImportDialog(this);
         dialog.add_uris(uris);
-        dialog.run();
-        dialog.destroy();
+        dialog.response.connect((response) => {
+            dialog.destroy();
+        });
+        dialog.present();
     }
 
     private void on_import_file(SimpleAction action, GLib.Variant? parameter) {
@@ -269,11 +271,8 @@ public class Seahorse.KeyManager : Catalog {
                                       Gtk.FileChooserAction.OPEN,
                                       _("_Open"), _("_Cancel"));
 
-        dialog.set_local_only(false);
-
-        // TODO: This should come from libgcr somehow
         Gtk.FileFilter filter = new Gtk.FileFilter();
-        filter.set_name(_("All key files"));
+        filter.name = _("All key files");
         filter.add_mime_type("application/pgp-keys");
         filter.add_mime_type("application/x-ssh-key");
         filter.add_mime_type("application/pkcs12");
@@ -307,66 +306,67 @@ public class Seahorse.KeyManager : Catalog {
         dialog.set_filter(filter);
 
         filter = new Gtk.FileFilter();
-        filter.set_name(_("All files"));
+        filter.name = _("All files");
         filter.add_pattern("*");
         dialog.add_filter(filter);
 
-        string? uri = null;
-        if (dialog.run() == Gtk.ResponseType.ACCEPT) {
-            uri = dialog.get_uri();
-        }
+        dialog.response.connect((response) => {
+            GLib.File? file = null;
+            if (response == Gtk.ResponseType.ACCEPT)
+                file = dialog.get_file();
 
-        dialog.destroy();
+            dialog.destroy();
 
-        if (uri != null) {
-            import_files({ uri });
-        }
+            if (file != null) {
+                string uris[1] = { file.get_uri() };
+                import_files(uris);
+            }
+        });
+        dialog.show();
     }
 
     private void import_text(string? display_name, string? text) {
         ImportDialog dialog = new ImportDialog(this);
         dialog.add_text(display_name, text);
-        dialog.run();
-        dialog.destroy();
+        dialog.response.connect((response) => {
+            dialog.destroy();
+        });
+        dialog.present();
     }
 
-    [GtkCallback]
-    private void on_drag_data_received(Gdk.DragContext context, int x, int y,
-                                       Gtk.SelectionData? selection_data, uint info, uint time) {
-        if (selection_data == null)
-            return;
-
-        if (info == DndTarget.PLAIN) {
-            string? text = selection_data.get_text();
-            import_text(_("Dropped text"), text);
-        } else if (info == DndTarget.URIS) {
-            string[]? uris = selection_data.get_uris();
-            foreach (string uri in uris)
-                uri._strip();
+    private bool on_drop(Gtk.DropTarget drop_target,
+                         GLib.Value? value,
+                         double x,
+                         double y) {
+        if (value.holds(typeof(string))) {
+            import_text(_("Dropped text"), value.get_string());
+            return true;
+        }
+        if (value.holds(typeof(GLib.Uri))) {
+            var uri = (GLib.Uri) value.get_boxed();
+            string uris[1] = { uri.to_string() };
             import_files(uris);
+            return true;
         }
-    }
 
-    private void on_paste(SimpleAction action, Variant? param) {
-        Gdk.Atom atom = Gdk.Atom.intern("CLIPBOARD", false);
-        Gtk.Clipboard clipboard = Gtk.Clipboard.get(atom);
-
-        if (clipboard.wait_is_text_available())
-            return;
-
-        clipboard.request_text(on_clipboard_received);
+        return false;
     }
 
-    private void on_clipboard_received(Gtk.Clipboard board, string? text) {
-        if (text == null)
-            return;
-
-        assert(this.filter_entry != null);
-        if (this.filter_entry.is_focus)
-            this.filter_entry.paste_clipboard();
-        else
-            if (text != null && text.char_count() > 0)
-                import_text(_("Clipboard text"), text);
+    private void on_paste(SimpleAction action, Variant? param) {
+        var clipboard = get_clipboard();
+        clipboard.read_text_async.begin(null, (obj, res) => {
+            try {
+                var text = clipboard.read_text_async.end(res);
+
+                if (this.filter_entry.is_focus())
+                    this.filter_entry.text = text;
+                else
+                    if (text != null && text.char_count() > 0)
+                        import_text(_("Clipboard text"), text);
+            } catch (GLib.Error err) {
+                warning("Couldn't read clipboard: %s", err.message);
+            }
+        });
     }
 
     private void update_view_filter(string filter_str, bool update_settings = true) {
@@ -379,8 +379,7 @@ public class Seahorse.KeyManager : Catalog {
         action.set_state(filter_str);
 
         // Update the item list
-        this.item_list.showfilter = ItemList.ShowFilter.from_string(filter_str);
-        this.item_list.refilter();
+        this.item_filter.show_filter = KeyManagerFilter.ShowFilter.from_string(filter_str);
     }
 
     private void on_show_search(SimpleAction action, Variant? param) {
@@ -411,25 +410,98 @@ public class Seahorse.KeyManager : Catalog {
         }
     }
 
-    private Gcr.Collection setup_sidebar() {
+    private void on_lock_place(SimpleAction action, Variant? param) {
+        string? uri = param.get_string();
+        return_if_fail(uri != null);
+
+        uint pos;
+        var place = this.sidebar.lookup_place_for_uri(uri, out pos) as Lockable;
+        return_if_fail(place != null && place.lockable);
+
+        place.lock.begin(null, null, (obj, res) => {
+            try {
+                place.lock.end(res);
+                check_empty_state();
+            } catch (Error e) {
+                Util.show_error(this, _("Couldn’t lock"), e.message);
+            }
+        });
+    }
+
+    private void on_unlock_place(SimpleAction action, Variant? param) {
+        string? uri = param.get_string();
+        return_if_fail(uri != null);
+
+        uint pos;
+        var place = this.sidebar.lookup_place_for_uri(uri, out pos) as Lockable;
+        return_if_fail(place != null && place.unlockable);
+
+        place.unlock.begin(null, null, (obj, res) => {
+            try {
+                place.unlock.end(res);
+                check_empty_state();
+            } catch (Error e) {
+                Util.show_error(this, _("Couldn’t unlock"), e.message);
+            }
+        });
+    }
+
+    private void on_delete_place(SimpleAction action, Variant? param) {
+        string? uri = param.get_string();
+        return_if_fail(uri != null);
+
+        uint pos;
+        var place = this.sidebar.lookup_place_for_uri(uri, out pos) as Deletable;
+        return_if_fail(place != null && place.deletable);
+
+        //XXX
+        var deleter = place.create_deleter();
+        var prompt = deleter.create_confirm(this);
+        ((Gtk.Dialog) prompt).response.connect((response) => {
+            if (response != Gtk.ResponseType.ACCEPT) {
+                prompt.destroy();
+                return;
+            }
+            prompt.destroy();
+
+            deleter.delete.begin(null, (obj, res) => {
+                try {
+                    deleter.delete.end(res);
+                    this.destroy();
+                } catch (GLib.Error e) {
+                    Util.show_error(parent, _("Couldn’t delete"), e.message);
+                }
+            });
+        });
+    }
+
+    private void on_view_place(SimpleAction action, Variant? param) {
+        string? uri = param.get_string();
+        return_if_fail(uri != null);
+
+        uint pos;
+        var place = this.sidebar.lookup_place_for_uri(uri, out pos) as Viewable;
+        return_if_fail(place != null);
+        Viewable.view(place, this);
+    }
+
+    private void setup_sidebar() {
         this.sidebar = new Sidebar();
 
         restore_keyring_selection();
 
         // Make sure we update the empty state on any change
-        this.sidebar.selected_rows_changed.connect(on_sidebar_selected_rows_changed);
-        this.sidebar.current_collection_changed.connect((sidebar) => { check_empty_state (); });
+        this.sidebar.selection.selection_changed.connect(on_sidebar_selection_changed);
 
-        foreach (weak Backend backend in get_backends()) {
+        var backends = Backend.get_registered();
+        for (uint i = 0; i < backends.get_n_items(); i++) {
+            var backend = (Backend) backends.get_item(i);
             ActionGroup actions = backend.actions;
             actions.catalog = this;
             insert_action_group(actions.prefix, actions);
         }
 
-        this.sidebar_area.add(this.sidebar);
-        this.sidebar.show();
-
-        return this.sidebar.objects;
+        this.sidebar_area.set_child(this.sidebar);
     }
 
     // On setup, select the LRU place from the user's last session (if any).
@@ -445,8 +517,12 @@ public class Seahorse.KeyManager : Catalog {
             unowned string uri = last_keyring[0];
 
             debug("Selecting last used place %s", uri);
-            if (this.sidebar.set_focused_place_for_uri(uri))
+            uint pos;
+            var place = this.sidebar.lookup_place_for_uri(uri, out pos);
+            if (place != null) {
+                this.sidebar.selection.selected = pos;
                 return GLib.Source.REMOVE;
+            }
 
             // If that didn't work, try do a attempt by matching the scheme.
             // FIXME should do this poperly (not always getting proper URIs atm)
@@ -455,66 +531,51 @@ public class Seahorse.KeyManager : Catalog {
                 return GLib.Source.REMOVE;
 
             // Still nothing. Can happen on first run -> just select first entry
-            this.sidebar.select_row(this.sidebar.get_row_at_index(0));
+            this.sidebar.selection.selected = 0;
             return GLib.Source.REMOVE;
         });
     }
 
-    private void on_sidebar_selected_rows_changed(Gtk.ListBox sidebar) {
+    private void on_sidebar_selection_changed(Gtk.SelectionModel selection,
+                                              uint position,
+                                              uint n_items) {
         check_empty_state();
 
         show_item_list_pane();
 
-        Place? place = this.sidebar.get_focused_place();
+        var place = (Place) this.sidebar.selection.selected_item;
         return_if_fail (place != null);
 
-        this.right_header.title = place.label;
+        this.right_header.title_widget = new Adw.WindowTitle(place.label, "");
         this.settings.set_strv("keyrings-selected", { place.uri });
     }
 
-    public override List<weak Backend> get_backends() {
-        return this.sidebar.get_backends();
-    }
-
-    [GtkCallback]
-    private void on_popover_grab_notify(Gtk.Widget widget, bool was_grabbed) {
-        if (!was_grabbed)
-            widget.hide();
-    }
-
     [GtkCallback]
     private void on_locked_keyring_unlock_button_clicked(Gtk.Button unlock_button) {
-        Lockable? place = this.sidebar.get_focused_place() as Lockable;
+        unowned var place = this.sidebar.selection.selected_item as Lockable;
         return_if_fail(place != null && place.unlockable);
 
-        unlock_button.sensitive = false;
-        place.unlock.begin(null, null, (obj, res) => {
-            try {
-                unlock_button.sensitive = true;
-                place.unlock.end(res);
-                // Explicitly trigger an update of the main view
-                check_empty_state();
-            } catch (GLib.Error e) {
-                unlock_button.sensitive = true;
-                Util.show_error(this, _("Couldn’t unlock keyring"), e.message);
-            }
-        });
+        // XXX test
+        activate_action("unlock-place", new Variant.string(((Place) place).uri));
     }
 
     [GtkCallback]
-    private bool on_key_pressed(Gtk.Widget widget, Gdk.EventKey event) {
+    private bool on_key_pressed(Gtk.EventControllerKey controller,
+                                uint keyval,
+                                uint keycode,
+                                Gdk.ModifierType state) {
         bool folded = this.content_box.folded;
 
-        switch (event.keyval) {
+        switch (keyval) {
           // <Alt>Down and <Alt>Up for easy sidebar navigation
           case Gdk.Key.Down:
-              if (Gdk.ModifierType.MOD1_MASK in event.state && !folded) {
+              if (Gdk.ModifierType.ALT_MASK in state && !folded) {
                   this.sidebar.select_next_place();
                   return true;
               }
               break;
           case Gdk.Key.Up:
-              if (Gdk.ModifierType.MOD1_MASK in event.state && !folded) {
+              if (Gdk.ModifierType.ALT_MASK in state && !folded) {
                   this.sidebar.select_previous_place();
                   return true;
               }
@@ -522,7 +583,7 @@ public class Seahorse.KeyManager : Catalog {
 
           // <Alt>Left to go back to sidebar in folded mode
           case Gdk.Key.Left:
-              if (Gdk.ModifierType.MOD1_MASK in event.state && folded) {
+              if (Gdk.ModifierType.ALT_MASK in state && folded) {
                   show_sidebar_pane();
                   return true;
               }
@@ -530,7 +591,7 @@ public class Seahorse.KeyManager : Catalog {
 
           // <Alt>Right to open currently selected element in folded mode
           case Gdk.Key.Right:
-              if (Gdk.ModifierType.MOD1_MASK in event.state && folded) {
+              if (Gdk.ModifierType.ALT_MASK in state && folded) {
                   show_item_list_pane();
                   return true;
               }
@@ -540,6 +601,6 @@ public class Seahorse.KeyManager : Catalog {
         }
 
         // By default: propagate to the search bar, for search-as-you-type
-        return this.search_bar.handle_event(event);
+        return controller.forward(this.search_bar);
     }
 }
diff --git a/src/main.vala b/src/main.vala
index 0d1eac0b..52c1e3ad 100644
--- a/src/main.vala
+++ b/src/main.vala
@@ -25,9 +25,5 @@ public int main (string[] args) {
     Intl.textdomain(Config.GETTEXT_PACKAGE);
 
     Seahorse.Application app = new Seahorse.Application();
-    int status = app.run(args);
-
-    Seahorse.Registry.cleanup();
-
-    return status;
+    return app.run(args);
 }
diff --git a/src/meson.build b/src/meson.build
index 396f9c4c..e8a62088 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -8,6 +8,7 @@ seahorse_sources = [
   'application.vala',
   'import-dialog.vala',
   'key-manager.vala',
+  'key-manager-filter.vala',
   'key-manager-item-row.vala',
   'main.vala',
   'search-provider.vala',
@@ -19,8 +20,8 @@ seahorse_sources = [
 
 seahorse_dependencies = [
   glib_deps,
-  gtk,
-  libhandy_dep,
+  gtk4_dep,
+  libadwaita_dep,
   libsecret,
   common_dep,
   libseahorse_dep,
diff --git a/src/seahorse-key-manager.ui b/src/seahorse-key-manager.ui
index 4b69b6af..81bc4032 100644
--- a/src/seahorse-key-manager.ui
+++ b/src/seahorse-key-manager.ui
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
   <menu id="main_menu">
     <section>
       <item>
@@ -57,538 +56,399 @@
   </menu>
 
   <object class="GtkPopover" id="new_item_menu_popover">
-    <signal name="grab-notify" handler="on_popover_grab_notify" after="yes"/>
+    <property name="halign">start</property>
     <child>
-      <object class="GtkBox">
-        <property name="visible">True</property>
-        <property name="orientation">vertical</property>
-        <property name="can_focus">False</property>
-        <property name="border_width">6</property>
+      <object class="GtkListBox" id="generate_list">
+        <property name="selection-mode">none</property>
+        <property name="selection-mode">browse</property>
+        <style>
+          <class name="new-item-list"/>
+        </style>
         <child>
-          <object class="GtkListBox" id="generate_list">
-            <property name="visible">True</property>
-            <property name="selection-mode">none</property>
-            <property name="can_focus">True</property>
-            <property name="has_focus">True</property>
-            <property name="selection-mode">browse</property>
-            <style>
-              <class name="new-item-list"/>
-            </style>
+          <object class="GtkListBoxRow" id="ssh_generate_key_button">
+            <property name="visible" bind-source="ssh_generate_key_button" bind-property="sensitive" />
+            <property name="selectable">False</property>
+            <property name="action-name">ssh.generate-key</property>
             <child>
-              <object class="GtkListBoxRow" id="ssh_generate_key_button">
-                <property name="visible" bind-source="ssh_generate_key_button" bind-property="sensitive" />
-                <property name="margin">3</property>
-                <property name="selectable">False</property>
-                <property name="action-name">ssh.generate-key</property>
+              <object class="GtkGrid">
+                <property name="orientation">vertical</property>
                 <child>
-                  <object class="GtkGrid">
-                    <property name="visible">True</property>
-                    <property name="orientation">vertical</property>
-                    <child>
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="halign">start</property>
-                        <property name="label" translatable="yes">Secure Shell key</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="halign">start</property>
-                        <property name="label" translatable="yes">Used to access other computers</property>
-                        <attributes>
-                          <attribute name="scale" value="0.8"/>
-                        </attributes>
-                        <style>
-                          <class name="dim-label"/>
-                        </style>
-                      </object>
-                    </child>
+                  <object class="GtkLabel">
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Secure Shell key</property>
                   </object>
                 </child>
-              </object>
-            </child>
-            <child>
-              <object class="GtkListBoxRow" id="pgp_generate_key_button">
-                <property name="visible" bind-source="pgp_generate_key_button" bind-property="sensitive" />
-                <property name="margin">3</property>
-                <property name="selectable">False</property>
-                <property name="action-name">pgp.pgp-generate-key</property>
                 <child>
-                  <object class="GtkGrid">
-                    <property name="visible">True</property>
-                    <property name="orientation">vertical</property>
-                    <child>
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="halign">start</property>
-                        <property name="label" translatable="yes">GPG key</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="halign">start</property>
-                        <property name="label" translatable="yes">Used to encrypt emails and files</property>
-                        <attributes>
-                          <attribute name="scale" value="0.8"/>
-                        </attributes>
-                        <style>
-                          <class name="dim-label"/>
-                        </style>
-                      </object>
-                    </child>
+                  <object class="GtkLabel">
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Used to access other computers</property>
+                    <attributes>
+                      <attribute name="scale" value="0.8"/>
+                    </attributes>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
                   </object>
                 </child>
               </object>
             </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkListBoxRow" id="pgp_generate_key_button">
+            <property name="visible" bind-source="pgp_generate_key_button" bind-property="sensitive" />
+            <property name="selectable">False</property>
+            <property name="action-name">pgp.pgp-generate-key</property>
             <child>
-              <object class="GtkListBoxRow" id="gkr_generate_keyring_button">
-                <property name="visible" bind-source="gkr_generate_keyring_button" bind-property="sensitive" 
/>
-                <property name="margin">3</property>
-                <property name="selectable">False</property>
-                <property name="action-name">gkr.keyring-new</property>
+              <object class="GtkGrid">
+                <property name="orientation">vertical</property>
                 <child>
-                  <object class="GtkGrid">
-                    <property name="visible">True</property>
-                    <property name="orientation">vertical</property>
-                    <child>
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="halign">start</property>
-                        <property name="label" translatable="yes">Password keyring</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="halign">start</property>
-                        <property name="label" translatable="yes">Used to store application and network 
passwords</property>
-                        <attributes>
-                          <attribute name="scale" value="0.8"/>
-                        </attributes>
-                        <style>
-                          <class name="dim-label"/>
-                        </style>
-                      </object>
-                    </child>
+                  <object class="GtkLabel">
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">GPG key</property>
                   </object>
                 </child>
-              </object>
-            </child>
-            <child>
-              <object class="GtkListBoxRow" id="gkr_generate_item_button">
-                <property name="visible" bind-source="gkr_generate_item_button" bind-property="sensitive" />
-                <property name="margin">3</property>
-                <property name="selectable">False</property>
-                <property name="action-name">gkr.keyring-item-new</property>
                 <child>
-                  <object class="GtkGrid">
-                    <property name="visible">True</property>
-                    <property name="orientation">vertical</property>
-                    <child>
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="halign">start</property>
-                        <property name="label" translatable="yes">Password</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="halign">start</property>
-                        <property name="label" translatable="yes">Safely store a password or 
secret</property>
-                        <attributes>
-                          <attribute name="scale" value="0.8"/>
-                        </attributes>
-                        <style>
-                          <class name="dim-label"/>
-                        </style>
-                      </object>
-                    </child>
+                  <object class="GtkLabel">
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Used to encrypt emails and files</property>
+                    <attributes>
+                      <attribute name="scale" value="0.8"/>
+                    </attributes>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
                   </object>
                 </child>
               </object>
             </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkListBoxRow" id="gkr_generate_keyring_button">
+            <property name="visible" bind-source="gkr_generate_keyring_button" bind-property="sensitive" />
+            <property name="selectable">False</property>
+            <property name="action-name">gkr.keyring-new</property>
             <child>
-              <object class="GtkListBoxRow" id="pkcs11_generate_key_button">
-                <property name="visible" bind-source="pkcs11_generate_key_button" bind-property="sensitive" 
/>
-                <property name="margin">3</property>
-                <property name="selectable">False</property>
-                <property name="action-name">pkcs11.pkcs11-generate-key</property>
+              <object class="GtkGrid">
+                <property name="orientation">vertical</property>
                 <child>
-                  <object class="GtkGrid">
-                    <property name="visible">True</property>
-                    <property name="orientation">vertical</property>
-                    <child>
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="halign">start</property>
-                        <property name="label" translatable="yes">Private key</property>
-                      </object>
-                    </child>
-                    <child>
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="halign">start</property>
-                        <property name="label" translatable="yes">Used to request a certificate</property>
-                        <attributes>
-                          <attribute name="scale" value="0.8"/>
-                        </attributes>
-                        <style>
-                          <class name="dim-label"/>
-                        </style>
-                      </object>
-                    </child>
+                  <object class="GtkLabel">
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Password keyring</property>
                   </object>
                 </child>
-              </object>
-            </child>
-            <child>
-              <object class="GtkListBoxRow" id="import_from_file_button">
-                <property name="visible" bind-source="import_from_file_button" bind-property="sensitive" />
-                <property name="margin">6</property>
-                <property name="selectable">False</property>
-                <property name="action-name">win.import-file</property>
                 <child>
                   <object class="GtkLabel">
-                    <property name="visible">True</property>
                     <property name="halign">start</property>
-                    <property name="vexpand">True</property>
-                    <property name="margin-top">3</property>
-                    <property name="margin-bottom">3</property>
-                    <property name="label" translatable="yes">Import from file…</property>
+                    <property name="label" translatable="yes">Used to store application and network 
passwords</property>
+                    <attributes>
+                      <attribute name="scale" value="0.8"/>
+                    </attributes>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
                   </object>
                 </child>
               </object>
             </child>
           </object>
         </child>
-      </object>
-    </child>
-  </object>
-
-  <template class="SeahorseKeyManager" parent="SeahorseCatalog">
-    <property name="default-width">600</property>
-    <property name="default-height">476</property>
-    <signal name="drag-data-received" handler="on_drag_data_received" />
-    <signal name="key-press-event" handler="on_key_pressed"/>
-    <child type="titlebar">
-      <object class="HdyTitleBar" id="titlebar">
-        <property name="visible">True</property>
         <child>
-          <object class="HdyLeaflet" id="header">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="mode-transition-duration" bind-source="content_box" 
bind-property="mode-transition-duration" bind-flags="bidirectional|sync-create"/>
-            <property name="child-transition-duration" bind-source="content_box" 
bind-property="child-transition-duration" bind-flags="bidirectional|sync-create"/>
-            <property name="transition-type" bind-source="content_box" bind-property="transition-type" 
bind-flags="bidirectional|sync-create"/>
+          <object class="GtkListBoxRow" id="gkr_generate_item_button">
+            <property name="visible" bind-source="gkr_generate_item_button" bind-property="sensitive" />
+            <property name="selectable">False</property>
+            <property name="action-name">gkr.keyring-item-new</property>
             <child>
-              <object class="GtkHeaderBar" id="left_header">
-                <property name="visible">True</property>
-                <property name="hexpand">False</property>
-                <property name="can_focus">False</property>
-                <property name="title" translatable="yes">Passwords and Keys</property>
-                <property name="show_close_button">True</property>
+              <object class="GtkGrid">
+                <property name="orientation">vertical</property>
                 <child>
-                  <object class="GtkMenuButton" id="new_item_button">
-                    <property name="visible">True</property>
+                  <object class="GtkLabel">
                     <property name="halign">start</property>
-                    <property name="tooltip_text" translatable="yes">Add a new key or item</property>
-                    <property name="popover">new_item_menu_popover</property>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="icon_name">list-add-symbolic</property>
-                      </object>
-                    </child>
+                    <property name="label" translatable="yes">Password</property>
                   </object>
-                  <packing>
-                    <property name="pack_type">start</property>
-                  </packing>
                 </child>
                 <child>
-                  <object class="GtkMenuButton" id="main_menu_button">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="focus_on_click">False</property>
-                    <property name="menu-model">main_menu</property>
-                    <accelerator key="F10" signal="activate"/>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="icon_name">open-menu-symbolic</property>
-                      </object>
-                    </child>
+                  <object class="GtkLabel">
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Safely store a password or secret</property>
+                    <attributes>
+                      <attribute name="scale" value="0.8"/>
+                    </attributes>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
                   </object>
-                  <packing>
-                    <property name="pack_type">end</property>
-                  </packing>
                 </child>
               </object>
-              <packing>
-                <property name="name">sidebar-pane</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkSeparator" id="header_separator">
-                <property name="visible">True</property>
-                <style>
-                  <class name="sidebar"/>
-                </style>
-              </object>
-              <packing>
-                <property name="navigatable">False</property>
-              </packing>
             </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkListBoxRow" id="pkcs11_generate_key_button">
+            <property name="visible" bind-source="pkcs11_generate_key_button" bind-property="sensitive" />
+            <property name="selectable">False</property>
+            <property name="action-name">pkcs11.pkcs11-generate-key</property>
             <child>
-              <object class="GtkHeaderBar" id="right_header">
-                <property name="visible">True</property>
-                <property name="hexpand">True</property>
-                <property name="show_close_button">True</property>
-                <child>
-                  <object class="GtkRevealer" id="back_revealer">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="transition-type">slide-right</property>
-                    <property name="transition-duration" bind-source="content_box" 
bind-property="mode-transition-duration" bind-flags="bidirectional|sync-create"/>
-                    <child>
-                      <object class="GtkButton" id="back">
-                        <property name="visible">True</property>
-                        <property name="valign">center</property>
-                        <property name="use-underline">True</property>
-                        <signal name="clicked" handler="on_back_clicked"/>
-                        <style>
-                          <class name="image-button"/>
-                        </style>
-                        <child internal-child="accessible">
-                          <object class="AtkObject" id="a11y-back">
-                            <property name="accessible-name" translatable="yes">Back</property>
-                          </object>
-                        </child>
-                        <child>
-                          <object class="GtkImage" id="back_image">
-                            <property name="visible">True</property>
-                            <property name="icon-name">go-previous-symbolic</property>
-                            <property name="icon-size">1</property>
-                          </object>
-                        </child>
-                      </object>
-                    </child>
-                  </object>
-                </child>
+              <object class="GtkGrid">
+                <property name="orientation">vertical</property>
                 <child>
-                  <object class="GtkMenuButton" id="items_menu_button">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="focus_on_click">False</property>
-                    <property name="menu-model">items_menu</property>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="icon_name">view-more-symbolic</property>
-                      </object>
-                    </child>
+                  <object class="GtkLabel">
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Private key</property>
                   </object>
-                  <packing>
-                    <property name="pack_type">end</property>
-                  </packing>
                 </child>
                 <child>
-                  <object class="GtkToggleButton" id="show_search_button">
-                    <property name="visible">True</property>
-                    <property name="tooltip_text" translatable="yes">Search for a key or password</property>
-                    <child>
-                      <object class="GtkImage">
-                        <property name="visible">True</property>
-                        <property name="icon_name">edit-find-symbolic</property>
-                      </object>
-                    </child>
+                  <object class="GtkLabel">
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes">Used to request a certificate</property>
+                    <attributes>
+                      <attribute name="scale" value="0.8"/>
+                    </attributes>
+                    <style>
+                      <class name="dim-label"/>
+                    </style>
                   </object>
-                  <packing>
-                    <property name="pack_type">end</property>
-                  </packing>
                 </child>
               </object>
-              <packing>
-                <property name="name">item-list-pane</property>
-              </packing>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkListBoxRow" id="import_from_file_button">
+            <property name="visible" bind-source="import_from_file_button" bind-property="sensitive" />
+            <property name="selectable">False</property>
+            <property name="action-name">win.import-file</property>
+            <child>
+              <object class="GtkLabel">
+                <property name="halign">start</property>
+                <property name="vexpand">True</property>
+                <property name="label" translatable="yes">Import from file…</property>
+              </object>
             </child>
           </object>
         </child>
       </object>
     </child>
+  </object>
+
+  <template class="SeahorseKeyManager" parent="SeahorseCatalog">
+    <property name="default-width">600</property>
+    <property name="default-height">476</property>
 
+    <!-- Event controllers -->
     <child>
-      <object class="HdyLeaflet" id="content_box">
-        <property name="visible">True</property>
+      <object class="GtkEventControllerKey">
+        <signal name="key-pressed" handler="on_key_pressed"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkDropTarget" id="drop_target">
+        <property name="actions">copy</property>
+        <!-- We can't use this here since Vala can't handle the "drop" name conflict -->
+        <!-- <signal name="drop" handler="on_drop"/> -->
+      </object>
+    </child>
+
+    <child>
+      <object class="AdwLeaflet" id="content_box">
         <property name="vexpand">True</property>
-        <property name="can_focus">False</property>
-        <property name="can-swipe-back">True</property>
+        <property name="can-navigate-back">True</property>
         <property name="transition-type">over</property>
         <signal name="notify::fold" handler="on_fold" object="SeahorseKeyManager" after="yes" swapped="no"/>
+
+        <!-- The sidebar pane -->
         <child>
-          <object class="GtkScrolledWindow" id="sidebar_area">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="hscrollbar-policy">never</property>
-          </object>
-          <packing>
+          <object class="AdwLeafletPage">
             <property name="name">sidebar-pane</property>
-          </packing>
+            <property name="child">
+              <object class="GtkBox">
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="AdwHeaderBar" id="left_header">
+                    <property name="hexpand">False</property>
+                    <property name="title-widget">
+                      <object class="AdwWindowTitle">
+                        <property name="title" translatable="yes">Passwords and Keys</property>
+                      </object>
+                    </property>
+                    <binding name="show-end-title-buttons">
+                      <lookup name="folded">content_box</lookup>
+                    </binding>
+                    <child>
+                      <object class="GtkMenuButton" id="new_item_button">
+                        <property name="halign">start</property>
+                        <property name="tooltip-text" translatable="yes">Add a new key or item</property>
+                        <property name="popover">new_item_menu_popover</property>
+                        <property name="icon-name">list-add-symbolic</property>
+                      </object>
+                    </child>
+                    <child type="end">
+                      <object class="GtkMenuButton" id="main_menu_button">
+                        <property name="menu-model">main_menu</property>
+                        <property name="icon-name">open-menu-symbolic</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkScrolledWindow" id="sidebar_area">
+                    <property name="hscrollbar-policy">never</property>
+                    <property name="vexpand">True</property>
+                  </object>
+                </child>
+              </object>
+            </property>
+          </object>
         </child>
+
+        <!-- Separator -->
         <child>
-          <object class="GtkSeparator">
-            <property name="visible">True</property>
-            <style>
-              <class name="sidebar"/>
-            </style>
-          </object>
-          <packing>
+          <object class="AdwLeafletPage">
             <property name="navigatable">False</property>
-          </packing>
+            <property name="child">
+              <object class="GtkSeparator"/>
+            </property>
+          </object>
         </child>
+
+        <!-- The items (right hand) pane -->
         <child>
-          <object class="GtkBox" id="right_content">
-            <property name="visible">True</property>
-            <property name="orientation">vertical</property>
-            <child>
-              <object class="GtkSearchBar" id="search_bar">
-                <property name="visible">True</property>
-                <property name="search-mode-enabled" bind-source="show_search_button" bind-property="active" 
bind-flags="bidirectional|sync-create" />
-                <property name="can_focus">False</property>
-                <child>
-                  <object class="GtkSearchEntry" id="filter_entry">
-                    <property name="visible">True</property>
-                    <property name="width_chars">30</property>
-                    <property name="placeholder_text" translatable="yes">Filter</property>
-                    <signal name="search-changed" handler="on_filter_changed" />
-                  </object>
-                </child>
-              </object>
-            </child>
-            <child>
-              <object class="GtkStack" id="content_stack">
-                <property name="visible">True</property>
-                <property name="homogeneous">True</property>
+          <object class="AdwLeafletPage">
+            <property name="name">item-list-pane</property>
+            <property name="child">
+              <object class="GtkBox" id="right_content">
+                <property name="orientation">vertical</property>
+                <property name="vexpand">True</property>
                 <child>
-                  <object class="GtkScrolledWindow">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="hscrollbar-policy">never</property>
+                  <object class="AdwHeaderBar" id="right_header">
+                    <property name="hexpand">True</property>
+                    <property name="title-widget">
+                      <object class="AdwWindowTitle">
+                        <property name="title"></property>
+                      </object>
+                    </property>
+                    <binding name="show-start-title-buttons">
+                      <lookup name="folded">content_box</lookup>
+                    </binding>
                     <child>
-                      <object class="HdyClamp">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="tightening-threshold">400</property>
-                        <property name="maximum-size">800</property>
-                        <property name="margin-top">12</property>
-                        <property name="margin-bottom">12</property>
+                      <object class="GtkRevealer" id="back_revealer">
+                        <binding name="visible">
+                          <lookup name="folded">content_box</lookup>
+                        </binding>
+                        <property name="transition-type">slide-right</property>
+                        <property name="transition-duration" bind-source="content_box" 
bind-property="mode-transition-duration" bind-flags="bidirectional|sync-create"/>
                         <child>
-                          <object class="GtkListBox" id="item_listbox">
-                            <property name="visible">True</property>
-                            <property name="hexpand">True</property>
-                            <property name="can-focus">True</property>
-                            <property name="has-focus">True</property>
-                            <property name="margin-start">12</property>
-                            <property name="margin-end">12</property>
-                            <property name="activate-on-single-click">False</property>
-                            <property name="selection-mode">multiple</property>
-                            <style>
-                              <class name="content"/>
-                            </style>
+                          <object class="GtkButton" id="back">
+                            <property name="valign">center</property>
+                            <property name="use-underline">True</property>
+                            <property name="icon-name">go-previous-symbolic</property>
+                            <signal name="clicked" handler="on_back_clicked"/>
                           </object>
                         </child>
                       </object>
                     </child>
+                    <child type="end">
+                      <object class="GtkMenuButton" id="items_menu_button">
+                        <property name="menu-model">items_menu</property>
+                        <property name="icon-name">view-more-symbolic</property>
+                      </object>
+                    </child>
+                    <child type="end">
+                      <object class="GtkToggleButton" id="show_search_button">
+                        <property name="tooltip-text" translatable="yes">Search for a key or 
password</property>
+                        <property name="icon-name">edit-find-symbolic</property>
+                      </object>
+                    </child>
                   </object>
-                  <packing>
-                    <property name="name">item_listbox_page</property>
-                  </packing>
                 </child>
                 <child>
-                  <object class="HdyStatusPage">
-                    <property name="visible">True</property>
-                    <property name="icon-name">org.gnome.seahorse.Application-symbolic</property>
-                    <property name="title" translatable="yes">This collection seems to be empty</property>
+                  <object class="GtkSearchBar" id="search_bar">
+                    <property name="search-mode-enabled" bind-source="show_search_button" 
bind-property="active" bind-flags="bidirectional|sync-create" />
                     <child>
-                      <object class="GtkButton">
-                        <property name="visible">True</property>
-                        <property name="halign">center</property>
-                        <property name="action-name">win.new-item</property>
-                        <property name="label" translatable="yes">Add new items</property>
-                        <style>
-                          <class name="suggested-action"/>
-                        </style>
+                      <object class="GtkSearchEntry" id="filter_entry">
+                        <property name="width_chars">30</property>
+                        <property name="placeholder_text" translatable="yes">Filter</property>
+                        <signal name="search-changed" handler="on_filter_changed" />
                       </object>
                     </child>
                   </object>
-                  <packing>
-                    <property name="name">empty_state_page</property>
-                  </packing>
                 </child>
                 <child>
-                  <object class="HdyStatusPage">
-                    <property name="visible">True</property>
-                    <property name="icon-name">org.gnome.seahorse.Application-symbolic</property>
-                    <property name="title" translatable="yes">Keyring is locked</property>
+                  <object class="GtkStack" id="content_stack">
+                    <property name="hhomogeneous">True</property>
+                    <property name="vhomogeneous">True</property>
+                    <child>
+                      <object class="GtkScrolledWindow" id="item_listbox_page">
+                        <property name="hscrollbar-policy">never</property>
+                        <property name="vexpand">True</property>
+                        <child>
+                          <object class="AdwClamp">
+                            <property name="tightening-threshold">400</property>
+                            <property name="maximum-size">800</property>
+                            <property name="margin-top">12</property>
+                            <property name="margin-bottom">12</property>
+                            <child>
+                              <object class="GtkListBox" id="item_listbox">
+                                <property name="hexpand">True</property>
+                                <property name="margin-start">12</property>
+                                <property name="margin-end">12</property>
+                                <property name="activate-on-single-click">False</property>
+                                <property name="selection-mode">multiple</property>
+                                <style>
+                                  <class name="content"/>
+                                </style>
+                                <child>
+                                  <object class="GtkGestureClick">
+                                    <property name="button">3</property>
+                                    <signal name="pressed" handler="on_item_listbox_right_click"/>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="AdwStatusPage" id="empty_state_page">
+                        <property name="icon-name">org.gnome.seahorse.Application-symbolic</property>
+                        <property name="title" translatable="yes">This collection seems to be 
empty</property>
+                        <child>
+                          <object class="GtkButton">
+                            <property name="halign">center</property>
+                            <property name="action-name">win.new-item</property>
+                            <property name="label" translatable="yes">Add new items</property>
+                            <style>
+                              <class name="suggested-action"/>
+                            </style>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
                     <child>
-                      <object class="GtkButton" id="locked_keyring_unlock_button">
-                        <property name="visible">True</property>
-                        <property name="halign">center</property>
-                        <property name="label" translatable="yes">Unlock</property>
-                        <signal name="clicked" handler="on_locked_keyring_unlock_button_clicked"/>
-                        <style>
-                          <class name="suggested-action"/>
-                        </style>
+                      <object class="AdwStatusPage" id="locked_keyring_page">
+                        <property name="icon-name">org.gnome.seahorse.Application-symbolic</property>
+                        <property name="title" translatable="yes">Keyring is locked</property>
+                        <child>
+                          <object class="GtkButton" id="locked_keyring_unlock_button">
+                            <property name="halign">center</property>
+                            <property name="label" translatable="yes">Unlock</property>
+                            <signal name="clicked" handler="on_locked_keyring_unlock_button_clicked"/>
+                            <style>
+                              <class name="suggested-action"/>
+                            </style>
+                          </object>
+                        </child>
                       </object>
                     </child>
                   </object>
-                  <packing>
-                    <property name="name">locked_keyring_page</property>
-                  </packing>
                 </child>
               </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-              </packing>
-            </child>
+            </property>
           </object>
-          <packing>
-            <property name="name">item-list-pane</property>
-          </packing>
         </child>
       </object>
     </child>
   </template>
-
-  <object class="GtkSizeGroup" id="left_pane_size_group">
-    <property name="mode">horizontal</property>
-    <widgets>
-      <widget name="left_header"/>
-      <widget name="sidebar_area"/>
-    </widgets>
-  </object>
-  <object class="GtkSizeGroup" id="right_pane_size_group">
-    <property name="mode">horizontal</property>
-    <widgets>
-      <widget name="right_header"/>
-      <widget name="right_content"/>
-    </widgets>
-  </object>
-  <object class="HdyHeaderGroup" id="header_group">
-    <headerbars>
-      <headerbar name="left_header"/>
-      <headerbar name="right_header"/>
-    </headerbars>
-  </object>
-  <object class="HdySwipeGroup" id="swipe_group">
-    <swipeables>
-      <swipeable name="header"/>
-      <swipeable name="content_box"/>
-    </swipeables>
-  </object>
 </interface>
diff --git a/src/search-provider.vala b/src/search-provider.vala
index 6873f191..e0f9116b 100644
--- a/src/search-provider.vala
+++ b/src/search-provider.vala
@@ -20,11 +20,13 @@
 
 [DBus (name = "org.gnome.Shell.SearchProvider2")]
 public class Seahorse.SearchProvider : GLib.Object {
-    private Gcr.UnionCollection union_collection = new Gcr.UnionCollection();
+
+    private GLib.ListModel backends;
+    private Gtk.FilterListModel collection;
+
     private HashTable<string, weak GLib.Object> handles
         = new HashTable<string, weak GLib.Object>.full(str_hash, str_equal, free, null);
 
-    private Gcr.FilterCollection collection;
     private GLib.Application app;
     private int n_loading = 0;
     private RWLock n_loading_lock = RWLock();
@@ -32,10 +34,15 @@ public class Seahorse.SearchProvider : GLib.Object {
 
     public SearchProvider(GLib.Application app) {
         this.app = app;
-        this.collection = new Gcr.FilterCollection.with_callback(this.union_collection, filter_objects);
+
+        // XXX test
+        this.backends = Backend.get_registered();
+        var places = new Gtk.FlattenListModel(this.backends);
+        var filter = new Gtk.CustomFilter(filter_objects);
+        this.collection = new Gtk.FilterListModel(places, filter);
     }
 
-    private static bool filter_objects (GLib.Object? obj) {
+    private static bool filter_objects(GLib.Object obj) {
         unowned Object? object = obj as Object;
         if (object == null || !(Flags.PERSONAL in object.object_flags))
             return false;
@@ -52,13 +59,7 @@ public class Seahorse.SearchProvider : GLib.Object {
         return true;
     }
 
-    ~SearchProvider() {
-        this.handles.foreach( (__, obj) => {
-            obj.weak_unref(on_object_gone);
-        });
-    }
-
-    public async void load () throws GLib.Error {
+    public async void load() throws GLib.Error {
         // avoid reentering load () from a different request
         while (!this.loaded && get_n_loading() > 0) {
             var wait = notify["loaded"].connect (() => {
@@ -72,9 +73,9 @@ public class Seahorse.SearchProvider : GLib.Object {
                 return;
         }
 
-        var backends = Backend.get_registered();
-        this.n_loading = (int) backends.length();
-        foreach (var backend in backends) {
+        this.n_loading = (int) this.backends.get_n_items();
+        for (uint i = 0; i < backends.get_n_items(); i++) {
+            var backend = (Backend) backends.get_item(i);
             if (!backend.loaded) {
                 backend.notify["loaded"].connect(() => {
                     change_n_loading(-1);
@@ -84,12 +85,6 @@ public class Seahorse.SearchProvider : GLib.Object {
             } else {
                 change_n_loading(-1);
             }
-
-            backend.added.connect(on_place_added);
-            backend.removed.connect(on_place_removed);
-
-            foreach (GLib.Object place in backend.get_objects())
-                on_place_added(backend, place);
         }
     }
 
@@ -113,7 +108,9 @@ public class Seahorse.SearchProvider : GLib.Object {
             yield load();
 
         string?[] results = {};
-        foreach (unowned GLib.Object obj in this.collection.get_objects()) {
+        for (uint i = 0; i < this.collection.get_n_items(); i++) {
+            var obj = this.collection.get_item(i);
+
             if (object_matches_search(obj, terms)) {
                 string str = "%p".printf(obj);
 
@@ -136,7 +133,7 @@ public class Seahorse.SearchProvider : GLib.Object {
         string?[] results = {};
         foreach (string previous_result in previous_results) {
             unowned GLib.Object? object = this.handles.lookup(previous_result);
-            if (object == null || !this.collection.contains(object))
+            if (object == null)
                 continue; // Bogus value
 
             if (object_matches_search(object, new_terms))
@@ -154,7 +151,7 @@ public class Seahorse.SearchProvider : GLib.Object {
         int good_results = 0;
         foreach (unowned string result in results) {
             unowned GLib.Object object = this.handles.lookup(result);
-            if (object == null || !(object in this.collection))
+            if (object == null)
                 continue; // Bogus value
 
             HashTable<string, Variant> meta = new HashTable<string, Variant>(str_hash, str_equal);
@@ -189,7 +186,7 @@ public class Seahorse.SearchProvider : GLib.Object {
         unowned GLib.Object? object = null;
         identifier.scanf("%p", &object);
         object = this.handles.lookup(identifier);
-        if (object == null || !(object in this.collection) || !(object is Viewable))
+        if (object == null || !(object is Viewable))
             return; // Bogus value
 
         KeyManager key_manager = new KeyManager(GLib.Application.get_default() as Application);
@@ -230,18 +227,6 @@ public class Seahorse.SearchProvider : GLib.Object {
         this.handles.remove("%p".printf(where_the_object_was));
     }
 
-    private void on_place_added (Gcr.Collection places, GLib.Object object) {
-        unowned var place = (Place) object;
-        if (!this.union_collection.have(place))
-            this.union_collection.add(place);
-    }
-
-    private void on_place_removed (Gcr.Collection places, GLib.Object object) {
-        unowned var place = (Place) object;
-        if (this.union_collection.have(place))
-            this.union_collection.remove(place);
-    }
-
     private static string? get_description_if_available (GLib.Object? obj) {
         if (obj == null)
             return null;
diff --git a/src/sidebar.vala b/src/sidebar.vala
index 5bdda3bb..11750f63 100644
--- a/src/sidebar.vala
+++ b/src/sidebar.vala
@@ -18,97 +18,47 @@
  * <http://www.gnu.org/licenses/>.
  */
 
-public class Seahorse.Sidebar : Gtk.ListBox {
+public class Seahorse.Sidebar : Adw.Bin {
 
-    private GLib.ListStore store = new GLib.ListStore(typeof(Seahorse.Place));
-    private List<Backend> backends = new List<Backend>();
+    private unowned Gtk.ListBox list_box;
 
-    /**
-     * Collection of objects sidebar represents
-     */
-    public Gcr.UnionCollection objects { get; private set; default = new Gcr.UnionCollection(); }
+    private Gtk.FlattenListModel places;
 
-    /**
-     * Emitted when the state of the current collection changed.
-     * For example: when going from locked to unlocked and vice versa.
-     */
-    public signal void current_collection_changed();
+    public Gtk.SingleSelection selection { get; private set; }
 
     construct {
-        this.selection_mode = Gtk.SelectionMode.BROWSE;
-
-        bind_model(this.store, place_widget_create_cb);
-        set_header_func(place_header_cb);
-        this.row_selected.connect(on_row_selected);
-
-        load_backends();
-    }
-
-    ~Sidebar() {
-        foreach (Backend backend in this.backends)
-            foreach (weak GLib.Object obj in backend.get_objects())
-                on_place_removed (backend, (Place) obj);
-    }
-
-    private void load_backends() {
-        foreach (Backend backend in Backend.get_registered()) {
-            this.backends.append(backend);
-            backend.added.connect(on_place_added);
-            backend.removed.connect(on_place_removed);
-            backend.notify.connect(on_backend_changed);
-
-            foreach (weak GLib.Object obj in backend.get_objects())
-                on_place_added(backend, obj);
-        }
-    }
-
-    private void on_place_added(Gcr.Collection? backend, GLib.Object place_obj) {
-        var place = place_obj as Place;
-        return_if_fail (place != null);
+        var listbox = new Gtk.ListBox();
+        this.child = listbox;
+        this.list_box = listbox;
+        listbox.add_css_class("navigation-sidebar");
+        listbox.selection_mode = Gtk.SelectionMode.BROWSE;
 
-        debug("New place '%s' added", place.label);
+        // ListModels everywhere \o/
+        this.places = new Gtk.FlattenListModel(Backend.get_registered());
 
-        if (place.get_length() > 0 || place.show_if_empty)
-            this.store.insert_sorted(place, compare_places);
-        place.notify.connect(on_place_changed);
-    }
+        var sorter = new Gtk.CustomSorter(compare_places);
+        var places_sorted = new Gtk.SortListModel(this.places, sorter);
 
-    private void on_place_changed(GLib.Object obj, ParamSpec pspec) {
-        update_places();
-    }
+        this.selection = new Gtk.SingleSelection(places_sorted);
 
-    private void on_place_removed(Gcr.Collection? backend, GLib.Object place_obj) {
-        var place = place_obj as Place;
-        return_if_fail (place != null);
+        // Listbox stuff
+        listbox.bind_model(this.selection, place_widget_create_cb);
+        listbox.set_header_func(place_header_cb);
+        listbox.row_selected.connect(on_row_selected);
 
-        debug("Place '%s' removed", place.label);
-        for (uint i = 0; i < this.store.get_n_items(); i++) {
-            if (this.store.get_item(i) == place) {
-                this.store.remove(i);
-                break;
-            }
-        }
-    }
-
-    private void on_backend_changed(GLib.Object obj, ParamSpec spec) {
-        Backend backend = (Backend) obj;
-        debug("Backend '%s' changed", backend.label);
-        update_places();
+        // Right click
+        var secondary_click_gesture = new Gtk.GestureClick ();
+        secondary_click_gesture.button = Gdk.BUTTON_SECONDARY;
+        secondary_click_gesture.pressed.connect (on_right_click);
+        listbox.add_controller (secondary_click_gesture);
     }
 
     private Gtk.Widget place_widget_create_cb(GLib.Object object) {
-        var item = new SidebarItem(object as Seahorse.Place);
-        item.place_changed.connect(on_sidebar_item_changed);
-        return item;
-    }
-
-    private void on_sidebar_item_changed(SidebarItem item) {
-        select_row(item);
-        current_collection_changed();
+        return new SidebarItem(object as Seahorse.Place);
     }
 
     private void place_header_cb(Gtk.ListBoxRow row, Gtk.ListBoxRow? before) {
-        Seahorse.Place place = ((SidebarItem) row).place;
+        unowned var place = ((SidebarItem) row).place;
 
         // We need a title iff
         // the previous row exists, and it's part of the same category
@@ -121,7 +71,7 @@ public class Seahorse.Sidebar : Gtk.ListBox {
         }
 
         var label = new Gtk.Label(place.category.to_string());
-        label.get_style_context().add_class("seahorse-sidebar-item-header");
+        label.add_css_class("seahorse-sidebar-item-header");
         label.xalign = 0f;
         label.margin_start = 9;
         label.margin_top = 6;
@@ -130,9 +80,9 @@ public class Seahorse.Sidebar : Gtk.ListBox {
         row.set_header(label);
     }
 
-    private int compare_places(GLib.Object obj_a, GLib.Object obj_b) {
-        Seahorse.Place a = (Seahorse.Place) obj_a;
-        Seahorse.Place b = (Seahorse.Place) obj_b;
+    private int compare_places(GLib.Object? obj_a, GLib.Object? obj_b) {
+        unowned var a = (Seahorse.Place) obj_a;
+        unowned var b = (Seahorse.Place) obj_b;
 
         // First of all, order the categories
         if (a.category != b.category)
@@ -143,118 +93,33 @@ public class Seahorse.Sidebar : Gtk.ListBox {
     }
 
     private void on_row_selected(Gtk.ListBoxRow? row) {
-        debug("Updating objects");
+        debug("Updating selected");
 
-        // First clear the list
-        foreach (var place in this.objects.elements())
-            this.objects.remove(place);
-
-        // Only selected ones should be in this.objects
-        var selected = row as SidebarItem;
-        if (selected == null)
+        if (row == null) {
+            this.selection.unselect_all();
             return;
-
-        foreach (var place in this.objects.elements()) {
-            if (selected.place != place)
-                this.objects.remove(place);
         }
-        if (!this.objects.have(selected.place))
-            this.objects.add(selected.place);
-    }
 
-    private void update_places() {
-        // Save current selection
-        var old_selected = get_selected_row() as SidebarItem;
-        Place? place = null;
-        if (old_selected != null)
-            place = old_selected.place;
-
-        foreach (Backend backend in this.backends)
-            update_backend(backend);
-
-        // Needed when you have multiple keyrings (this can lead
-        // to multiple 'Password' titles).
-        invalidate_headers();
+        this.selection.selected = row.get_index();
     }
 
-    private void update_backend(Backend? backend) {
-        if (backend.get_objects() == null) // Ignore categories that have nothing
-            return;
-
-        foreach (weak GLib.Object obj in backend.get_objects()) {
-            unowned Place? place = obj as Place;
-            if (place == null)
-                continue;
-
-            bool already_in = false;
-            for (int i = 0; i < this.store.get_n_items(); i++) {
-                if (this.store.get_object(i) == place) {
-                    already_in = true;
-                    break;
-                }
-            }
-
-            if (!already_in && (place.get_length() > 0 || place.show_if_empty))
-                this.store.insert_sorted(place, compare_places);
-        }
-    }
-
-    public override bool popup_menu() {
-        var row = get_selected_row() as SidebarItem;
-        if (row == null)
-            return false;
-
-        row.show_popup_menu();
-        return true;
-    }
-
-    public override bool button_press_event(Gdk.EventButton event) {
-        if (base.button_press_event(event))
-            return true;
-
-        if (event.button != 3 || event.type != Gdk.EventType.BUTTON_PRESS)
-            return false;
-
-        var row = get_row_at_y((int) event.y) as SidebarItem;
+    private void on_right_click(Gtk.GestureClick gesture, int n_press, double x, double y) {
+        var row = this.list_box.get_row_at_y((int) y) as SidebarItem;
         if (row != null)
-            row.show_popup_menu();
-
-        return true;
+            row.show_popup_menu(null);// XXX
     }
 
-    public List<weak Gcr.Collection>? get_selected_places() {
-        List<weak Gcr.Collection>? places = null;
-
-        foreach (var row in get_selected_rows()) {
-            var item = row as SidebarItem;
-            if (item != null)
-                places.append(item.place);
-        }
-
-        return places;
-    }
-
-    public Place? get_focused_place() {
-        var row = get_selected_row() as SidebarItem;
-        return (row != null)? row.place : null;
-    }
-
-    /**
-     * Tries to find a place with the given URI and selects it.
-     *
-     * @return Whether a matching place was found
-     */
-    public bool set_focused_place_for_uri(string uri) {
-        // First, try to see if we have an exact match
-        foreach (var row in get_children()) {
-            var item = (SidebarItem) row;
-            if (item.place.uri == uri) {
-                select_row(item);
-                return true;
+    public Place? lookup_place_for_uri(string uri, out uint pos) {
+        for (uint i = 0; i < this.selection.get_n_items(); i++) {
+            var place = (Place) this.selection.get_item(i);
+            if (place.uri == uri) {
+                pos = i;
+                return place;
             }
         }
 
-        return false;
+        pos = 0;
+        return null;
     }
 
     /**
@@ -263,10 +128,10 @@ public class Seahorse.Sidebar : Gtk.ListBox {
      * @return Whether a matching place was found
      */
     public bool set_focused_place_for_scheme(string uri_scheme) {
-        foreach (var row in get_children()) {
-            var item = (SidebarItem) row;
-            if (item.place.uri.has_prefix(uri_scheme)) {
-                select_row(item);
+        for (uint i = 0; i < this.selection.get_n_items(); i++) {
+            var place = (Place) this.selection.get_item(i);
+            if (place.uri.has_prefix(uri_scheme)) {
+                this.selection.selected = i;
                 return true;
             }
         }
@@ -283,187 +148,99 @@ public class Seahorse.Sidebar : Gtk.ListBox {
 
     // Selects the item that is n positions lower/above the current selection
     private void select_relative(int positions) {
-        var selected = get_selected_row();
-        if (selected == null) {
+        if (this.selection.selected == Gtk.INVALID_LIST_POSITION) {
             // Select the first row
-            select_row(get_row_at_index(0));
+            this.selection.selected = 0;
             return;
         }
 
-        var next = get_row_at_index(selected.get_index() + positions);
-        // If we're at the top/bottom of the list, don't do anything
-        if (next != null)
-            select_row(next);
-    }
-
-    public List<weak Backend>? get_backends() {
-        return this.backends.copy();
+        this.selection.selected += positions;
     }
 }
 
 internal class Seahorse.SidebarItem : Gtk.ListBoxRow {
 
-    private Gtk.Button? lock_button = null;
-
     public weak Seahorse.Place place { get; construct set; }
 
-    public signal void place_changed();
-
     construct {
-      var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
-      box.get_style_context().add_class("seahorse-sidebar-item");
-      box.valign = Gtk.Align.CENTER;
-      box.margin_top = 3;
-      box.margin_bottom = 3;
-      add(box);
-
-      var icon = new Gtk.Image.from_gicon(place.icon, Gtk.IconSize.BUTTON);
-      box.pack_start(icon, false, false);
-
-      var label = new Gtk.Label(place.label);
-      label.ellipsize = Pango.EllipsizeMode.END;
-      label.xalign = 0f;
-      box.pack_start(label);
-
-      var lockable = place as Lockable;
-      if (lockable != null && (lockable.lockable || lockable.unlockable)) {
-          this.lock_button = new Gtk.Button.from_icon_name(get_lock_icon_name(lockable),
-                                                          Gtk.IconSize.BUTTON);
-          this.lock_button.get_style_context().add_class("flat");
-          this.lock_button.halign = Gtk.Align.END;
-          this.lock_button.clicked.connect((b) => {
-              if (lockable.unlockable)
-                  place_unlock(lockable, (Gtk.Window) get_toplevel());
-              else if (lockable.lockable)
-                  place_lock(lockable, (Gtk.Window) get_toplevel());
-
-              update_lock_icon(lockable);
-          });
-          box.pack_end(this.lock_button, false, false);
-      }
-
-      show_all();
+        var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
+        box.add_css_class("seahorse-sidebar-item");
+        box.valign = Gtk.Align.CENTER;
+        box.margin_top = 3;
+        box.margin_bottom = 3;
+        set_child(box);
+
+        var label = new Gtk.Label(this.place.label);
+        label.ellipsize = Pango.EllipsizeMode.END;
+        label.xalign = 0f;
+        label.hexpand = true;
+        box.append(label);
+
+        unowned var lockable = this.place as Lockable;
+        if (lockable != null && (lockable.lockable || lockable.unlockable)) {
+            var lock_button = new Gtk.Button();
+            lock_button.add_css_class("flat");
+            lock_button.halign = Gtk.Align.END;
+            update_lock_button(lock_button);
+            this.place.notify.connect((obj, pspec) => {
+                update_lock_button(lock_button);
+            });
+            box.append(lock_button);
+        }
     }
 
-    private static unowned string? get_lock_icon_name(Lockable lockable) {
-          if (lockable.unlockable)
-              return "changes-prevent-symbolic";
-
-          if (lockable.lockable)
-              return "changes-allow-symbolic";
+    private void update_lock_button(Gtk.Button lock_button) {
+        unowned var lockable = this.place as Lockable;
 
-          return null;
-    }
-
-    private void update_lock_icon(Lockable lockable) {
-        ((Gtk.Image) this.lock_button.get_image()).icon_name = get_lock_icon_name(lockable);
+        if (lockable.unlockable) {
+            lock_button.icon_name = "changes-prevent-symbolic";
+            lock_button.tooltip_text = _("Unlock collection %s").printf(place.label);
+            lock_button.action_name = "win.unlock-place";
+            lock_button.action_target = new Variant.string(place.uri);
+        } else if (lockable.lockable) {
+            lock_button.icon_name = "changes-allow-symbolic";
+            lock_button.tooltip_text = _("Lock collection %s").printf(place.label);
+            lock_button.action_name = "win.lock-place";
+            lock_button.action_target = new Variant.string(place.uri);
+        }
     }
 
     public SidebarItem(Seahorse.Place place) {
         GLib.Object(place: place);
     }
 
-    public void show_popup_menu() {
+    public void show_popup_menu(Gdk.Rectangle? rect) {
+        // XXX
+        var model = new GLib.Menu();
+
         // Start from the menu model provided by the this.place (if any)
-        var menu = (this.place.menu_model != null)? new Gtk.Menu.from_model(this.place.menu_model)
-                                                  : new Gtk.Menu();
+        if (this.place.menu_model != null)
+            model.append_section(null, this.place.menu_model);
 
-        // Make sure the actions from the collection
-        if (this.place.actions != null)
-            menu.insert_action_group(this.place.action_prefix, this.place.actions);
+        var uri = place.uri;
 
         // Lock and unlock items
         if (this.place is Lockable) {
-            Gtk.MenuItem item = new Gtk.MenuItem.with_mnemonic(_("_Lock"));
-            item.activate.connect(() => on_place_lock(item, (Lockable) this.place));
-            this.place.bind_property("lockable", item, "visible", BindingFlags.SYNC_CREATE);
-            menu.append(item);
-
-            item = new Gtk.MenuItem.with_mnemonic(_("_Unlock"));
-            item.activate.connect(() => on_place_unlock(item, (Lockable) this.place));
-            this.place.bind_property("unlockable", item, "visible", BindingFlags.SYNC_CREATE);
-            menu.append(item);
+            if (((Lockable) this.place).lockable)
+                model.append(_("_Lock"), "win.lock-place('%s')".printf(uri));
+
+            if (((Lockable) this.place).unlockable)
+                model.append(_("_Unlock"), "win.unlock-place('%s')".printf(uri));
         }
 
         // Delete item
-        if (this.place is Deletable) {
-            Gtk.MenuItem item = new Gtk.MenuItem.with_mnemonic(_("_Delete"));
-            item.activate.connect(() => on_place_delete(item, (Deletable) this.place));
-            this.place.bind_property("deletable", item, "sensitive", BindingFlags.SYNC_CREATE);
-            menu.append(item);
-            item.show();
-        }
+        if (this.place is Deletable && ((Deletable) this.place).deletable)
+            model.append(_("_Delete"), "win.delete-place('%s')".printf(uri));
 
         // Properties item
-        if (this.place is Viewable) {
-            Gtk.MenuItem item = new Gtk.MenuItem.with_mnemonic(_("_Properties"));
-            item.activate.connect(() => Viewable.view(this.place, (Gtk.Window) item.get_toplevel()));
-            menu.append(item);
-            item.show();
-        }
-
-        bool visible = false;
-        menu.foreach((widget) => {
-            visible |= widget.visible;
-        });
-
-        if (visible) {
-            menu.popup_at_pointer();
-            menu.attach_to_widget(this, null);
-            menu.show();
-        } else {
-            menu.destroy();
-        }
-    }
-
-    private void place_lock(Lockable lockable, Gtk.Window? window) {
-        Cancellable cancellable = new Cancellable();
-        TlsInteraction interaction = new Interaction(window);
-
-        lockable.lock.begin(interaction, cancellable, (obj, res) => {
-            try {
-                lockable.lock.end(res);
-                update_lock_icon(lockable);
-                place_changed();
-            } catch (Error e) {
-                Util.show_error(window, _("Couldn’t lock"), e.message);
-            }
-        });
-    }
+        if (this.place is Viewable)
+            model.append(_("_Properties"), "win.view-place('%s')".printf(uri));
 
-    private void on_place_lock(Gtk.Widget widget, Lockable lockable) {
-        place_lock(lockable, (Gtk.Window) widget.get_toplevel());
-    }
-
-    private void place_unlock(Lockable lockable, Gtk.Window? window) {
-        Cancellable cancellable = new Cancellable();
-        TlsInteraction interaction = new Interaction(window);
-
-        lockable.unlock.begin(interaction, cancellable, (obj, res) => {
-            try {
-                lockable.unlock.end(res);
-                update_lock_icon(lockable);
-                place_changed();
-            } catch (Error e) {
-                Util.show_error(window, _("Couldn’t unlock"), e.message);
-            }
-        });
-    }
-
-    private void on_place_unlock(Gtk.MenuItem widget, Lockable lockable) {
-        place_unlock(lockable, (Gtk.Window) widget.get_toplevel());
-    }
-
-    private void on_place_delete(Gtk.MenuItem item, Deletable deletable) {
-        Deleter deleter = deletable.create_deleter();
-        if (deleter.prompt((Gtk.Window) item.get_toplevel())) {
-            deleter.delete.begin(null, (obj, res) => {
-                try {
-                    deleter.delete.end(res);
-                } catch (Error e) {
-                    Util.show_error(parent, _("Couldn’t delete"), e.message);
-                }
-            });
-        }
+        var popover = new Gtk.PopoverMenu.from_model(model);
+        popover.set_parent(this);
+        popover.set_pointing_to(rect);
+        if (this.place.actions != null)
+            popover.insert_action_group(this.place.action_prefix, this.place.actions);
+        popover.popup();
     }
 }
diff --git a/ssh/actions.vala b/ssh/actions.vala
index 6aad1ee5..df518d64 100644
--- a/ssh/actions.vala
+++ b/ssh/actions.vala
@@ -60,16 +60,19 @@ public class Seahorse.Ssh.Actions : ActionGroup {
         var dialog = new Generate(Backend.instance.get_dot_ssh(),
                                   this.catalog);
 
-        if (dialog.run() != Gtk.ResponseType.ACCEPT) {
-            dialog.destroy();
-            return;
-        }
+        dialog.response.connect((response) => {
+            if (response != Gtk.ResponseType.ACCEPT) {
+                dialog.destroy();
+                return;
+            }
 
-        dialog.generate_key.begin ((obj, res) => {
-            dialog.generate_key.end (res);
-            dialog.destroy();
-            this.catalog.activate_action("focus-place", "openssh");
+            dialog.generate_key.begin ((obj, res) => {
+                dialog.generate_key.end (res);
+                dialog.destroy();
+                this.catalog.activate_action("focus-place", "openssh");
+            });
         });
+        dialog.show();
     }
 
     private void on_ssh_upload(SimpleAction action, Variant? param) {
diff --git a/ssh/backend.vala b/ssh/backend.vala
index 84ea529a..4cedb182 100644
--- a/ssh/backend.vala
+++ b/ssh/backend.vala
@@ -19,7 +19,7 @@
  * <http://www.gnu.org/licenses/>.
  */
 
-public class Seahorse.Ssh.Backend : GLib.Object, Gcr.Collection, Seahorse.Backend {
+public class Seahorse.Ssh.Backend : GLib.Object, GLib.ListModel, Seahorse.Backend {
 
     private Source dot_ssh;
 
@@ -48,20 +48,16 @@ public class Seahorse.Ssh.Backend : GLib.Object, Gcr.Collection, Seahorse.Backen
 
     public static Backend? instance { get; internal set; default = null; }
 
-    public uint get_length() {
-        return 1;
+    public GLib.Type get_item_type() {
+        return typeof(Ssh.Source);
     }
 
-    public List<weak GLib.Object> get_objects() {
-        List<weak GLib.Object> list = new List<weak GLib.Object>();
-        list.append(this.dot_ssh);
-        return list;
+    public uint get_n_items() {
+        return 1;
     }
 
-    public bool contains(GLib.Object object) {
-        Source? src = object as Source;
-
-        return (src != null) && (this.dot_ssh == src);
+    public GLib.Object? get_item(uint position) {
+        return (position == 0)? this.dot_ssh : null;
     }
 
     public Seahorse.Place? lookup_place(string uri) {
diff --git a/ssh/deleter.vala b/ssh/deleter.vala
index e2e1221b..80e248bd 100644
--- a/ssh/deleter.vala
+++ b/ssh/deleter.vala
@@ -31,7 +31,7 @@ public class Seahorse.Ssh.Deleter : Seahorse.Deleter {
             assert_not_reached ();
     }
 
-    public override Gtk.Dialog create_confirm(Gtk.Window? parent) {
+    public override Gtk.Window create_confirm(Gtk.Window? parent) {
         uint num = this.keys.length();
 
         string confirm, prompt;
diff --git a/ssh/exporter.vala b/ssh/exporter.vala
index e9499493..b71aa1fe 100644
--- a/ssh/exporter.vala
+++ b/ssh/exporter.vala
@@ -49,11 +49,11 @@ public class Seahorse.Ssh.Exporter : GLib.Object, Seahorse.Exporter {
             Gtk.FileFilter filter = new Gtk.FileFilter();
 
             if (this.secret) {
-                filter.set_name(_("Secret SSH keys"));
+                filter.name = _("Secret SSH keys");
                 filter.add_mime_type("application/x-pem-key");
                 filter.add_pattern("id_*");
             } else {
-                filter.set_name(_("Public SSH keys"));
+                filter.name = _("Public SSH keys");
                 filter.add_mime_type("application/x-ssh-key");
                 filter.add_pattern("*.pub");
             }
diff --git a/ssh/generate.vala b/ssh/generate.vala
index e797d779..b1cee01a 100644
--- a/ssh/generate.vala
+++ b/ssh/generate.vala
@@ -27,12 +27,12 @@ public class Seahorse.Ssh.Generate : Gtk.Dialog {
     public Ssh.Source source { get; construct set; }
 
     [GtkChild]
-    private unowned Gtk.Grid details_grid;
+    private unowned Adw.ActionRow key_strength_row;
     private KeyLengthChooser key_length_chooser;
     [GtkChild]
-    private unowned Gtk.Entry email_entry;
+    private unowned Adw.EntryRow email_row;
     [GtkChild]
-    private unowned Gtk.ComboBoxText algorithm_combo_box;
+    private unowned Adw.ComboRow algo_row;
 
     public Generate(Source src, Gtk.Window parent) {
         GLib.Object (
@@ -44,16 +44,16 @@ public class Seahorse.Ssh.Generate : Gtk.Dialog {
         this.source = src;
 
         this.key_length_chooser = new KeyLengthChooser();
-        this.key_length_chooser.halign = Gtk.Align.START;
-        this.details_grid.attach(this.key_length_chooser, 1, 3);
+        this.key_length_chooser.valign = Gtk.Align.CENTER;
+        this.key_strength_row.add_suffix(this.key_length_chooser);
 
         // on_algo_changed() gets called, bits chooser is setup
-        algorithm_combo_box.set_active(0);
+        this.algo_row.selected = 0;
     }
 
     [GtkCallback]
-    private void on_algo_changed(Gtk.ComboBox combo) {
-        string t = algorithm_combo_box.get_active_text();
+    private void on_algo_row_selected_changed(GLib.Object obj, ParamSpec pspec) {
+        var t = ((Gtk.StringObject) this.algo_row.selected_item).string;
         this.key_length_chooser.algorithm = Algorithm.from_string(t);
     }
 
@@ -65,10 +65,10 @@ public class Seahorse.Ssh.Generate : Gtk.Dialog {
      */
     public async void generate_key() {
         // The email address
-        string email = this.email_entry.text;
+        string email = this.email_row.text;
 
         // The algorithm
-        string t = this.algorithm_combo_box.get_active_text();
+        var t = ((Gtk.StringObject) this.algo_row.selected_item).string;
         Algorithm type = Algorithm.from_string(t);
         assert(type != Algorithm.UNKNOWN);
 
diff --git a/ssh/key-length-chooser.vala b/ssh/key-length-chooser.vala
index be65e4fc..e8850b80 100644
--- a/ssh/key-length-chooser.vala
+++ b/ssh/key-length-chooser.vala
@@ -26,35 +26,31 @@
  * Please also refer to the man pages of ssh-keygen to know why some
  * some restrictions are added.
  */
-public class Seahorse.Ssh.KeyLengthChooser : Gtk.Stack {
+public class Seahorse.Ssh.KeyLengthChooser : Adw.Bin {
 
     private Gtk.SpinButton spin_button;  // For RSA
-    private Gtk.ComboBoxText combobox;   // For ECDSA
+    private Gtk.DropDown dropdown;   // For ECDSA
     private Gtk.Label not_supported;     // For DSA, ED25519 and unknowns
 
     public Algorithm algorithm { get; set; }
 
     public KeyLengthChooser(Algorithm algo = Algorithm.RSA) {
-        this.visible = true;
+        var stack = new Gtk.Stack();
+        this.child = stack;
 
         // RSA
         var rsa_size_adj = new Gtk.Adjustment(2048, 1024, 8193, 256, 10, 1);
         this.spin_button = new Gtk.SpinButton(rsa_size_adj, 1, 0);
-        add(this.spin_button);
+        stack.add_child(this.spin_button);
 
         // ECDSA
-        this.combobox = new Gtk.ComboBoxText();
-        this.combobox.append_text("256");
-        this.combobox.append_text("384");
-        this.combobox.append_text("521");
-        this.combobox.active = 0;
-        add(this.combobox);
+        this.dropdown = new Gtk.DropDown.from_strings({ "256", "384", "521" });
+        this.dropdown.selected = 0;
+        stack.add_child(this.dropdown);
 
         // DSA, ED25519, and Unknown
         this.not_supported = new Gtk.Label(null);
-        add(this.not_supported);
-
-        show_all();
+        stack.add_child(this.not_supported);
 
         // Now set the initial algorithm
         this.notify["algorithm"].connect(on_algo_changed);
@@ -72,7 +68,8 @@ public class Seahorse.Ssh.KeyLengthChooser : Gtk.Stack {
             case Algorithm.DSA:
                 return 1024;
             case Algorithm.ECDSA:
-                return int.parse(this.combobox.get_active_text());
+                var str = ((Gtk.StringObject) this.dropdown.selected_item).string;
+                return int.parse(str);
             case Algorithm.ED25519:
                 return 256;
             default:
@@ -81,24 +78,26 @@ public class Seahorse.Ssh.KeyLengthChooser : Gtk.Stack {
     }
 
     private void on_algo_changed (GLib.Object src, ParamSpec pspec) {
+        unowned var stack = (Gtk.Stack) this.child;
+
         switch (this.algorithm) {
             case Algorithm.RSA:
-                this.visible_child = this.spin_button;
+                stack.visible_child = this.spin_button;
                 break;
             case Algorithm.ECDSA:
-                this.visible_child = this.combobox;
+                stack.visible_child = this.dropdown;
                 break;
             case Algorithm.DSA:
                 this.not_supported.label = _("1024 bits");
-                this.visible_child = this.not_supported;
+                stack.visible_child = this.not_supported;
                 break;
             case Algorithm.ED25519:
                 this.not_supported.label = _("256 bits");
-                this.visible_child = this.not_supported;
+                stack.visible_child = this.not_supported;
                 break;
             case Algorithm.UNKNOWN:
                 this.not_supported.label = _("Unknown key type!");
-                this.visible_child = this.not_supported;
+                stack.visible_child = this.not_supported;
                 break;
         }
     }
diff --git a/ssh/key-properties.vala b/ssh/key-properties.vala
index 102940d5..a347bb96 100644
--- a/ssh/key-properties.vala
+++ b/ssh/key-properties.vala
@@ -28,7 +28,7 @@ public class Seahorse.Ssh.KeyProperties : Gtk.Dialog {
     private bool updating_ui = false;
 
     [GtkChild]
-    private unowned Gtk.Entry comment_entry;
+    private unowned Adw.EntryRow comment_row;
     [GtkChild]
     private unowned Gtk.Switch trust_check;
     [GtkChild]
@@ -71,7 +71,7 @@ public class Seahorse.Ssh.KeyProperties : Gtk.Dialog {
         this.updating_ui = true;
 
         // Name and title
-        this.comment_entry.text = this.key.label;
+        this.comment_row.text = this.key.label;
 
         // Setup the check
         this.trust_check.active = (this.key.trust >= Seahorse.Validity.FULL);
@@ -90,7 +90,7 @@ public class Seahorse.Ssh.KeyProperties : Gtk.Dialog {
     }
 
     [GtkCallback]
-    public void on_ssh_comment_activate(Gtk.Entry entry) {
+    public void on_ssh_comment_apply(Adw.EntryRow entry) {
         // Make sure not the same
         if (key.key_data.comment != null && entry.text == key.key_data.comment)
             return;
@@ -110,12 +110,6 @@ public class Seahorse.Ssh.KeyProperties : Gtk.Dialog {
         });
     }
 
-    [GtkCallback]
-    private bool on_ssh_comment_focus_out(Gtk.Widget widget, Gdk.EventFocus event) {
-        on_ssh_comment_activate((Gtk.Entry) widget);
-        return false;
-    }
-
     [GtkCallback]
     private void on_ssh_trust_changed(GLib.Object button, GLib.ParamSpec p) {
         if (updating_ui)
@@ -154,24 +148,28 @@ public class Seahorse.Ssh.KeyProperties : Gtk.Dialog {
     [GtkCallback]
     private void on_delete_clicked(Gtk.Button button) {
         var deleter = this.key.create_deleter();
-        var ret = deleter.prompt(this);
 
-        if (!ret)
-            return;
-
-        deleter.delete.begin(null, (obj, res) => {
-            try {
-                deleter.delete.end(res);
-                this.destroy();
-            } catch (GLib.Error e) {
-                var dialog = new Gtk.MessageDialog(this,
-                    Gtk.DialogFlags.MODAL,
-                    Gtk.MessageType.ERROR,
-                    Gtk.ButtonsType.OK,
-                    _("Error deleting the SSH key."));
-                dialog.run();
-                dialog.destroy();
+        var prompt = deleter.create_confirm(this);
+        //XXX
+        ((Gtk.Dialog) prompt).response.connect((response) => {
+            if (response != Gtk.ResponseType.ACCEPT) {
+                prompt.destroy();
+                return;
             }
+            prompt.destroy();
+
+            deleter.delete.begin(null, (obj, res) => {
+                try {
+                    deleter.delete.end(res);
+                    this.destroy();
+                } catch (GLib.Error e) {
+                    var dialog = new Gtk.MessageDialog(this,
+                        Gtk.DialogFlags.MODAL,
+                        Gtk.MessageType.ERROR,
+                        Gtk.ButtonsType.OK,
+                        _("Error deleting the SSH key."));
+                }
+            });
         });
     }
 
@@ -196,9 +194,6 @@ public class Seahorse.Ssh.KeyProperties : Gtk.Dialog {
 
     [GtkCallback]
     private void on_ssh_copy_button_clicked (Gtk.Widget widget) {
-        var display = widget.get_display ();
-        var clipboard = Gtk.Clipboard.get_for_display (display, Gdk.SELECTION_CLIPBOARD);
-
-        clipboard.set_text(this.key.pubkey, -1);
+        widget.get_clipboard().set_text(this.key.pubkey);
     }
 }
diff --git a/ssh/key.vala b/ssh/key.vala
index e2ebc30b..0e84f82d 100644
--- a/ssh/key.vala
+++ b/ssh/key.vala
@@ -136,11 +136,11 @@ public class Seahorse.Ssh.Key : Seahorse.Object, Seahorse.Exportable, Seahorse.D
         if (this.key_data.privfile != null) {
             this.usage = Seahorse.Usage.PRIVATE_KEY;
             this.object_flags |= Seahorse.Flags.PERSONAL | Seahorse.Flags.TRUSTED;
-            this.icon = new ThemedIcon(Gcr.ICON_KEY_PAIR);
+            // this.icon = new ThemedIcon(Gcr.ICON_KEY_PAIR); XXX
         } else {
             this.object_flags = 0;
             this.usage = Seahorse.Usage.PUBLIC_KEY;
-            this.icon = new ThemedIcon(Gcr.ICON_KEY);
+            // this.icon = new ThemedIcon(Gcr.ICON_KEY); XXX
         }
 
         string filename = Path.get_basename(this.key_data.privfile ?? this.key_data.pubfile);
diff --git a/ssh/meson.build b/ssh/meson.build
index 15418772..5d6bb093 100644
--- a/ssh/meson.build
+++ b/ssh/meson.build
@@ -18,10 +18,9 @@ ssh_sources = files(
 
 ssh_dependencies = [
   glib_deps,
-  gcr,
-  gcr_ui,
+  gcr4_dep,
   posix,
-  gtk,
+  gtk4_dep,
   common_dep,
 ]
 
@@ -46,8 +45,8 @@ ssh_askpass_sources = files(
 )
 
 ssh_askpass_dependencies = [
-  gcr,
-  gtk,
+  gcr4_dep,
+  gtk4_dep,
   config,
   common_dep,
 ]
diff --git a/ssh/operation.vala b/ssh/operation.vala
index afa161ce..fa06529f 100644
--- a/ssh/operation.vala
+++ b/ssh/operation.vala
@@ -36,7 +36,6 @@ public abstract class Operation : GLib.Object {
     protected string? prompt_title;
     protected string? prompt_message;
     protected string? prompt_argument;
-    protected ulong prompt_transient_for;
 
     /**
      * Calls a command and returns the output.
@@ -77,10 +76,6 @@ public abstract class Operation : GLib.Object {
             launcher.setenv("SEAHORSE_SSH_ASKPASS_TITLE", prompt_title, true);
         if (this.prompt_message != null)
             launcher.setenv("SEAHORSE_SSH_ASKPASS_MESSAGE", prompt_message, true);
-        if (this.prompt_transient_for != 0) {
-            string parent = "%lu".printf(prompt_transient_for);
-            launcher.setenv("SEAHORSE_SSH_ASKPASS_PARENT", parent, true);
-        }
 
         // And off we go to run the program
         var subprocess = launcher.spawnv(args);
diff --git a/ssh/seahorse-ssh-askpass.c b/ssh/seahorse-ssh-askpass.c
index b187fb9f..dbcc5f8c 100644
--- a/ssh/seahorse-ssh-askpass.c
+++ b/ssh/seahorse-ssh-askpass.c
@@ -32,113 +32,113 @@
 #include <glib/gi18n.h>
 #include <gtk/gtk.h>
 
-#ifdef GDK_WINDOWING_X11
-#include <gdk/gdkx.h>
-#endif
+static SeahorsePassphrasePrompt *dialog = NULL;
+static int response = GTK_RESPONSE_CANCEL;
+
+static void
+on_response (GtkDialog *dialog,
+             int resp,
+             void *user_data)
+{
+    GApplication *app = G_APPLICATION (user_data);
+
+    response = resp;
+
+    gtk_window_destroy (GTK_WINDOW (dialog));
+    g_application_quit (app);
+}
+
+static int
+on_app_command_line (GApplication *app,
+                     GApplicationCommandLine *cmd_line,
+                     void *user_data)
+{
+    g_auto(GStrv) argv = NULL;
+    int argc;
+    const char *title;
+    const char *argument;
+    g_autofree char *message = NULL;
+    const char *flags;
+
+    title = g_getenv ("SEAHORSE_SSH_ASKPASS_TITLE");
+    if (!title || !title[0])
+        title = _("Enter your Secure Shell passphrase:");
+
+    message = (char *) g_getenv ("SEAHORSE_SSH_ASKPASS_MESSAGE");
+    argv = g_application_command_line_get_arguments (cmd_line, &argc);
+    if (message && message[0])
+        message = g_strdup (message);
+    else if (argc > 1)
+        message = g_strjoinv (" ", argv + 1);
+    else
+        message = g_strdup (_("Enter your Secure Shell passphrase:"));
+
+    argument = g_getenv ("SEAHORSE_SSH_ASKPASS_ARGUMENT");
+    if (!argument)
+        argument = "";
+
+    flags = g_getenv ("SEAHORSE_SSH_ASKPASS_FLAGS");
+    if (!flags)
+        flags = "";
+    if (strstr (flags, "multiple")) {
+        g_autofree char *lower = g_ascii_strdown (message, -1);
+
+        /* Need the old passphrase */
+        if (strstr (lower, "old pass")) {
+            title = _("Old Key Passphrase");
+            message = g_strdup_printf (_("Enter the old passphrase for: %s"), argument);
+
+        /* Look for the new passphrase thingy */
+        } else if (strstr (lower, "new pass")) {
+            title = _("New Key Passphrase");
+            message = g_strdup_printf (_("Enter the new passphrase for: %s"), argument);
+
+        /* Confirm the new passphrase, just send it again */
+        } else if (strstr (lower, "again")) {
+            title = _("New Key Passphrase");
+            message = g_strdup_printf (_("Enter the new passphrase again: %s"), argument);
+        }
+    }
+
+    dialog = seahorse_passphrase_prompt_show_dialog (title, message, _("Password:"),
+                                                     NULL, FALSE);
+    g_signal_connect (dialog, "response", G_CALLBACK (on_response), app);
+    gtk_window_present (GTK_WINDOW (dialog));
+
+    return 0;
+}
 
 int
 main (int argc, char* argv[])
 {
-       GdkWindow *transient_for = NULL;
-       SeahorsePassphrasePrompt *dialog;
-       const gchar *title;
-       const gchar *argument;
-       gchar *message;
-       const gchar *flags;
-       int result;
-       const gchar *pass;
-       gulong xid;
-       gssize len;
-
-       bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
-       bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
-       textdomain (GETTEXT_PACKAGE);
-
-       gtk_init (&argc, &argv);
-
-       /* Non buffered stdout */
-       setvbuf (stdout, 0, _IONBF, 0);
-
-#ifdef GDK_WINDOWING_X11
-       argument = g_getenv ("SEAHORSE_SSH_ASKPASS_PARENT");
-       if (argument) {
-               GdkDisplay *display = gdk_display_get_default ();
-               if (GDK_IS_X11_DISPLAY (display)) {
-                       xid = strtoul (argument, NULL, 10);
-                       if (xid != 0)
-                               transient_for = gdk_x11_window_foreign_new_for_display (display, xid);
-                       if (transient_for == NULL)
-                               g_warning ("Couldn't find window to be transient for: %s", argument);
-               }
-       }
-#endif
-
-       title = g_getenv ("SEAHORSE_SSH_ASKPASS_TITLE");
-       if (!title || !title[0])
-               title = _("Enter your Secure Shell passphrase:");
-
-       message = (gchar *)g_getenv ("SEAHORSE_SSH_ASKPASS_MESSAGE");
-       if (message && message[0])
-               message = g_strdup (message);
-       else if (argc > 1)
-               message = g_strjoinv (" ", argv + 1);
-       else
-               message = g_strdup (_("Enter your Secure Shell passphrase:"));
-
-       argument = g_getenv ("SEAHORSE_SSH_ASKPASS_ARGUMENT");
-       if (!argument)
-               argument = "";
-
-       flags = g_getenv ("SEAHORSE_SSH_ASKPASS_FLAGS");
-       if (!flags)
-               flags = "";
-       if (strstr (flags, "multiple")) {
-               gchar *lower = g_ascii_strdown (message, -1);
-
-               /* Need the old passphrase */
-               if (strstr (lower, "old pass")) {
-                       title = _("Old Key Passphrase");
-                       message = g_strdup_printf (_("Enter the old passphrase for: %s"), argument);
-
-               /* Look for the new passphrase thingy */
-               } else if (strstr (lower, "new pass")) {
-                       title = _("New Key Passphrase");
-                       message = g_strdup_printf (_("Enter the new passphrase for: %s"), argument);
-
-               /* Confirm the new passphrase, just send it again */
-               } else if (strstr (lower, "again")) {
-                       title = _("New Key Passphrase");
-                       message = g_strdup_printf (_("Enter the new passphrase again: %s"), argument);
-               }
-
-               g_free (lower);
-       }
-
-       dialog = seahorse_passphrase_prompt_show_dialog (title, message, _("Password:"),
-                                                 NULL, FALSE);
-
-       g_free (message);
-
-       if (transient_for) {
-               gdk_window_set_transient_for (gtk_widget_get_window (GTK_WIDGET (dialog)),
-                                             transient_for);
-       }
-
-       result = 1;
-       if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
-               pass = seahorse_passphrase_prompt_get_text (dialog);
-               len = strlen (pass ? pass : "");
-               if (write (1, pass, len) != len) {
-                       g_warning ("couldn't write out password properly");
-                       result = 1;
-               } else {
-                       result = 0;
-               }
-       }
-
-       if (transient_for)
-               g_object_unref (transient_for);
-       gtk_widget_destroy (GTK_WIDGET (dialog));
-       return result;
-}
+    g_autoptr(GtkApplication) app = NULL;
+    int result;
+    const char *pass;
+    gssize len;
+
+    bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+    bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+    textdomain (GETTEXT_PACKAGE);
+
+    /* Non buffered stdout */
+    setvbuf (stdout, 0, _IONBF, 0);
+
+    app = gtk_application_new (NULL, G_APPLICATION_HANDLES_COMMAND_LINE);
+    g_signal_connect (app, "command-line", G_CALLBACK (on_app_command_line), NULL);
 
+    result = g_application_run (G_APPLICATION (app), argc, argv);
+    if (result != 0)
+        return result;
+
+    if (response != GTK_RESPONSE_ACCEPT)
+        return 2;
+
+    pass = seahorse_passphrase_prompt_get_text (dialog);
+    len = strlen (pass ? pass : "");
+    if (write (1, pass, len) != len) {
+        g_warning ("couldn't write out password properly");
+        return 1;
+    }
+
+    return 0;
+}
diff --git a/ssh/seahorse-ssh-generate.ui b/ssh/seahorse-ssh-generate.ui
index a9db2c27..4cf42dc5 100644
--- a/ssh/seahorse-ssh-generate.ui
+++ b/ssh/seahorse-ssh-generate.ui
@@ -1,207 +1,79 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.24"/>
   <template class="SeahorseSshGenerate" parent="GtkDialog">
     <property name="modal">True</property>
-    <property name="border_width">18</property>
     <property name="title" translatable="yes">New Secure Shell Key</property>
-    <child internal-child="vbox">
+    <child internal-child="content_area">
       <object class="GtkBox">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="orientation">vertical</property>
-        <property name="spacing">2</property>
+        <property name="spacing">12</property>
+        <property name="margin-top">12</property>
+        <property name="margin-bottom">12</property>
+        <property name="margin-start">12</property>
+        <property name="margin-end">12</property>
         <child>
-          <object class="GtkBox" id="vbox5">
-            <property name="visible">True</property>
-            <property name="orientation">vertical</property>
-            <property name="can_focus">False</property>
-            <property name="spacing">12</property>
+          <object class="GtkLabel" id="label45">
+            <property name="xalign">0</property>
+            <property name="yalign">0</property>
+            <property name="label" translatable="yes">A Secure Shell (SSH) key lets you connect securely to 
other computers.</property>
+            <property name="wrap">True</property>
+          </object>
+        </child>
+        <child>
+          <object class="AdwPreferencesGroup">
             <child>
-              <object class="GtkBox" id="vbox3">
-                <property name="visible">True</property>
-                <property name="orientation">vertical</property>
-                <property name="can_focus">False</property>
-                <property name="spacing">12</property>
-                <child>
-                  <object class="GtkBox" id="hbox41">
-                    <property name="visible">True</property>
-                    <property name="orientation">horizontal</property>
-                    <property name="can_focus">False</property>
-                    <property name="spacing">12</property>
-                    <child>
-                      <object class="GtkLabel" id="label45">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="xalign">0</property>
-                        <property name="yalign">0</property>
-                        <property name="label" translatable="yes">A Secure Shell (SSH) key lets you connect 
securely to other computers.</property>
-                        <property name="wrap">True</property>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkGrid" id="details_grid">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="row_spacing">6</property>
-                    <property name="column_spacing">12</property>
-                    <child>
-                      <object class="GtkLabel" id="email_label">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="xalign">1</property>
-                        <property name="yalign">0</property>
-                        <property name="label" translatable="yes">_Description</property>
-                        <property name="use_markup">True</property>
-                        <property name="use_underline">True</property>
-                        <property name="mnemonic_widget">email_entry</property>
-                        <style>
-                          <class name="dim-label"/>
-                        </style>
-                      </object>
-                      <packing>
-                        <property name="top_attach">0</property>
-                        <property name="left_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEntry" id="email_entry">
-                        <property name="width_request">180</property>
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="invisible_char">●</property>
-                        <property name="activates_default">True</property>
-                        <property name="invisible_char_set">True</property>
-                      </object>
-                      <packing>
-                        <property name="top_attach">0</property>
-                        <property name="left_attach">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="xalign">0</property>
-                        <property name="xpad">3</property>
-                        <property name="label" translatable="yes">Your email address, or a reminder of what 
this key is for.</property>
-                        <property name="wrap">True</property>
-                        <style>
-                          <class name="dim-label"/>
-                        </style>
-                      </object>
-                      <packing>
-                        <property name="left_attach">1</property>
-                        <property name="top_attach">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label49">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="halign">end</property>
-                        <property name="label" translatable="yes">Encryption _Type</property>
-                        <property name="use_underline">True</property>
-                        <property name="mnemonic_widget">algorithm_combo_box</property>
-                        <style>
-                          <class name="dim-label"/>
-                        </style>
-                      </object>
-                      <packing>
-                        <property name="top_attach">2</property>
-                        <property name="left_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkComboBoxText" id="algorithm_combo_box">
-                        <property name="visible">True</property>
-                        <property name="halign">start</property>
-                        <property name="can_focus">False</property>
-                        <property name="entry_text_column">0</property>
-                        <property name="id_column">1</property>
-                        <items>
-                          <item translatable="yes">RSA</item>
-                          <item translatable="yes">DSA</item>
-                          <item translatable="yes">ECDSA</item>
-                          <item translatable="yes">ED25519</item>
-                        </items>
-                        <signal name="changed" handler="on_algo_changed"/>
-                      </object>
-                      <packing>
-                        <property name="top_attach">2</property>
-                        <property name="left_attach">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label50">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="halign">end</property>
-                        <property name="label" translatable="yes">Key _Strength (bits)</property>
-                        <property name="use_underline">True</property>
-                        <style>
-                          <class name="dim-label"/>
-                        </style>
-                      </object>
-                      <packing>
-                        <property name="top_attach">3</property>
-                        <property name="left_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <!-- Key strength widget comes here -->
-                      <placeholder/>
-                    </child>
+              <object class="AdwEntryRow" id="email_row">
+                <property name="title" translatable="yes">_Description</property>
+                <property name="use_underline">True</property>
+                <!-- XXX Ideally, this would be a placeholder-text instead -->
+                <property name="tooltip-text" translatable="yes">Your email address, or a reminder of what 
this key is for.</property>
+              </object>
+            </child>
+            <child>
+              <object class="AdwComboRow" id="algo_row">
+                <property name="title" translatable="yes">Encryption _Type</property>
+                <property name="use_underline">True</property>
+                <property name="model">
+                  <object class="GtkStringList">
+                    <items>
+                      <item translatable="yes">RSA</item>
+                      <item translatable="yes">DSA</item>
+                      <item translatable="yes">ECDSA</item>
+                      <item translatable="yes">ED25519</item>
+                    </items>
                   </object>
-                  <packing>
-                    <property name="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
+                </property>
+                <signal name="notify::selected" handler="on_algo_row_selected_changed"/>
               </object>
             </child>
             <child>
-              <object class="GtkLabel">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="margin_top">24</property>
-                <property name="xalign">0</property>
-                <property name="max-width-chars">75</property>
-                <property name="label" translatable="yes">If there is a computer you want to use this key 
with, you can set up that computer to recognize your new key.</property>
-                <property name="wrap">True</property>
+              <object class="AdwActionRow" id="key_strength_row">
+                <property name="title" translatable="yes">Key _Strength (bits)</property>
+                <property name="use_underline">True</property>
               </object>
             </child>
           </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">1</property>
-          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel">
+            <property name="margin-top">24</property>
+            <property name="xalign">0</property>
+            <property name="max-width-chars">75</property>
+            <property name="label" translatable="yes">If there is a computer you want to use this key with, 
you can set up that computer to recognize your new key.</property>
+            <property name="wrap">True</property>
+          </object>
         </child>
       </object>
     </child>
     <child type="action">
       <object class="GtkButton" id="cancel_button">
         <property name="label" translatable="yes">_Cancel</property>
-        <property name="visible">True</property>
-        <property name="receives_default">False</property>
         <property name="use_underline">True</property>
       </object>
     </child>
     <child type="action">
       <object class="GtkButton" id="ok_button">
         <property name="label" translatable="yes">_Generate</property>
-        <property name="visible">True</property>
-        <property name="can_default">True</property>
-        <property name="receives_default">False</property>
         <property name="use_underline">True</property>
       </object>
     </child>
diff --git a/ssh/seahorse-ssh-key-properties.ui b/ssh/seahorse-ssh-key-properties.ui
index 4959250d..0e3b58fc 100644
--- a/ssh/seahorse-ssh-key-properties.ui
+++ b/ssh/seahorse-ssh-key-properties.ui
@@ -1,214 +1,84 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
   <template class="SeahorseSshKeyProperties" parent="GtkDialog">
-    <property name="can_focus">False</property>
     <property name="resizable">False</property>
     <child type="titlebar">
       <object class="GtkHeaderBar">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="title" translatable="yes">SSH Key Properties</property>
-        <property name="has_subtitle">False</property>
-        <property name="show_close_button">True</property>
+        <property name="show-title-buttons">True</property>
+        <property name="title-widget">
+          <object class="AdwWindowTitle">
+            <property name="title" translatable="yes">SSH Key Properties</property>
+          </object>
+        </property>
       </object>
     </child>
-    <child internal-child="vbox">
+    <child internal-child="content_area">
       <object class="GtkBox">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="orientation">vertical</property>
         <property name="spacing">2</property>
         <child internal-child="action_area">
-          <object class="GtkButtonBox">
-            <property name="can_focus">False</property>
+          <object class="GtkBox">
           </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
-            <property name="position">0</property>
-          </packing>
         </child>
         <child>
           <object class="GtkBox">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="border_width">12</property>
+            <property name="margin-start">12</property>
+            <property name="margin-end">12</property>
+            <property name="margin-top">12</property>
+            <property name="margin-bottom">12</property>
             <property name="orientation">vertical</property>
             <property name="spacing">20</property>
             <child>
               <object class="GtkListBox">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="selection-mode">none</property>
                 <style>
-                  <class name="content"/>
+                  <class name="boxed-list"/>
                 </style>
                 <child>
-                  <object class="GtkListBoxRow">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <child>
-                      <object class="GtkBox">
-                        <property name="visible">True</property>
-                        <property name="orientation">horizontal</property>
-                        <property name="margin-top">6</property>
-                        <property name="margin-bottom">6</property>
-                        <property name="margin-start">12</property>
-                        <property name="margin-end">12</property>
-                        <property name="spacing">12</property>
-                        <child>
-                          <object class="GtkLabel">
-                            <property name="visible">True</property>
-                            <property name="halign">start</property>
-                            <property name="label" translatable="yes" context="name-of-ssh-key" 
comments="Name of key, often a persons name">Name</property>
-                            <property name="xalign">0</property>
-                          </object>
-                        </child>
-                        <child>
-                          <object class="GtkEntry" id="comment_entry">
-                            <property name="visible">True</property>
-                            <property name="halign">end</property>
-                            <property name="width-chars">24</property>
-                            <property name="activates_default">True</property>
-                            <signal name="activate" handler="on_ssh_comment_activate" swapped="no"/>
-                            <signal name="focus-out-event" handler="on_ssh_comment_focus_out" swapped="no"/>
-                          </object>
-                          <packing>
-                            <property name="pack_type">end</property>
-                          </packing>
-                        </child>
-                      </object>
-                    </child>
+                  <object class="AdwEntryRow" id="comment_row">
+                    <property name="show-apply-button">True</property>
+                    <property name="title" translatable="yes" context="name-of-ssh-key" comments="Name of 
key, often a persons name">Name</property>
+                    <signal name="apply" handler="on_ssh_comment_apply"/>
                   </object>
                 </child>
                 <child>
-                  <object class="GtkListBoxRow">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <child>
-                      <object class="GtkBox">
-                        <property name="visible">True</property>
-                        <property name="orientation">horizontal</property>
-                        <property name="margin">12</property>
-                        <property name="spacing">12</property>
-                        <child>
-                          <object class="GtkLabel">
-                            <property name="visible">True</property>
-                            <property name="label" translatable="yes">Algorithm</property>
-                            <property name="halign">start</property>
-                            <property name="xalign">0</property>
-                          </object>
-                        </child>
-                        <child>
-                          <object class="GtkLabel" id="algo_label">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="selectable">True</property>
-                            <property name="xalign">1</property>
-                          </object>
-                          <packing>
-                            <property name="pack_type">end</property>
-                          </packing>
-                        </child>
+                  <object class="AdwActionRow">
+                    <property name="title" translatable="yes">Algorithm</property>
+                    <child type="suffix">
+                      <object class="GtkLabel" id="algo_label">
+                         <property name="selectable">True</property>
                       </object>
                     </child>
                   </object>
                 </child>
                 <child>
-                  <object class="GtkListBoxRow">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
+                  <object class="AdwActionRow">
+                    <property name="title" translatable="yes">Key Length</property>
                     <child>
-                      <object class="GtkBox">
-                        <property name="visible">True</property>
-                        <property name="orientation">horizontal</property>
-                        <property name="margin">12</property>
-                        <property name="spacing">12</property>
-                        <child>
-                          <object class="GtkLabel">
-                            <property name="visible">True</property>
-                            <property name="label" translatable="yes">Key Length</property>
-                            <property name="halign">start</property>
-                            <property name="xalign">0</property>
-                          </object>
-                        </child>
-                        <child>
-                          <object class="GtkLabel" id="key_length_label">
-                            <property name="visible">True</property>
+                      <object class="GtkLabel" id="key_length_label">
                             <property name="selectable">True</property>
-                            <property name="xalign">1</property>
-                          </object>
-                          <packing>
-                            <property name="pack_type">end</property>
-                          </packing>
-                        </child>
                       </object>
                     </child>
                   </object>
                 </child>
                 <child>
-                  <object class="GtkListBoxRow">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
+                  <object class="AdwActionRow">
+                    <property name="title" translatable="yes">Location</property>
                     <child>
-                      <object class="GtkBox">
-                        <property name="visible">True</property>
-                        <property name="orientation">horizontal</property>
-                        <property name="margin">12</property>
-                        <property name="spacing">12</property>
-                        <child>
-                          <object class="GtkLabel">
-                            <property name="visible">True</property>
-                            <property name="label" translatable="yes">Location</property>
-                            <property name="halign">start</property>
-                            <property name="xalign">0</property>
-                          </object>
-                        </child>
-                        <child>
-                          <object class="GtkLabel" id="location_label">
-                            <property name="visible">True</property>
-                            <property name="use_markup">True</property>
-                            <property name="selectable">True</property>
-                            <property name="ellipsize">start</property>
-                            <property name="xalign">1</property>
-                          </object>
-                          <packing>
-                            <property name="pack_type">end</property>
-                          </packing>
-                        </child>
+                      <object class="GtkLabel" id="location_label">
+                        <property name="use_markup">True</property>
+                        <property name="selectable">True</property>
+                        <property name="ellipsize">start</property>
                       </object>
                     </child>
                   </object>
                 </child>
                 <child>
-                  <object class="GtkListBoxRow">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
+                  <object class="AdwActionRow">
+                    <property name="title" translatable="yes">Fingerprint</property>
                     <child>
-                      <object class="GtkBox">
-                        <property name="visible">True</property>
-                        <property name="orientation">horizontal</property>
-                        <property name="margin">12</property>
-                        <property name="spacing">12</property>
-                        <child>
-                          <object class="GtkLabel">
-                            <property name="visible">True</property>
-                            <property name="label" translatable="yes">Fingerprint</property>
-                            <property name="halign">start</property>
-                            <property name="xalign">0</property>
-                          </object>
-                        </child>
-                        <child>
-                          <object class="GtkLabel" id="fingerprint_label">
-                            <property name="visible">True</property>
-                            <property name="selectable">True</property>
-                            <property name="xalign">1</property>
-                          </object>
-                          <packing>
-                            <property name="pack_type">end</property>
-                          </packing>
-                        </child>
+                      <object class="GtkLabel" id="fingerprint_label">
+                        <property name="selectable">True</property>
                       </object>
                     </child>
                   </object>
@@ -217,28 +87,21 @@
             </child>
             <child>
               <object class="GtkListBox">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="selection-mode">none</property>
                 <style>
                   <class name="content"/>
                 </style>
                 <child>
-                  <object class="HdyExpanderRow">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
+                  <object class="AdwExpanderRow">
                     <property name="title" translatable="yes">Public Key</property>
                     <child type="action">
                       <object class="GtkButton" id="copy_button">
-                        <property name="visible">True</property>
                         <property name="valign">center</property>
-                        <property name="can_focus">True</property>
                         <property name="receives_default">True</property>
                         <property name="tooltip_text" translatable="yes">Copy public key to 
clipboard</property>
                         <signal name="clicked" handler="on_ssh_copy_button_clicked" swapped="no"/>
                         <child>
                           <object class="GtkImage">
-                            <property name="visible">True</property>
                             <property name="icon_name">edit-copy-symbolic</property>
                           </object>
                         </child>
@@ -249,8 +112,6 @@
                     </child>
                     <child>
                       <object class="GtkLabel" id="pubkey_label">
-                        <property name="visible">True</property>
-                        <property name="margin">12</property>
                         <property name="wrap">True</property>
                         <property name="wrap_mode">char</property>
                         <property name="selectable">True</property>
@@ -263,23 +124,17 @@
             </child>
             <child>
               <object class="GtkListBox">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="selection-mode">none</property>
                 <style>
                   <class name="content"/>
                 </style>
                 <child>
-                  <object class="HdyActionRow">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
+                  <object class="AdwActionRow">
                     <property name="title" translatable="yes">Remote access</property>
                     <property name="subtitle" translatable="yes">Allows accessing this computer 
remotely</property>
                     <property name="activatable_widget">trust_check</property>
                     <child>
                       <object class="GtkSwitch" id="trust_check">
-                        <property name="visible">True</property>
-                        <property name="can_focus">True</property>
                         <property name="valign">center</property>
                         <signal name="notify::active" handler="on_ssh_trust_changed" swapped="no"/>
                       </object>
@@ -290,45 +145,29 @@
             </child>
             <child>
               <object class="GtkBox">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="halign">end</property>
                 <property name="spacing">12</property>
                 <child>
                   <object class="GtkButton" id="export_button">
                     <property name="label" translatable="yes">_Export</property>
-                    <property name="visible">True</property>
                     <property name="use_underline">True</property>
-                    <property name="can_focus">True</property>
                     <property name="receives_default">True</property>
                     <signal name="clicked" handler="on_ssh_export_button_clicked" swapped="no"/>
                   </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                  </packing>
                 </child>
                 <child>
                   <object class="GtkButton" id="passphrase_button">
                     <property name="label" translatable="yes">Change _Passphrase</property>
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
                     <property name="receives_default">False</property>
                     <property name="halign">end</property>
                     <property name="use_underline">True</property>
                     <signal name="clicked" handler="on_ssh_passphrase_button_clicked" swapped="no"/>
                   </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                  </packing>
                 </child>
                 <child>
                   <object class="GtkButton">
                     <property name="label" translatable="yes">_Delete SSH Key</property>
-                    <property name="visible">True</property>
                     <property name="use_underline">True</property>
-                    <property name="can_focus">True</property>
                     <property name="receives_default">True</property>
                     <property name="halign">end</property>
                     <signal name="clicked" handler="on_delete_clicked" swapped="no"/>
@@ -336,22 +175,10 @@
                       <class name="destructive-action"/>
                     </style>
                   </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                  </packing>
                 </child>
               </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-              </packing>
             </child>
           </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-          </packing>
         </child>
       </object>
     </child>
diff --git a/ssh/seahorse-ssh-upload.ui b/ssh/seahorse-ssh-upload.ui
index ee335019..578e1815 100644
--- a/ssh/seahorse-ssh-upload.ui
+++ b/ssh/seahorse-ssh-upload.ui
@@ -1,141 +1,55 @@
 <?xml version="1.0"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
   <template class="SeahorseSshUpload" parent="GtkDialog">
     <property name="title" translatable="yes">Set Up Computer for SSH Connection</property>
     <property name="resizable">False</property>
-    <property name="border_width">5</property>
     <property name="modal">True</property>
-    <property name="skip_taskbar_hint">True</property>
-    <property name="skip_pager_hint">True</property>
-    <property name="window_position">center-on-parent</property>
-    <child internal-child="vbox">
+
+    <child internal-child="content_area">
       <object class="GtkBox">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
-        <property name="spacing">2</property>
+        <property name="spacing">12</property>
         <child>
-          <object class="GtkBox" id="vbox1">
-            <property name="visible">True</property>
-            <property name="orientation">vertical</property>
-            <property name="border_width">5</property>
-            <property name="spacing">12</property>
+          <object class="GtkLabel">
+            <property name="max_width_chars">60</property>
+            <property name="label" translatable="yes">To use your Secure Shell key with another computer 
that uses SSH, you must already have a login account on that computer.</property>
+            <property name="margin_bottom">6</property>
+            <property name="wrap">True</property>
+          </object>
+        </child>
+        <child>
+          <object class="AdwPreferencesGroup">
+            <child>
+              <object class="AdwEntryRow" id="host_row">
+                <property name="title" translatable="yes">_Server address</property>
+                <property name="tooltip_text" translatable="yes">The host name or address of the 
server.</property>
+                <property name="use-underline">True</property>
+                <signal name="changed" handler="on_upload_input_changed"/>
+              </object>
+            </child>
             <child>
-              <object class="GtkBox" id="vbox2">
-                <property name="visible">True</property>
-                <property name="orientation">vertical</property>
-                <property name="spacing">12</property>
-                <child>
-                  <object class="GtkLabel" id="label5">
-                    <property name="visible">True</property>
-                    <property name="max_width_chars">60</property>
-                    <property name="label" translatable="yes">To use your Secure Shell key with another 
computer that uses SSH, you must already have a login account on that computer.</property>
-                    <property name="margin_bottom">6</property>
-                    <property name="wrap">True</property>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkGrid" id="table1">
-                    <property name="visible">True</property>
-                    <property name="column_spacing">12</property>
-                    <property name="row_spacing">6</property>
-                    <child>
-                      <object class="GtkLabel" id="label3">
-                        <property name="visible">True</property>
-                        <property name="xalign">1</property>
-                        <property name="yalign">0</property>
-                        <property name="ypad">4</property>
-                        <property name="label" translatable="yes">_Server address:</property>
-                        <property name="use_underline">True</property>
-                      </object>
-                      <packing>
-                        <property name="top_attach">0</property>
-                        <property name="left_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkBox" id="vbox3">
-                        <property name="visible">True</property>
-                        <property name="orientation">vertical</property>
-                        <property name="spacing">3</property>
-                        <child>
-                          <object class="GtkEntry" id="host_entry">
-                            <property name="visible">True</property>
-                            <property name="hexpand">True</property>
-                            <property name="can_focus">True</property>
-                            <property name="has_focus">True</property>
-                            <property name="tooltip_text" translatable="yes">The host name or address of the 
server.</property>
-                            <property name="invisible_char">&#x25CF;</property>
-                            <property name="activates_default">True</property>
-                            <signal name="changed" handler="on_upload_input_changed"/>
-                          </object>
-                        </child>
-                        <child>
-                          <object class="GtkLabel" id="label7">
-                            <property name="visible">True</property>
-                            <property name="halign">start</property>
-                            <property name="label" translatable="yes">eg: 
fileserver.example.com:port</property>
-                            <style>
-                              <class name="dim-label"/>
-                            </style>
-                          </object>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="top_attach">0</property>
-                        <property name="left_attach">1</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="label2">
-                        <property name="visible">True</property>
-                        <property name="xalign">1</property>
-                        <property name="label" translatable="yes">_Login name:</property>
-                        <property name="use_underline">True</property>
-                        <property name="mnemonic_widget">user_entry</property>
-                      </object>
-                      <packing>
-                        <property name="top_attach">1</property>
-                        <property name="left_attach">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkEntry" id="user_entry">
-                        <property name="visible">True</property>
-                        <property name="hexpand">True</property>
-                        <property name="can_focus">True</property>
-                        <property name="has_focus">True</property>
-                        <property name="tooltip_text" translatable="yes">The host name or address of the 
server.</property>
-                        <property name="activates_default">True</property>
-                        <signal name="changed" handler="on_upload_input_changed"/>
-                      </object>
-                      <packing>
-                        <property name="top_attach">1</property>
-                        <property name="left_attach">1</property>
-                      </packing>
-                    </child>
-                  </object>
-                </child>
+              <object class="AdwEntryRow" id="user_row">
+                <property name="title" translatable="yes">_Login name</property>
+                <property name="use-underline">True</property>
+                <property name="tooltip-text" translatable="yes">The host name or address of the 
server.</property>
+                <signal name="changed" handler="on_upload_input_changed"/>
               </object>
             </child>
           </object>
         </child>
       </object>
     </child>
+
     <child type="action">
       <object class="GtkButton" id="setup_button">
-        <property name="visible">True</property>
-        <property name="can-default">True</property>
-        <property name="label" translatable="yes">Set Up</property>
-        <style>
-          <class name="suggested-action"/>
-        </style>
+        <property name="label" translatable="yes">_Set Up</property>
+        <property name="use-underline">True</property>
       </object>
     </child>
     <child type="action">
       <object class="GtkButton" id="cancel_button">
-        <property name="visible">True</property>
-        <property name="label" translatable="yes">Cancel</property>
+        <property name="label" translatable="yes">_Cancel</property>
+        <property name="use-underline">True</property>
       </object>
     </child>
     <action-widgets>
diff --git a/ssh/source.vala b/ssh/source.vala
index c4646c9f..911af7f5 100644
--- a/ssh/source.vala
+++ b/ssh/source.vala
@@ -23,7 +23,7 @@
  * The {@link Place} where SSH keys are stored. By default that is ~/.ssh.
  * Basically, this becomes an interface to the SSH home directory.
  */
-public class Seahorse.Ssh.Source : GLib.Object, Gcr.Collection, Seahorse.Place {
+public class Seahorse.Ssh.Source : GLib.Object, GLib.ListModel, Seahorse.Place {
 
     // Paths to the authorized_keys file and one for unauthorized keys
     // The second file is Seahorse-specific, to allow users to retain public
@@ -40,7 +40,7 @@ public class Seahorse.Ssh.Source : GLib.Object, Gcr.Collection, Seahorse.Place {
     private FileMonitor? monitor_handle = null;
 
     // The list of Seahorse.Ssh.Keys
-    private ListStore keys = new ListStore(typeof(Ssh.Key));
+    private GenericArray<Ssh.Key> keys = new GenericArray<Ssh.Key>();
 
     public string label {
         owned get { return _("OpenSSH keys"); }
@@ -55,10 +55,6 @@ public class Seahorse.Ssh.Source : GLib.Object, Gcr.Collection, Seahorse.Place {
         owned get { return _("openssh://%s").printf(this.ssh_homedir); }
     }
 
-    public Icon icon {
-        owned get { return new ThemedIcon(Gcr.ICON_HOME_DIRECTORY); }
-    }
-
     public Place.Category category {
         get { return Place.Category.KEYS; }
     }
@@ -75,10 +71,6 @@ public class Seahorse.Ssh.Source : GLib.Object, Gcr.Collection, Seahorse.Place {
         owned get { return null; }
     }
 
-    public bool show_if_empty {
-        get { return true; }
-    }
-
     /**
      * The directory containing the SSH keys.
      */
@@ -173,26 +165,25 @@ public class Seahorse.Ssh.Source : GLib.Object, Gcr.Collection, Seahorse.Place {
         }
     }
 
-    public uint get_length() {
-        return this.keys.get_n_items();
+    public GLib.Type get_item_type() {
+        return typeof(Ssh.Key);
     }
 
-    public List<weak GLib.Object> get_objects() {
-        var objects = new List<weak GLib.Object>();
-        for (uint i = 0; i < this.keys.get_n_items(); i++)
-            objects.append(this.keys.get_item(i));
-        return objects;
+    public uint get_n_items() {
+        return this.keys.length;
     }
 
-    public bool contains(GLib.Object object) {
-        return this.keys.find(object, null);
+    public GLib.Object? get_item(uint index) {
+        if (index >= this.keys.length)
+            return null;
+        return this.keys[index];
     }
 
-    public void remove_object(GLib.Object object) {
+    public void remove_object(Ssh.Key key) {
         uint pos;
-        if (this.keys.find(object, out pos)) {
-            this.keys.remove(pos);
-            removed(object);
+        if (this.keys.find(key, out pos)) {
+            this.keys.remove_index(pos);
+            items_changed(pos, 1, 0);
         }
     }
 
@@ -209,8 +200,8 @@ public class Seahorse.Ssh.Source : GLib.Object, Gcr.Collection, Seahorse.Place {
             return null;
 
         // Check if it was already loaded once. If not, load it now
-        for (uint i = 0; i < this.keys.get_n_items(); i++) {
-            var key = (Ssh.Key) this.keys.get_item(i);
+        for (uint i = 0; i < this.keys.length; i++) {
+            unowned var key = this.keys[i];
             if (key.key_data.privfile == privfile) {
                 return key;
             }
@@ -324,8 +315,8 @@ public class Seahorse.Ssh.Source : GLib.Object, Gcr.Collection, Seahorse.Place {
 
         // Create a new key
         Key key = new Key(src, keydata);
-        src.keys.append(key);
-        src.added(key);
+        src.keys.add(key);
+        src.items_changed(src.keys.length - 1, 0, 1);
 
         return key;
     }
@@ -455,11 +446,9 @@ public class Seahorse.Ssh.Source : GLib.Object, Gcr.Collection, Seahorse.Place {
     }
 
     public Key? find_key_by_fingerprint(string fingerprint) {
-        for (uint i = 0; i < this.keys.get_n_items(); i++) {
-            var key = (Ssh.Key) this.keys.get_item(i);
-            if (key.fingerprint == fingerprint) {
-                return key;
-            }
+        for (uint i = 0; i < this.keys.length; i++) {
+            if (this.keys[i].fingerprint == fingerprint)
+                return this.keys[i];
         }
 
         return null;
diff --git a/ssh/upload.vala b/ssh/upload.vala
index aa76801d..a1f87b46 100644
--- a/ssh/upload.vala
+++ b/ssh/upload.vala
@@ -25,9 +25,9 @@ public class Seahorse.Ssh.Upload : Gtk.Dialog {
     private unowned List<Key> keys;
 
     [GtkChild]
-    private unowned Gtk.Entry user_entry;
+    private unowned Adw.EntryRow host_row;
     [GtkChild]
-    private unowned Gtk.Entry host_entry;
+    private unowned Adw.EntryRow user_row;
     [GtkChild]
     private unowned Gtk.Button setup_button;
 
@@ -37,16 +37,14 @@ public class Seahorse.Ssh.Upload : Gtk.Dialog {
         this.keys = keys;
 
         // Default to the users current name
-        this.user_entry.text = Environment.get_user_name();
-        // Focus the host
-        this.host_entry.grab_focus();
+        this.user_row.text = Environment.get_user_name();
 
         on_upload_input_changed();
     }
 
     private void upload_keys() {
-        string user = this.user_entry.text.strip();
-        string host_port = this.host_entry.text.strip();
+        string user = this.user_row.text.strip();
+        string host_port = this.host_row.text.strip();
 
         if (!user.validate() || host_port == "" || !host_port.validate())
             return;
@@ -73,8 +71,8 @@ public class Seahorse.Ssh.Upload : Gtk.Dialog {
 
     [GtkCallback]
     private void on_upload_input_changed () {
-        string user = this.user_entry.text;
-        string host = this.host_entry.text;
+        string user = this.user_row.text;
+        string host = this.host_row.text;
 
         if (!user.validate() || !host.validate())
             return;
@@ -98,22 +96,11 @@ public class Seahorse.Ssh.Upload : Gtk.Dialog {
             return;
 
         Upload upload_dialog = new Upload(keys, parent);
-        for (;;) {
-            switch (upload_dialog.run()) {
-            case Gtk.ResponseType.HELP:
-                /* TODO: Help */
-                continue;
-            case Gtk.ResponseType.ACCEPT:
+        upload_dialog.response.connect((response) => {
+            if (response == Gtk.ResponseType.ACCEPT)
                 upload_dialog.upload_keys();
-                break;
-            default:
-                break;
-            };
-
-            break;
-        }
-
-        upload_dialog.destroy();
+            upload_dialog.destroy();
+        });
     }
 
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]