[seahorse/nielsdg/gtk4: 2/2] Port to GTK4




commit 01e252343cc3cb63572b31f63f445b6ce1a51606
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                        |  29 +-
 common/catalog.vala                        |  38 +-
 common/config.vapi                         |  38 +-
 common/datepicker.vala                     |  23 +-
 common/deletable.vala                      |   4 +-
 common/delete-dialog.vala                  | 142 ++---
 common/deleter.vala                        |  20 +-
 common/exportable.vala                     | 440 +++++++-------
 common/icons.vala                          |  44 --
 common/interaction.vala                    |   4 +-
 common/item-list.vala                      | 226 -------
 common/meson.build                         |   9 +-
 common/passphrase-prompt.vala              | 107 ++--
 common/place.vala                          |  10 +-
 common/prefs-keyservers.vala               |  40 +-
 common/prefs.vala                          |   2 +-
 common/seahorse-prefs-keyservers.ui        |   6 +-
 common/util.vala                           |  43 +-
 common/viewable.vala                       |   3 +-
 gkr/gkr-backend.vala                       | 388 ++++++------
 gkr/gkr-dialogs.vala                       |  93 ++-
 gkr/gkr-item-add.vala                      |  32 +-
 gkr/gkr-item-properties.vala               |  82 ++-
 gkr/gkr-item.vala                          |  10 +-
 gkr/gkr-keyring-add.vala                   |   9 +-
 gkr/gkr-keyring.vala                       | 112 ++--
 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 +---
 libseahorse/meson.build                    |   2 +-
 libseahorse/seahorse-progress.c            |   2 +-
 meson.build                                |   8 +-
 pgp/meson.build                            |   3 +-
 pgp/seahorse-combo-keys.c                  | 362 ------------
 pgp/seahorse-combo-keys.h                  |  46 --
 pgp/seahorse-gpgme-add-uid.c               |  43 +-
 pgp/seahorse-gpgme-add-uid.ui              |   2 -
 pgp/seahorse-gpgme-dialogs.h               |   3 -
 pgp/seahorse-gpgme-expires-dialog.c        |  21 +-
 pgp/seahorse-gpgme-expires-dialog.ui       |  22 +-
 pgp/seahorse-gpgme-generate-dialog.c       | 159 +++--
 pgp/seahorse-gpgme-generate-dialog.ui      | 325 +++-------
 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         |   2 +-
 pgp/seahorse-gpgme-sign-dialog.c           |  37 +-
 pgp/seahorse-gpgme-sign-dialog.ui          |  79 +--
 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-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-key-properties.c          | 180 ++----
 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 | 197 ++-----
 pgp/seahorse-pgp-public-key-properties.ui  | 202 ++-----
 pgp/seahorse-pgp-subkey-list-box-row.ui    |  98 ++-
 pgp/seahorse-pgp-subkey-list-box.c         |  52 +-
 pgp/seahorse-pgp-subkey-list-box.h         |   4 +-
 pgp/seahorse-pgp-uid-list-box-row.ui       |  20 +-
 pgp/seahorse-pgp-uid-list-box.c            |  48 +-
 pgp/seahorse-pgp-uid-list-box.h            |   4 +-
 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             | 426 +++++++-------
 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                     |  79 +--
 src/key-manager-filter.vala                | 125 ++++
 src/key-manager-item-row.vala              |  11 +-
 src/key-manager.vala                       | 280 +++++----
 src/meson.build                            |   5 +-
 src/seahorse-key-manager.ui                | 484 ++++++---------
 src/search-provider.vala                   |  56 +-
 src/sidebar.vala                           | 299 +++-------
 ssh/actions.vala                           |  19 +-
 ssh/backend.vala                           |  18 +-
 ssh/deleter.vala                           |   2 +-
 ssh/exporter.vala                          |   4 +-
 ssh/generate.vala                          |  10 +-
 ssh/key-length-chooser.vala                |  25 +-
 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               | 225 ++-----
 ssh/seahorse-ssh-key-properties.ui         | 247 ++------
 ssh/source.vala                            |  53 +-
 ssh/upload.vala                            |  19 +-
 122 files changed, 4208 insertions(+), 6453 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..94d636d0 100644
--- a/common/backend.vala
+++ b/common/backend.vala
@@ -16,24 +16,21 @@
  * 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");
-       }
-
-       public static GLib.List<Backend> get_registered() {
-               return (GLib.List<Seahorse.Backend>)Registry.object_instances("backend");
-       }
-}
+    public void register() {
+        Registry.register_object(this, "backend");
+    }
 
+    public static GLib.List<Backend> get_registered() {
+        return (GLib.List<Seahorse.Backend>)Registry.object_instances("backend");
+    }
 }
