From 76dd735a143c4e8e721698ae8347b10c1a84abc4 Mon Sep 17 00:00:00 2001 From: Salman Malik Date: Wed, 16 Mar 2022 21:07:11 +0000 Subject: [PATCH] desktop_capturer: Extract helpers from screencast portal Extract helper methods from screencast portal that can be reused for remote desktop portal client. Bug: chromium:1291247 Change-Id: I66d09c75f0c34d81c7ceff8998720fbbd1902ac8 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/249860 Reviewed-by: Alexander Cooper Commit-Queue: Salman Malik Cr-Commit-Position: refs/heads/main@{#36256} --- modules/desktop_capture/BUILD.gn | 2 + .../linux/wayland/base_capturer_pipewire.cc | 16 +- .../linux/wayland/base_capturer_pipewire.h | 3 +- .../linux/wayland/screencast_portal.cc | 299 ++++-------------- .../linux/wayland/screencast_portal.h | 35 +- .../linux/wayland/xdg_desktop_portal_utils.cc | 169 ++++++++++ .../linux/wayland/xdg_desktop_portal_utils.h | 234 ++++++++++++++ 7 files changed, 497 insertions(+), 261 deletions(-) create mode 100644 modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.cc create mode 100644 modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h diff --git a/modules/desktop_capture/BUILD.gn b/modules/desktop_capture/BUILD.gn index 6c855b9c1c..0faad43a1f 100644 --- a/modules/desktop_capture/BUILD.gn +++ b/modules/desktop_capture/BUILD.gn @@ -574,6 +574,8 @@ rtc_library("desktop_capture_generic") { "linux/wayland/screencast_portal.h", "linux/wayland/shared_screencast_stream.cc", "linux/wayland/shared_screencast_stream.h", + "linux/wayland/xdg_desktop_portal_utils.cc", + "linux/wayland/xdg_desktop_portal_utils.h", ] configs += [ diff --git a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc index b6120ee68c..ae26570a7c 100644 --- a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc +++ b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc @@ -12,11 +12,18 @@ #include "modules/desktop_capture/desktop_capture_options.h" #include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" namespace webrtc { +namespace { + +using xdg_portal::RequestResponse; + +} // namespace + BaseCapturerPipeWire::BaseCapturerPipeWire(const DesktopCaptureOptions& options) : options_(options) { screencast_portal_ = std::make_unique( @@ -25,11 +32,10 @@ BaseCapturerPipeWire::BaseCapturerPipeWire(const DesktopCaptureOptions& options) BaseCapturerPipeWire::~BaseCapturerPipeWire() {} -void BaseCapturerPipeWire::OnScreenCastRequestResult( - ScreenCastPortal::RequestResponse result, - uint32_t stream_node_id, - int fd) { - if (result != ScreenCastPortal::RequestResponse::kSuccess || +void BaseCapturerPipeWire::OnScreenCastRequestResult(RequestResponse result, + uint32_t stream_node_id, + int fd) { + if (result != RequestResponse::kSuccess || !options_.screencast_stream()->StartScreenCastStream(stream_node_id, fd)) { capturer_failed_ = true; diff --git a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h index 7663cb60ae..e9d67d04be 100644 --- a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h +++ b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.h @@ -15,6 +15,7 @@ #include "modules/desktop_capture/desktop_capturer.h" #include "modules/desktop_capture/linux/wayland/screencast_portal.h" #include "modules/desktop_capture/linux/wayland/shared_screencast_stream.h" +#include "modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h" namespace webrtc { @@ -34,7 +35,7 @@ class BaseCapturerPipeWire : public DesktopCapturer, bool SelectSource(SourceId id) override; // ScreenCastPortal::PortalNotifier interface. - void OnScreenCastRequestResult(ScreenCastPortal::RequestResponse result, + void OnScreenCastRequestResult(xdg_portal::RequestResponse result, uint32_t stream_node_id, int fd) override; void OnScreenCastSessionClosed() override; diff --git a/modules/desktop_capture/linux/wayland/screencast_portal.cc b/modules/desktop_capture/linux/wayland/screencast_portal.cc index ac0e9700ed..0b9db6ffb0 100644 --- a/modules/desktop_capture/linux/wayland/screencast_portal.cc +++ b/modules/desktop_capture/linux/wayland/screencast_portal.cc @@ -14,189 +14,96 @@ #include #include "modules/desktop_capture/linux/wayland/scoped_glib.h" +#include "modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" namespace webrtc { +namespace { -const char kDesktopBusName[] = "org.freedesktop.portal.Desktop"; -const char kDesktopObjectPath[] = "/org/freedesktop/portal/desktop"; -const char kDesktopRequestObjectPath[] = - "/org/freedesktop/portal/desktop/request"; -const char kSessionInterfaceName[] = "org.freedesktop.portal.Session"; -const char kRequestInterfaceName[] = "org.freedesktop.portal.Request"; -const char kScreenCastInterfaceName[] = "org.freedesktop.portal.ScreenCast"; +using xdg_portal::kScreenCastInterfaceName; +using xdg_portal::PrepareSignalHandle; +using xdg_portal::RequestResponse; +using xdg_portal::RequestSessionProxy; +using xdg_portal::RequestSessionUsingProxy; +using xdg_portal::SessionRequestHandler; +using xdg_portal::SessionRequestResponseSignalHelper; +using xdg_portal::SetupRequestResponseSignal; +using xdg_portal::SetupSessionRequestHandlers; +using xdg_portal::StartRequestedHandler; +using xdg_portal::StartSessionRequest; +using xdg_portal::TearDownSession; -ScreenCastPortal::ScreenCastPortal(CaptureSourceType source_type, - PortalNotifier* notifier) +} // namespace + +ScreenCastPortal::ScreenCastPortal( + ScreenCastPortal::CaptureSourceType source_type, + PortalNotifier* notifier) : notifier_(notifier), capture_source_type_(source_type) {} ScreenCastPortal::~ScreenCastPortal() { - if (start_request_signal_id_) { - g_dbus_connection_signal_unsubscribe(connection_, start_request_signal_id_); - } - if (sources_request_signal_id_) { - g_dbus_connection_signal_unsubscribe(connection_, - sources_request_signal_id_); - } - if (session_request_signal_id_) { - g_dbus_connection_signal_unsubscribe(connection_, - session_request_signal_id_); - } - - if (!session_handle_.empty()) { - Scoped message( - g_dbus_message_new_method_call(kDesktopBusName, session_handle_.c_str(), - kSessionInterfaceName, "Close")); - if (message.get()) { - Scoped error; - g_dbus_connection_send_message(connection_, message.get(), - G_DBUS_SEND_MESSAGE_FLAGS_NONE, - /*out_serial=*/nullptr, error.receive()); - if (error.get()) { - RTC_LOG(LS_ERROR) << "Failed to close the session: " << error->message; - } - } - } - - if (cancellable_) { - g_cancellable_cancel(cancellable_); - g_object_unref(cancellable_); - cancellable_ = nullptr; - } - - if (proxy_) { - g_object_unref(proxy_); - proxy_ = nullptr; - } + UnsubscribeSignalHandlers(); + TearDownSession(std::move(session_handle_), proxy_, cancellable_, + connection_); + cancellable_ = nullptr; + proxy_ = nullptr; if (pw_fd_ != -1) { close(pw_fd_); } } +void ScreenCastPortal::UnsubscribeSignalHandlers() { + if (start_request_signal_id_) { + g_dbus_connection_signal_unsubscribe(connection_, start_request_signal_id_); + start_request_signal_id_ = 0; + } + + if (sources_request_signal_id_) { + g_dbus_connection_signal_unsubscribe(connection_, + sources_request_signal_id_); + sources_request_signal_id_ = 0; + } + + if (session_request_signal_id_) { + g_dbus_connection_signal_unsubscribe(connection_, + session_request_signal_id_); + session_request_signal_id_ = 0; + } +} + void ScreenCastPortal::Start() { cancellable_ = g_cancellable_new(); - g_dbus_proxy_new_for_bus( - G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, /*info=*/nullptr, - kDesktopBusName, kDesktopObjectPath, kScreenCastInterfaceName, - cancellable_, reinterpret_cast(OnProxyRequested), - this); + RequestSessionProxy(kScreenCastInterfaceName, OnProxyRequested, cancellable_, + this); } void ScreenCastPortal::PortalFailed(RequestResponse result) { notifier_->OnScreenCastRequestResult(result, pw_stream_node_id_, pw_fd_); } -uint32_t ScreenCastPortal::SetupRequestResponseSignal( - const char* object_path, - GDBusSignalCallback callback) { - return g_dbus_connection_signal_subscribe( - connection_, kDesktopBusName, kRequestInterfaceName, "Response", - object_path, /*arg0=*/nullptr, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, - callback, this, /*user_data_free_func=*/nullptr); -} - // static -void ScreenCastPortal::OnProxyRequested(GObject* /*object*/, +void ScreenCastPortal::OnProxyRequested(GObject* gobject, GAsyncResult* result, gpointer user_data) { - ScreenCastPortal* that = static_cast(user_data); - RTC_DCHECK(that); - - Scoped error; - GDBusProxy* proxy = g_dbus_proxy_new_finish(result, error.receive()); - if (!proxy) { - if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) - return; - RTC_LOG(LS_ERROR) << "Failed to create a proxy for the screen cast portal: " - << error->message; - that->PortalFailed(RequestResponse::kError); - return; - } - that->proxy_ = proxy; - that->connection_ = g_dbus_proxy_get_connection(that->proxy_); - - RTC_LOG(LS_INFO) << "Created proxy for the screen cast portal."; - - that->SessionRequest(); + RequestSessionUsingProxy( + static_cast(user_data), gobject, result); } -// static -std::string ScreenCastPortal::PrepareSignalHandle(GDBusConnection* connection, - const char* token) { - Scoped sender( - g_strdup(g_dbus_connection_get_unique_name(connection) + 1)); - for (int i = 0; sender.get()[i]; ++i) { - if (sender.get()[i] == '.') { - sender.get()[i] = '_'; - } - } - - const char* handle = g_strconcat(kDesktopRequestObjectPath, "/", sender.get(), - "/", token, /*end of varargs*/ nullptr); - - return handle; -} - -void ScreenCastPortal::SessionRequest() { - GVariantBuilder builder; - Scoped variant_string; - - g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); - variant_string = - g_strdup_printf("webrtc_session%d", g_random_int_range(0, G_MAXINT)); - g_variant_builder_add(&builder, "{sv}", "session_handle_token", - g_variant_new_string(variant_string.get())); - 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())); - - portal_handle_ = PrepareSignalHandle(connection_, variant_string.get()); - session_request_signal_id_ = SetupRequestResponseSignal( - portal_handle_.c_str(), OnSessionRequestResponseSignal); - - RTC_LOG(LS_INFO) << "Screen cast session requested."; - g_dbus_proxy_call(proxy_, "CreateSession", g_variant_new("(a{sv})", &builder), - G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_, - reinterpret_cast(OnSessionRequested), - this); +void ScreenCastPortal::SessionRequest(GDBusProxy* proxy) { + proxy_ = proxy; + connection_ = g_dbus_proxy_get_connection(proxy_); + SetupSessionRequestHandlers( + "webrtc", OnSessionRequested, OnSessionRequestResponseSignal, connection_, + proxy_, cancellable_, portal_handle_, session_request_signal_id_, this); } // static void ScreenCastPortal::OnSessionRequested(GDBusProxy* proxy, GAsyncResult* result, gpointer user_data) { - ScreenCastPortal* that = static_cast(user_data); - RTC_DCHECK(that); - - Scoped error; - Scoped variant( - g_dbus_proxy_call_finish(proxy, result, error.receive())); - if (!variant) { - if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) - return; - RTC_LOG(LS_ERROR) << "Failed to create a screen cast session: " - << error->message; - that->PortalFailed(RequestResponse::kError); - return; - } - RTC_LOG(LS_INFO) << "Initializing the screen cast session."; - - Scoped handle; - g_variant_get_child(variant.get(), 0, "o", &handle); - if (!handle) { - RTC_LOG(LS_ERROR) << "Failed to initialize the screen cast session."; - if (that->session_request_signal_id_) { - g_dbus_connection_signal_unsubscribe(that->connection_, - that->session_request_signal_id_); - that->session_request_signal_id_ = 0; - } - that->PortalFailed(RequestResponse::kError); - return; - } - - RTC_LOG(LS_INFO) << "Subscribing to the screen cast session."; + SessionRequestHandler(static_cast(user_data), proxy, + result, user_data); } // static @@ -210,30 +117,9 @@ void ScreenCastPortal::OnSessionRequestResponseSignal( gpointer user_data) { ScreenCastPortal* that = static_cast(user_data); RTC_DCHECK(that); - - RTC_LOG(LS_INFO) - << "Received response for the screen cast session subscription."; - - uint32_t portal_response; - Scoped response_data; - g_variant_get(parameters, "(u@a{sv})", &portal_response, - response_data.receive()); - Scoped session_handle( - g_variant_lookup_value(response_data.get(), "session_handle", nullptr)); - that->session_handle_ = g_variant_dup_string(session_handle.get(), nullptr); - - if (that->session_handle_.empty() || portal_response) { - RTC_LOG(LS_ERROR) - << "Failed to request the screen cast session subscription."; - that->PortalFailed(RequestResponse::kError); - return; - } - - that->session_closed_signal_id_ = g_dbus_connection_signal_subscribe( - that->connection_, kDesktopBusName, kSessionInterfaceName, "Closed", - that->session_handle_.c_str(), /*arg0=*/nullptr, G_DBUS_SIGNAL_FLAGS_NONE, - OnSessionClosedSignal, that, /*user_data_free_func=*/nullptr); - + SessionRequestResponseSignalHelper( + OnSessionClosedSignal, that, that->connection_, that->session_handle_, + parameters, that->session_closed_signal_id_); that->SourcesRequest(); } @@ -289,9 +175,10 @@ void ScreenCastPortal::SourcesRequest() { g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_new_string(variant_string.get())); - sources_handle_ = PrepareSignalHandle(connection_, variant_string.get()); + sources_handle_ = PrepareSignalHandle(variant_string.get(), connection_); sources_request_signal_id_ = SetupRequestResponseSignal( - sources_handle_.c_str(), OnSourcesRequestResponseSignal); + sources_handle_.c_str(), OnSourcesRequestResponseSignal, this, + connection_); RTC_LOG(LS_INFO) << "Requesting sources from the screen cast session."; g_dbus_proxy_call( @@ -364,66 +251,17 @@ void ScreenCastPortal::OnSourcesRequestResponseSignal( } void ScreenCastPortal::StartRequest() { - GVariantBuilder builder; - Scoped variant_string; - - g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); - 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())); - - start_handle_ = PrepareSignalHandle(connection_, variant_string.get()); - start_request_signal_id_ = SetupRequestResponseSignal( - start_handle_.c_str(), OnStartRequestResponseSignal); - - // "Identifier for the application window", this is Wayland, so not "x11:...". - const char parent_window[] = ""; - - RTC_LOG(LS_INFO) << "Starting the screen cast session."; - g_dbus_proxy_call(proxy_, "Start", - g_variant_new("(osa{sv})", session_handle_.c_str(), - parent_window, &builder), - G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_, - reinterpret_cast(OnStartRequested), - this); + StartSessionRequest("webrtc", session_handle_, OnStartRequestResponseSignal, + OnStartRequested, proxy_, connection_, cancellable_, + start_request_signal_id_, start_handle_, this); } // static void ScreenCastPortal::OnStartRequested(GDBusProxy* proxy, GAsyncResult* result, gpointer user_data) { - ScreenCastPortal* that = static_cast(user_data); - RTC_DCHECK(that); - - Scoped error; - Scoped variant( - g_dbus_proxy_call_finish(proxy, result, error.receive())); - if (!variant) { - if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) - return; - RTC_LOG(LS_ERROR) << "Failed to start the screen cast session: " - << error->message; - that->PortalFailed(RequestResponse::kError); - return; - } - - RTC_LOG(LS_INFO) << "Initializing the start of the screen cast session."; - - Scoped handle; - g_variant_get_child(variant.get(), 0, "o", handle.receive()); - if (!handle) { - RTC_LOG(LS_ERROR) - << "Failed to initialize the start of the screen cast session."; - if (that->start_request_signal_id_) { - g_dbus_connection_signal_unsubscribe(that->connection_, - that->start_request_signal_id_); - that->start_request_signal_id_ = 0; - } - that->PortalFailed(RequestResponse::kError); - return; - } - - RTC_LOG(LS_INFO) << "Subscribed to the start signal."; + StartRequestedHandler(static_cast(user_data), proxy, + result); } // static @@ -525,8 +363,7 @@ void ScreenCastPortal::OnOpenPipeWireRemoteRequested(GDBusProxy* proxy, } that->notifier_->OnScreenCastRequestResult( - ScreenCastPortal::RequestResponse::kSuccess, that->pw_stream_node_id_, - that->pw_fd_); + RequestResponse::kSuccess, that->pw_stream_node_id_, that->pw_fd_); } } // namespace webrtc diff --git a/modules/desktop_capture/linux/wayland/screencast_portal.h b/modules/desktop_capture/linux/wayland/screencast_portal.h index fd6bb1d468..75884450db 100644 --- a/modules/desktop_capture/linux/wayland/screencast_portal.h +++ b/modules/desktop_capture/linux/wayland/screencast_portal.h @@ -15,7 +15,7 @@ #include -#include "absl/types/optional.h" +#include "modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h" namespace webrtc { @@ -43,20 +43,9 @@ class ScreenCastPortal { }; // Interface that must be implemented by the ScreenCastPortal consumers. - enum class RequestResponse { - // Success, the request is carried out. - kSuccess, - // The user cancelled the interaction. - kUserCancelled, - // The user interaction was ended in some other way. - kError, - - kMaxValue = kError - }; - class PortalNotifier { public: - virtual void OnScreenCastRequestResult(RequestResponse result, + virtual void OnScreenCastRequestResult(xdg_portal::RequestResponse result, uint32_t stream_node_id, int fd) = 0; virtual void OnScreenCastSessionClosed() = 0; @@ -66,7 +55,7 @@ class ScreenCastPortal { virtual ~PortalNotifier() = default; }; - explicit ScreenCastPortal(CaptureSourceType source_type, + explicit ScreenCastPortal(ScreenCastPortal::CaptureSourceType source_type, PortalNotifier* notifier); ~ScreenCastPortal(); @@ -79,6 +68,14 @@ class ScreenCastPortal { // information in order to continue working with PipeWire. void Start(); + // Method to notify the reason for failure of a portal request. + void PortalFailed(xdg_portal::RequestResponse result); + + // Sends a create session request to the portal. + void SessionRequest(GDBusProxy* proxy); + + void UnsubscribeSignalHandlers(); + private: PortalNotifier* notifier_; @@ -104,19 +101,9 @@ class ScreenCastPortal { guint start_request_signal_id_ = 0; guint session_closed_signal_id_ = 0; - void PortalFailed(RequestResponse result); - - uint32_t SetupRequestResponseSignal(const char* object_path, - GDBusSignalCallback callback); - static void OnProxyRequested(GObject* object, GAsyncResult* result, gpointer user_data); - - static std::string PrepareSignalHandle(GDBusConnection* connection, - const char* token); - - void SessionRequest(); static void OnSessionRequested(GDBusProxy* proxy, GAsyncResult* result, gpointer user_data); diff --git a/modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.cc b/modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.cc new file mode 100644 index 0000000000..ca98044c97 --- /dev/null +++ b/modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.cc @@ -0,0 +1,169 @@ +/* + * 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/xdg_desktop_portal_utils.h" + +#include "modules/desktop_capture/linux/wayland/scoped_glib.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace xdg_portal { + +std::string RequestResponseToString(RequestResponse request) { + switch (request) { + case RequestResponse::kSuccess: + return "kSuccess"; + case RequestResponse::kUserCancelled: + return "kUserCancelled"; + case RequestResponse::kError: + return "kError"; + default: + return "Uknown"; + } +} + +std::string PrepareSignalHandle(const char* token, + GDBusConnection* connection) { + Scoped sender( + g_strdup(g_dbus_connection_get_unique_name(connection) + 1)); + for (int i = 0; sender.get()[i]; ++i) { + if (sender.get()[i] == '.') { + sender.get()[i] = '_'; + } + } + const char* handle = g_strconcat(kDesktopRequestObjectPath, "/", sender.get(), + "/", token, /*end of varargs*/ nullptr); + return handle; +} + +uint32_t SetupRequestResponseSignal(const char* object_path, + const GDBusSignalCallback callback, + gpointer user_data, + GDBusConnection* connection) { + return g_dbus_connection_signal_subscribe( + connection, kDesktopBusName, kRequestInterfaceName, "Response", + object_path, /*arg0=*/nullptr, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, + callback, user_data, /*user_data_free_func=*/nullptr); +} + +void RequestSessionProxy(const char* interface_name, + const ProxyRequestCallback proxy_request_callback, + GCancellable* cancellable, + gpointer user_data) { + g_dbus_proxy_new_for_bus( + G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, /*info=*/nullptr, + kDesktopBusName, kDesktopObjectPath, interface_name, cancellable, + reinterpret_cast(proxy_request_callback), user_data); +} + +void SetupSessionRequestHandlers( + const std::string& portal_prefix, + const SessionRequestCallback session_request_callback, + const SessionRequestResponseSignalHandler request_response_signale_handler, + GDBusConnection* connection, + GDBusProxy* proxy, + GCancellable* cancellable, + std::string& portal_handle, + guint& session_request_signal_id, + gpointer user_data) { + GVariantBuilder builder; + Scoped variant_string; + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + variant_string = g_strdup_printf("%s_session%d", portal_prefix.c_str(), + g_random_int_range(0, G_MAXINT)); + g_variant_builder_add(&builder, "{sv}", "session_handle_token", + g_variant_new_string(variant_string.get())); + + variant_string = g_strdup_printf("%s_%d", portal_prefix.c_str(), + g_random_int_range(0, G_MAXINT)); + g_variant_builder_add(&builder, "{sv}", "handle_token", + g_variant_new_string(variant_string.get())); + + portal_handle = PrepareSignalHandle(variant_string.get(), connection); + session_request_signal_id = SetupRequestResponseSignal( + portal_handle.c_str(), request_response_signale_handler, user_data, + connection); + + RTC_LOG(LS_INFO) << "Desktop session requested."; + g_dbus_proxy_call( + proxy, "CreateSession", g_variant_new("(a{sv})", &builder), + G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable, + reinterpret_cast(session_request_callback), + user_data); +} + +void StartSessionRequest( + const std::string& prefix, + const std::string session_handle, + const StartRequestResponseSignalHandler signal_handler, + const SessionStartRequestedHandler session_started_handler, + GDBusProxy* proxy, + GDBusConnection* connection, + GCancellable* cancellable, + guint& start_request_signal_id, + std::string& start_handle, + gpointer user_data) { + GVariantBuilder builder; + Scoped variant_string; + + g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); + variant_string = + g_strdup_printf("%s%d", prefix.c_str(), g_random_int_range(0, G_MAXINT)); + g_variant_builder_add(&builder, "{sv}", "handle_token", + g_variant_new_string(variant_string.get())); + + start_handle = PrepareSignalHandle(variant_string.get(), connection); + start_request_signal_id = SetupRequestResponseSignal( + start_handle.c_str(), signal_handler, user_data, connection); + + // "Identifier for the application window", this is Wayland, so not "x11:...". + const char parent_window[] = ""; + + RTC_LOG(LS_INFO) << "Starting the portal session."; + g_dbus_proxy_call( + proxy, "Start", + g_variant_new("(osa{sv})", session_handle.c_str(), parent_window, + &builder), + G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable, + reinterpret_cast(session_started_handler), + user_data); +} + +void TearDownSession(std::string session_handle, + GDBusProxy* proxy, + GCancellable* cancellable, + GDBusConnection* connection) { + if (!session_handle.empty()) { + Scoped message( + g_dbus_message_new_method_call(kDesktopBusName, session_handle.c_str(), + kSessionInterfaceName, "Close")); + if (message.get()) { + Scoped error; + g_dbus_connection_send_message(connection, message.get(), + G_DBUS_SEND_MESSAGE_FLAGS_NONE, + /*out_serial=*/nullptr, error.receive()); + if (error.get()) { + RTC_LOG(LS_ERROR) << "Failed to close the session: " << error->message; + } + } + } + + if (cancellable) { + g_cancellable_cancel(cancellable); + g_object_unref(cancellable); + } + + if (proxy) { + g_object_unref(proxy); + } +} + +} // namespace xdg_portal +} // namespace webrtc diff --git a/modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h b/modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h new file mode 100644 index 0000000000..9b7953424d --- /dev/null +++ b/modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h @@ -0,0 +1,234 @@ +/* + * 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_XDG_DESKTOP_PORTAL_UTILS_H_ +#define MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_DESKTOP_PORTAL_UTILS_H_ + +#include +#include + +#include +#include + +#include "modules/desktop_capture/linux/wayland/scoped_glib.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace xdg_portal { + +constexpr char kDesktopBusName[] = "org.freedesktop.portal.Desktop"; +constexpr char kDesktopObjectPath[] = "/org/freedesktop/portal/desktop"; +constexpr char kDesktopRequestObjectPath[] = + "/org/freedesktop/portal/desktop/request"; +constexpr char kSessionInterfaceName[] = "org.freedesktop.portal.Session"; +constexpr char kRequestInterfaceName[] = "org.freedesktop.portal.Request"; +constexpr char kScreenCastInterfaceName[] = "org.freedesktop.portal.ScreenCast"; + +using ProxyRequestCallback = void (*)(GObject*, GAsyncResult*, gpointer); +using SessionRequestCallback = void (*)(GDBusProxy*, GAsyncResult*, gpointer); +using SessionRequestResponseSignalHandler = void (*)(GDBusConnection*, + const char*, + const char*, + const char*, + const char*, + GVariant*, + gpointer); +using SessionRequestResponseSignalCallback = void (*)(std::string); +using SessionClosedSignalHandler = void (*)(GDBusConnection*, + const char*, + const char*, + const char*, + const char*, + GVariant*, + gpointer); +using StartRequestResponseSignalHandler = void (*)(GDBusConnection*, + const char*, + const char*, + const char*, + const char*, + GVariant*, + gpointer); +using SessionStartRequestedHandler = void (*)(GDBusProxy*, + GAsyncResult*, + gpointer); + +// Contains type of responses that can be observed when making a request to +// a desktop portal interface. +enum class RequestResponse { + // Success, the request is carried out. + kSuccess, + // The user cancelled the interaction. + kUserCancelled, + // The user interaction was ended in some other way. + kError, + + kMaxValue = kError, +}; + +std::string RequestResponseToString(RequestResponse request); + +// Returns a string path for signal handle based on the provided connection and +// token. +std::string PrepareSignalHandle(const char* token, GDBusConnection* connection); + +// Sets up the callback to execute when a response signal is received for the +// given object. +uint32_t SetupRequestResponseSignal(const char* object_path, + const GDBusSignalCallback callback, + gpointer user_data, + GDBusConnection* connection); + +void RequestSessionProxy(const char* interface_name, + const ProxyRequestCallback proxy_request_callback, + GCancellable* cancellable, + gpointer user_data); + +void SetupSessionRequestHandlers( + const std::string& portal_prefix, + const SessionRequestCallback session_request_callback, + const SessionRequestResponseSignalHandler request_response_signale_handler, + GDBusConnection* connection, + GDBusProxy* proxy, + GCancellable* cancellable, + std::string& portal_handle, + guint& session_request_signal_id, + gpointer user_data); + +void StartSessionRequest( + const std::string& prefix, + const std::string session_handle, + const StartRequestResponseSignalHandler signal_handler, + const SessionStartRequestedHandler session_started_handler, + GDBusProxy* proxy, + GDBusConnection* connection, + GCancellable* cancellable, + guint& start_request_signal_id, + std::string& start_handle, + gpointer user_data); + +// Tears down the portal session and cleans up related objects. +void TearDownSession(std::string session_handle, + GDBusProxy* proxy, + GCancellable* cancellable, + GDBusConnection* connection); + +template +void RequestSessionUsingProxy(T* portal, + GObject* gobject, + GAsyncResult* result) { + RTC_DCHECK(portal); + Scoped error; + GDBusProxy* proxy = g_dbus_proxy_new_finish(result, error.receive()); + if (!proxy) { + if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + RTC_LOG(LS_ERROR) << "Failed to get a proxy for the portal: " + << error->message; + portal->PortalFailed(RequestResponse::kError); + return; + } + + RTC_LOG(LS_INFO) << "Successfully created proxy for the portal."; + portal->SessionRequest(proxy); +} + +template +void SessionRequestHandler(T* portal, + GDBusProxy* proxy, + GAsyncResult* result, + gpointer user_data) { + RTC_DCHECK(portal); + + Scoped error; + Scoped variant( + g_dbus_proxy_call_finish(proxy, result, error.receive())); + if (!variant) { + if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + RTC_LOG(LS_ERROR) << "Failed to session: " << error->message; + portal->PortalFailed(RequestResponse::kError); + return; + } + + RTC_LOG(LS_INFO) << "Initializing the session."; + + Scoped handle; + g_variant_get_child(variant.get(), /*index=*/0, /*format_string=*/"o", + &handle); + if (!handle) { + RTC_LOG(LS_ERROR) << "Failed to initialize the session."; + portal->UnsubscribeSignalHandlers(); + portal->PortalFailed(RequestResponse::kError); + return; + } +} + +template +void SessionRequestResponseSignalHelper( + const SessionClosedSignalHandler session_close_signal_handler, + T* portal, + GDBusConnection* connection, + std::string& session_handle, + GVariant* parameters, + guint& session_closed_signal_id) { + uint32_t portal_response; + Scoped response_data; + g_variant_get(parameters, /*format_string=*/"(u@a{sv})", &portal_response, + response_data.receive()); + Scoped g_session_handle( + g_variant_lookup_value(response_data.get(), /*key=*/"session_handle", + /*expected_type=*/nullptr)); + session_handle = g_variant_dup_string( + /*value=*/g_session_handle.get(), /*length=*/nullptr); + + if (session_handle.empty() || portal_response) { + RTC_LOG(LS_ERROR) << "Failed to request the session subscription."; + portal->PortalFailed(RequestResponse::kError); + return; + } + + session_closed_signal_id = g_dbus_connection_signal_subscribe( + connection, kDesktopBusName, kSessionInterfaceName, /*member=*/"Closed", + session_handle.c_str(), /*arg0=*/nullptr, G_DBUS_SIGNAL_FLAGS_NONE, + session_close_signal_handler, portal, /*user_data_free_func=*/nullptr); +} + +template +void StartRequestedHandler(T* portal, GDBusProxy* proxy, GAsyncResult* result) { + RTC_DCHECK(portal); + Scoped error; + Scoped variant( + g_dbus_proxy_call_finish(proxy, result, error.receive())); + if (!variant) { + if (g_error_matches(error.get(), G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + RTC_LOG(LS_ERROR) << "Failed to start the portal session: " + << error->message; + portal->PortalFailed(RequestResponse::kError); + return; + } + + Scoped handle; + g_variant_get_child(variant.get(), 0, "o", handle.receive()); + if (!handle) { + RTC_LOG(LS_ERROR) << "Failed to initialize the start portal session."; + portal->UnsubscribeSignalHandlers(); + portal->PortalFailed(RequestResponse::kError); + return; + } + + RTC_LOG(LS_INFO) << "Subscribed to the start signal."; +} + +} // namespace xdg_portal +} // namespace webrtc + +#endif // MODULES_DESKTOP_CAPTURE_LINUX_WAYLAND_XDG_DESKTOP_PORTAL_UTILS_H_