mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 05:40:42 +01:00

We use value -1 on over all the places through our code so it might be better to define a constant and use it instead to make the code more understandable on first look. Bug: webrtc:15203 Change-Id: I4fc3e561bc7a7778c43ec6cfde7acebef2af79e8 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/306620 Reviewed-by: Alexander Cooper <alcooper@chromium.org> Commit-Queue: Jan Grulich <grulja@gmail.com> Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org> Cr-Commit-Position: refs/heads/main@{#40156}
469 lines
16 KiB
C++
469 lines
16 KiB
C++
/*
|
|
* 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/screencast_portal.h"
|
|
|
|
#include <gio/gunixfdlist.h>
|
|
#include <glib-object.h>
|
|
|
|
#include "modules/portal/scoped_glib.h"
|
|
#include "modules/portal/xdg_desktop_portal_utils.h"
|
|
#include "rtc_base/checks.h"
|
|
#include "rtc_base/logging.h"
|
|
|
|
namespace webrtc {
|
|
namespace {
|
|
|
|
using xdg_portal::kScreenCastInterfaceName;
|
|
using xdg_portal::PrepareSignalHandle;
|
|
using xdg_portal::RequestResponse;
|
|
using xdg_portal::RequestResponseFromPortalResponse;
|
|
using xdg_portal::RequestSessionProxy;
|
|
using xdg_portal::SetupRequestResponseSignal;
|
|
using xdg_portal::SetupSessionRequestHandlers;
|
|
using xdg_portal::StartSessionRequest;
|
|
using xdg_portal::TearDownSession;
|
|
|
|
} // namespace
|
|
|
|
// static
|
|
ScreenCastPortal::CaptureSourceType ScreenCastPortal::ToCaptureSourceType(
|
|
CaptureType type) {
|
|
switch (type) {
|
|
case CaptureType::kScreen:
|
|
return ScreenCastPortal::CaptureSourceType::kScreen;
|
|
case CaptureType::kWindow:
|
|
return ScreenCastPortal::CaptureSourceType::kWindow;
|
|
}
|
|
}
|
|
|
|
ScreenCastPortal::ScreenCastPortal(CaptureType type, PortalNotifier* notifier)
|
|
: ScreenCastPortal(type,
|
|
notifier,
|
|
OnProxyRequested,
|
|
OnSourcesRequestResponseSignal,
|
|
this) {}
|
|
|
|
ScreenCastPortal::ScreenCastPortal(
|
|
CaptureType type,
|
|
PortalNotifier* notifier,
|
|
ProxyRequestResponseHandler proxy_request_response_handler,
|
|
SourcesRequestResponseSignalHandler sources_request_response_signal_handler,
|
|
gpointer user_data,
|
|
bool prefer_cursor_embedded)
|
|
: notifier_(notifier),
|
|
capture_source_type_(ToCaptureSourceType(type)),
|
|
cursor_mode_(prefer_cursor_embedded ? CursorMode::kEmbedded
|
|
: CursorMode::kMetadata),
|
|
proxy_request_response_handler_(proxy_request_response_handler),
|
|
sources_request_response_signal_handler_(
|
|
sources_request_response_signal_handler),
|
|
user_data_(user_data) {}
|
|
|
|
ScreenCastPortal::~ScreenCastPortal() {
|
|
Stop();
|
|
}
|
|
|
|
void ScreenCastPortal::Stop() {
|
|
UnsubscribeSignalHandlers();
|
|
TearDownSession(std::move(session_handle_), proxy_, cancellable_,
|
|
connection_);
|
|
session_handle_ = "";
|
|
cancellable_ = nullptr;
|
|
proxy_ = nullptr;
|
|
restore_token_ = "";
|
|
|
|
if (pw_fd_ != kInvalidPipeWireFd) {
|
|
close(pw_fd_);
|
|
pw_fd_ = kInvalidPipeWireFd;
|
|
}
|
|
}
|
|
|
|
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::SetSessionDetails(
|
|
const xdg_portal::SessionDetails& session_details) {
|
|
if (session_details.proxy) {
|
|
proxy_ = session_details.proxy;
|
|
connection_ = g_dbus_proxy_get_connection(proxy_);
|
|
}
|
|
if (session_details.cancellable) {
|
|
cancellable_ = session_details.cancellable;
|
|
}
|
|
if (!session_details.session_handle.empty()) {
|
|
session_handle_ = session_details.session_handle;
|
|
}
|
|
if (session_details.pipewire_stream_node_id) {
|
|
pw_stream_node_id_ = session_details.pipewire_stream_node_id;
|
|
}
|
|
}
|
|
|
|
void ScreenCastPortal::Start() {
|
|
cancellable_ = g_cancellable_new();
|
|
RequestSessionProxy(kScreenCastInterfaceName, proxy_request_response_handler_,
|
|
cancellable_, this);
|
|
}
|
|
|
|
xdg_portal::SessionDetails ScreenCastPortal::GetSessionDetails() {
|
|
return {}; // No-op
|
|
}
|
|
|
|
void ScreenCastPortal::OnPortalDone(RequestResponse result) {
|
|
notifier_->OnScreenCastRequestResult(result, pw_stream_node_id_, pw_fd_);
|
|
if (result != RequestResponse::kSuccess) {
|
|
Stop();
|
|
}
|
|
}
|
|
|
|
// static
|
|
void ScreenCastPortal::OnProxyRequested(GObject* gobject,
|
|
GAsyncResult* result,
|
|
gpointer user_data) {
|
|
static_cast<ScreenCastPortal*>(user_data)->RequestSessionUsingProxy(result);
|
|
}
|
|
|
|
void ScreenCastPortal::RequestSession(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) {
|
|
static_cast<ScreenCastPortal*>(user_data)->OnSessionRequestResult(proxy,
|
|
result);
|
|
}
|
|
|
|
// static
|
|
void ScreenCastPortal::OnSessionRequestResponseSignal(
|
|
GDBusConnection* connection,
|
|
const char* sender_name,
|
|
const char* object_path,
|
|
const char* interface_name,
|
|
const char* signal_name,
|
|
GVariant* parameters,
|
|
gpointer user_data) {
|
|
ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
|
|
RTC_DCHECK(that);
|
|
that->RegisterSessionClosedSignalHandler(
|
|
OnSessionClosedSignal, parameters, that->connection_,
|
|
that->session_handle_, that->session_closed_signal_id_);
|
|
|
|
// Do not continue if we don't get session_handle back. The call above will
|
|
// already notify the capturer there is a failure, but we would still continue
|
|
// to make following request and crash on that.
|
|
if (!that->session_handle_.empty()) {
|
|
that->SourcesRequest();
|
|
}
|
|
}
|
|
|
|
// static
|
|
void ScreenCastPortal::OnSessionClosedSignal(GDBusConnection* connection,
|
|
const char* sender_name,
|
|
const char* object_path,
|
|
const char* interface_name,
|
|
const char* signal_name,
|
|
GVariant* parameters,
|
|
gpointer user_data) {
|
|
ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
|
|
RTC_DCHECK(that);
|
|
|
|
RTC_LOG(LS_INFO) << "Received closed signal from session.";
|
|
|
|
that->notifier_->OnScreenCastSessionClosed();
|
|
|
|
// Unsubscribe from the signal and free the session handle to avoid calling
|
|
// Session::Close from the destructor since it's already closed
|
|
g_dbus_connection_signal_unsubscribe(that->connection_,
|
|
that->session_closed_signal_id_);
|
|
}
|
|
|
|
void ScreenCastPortal::SourcesRequest() {
|
|
GVariantBuilder builder;
|
|
Scoped<char> variant_string;
|
|
|
|
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
|
|
// We want to record monitor content.
|
|
g_variant_builder_add(
|
|
&builder, "{sv}", "types",
|
|
g_variant_new_uint32(static_cast<uint32_t>(capture_source_type_)));
|
|
// We don't want to allow selection of multiple sources.
|
|
g_variant_builder_add(&builder, "{sv}", "multiple",
|
|
g_variant_new_boolean(false));
|
|
|
|
Scoped<GVariant> cursorModesVariant(
|
|
g_dbus_proxy_get_cached_property(proxy_, "AvailableCursorModes"));
|
|
if (cursorModesVariant.get()) {
|
|
uint32_t modes = 0;
|
|
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_)) {
|
|
g_variant_builder_add(
|
|
&builder, "{sv}", "cursor_mode",
|
|
g_variant_new_uint32(static_cast<uint32_t>(cursor_mode_)));
|
|
}
|
|
}
|
|
|
|
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()));
|
|
|
|
sources_handle_ = PrepareSignalHandle(variant_string.get(), connection_);
|
|
sources_request_signal_id_ = SetupRequestResponseSignal(
|
|
sources_handle_.c_str(), sources_request_response_signal_handler_,
|
|
user_data_, connection_);
|
|
|
|
RTC_LOG(LS_INFO) << "Requesting sources from the screen cast session.";
|
|
g_dbus_proxy_call(
|
|
proxy_, "SelectSources",
|
|
g_variant_new("(oa{sv})", session_handle_.c_str(), &builder),
|
|
G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_,
|
|
reinterpret_cast<GAsyncReadyCallback>(OnSourcesRequested), this);
|
|
}
|
|
|
|
// static
|
|
void ScreenCastPortal::OnSourcesRequested(GDBusProxy* proxy,
|
|
GAsyncResult* result,
|
|
gpointer user_data) {
|
|
ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
|
|
RTC_DCHECK(that);
|
|
|
|
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 request the sources: " << error->message;
|
|
that->OnPortalDone(RequestResponse::kError);
|
|
return;
|
|
}
|
|
|
|
RTC_LOG(LS_INFO) << "Sources requested from 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 screen cast session.";
|
|
if (that->sources_request_signal_id_) {
|
|
g_dbus_connection_signal_unsubscribe(that->connection_,
|
|
that->sources_request_signal_id_);
|
|
that->sources_request_signal_id_ = 0;
|
|
}
|
|
that->OnPortalDone(RequestResponse::kError);
|
|
return;
|
|
}
|
|
|
|
RTC_LOG(LS_INFO) << "Subscribed to sources signal.";
|
|
}
|
|
|
|
// static
|
|
void ScreenCastPortal::OnSourcesRequestResponseSignal(
|
|
GDBusConnection* connection,
|
|
const char* sender_name,
|
|
const char* object_path,
|
|
const char* interface_name,
|
|
const char* signal_name,
|
|
GVariant* parameters,
|
|
gpointer user_data) {
|
|
ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
|
|
RTC_DCHECK(that);
|
|
|
|
RTC_LOG(LS_INFO) << "Received sources signal from session.";
|
|
|
|
uint32_t portal_response;
|
|
g_variant_get(parameters, "(u@a{sv})", &portal_response, nullptr);
|
|
if (portal_response) {
|
|
RTC_LOG(LS_ERROR)
|
|
<< "Failed to select sources for the screen cast session.";
|
|
that->OnPortalDone(RequestResponse::kError);
|
|
return;
|
|
}
|
|
|
|
that->StartRequest();
|
|
}
|
|
|
|
void ScreenCastPortal::StartRequest() {
|
|
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) {
|
|
static_cast<ScreenCastPortal*>(user_data)->OnStartRequestResult(proxy,
|
|
result);
|
|
}
|
|
|
|
// static
|
|
void ScreenCastPortal::OnStartRequestResponseSignal(GDBusConnection* connection,
|
|
const char* sender_name,
|
|
const char* object_path,
|
|
const char* interface_name,
|
|
const char* signal_name,
|
|
GVariant* parameters,
|
|
gpointer user_data) {
|
|
ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
|
|
RTC_DCHECK(that);
|
|
|
|
RTC_LOG(LS_INFO) << "Start signal received.";
|
|
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) {
|
|
RTC_LOG(LS_ERROR) << "Failed to start the screen cast session.";
|
|
that->OnPortalDone(RequestResponseFromPortalResponse(portal_response));
|
|
return;
|
|
}
|
|
|
|
// Array of PipeWire streams. See
|
|
// https://github.com/flatpak/xdg-desktop-portal/blob/main/data/org.freedesktop.portal.ScreenCast.xml
|
|
// documentation for <method name="Start">.
|
|
if (g_variant_lookup(response_data.get(), "streams", "a(ua{sv})",
|
|
iter.receive())) {
|
|
Scoped<GVariant> variant;
|
|
|
|
while (g_variant_iter_next(iter.get(), "@(ua{sv})", variant.receive())) {
|
|
uint32_t stream_id;
|
|
uint32_t type;
|
|
Scoped<GVariant> options;
|
|
|
|
g_variant_get(variant.get(), "(u@a{sv})", &stream_id, options.receive());
|
|
RTC_DCHECK(options.get());
|
|
|
|
if (g_variant_lookup(options.get(), "source_type", "u", &type)) {
|
|
that->capture_source_type_ =
|
|
static_cast<ScreenCastPortal::CaptureSourceType>(type);
|
|
}
|
|
|
|
that->pw_stream_node_id_ = stream_id;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (g_variant_lookup(response_data.get(), "restore_token", "s",
|
|
restore_token.receive())) {
|
|
that->restore_token_ = restore_token.get();
|
|
}
|
|
|
|
that->OpenPipeWireRemote();
|
|
}
|
|
|
|
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);
|
|
|
|
RTC_LOG(LS_INFO) << "Opening the PipeWire remote.";
|
|
|
|
g_dbus_proxy_call_with_unix_fd_list(
|
|
proxy_, "OpenPipeWireRemote",
|
|
g_variant_new("(oa{sv})", session_handle_.c_str(), &builder),
|
|
G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, /*fd_list=*/nullptr, cancellable_,
|
|
reinterpret_cast<GAsyncReadyCallback>(OnOpenPipeWireRemoteRequested),
|
|
this);
|
|
}
|
|
|
|
// static
|
|
void ScreenCastPortal::OnOpenPipeWireRemoteRequested(GDBusProxy* proxy,
|
|
GAsyncResult* result,
|
|
gpointer user_data) {
|
|
ScreenCastPortal* that = static_cast<ScreenCastPortal*>(user_data);
|
|
RTC_DCHECK(that);
|
|
|
|
Scoped<GError> error;
|
|
Scoped<GUnixFDList> outlist;
|
|
Scoped<GVariant> variant(g_dbus_proxy_call_with_unix_fd_list_finish(
|
|
proxy, outlist.receive(), 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 open the PipeWire remote: "
|
|
<< error->message;
|
|
that->OnPortalDone(RequestResponse::kError);
|
|
return;
|
|
}
|
|
|
|
int32_t index;
|
|
g_variant_get(variant.get(), "(h)", &index);
|
|
|
|
that->pw_fd_ = g_unix_fd_list_get(outlist.get(), index, error.receive());
|
|
|
|
if (that->pw_fd_ == kInvalidPipeWireFd) {
|
|
RTC_LOG(LS_ERROR) << "Failed to get file descriptor from the list: "
|
|
<< error->message;
|
|
that->OnPortalDone(RequestResponse::kError);
|
|
return;
|
|
}
|
|
|
|
that->OnPortalDone(RequestResponse::kSuccess);
|
|
}
|
|
|
|
} // namespace webrtc
|