diff --git a/common/catalog.vala b/common/catalog.vala
index 2cff02e5..417f9143 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,18 @@ 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()) {
+        foreach (unowned var backend in Backend.get_registered()) {
             ActionGroup actions = backend.actions;
             menu.insert_action_group(actions.prefix, actions);
         }
-        menu.popup_at_pointer(event);
+
+               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 +137,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..f584929c 100644
--- a/common/datepicker.vala
+++ b/common/datepicker.vala
@@ -37,15 +37,13 @@ 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();
@@ -54,16 +52,16 @@ public class Seahorse.DatePicker : Gtk.Box {
         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
@@ -72,8 +70,9 @@ public class Seahorse.DatePicker : Gtk.Box {
         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 +96,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..8ba8ce85 100644
--- a/common/delete-dialog.vala
+++ b/common/delete-dialog.vala
@@ -19,93 +19,75 @@
  * 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();
+        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..f02c612c 100644
--- a/common/exportable.vala
+++ b/common/exportable.vala
@@ -21,221 +21,231 @@
 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 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"));
+
+        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..cb5fd44e 100644
--- a/common/meson.build
+++ b/common/meson.build
@@ -10,9 +10,7 @@ common_sources = files(
   'deleter.vala',
   'exportable.vala',
   'exporter.vala',
-  'icons.vala',
   'interaction.vala',
-  'item-list.vala',
   'lockable.vala',
   'object.vala',
   'passphrase-prompt.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..14909d37 100644
--- a/common/prefs-keyservers.vala
+++ b/common/prefs-keyservers.vala
@@ -19,7 +19,7 @@
  */
 
 [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;
@@ -42,8 +42,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 +62,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 +120,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/seahorse-prefs-keyservers.ui b/common/seahorse-prefs-keyservers.ui
index 419ab9cf..a103c738 100644
--- a/common/seahorse-prefs-keyservers.ui
+++ b/common/seahorse-prefs-keyservers.ui
@@ -1,11 +1,11 @@
 <?xml version="1.0"?>
 <interface>
   <requires lib="gtk+" version="3.24"/>
-  <template class="SeahorsePrefsKeyservers" parent="HdyPreferencesPage">
+  <template class="SeahorsePrefsKeyservers" parent="AdwPreferencesPage">
     <property name="visible">True</property>
     <property name="title" translatable="yes">Keyservers</property>
     <child>
-      <object class="HdyPreferencesGroup">
+      <object class="AdwPreferencesGroup">
         <property name="visible">True</property>
         <property name="title" translatable="yes">Keyservers</property>
         <child>
@@ -46,7 +46,7 @@
       </object>
     </child>
     <child>
-      <object class="HdyPreferencesGroup">
+      <object class="AdwPreferencesGroup">
         <property name="visible">True</property>
         <property name="title" translatable="yes">Key Synchronization</property>
         <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/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..ee179cd6 100644
--- a/gkr/gkr-item-add.vala
+++ b/gkr/gkr-item-add.vala
@@ -22,10 +22,9 @@ public class Seahorse.Gkr.ItemAdd : Gtk.Dialog {
     [GtkChild]
     private unowned Gtk.ComboBox item_keyring_combo;
     [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]
@@ -42,7 +41,9 @@ public class Seahorse.Gkr.ItemAdd : Gtk.Dialog {
         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()) {
+        var backend = Backend.instance();
+        for (uint i = 0; i < backend.get_n_items(); i++) {
+            var keyring = (Gkr.Keyring) backend.get_item(i);
             Gtk.TreeIter iter;
             store.append(out iter);
             store.set(iter, 0, keyring.label,
@@ -51,13 +52,10 @@ public class Seahorse.Gkr.ItemAdd : Gtk.Dialog {
                 this.item_keyring_combo.set_active_iter(iter);
         }
 
+        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 +66,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,7 +85,7 @@ 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;
 
@@ -101,13 +99,11 @@ public class Seahorse.Gkr.ItemAdd : Gtk.Dialog {
         var keyring = (Keyring) collection;
         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, collection);
                 }
             } catch (Error e) {
                 Util.show_error(this, _("Couldn’t unlock"), e.message);
@@ -115,10 +111,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 +122,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.vala b/gkr/gkr-keyring.vala
index a7c8113b..756d8dc7 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,19 +92,27 @@ 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);
@@ -153,36 +151,37 @@ public class Keyring : Secret.Collection, Gcr.Collection, Place, Deletable, Lock
                return true;
        }
 
-       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();
-
-               foreach (var item in items) {
-                       var object_path = item.get_object_path();
-                       seen.add(object_path);
-
-                       if (_items.lookup(object_path) == null) {
-                               item.set("place", this);
-                               _items.insert(object_path, (Item)item);
-                               emit_added(item);
-                       }
-               }
-
-               /* 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,7 +189,7 @@ public class Keyring : Secret.Collection, Gcr.Collection, Place, Deletable, Lock
 
     public void set_as_default() {
         var parent = null;
-               var service = this.service;
+        var service = this.service;
 
                service.set_alias.begin("default", this, null, (obj, res) => {
                        try {
@@ -258,7 +257,7 @@ class KeyringDeleter : Deleter {
        private Keyring? _keyring;
        private GLib.List<GLib.Object> _objects;
 
-       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 delete the password keyring “%s”?"),
                                              this._keyring.label);
@@ -296,4 +295,3 @@ class KeyringDeleter : Deleter {
 }
 
 }
-}
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..83695bce 100644
--- a/gkr/seahorse-gkr-add-item.ui
+++ b/gkr/seahorse-gkr-add-item.ui
@@ -1,110 +1,63 @@
 <?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="AdwActionRow">
+                <property name="title" translatable="yes">_Keyring</property>
                 <property name="use_underline">True</property>
+                <child type="suffix">
+                  <object class="GtkComboBox" id="item_keyring_combo">
+                    <property name="valign">center</property>
+                  </object>
+                </child>
               </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,10 +70,6 @@
                   <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>
@@ -128,15 +77,14 @@
     </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/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..f8bb6645 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')
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-uid.c b/pgp/seahorse-gpgme-add-uid.c
index ed9b70a2..96cbca89 100644
--- a/pgp/seahorse-gpgme-add-uid.c
+++ b/pgp/seahorse-gpgme-add-uid.c
@@ -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_entry));
     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_entry));
     return strlen (email) == 0 || g_pattern_match_simple ("?*@?*", email);
 }
 
@@ -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_entry));
+    email = gtk_editable_get_text (GTK_EDITABLE (dialog->email_entry));
+    comment = gtk_editable_get_text (GTK_EDITABLE (dialog->comment_entry));
 
-    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..316c142b 100644
--- a/pgp/seahorse-gpgme-add-uid.ui
+++ b/pgp/seahorse-gpgme-add-uid.ui
@@ -1,10 +1,8 @@
 <?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">
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..4e66daa5 100644
--- a/pgp/seahorse-gpgme-expires-dialog.ui
+++ b/pgp/seahorse-gpgme-expires-dialog.ui
@@ -1,50 +1,34 @@
 <?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">_Change</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..5857d086 100644
--- a/pgp/seahorse-gpgme-generate-dialog.c
+++ b/pgp/seahorse-gpgme-generate-dialog.c
@@ -49,14 +49,15 @@ 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 *bits_spin;
 
-    GtkWidget *expires_check;
+    GtkWidget *expires_switch;
     GtkWidget *expires_datepicker;
 };
 
@@ -104,6 +105,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 +189,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,31 +221,25 @@ 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)
+on_gpgme_generate_expires_notify (GObject    *object,
+                                  GParamSpec *pspec,
+                                  void       *user_data)
 {
     SeahorseGpgmeGenerateDialog *self = SEAHORSE_GPGME_GENERATE_DIALOG (user_data);
+       gboolean never_expires;
 
-    gtk_widget_set_sensitive (self->expires_datepicker,
-                              !gtk_toggle_button_get_active (button));
+       never_expires = gtk_switch_get_active (GTK_SWITCH (self->expires_switch));
+    gtk_widget_set_sensitive (self->expires_datepicker, !never_expires);
 }
 
 /* Changes the bit range depending on the algorithm set */
@@ -207,15 +253,15 @@ on_gpgme_generate_algorithm_changed (GtkComboBox *combo,
     sel = gtk_combo_box_get_active (combo);
     g_assert (sel < (int)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
@@ -233,13 +279,13 @@ 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));
@@ -247,14 +293,14 @@ seahorse_gpgme_generate_dialog_response (GtkDialog *dialog, int response)
     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 */
@@ -362,15 +408,16 @@ 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, 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_choice);
-    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeGenerateDialog, bits_entry);
+    gtk_widget_class_bind_template_child (widget_class, SeahorseGpgmeGenerateDialog, bits_spin);
     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_expires_notify);
     gtk_widget_class_bind_template_callback (widget_class, on_gpgme_generate_algorithm_changed);
 
     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..940dfccf 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>
@@ -16,264 +15,109 @@
   </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="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="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="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="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="AdwActionRow" id="algorithm_row">
+                <property name="title" translatable="yes">Encryption _Type</property>
+                <property name="use-underline">True</property>
+                <child type="suffix">
                   <object class="GtkComboBoxText" id="algorithm_choice">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
+                    <property name="valign">center</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>
+            </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>
+                    <signal name="notify" handler="on_gpgme_generate_expires_notify"/>
                   </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_row">
+                <property name="title" translatable="yes">E_xpiration Date</property>
+                <property name="use-underline">True</property>
+                <child type="suffix">
+                  <object class="SeahorseDatePicker" id="expires_datepicker">
+                    <property name="valign">center</property>
+                    <property name="sensitive">False</property>
                   </object>
-                  <packing>
-                    <property name="top_attach">6</property>
-                    <property name="left_attach">1</property>
-                  </packing>
                 </child>
               </object>
             </child>
@@ -284,22 +128,13 @@
     <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..8f9599f2 100644
--- a/pgp/seahorse-gpgme-revoke-dialog.c
+++ b/pgp/seahorse-gpgme-revoke-dialog.c
@@ -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_entry));
 
     err = seahorse_gpgme_key_op_revoke_subkey (self->subkey, reason, description);
     if (!GPG_IS_OK (err))
diff --git a/pgp/seahorse-gpgme-sign-dialog.c b/pgp/seahorse-gpgme-sign-dialog.c
index 1059f29e..7e504d68 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
@@ -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"));
     }
@@ -258,7 +259,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 +269,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 +290,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..884dfef2 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">
       <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>
@@ -41,12 +34,10 @@
         </child>
         <child>
           <object class="GtkBox" id="vbox6">
-            <property name="visible">True</property>
             <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,26 +48,21 @@
             </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">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>
@@ -86,8 +72,6 @@
                         <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>
@@ -98,8 +82,6 @@
                         <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>
@@ -111,17 +93,14 @@
                     </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>
                         <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>
@@ -134,7 +113,6 @@
                             </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>
@@ -151,11 +129,8 @@
                                 <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>
@@ -170,8 +145,6 @@
                                 </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>
@@ -196,12 +169,10 @@
         </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>
@@ -211,13 +182,11 @@
             </child>
             <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>
@@ -231,8 +200,6 @@
                 </child>
                 <child>
                   <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>
                   </object>
@@ -243,7 +210,6 @@
                 </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>
@@ -257,7 +223,6 @@
                 </child>
                 <child>
                   <object class="GtkSwitch" id="sign_option_revocable">
-                    <property name="visible">True</property>
                     <property name="tooltip_text">If signature can be revoked</property>
                     <property name="active">True</property>
                   </object>
@@ -271,44 +236,10 @@
           </object>
         </child>
         <child>
