diff --git a/pressure-vessel/launcher.c b/pressure-vessel/launcher.c
index 252d5d5a4e3a3a44819932ae411e588f9fe60997..37e3b31faaeee2a1d03e7097fc1591fb6065f37c 100644
--- a/pressure-vessel/launcher.c
+++ b/pressure-vessel/launcher.c
@@ -50,21 +50,10 @@
 #include "portal-listener.h"
 #include "utils.h"
 
-typedef GCredentials AutoCredentials;
-G_DEFINE_AUTOPTR_CLEANUP_FUNC(AutoCredentials, g_object_unref)
-
-typedef GDBusAuthObserver AutoDBusAuthObserver;
-G_DEFINE_AUTOPTR_CLEANUP_FUNC(AutoDBusAuthObserver, g_object_unref)
-
-typedef GDBusServer AutoDBusServer;
-G_DEFINE_AUTOPTR_CLEANUP_FUNC(AutoDBusServer, g_object_unref)
-
 static PvPortalListener *global_listener;
 
 static GHashTable *lock_env_hash = NULL;
-static GDBusConnection *session_bus = NULL;
 static GHashTable *client_pid_data_hash = NULL;
-static guint name_owner_id = 0;
 static GMainLoop *main_loop;
 static PvLauncher1 *launcher;
 
