Wayland screensharing: implement stream restoration

Make use of "persist_mode" option in ScreenCast portal to restore
previously selected screen/window and avoid picking it again in yet
another xdg-desktop-portal dialog.

Bug: webrtc:13429
Change-Id: I3a0068091c2dd38003a7dff3f82b9cdb2ccd0f42
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/263901
Commit-Queue: Jan Grulich <grulja@gmail.com>
Reviewed-by: Alexander Cooper <alcooper@chromium.org>
Cr-Commit-Position: refs/heads/main@{#37257}
diff --git a/modules/desktop_capture/BUILD.gn b/modules/desktop_capture/BUILD.gn
index 0c59ecd..864732b 100644
--- a/modules/desktop_capture/BUILD.gn
+++ b/modules/desktop_capture/BUILD.gn
@@ -622,6 +622,8 @@
       "linux/wayland/mouse_cursor_monitor_pipewire.cc",
       "linux/wayland/mouse_cursor_monitor_pipewire.h",
       "linux/wayland/portal_request_response.h",
+      "linux/wayland/restore_token_manager.cc",
+      "linux/wayland/restore_token_manager.h",
       "linux/wayland/scoped_glib.cc",
       "linux/wayland/scoped_glib.h",
       "linux/wayland/screen_capture_portal_interface.cc",
diff --git a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc
index 4aafa2a..baa97da 100644
--- a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc
+++ b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc
@@ -12,9 +12,12 @@
 
 #include "modules/desktop_capture/desktop_capture_options.h"
 #include "modules/desktop_capture/desktop_capturer.h"
+#include "modules/desktop_capture/linux/wayland/restore_token_manager.h"
 #include "modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/logging.h"
+#include "rtc_base/random.h"
+#include "rtc_base/time_utils.h"
 
 namespace webrtc {
 
@@ -31,12 +34,19 @@
           options,
           std::make_unique<ScreenCastPortal>(
               ScreenCastPortal::CaptureSourceType::kAnyScreenContent,
-              this)) {}
+              this)) {
+  is_screencast_portal_ = true;
+}
 
 BaseCapturerPipeWire::BaseCapturerPipeWire(
     const DesktopCaptureOptions& options,
     std::unique_ptr<ScreenCapturePortalInterface> portal)
-    : options_(options), portal_(std::move(portal)) {}
+    : options_(options),
+      is_screencast_portal_(false),
+      portal_(std::move(portal)) {
+  Random random(rtc::TimeMicros());
+  source_id_ = static_cast<SourceId>(random.Rand(1, INT_MAX));
+}
 
 BaseCapturerPipeWire::~BaseCapturerPipeWire() {}
 
@@ -49,6 +59,11 @@
     capturer_failed_ = true;
     RTC_LOG(LS_ERROR) << "ScreenCastPortal failed: "
                       << static_cast<uint>(result);
+  } else if (ScreenCastPortal* screencast_portal = GetScreenCastPortal()) {
+    if (!screencast_portal->RestoreToken().empty()) {
+      RestoreTokenManager::GetInstance().AddToken(
+          source_id_, screencast_portal->RestoreToken());
+    }
   }
 }
 
@@ -71,6 +86,15 @@
 
   callback_ = callback;
 
+  if (ScreenCastPortal* screencast_portal = GetScreenCastPortal()) {
+    screencast_portal->SetPersistMode(
+        ScreenCastPortal::PersistMode::kTransient);
+    if (selected_source_id_) {
+      screencast_portal->SetRestoreToken(
+          RestoreTokenManager::GetInstance().TakeToken(selected_source_id_));
+    }
+  }
+
   portal_->Start();
 }
 
@@ -103,12 +127,13 @@
   // is often treated as a null/placeholder id, so we shouldn't use that.
   // TODO(https://crbug.com/1297671): Reconsider type of ID when plumbing
   // token that will enable stream re-use.
-  sources->push_back({1});
+  sources->push_back({source_id_});
   return true;
 }
 
 bool BaseCapturerPipeWire::SelectSource(SourceId id) {
   // Screen selection is handled by the xdg-desktop-portal.
+  selected_source_id_ = id;
   return true;
 }
 
@@ -116,4 +141,9 @@
   return portal_->GetSessionDetails();
 }
 
+ScreenCastPortal* BaseCapturerPipeWire::GetScreenCastPortal() {
+  return is_screencast_portal_ ? static_cast<ScreenCastPortal*>(portal_.get())
+                               : nullptr;
+}
+
 }  // namespace webrtc