-          <object class="GtkBox" id="signer_frame">
-            <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="xalign">0</property>
-                <property name="label" translatable="yes">Sign key as:</property>
-                <attributes>
-                 <attribute name="weight" value="bold"/>
-                </attributes>
-              </object>
-            </child>
+          <object class="GtkListBox" id="signer_frame">
             <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="label" translatable="yes">_Sign key as</property>
               </object>
             </child>
           </object>
@@ -318,8 +249,6 @@
     <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>
@@ -327,8 +256,6 @@
     </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>
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-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-key-properties.c b/pgp/seahorse-pgp-key-properties.c
index 4c308195..eeabe985 100644
--- a/pgp/seahorse-pgp-key-properties.c
+++ b/pgp/seahorse-pgp-key-properties.c
@@ -73,7 +73,6 @@ struct _SeahorsePgpKeyProperties {
     GtkWidget *expired_message;
 
     GtkImage *photoid;
-    GtkEventBox *photo_event_box;
 
     GtkWidget *name_label;
     GtkWidget *email_label;
@@ -125,59 +124,6 @@ on_uids_add (GSimpleAction *action, GVariant *param, void *user_data)
                                        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)
 {
@@ -343,32 +289,6 @@ on_photos_previous (GSimpleAction *action, GVariant *param, void *user_data)
     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);
-}
-
 static void
 on_gpgme_key_change_pass_done (GObject      *source,
                                GAsyncResult *res,
@@ -408,19 +328,17 @@ setup_titlebar (SeahorsePgpKeyProperties *self)
 {
     const char *name;
     GtkWidget *headerbar;
-    GtkWidget *menu_icon;
     GtkWidget *menu_button;
     g_autofree char *title = NULL;
+    GtkWidget *title_widget;
 
     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);
+    gtk_header_bar_set_show_title_buttons (GTK_HEADER_BAR (headerbar), TRUE);
 
     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_icon_name (GTK_MENU_BUTTON (menu_button), "open-menu-symbolic");
     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);
@@ -434,7 +352,8 @@ setup_titlebar (SeahorsePgpKeyProperties *self)
         title = g_strdup_printf (_("%s — Public key"), name);
         gtk_widget_hide (menu_button);
     }
-    gtk_header_bar_set_title (GTK_HEADER_BAR (headerbar), title);
+    title_widget = adw_window_title_new (title, "");
+    gtk_header_bar_set_title_widget (GTK_HEADER_BAR (headerbar), title_widget);
 
     gtk_window_set_titlebar (GTK_WINDOW (self), headerbar);
 }
@@ -551,34 +470,41 @@ on_add_subkey_completed (GObject *object, GAsyncResult *res, void *user_data)
 }
 
 static void
-on_subkeys_add (GSimpleAction *action, GVariant *param, void *user_data)
+on_add_subkey_response (GtkDialog *dialog, int response, void *user_data)
 {
     SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
-    SeahorseGpgmeAddSubkey *dialog;
-    int response;
+    SeahorseGpgmeAddSubkey *add_dialog = SEAHORSE_GPGME_ADD_SUBKEY (dialog);
     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));
+        gtk_window_destroy (GTK_WINDOW (add_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);
+    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_widget_destroy (GTK_WIDGET (dialog));
+    gtk_window_destroy (GTK_WINDOW (add_dialog));
+}
+
+static void
+on_subkeys_add (GSimpleAction *action, GVariant *param, void *user_data)
+{
+    SeahorsePgpKeyProperties *self = SEAHORSE_PGP_KEY_PROPERTIES (user_data);
+    SeahorseGpgmeAddSubkey *dialog;
+
+    g_return_if_fail (SEAHORSE_GPGME_IS_KEY (self->key));
+
+    dialog = seahorse_gpgme_add_subkey_new (SEAHORSE_GPGME_KEY (self->key),
+                                            GTK_WINDOW (self));
+    g_signal_connect (dialog, "response", G_CALLBACK (on_add_subkey_response), self);
+    gtk_window_present (GTK_WINDOW (dialog));
 }
 
 static void
@@ -671,8 +597,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
@@ -792,9 +718,8 @@ on_sign_key (GSimpleAction *action, GVariant *param, void *user_data)
     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
@@ -931,44 +856,36 @@ get_common_widgets (SeahorsePgpKeyProperties *self, GtkBuilder *builder)
     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);
+    gtk_box_append (GTK_BOX (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);
+    gtk_box_append (GTK_BOX (self->subkeys_container), subkeys_listbox);
 }
 
 static void
 create_public_key_dialog (SeahorsePgpKeyProperties *self)
 {
     g_autoptr(GtkBuilder) builder = NULL;
-    GtkWidget *content_area, *content;
+    GtkWidget *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);
+       // XXX
+    //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);
+    gtk_window_set_child (GTK_WINDOW (self), content);
 
     g_action_map_add_action_entries (G_ACTION_MAP (self->action_group),
                                      PUBLIC_KEY_ACTIONS,
@@ -995,28 +912,27 @@ 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);
+                                user);
+    adw_action_row_set_subtitle (ADW_ACTION_ROW (self->trust_sign_row), sign_text);
+    adw_action_row_set_subtitle_lines (ADW_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);
+    adw_action_row_set_subtitle (ADW_ACTION_ROW (self->trust_revoke_row), revoke_text);
+    adw_action_row_set_subtitle_lines (ADW_ACTION_ROW (self->trust_revoke_row), 0);
 }
 
 static void
 create_private_key_dialog (SeahorsePgpKeyProperties *self)
 {
     g_autoptr(GtkBuilder) builder = NULL;
-    GtkWidget *content_area, *content;
+    GtkWidget *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);
+    gtk_window_set_child (GTK_WINDOW (self), content);
 
     g_action_map_add_action_entries (G_ACTION_MAP (self->action_group),
                                      PRIVATE_KEY_ACTIONS,
@@ -1036,15 +952,6 @@ create_private_key_dialog (SeahorsePgpKeyProperties *self)
     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
@@ -1119,7 +1026,6 @@ 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);
 }
 
 static void
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-private-key-properties.ui b/pgp/seahorse-pgp-private-key-properties.ui
index 857012c9..90ed423e 100644
--- a/pgp/seahorse-pgp-private-key-properties.ui
+++ b/pgp/seahorse-pgp-private-key-properties.ui
@@ -1,6 +1,5 @@
 <?xml version="1.0"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
   <object class="GtkListStore" id="model1">
     <columns>
       <!-- column-name gchararray -->
@@ -43,104 +42,69 @@
     </section>
   </menu>
       <object class="GtkBox" id="window_content">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
         <child>
           <object class="GtkInfoBar" id="revoked_area">
-            <property name="visible">True</property>
             <property name="message-type">error</property>
-            <property name="spacing">6</property>
-            <child internal-child="content_area">
-              <object class="GtkContainer">
-                <property name="visible">True</property>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <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>
+            <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="spacing">12</property>
             <property name="message-type">error</property>
-            <property name="orientation">horizontal</property>
-            <child internal-child="content_area">
-              <object class="GtkContainer">
-                <property name="visible">True</property>
-                <child>
-                  <object class="GtkLabel" id="expired_message">
-                    <property name="visible">True</property>
-                    <property name="xalign">0</property>
-                    <property name="margin-start">12</property>
-                    <property name="label">This key expired on (placeholder)</property>
-                  </object>
-                </child>
+            <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="visible">True</property>
             <property name="propagate-natural-height">True</property>
             <property name="vscrollbar-policy">automatic</property>
             <property name="hscrollbar-policy">never</property>
             <child>
-              <object class="HdyClamp">
-                <property name="visible">True</property>
+              <object class="AdwClamp">
                 <child>
                   <object class="GtkBox">
-                    <property name="visible">True</property>
                     <property name="orientation">vertical</property>
                     <property name="spacing">18</property>
-                    <property name="margin">18</property>
                     <child>
                       <object class="GtkBox">
-                        <property name="visible">True</property>
                         <property name="orientation">horizontal</property>
                         <property name="spacing">12</property>
                         <child>
                           <object class="GtkBox" id="owner-photo-frame">
-                            <property name="visible">True</property>
                             <property name="orientation">vertical</property>
                             <property name="spacing">6</property>
