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 <alcooper@chromium.org>
Commit-Queue: Salman Malik <salmanmalik@google.com>
Cr-Commit-Position: refs/heads/main@{#36256}
This commit is contained in:
Salman Malik
2022-03-16 21:07:11 +00:00
committed by WebRTC LUCI CQ
parent 29ad53e466
commit 76dd735a14
7 changed files with 497 additions and 261 deletions

View File

@ -574,6 +574,8 @@ rtc_library("desktop_capture_generic") {
"linux/wayland/screencast_portal.h", "linux/wayland/screencast_portal.h",
"linux/wayland/shared_screencast_stream.cc", "linux/wayland/shared_screencast_stream.cc",
"linux/wayland/shared_screencast_stream.h", "linux/wayland/shared_screencast_stream.h",
"linux/wayland/xdg_desktop_portal_utils.cc",
"linux/wayland/xdg_desktop_portal_utils.h",
] ]
configs += [ configs += [

View File

@ -12,11 +12,18 @@
#include "modules/desktop_capture/desktop_capture_options.h" #include "modules/desktop_capture/desktop_capture_options.h"
#include "modules/desktop_capture/desktop_capturer.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/checks.h"
#include "rtc_base/logging.h" #include "rtc_base/logging.h"
namespace webrtc { namespace webrtc {
namespace {
using xdg_portal::RequestResponse;
} // namespace
BaseCapturerPipeWire::BaseCapturerPipeWire(const DesktopCaptureOptions& options) BaseCapturerPipeWire::BaseCapturerPipeWire(const DesktopCaptureOptions& options)
: options_(options) { : options_(options) {
screencast_portal_ = std::make_unique<ScreenCastPortal>( screencast_portal_ = std::make_unique<ScreenCastPortal>(
@ -25,11 +32,10 @@ BaseCapturerPipeWire::BaseCapturerPipeWire(const DesktopCaptureOptions& options)
BaseCapturerPipeWire::~BaseCapturerPipeWire() {} BaseCapturerPipeWire::~BaseCapturerPipeWire() {}
void BaseCapturerPipeWire::OnScreenCastRequestResult( void BaseCapturerPipeWire::OnScreenCastRequestResult(RequestResponse result,
ScreenCastPortal::RequestResponse result,
uint32_t stream_node_id, uint32_t stream_node_id,
int fd) { int fd) {
if (result != ScreenCastPortal::RequestResponse::kSuccess || if (result != RequestResponse::kSuccess ||
!options_.screencast_stream()->StartScreenCastStream(stream_node_id, !options_.screencast_stream()->StartScreenCastStream(stream_node_id,
fd)) { fd)) {
capturer_failed_ = true; capturer_failed_ = true;

View File

@ -15,6 +15,7 @@
#include "modules/desktop_capture/desktop_capturer.h" #include "modules/desktop_capture/desktop_capturer.h"
#include "modules/desktop_capture/linux/wayland/screencast_portal.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/shared_screencast_stream.h"
#include "modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h"
namespace webrtc { namespace webrtc {
@ -34,7 +35,7 @@ class BaseCapturerPipeWire : public DesktopCapturer,
bool SelectSource(SourceId id) override; bool SelectSource(SourceId id) override;
// ScreenCastPortal::PortalNotifier interface. // ScreenCastPortal::PortalNotifier interface.
void OnScreenCastRequestResult(ScreenCastPortal::RequestResponse result, void OnScreenCastRequestResult(xdg_portal::RequestResponse result,
uint32_t stream_node_id, uint32_t stream_node_id,
int fd) override; int fd) override;
void OnScreenCastSessionClosed() override; void OnScreenCastSessionClosed() override;

View File

@ -14,73 +14,67 @@
#include <glib-object.h> #include <glib-object.h>
#include "modules/desktop_capture/linux/wayland/scoped_glib.h" #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/checks.h"
#include "rtc_base/logging.h" #include "rtc_base/logging.h"
namespace webrtc { namespace webrtc {
namespace {
const char kDesktopBusName[] = "org.freedesktop.portal.Desktop"; using xdg_portal::kScreenCastInterfaceName;
const char kDesktopObjectPath[] = "/org/freedesktop/portal/desktop"; using xdg_portal::PrepareSignalHandle;
const char kDesktopRequestObjectPath[] = using xdg_portal::RequestResponse;
"/org/freedesktop/portal/desktop/request"; using xdg_portal::RequestSessionProxy;
const char kSessionInterfaceName[] = "org.freedesktop.portal.Session"; using xdg_portal::RequestSessionUsingProxy;
const char kRequestInterfaceName[] = "org.freedesktop.portal.Request"; using xdg_portal::SessionRequestHandler;
const char kScreenCastInterfaceName[] = "org.freedesktop.portal.ScreenCast"; 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, } // namespace
ScreenCastPortal::ScreenCastPortal(
ScreenCastPortal::CaptureSourceType source_type,
PortalNotifier* notifier) PortalNotifier* notifier)
: notifier_(notifier), capture_source_type_(source_type) {} : notifier_(notifier), capture_source_type_(source_type) {}
ScreenCastPortal::~ScreenCastPortal() { ScreenCastPortal::~ScreenCastPortal() {
if (start_request_signal_id_) { UnsubscribeSignalHandlers();
g_dbus_connection_signal_unsubscribe(connection_, start_request_signal_id_); TearDownSession(std::move(session_handle_), proxy_, cancellable_,
} connection_);
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<GDBusMessage> message(
g_dbus_message_new_method_call(kDesktopBusName, session_handle_.c_str(),
kSessionInterfaceName, "Close"));
if (message.get()) {
Scoped<GError> 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; cancellable_ = nullptr;
}
if (proxy_) {
g_object_unref(proxy_);
proxy_ = nullptr; proxy_ = nullptr;
}
if (pw_fd_ != -1) { if (pw_fd_ != -1) {
close(pw_fd_); 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() { void ScreenCastPortal::Start() {
cancellable_ = g_cancellable_new(); cancellable_ = g_cancellable_new();
g_dbus_proxy_new_for_bus( RequestSessionProxy(kScreenCastInterfaceName, OnProxyRequested, cancellable_,
G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, /*info=*/nullptr,
kDesktopBusName, kDesktopObjectPath, kScreenCastInterfaceName,
cancellable_, reinterpret_cast<GAsyncReadyCallback>(OnProxyRequested),
this); this);
} }
@ -88,115 +82,28 @@ void ScreenCastPortal::PortalFailed(RequestResponse result) {
notifier_->OnScreenCastRequestResult(result, pw_stream_node_id_, pw_fd_); 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 // static
void ScreenCastPortal::OnProxyRequested(GObject* /*object*/, void ScreenCastPortal::OnProxyRequested(GObject* gobject,
GAsyncResult* result, GAsyncResult* result,
gpointer user_data) { gpointer user_data) {
ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data); RequestSessionUsingProxy<ScreenCastPortal>(
RTC_DCHECK(that); static_cast<ScreenCastPortal*>(user_data), gobject, result);
Scoped<GError> 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();
} }
// static void ScreenCastPortal::SessionRequest(GDBusProxy* proxy) {
std::string ScreenCastPortal::PrepareSignalHandle(GDBusConnection* connection, proxy_ = proxy;
const char* token) { connection_ = g_dbus_proxy_get_connection(proxy_);
Scoped<char> sender( SetupSessionRequestHandlers(
g_strdup(g_dbus_connection_get_unique_name(connection) + 1)); "webrtc", OnSessionRequested, OnSessionRequestResponseSignal, connection_,
for (int i = 0; sender.get()[i]; ++i) { proxy_, cancellable_, portal_handle_, session_request_signal_id_, this);
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<char> 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<GAsyncReadyCallback>(OnSessionRequested),
this);
} }
// static // static
void ScreenCastPortal::OnSessionRequested(GDBusProxy* proxy, void ScreenCastPortal::OnSessionRequested(GDBusProxy* proxy,
GAsyncResult* result, GAsyncResult* result,
gpointer user_data) { gpointer user_data) {
ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data); SessionRequestHandler(static_cast<ScreenCastPortal*>(user_data), proxy,
RTC_DCHECK(that); result, user_data);
Scoped<GError> error;
Scoped<GVariant> 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<char> 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.";
} }
// static // static
@ -210,30 +117,9 @@ void ScreenCastPortal::OnSessionRequestResponseSignal(
gpointer user_data) { gpointer user_data) {
ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data); ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
RTC_DCHECK(that); RTC_DCHECK(that);
SessionRequestResponseSignalHelper(
RTC_LOG(LS_INFO) OnSessionClosedSignal, that, that->connection_, that->session_handle_,
<< "Received response for the screen cast session subscription."; parameters, that->session_closed_signal_id_);
uint32_t portal_response;
Scoped<GVariant> response_data;
g_variant_get(parameters, "(u@a{sv})", &portal_response,
response_data.receive());
Scoped<GVariant> 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);
that->SourcesRequest(); that->SourcesRequest();
} }
@ -289,9 +175,10 @@ void ScreenCastPortal::SourcesRequest() {
g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_builder_add(&builder, "{sv}", "handle_token",
g_variant_new_string(variant_string.get())); 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_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."; RTC_LOG(LS_INFO) << "Requesting sources from the screen cast session.";
g_dbus_proxy_call( g_dbus_proxy_call(
@ -364,66 +251,17 @@ void ScreenCastPortal::OnSourcesRequestResponseSignal(
} }
void ScreenCastPortal::StartRequest() { void ScreenCastPortal::StartRequest() {
GVariantBuilder builder; StartSessionRequest("webrtc", session_handle_, OnStartRequestResponseSignal,
Scoped<char> variant_string; OnStartRequested, proxy_, connection_, cancellable_,
start_request_signal_id_, start_handle_, this);
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<GAsyncReadyCallback>(OnStartRequested),
this);
} }
// static // static
void ScreenCastPortal::OnStartRequested(GDBusProxy* proxy, void ScreenCastPortal::OnStartRequested(GDBusProxy* proxy,
GAsyncResult* result, GAsyncResult* result,
gpointer user_data) { gpointer user_data) {
ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data); StartRequestedHandler(static_cast<ScreenCastPortal*>(user_data), proxy,
RTC_DCHECK(that); result);
Scoped<GError> error;
Scoped<GVariant> 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<char> 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.";
} }
// static // static
@ -525,8 +363,7 @@ void ScreenCastPortal::OnOpenPipeWireRemoteRequested(GDBusProxy* proxy,
} }
that->notifier_->OnScreenCastRequestResult( that->notifier_->OnScreenCastRequestResult(
ScreenCastPortal::RequestResponse::kSuccess, that->pw_stream_node_id_, RequestResponse::kSuccess, that->pw_stream_node_id_, that->pw_fd_);
that->pw_fd_);
} }
} // namespace webrtc } // namespace webrtc

View File

@ -15,7 +15,7 @@
#include <string> #include <string>
#include "absl/types/optional.h" #include "modules/desktop_capture/linux/wayland/xdg_desktop_portal_utils.h"
namespace webrtc { namespace webrtc {
@ -43,20 +43,9 @@ class ScreenCastPortal {
}; };
// Interface that must be implemented by the ScreenCastPortal consumers. // 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 { class PortalNotifier {
public: public:
virtual void OnScreenCastRequestResult(RequestResponse result, virtual void OnScreenCastRequestResult(xdg_portal::RequestResponse result,
uint32_t stream_node_id, uint32_t stream_node_id,
int fd) = 0; int fd) = 0;
virtual void OnScreenCastSessionClosed() = 0; virtual void OnScreenCastSessionClosed() = 0;
@ -66,7 +55,7 @@ class ScreenCastPortal {
virtual ~PortalNotifier() = default; virtual ~PortalNotifier() = default;
}; };
explicit ScreenCastPortal(CaptureSourceType source_type, explicit ScreenCastPortal(ScreenCastPortal::CaptureSourceType source_type,
PortalNotifier* notifier); PortalNotifier* notifier);
~ScreenCastPortal(); ~ScreenCastPortal();
@ -79,6 +68,14 @@ class ScreenCastPortal {
// information in order to continue working with PipeWire. // information in order to continue working with PipeWire.
void Start(); 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: private:
PortalNotifier* notifier_; PortalNotifier* notifier_;
@ -104,19 +101,9 @@ class ScreenCastPortal {
guint start_request_signal_id_ = 0; guint start_request_signal_id_ = 0;
guint session_closed_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, static void OnProxyRequested(GObject* object,
GAsyncResult* result, GAsyncResult* result,
gpointer user_data); gpointer user_data);
static std::string PrepareSignalHandle(GDBusConnection* connection,
const char* token);
void SessionRequest();
static void OnSessionRequested(GDBusProxy* proxy, static void OnSessionRequested(GDBusProxy* proxy,
GAsyncResult* result, GAsyncResult* result,
gpointer user_data); gpointer user_data);

View File

@ -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<char> 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<GAsyncReadyCallback>(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<char> 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<GAsyncReadyCallback>(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<char> 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<GAsyncReadyCallback>(session_started_handler),
user_data);
}
void TearDownSession(std::string session_handle,
GDBusProxy* proxy,
GCancellable* cancellable,
GDBusConnection* connection) {
if (!session_handle.empty()) {
Scoped<GDBusMessage> message(
g_dbus_message_new_method_call(kDesktopBusName, session_handle.c_str(),
kSessionInterfaceName, "Close"));
if (message.get()) {
Scoped<GError> 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

View File

@ -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 <gio/gio.h>
#include <stdint.h>
#include <string>
#include <vector>
#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 <typename T>
void RequestSessionUsingProxy(T* portal,
GObject* gobject,
GAsyncResult* result) {
RTC_DCHECK(portal);
Scoped<GError> 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 <typename T>
void SessionRequestHandler(T* portal,
GDBusProxy* proxy,
GAsyncResult* result,
gpointer user_data) {
RTC_DCHECK(portal);
Scoped<GError> error;
Scoped<GVariant> 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<char> 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 <typename T>
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<GVariant> response_data;
g_variant_get(parameters, /*format_string=*/"(u@a{sv})", &portal_response,
response_data.receive());
Scoped<GVariant> 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 <typename T>
void StartRequestedHandler(T* portal, GDBusProxy* proxy, GAsyncResult* result) {
RTC_DCHECK(portal);
Scoped<GError> error;
Scoped<GVariant> 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<char> 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_