diff --git a/meson.build b/meson.build
index b53590c3..2b08ec2a 100644
--- a/meson.build
+++ b/meson.build
@@ -143,7 +143,6 @@ glib = dependency('glib-2.0', version: '> 2.26')
 gio_unix = dependency('gio-unix-2.0')
 gee = dependency('gee-0.8')
 gtk3 = dependency('gtk+-3.0')
-libnotify = dependency('libnotify')
 posix = meson.get_compiler('vala').find_library('posix')
 
 #####################################################################
diff --git a/src/gnome-ask-password-agent.vala b/src/gnome-ask-password-agent.vala
index 7124d3f8..f77a14d8 100644
--- a/src/gnome-ask-password-agent.vala
+++ b/src/gnome-ask-password-agent.vala
@@ -2,6 +2,7 @@
   This file is part of systemd.
 
   Copyright 2010 Lennart Poettering
+  Copyright 2024 Ben Boeckel
 
   systemd is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
@@ -17,10 +18,10 @@
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+using Gee;
 using Gtk;
 using GLib;
 using Posix;
-using Notify;
 
 [CCode (cheader_filename = "time.h")]
 extern int clock_gettime(int id, out timespec ts);
@@ -29,8 +30,8 @@ public class PasswordDialog : Dialog {
 
         public Entry entry;
 
-        public PasswordDialog(string message, string icon) {
-                set_title("System Password");
+        public PasswordDialog(string domain, string message, string icon) {
+                set_title("%s Password".printf(domain));
                 set_border_width(8);
                 set_default_response(ResponseType.OK);
                 set_icon_name(icon);
@@ -68,87 +69,69 @@ public class PasswordDialog : Dialog {
         }
 }
 
-public class MyStatusIcon : StatusIcon {
-
+class Watch : GLib.Object {
         File directory;
-        File current;
         FileMonitor file_monitor;
 
-        string message;
-        string icon;
-        string socket;
+        private weak Application app;
 
-        PasswordDialog password_dialog;
-        Notify.Notification n;
+        string title;
+        string domain_display;
+        string domain;
 
-        public MyStatusIcon() throws GLib.Error {
-                GLib.Object(icon_name : "dialog-password");
-                set_title("System Password Request");
+        public Watch(Application gapp, string domain, string path) throws GLib.Error {
+                app = gapp;
 
-                directory = File.new_for_path("/run/systemd/ask-password/");
+                directory = File.new_for_path(path);
                 file_monitor = directory.monitor_directory(0);
                 file_monitor.changed.connect(file_monitor_changed);
 
-                current = null;
-                look_for_password();
+                domain_display = "%s%s".printf(domain.ascii_up(1), domain.substring(1));
+                title = "Password Request (%s)".printf(domain_display);
+                this.domain = domain;
 
-                activate.connect(status_icon_activate);
+                look_in_directory(directory);
         }
 
-        void file_monitor_changed(GLib.File file, GLib.File? other_file, GLib.FileMonitorEvent event_type) {
+        void look_in_directory(File dir) throws GLib.Error {
+                FileEnumerator enumerator = dir.enumerate_children("standard::name", FileQueryInfoFlags.NOFOLLOW_SYMLINKS);
+
+                FileInfo i;
+                while ((i = enumerator.next_file()) != null) {
+                        if (!i.get_name().has_prefix("ask.")) {
+                                continue;
+                        }
+
+                        load_password(dir.get_child(i.get_name()));
+                }
+        }
 
-                if (!file.get_basename().has_prefix("ask."))
+        void file_monitor_changed(GLib.File file, GLib.File? other_file, GLib.FileMonitorEvent event_type) {
+                if (!file.get_basename().has_prefix("ask.")) {
                         return;
+                }
 
                 if (event_type == FileMonitorEvent.CREATED ||
                     event_type == FileMonitorEvent.DELETED) {
                         try {
-                                look_for_password();
+                                load_password(file);
                         } catch (Error e) {
                                 show_error(e.message);
                         }
                 }
         }
 
-        void look_for_password() throws GLib.Error {
-
-                if (current != null) {
-                        if (!current.query_exists()) {
-                                current = null;
-                                if (password_dialog != null)
-                                        password_dialog.response(ResponseType.REJECT);
-                        }
-                }
-
-                if (current == null) {
-                        FileEnumerator enumerator = directory.enumerate_children("standard::name", FileQueryInfoFlags.NOFOLLOW_SYMLINKS);
-
-                        FileInfo i;
-                        while ((i = enumerator.next_file()) != null) {
-                                if (!i.get_name().has_prefix("ask."))
-                                        continue;
-
-                                current = directory.get_child(i.get_name());
-
-                                if (load_password())
-                                        break;
-
-                                current = null;
-                        }
-                }
-
-                if (current == null)
-                        set_visible(false);
-        }
-
-        bool load_password() throws GLib.Error {
-
+        bool load_password(File file) throws GLib.Error {
                 KeyFile key_file = new KeyFile();
+                int? timeout = null;
+                string socket;
+                string message;
+                string icon;
 
                 try {
                         timespec ts;
 
-                        key_file.load_from_file(current.get_path(), KeyFileFlags.NONE);
+                        key_file.load_from_file(file.get_path(), KeyFileFlags.NONE);
 
                         string not_after_as_string = key_file.get_string("Ask", "NotAfter");
 
@@ -157,11 +140,17 @@ public class MyStatusIcon : StatusIcon {
 
                         uint64 not_after = uint64.parse(not_after_as_string);;
                         if ((not_after == 0 && GLib.errno == Posix.EINVAL) ||
-                            (not_after == int64.MAX && GLib.errno == Posix.ERANGE))
+                            (not_after == int64.MAX && GLib.errno == Posix.ERANGE)) {
                                 return false;
+                        }
 
-                        if (not_after > 0 && not_after < now)
+                        if (not_after > 0 && not_after < now) {
                                 return false;
+                        }
+
+                        if (not_after > 0) {
+                                timeout = (int)(not_after - now) / 1000;
+                        }
 
                         socket = key_file.get_string("Ask", "Socket");
                 } catch (GLib.Error e) {
@@ -171,51 +160,142 @@ public class MyStatusIcon : StatusIcon {
                 try {
                         message = key_file.get_string("Ask", "Message").compress();
                 } catch (GLib.Error e) {
-                        message = "Please Enter System Password!";
+                        message = "Please Enter %s Password!".printf(domain_display);
                 }
 
-                set_tooltip_text(message);
-
                 try {
                         icon = key_file.get_string("Ask", "Icon");
                 } catch (GLib.Error e) {
                         icon = "dialog-password";
                 }
-                set_from_icon_name(icon);
 
-                n = new Notify.Notification(title, message, icon);
-                n.set_timeout(5000);
-                n.closed.connect(() => {
-                        set_visible(true);
+                GLib.Notification n = new GLib.Notification(title);
+                n.set_category("password.request." + domain);
+                n.set_body(message);
+                n.set_priority(GLib.NotificationPriority.NORMAL);
+                n.set_icon(new ThemedIcon(icon));
+                n.add_button_with_target("Enter password", "app.password-request", "(ssss)", domain, message, icon, socket);
+
+                string n_id = "password-request-%s".printf(socket);
+                app.send_notification(n_id, n);
+                if (timeout != null) {
+                        uint s = GLib.Timeout.add_once((!) timeout, () => {
+                                app.withdraw_notification(n_id);
+                                app.timeouts.unset(socket);
                 });
-                n.add_action("enter_pw", "Enter password", status_icon_activate);
-                n.show();
+                        app.timeouts[socket] = s;
+                }
 
                 return true;
         }
+}
+
+void show_error(string e) {
+        Posix.stderr.printf("%s\n", e);
+        var m = new MessageDialog(null, 0, MessageType.ERROR, ButtonsType.CLOSE, "%s", e);
+        m.run();
+        m.destroy();
+}
+
+class Application : Gtk.Application {
+        private static bool system = false;
+        private static bool user = false;
+
+        private Watch? system_watch = null;
+        private Watch? user_watch = null;
+
+        public Gee.HashMap<string, uint> timeouts;
+
+        private const OptionEntry entries[] = {
+                { "system", 's', OptionFlags.NONE, OptionArg.NONE, ref system, "Watch for system requests", null },
+                { "user", 'u', OptionFlags.NONE, OptionArg.NONE, ref user, "Watch for system requests", null },
+                { null }
+        };
+
+        private const GLib.ActionEntry actions[] = {
+                { "password-request", password_request, "(ssss)" },
+        };
+
+        public Application() {
+                Object(application_id: "org.freedesktop.systemd.gnome-ask-password-agent",
+                       flags: GLib.ApplicationFlags.IS_SERVICE);
+                add_main_option_entries(entries);
+                add_action_entries(actions, this);
+                set_default(this);
+
+                timeouts = new Gee.HashMap<string, uint>();
+        }
+
+        protected override void startup() {
+                if (system) {
+                        system_watch = add_watch("system", "/run/systemd/ask-password/");
+                }
+
+                if (user) {
+                        string? xdg_runtime_dir = Environment.get_variable("XDG_RUNTIME_DIR");
+                        if (xdg_runtime_dir == null) {
+                                show_error("no user XDG runtime directory");
+                        } else {
+                                add_watch("user", (!) xdg_runtime_dir + "/systemd/ask-password/");
+                        }
+                }
+
+                if (system_watch != null || user_watch != null) {
+                        hold();
+                } else {
+                        show_error("no watches requested");
+                }
+        }
+
+        private Watch? add_watch(string domain, string path) {
+                try {
+                        return new Watch(this, domain, path);
+                } catch (IOError e) {
+                        show_error("failed to set up %s watches on %s: %s".printf(domain, path, e.message));
+                } catch (GLib.Error e) {
+                        show_error("failed to set up %s watches on %s: %s".printf(domain, path, e.message));
+                }
+
+                return null;
+        }
 
-        void status_icon_activate() {
+        private static void password_request(GLib.SimpleAction action, GLib.Variant? variant) {
+                var gapp = GLib.Application.get_default();
+                if (gapp == null) {
+                        return;
+                }
+                var app = (Application) (!) gapp;
 
-                if (current == null)
+                if (variant.n_children() != 4) {
                         return;
+                }
+
+                string domain = variant.get_child_value(0).get_string();
+                string message = variant.get_child_value(1).get_string();
+                string icon = variant.get_child_value(2).get_string();
+                string socket = variant.get_child_value(3).get_string();
 
-                if (password_dialog != null) {
-                        password_dialog.present();
+                if (domain.length == 0 || message.length == 0 || icon.length == 0 || socket.length == 0) {
+                        show_error("invalid password request (domain: '%s', message: '%s', icon: '%s', socket: '%s')".printf(domain, message, icon, socket));
                         return;
                 }
 
-                password_dialog = new PasswordDialog(message, icon);
+                PasswordDialog password_dialog = new PasswordDialog(domain, message, icon);
 
                 int result = password_dialog.run();
                 string password = password_dialog.entry.get_text();
-
                 password_dialog.destroy();
-                password_dialog = null;
+
+                uint n_id;
+                if (app.timeouts.unset(socket, out n_id)) {
+                        GLib.Source.remove(n_id);
+                }
 
                 if (result == ResponseType.REJECT ||
                     result == ResponseType.DELETE_EVENT ||
-                    result == ResponseType.CANCEL)
+                    result == ResponseType.CANCEL) {
                         return;
+                }
 
                 Pid child_pid;
                 int to_process;
@@ -241,31 +321,15 @@ public class MyStatusIcon : StatusIcon {
                         show_error(e.message);
                 }
         }
-}
-
-const OptionEntry entries[] = {
-        { null }
-};
-
-void show_error(string e) {
-        Posix.stderr.printf("%s\n", e);
-        var m = new MessageDialog(null, 0, MessageType.ERROR, ButtonsType.CLOSE, "%s", e);
-        m.run();
-        m.destroy();
-}
 
-int main(string[] args) {
-        try {
-                Gtk.init_with_args(ref args, "[OPTION...]", entries, "systemd-ask-password-agent");
-                Notify.init("Password Agent");
-
-                MyStatusIcon i = new MyStatusIcon();
-                Gtk.main();
-        } catch (IOError e) {
-                show_error(e.message);
-        } catch (GLib.Error e) {
-                Posix.stderr.printf("%s\n", e.message);
+        public static int main(string[] args) {
+                try {
+                        Gtk.init_with_args(ref args, "[OPTION...]", entries, "systemd-ask-password-agent");
+                } catch (GLib.Error e) {
+                        Posix.stderr.printf("%s\n", e.message);
+                        return 1;
+                }
+                Application app = new Application();
+                return app.run(args);
         }
-
-        return 0;
 }
diff --git a/src/meson.build b/src/meson.build
index 449a4770..c8039614 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -8,8 +8,9 @@ install_data('systemadm.appdata.xml', install_dir: appdatadir)
 
 sgapa_files = files('gnome-ask-password-agent.vala')
 sgapa = executable('systemd-gnome-ask-password-agent', sgapa_files,
-                   dependencies: [common_flags, gtk3, gee, gio_unix, libnotify, posix],
+                   dependencies: [common_flags, gtk3, gee, gio_unix, posix],
                    install: true)
+install_data('org.freedesktop.systemd.gnome-ask-password-agent.desktop', install_dir: applicationsdir)
 install_data('systemd-gnome-ask-password-agent.rules', install_dir: polkitrulesdir)
 sgapa_units = files('systemd-gnome-ask-password-agent.path',
                     'systemd-gnome-ask-password-agent.service')
diff --git a/src/org.freedesktop.systemd.gnome-ask-password-agent.desktop b/src/org.freedesktop.systemd.gnome-ask-password-agent.desktop
new file mode 100644
index 00000000..fbcab9df
--- /dev/null
+++ b/src/org.freedesktop.systemd.gnome-ask-password-agent.desktop
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Name=systemd GNOME ask password agent
+Comment=Agent for system- and user-level password requests
+Exec=systemd-gnome-ask-password-agent
+Icon=org.freedesktop.systemd.gnome-ask-password-agent
+Terminal=false
+Type=Application
+Categories=Utility
+StartupNotify=true
+DBusActivatable=true
+X-GNOME-UsesNotifications=true
diff --git a/src/systemd-gnome-ask-password-agent.service b/src/systemd-gnome-ask-password-agent.service
index 983d0d4d..e8b627e9 100644
--- a/src/systemd-gnome-ask-password-agent.service
+++ b/src/systemd-gnome-ask-password-agent.service
@@ -12,4 +12,4 @@ Description=Forward Password Requests to GNOME Password Agent
 After=systemd-user-sessions.service
 
 [Service]
-ExecStart=systemd-gnome-ask-password-agent
+ExecStart=systemd-gnome-ask-password-agent --system --user