-                            <property name="margin">6</property>
                             <property name="margin-top">0</property>
                             <child>
-                              <object class="GtkEventBox" id="photo-event-box">
-                                <property name="visible">True</property>
-                                <child>
-                                  <object class="GtkImage" id="photoid">
-                                    <property name="width_request">150</property>
-                                    <property name="height_request">160</property>
-                                    <property name="visible">True</property>
-                                    <property name="icon-name">avatar-default-symbolic</property>
-                                    <property name="icon-size">6</property>
-                                  </object>
-                                </child>
+                              <object class="GtkImage" id="photoid">
+                                <property name="width_request">150</property>
+                                <property name="height_request">160</property>
+                                <property name="icon-name">avatar-default-symbolic</property>
                               </object>
                             </child>
                             <child>
                               <object class="GtkBox">
-                                <property name="visible">True</property>
                                 <property name="orientation">horizontal</property>
                                 <property name="spacing">6</property>
                                 <child>
                                   <object class="GtkButton" id="owner-photo-add-button">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">True</property>
                                     <property name="receives_default">False</property>
                                     <property name="action-name">props.photos.add</property>
                                     <property name="tooltip-text" translatable="yes">Add a photo to this 
key</property>
                                     <child>
                                       <object class="GtkImage">
-                                        <property name="visible">True</property>
                                         <property name="icon-name">list-add-symbolic</property>
                                       </object>
                                     </child>
@@ -148,14 +112,11 @@
                                 </child>
                                 <child>
                                   <object class="GtkButton" id="owner-photo-delete-button">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">True</property>
                                     <property name="receives_default">False</property>
                                     <property name="action-name">props.photos.delete</property>
                                     <property name="tooltip-text" translatable="yes">Remove this photo from 
this key</property>
                                     <child>
                                       <object class="GtkImage">
-                                        <property name="visible">True</property>
                                         <property name="icon-name">list-remove-symbolic</property>
                                       </object>
                                     </child>
@@ -163,14 +124,11 @@
                                 </child>
                                 <child>
                                   <object class="GtkButton" id="owner-photo-primary-button">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">True</property>
                                     <property name="receives_default">False</property>
                                     <property name="action-name">props.photos.make-primary</property>
                                     <property name="tooltip-text" translatable="yes">Make this photo the 
primary photo</property>
                                     <child>
                                       <object class="GtkImage">
-                                        <property name="visible">True</property>
                                         <property name="icon-name">emblem-default-symbolic</property>
                                       </object>
                                     </child>
@@ -178,20 +136,16 @@
                                 </child>
                                 <child>
                                   <object class="GtkLabel">
-                                    <property name="visible">True</property>
                                   </object>
                                 </child>
                                 <child>
                                   <object class="GtkButton" id="photo_previous_button">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">True</property>
                                     <property name="receives_default">False</property>
                                     <property name="focus_on_click">False</property>
                                     <property name="action-name">props.photos.previous</property>
                                     <property name="tooltip-text" translatable="yes">Go to previous 
photo</property>
                                     <child>
                                       <object class="GtkImage">
-                                        <property name="visible">True</property>
                                         <property name="icon-name">go-previous-symbolic</property>
                                       </object>
                                     </child>
@@ -199,14 +153,11 @@
                                 </child>
                                 <child>
                                   <object class="GtkButton" id="photo_next_button">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">True</property>
                                     <property name="receives_default">False</property>
                                     <property name="action-name">props.photos.next</property>
                                     <property name="tooltip-text" translatable="yes">Go to next 
photo</property>
                                     <child>
                                       <object class="GtkImage">
-                                        <property name="visible">True</property>
                                         <property name="icon-name">go-next-symbolic</property>
                                       </object>
                                     </child>
@@ -218,20 +169,15 @@
                         </child>
                         <child>
                           <object class="GtkGrid">
-                            <property name="visible">True</property>
-                            <property name="border_width">3</property>
                             <property name="column_spacing">12</property>
                             <property name="row_spacing">6</property>
                             <child>
                               <object class="GtkBox">
-                                <property name="visible">True</property>
                                 <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="visible">True</property>
-                                    <property name="can_focus">True</property>
                                     <property name="xalign">0</property>
                                     <property name="selectable">True</property>
                                     <attributes>
@@ -241,19 +187,17 @@
                                 </child>
                                 <child>
                                   <object class="GtkLabel" id="email_label">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">True</property>
                                     <property name="xalign">0</property>
                                     <property name="selectable">True</property>
                                     <property name="use-markup">True</property>
                                   </object>
                                 </child>
+                                <layout>
+                                  <property name="row">0</property>
+                                  <property name="column">0</property>
+                                  <property name="column-span">2</property>
+                                </layout>
                               </object>
-                              <packing>
-                                <property name="top_attach">0</property>
-                                <property name="left_attach">0</property>
-                                <property name="width">2</property>
-                              </packing>
                             </child>
                             <child>
                               <object class="GtkLabel">
@@ -263,70 +207,62 @@
                                 <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="comment_label">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
                                 <property name="xalign">0</property>
                                 <property name="selectable">True</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="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>
-                              <packing>
-                                <property name="top_attach">2</property>
-                                <property name="left_attach">0</property>
-                              </packing>
                             </child>
                             <child>
                               <object class="GtkLabel" id="keyid_label">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
                                 <property name="xalign">0</property>
                                 <property name="selectable">True</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="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>
-                              <packing>
-                                <property name="top_attach">3</property>
-                                <property name="left_attach">0</property>
-                              </packing>
                             </child>
                             <child>
                               <object class="GtkLabel" id="fingerprint_label">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
                                 <property name="xalign">0</property>
                                 <property name="selectable">True</property>
                                 <property name="wrap">True</property>
@@ -334,46 +270,39 @@
                                 <property name="max-width-chars">24</property>
                                 <property name="width-chars">24</property>
                                 <property name="lines">2</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="GtkLabel">
-                                <property name="visible">True</property>
                                 <property name="xalign">1</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>
-                              <packing>
-                                <property name="top_attach">4</property>
-                                <property name="left_attach">0</property>
-                              </packing>
                             </child>
                             <child>
                               <object class="GtkBox">
-                                <property name="visible">True</property>
                                 <property name="spacing">6</property>
                                 <child>
                                   <object class="GtkLabel" id="expires_label">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">True</property>
                                     <property name="xalign">0</property>
                                   </object>
                                 </child>
                                 <child>
                                   <object class="GtkButton" id="details_expires_button">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">True</property>
                                     <property name="receives_default">False</property>
                                     <property name="action-name">props.change-expires</property>
                                     <child>
                                       <object class="GtkImage">
-                                        <property name="visible">True</property>
                                         <property name="icon_name">x-office-calendar-symbolic</property>
                                       </object>
                                     </child>
@@ -382,11 +311,11 @@
                                     </style>
                                   </object>
                                 </child>
+                                <layout>
+                                  <property name="row">4</property>
+                                  <property name="column">1</property>
+                                </layout>
                               </object>
-                              <packing>
-                                <property name="top_attach">4</property>
-                                <property name="left_attach">1</property>
-                              </packing>
                             </child>
                           </object>
                         </child>
@@ -394,16 +323,13 @@
                     </child>
                     <child>
                       <object class="GtkBox" id="uids_container">
-                        <property name="visible">True</property>
                         <property name="orientation">vertical</property>
                         <property name="spacing">6</property>
                         <child>
                           <object class="GtkBox">
-                            <property name="visible">True</property>
                             <property name="orientation">horizontal</property>
                             <child>
                               <object class="GtkLabel">
-                                <property name="visible">True</property>
                                 <property name="xalign">0</property>
                                 <property name="label" translatable="yes">User IDs</property>
                                 <attributes>
@@ -413,16 +339,11 @@
                             </child>
                             <child>
                               <object class="GtkButton">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
                                 <property name="receives_default">False</property>
                                 <property name="action-name">props.uids.add</property>
                                 <property name="use-underline">True</property>
                                 <property name="label" translatable="yes">Add user ID</property>
                               </object>
-                              <packing>
-                                <property name="pack_type">end</property>
-                              </packing>
                             </child>
                           </object>
                         </child>
@@ -430,16 +351,13 @@
                     </child>
                     <child>
                       <object class="GtkBox" id="subkeys_container">