@@ -85,10 +74,7 @@ unref_skeleton_in_timeout_cb (gpointer user_data)
 static void
 unref_skeleton_in_timeout (void)
 {
-  if (name_owner_id)
-    g_bus_unown_name (name_owner_id);
-
-  name_owner_id = 0;
+  pv_portal_listener_release_name (global_listener);
 
   /* After we've lost the name we drop the main ref on the helper
      so that we'll exit when it drops to zero. However, if there are
@@ -578,9 +564,9 @@ export_launcher (GDBusConnection *connection,
 }
 
 static void
-on_bus_acquired (GDBusConnection *connection,
-                 const gchar     *name,
-                 gpointer         user_data)
+on_bus_acquired (PvPortalListener *listener,
+                 GDBusConnection *connection,
+                 gpointer user_data)
 {
   g_autoptr(GError) error = NULL;
   int *ret = user_data;
@@ -607,9 +593,10 @@ on_bus_acquired (GDBusConnection *connection,
 }
 
 static void
-on_name_acquired (GDBusConnection *connection,
-                  const gchar     *name,
-                  gpointer         user_data)
+on_name_acquired (PvPortalListener *listener,
+                  GDBusConnection *connection,
+                  const gchar *name,
+                  gpointer user_data)
 {
   int *ret = user_data;
 
@@ -624,93 +611,15 @@ on_name_acquired (GDBusConnection *connection,
 }
 
 static void
-on_name_lost (GDBusConnection *connection,
-              const gchar     *name,
-              gpointer         user_data)
+on_name_lost (PvPortalListener *listener,
+              GDBusConnection *connection,
+              const gchar *name,
+              gpointer user_data)
 {
   g_debug ("Name lost");
   unref_skeleton_in_timeout ();
 }
 
-#if GLIB_CHECK_VERSION(2, 34, 0)
-/*
- * Callback for GDBusServer::allow-mechanism.
- * Only allow the (most secure) EXTERNAL authentication mechanism,
- * if possible.
- */
-static gboolean
-allow_external_cb (G_GNUC_UNUSED GDBusAuthObserver *observer,
-                   const char *mechanism,
-                   G_GNUC_UNUSED gpointer user_data)
-{
-  return (g_strcmp0 (mechanism, "EXTERNAL") == 0);
-}
-#endif
-
-/*
- * Callback for GDBusServer::authorize-authenticated-peer.
- * Only allow D-Bus connections from a matching uid.
- */
-static gboolean
-authorize_authenticated_peer_cb (G_GNUC_UNUSED GDBusAuthObserver *observer,
-                                 G_GNUC_UNUSED GIOStream *stream,
-                                 GCredentials *credentials,
-                                 G_GNUC_UNUSED gpointer user_data)
-{
-  g_autoptr(AutoCredentials) retry_credentials = NULL;
-  g_autoptr(AutoCredentials) myself = NULL;
-  g_autofree gchar *credentials_str = NULL;
-
-  if (g_credentials_get_unix_user (credentials, NULL) == (uid_t) -1)
-    {
-      /* Work around https://gitlab.gnome.org/GNOME/glib/issues/1831:
-       * older GLib versions might retrieve incomplete credentials.
-       * Make them try again. */
-      credentials = NULL;
-    }
-
-  if (credentials == NULL && G_IS_SOCKET_CONNECTION (stream))
-    {
-      /* Continue to work around
-       * https://gitlab.gnome.org/GNOME/glib/issues/1831:
-       * older GLib versions might retrieve incomplete or no credentials. */
-      GSocket *sock;
-
-      sock = g_socket_connection_get_socket (G_SOCKET_CONNECTION (stream));
-      retry_credentials = g_socket_get_credentials (sock, NULL);
-      credentials = retry_credentials;
-    }
-
-  myself = g_credentials_new ();
-
-  if (credentials != NULL &&
-      g_credentials_is_same_user (credentials, myself, NULL))
-    return TRUE;
-
-  if (credentials == NULL)
-    credentials_str = g_strdup ("peer with unknown credentials");
-  else
-    credentials_str = g_credentials_to_string (credentials);
-
-  g_warning ("Rejecting connection from %s", credentials_str);
-  return FALSE;
-}
-
-static GDBusAuthObserver *
-observer_new (void)
-{
-  g_autoptr(AutoDBusAuthObserver) observer = g_dbus_auth_observer_new ();
-
-#if GLIB_CHECK_VERSION(2, 34, 0)
-  g_signal_connect (observer, "allow-mechanism",
-                    G_CALLBACK (allow_external_cb), NULL);
-#endif
-  g_signal_connect (observer, "authorize-authenticated-peer",
-                    G_CALLBACK (authorize_authenticated_peer_cb), NULL);
-
-  return g_steal_pointer (&observer);
-}
-
 /*
  * Callback for GDBusConnection::closed.
  */
@@ -724,67 +633,12 @@ peer_connection_closed_cb (GDBusConnection *connection,
   g_object_unref (connection);
 }
 
-/*
- * Double-check credentials of a peer, since we are working with older
- * versions of GLib that can't necessarily be completely relied on.
- * We are willing to execute arbitrary code on behalf of an authenticated
- * connection, so it seems worthwhile to be extra-careful.
- */
-static gboolean
-check_credentials (GDBusConnection *connection,
-                   GError **error)
-{
-  struct ucred creds;
-  socklen_t len;
-  GIOStream *stream;
-  GSocket *sock;
-  int sockfd;
-
-  stream = g_dbus_connection_get_stream (connection);
-  len = sizeof (creds);
-
-  if (!G_IS_SOCKET_CONNECTION (stream))
-    return glnx_throw (error, "Incoming D-Bus connection is not a socket?");
-
-  sock = g_socket_connection_get_socket (G_SOCKET_CONNECTION (stream));
-
-  if (sock == NULL)
-    return glnx_throw (error,
-                       "Incoming D-Bus connection does not have a socket?");
-
-  sockfd = g_socket_get_fd (sock);
-
-  if (getsockopt (sockfd, SOL_SOCKET, SO_PEERCRED, &creds, &len) < 0)
-    return glnx_throw_errno_prefix (error, "Unable to check credentials");
-
-  if (creds.uid != geteuid ())
-    return glnx_throw (error,
-                       "Connection from uid %ld != %ld should have been "
-                       "rejected already",
-                       (long) creds.uid, (long) geteuid ());
-
-  return TRUE;
-}
-
-/*
- * Callback for GDBusServer::new-connection.
- *
- * Returns: %TRUE if the new connection attempt was handled, even if
- *  unsuccessfully.
- */
 static gboolean
-new_connection_cb (GDBusServer *server,
+new_connection_cb (PvPortalListener *listener,
                    GDBusConnection *connection,
                    gpointer user_data)
 {
-  g_autoptr(GError) error = NULL;
-
-  if (!check_credentials (connection, &error))
-    {
-      g_warning ("Credentials verification failed: %s", error->message);
-      g_dbus_connection_close (connection, NULL, NULL, NULL);
-      return TRUE;  /* handled, unsuccessfully */
-    }
+  GError *error = NULL;
 
   /* Paired with g_object_unref() in peer_connection_closed_cb() */
   g_object_ref (connection);
@@ -801,62 +655,6 @@ new_connection_cb (GDBusServer *server,
   return G_DBUS_METHOD_INVOCATION_HANDLED;
 }
 
-static GDBusServer *
-listen_on_address (const char *address,
-                   GError **error)
-{
-  g_autofree gchar *guid = NULL;
-  g_autoptr(AutoDBusAuthObserver) observer = NULL;
-  g_autoptr(AutoDBusServer) server = NULL;
-
-  guid = g_dbus_generate_guid ();
-
-  observer = observer_new ();
-
-  server = g_dbus_server_new_sync (address,
-                                   G_DBUS_SERVER_FLAGS_NONE,
-                                   guid,
-                                   observer,
-                                   NULL,
-                                   error);
-
-  if (server == NULL)
-    return NULL;
-
-  g_signal_connect (server, "new-connection",
-                    G_CALLBACK (new_connection_cb), NULL);
-  g_dbus_server_start (server);
-
-  return g_steal_pointer (&server);
-}
-
-static GDBusServer *
-listen_on_socket (const char *name,
-                  GError **error)
-{
-  g_autofree gchar *address = NULL;
-  g_autofree gchar *escaped = NULL;
-
-  if (name[0] == '@')
-    {
-      escaped = g_dbus_address_escape_value (&name[1]);
-      address = g_strdup_printf ("unix:abstract=%s", escaped);
-    }
-  else if (name[0] == '/')
-    {
-      escaped = g_dbus_address_escape_value (name);
-      address = g_strdup_printf ("unix:path=%s", escaped);
-    }
-  else
-    {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "Invalid socket address '%s'", name);
-      return FALSE;
-    }
-
-  return listen_on_address (address, error);
-}
-
 static gboolean
 signal_handler (int sfd,
                 G_GNUC_UNUSED GIOCondition condition,
@@ -1090,13 +888,13 @@ int
 main (int argc,
       char *argv[])
 {
-  g_autoptr(AutoDBusServer) server = NULL;
   g_autoptr(GOptionContext) context = NULL;
   guint signals_id = 0;
   guint exit_on_readable_id = 0;
   g_autoptr(GError) local_error = NULL;
   GError **error = &local_error;
   int ret = EX_USAGE;
+  GBusNameOwnerFlags flags;
 
   my_pid = getpid ();
 
@@ -1219,108 +1017,39 @@ main (int argc,
   /* Exit with this status until we know otherwise */
   ret = EX_SOFTWARE;
 
-  if (opt_bus_name != NULL)
-    {
-      GBusNameOwnerFlags flags;
-
-      g_debug ("Connecting to D-Bus session bus...");
-
-      session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
-
-      if (session_bus == NULL)
-        {
-          glnx_prefix_error (error, "Can't find session bus");
-          goto out;
-        }
-
-      flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT;
-      if (opt_replace)
-        flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE;
-
-      ret = EX_UNAVAILABLE;
-      g_debug ("Claiming bus name %s...", opt_bus_name);
-      name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
-                                      opt_bus_name,
-                                      flags,
-                                      on_bus_acquired,
-                                      on_name_acquired,
-                                      on_name_lost,
-                                      &ret,
-                                      NULL);
-    }
-  else if (opt_socket != NULL)
-    {
-      g_debug ("Listening on socket %s...", opt_socket);
-      server = listen_on_socket (opt_socket, error);
-
-      if (server == NULL)
-        {
-          glnx_prefix_error (error,
-                             "Unable to listen on socket \"%s\"", opt_socket);
-          goto out;
-        }
-
-      ret = 0;
-    }
-  else if (opt_socket_directory != NULL)
-    {
-      g_autofree gchar *unique = NULL;
-      g_autofree gchar *dir = NULL;
-
-      if (strlen (opt_socket_directory) > PV_MAX_SOCKET_DIRECTORY_LEN)
-        {
-          glnx_throw (error, "Socket directory path \"%s\" too long",
-                      opt_socket_directory);
-          goto out;
-        }
-
-      dir = realpath (opt_socket_directory, NULL);
-
-      if (strlen (dir) > PV_MAX_SOCKET_DIRECTORY_LEN)
-        {
-          glnx_throw (error, "Socket directory path \"%s\" too long", dir);
-          goto out;
-        }
-
-      g_debug ("Listening on a socket in %s...", opt_socket_directory);
-
-      /* @unique is long and random, so we assume it is not guessable
-       * by an attacker seeking to deny service by using the name we
-       * intended to use; so we don't need a retry loop for alternative
-       * names in the same directory. */
-      unique = pv_get_random_uuid (error);
-
-      if (unique == NULL)
-        goto out;
-
-      opt_socket = g_build_filename (dir, unique, NULL);
-      g_debug ("Chosen socket is %s", opt_socket);
-      server = listen_on_socket (opt_socket, error);
-
-      if (server == NULL)
-        {
-          glnx_prefix_error (error,
-                             "Unable to listen on socket \"%s\"", opt_socket);
-          goto out;
-        }
+  g_signal_connect (global_listener,
+                    "new-peer-connection", G_CALLBACK (new_connection_cb),
+                    NULL);
+  g_signal_connect (global_listener,
+                    "session-bus-connected", G_CALLBACK (on_bus_acquired),
+                    &ret);
+  g_signal_connect (global_listener,
+                    "session-bus-name-acquired", G_CALLBACK (on_name_acquired),
+                    &ret);
+  g_signal_connect (global_listener,
+                    "session-bus-name-lost", G_CALLBACK (on_name_lost),
+                    NULL);
+
+  flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT;
+
+  if (opt_replace)
+    flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE;
+
+  if (!pv_portal_listener_listen (global_listener,
+                                  opt_bus_name,
+                                  flags,
+                                  opt_socket,
+                                  opt_socket_directory,
+                                  error))
+    goto out;
 
-      ret = 0;
-    }
+  /* If we're using the bus name method, we can't exit successfully
+   * until we claimed the bus name at least once. Otherwise we're
+   * already content. */
+  if (opt_bus_name != NULL)
+    ret = EX_UNAVAILABLE;
   else
-    {
-      /* We already checked that exactly one of them is non-NULL */
-      g_assert_not_reached ();
-    }
-
-  if (opt_socket != NULL)
-    fprintf (global_listener->info_fh, "socket=%s\n", opt_socket);
-
-  if (server != NULL)
-    fprintf (global_listener->info_fh, "dbus_address=%s\n",
-             g_dbus_server_get_client_address (server));
-
-  if (opt_bus_name == NULL)
-    pv_portal_listener_close_info_fh (global_listener, NULL);
+    ret = 0;
 
   g_debug ("Entering main loop");
 
@@ -1337,16 +1066,9 @@ out:
   if (signals_id > 0)
     g_source_remove (signals_id);
 
-  if (name_owner_id)
-    g_bus_unown_name (name_owner_id);
-
-  if (server != NULL && opt_socket != NULL)
-    unlink (opt_socket);
-
   g_free (opt_bus_name);
   g_free (opt_socket);
   g_free (opt_socket_directory);
-  g_clear_object (&session_bus);
   g_clear_object (&global_listener);
   g_hash_table_destroy (lock_env_hash);
 
diff --git a/pressure-vessel/portal-listener.c b/pressure-vessel/portal-listener.c
index d75af840fed4aa773835de7e834376c8a311ff6a..bff5152c7fb221f066e522d31b826cf7d6c5d65f 100644
--- a/pressure-vessel/portal-listener.c
+++ b/pressure-vessel/portal-listener.c
@@ -27,20 +27,58 @@
 #include "config.h"
 #include "subprojects/libglnx/config.h"
 
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#include <sysexits.h>
+#include <sys/ioctl.h>
+#include <sys/signalfd.h>
+#include <sys/socket.h>
+
+#include <glib.h>
+#include <glib-unix.h>
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+
 #include "steam-runtime-tools/glib-backports-internal.h"
 #include "steam-runtime-tools/utils-internal.h"
 #include "libglnx/libglnx.h"
 
+#include "launcher.h"
 #include "portal-listener.h"
 #include "utils.h"
 
+typedef GCredentials AutoCredentials;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(AutoCredentials, g_object_unref)
+
+typedef GDBusAuthObserver AutoDBusAuthObserver;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(AutoDBusAuthObserver, g_object_unref)
+
+typedef GDBusServer AutoDBusServer;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(AutoDBusServer, g_object_unref)
+
 struct _PvPortalListenerClass
 {
   GObjectClass parent;
+
+  gboolean (*new_peer_connection) (PvPortalListener *self,
+                                   GDBusConnection *connection,
+                                   gpointer user_data);
 };
 
 G_DEFINE_TYPE (PvPortalListener, pv_portal_listener, G_TYPE_OBJECT)
 
+enum
+{
+  NEW_PEER_CONNECTION,
+  SESSION_BUS_CONNECTED,
+  SESSION_BUS_NAME_ACQUIRED,
+  SESSION_BUS_NAME_LOST,
+  N_SIGNALS
+};
+
+static guint signal_ids[N_SIGNALS] = { 0 };
+
 void
 pv_portal_listener_init (PvPortalListener *self)
 {
@@ -123,6 +161,343 @@ pv_portal_listener_check_socket_arguments (PvPortalListener *listener,
   return TRUE;
 }
 
+static void
+on_bus_acquired (GDBusConnection *connection,
+                 const gchar *name,
+                 gpointer user_data)
+{
+  PvPortalListener *self = user_data;
+
+  g_signal_emit (self, signal_ids[SESSION_BUS_CONNECTED], 0, connection);
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+                  const gchar *name,
+                  gpointer user_data)
+{
+  PvPortalListener *self = user_data;
+
+  g_signal_emit (self, signal_ids[SESSION_BUS_NAME_ACQUIRED], 0,
+                 connection, name);
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+              const gchar *name,
+              gpointer user_data)
+{
+  PvPortalListener *self = user_data;
+
+  g_signal_emit (self, signal_ids[SESSION_BUS_NAME_LOST], 0,
+                 connection, name);
+}
+
+#if GLIB_CHECK_VERSION(2, 34, 0)
+/*
+ * Callback for GDBusServer::allow-mechanism.
+ * Only allow the (most secure) EXTERNAL authentication mechanism,
+ * if possible.
+ */
+static gboolean
+allow_external_cb (G_GNUC_UNUSED GDBusAuthObserver *observer,
+                   const char *mechanism,
+                   G_GNUC_UNUSED gpointer user_data)
+{
+  return (g_strcmp0 (mechanism, "EXTERNAL") == 0);
+}
+#endif
+
+/*
+ * Callback for GDBusServer::authorize-authenticated-peer.
+ * Only allow D-Bus connections from a matching uid.
+ */
+static gboolean
+authorize_authenticated_peer_cb (G_GNUC_UNUSED GDBusAuthObserver *observer,
+                                 G_GNUC_UNUSED GIOStream *stream,
+                                 GCredentials *credentials,
+                                 G_GNUC_UNUSED gpointer user_data)
+{
+  g_autoptr(AutoCredentials) retry_credentials = NULL;
+  g_autoptr(AutoCredentials) myself = NULL;
+  g_autofree gchar *credentials_str = NULL;
+
+  if (g_credentials_get_unix_user (credentials, NULL) == (uid_t) -1)
+    {
+      /* Work around https://gitlab.gnome.org/GNOME/glib/issues/1831:
+       * older GLib versions might retrieve incomplete credentials.
+       * Make them try again. */
+      credentials = NULL;
+    }
+
+  if (credentials == NULL && G_IS_SOCKET_CONNECTION (stream))
+    {
+      /* Continue to work around
+       * https://gitlab.gnome.org/GNOME/glib/issues/1831:
+       * older GLib versions might retrieve incomplete or no credentials. */
+      GSocket *sock;
+
+      sock = g_socket_connection_get_socket (G_SOCKET_CONNECTION (stream));
+      retry_credentials = g_socket_get_credentials (sock, NULL);
+      credentials = retry_credentials;
+    }
+
+  myself = g_credentials_new ();
+
+  if (credentials != NULL &&
+      g_credentials_is_same_user (credentials, myself, NULL))
+    return TRUE;
+
+  if (credentials == NULL)
+    credentials_str = g_strdup ("peer with unknown credentials");
+  else
+    credentials_str = g_credentials_to_string (credentials);
+
+  g_warning ("Rejecting connection from %s", credentials_str);
+  return FALSE;
+}
+
+static GDBusAuthObserver *
+observer_new (void)
+{
+  g_autoptr(AutoDBusAuthObserver) observer = g_dbus_auth_observer_new ();
+
+#if GLIB_CHECK_VERSION(2, 34, 0)
+  g_signal_connect (observer, "allow-mechanism",
+                    G_CALLBACK (allow_external_cb), NULL);
+#endif
+  g_signal_connect (observer, "authorize-authenticated-peer",
+                    G_CALLBACK (authorize_authenticated_peer_cb), NULL);
+
+  return g_steal_pointer (&observer);
+}
+
+/*
+ * Double-check credentials of a peer, since we are working with older
+ * versions of GLib that can't necessarily be completely relied on.
+ * We are willing to execute arbitrary code on behalf of an authenticated
+ * connection, so it seems worthwhile to be extra-careful.
+ */
+static gboolean
+check_credentials (GDBusConnection *connection,
+                   GError **error)
+{
+  struct ucred creds;
+  socklen_t len;
+  GIOStream *stream;
+  GSocket *sock;
+  int sockfd;
+
+  stream = g_dbus_connection_get_stream (connection);
+  len = sizeof (creds);
+
+  if (!G_IS_SOCKET_CONNECTION (stream))
+    return glnx_throw (error, "Incoming D-Bus connection is not a socket?");
+
+  sock = g_socket_connection_get_socket (G_SOCKET_CONNECTION (stream));
+
+  if (sock == NULL)
+    return glnx_throw (error,
+                       "Incoming D-Bus connection does not have a socket?");
+
+  sockfd = g_socket_get_fd (sock);
+
+  if (getsockopt (sockfd, SOL_SOCKET, SO_PEERCRED, &creds, &len) < 0)
+    return glnx_throw_errno_prefix (error, "Unable to check credentials");
+
+  if (creds.uid != geteuid ())
+    return glnx_throw (error,
+                       "Connection from uid %ld != %ld should have been "
+                       "rejected already",
+                       (long) creds.uid, (long) geteuid ());
+
+  return TRUE;
+}
+
+/*
+ * Callback for GDBusServer::new-connection.
+ *
+ * Returns: %TRUE if the new connection attempt was handled, even if
+ *  unsuccessfully.
+ */
+static gboolean
+new_connection_cb (GDBusServer *server,
+                   GDBusConnection *connection,
+                   gpointer user_data)
+{
+  PvPortalListener *self = user_data;
+  g_autoptr(GError) error = NULL;
+  gboolean handled;
+
+  g_return_val_if_fail (self->server == server, FALSE);
+
+  if (!check_credentials (connection, &error))
+    {
+      g_warning ("Credentials verification failed: %s", error->message);
+      g_dbus_connection_close (connection, NULL, NULL, NULL);
+      return TRUE;  /* handled, unsuccessfully */
+    }
+
+  g_signal_emit (self, signal_ids[NEW_PEER_CONNECTION], 0, connection,
+                 &handled);
+  g_return_val_if_fail (handled, FALSE);
+  return TRUE;
+}
+
+static GDBusServer *
+listen_on_address (PvPortalListener *self,
+                   const char *address,
+                   GError **error)
+{
+  g_autofree gchar *guid = NULL;
+  g_autoptr(AutoDBusAuthObserver) observer = NULL;
+  g_autoptr(AutoDBusServer) server = NULL;
+
+  guid = g_dbus_generate_guid ();
+
+  observer = observer_new ();
+
+  server = g_dbus_server_new_sync (address,
+                                   G_DBUS_SERVER_FLAGS_NONE,
+                                   guid,
+                                   observer,
+                                   NULL,
+                                   error);
+
+  if (server == NULL)
+    return NULL;
+
+  g_signal_connect (server, "new-connection",
+                    G_CALLBACK (new_connection_cb), self);
+  g_dbus_server_start (server);
+
+  return g_steal_pointer (&server);
+}
+
+static GDBusServer *
+listen_on_socket (PvPortalListener *self,
+                  GError **error)
+{
+  g_autofree gchar *address = NULL;
+  g_autofree gchar *escaped = NULL;
+
+  if (self->server_socket[0] == '@')
+    {
+      escaped = g_dbus_address_escape_value (&self->server_socket[1]);
+      address = g_strdup_printf ("unix:abstract=%s", escaped);
+    }
+  else if (self->server_socket[0] == '/')
+    {
+      escaped = g_dbus_address_escape_value (self->server_socket);
+      address = g_strdup_printf ("unix:path=%s", escaped);
+    }
+  else
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Invalid socket address '%s'", self->server_socket);
+      return FALSE;
+    }
+
+  return listen_on_address (self, address, error);
+}
+
+gboolean
+pv_portal_listener_listen (PvPortalListener *self,
+                           const char *opt_bus_name,
+                           GBusNameOwnerFlags flags,
+                           const char *opt_socket,
+                           const char *opt_socket_directory,
+                           GError **error)
+{
+  g_return_val_if_fail ((opt_bus_name != NULL)
+                        + (opt_socket != NULL)
+                        + (opt_socket_directory != NULL) == 1, FALSE);
+
+  if (opt_bus_name != NULL)
+    {
+      g_debug ("Connecting to D-Bus session bus...");
+
+      self->session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
+
+      if (self->session_bus == NULL)
+        return glnx_prefix_error (error, "Can't find session bus");
+
+      g_debug ("Claiming bus name %s...", opt_bus_name);
+      self->name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
+                                            opt_bus_name,
+                                            flags,
+                                            on_bus_acquired,
+                                            on_name_acquired,
+                                            on_name_lost,
+                                            self,
+                                            NULL);
+    }
+  else if (opt_socket != NULL)
+    {
+      g_debug ("Listening on socket %s...", opt_socket);
+      self->server_socket = g_strdup (opt_socket);
+      self->server = listen_on_socket (self, error);
+
+      if (self->server == NULL)
+        return glnx_prefix_error (error,
+                                  "Unable to listen on socket \"%s\"",
+                                  opt_socket);
+    }
+  else if (opt_socket_directory != NULL)
+    {
+      g_autofree gchar *unique = NULL;
+      g_autofree gchar *dir = NULL;
+
+      if (strlen (opt_socket_directory) > PV_MAX_SOCKET_DIRECTORY_LEN)
+        return glnx_throw (error, "Socket directory path \"%s\" too long",
+                           opt_socket_directory);
+
+      dir = realpath (opt_socket_directory, NULL);
+
+      if (strlen (dir) > PV_MAX_SOCKET_DIRECTORY_LEN)
+        return glnx_throw (error,
+                           "Socket directory path \"%s\" too long",
+                           dir);
+
+      g_debug ("Listening on a socket in %s...", opt_socket_directory);
+
+      /* @unique is long and random, so we assume it is not guessable
+       * by an attacker seeking to deny service by using the name we
+       * intended to use; so we don't need a retry loop for alternative
+       * names in the same directory. */
+      unique = pv_get_random_uuid (error);
+
+      if (unique == NULL)
+        return FALSE;
+
+      self->server_socket = g_build_filename (dir, unique, NULL);
+      g_debug ("Chosen socket is %s", self->server_socket);
+      self->server = listen_on_socket (self, error);
+
+      if (self->server == NULL)
+        return glnx_prefix_error (error,
+                                  "Unable to listen on socket \"%s\"",
+                                  self->server_socket);
+    }
+  else
+    {
+      g_return_val_if_reached (FALSE);
+    }
+
+  if (self->server_socket != NULL)
+    fprintf (self->info_fh, "socket=%s\n", self->server_socket);
+
+  if (self->server != NULL)
+    fprintf (self->info_fh, "dbus_address=%s\n",
+             g_dbus_server_get_client_address (self->server));
+
+  if (opt_bus_name == NULL)
+    pv_portal_listener_close_info_fh (self, NULL);
+
+  return TRUE;
+}
+
+
 /*
  * If @bus_name is non-NULL, print it to the info fd. Then
  * close the --info-fd, and also close standard output (if different).
@@ -147,12 +522,28 @@ pv_portal_listener_close_info_fh (PvPortalListener *self,
   g_clear_pointer (&self->info_fh, fclose);
 }
 
+void
+pv_portal_listener_release_name (PvPortalListener *self)
+{
+  if (self->name_owner_id)
+    g_bus_unown_name (self->name_owner_id);
+
+  self->name_owner_id = 0;
+}
+
 static void
 pv_portal_listener_dispose (GObject *object)
 {
   PvPortalListener *self = PV_PORTAL_LISTENER (object);
 
   pv_portal_listener_close_info_fh (self, NULL);
+  pv_portal_listener_release_name (self);
+
+  if (self->server != NULL && self->server_socket != NULL)
+    unlink (self->server_socket);
+
+  g_clear_object (&self->server);
+  g_clear_object (&self->session_bus);
 
   G_OBJECT_CLASS (pv_portal_listener_parent_class)->dispose (object);
 }
@@ -162,6 +553,7 @@ pv_portal_listener_finalize (GObject *object)
 {
   PvPortalListener *self = PV_PORTAL_LISTENER (object);
 
+  g_clear_pointer (&self->server_socket, g_free);
   g_clear_pointer (&self->original_environ, g_strfreev);
   g_clear_pointer (&self->original_cwd_l, g_free);
 
@@ -175,6 +567,52 @@ pv_portal_listener_class_init (PvPortalListenerClass *cls)
 
   object_class->dispose = pv_portal_listener_dispose;
   object_class->finalize = pv_portal_listener_finalize;
+
+  /*
+   * Basically the same as GDBusServer::new-connection
+   */
+  signal_ids[NEW_PEER_CONNECTION] =
+    g_signal_new ("new-peer-connection",
+                  PV_TYPE_PORTAL_LISTENER,
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (PvPortalListenerClass, new_peer_connection),
+                  g_signal_accumulator_true_handled, NULL,
+                  NULL,
+                  G_TYPE_BOOLEAN,
+                  1,
+                  G_TYPE_DBUS_CONNECTION);
+
+  signal_ids[SESSION_BUS_CONNECTED] =
+    g_signal_new ("session-bus-connected",
+                  PV_TYPE_PORTAL_LISTENER,
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  1,
+                  G_TYPE_DBUS_CONNECTION);
+
+  signal_ids[SESSION_BUS_NAME_ACQUIRED] =
+    g_signal_new ("session-bus-name-acquired",
+                  PV_TYPE_PORTAL_LISTENER,
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  2,
+                  G_TYPE_DBUS_CONNECTION,
+                  G_TYPE_STRING);
+
+  signal_ids[SESSION_BUS_NAME_LOST] =
+    g_signal_new ("session-bus-name-lost",
+                  PV_TYPE_PORTAL_LISTENER,
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  2,
+                  G_TYPE_DBUS_CONNECTION,
+                  G_TYPE_STRING);
 }
 
 PvPortalListener *
diff --git a/pressure-vessel/portal-listener.h b/pressure-vessel/portal-listener.h
index cddb174eb39e3de9d1281d9eae35f297c64b282a..38b7cca5b5788b9b8ef46af30b8995212ec135c4 100644
--- a/pressure-vessel/portal-listener.h
+++ b/pressure-vessel/portal-listener.h
@@ -53,7 +53,11 @@ struct _PvPortalListener
   GStrv original_environ;
   FILE *original_stdout;
   FILE *info_fh;
+  GDBusConnection *session_bus;
+  GDBusServer *server;
   gchar *original_cwd_l;
+  gchar *server_socket;
+  guint name_owner_id;
 };
 
 PvPortalListener *pv_portal_listener_new (void);
@@ -68,7 +72,16 @@ gboolean pv_portal_listener_check_socket_arguments (PvPortalListener *self,
                                                     const char *opt_socket_directory,
                                                     GError **error);
 
+gboolean pv_portal_listener_listen (PvPortalListener *self,
+                                    const char *opt_bus_name,
+                                    GBusNameOwnerFlags flags,
+                                    const char *opt_socket,
+                                    const char *opt_socket_directory,
+                                    GError **error);
+
 void pv_portal_listener_close_info_fh (PvPortalListener *self,
                                        const char *bus_name);
 
+void pv_portal_listener_release_name (PvPortalListener *self);
+
 G_DEFINE_AUTOPTR_CLEANUP_FUNC (PvPortalListener, g_object_unref)