diff --git a/modules/desktop_capture/BUILD.gn b/modules/desktop_capture/BUILD.gn index 0c59ecd38d..864732b4b0 100644 --- a/modules/desktop_capture/BUILD.gn +++ b/modules/desktop_capture/BUILD.gn @@ -622,6 +622,8 @@ rtc_library("desktop_capture_generic") { "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 4aafa2a007..baa97daa76 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 @@ BaseCapturerPipeWire::BaseCapturerPipeWire(const DesktopCaptureOptions& options) options, std::make_unique( ScreenCastPortal::CaptureSourceType::kAnyScreenContent, - this)) {} + this)) { + is_screencast_portal_ = true; +} BaseCapturerPipeWire::BaseCapturerPipeWire( const DesktopCaptureOptions& options, std::unique_ptr 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(random.Rand(1, INT_MAX)); +} BaseCapturerPipeWire::~BaseCapturerPipeWire() {} @@ -49,6 +59,11 @@ void BaseCapturerPipeWire::OnScreenCastRequestResult(RequestResponse result, capturer_failed_ = true; RTC_LOG(LS_ERROR) << "ScreenCastPortal failed: " << static_cast(result); + } else if (ScreenCastPortal* screencast_portal = GetScreenCastPortal()) { + if (!screencast_portal->RestoreToken().empty()) { + RestoreTokenManager::GetInstance().AddToken( + source_id_, screencast_portal->RestoreToken()); + } } } @@ -71,6 +86,15 @@ void BaseCapturerPipeWire::Start(Callback* callback) { 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 @@ bool BaseCapturerPipeWire::GetSourceList(SourceList* sources) { // 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 @@ SessionDetails BaseCapturerPipeWire::GetSessionDetails() { return portal_->GetSessionDetails(); } +ScreenCastPortal* BaseCapturerPipeWire::GetScreenCastPortal() { + return is_screencast_portal_ ? static_cast(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 b6a8816cdb..6e18f4d11b 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 @@ class BaseCapturerPipeWire : public DesktopCapturer, 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 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 0000000000..cc626d3065 --- /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 0000000000..37c9a39cac --- /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 +#include +#include + +#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 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 c70628bcf2..d723e37675 100644 --- a/modules/desktop_capture/linux/wayland/screencast_portal.cc +++ b/modules/desktop_capture/linux/wayland/screencast_portal.cc @@ -198,11 +198,11 @@ void ScreenCastPortal::SourcesRequest() { g_variant_builder_add(&builder, "{sv}", "multiple", g_variant_new_boolean(false)); - Scoped variant( + Scoped 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(cursor_mode_)) { @@ -212,6 +212,23 @@ void ScreenCastPortal::SourcesRequest() { } } + Scoped 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(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 @@ void ScreenCastPortal::OnStartRequestResponseSignal(GDBusConnection* connection, uint32_t portal_response; Scoped response_data; Scoped iter; + Scoped restore_token; g_variant_get(parameters, "(u@a{sv})", &portal_response, response_data.receive()); if (portal_response || !response_data) { @@ -354,6 +372,11 @@ void ScreenCastPortal::OnStartRequestResponseSignal(GDBusConnection* connection, } } + 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 @@ uint32_t ScreenCastPortal::pipewire_stream_node_id() { 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 73a6038c3c..7970710c41 100644 --- a/modules/desktop_capture/linux/wayland/screencast_portal.h +++ b/modules/desktop_capture/linux/wayland/screencast_portal.h @@ -58,6 +58,21 @@ class ScreenCastPortal : public xdg_portal::ScreenCapturePortalInterface { 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 @@ class ScreenCastPortal : public xdg_portal::ScreenCapturePortalInterface { 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 @@ class ScreenCastPortal : public xdg_portal::ScreenCapturePortalInterface { 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_;