-                        <property name="visible">True</property>
                         <property name="orientation">vertical</property>
                         <property name="spacing">6</property>
                         <child>
                           <object class="GtkBox">
-                            <property name="visible">True</property>
                             <property name="orientation">horizontal</property>
                             <child>
                               <object class="GtkLabel">
-                                <property name="visible">True</property>
                                 <property name="hexpand">True</property>
                                 <property name="xalign">0</property>
                                 <property name="label" translatable="yes">Subkeys</property>
@@ -450,8 +368,6 @@
                             </child>
                             <child>
                               <object class="GtkButton">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
                                 <property name="receives_default">False</property>
                                 <property name="action-name">props.subkeys.add</property>
                                 <property name="use-underline">True</property>
@@ -464,13 +380,10 @@
                     </child>
                     <child>
                       <object class="GtkBox" id="trust_section">
-                        <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="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="yalign">0</property>
                             <property name="label" translatable="yes">Trust</property>
@@ -482,20 +395,16 @@
                         </child>
                         <child>
                           <object class="GtkListBox">
-                            <property name="visible">True</property>
                             <property name="selection-mode">none</property>
                             <style>
                               <class name="content"/>
                             </style>
                             <child>
-                              <object class="HdyActionRow" id="indicate_trust_row">
-                                <property name="visible">True</property>
+                              <object class="AdwActionRow" id="indicate_trust_row">
                                 <property name="title" translatable="yes">Override owner trust</property>
                                 <child>
                                   <object class="GtkComboBox" id="ownertrust_combobox">
-                                    <property name="visible">True</property>
                                     <property name="valign">center</property>
-                                    <property name="can_focus">False</property>
                                     <property name="model">model1</property>
                                     <child>
                                       <object class="GtkCellRendererText"/>
diff --git a/pgp/seahorse-pgp-public-key-properties.ui b/pgp/seahorse-pgp-public-key-properties.ui
index 18eec594..51f90e74 100644
--- a/pgp/seahorse-pgp-public-key-properties.ui
+++ b/pgp/seahorse-pgp-public-key-properties.ui
@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
-  <requires lib="gtk+" version="3.22"/>
   <object class="GtkListStore" id="model1">
     <columns>
       <!-- column-name gchararray -->
@@ -25,107 +24,70 @@
     </data>
   </object>
       <object class="GtkBox" id="window_content">
-        <property name="visible">True</property>
         <property name="orientation">vertical</property>
         <child>
           <object class="GtkInfoBar" id="revoked_area">
-            <property name="visible">True</property>
             <property name="message-type">error</property>
-            <property name="spacing">6</property>
-            <child internal-child="content_area">
-              <object class="GtkContainer">
-                <property name="visible">True</property>
-                <child>
-                  <object class="GtkLabel">
-                    <property name="visible">True</property>
-                    <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>
+            <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="spacing">12</property>
             <property name="message-type">error</property>
-            <property name="orientation">horizontal</property>
-            <child internal-child="content_area">
-              <object class="GtkContainer">
-                <property name="visible">True</property>
-                <child>
-                  <object class="GtkLabel" id="expired_message">
-                    <property name="visible">True</property>
-                    <property name="xalign">0</property>
-                    <property name="margin-start">12</property>
-                    <property name="label">This key expired on (placeholder)</property>
-                  </object>
-                </child>
+            <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="visible">True</property>
             <property name="propagate-natural-height">True</property>
             <property name="vscrollbar-policy">automatic</property>
             <property name="hscrollbar-policy">never</property>
             <child>
-              <object class="HdyClamp">
-                <property name="visible">True</property>
+              <object class="AdwClamp">
                 <child>
                   <object class="GtkBox">
-                    <property name="visible">True</property>
                     <property name="orientation">vertical</property>
-                    <property name="can_focus">False</property>
                     <property name="spacing">18</property>
-                    <property name="margin">18</property>
                     <child>
                       <object class="GtkBox" id="summary_container">
-                        <property name="visible">True</property>
                         <property name="orientation">horizontal</property>
                         <property name="spacing">12</property>
                         <child>
                           <object class="GtkBox">
-                            <property name="visible">True</property>
                             <property name="orientation">vertical</property>
                             <property name="spacing">6</property>
                             <child>
-                              <object class="GtkEventBox" id="photo-event-box">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <child>
-                                  <object class="GtkImage" id="photoid">
-                                    <property name="width_request">150</property>
-                                    <property name="height_request">160</property>
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
-                                    <property name="icon-name">avatar-default-symbolic</property>
-                                    <property name="icon-size">6</property>
-                                  </object>
-                                </child>
+                              <object class="GtkImage" id="photoid">
+                                <property name="width_request">150</property>
+                                <property name="height_request">160</property>
+                                <property name="icon-name">avatar-default-symbolic</property>
                               </object>
                             </child>
                             <child>
                               <object class="GtkBox" id="key-controls">
-                                <property name="visible">True</property>
                                 <property name="orientation">horizontal</property>
                                 <property name="halign">center</property>
                                 <property name="spacing">6</property>
                                 <child>
                                   <object class="GtkButton" id="photo_previous_button">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">True</property>
                                     <property name="receives_default">False</property>
                                     <property name="focus_on_click">False</property>
                                     <property name="action-name">props.photos.previous</property>
                                     <property name="tooltip-text" translatable="yes">Go to previous 
photo</property>
                                     <child>
                                       <object class="GtkImage">
-                                        <property name="visible">True</property>
                                         <property name="icon-name">go-previous-symbolic</property>
                                       </object>
                                     </child>
@@ -133,20 +95,15 @@
                                 </child>
                                 <child>
                                   <object class="GtkButton" id="photo_next_button">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">True</property>
                                     <property name="receives_default">False</property>
                                     <property name="action-name">props.photos.next</property>
                                     <child>
                                       <object class="GtkBox">
-                                        <property name="visible">True</property>
                                         <property name="orientation">horizontal</property>
-                                        <property name="can_focus">False</property>
                                         <property name="spacing">2</property>
                                         <property name="tooltip-text" translatable="yes">Go to next 
photo</property>
                                         <child>
                                           <object class="GtkImage">
-                                            <property name="visible">True</property>
                                             <property name="icon-name">go-next-symbolic</property>
                                           </object>
                                         </child>
@@ -160,26 +117,19 @@
                         </child>
                         <child>
                           <object class="GtkBox">
-                            <property name="visible">True</property>
                             <property name="orientation">vertical</property>
                             <property name="spacing">6</property>
                             <child>
                               <object class="GtkGrid">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="margin">3</property>
                                 <property name="column_spacing">12</property>
                                 <property name="row_spacing">6</property>
                                 <child>
                                   <object class="GtkBox">
-                                    <property name="visible">True</property>
                                     <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="visible">True</property>
-                                        <property name="can_focus">True</property>
                                         <property name="halign">start</property>
                                         <property name="selectable">True</property>
                                         <attributes>
@@ -189,94 +139,81 @@
                                     </child>
                                     <child>
                                       <object class="GtkLabel" id="email_label">
-                                        <property name="visible">True</property>
-                                        <property name="can_focus">True</property>
                                         <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>
-                                  <packing>
-                                    <property name="top_attach">0</property>
-                                    <property name="left_attach">0</property>
-                                    <property name="width">2</property>
-                                  </packing>
                                 </child>
                                 <child>
                                   <object class="GtkLabel">
                                     <property name="visible" bind-source="comment_label" 
bind-property="visible" bind-flags="sync-create" />
-                                    <property name="can_focus">False</property>
                                     <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>
-                                  <packing>
-                                    <property name="top_attach">1</property>
-                                    <property name="left_attach">0</property>
-                                  </packing>
                                 </child>
                                 <child>
                                   <object class="GtkLabel" id="comment_label">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">True</property>
                                     <property name="xalign">0</property>
                                     <property name="selectable">True</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="can_focus">False</property>
                                     <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>
-                                  <packing>
-                                    <property name="top_attach">2</property>
-                                    <property name="left_attach">0</property>
-                                  </packing>
                                 </child>
                                 <child>
                                   <object class="GtkLabel" id="keyid_label">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">True</property>
                                     <property name="xalign">0</property>
                                     <property name="selectable">True</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="can_focus">False</property>
                                     <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>
