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_;