diff --git a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h
index b6a8816..6e18f4d 100644
--- a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h
+++ b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h
@@ -50,9 +50,22 @@
   xdg_portal::SessionDetails GetSessionDetails();
 
  private:
+  ScreenCastPortal* GetScreenCastPortal();
+
   DesktopCaptureOptions options_ = {};
   Callback* callback_ = nullptr;
   bool capturer_failed_ = false;
+  bool is_screencast_portal_ = false;
+
+  // SourceId that is selected using SelectSource() and that we previously
+  // returned in GetSourceList(). This should be a SourceId that has a restore
+  // token associated with it and can be restored if we have required version
+  // of xdg-desktop-portal.
+  SourceId selected_source_id_ = 0;
+  // SourceID we randomly generate and that is returned in GetSourceList() as
+  // available source that will later get assigned to a restore token in order
+  // to be restored later using SelectSource().
+  SourceId source_id_ = 0;
   std::unique_ptr<xdg_portal::ScreenCapturePortalInterface> portal_;
 };
 
diff --git a/modules/desktop_capture/linux/wayland/restore_token_manager.cc b/modules/desktop_capture/linux/wayland/restore_token_manager.cc
new file mode 100644
index 0000000..cc626d3
--- /dev/null
+++ b/modules/desktop_capture/linux/wayland/restore_token_manager.cc
@@ -0,0 +1,33 @@
+/*
+ *  Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "modules/desktop_capture/linux/wayland/restore_token_manager.h"
+
+namespace webrtc {
+
+// static
+RestoreTokenManager& RestoreTokenManager::GetInstance() {
+  static webrtc::RestoreTokenManager* manager = new RestoreTokenManager();
+  return *manager;
+}
+
+void RestoreTokenManager::AddToken(DesktopCapturer::SourceId id,
+                                   const std::string& token) {
+  restore_tokens_.insert({id, token});
+}
+
+std::string RestoreTokenManager::TakeToken(DesktopCapturer::SourceId id) {
+  std::string token = restore_tokens_[id];
+  // Remove the token as it cannot be used anymore
+  restore_tokens_.erase(id);
+  return token;
+}
+
+}  // namespace webrtc
diff --git a/modules/desktop_capture/linux/wayland/restore_token_manager.h b/modules/desktop_capture/linux/wayland/restore_token_manager.h
new file mode 100644
index 0000000..37c9a39
--- /dev/null
+++ b/modules/desktop_capture/linux/wayland/restore_token_manager.h
@@ -0,0 +1,41 @@
+/*
+ *  Copyright 2022 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_RESTORE_TOKEN_MANAGER_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_RESTORE_TOKEN_MANAGER_H_
+
+#include <mutex>
+#include <string>
+#include <unordered_map>
+
+#include "modules/desktop_capture/desktop_capturer.h"
+
+namespace webrtc {
+
+class RestoreTokenManager {
+ public:
+  RestoreTokenManager(const RestoreTokenManager& manager) = delete;
+  RestoreTokenManager& operator=(const RestoreTokenManager& manager) = delete;
+
+  static RestoreTokenManager& GetInstance();
+
+  void AddToken(DesktopCapturer::SourceId id, const std::string& token);
+  std::string TakeToken(DesktopCapturer::SourceId id);
+
+ private:
+  RestoreTokenManager() = default;
+  ~RestoreTokenManager() = default;
+
+  std::unordered_map<DesktopCapturer::SourceId, std::string> restore_tokens_;
+};
+
+}  // namespace webrtc
+
+#endif  // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_RESTORE_TOKEN_MANAGER_H_
diff --git a/modules/desktop_capture/linux/wayland/screencast_portal.cc b/modules/desktop_capture/linux/wayland/screencast_portal.cc
index c70628b..d723e37 100644
--- a/modules/desktop_capture/linux/wayland/screencast_portal.cc
+++ b/modules/desktop_capture/linux/wayland/screencast_portal.cc
@@ -198,11 +198,11 @@
   g_variant_builder_add(&builder, "{sv}", "multiple",
                         g_variant_new_boolean(false));
 
-  Scoped<GVariant> variant(
+  Scoped<GVariant> cursorModesVariant(
       g_dbus_proxy_get_cached_property(proxy_, "AvailableCursorModes"));
-  if (variant.get()) {
+  if (cursorModesVariant.get()) {
     uint32_t modes = 0;
-    g_variant_get(variant.get(), "u", &modes);
+    g_variant_get(cursorModesVariant.get(), "u", &modes);
     // Make request only if this mode is advertised by the portal
     // implementation.
     if (modes & static_cast<uint32_t>(cursor_mode_)) {
@@ -212,6 +212,23 @@
     }
   }
 
+  Scoped<GVariant> versionVariant(
+      g_dbus_proxy_get_cached_property(proxy_, "version"));
+  if (versionVariant.get()) {
+    uint32_t version = 0;
+    g_variant_get(versionVariant.get(), "u", &version);
+    // Make request only if xdg-desktop-portal has required API version
+    if (version >= 4) {
+      g_variant_builder_add(
+          &builder, "{sv}", "persist_mode",
+          g_variant_new_uint32(static_cast<uint32_t>(persist_mode_)));
+      if (!restore_token_.empty()) {
+        g_variant_builder_add(&builder, "{sv}", "restore_token",
+                              g_variant_new_string(restore_token_.c_str()));
+      }
+    }
+  }
+
   variant_string = g_strdup_printf("webrtc%d", g_random_int_range(0, G_MAXINT));
   g_variant_builder_add(&builder, "{sv}", "handle_token",
                         g_variant_new_string(variant_string.get()));
@@ -320,6 +337,7 @@
   uint32_t portal_response;
   Scoped<GVariant> response_data;
   Scoped<GVariantIter> iter;
+  Scoped<char> restore_token;
   g_variant_get(parameters, "(u@a{sv})", &portal_response,
                 response_data.receive());
   if (portal_response || !response_data) {
@@ -354,6 +372,11 @@
     }
   }
 
+  if (g_variant_lookup(response_data.get(), "restore_token", "s",
+                       restore_token.receive())) {
+    that->restore_token_ = restore_token.get();
+  }
+
   that->OpenPipeWireRemote();
 }
 
@@ -361,6 +384,18 @@
   return pw_stream_node_id_;
 }
 
+void ScreenCastPortal::SetPersistMode(ScreenCastPortal::PersistMode mode) {
+  persist_mode_ = mode;
+}
+
+void ScreenCastPortal::SetRestoreToken(const std::string& token) {
+  restore_token_ = token;
+}
+
+std::string ScreenCastPortal::RestoreToken() const {
+  return restore_token_;
+}
+
 void ScreenCastPortal::OpenPipeWireRemote() {
   GVariantBuilder builder;
   g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
diff --git a/modules/desktop_capture/linux/wayland/screencast_portal.h b/modules/desktop_capture/linux/wayland/screencast_portal.h
index 73a6038..7970710 100644
--- a/modules/desktop_capture/linux/wayland/screencast_portal.h
+++ b/modules/desktop_capture/linux/wayland/screencast_portal.h
@@ -58,6 +58,21 @@
     kMetadata = 0b100
   };
 
+  // Values are set based on persist mode property in
+  // xdg-desktop-portal/screencast
+  // https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.ScreenCast.xml
+  enum class PersistMode : uint32_t {
+    // Do not allow to restore stream
+    kDoNotPersist = 0b00,
+    // The restore token is valid as long as the application is alive. It's
+    // stored in memory and revoked when the application closes its DBus
+    // connection
+    kTransient = 0b01,
+    // The restore token is stored in disk and is valid until the user manually
+    // revokes it
+    kPersistent = 0b10
+  };
+
   // Interface that must be implemented by the ScreenCastPortal consumers.
   class PortalNotifier {
    public:
@@ -106,6 +121,11 @@
   void SourcesRequest();
   void OpenPipeWireRemote();
 
+  // ScreenCast specific methods for stream restoration
+  void SetPersistMode(ScreenCastPortal::PersistMode mode);
+  void SetRestoreToken(const std::string& token);
+  std::string RestoreToken() const;
+
  private:
   PortalNotifier* notifier_;
 
@@ -113,12 +133,16 @@
   uint32_t pw_stream_node_id_ = 0;
   // A file descriptor of PipeWire socket
   int pw_fd_ = -1;
+  // Restore token that can be used to restore previous session
+  std::string restore_token_;
 
   CaptureSourceType capture_source_type_ =
       ScreenCastPortal::CaptureSourceType::kScreen;
 
   CursorMode cursor_mode_ = ScreenCastPortal::CursorMode::kMetadata;
 
+  PersistMode persist_mode_ = ScreenCastPortal::PersistMode::kDoNotPersist;
+
   ProxyRequestResponseHandler proxy_request_response_handler_;
   SourcesRequestResponseSignalHandler sources_request_response_signal_handler_;
   gpointer user_data_;