-                                  <packing>
-                                    <property name="top_attach">3</property>
-                                    <property name="left_attach">0</property>
-                                  </packing>
                                 </child>
                                 <child>
                                   <object class="GtkLabel" id="fingerprint_label">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">True</property>
                                     <property name="xalign">0</property>
                                     <property name="selectable">True</property>
                                     <property name="wrap">True</property>
@@ -284,37 +221,34 @@
                                     <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>
-                                  <packing>
-                                    <property name="top_attach">3</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">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>
-                                  <packing>
-                                    <property name="top_attach">4</property>
-                                    <property name="left_attach">0</property>
-                                  </packing>
                                 </child>
                                 <child>
                                   <object class="GtkLabel" id="expires_label">
-                                    <property name="visible">True</property>
                                     <property name="xalign">0</property>
+                                    <layout>
+                                      <property name="row">4</property>
+                                      <property name="column">1</property>
+                                    </layout>
                                   </object>
-                                  <packing>
-                                    <property name="top_attach">4</property>
-                                    <property name="left_attach">1</property>
-                                  </packing>
                                 </child>
                               </object>
                             </child>
@@ -324,12 +258,10 @@
                     </child>
                     <child>
                       <object class="GtkBox" id="uids_container">
-                        <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="label" translatable="yes">User IDs</property>
                             <attributes>
@@ -341,16 +273,13 @@
                     </child>
                     <child>
                       <object class="GtkBox" id="subkeys_container">
-                        <property name="visible">True</property>
                         <property name="orientation">vertical</property>
                         <property name="spacing">6</property>
                         <child>
                           <object class="GtkBox">
-                            <property name="visible">True</property>
                             <property name="orientation">horizontal</property>
                             <child>
                               <object class="GtkLabel">
-                                <property name="visible">True</property>
                                 <property name="hexpand">True</property>
                                 <property name="xalign">0</property>
                                 <property name="label" translatable="yes">Subkeys</property>
@@ -365,20 +294,14 @@
                     </child>
                     <child>
                       <object class="GtkBox" id="trust-page">
-                        <property name="visible">True</property>
                         <property name="orientation">vertical</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">24</property>
                         <child>
                           <object class="GtkBox">
-                            <property name="visible">True</property>
                             <property name="orientation">vertical</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">6</property>
                             <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">Trust</property>
@@ -390,22 +313,18 @@
                             </child>
                             <child>
                               <object class="GtkListBox">
-                                <property name="visible">True</property>
                                 <property name="selection-mode">none</property>
                                 <style>
                                   <class name="content"/>
                                 </style>
                                 <child>
-                                  <object class="HdyActionRow">
-                                    <property name="visible">True</property>
+                                  <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="visible">True</property>
                                         <property name="halign">end</property>
                                         <property name="valign">center</property>
-                                        <property name="can_focus">True</property>
                                         <property name="receives_default">False</property>
                                         <property name="action-name">props.trust-marginal</property>
                                       </object>
@@ -413,15 +332,12 @@
                                   </object>
                                 </child>
                                 <child>
-                                  <object class="HdyActionRow" id="trust_sign_row">
-                                    <property name="visible">True</property>
+                                  <object class="AdwActionRow" id="trust_sign_row">
                                     <property name="title" translatable="yes">Sign key</property>
                                     <child>
                                       <object class="GtkButton" id="sign-button">
-                                        <property name="visible">True</property>
                                         <property name="halign">end</property>
                                         <property name="valign">center</property>
-                                        <property name="can_focus">True</property>
                                         <property name="label" translatable="yes">_Sign key</property>
                                         <property name="use_underline">True</property>
                                         <property name="receives_default">False</property>
@@ -431,15 +347,12 @@
                                   </object>
                                 </child>
                                 <child>
-                                  <object class="HdyActionRow" id="trust_revoke_row">
-                                    <property name="visible">True</property>
+                                  <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="visible">True</property>
                                         <property name="halign">end</property>
                                         <property name="valign">center</property>
-                                        <property name="can_focus">True</property>
                                         <property name="label" translatable="yes">_Revoke</property>
                                         <property name="use_underline">True</property>
                                         <property name="receives_default">False</property>
@@ -449,15 +362,12 @@
                                   </object>
                                 </child>
                                 <child>
-                                  <object class="HdyActionRow" id="indicate_trust_row">
-                                    <property name="visible">True</property>
+                                  <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="visible">True</property>
                                         <property name="valign">center</property>
-                                        <property name="can_focus">False</property>
                                         <property name="model">model1</property>
                                         <child>
                                           <object class="GtkCellRendererText"/>
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..b6ec834e 100644
--- a/pgp/seahorse-pgp-subkey-list-box.c
+++ b/pgp/seahorse-pgp-subkey-list-box.c
@@ -29,9 +29,11 @@
 #include <glib/gi18n.h>
 
 struct _SeahorsePgpSubkeyListBox {
-    GtkListBox parent_instance;
+    AdwBin parent_instance;
 
     SeahorsePgpKey *key;
+
+    GtkWidget *listbox;
 };
 
 enum {
@@ -41,7 +43,7 @@ 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_BIN)
 
 static void
 seahorse_pgp_subkey_list_box_set_property (GObject      *object,
@@ -53,7 +55,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;
     }
 }
@@ -91,7 +93,7 @@ seahorse_pgp_subkey_list_box_constructed (GObject *obj)
 
     G_OBJECT_CLASS (seahorse_pgp_subkey_list_box_parent_class)->constructed (obj);
 
-    gtk_list_box_bind_model (GTK_LIST_BOX (self),
+    gtk_list_box_bind_model (GTK_LIST_BOX (self->listbox),
                              seahorse_pgp_key_get_subkeys (self->key),
                              create_subkey_row,
                              self,
@@ -101,10 +103,11 @@ seahorse_pgp_subkey_list_box_constructed (GObject *obj)
 static void
 seahorse_pgp_subkey_list_box_init (SeahorsePgpSubkeyListBox *self)
 {
-    GtkStyleContext *style_context;
-
-    style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
-    gtk_style_context_add_class (style_context, "content");
+    self->listbox = gtk_list_box_new ();
+    gtk_list_box_set_selection_mode (GTK_LIST_BOX (self->listbox),
+                                     GTK_SELECTION_NONE);
+    adw_bin_set_child (ADW_BIN (self), self->listbox);
+    gtk_widget_add_css_class (GTK_WIDGET (self->listbox), "boxed-list");
 }
 
 static void
@@ -139,7 +142,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 +156,7 @@ seahorse_pgp_subkey_list_box_get_key (SeahorsePgpSubkeyListBox *self)
 /* SeahorsePhpSubkeyListBoxRow */
 
 struct _SeahorsePgpSubkeyListBoxRow {
-    HdyExpanderRow parent_instance;
+    AdwExpanderRow parent_instance;
 
     SeahorsePgpSubkey *subkey;
 
@@ -175,14 +177,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 +204,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 +224,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 +238,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 +281,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 +296,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..64b03d5e 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)
+                      AdwBin)
 
 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..db183aec 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;
+    AdwBin parent_instance;
 
     SeahorsePgpKey *key;
+
+    GtkWidget *listbox;
 };
 
 enum {
@@ -45,7 +47,7 @@ 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_BIN);
 
 static GtkWidget *
 create_row_for_uid (void *item, void *user_data)
@@ -64,7 +66,7 @@ seahorse_pgp_uid_list_box_constructed (GObject *object)
 
     G_OBJECT_CLASS (seahorse_pgp_uid_list_box_parent_class)->constructed (object);
 
-    gtk_list_box_bind_model (GTK_LIST_BOX (self),
+    gtk_list_box_bind_model (GTK_LIST_BOX (self->listbox),
                              seahorse_pgp_key_get_uids (self->key),
                              create_row_for_uid,
                              self,
@@ -74,10 +76,11 @@ seahorse_pgp_uid_list_box_constructed (GObject *object)
 static void
 seahorse_pgp_uid_list_box_init (SeahorsePgpUidListBox *self)
 {
-    GtkStyleContext *style_context;
-
-    style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
-    gtk_style_context_add_class (style_context, "content");
+    self->listbox = gtk_list_box_new ();
+    gtk_list_box_set_selection_mode (GTK_LIST_BOX (self->listbox),
+                                     GTK_SELECTION_NONE);
+    adw_bin_set_child (ADW_BIN (self), self->listbox);
+    gtk_widget_add_css_class (GTK_WIDGET (self->listbox), "boxed-list");
 }
 
 static void
@@ -147,14 +150,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 +174,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)
@@ -256,16 +258,19 @@ on_uid_delete (GSimpleAction *action, GVariant *param, void *user_data)
 
     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)));
 
+    // XXX
+#if 0
     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"));
+#endif
 }
 
 static void
@@ -292,7 +297,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 +314,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 +340,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 +376,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 +396,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 +447,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..641ffa64 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)
+                      AdwBin)
 
 #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..ca7095e7 100644
--- a/pkcs11/pkcs11-certificate.vala
+++ b/pkcs11/pkcs11-certificate.vala
@@ -21,224 +21,210 @@
  * 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.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 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(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_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();
+    }
+
+    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 ();
+    }
 }
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..5abe2bc0 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();
 
         /* ... */
 
@@ -82,7 +83,7 @@ public class Seahorse.Pkcs11.Properties : Gtk.Dialog {
 
         this.export_button.set_visible(exporters != null);
 
-        this._viewer.grab_focus();
+//         this._viewer.grab_focus();
     }
 
     public override void dispose() {
@@ -105,18 +106,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 +149,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 +166,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 +186,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..2747cef4 100644
--- a/src/import-dialog.vala
+++ b/src/import-dialog.vala
@@ -20,61 +20,62 @@
  */
 
 public class Seahorse.ImportDialog : Gtk.Dialog {
+    //XXX
 
-    private Gcr.ViewerWidget viewer;
-    private Gcr.ImportButton import;
+    // 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
-        );
+        // GLib.Object(
+        //     transient_for: parent,
+        //     title: _("Data to be imported"),
+        //     use_header_bar: 1
+        // );
 
-        Gtk.Widget button = new Gtk.Button.with_mnemonic(_("_Cancel"));
-        button.show();
-        add_action_widget(button, Gtk.ResponseType.CANCEL);
+        // Gtk.Widget button = new Gtk.Button.with_mnemonic(_("_Cancel"));
+        // button.show();
+        // 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);
+        // 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);
 
-        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()).pack_end(this.viewer);
+        // 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()).pack_end(this.viewer);
     }
 
     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);
+        // 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);
+        // 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);
-            }
+        //     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);
-        }
+        // } 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..bf514258 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,15 +56,13 @@ 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            },
@@ -78,18 +80,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 +105,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 +118,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 +148,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,7 +189,7 @@ public class Seahorse.KeyManager : Catalog {
         base.selection_changed();
 
         var objects = get_selected_objects();
-        foreach (weak Backend backend in get_backends())
+        foreach (unowned var backend in Backend.get_registered())
             backend.actions.set_actions_for_selected_objects(objects);
     }
 
@@ -199,19 +199,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 +220,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 +244,20 @@ 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;
+        // XXX still necesssary?
+        // bool folded = this.content_box.folded;
 
-        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.back_revealer.visible =
+        //     folded && this.content_b.visible_child == right_header;
     }
 
     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 +266,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 +301,66 @@ 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);
+            }
+        });
     }
 
     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 +373,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 +404,21 @@ public class Seahorse.KeyManager : Catalog {
         }
     }
 
-    private Gcr.Collection setup_sidebar() {
+    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()) {
+        foreach (unowned var backend in Backend.get_registered()) {
             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).
@@ -455,36 +444,28 @@ 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;
+        var place = this.sidebar.selection.selected_item as Lockable;
         return_if_fail(place != null && place.unlockable);
 
         unlock_button.sensitive = false;
@@ -502,19 +483,22 @@ public class Seahorse.KeyManager : Catalog {
     }
 
     [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 +506,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 +514,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 +524,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/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..aefc0ff0 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,19 +56,12 @@
   </menu>
 
   <object class="GtkPopover" id="new_item_menu_popover">
-    <signal name="grab-notify" handler="on_popover_grab_notify" after="yes"/>
     <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>
         <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"/>
@@ -77,23 +69,19 @@
             <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>
                 <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>
@@ -111,23 +99,19 @@
             <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>
@@ -145,23 +129,19 @@
             <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>
                 <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>
@@ -179,23 +159,19 @@
             <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>
@@ -213,23 +189,19 @@
             <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>
                 <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>
@@ -247,16 +219,12 @@
             <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>
                   </object>
                 </child>
@@ -271,324 +239,242 @@
   <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>
+
+    <!-- Event controllers -->
+    <child>
+      <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-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="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"/>
-            <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>
-                <child>
-                  <object class="GtkMenuButton" id="new_item_button">
-                    <property name="visible">True</property>
-                    <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>
-                  </object>
-                  <packing>
-                    <property name="pack_type">start</property>
-                  </packing>
-                </child>
+          <object class="AdwLeafletPage">
+            <property name="name">sidebar-pane</property>
+            <property name="child">
+              <object class="GtkBox">
+                <property name="orientation">vertical</property>
                 <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 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>
-                    </child>
-                  </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>
-            <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"/>
+                    </property>
+                    <binding name="show-end-title-buttons">
+                      <lookup name="folded">content_box</lookup>
+                    </binding>
                     <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>
+                      <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>
                         <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 class="GtkImage">
+                            <property name="icon_name">list-add-symbolic</property>
                           </object>
                         </child>
                       </object>
                     </child>
-                  </object>
-                </child>
-                <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>
+                    <child type="end">
+                      <object class="GtkMenuButton" id="main_menu_button">
+                        <property name="focus_on_click">False</property>
+                        <property name="menu-model">main_menu</property>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="icon_name">open-menu-symbolic</property>
+                          </object>
+                        </child>
                       </object>
                     </child>
                   </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="GtkScrolledWindow" id="sidebar_area">
+                    <property name="hscrollbar-policy">never</property>
+                    <property name="vexpand">True</property>
                   </object>
-                  <packing>
-                    <property name="pack_type">end</property>
-                  </packing>
                 </child>
               </object>
-              <packing>
-                <property name="name">item-list-pane</property>
-              </packing>
-            </child>
+            </property>
           </object>
         </child>
-      </object>
-    </child>
 
-    <child>
-      <object class="HdyLeaflet" id="content_box">
-        <property name="visible">True</property>
-        <property name="vexpand">True</property>
-        <property name="can_focus">False</property>
-        <property name="can-swipe-back">True</property>
-        <property name="transition-type">over</property>
-        <signal name="notify::fold" handler="on_fold" object="SeahorseKeyManager" after="yes" swapped="no"/>
-        <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>
-            <property name="name">sidebar-pane</property>
-          </packing>
-        </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">
+                        <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>
+                          <object class="GtkButton" id="back">
+                            <property name="valign">center</property>
+                            <property name="use-underline">True</property>
+                            <signal name="clicked" handler="on_back_clicked"/>
                             <style>
-                              <class name="content"/>
+                              <class name="image-button"/>
                             </style>
+                            <child>
+                              <object class="GtkImage" id="back_image">
+                                <property name="icon-name">go-previous-symbolic</property>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child type="end">
+                      <object class="GtkMenuButton" id="items_menu_button">
+                        <property name="focus_on_click">False</property>
+                        <property name="menu-model">items_menu</property>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="icon_name">view-more-symbolic</property>
+                          </object>
+                        </child>
+                      </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>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="icon_name">edit-find-symbolic</property>
                           </object>
                         </child>
                       </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="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="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="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..0753efa4 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.ListStore 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,14 @@ 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);
+
+        var backends = new GLib.ListStore(typeof(Backend));
+        var places = new Gtk.FlattenListModel(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 +58,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 +72,9 @@ public class Seahorse.SearchProvider : GLib.Object {
                 return;
         }
 
-        var backends = Backend.get_registered();
-        this.n_loading = (int) backends.length();
-        foreach (var backend in backends) {
+        var reg_backends = Backend.get_registered();
+        this.n_loading = (int) reg_backends.length();
+        foreach (var backend in reg_backends) {
             if (!backend.loaded) {
                 backend.notify["loaded"].connect(() => {
                     change_n_loading(-1);
@@ -85,11 +85,7 @@ public class Seahorse.SearchProvider : GLib.Object {
                 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);
+            backends.append(backend);
         }
     }
 
@@ -113,7 +109,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 +134,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 +152,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 +187,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 +228,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..21943fc9 100644
--- a/src/sidebar.vala
+++ b/src/sidebar.vala
@@ -18,82 +18,44 @@
  * <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);
+        // Load backends
+        var backends = new GLib.ListStore(typeof(Backend));
+        foreach (unowned var backend in Backend.get_registered())
+            backends.append(backend);
 
-        if (place.get_length() > 0 || place.show_if_empty)
-            this.store.insert_sorted(place, compare_places);
-        place.notify.connect(on_place_changed);
-    }
+        // ListModels everywhere \o/
+        this.places = new Gtk.FlattenListModel(backends);
 
-    private void on_place_changed(GLib.Object obj, ParamSpec pspec) {
-        update_places();
-    }
+        var sorter = new Gtk.CustomSorter(compare_places);
+        var places_sorted = new Gtk.SortListModel(this.places, sorter);
 
-    private void on_place_removed(Gcr.Collection? backend, GLib.Object place_obj) {
-        var place = place_obj as Place;
-        return_if_fail (place != null);
+        this.selection = new Gtk.SingleSelection(places_sorted);
 
-        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;
-            }
-        }
-    }
+        // 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);
 
-    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) {
@@ -103,12 +65,13 @@ public class Seahorse.Sidebar : Gtk.ListBox {
     }
 
     private void on_sidebar_item_changed(SidebarItem item) {
-        select_row(item);
-        current_collection_changed();
+        // need to find proper index here
+        // this.places.items_changed(item.get_index(), 1, 1);
+        this.selection.selected = item.get_index();
     }
 
     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 +84,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 +93,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,113 +106,33 @@ public class Seahorse.Sidebar : Gtk.ListBox {
     }
 
     private void on_row_selected(Gtk.ListBoxRow? row) {
-        debug("Updating objects");
-
-        // First clear the list
-        foreach (var place in this.objects.elements())
-            this.objects.remove(place);
+        debug("Updating selected");
 
-        // 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;
-    }
-
-    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);
+        for (uint i = 0; i < this.selection.get_n_items(); i++) {
+            var place = (Place) this.selection.get_item(i);
+            if (place.uri == uri) {
+                this.selection.selected = i;
                 return true;
             }
         }
@@ -263,10 +146,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,21 +166,13 @@ 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;
     }
 }
 
@@ -311,38 +186,33 @@ internal class Seahorse.SidebarItem : Gtk.ListBoxRow {
 
     construct {
       var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
-      box.get_style_context().add_class("seahorse-sidebar-item");
+      box.add_css_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);
+      set_child(box);
 
       var label = new Gtk.Label(place.label);
       label.ellipsize = Pango.EllipsizeMode.END;
       label.xalign = 0f;
-      box.pack_start(label);
+      label.hexpand = true;
+      box.append(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 = new Gtk.Button.from_icon_name(get_lock_icon_name(lockable));
+          this.lock_button.add_css_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());
+                  place_unlock(lockable, (Gtk.Window) get_root());
               else if (lockable.lockable)
-                  place_lock(lockable, (Gtk.Window) get_toplevel());
+                  place_lock(lockable, (Gtk.Window) get_root());
 
               update_lock_icon(lockable);
           });
-          box.pack_end(this.lock_button, false, false);
+          box.append(this.lock_button);
       }
-
-      show_all();
     }
 
     private static unowned string? get_lock_icon_name(Lockable lockable) {
@@ -356,7 +226,7 @@ internal class Seahorse.SidebarItem : Gtk.ListBoxRow {
     }
 
     private void update_lock_icon(Lockable lockable) {
-        ((Gtk.Image) this.lock_button.get_image()).icon_name = get_lock_icon_name(lockable);
+        this.lock_button.icon_name = get_lock_icon_name(lockable);
     }
 
     public SidebarItem(Seahorse.Place place) {
@@ -364,6 +234,8 @@ internal class Seahorse.SidebarItem : Gtk.ListBoxRow {
     }
 
     public void show_popup_menu() {
+        // XXX
+#if 0
         // 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();
@@ -397,7 +269,7 @@ internal class Seahorse.SidebarItem : Gtk.ListBoxRow {
         // 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()));
+            item.activate.connect(() => Viewable.view(this.place, (Gtk.Window) item.get_root()));
             menu.append(item);
             item.show();
         }
@@ -414,6 +286,7 @@ internal class Seahorse.SidebarItem : Gtk.ListBoxRow {
         } else {
             menu.destroy();
         }
+#endif
     }
 
     private void place_lock(Lockable lockable, Gtk.Window? window) {
@@ -432,12 +305,12 @@ internal class Seahorse.SidebarItem : Gtk.ListBoxRow {
     }
 
     private void on_place_lock(Gtk.Widget widget, Lockable lockable) {
-        place_lock(lockable, (Gtk.Window) widget.get_toplevel());
+        place_lock(lockable, (Gtk.Window) widget.get_root());
     }
 
     private void place_unlock(Lockable lockable, Gtk.Window? window) {
-        Cancellable cancellable = new Cancellable();
-        TlsInteraction interaction = new Interaction(window);
+        var cancellable = new Cancellable();
+        var interaction = new Seahorse.Interaction(window);
 
         lockable.unlock.begin(interaction, cancellable, (obj, res) => {
             try {
@@ -450,20 +323,20 @@ internal class Seahorse.SidebarItem : Gtk.ListBoxRow {
         });
     }
 
-    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);
-                }
-            });
-        }
-    }
+    // private void on_place_unlock(Gtk.MenuItem widget, Lockable lockable) {
+    //     place_unlock(lockable, (Gtk.Window) widget.get_root());
+    // }
+
+    // private void on_place_delete(Gtk.MenuItem item, Deletable deletable) {
+    //     Deleter deleter = deletable.create_deleter();
+    //     if (deleter.prompt((Gtk.Window) item.get_root())) {
+    //         deleter.delete.begin(null, (obj, res) => {
+    //             try {
+    //                 deleter.delete.end(res);
+    //             } catch (Error e) {
+    //                 Util.show_error(parent, _("Couldn’t delete"), e.message);
+    //             }
+    //         });
+    //     }
+    // }
 }
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..021808f3 100644
--- a/ssh/generate.vala
+++ b/ssh/generate.vala
@@ -27,10 +27,10 @@ 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;
 
@@ -44,8 +44,8 @@ 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);
@@ -65,7 +65,7 @@ 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();
diff --git a/ssh/key-length-chooser.vala b/ssh/key-length-chooser.vala
index be65e4fc..bc767ddb 100644
--- a/ssh/key-length-chooser.vala
+++ b/ssh/key-length-chooser.vala
@@ -26,7 +26,7 @@
  * 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
@@ -35,12 +35,13 @@ public class Seahorse.Ssh.KeyLengthChooser : Gtk.Stack {
     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();
@@ -48,13 +49,11 @@ public class Seahorse.Ssh.KeyLengthChooser : Gtk.Stack {
         this.combobox.append_text("384");
         this.combobox.append_text("521");
         this.combobox.active = 0;
-        add(this.combobox);
+        stack.add_child(this.combobox);
 
         // 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);
@@ -81,24 +80,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.combobox;
                 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..f7a00130 100644
--- a/ssh/seahorse-ssh-generate.ui
+++ b/ssh/seahorse-ssh-generate.ui
@@ -1,207 +1,82 @@
 <?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="AdwActionRow" id="algo_row">
+                <property name="title" translatable="yes">Encryption _Type</property>
+                <property name="use_underline">True</property>
+                <child type="suffix">
+                  <object class="GtkComboBoxText" id="algorithm_combo_box">
+                    <property name="valign">center</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="expand">True</property>
-                    <property name="fill">True</property>
-                    <property name="position">1</property>
-                  </packing>
                 </child>
               </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/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..90fe7709 100644
--- a/ssh/upload.vala
+++ b/ssh/upload.vala
@@ -98,22 +98,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]