Improve screen sharing with PipeWire on Wayland
Currently, sharing a screen or a window on Wayland opens unnecessary
preview dialog on Chromium side, which is then followed by a similar
dialog on xdg-desktop-portal side. The Chromium dialog is useless on
Wayland, as it doesn't show anything. This is because Chromium doesn't
have access to screen content as in case of X11 session. To fix this, we
want to avoid showing the preview dialog in case we find that we run on
Wayland and only pick a screen or a window from the dialog that comes
from xdg-desktop-portal.
This patch splits BaseCapturerPipeWire class, moving portal related code
into XdgPortalBase, which does all the DBus communication and which is
supposed to be reused by BaseCapturerPipeWire when the user confirms
the dialog from xdg-desktop-portal. The XdgPortalBase is extended to
support multiple calls at once, where each call is identified by Id.
Relevant change on Chromium side will be in a different review.
Bug: chromium:682122
Change-Id: If8afd36da66231eb154cdc00114908ac897ee4cf
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/160649
Commit-Queue: Tommi <tommi@webrtc.org>
Reviewed-by: Tommi <tommi@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#32342}
diff --git a/modules/desktop_capture/BUILD.gn b/modules/desktop_capture/BUILD.gn
index 2cb6891..9bb9f62 100644
--- a/modules/desktop_capture/BUILD.gn
+++ b/modules/desktop_capture/BUILD.gn
@@ -76,6 +76,21 @@
"window_finder_unittest.cc",
]
public_configs = [ ":x11_config" ]
+ if (is_linux) {
+ public_configs += [ ":pipewire_config" ]
+ if (rtc_use_pipewire) {
+ configs += [
+ ":pipewire_config",
+ ":gio",
+ ]
+
+ if (rtc_link_pipewire) {
+ configs += [ ":pipewire" ]
+ } else {
+ deps += [ ":pipewire_stubs" ]
+ }
+ }
+ }
if (is_win) {
deps += [ "../../rtc_base:win32" ]
}
@@ -132,6 +147,21 @@
}
deps += [ ":desktop_capture_mock" ]
public_configs = [ ":x11_config" ]
+ if (is_linux) {
+ public_configs += [ ":pipewire_config" ]
+ if (rtc_use_pipewire) {
+ configs += [
+ ":pipewire_config",
+ ":gio",
+ ]
+
+ if (rtc_link_pipewire) {
+ configs += [ ":pipewire" ]
+ } else {
+ deps += [ ":pipewire_stubs" ]
+ }
+ }
+ }
}
}
@@ -496,6 +526,7 @@
absl_deps = [
"//third_party/abseil-cpp/absl/memory",
"//third_party/abseil-cpp/absl/strings",
+ "//third_party/abseil-cpp/absl/types:optional",
]
if (rtc_use_x11_extensions) {
@@ -516,10 +547,10 @@
sources += [
"linux/base_capturer_pipewire.cc",
"linux/base_capturer_pipewire.h",
- "linux/screen_capturer_pipewire.cc",
- "linux/screen_capturer_pipewire.h",
- "linux/window_capturer_pipewire.cc",
- "linux/window_capturer_pipewire.h",
+ "linux/pipewire_base.cc",
+ "linux/pipewire_base.h",
+ "linux/xdg_desktop_portal_base.cc",
+ "linux/xdg_desktop_portal_base.h",
]
configs += [
diff --git a/modules/desktop_capture/desktop_capture_options.cc b/modules/desktop_capture/desktop_capture_options.cc
index c89896d..5c17dfb 100644
--- a/modules/desktop_capture/desktop_capture_options.cc
+++ b/modules/desktop_capture/desktop_capture_options.cc
@@ -35,6 +35,9 @@
#if defined(WEBRTC_USE_X11)
result.set_x_display(SharedXDisplay::CreateDefault());
#endif
+#if defined(WEBRTC_USE_PIPEWIRE)
+ result.set_xdp_base(XdgDesktopPortalBase::CreateDefault());
+#endif
#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
result.set_configuration_monitor(new DesktopConfigurationMonitor());
result.set_full_screen_window_detector(
diff --git a/modules/desktop_capture/desktop_capture_options.h b/modules/desktop_capture/desktop_capture_options.h
index 521c80b..5323841 100644
--- a/modules/desktop_capture/desktop_capture_options.h
+++ b/modules/desktop_capture/desktop_capture_options.h
@@ -10,6 +10,7 @@
#ifndef MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_OPTIONS_H_
#define MODULES_DESKTOP_CAPTURE_DESKTOP_CAPTURE_OPTIONS_H_
+#include "absl/types/optional.h"
#include "api/scoped_refptr.h"
#include "rtc_base/system/rtc_export.h"
@@ -23,6 +24,10 @@
#include "modules/desktop_capture/full_screen_window_detector.h"
+#if defined(WEBRTC_USE_PIPEWIRE)
+#include "modules/desktop_capture/linux/xdg_desktop_portal_base.h"
+#endif
+
namespace webrtc {
// An object that stores initialization parameters for screen and window
@@ -131,13 +136,68 @@
#if defined(WEBRTC_USE_PIPEWIRE)
bool allow_pipewire() const { return allow_pipewire_; }
void set_allow_pipewire(bool allow) { allow_pipewire_ = allow; }
+
+ // Provides a way how to identify portal call for a sharing request
+ // made by the client. This allows to go through the preview dialog
+ // and to the web page itself with just one xdg-desktop-portal call.
+ // Client is supposed to:
+ // 1) Call start_request(id) to tell us an identificator for the current
+ // request
+ // 2) Call close_request(id) in case the preview dialog was cancelled
+ // or user picked a web page to be shared
+ // Note: In case the current request is not finalized, we will close it for
+ // safety reasons and client will need to ask the portal again
+ // This was done primarily for chromium support as there was no way how to
+ // identify a portal call made for the preview and later on continue with the
+ // same content on the web page itself.
+
+ void start_request(int32_t request_id) {
+ // In case we get a duplicit start_request call, which might happen when a
+ // browser requests both screen and window sharing, we don't want to do
+ // anything.
+ if (request_id == xdp_base_->CurrentConnectionId()) {
+ return;
+ }
+
+ // In case we are about to start a new request and the previous one is not
+ // finalized and not stream to the web page itself we will just close it.
+ if (!xdp_base_->IsConnectionStreamingOnWeb(absl::nullopt) &&
+ xdp_base_->IsConnectionInitialized(absl::nullopt)) {
+ xdp_base_->CloseConnection(absl::nullopt);
+ }
+
+ xdp_base_->SetCurrentConnectionId(request_id);
+ }
+
+ void close_request(int32_t request_id) {
+ xdp_base_->CloseConnection(request_id);
+ xdp_base_->SetCurrentConnectionId(absl::nullopt);
+ }
+
+ absl::optional<int32_t> request_id() {
+ // Reset request_id in case the connection is in final state, which means it
+ // is streaming content to the web page itself and nobody should be asking
+ // again for this ID.
+ if (xdp_base_->IsConnectionStreamingOnWeb(absl::nullopt)) {
+ xdp_base_->SetCurrentConnectionId(absl::nullopt);
+ }
+
+ return xdp_base_->CurrentConnectionId();
+ }
+
+ XdgDesktopPortalBase* xdp_base() const { return xdp_base_; }
+ void set_xdp_base(rtc::scoped_refptr<XdgDesktopPortalBase> xdp_base) {
+ xdp_base_ = std::move(xdp_base);
+ }
#endif
private:
#if defined(WEBRTC_USE_X11)
rtc::scoped_refptr<SharedXDisplay> x_display_;
#endif
-
+#if defined(WEBRTC_USE_PIPEWIRE)
+ rtc::scoped_refptr<XdgDesktopPortalBase> xdp_base_;
+#endif
#if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor_;
bool allow_iosurface_ = false;
diff --git a/modules/desktop_capture/linux/base_capturer_pipewire.cc b/modules/desktop_capture/linux/base_capturer_pipewire.cc
index 2640e93..f9323f18 100644
--- a/modules/desktop_capture/linux/base_capturer_pipewire.cc
+++ b/modules/desktop_capture/linux/base_capturer_pipewire.cc
@@ -10,859 +10,102 @@
#include "modules/desktop_capture/linux/base_capturer_pipewire.h"
-#include <gio/gunixfdlist.h>
-#include <glib-object.h>
-#include <spa/param/format-utils.h>
-#include <spa/param/props.h>
-#include <spa/param/video/raw-utils.h>
-#include <spa/support/type-map.h>
-
-#include <memory>
-#include <utility>
-
-#include "absl/memory/memory.h"
-#include "modules/desktop_capture/desktop_capture_options.h"
#include "modules/desktop_capture/desktop_capturer.h"
+#include "modules/desktop_capture/linux/pipewire_base.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
-#if defined(WEBRTC_DLOPEN_PIPEWIRE)
-#include "modules/desktop_capture/linux/pipewire_stubs.h"
-
-using modules_desktop_capture_linux::InitializeStubs;
-using modules_desktop_capture_linux::kModulePipewire;
-using modules_desktop_capture_linux::StubPathMap;
-#endif // defined(WEBRTC_DLOPEN_PIPEWIRE)
-
namespace webrtc {
-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";
-
-const int kBytesPerPixel = 4;
-
-#if defined(WEBRTC_DLOPEN_PIPEWIRE)
-const char kPipeWireLib[] = "libpipewire-0.2.so.1";
-#endif
-
-// static
-void BaseCapturerPipeWire::OnStateChanged(void* data,
- pw_remote_state old_state,
- pw_remote_state state,
- const char* error_message) {
- BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data);
- RTC_DCHECK(that);
-
- switch (state) {
- case PW_REMOTE_STATE_ERROR:
- RTC_LOG(LS_ERROR) << "PipeWire remote state error: " << error_message;
- break;
- case PW_REMOTE_STATE_CONNECTED:
- RTC_LOG(LS_INFO) << "PipeWire remote state: connected.";
- that->CreateReceivingStream();
- break;
- case PW_REMOTE_STATE_CONNECTING:
- RTC_LOG(LS_INFO) << "PipeWire remote state: connecting.";
- break;
- case PW_REMOTE_STATE_UNCONNECTED:
- RTC_LOG(LS_INFO) << "PipeWire remote state: unconnected.";
- break;
- }
-}
-
-// static
-void BaseCapturerPipeWire::OnStreamStateChanged(void* data,
- pw_stream_state old_state,
- pw_stream_state state,
- const char* error_message) {
- BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data);
- RTC_DCHECK(that);
-
- switch (state) {
- case PW_STREAM_STATE_ERROR:
- RTC_LOG(LS_ERROR) << "PipeWire stream state error: " << error_message;
- break;
- case PW_STREAM_STATE_CONFIGURE:
- pw_stream_set_active(that->pw_stream_, true);
- break;
- case PW_STREAM_STATE_UNCONNECTED:
- case PW_STREAM_STATE_CONNECTING:
- case PW_STREAM_STATE_READY:
- case PW_STREAM_STATE_PAUSED:
- case PW_STREAM_STATE_STREAMING:
- break;
- }
-}
-
-// static
-void BaseCapturerPipeWire::OnStreamFormatChanged(void* data,
- const struct spa_pod* format) {
- BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data);
- RTC_DCHECK(that);
-
- RTC_LOG(LS_INFO) << "PipeWire stream format changed.";
-
- if (!format) {
- pw_stream_finish_format(that->pw_stream_, /*res=*/0, /*params=*/nullptr,
- /*n_params=*/0);
- return;
- }
-
- that->spa_video_format_ = new spa_video_info_raw();
- spa_format_video_raw_parse(format, that->spa_video_format_,
- &that->pw_type_->format_video);
-
- auto width = that->spa_video_format_->size.width;
- auto height = that->spa_video_format_->size.height;
- auto stride = SPA_ROUND_UP_N(width * kBytesPerPixel, 4);
- auto size = height * stride;
-
- uint8_t buffer[1024] = {};
- auto builder = spa_pod_builder{buffer, sizeof(buffer)};
-
- // Setup buffers and meta header for new format.
- const struct spa_pod* params[2];
- params[0] = reinterpret_cast<spa_pod*>(spa_pod_builder_object(
- &builder,
- // id to enumerate buffer requirements
- that->pw_core_type_->param.idBuffers,
- that->pw_core_type_->param_buffers.Buffers,
- // Size: specified as integer (i) and set to specified size
- ":", that->pw_core_type_->param_buffers.size, "i", size,
- // Stride: specified as integer (i) and set to specified stride
- ":", that->pw_core_type_->param_buffers.stride, "i", stride,
- // Buffers: specifies how many buffers we want to deal with, set as
- // integer (i) where preferred number is 8, then allowed number is defined
- // as range (r) from min and max values and it is undecided (u) to allow
- // negotiation
- ":", that->pw_core_type_->param_buffers.buffers, "iru", 8,
- SPA_POD_PROP_MIN_MAX(1, 32),
- // Align: memory alignment of the buffer, set as integer (i) to specified
- // value
- ":", that->pw_core_type_->param_buffers.align, "i", 16));
- params[1] = reinterpret_cast<spa_pod*>(spa_pod_builder_object(
- &builder,
- // id to enumerate supported metadata
- that->pw_core_type_->param.idMeta, that->pw_core_type_->param_meta.Meta,
- // Type: specified as id or enum (I)
- ":", that->pw_core_type_->param_meta.type, "I",
- that->pw_core_type_->meta.Header,
- // Size: size of the metadata, specified as integer (i)
- ":", that->pw_core_type_->param_meta.size, "i",
- sizeof(struct spa_meta_header)));
-
- pw_stream_finish_format(that->pw_stream_, /*res=*/0, params, /*n_params=*/2);
-}
-
-// static
-void BaseCapturerPipeWire::OnStreamProcess(void* data) {
- BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(data);
- RTC_DCHECK(that);
-
- pw_buffer* buf = nullptr;
-
- if (!(buf = pw_stream_dequeue_buffer(that->pw_stream_))) {
- return;
- }
-
- that->HandleBuffer(buf);
-
- pw_stream_queue_buffer(that->pw_stream_, buf);
-}
-
-BaseCapturerPipeWire::BaseCapturerPipeWire(CaptureSourceType source_type)
- : capture_source_type_(source_type) {}
+BaseCapturerPipeWire::BaseCapturerPipeWire() {}
BaseCapturerPipeWire::~BaseCapturerPipeWire() {
- if (pw_main_loop_) {
- pw_thread_loop_stop(pw_main_loop_);
- }
-
- if (pw_type_) {
- delete pw_type_;
- }
-
- if (spa_video_format_) {
- delete spa_video_format_;
- }
-
- if (pw_stream_) {
- pw_stream_destroy(pw_stream_);
- }
-
- if (pw_remote_) {
- pw_remote_destroy(pw_remote_);
- }
-
- if (pw_core_) {
- pw_core_destroy(pw_core_);
- }
-
- if (pw_main_loop_) {
- pw_thread_loop_destroy(pw_main_loop_);
- }
-
- if (pw_loop_) {
- pw_loop_destroy(pw_loop_);
- }
-
- if (current_frame_) {
- free(current_frame_);
- }
-
- 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_) {
- GDBusMessage* message = g_dbus_message_new_method_call(
- kDesktopBusName, session_handle_, kSessionInterfaceName, "Close");
- if (message) {
- GError* error = nullptr;
- g_dbus_connection_send_message(connection_, message,
- G_DBUS_SEND_MESSAGE_FLAGS_NONE,
- /*out_serial=*/nullptr, &error);
- if (error) {
- RTC_LOG(LS_ERROR) << "Failed to close the session: " << error->message;
- g_error_free(error);
- }
- g_object_unref(message);
- }
- }
-
- g_free(start_handle_);
- g_free(sources_handle_);
- g_free(session_handle_);
- g_free(portal_handle_);
-
- if (cancellable_) {
- g_cancellable_cancel(cancellable_);
- g_object_unref(cancellable_);
- cancellable_ = nullptr;
- }
-
- if (proxy_) {
- g_object_unref(proxy_);
- proxy_ = nullptr;
+ webrtc::XdgDesktopPortalBase* xdpBase = options_.xdp_base();
+ if (auto_close_connection_ || xdpBase->IsConnectionStreamingOnWeb(id_)) {
+ xdpBase->CloseConnection(id_);
}
}
-void BaseCapturerPipeWire::InitPortal() {
- 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<GAsyncReadyCallback>(OnProxyRequested), this);
-}
+bool BaseCapturerPipeWire::Init(const DesktopCaptureOptions& options,
+ XdgDesktopPortalBase::CaptureSourceType type) {
+ options_ = options;
+ type_ = type;
-void BaseCapturerPipeWire::InitPipeWire() {
-#if defined(WEBRTC_DLOPEN_PIPEWIRE)
- StubPathMap paths;
+ XdgDesktopPortalBase::CaptureSourceType requestedType_ =
+ XdgDesktopPortalBase::CaptureSourceType::kAny;
- // Check if the PipeWire library is available.
- paths[kModulePipewire].push_back(kPipeWireLib);
- if (!InitializeStubs(paths)) {
- RTC_LOG(LS_ERROR) << "Failed to load the PipeWire library and symbols.";
- portal_init_failed_ = true;
- return;
- }
-#endif // defined(WEBRTC_DLOPEN_PIPEWIRE)
+ id_ = options_.request_id();
- pw_init(/*argc=*/nullptr, /*argc=*/nullptr);
-
- pw_loop_ = pw_loop_new(/*properties=*/nullptr);
- pw_main_loop_ = pw_thread_loop_new(pw_loop_, "pipewire-main-loop");
-
- pw_core_ = pw_core_new(pw_loop_, /*properties=*/nullptr);
- pw_core_type_ = pw_core_get_type(pw_core_);
- pw_remote_ = pw_remote_new(pw_core_, nullptr, /*user_data_size=*/0);
-
- InitPipeWireTypes();
-
- // Initialize event handlers, remote end and stream-related.
- pw_remote_events_.version = PW_VERSION_REMOTE_EVENTS;
- pw_remote_events_.state_changed = &OnStateChanged;
-
- pw_stream_events_.version = PW_VERSION_STREAM_EVENTS;
- pw_stream_events_.state_changed = &OnStreamStateChanged;
- pw_stream_events_.format_changed = &OnStreamFormatChanged;
- pw_stream_events_.process = &OnStreamProcess;
-
- pw_remote_add_listener(pw_remote_, &spa_remote_listener_, &pw_remote_events_,
- this);
- pw_remote_connect_fd(pw_remote_, pw_fd_);
-
- if (pw_thread_loop_start(pw_main_loop_) < 0) {
- RTC_LOG(LS_ERROR) << "Failed to start main PipeWire loop";
- portal_init_failed_ = true;
+ // We need some ID to be able to identify our capturer
+ if (!id_) {
+ id_ = g_random_int_range(0, G_MAXINT);
+ auto_close_connection_ = true;
+ requestedType_ = type;
}
- RTC_LOG(LS_INFO) << "PipeWire remote opened.";
-}
+ webrtc::XdgDesktopPortalBase* xdpBase = options_.xdp_base();
-void BaseCapturerPipeWire::InitPipeWireTypes() {
- spa_type_map* map = pw_core_type_->map;
- pw_type_ = new PipeWireType();
-
- spa_type_media_type_map(map, &pw_type_->media_type);
- spa_type_media_subtype_map(map, &pw_type_->media_subtype);
- spa_type_format_video_map(map, &pw_type_->format_video);
- spa_type_video_format_map(map, &pw_type_->video_format);
-}
-
-void BaseCapturerPipeWire::CreateReceivingStream() {
- spa_rectangle pwMinScreenBounds = spa_rectangle{1, 1};
- spa_rectangle pwScreenBounds =
- spa_rectangle{static_cast<uint32_t>(desktop_size_.width()),
- static_cast<uint32_t>(desktop_size_.height())};
-
- spa_fraction pwFrameRateMin = spa_fraction{0, 1};
- spa_fraction pwFrameRateMax = spa_fraction{60, 1};
-
- pw_properties* reuseProps =
- pw_properties_new_string("pipewire.client.reuse=1");
- pw_stream_ = pw_stream_new(pw_remote_, "webrtc-consume-stream", reuseProps);
-
- uint8_t buffer[1024] = {};
- const spa_pod* params[1];
- spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)};
- params[0] = reinterpret_cast<spa_pod*>(spa_pod_builder_object(
- &builder,
- // id to enumerate formats
- pw_core_type_->param.idEnumFormat, pw_core_type_->spa_format, "I",
- pw_type_->media_type.video, "I", pw_type_->media_subtype.raw,
- // Video format: specified as id or enum (I), preferred format is BGRx,
- // then allowed formats are enumerated (e) and the format is undecided (u)
- // to allow negotiation
- ":", pw_type_->format_video.format, "Ieu", pw_type_->video_format.BGRx,
- SPA_POD_PROP_ENUM(2, pw_type_->video_format.RGBx,
- pw_type_->video_format.BGRx),
- // Video size: specified as rectangle (R), preferred size is specified as
- // first parameter, then allowed size is defined as range (r) from min and
- // max values and the format is undecided (u) to allow negotiation
- ":", pw_type_->format_video.size, "Rru", &pwScreenBounds, 2,
- &pwMinScreenBounds, &pwScreenBounds,
- // Frame rate: specified as fraction (F) and set to minimum frame rate
- // value
- ":", pw_type_->format_video.framerate, "F", &pwFrameRateMin,
- // Max frame rate: specified as fraction (F), preferred frame rate is set
- // to maximum value, then allowed frame rate is defined as range (r) from
- // min and max values and it is undecided (u) to allow negotiation
- ":", pw_type_->format_video.max_framerate, "Fru", &pwFrameRateMax, 2,
- &pwFrameRateMin, &pwFrameRateMax));
-
- pw_stream_add_listener(pw_stream_, &spa_stream_listener_, &pw_stream_events_,
- this);
- pw_stream_flags flags = static_cast<pw_stream_flags>(
- PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE |
- PW_STREAM_FLAG_MAP_BUFFERS);
- if (pw_stream_connect(pw_stream_, PW_DIRECTION_INPUT, /*port_path=*/nullptr,
- flags, params,
- /*n_params=*/1) != 0) {
- RTC_LOG(LS_ERROR) << "Could not connect receiving stream.";
- portal_init_failed_ = true;
- return;
- }
-}
-
-void BaseCapturerPipeWire::HandleBuffer(pw_buffer* buffer) {
- spa_buffer* spaBuffer = buffer->buffer;
- void* src = nullptr;
-
- if (!(src = spaBuffer->datas[0].data)) {
- return;
+ if (xdpBase->IsConnectionInitialized(id_.value())) {
+ // Because capturers created for the preview dialog (Chrome, Firefox) will
+ // be created simultaneously and because of that the connection cannot be
+ // initialized yet, we can safely assume this a capturer created in the
+ // final state to show the content on the web page itself Note: this will
+ // have no effect on clients not using our specific API in
+ // DesktopCaptureOptions
+ xdpBase->SetConnectionStreamingOnWeb(id_.value());
+ portal_initialized_ = true;
+ return true;
}
- uint32_t maxSize = spaBuffer->datas[0].maxsize;
- int32_t srcStride = spaBuffer->datas[0].chunk->stride;
- if (srcStride != (desktop_size_.width() * kBytesPerPixel)) {
- RTC_LOG(LS_ERROR) << "Got buffer with stride different from screen stride: "
- << srcStride
- << " != " << (desktop_size_.width() * kBytesPerPixel);
- portal_init_failed_ = true;
- return;
- }
+ auto lambda = [=](bool result) { portal_initialized_ = result; };
- if (!current_frame_) {
- current_frame_ = static_cast<uint8_t*>(malloc(maxSize));
- }
- RTC_DCHECK(current_frame_ != nullptr);
+ rtc::Callback1<void, bool> cb = lambda;
+ xdpBase->InitPortal(cb, requestedType_, id_.value());
- // If both sides decided to go with the RGBx format we need to convert it to
- // BGRx to match color format expected by WebRTC.
- if (spa_video_format_->format == pw_type_->video_format.RGBx) {
- uint8_t* tempFrame = static_cast<uint8_t*>(malloc(maxSize));
- std::memcpy(tempFrame, src, maxSize);
- ConvertRGBxToBGRx(tempFrame, maxSize);
- std::memcpy(current_frame_, tempFrame, maxSize);
- free(tempFrame);
- } else {
- std::memcpy(current_frame_, src, maxSize);
- }
-}
-
-void BaseCapturerPipeWire::ConvertRGBxToBGRx(uint8_t* frame, uint32_t size) {
- // Change color format for KDE KWin which uses RGBx and not BGRx
- for (uint32_t i = 0; i < size; i += 4) {
- uint8_t tempR = frame[i];
- uint8_t tempB = frame[i + 2];
- frame[i] = tempB;
- frame[i + 2] = tempR;
- }
-}
-
-guint BaseCapturerPipeWire::SetupRequestResponseSignal(
- const gchar* 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 BaseCapturerPipeWire::OnProxyRequested(GObject* /*object*/,
- GAsyncResult* result,
- gpointer user_data) {
- BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
- RTC_DCHECK(that);
-
- GError* error = nullptr;
- GDBusProxy *proxy = g_dbus_proxy_new_finish(result, &error);
- if (!proxy) {
- if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
- return;
- RTC_LOG(LS_ERROR) << "Failed to create a proxy for the screen cast portal: "
- << error->message;
- g_error_free(error);
- that->portal_init_failed_ = true;
- 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
-gchar* BaseCapturerPipeWire::PrepareSignalHandle(GDBusConnection* connection,
- const gchar* token) {
- gchar* sender = g_strdup(g_dbus_connection_get_unique_name(connection) + 1);
- for (int i = 0; sender[i]; i++) {
- if (sender[i] == '.') {
- sender[i] = '_';
- }
- }
-
- gchar* handle = g_strconcat(kDesktopRequestObjectPath, "/", sender, "/",
- token, /*end of varargs*/ nullptr);
- g_free(sender);
-
- return handle;
-}
-
-void BaseCapturerPipeWire::SessionRequest() {
- GVariantBuilder builder;
- gchar* 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));
- g_free(variant_string);
- 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));
-
- portal_handle_ = PrepareSignalHandle(connection_, variant_string);
- session_request_signal_id_ = SetupRequestResponseSignal(
- portal_handle_, OnSessionRequestResponseSignal);
- g_free(variant_string);
-
- 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
-void BaseCapturerPipeWire::OnSessionRequested(GDBusProxy *proxy,
- GAsyncResult* result,
- gpointer user_data) {
- BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
- RTC_DCHECK(that);
-
- GError* error = nullptr;
- GVariant* variant = g_dbus_proxy_call_finish(proxy, result, &error);
- if (!variant) {
- if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
- return;
- RTC_LOG(LS_ERROR) << "Failed to create a screen cast session: "
- << error->message;
- g_error_free(error);
- that->portal_init_failed_ = true;
- return;
- }
- RTC_LOG(LS_INFO) << "Initializing the screen cast session.";
-
- gchar* handle = nullptr;
- g_variant_get_child(variant, 0, "o", &handle);
- g_variant_unref(variant);
- 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->portal_init_failed_ = true;
- return;
- }
-
- g_free(handle);
-
- RTC_LOG(LS_INFO) << "Subscribing to the screen cast session.";
-}
-
-// static
-void BaseCapturerPipeWire::OnSessionRequestResponseSignal(
- GDBusConnection* connection,
- const gchar* sender_name,
- const gchar* object_path,
- const gchar* interface_name,
- const gchar* signal_name,
- GVariant* parameters,
- gpointer user_data) {
- BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
- RTC_DCHECK(that);
-
- RTC_LOG(LS_INFO)
- << "Received response for the screen cast session subscription.";
-
- guint32 portal_response;
- GVariant* response_data;
- g_variant_get(parameters, "(u@a{sv})", &portal_response, &response_data);
- g_variant_lookup(response_data, "session_handle", "s",
- &that->session_handle_);
- g_variant_unref(response_data);
-
- if (!that->session_handle_ || portal_response) {
- RTC_LOG(LS_ERROR)
- << "Failed to request the screen cast session subscription.";
- that->portal_init_failed_ = true;
- return;
- }
-
- that->SourcesRequest();
-}
-
-void BaseCapturerPipeWire::SourcesRequest() {
- GVariantBuilder builder;
- gchar* 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(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));
- 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));
-
- sources_handle_ = PrepareSignalHandle(connection_, variant_string);
- sources_request_signal_id_ = SetupRequestResponseSignal(
- sources_handle_, OnSourcesRequestResponseSignal);
- g_free(variant_string);
-
- RTC_LOG(LS_INFO) << "Requesting sources from the screen cast session.";
- g_dbus_proxy_call(
- proxy_, "SelectSources",
- g_variant_new("(oa{sv})", session_handle_, &builder),
- G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_,
- reinterpret_cast<GAsyncReadyCallback>(OnSourcesRequested), this);
-}
-
-// static
-void BaseCapturerPipeWire::OnSourcesRequested(GDBusProxy *proxy,
- GAsyncResult* result,
- gpointer user_data) {
- BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
- RTC_DCHECK(that);
-
- GError* error = nullptr;
- GVariant* variant = g_dbus_proxy_call_finish(proxy, result, &error);
- if (!variant) {
- if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
- return;
- RTC_LOG(LS_ERROR) << "Failed to request the sources: " << error->message;
- g_error_free(error);
- that->portal_init_failed_ = true;
- return;
- }
-
- RTC_LOG(LS_INFO) << "Sources requested from the screen cast session.";
-
- gchar* handle = nullptr;
- g_variant_get_child(variant, 0, "o", &handle);
- g_variant_unref(variant);
- 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->portal_init_failed_ = true;
- return;
- }
-
- g_free(handle);
-
- RTC_LOG(LS_INFO) << "Subscribed to sources signal.";
-}
-
-// static
-void BaseCapturerPipeWire::OnSourcesRequestResponseSignal(
- GDBusConnection* connection,
- const gchar* sender_name,
- const gchar* object_path,
- const gchar* interface_name,
- const gchar* signal_name,
- GVariant* parameters,
- gpointer user_data) {
- BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
- RTC_DCHECK(that);
-
- RTC_LOG(LS_INFO) << "Received sources signal from session.";
-
- guint32 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->portal_init_failed_ = true;
- return;
- }
-
- that->StartRequest();
-}
-
-void BaseCapturerPipeWire::StartRequest() {
- GVariantBuilder builder;
- gchar* 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));
-
- start_handle_ = PrepareSignalHandle(connection_, variant_string);
- start_request_signal_id_ =
- SetupRequestResponseSignal(start_handle_, OnStartRequestResponseSignal);
- g_free(variant_string);
-
- // "Identifier for the application window", this is Wayland, so not "x11:...".
- const gchar parent_window[] = "";
-
- RTC_LOG(LS_INFO) << "Starting the screen cast session.";
- g_dbus_proxy_call(
- proxy_, "Start",
- g_variant_new("(osa{sv})", session_handle_, parent_window, &builder),
- G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, cancellable_,
- reinterpret_cast<GAsyncReadyCallback>(OnStartRequested), this);
-}
-
-// static
-void BaseCapturerPipeWire::OnStartRequested(GDBusProxy *proxy,
- GAsyncResult* result,
- gpointer user_data) {
- BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
- RTC_DCHECK(that);
-
- GError* error = nullptr;
- GVariant* variant = g_dbus_proxy_call_finish(proxy, result, &error);
- if (!variant) {
- if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
- return;
- RTC_LOG(LS_ERROR) << "Failed to start the screen cast session: "
- << error->message;
- g_error_free(error);
- that->portal_init_failed_ = true;
- return;
- }
-
- RTC_LOG(LS_INFO) << "Initializing the start of the screen cast session.";
-
- gchar* handle = nullptr;
- g_variant_get_child(variant, 0, "o", &handle);
- g_variant_unref(variant);
- 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->portal_init_failed_ = true;
- return;
- }
-
- g_free(handle);
-
- RTC_LOG(LS_INFO) << "Subscribed to the start signal.";
-}
-
-// static
-void BaseCapturerPipeWire::OnStartRequestResponseSignal(
- GDBusConnection* connection,
- const gchar* sender_name,
- const gchar* object_path,
- const gchar* interface_name,
- const gchar* signal_name,
- GVariant* parameters,
- gpointer user_data) {
- BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
- RTC_DCHECK(that);
-
- RTC_LOG(LS_INFO) << "Start signal received.";
- guint32 portal_response;
- GVariant* response_data;
- GVariantIter* iter = nullptr;
- g_variant_get(parameters, "(u@a{sv})", &portal_response, &response_data);
- if (portal_response || !response_data) {
- RTC_LOG(LS_ERROR) << "Failed to start the screen cast session.";
- that->portal_init_failed_ = true;
- return;
- }
-
- // Array of PipeWire streams. See
- // https://github.com/flatpak/xdg-desktop-portal/blob/master/data/org.freedesktop.portal.ScreenCast.xml
- // documentation for <method name="Start">.
- if (g_variant_lookup(response_data, "streams", "a(ua{sv})", &iter)) {
- GVariant* variant;
-
- while (g_variant_iter_next(iter, "@(ua{sv})", &variant)) {
- guint32 stream_id;
- gint32 width;
- gint32 height;
- GVariant* options;
-
- g_variant_get(variant, "(u@a{sv})", &stream_id, &options);
- RTC_DCHECK(options != nullptr);
-
- g_variant_lookup(options, "size", "(ii)", &width, &height);
-
- that->desktop_size_.set(width, height);
-
- g_variant_unref(options);
- g_variant_unref(variant);
- }
- }
- g_variant_iter_free(iter);
- g_variant_unref(response_data);
-
- that->OpenPipeWireRemote();
-}
-
-void BaseCapturerPipeWire::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_, &builder),
- G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, /*fd_list=*/nullptr,
- cancellable_,
- reinterpret_cast<GAsyncReadyCallback>(OnOpenPipeWireRemoteRequested),
- this);
-}
-
-// static
-void BaseCapturerPipeWire::OnOpenPipeWireRemoteRequested(
- GDBusProxy *proxy,
- GAsyncResult* result,
- gpointer user_data) {
- BaseCapturerPipeWire* that = static_cast<BaseCapturerPipeWire*>(user_data);
- RTC_DCHECK(that);
-
- GError* error = nullptr;
- GUnixFDList* outlist = nullptr;
- GVariant* variant = g_dbus_proxy_call_with_unix_fd_list_finish(
- proxy, &outlist, result, &error);
- if (!variant) {
- if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
- return;
- RTC_LOG(LS_ERROR) << "Failed to open the PipeWire remote: "
- << error->message;
- g_error_free(error);
- that->portal_init_failed_ = true;
- return;
- }
-
- gint32 index;
- g_variant_get(variant, "(h)", &index);
-
- if ((that->pw_fd_ = g_unix_fd_list_get(outlist, index, &error)) == -1) {
- RTC_LOG(LS_ERROR) << "Failed to get file descriptor from the list: "
- << error->message;
- g_error_free(error);
- g_variant_unref(variant);
- that->portal_init_failed_ = true;
- return;
- }
-
- g_variant_unref(variant);
- g_object_unref(outlist);
-
- that->InitPipeWire();
+ return true;
}
void BaseCapturerPipeWire::Start(Callback* callback) {
RTC_DCHECK(!callback_);
RTC_DCHECK(callback);
- InitPortal();
-
callback_ = callback;
}
void BaseCapturerPipeWire::CaptureFrame() {
- if (portal_init_failed_) {
- callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
- return;
- }
-
- if (!current_frame_) {
+ if (!portal_initialized_) {
callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
return;
}
- std::unique_ptr<DesktopFrame> result(new BasicDesktopFrame(desktop_size_));
+ webrtc::XdgDesktopPortalBase* xdpBase = options_.xdp_base();
+ if (type_ != XdgDesktopPortalBase::CaptureSourceType::kAny &&
+ xdpBase->GetConnectionData(id_)->capture_source_type_ != type_ &&
+ xdpBase->GetConnectionData(id_)->capture_source_type_ !=
+ XdgDesktopPortalBase::CaptureSourceType::kAny) {
+ callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
+ return;
+ }
+
+ const webrtc::PipeWireBase* pwBase = xdpBase->GetPipeWireBase(id_);
+
+ if (!pwBase) {
+ callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
+ return;
+ }
+
+ if (!pwBase->Frame()) {
+ callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
+ return;
+ }
+
+ const DesktopSize frame_size = pwBase->FrameSize();
+
+ std::unique_ptr<DesktopFrame> result(new BasicDesktopFrame(frame_size));
result->CopyPixelsFrom(
- current_frame_, (desktop_size_.width() * kBytesPerPixel),
- DesktopRect::MakeWH(desktop_size_.width(), desktop_size_.height()));
+ pwBase->Frame(), (frame_size.width() * 4), // kBytesPerPixel = 4
+ DesktopRect::MakeWH(frame_size.width(), frame_size.height()));
+
if (!result) {
callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
return;
@@ -887,4 +130,36 @@
return true;
}
+// static
+std::unique_ptr<DesktopCapturer> BaseCapturerPipeWire::CreateRawScreenCapturer(
+ const DesktopCaptureOptions& options) {
+ if (!options.xdp_base())
+ return nullptr;
+
+ std::unique_ptr<BaseCapturerPipeWire> capturer =
+ std::make_unique<BaseCapturerPipeWire>();
+ if (!capturer->Init(options,
+ XdgDesktopPortalBase::CaptureSourceType::kScreen)) {
+ return nullptr;
+ }
+
+ return std::move(capturer);
+}
+
+// static
+std::unique_ptr<DesktopCapturer> BaseCapturerPipeWire::CreateRawWindowCapturer(
+ const DesktopCaptureOptions& options) {
+ if (!options.xdp_base())
+ return nullptr;
+
+ std::unique_ptr<BaseCapturerPipeWire> capturer =
+ std::make_unique<BaseCapturerPipeWire>();
+ if (!capturer->Init(options,
+ XdgDesktopPortalBase::CaptureSourceType::kWindow)) {
+ return nullptr;
+ }
+
+ return std::move(capturer);
+}
+
} // namespace webrtc
diff --git a/modules/desktop_capture/linux/base_capturer_pipewire.h b/modules/desktop_capture/linux/base_capturer_pipewire.h
index f28d7a5..849b283 100644
--- a/modules/desktop_capture/linux/base_capturer_pipewire.h
+++ b/modules/desktop_capture/linux/base_capturer_pipewire.h
@@ -11,156 +11,43 @@
#ifndef MODULES_DESKTOP_CAPTURE_LINUX_BASE_CAPTURER_PIPEWIRE_H_
#define MODULES_DESKTOP_CAPTURE_LINUX_BASE_CAPTURER_PIPEWIRE_H_
-#include <gio/gio.h>
-#define typeof __typeof__
-#include <pipewire/pipewire.h>
-#include <spa/param/video/format-utils.h>
-
#include "modules/desktop_capture/desktop_capture_options.h"
#include "modules/desktop_capture/desktop_capturer.h"
+#include "modules/desktop_capture/linux/xdg_desktop_portal_base.h"
+
+#include "api/ref_counted_base.h"
#include "rtc_base/constructor_magic.h"
namespace webrtc {
-class PipeWireType {
+class RTC_EXPORT BaseCapturerPipeWire : public DesktopCapturer {
public:
- spa_type_media_type media_type;
- spa_type_media_subtype media_subtype;
- spa_type_format_video format_video;
- spa_type_video_format video_format;
-};
-
-class BaseCapturerPipeWire : public DesktopCapturer {
- public:
- enum CaptureSourceType { Screen = 1, Window };
-
- explicit BaseCapturerPipeWire(CaptureSourceType source_type);
+ explicit BaseCapturerPipeWire();
~BaseCapturerPipeWire() override;
- // DesktopCapturer interface.
+ bool Init(const DesktopCaptureOptions& options,
+ XdgDesktopPortalBase::CaptureSourceType type);
+
void Start(Callback* delegate) override;
void CaptureFrame() override;
bool GetSourceList(SourceList* sources) override;
bool SelectSource(SourceId id) override;
+ static std::unique_ptr<DesktopCapturer> CreateRawScreenCapturer(
+ const DesktopCaptureOptions& options);
+
+ static std::unique_ptr<DesktopCapturer> CreateRawWindowCapturer(
+ const DesktopCaptureOptions& options);
+
private:
- // PipeWire types -->
- pw_core* pw_core_ = nullptr;
- pw_type* pw_core_type_ = nullptr;
- pw_stream* pw_stream_ = nullptr;
- pw_remote* pw_remote_ = nullptr;
- pw_loop* pw_loop_ = nullptr;
- pw_thread_loop* pw_main_loop_ = nullptr;
- PipeWireType* pw_type_ = nullptr;
-
- spa_hook spa_stream_listener_ = {};
- spa_hook spa_remote_listener_ = {};
-
- pw_stream_events pw_stream_events_ = {};
- pw_remote_events pw_remote_events_ = {};
-
- spa_video_info_raw* spa_video_format_ = nullptr;
-
- gint32 pw_fd_ = -1;
-
- CaptureSourceType capture_source_type_ =
- BaseCapturerPipeWire::CaptureSourceType::Screen;
-
- // <-- end of PipeWire types
-
- GDBusConnection* connection_ = nullptr;
- GDBusProxy* proxy_ = nullptr;
- GCancellable *cancellable_ = nullptr;
- gchar* portal_handle_ = nullptr;
- gchar* session_handle_ = nullptr;
- gchar* sources_handle_ = nullptr;
- gchar* start_handle_ = nullptr;
- guint session_request_signal_id_ = 0;
- guint sources_request_signal_id_ = 0;
- guint start_request_signal_id_ = 0;
-
- DesktopSize desktop_size_ = {};
DesktopCaptureOptions options_ = {};
-
- uint8_t* current_frame_ = nullptr;
Callback* callback_ = nullptr;
- bool portal_init_failed_ = false;
-
- void InitPortal();
- void InitPipeWire();
- void InitPipeWireTypes();
-
- void CreateReceivingStream();
- void HandleBuffer(pw_buffer* buffer);
-
- void ConvertRGBxToBGRx(uint8_t* frame, uint32_t size);
-
- static void OnStateChanged(void* data,
- pw_remote_state old_state,
- pw_remote_state state,
- const char* error);
- static void OnStreamStateChanged(void* data,
- pw_stream_state old_state,
- pw_stream_state state,
- const char* error_message);
-
- static void OnStreamFormatChanged(void* data, const struct spa_pod* format);
- static void OnStreamProcess(void* data);
- static void OnNewBuffer(void* data, uint32_t id);
-
- guint SetupRequestResponseSignal(const gchar* object_path,
- GDBusSignalCallback callback);
-
- static void OnProxyRequested(GObject* object,
- GAsyncResult* result,
- gpointer user_data);
-
- static gchar* PrepareSignalHandle(GDBusConnection* connection,
- const gchar* token);
-
- void SessionRequest();
- static void OnSessionRequested(GDBusProxy *proxy,
- GAsyncResult* result,
- gpointer user_data);
- static void OnSessionRequestResponseSignal(GDBusConnection* connection,
- const gchar* sender_name,
- const gchar* object_path,
- const gchar* interface_name,
- const gchar* signal_name,
- GVariant* parameters,
- gpointer user_data);
-
- void SourcesRequest();
- static void OnSourcesRequested(GDBusProxy *proxy,
- GAsyncResult* result,
- gpointer user_data);
- static void OnSourcesRequestResponseSignal(GDBusConnection* connection,
- const gchar* sender_name,
- const gchar* object_path,
- const gchar* interface_name,
- const gchar* signal_name,
- GVariant* parameters,
- gpointer user_data);
-
- void StartRequest();
- static void OnStartRequested(GDBusProxy *proxy,
- GAsyncResult* result,
- gpointer user_data);
- static void OnStartRequestResponseSignal(GDBusConnection* connection,
- const gchar* sender_name,
- const gchar* object_path,
- const gchar* interface_name,
- const gchar* signal_name,
- GVariant* parameters,
- gpointer user_data);
-
- void OpenPipeWireRemote();
- static void OnOpenPipeWireRemoteRequested(GDBusProxy *proxy,
- GAsyncResult* result,
- gpointer user_data);
-
- RTC_DISALLOW_COPY_AND_ASSIGN(BaseCapturerPipeWire);
+ XdgDesktopPortalBase::CaptureSourceType type_ =
+ XdgDesktopPortalBase::CaptureSourceType::kScreen;
+ absl::optional<int32_t> id_;
+ bool auto_close_connection_ = false;
+ bool portal_initialized_ = false;
};
} // namespace webrtc
diff --git a/modules/desktop_capture/linux/pipewire.sigs b/modules/desktop_capture/linux/pipewire.sigs
index 3e21e9d..e915bc9 100644
--- a/modules/desktop_capture/linux/pipewire.sigs
+++ b/modules/desktop_capture/linux/pipewire.sigs
@@ -42,3 +42,5 @@
pw_thread_loop * pw_thread_loop_new(pw_loop *loop, const char *name);
int pw_thread_loop_start(pw_thread_loop *loop);
void pw_thread_loop_stop(pw_thread_loop *loop);
+void pw_thread_loop_lock(struct pw_thread_loop *loop);
+void pw_thread_loop_unlock(struct pw_thread_loop *loop);
diff --git a/modules/desktop_capture/linux/pipewire_base.cc b/modules/desktop_capture/linux/pipewire_base.cc
new file mode 100644
index 0000000..6b789ea
--- /dev/null
+++ b/modules/desktop_capture/linux/pipewire_base.cc
@@ -0,0 +1,500 @@
+/*
+ * Copyright 2020 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/pipewire_base.h"
+
+#include <spa/param/format-utils.h>
+#include <spa/param/props.h>
+#include <spa/param/video/raw-utils.h>
+#include <spa/support/type-map.h>
+
+#include <linux/dma-buf.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+
+#include "absl/memory/memory.h"
+#include "modules/desktop_capture/desktop_capture_options.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+
+#if defined(WEBRTC_DLOPEN_PIPEWIRE)
+#include "modules/desktop_capture/linux/pipewire_stubs.h"
+
+using modules_desktop_capture_linux::InitializeStubs;
+using modules_desktop_capture_linux::kModulePipewire;
+using modules_desktop_capture_linux::StubPathMap;
+#endif // defined(WEBRTC_DLOPEN_PIPEWIRE)
+
+namespace webrtc {
+
+const int kBytesPerPixel = 4;
+
+#if defined(WEBRTC_DLOPEN_PIPEWIRE)
+const char kPipeWireLib[] = "libpipewire-0.2.so.1";
+#endif
+
+// static
+void PipeWireBase::SyncDmaBuf(int fd, uint64_t start_or_end) {
+ struct dma_buf_sync sync = {0};
+
+ sync.flags = start_or_end | DMA_BUF_SYNC_READ;
+
+ while (true) {
+ int ret;
+ ret = ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync);
+ if (ret == -1 && errno == EINTR) {
+ continue;
+ } else if (ret == -1) {
+ RTC_LOG(LS_ERROR) << "Failed to synchronize DMA buffer: "
+ << g_strerror(errno);
+ break;
+ } else {
+ break;
+ }
+ }
+}
+
+// static
+void PipeWireBase::OnStateChanged(void* data,
+ pw_remote_state old_state,
+ pw_remote_state state,
+ const char* error_message) {
+ PipeWireBase* that = static_cast<PipeWireBase*>(data);
+ RTC_DCHECK(that);
+
+ switch (state) {
+ case PW_REMOTE_STATE_ERROR:
+ RTC_LOG(LS_ERROR) << "PipeWire remote state error: " << error_message;
+ break;
+ case PW_REMOTE_STATE_CONNECTED:
+ RTC_LOG(LS_INFO) << "PipeWire remote state: connected.";
+ that->CreateReceivingStream();
+ break;
+ case PW_REMOTE_STATE_CONNECTING:
+ RTC_LOG(LS_INFO) << "PipeWire remote state: connecting.";
+ break;
+ case PW_REMOTE_STATE_UNCONNECTED:
+ RTC_LOG(LS_INFO) << "PipeWire remote state: unconnected.";
+ break;
+ }
+}
+
+// static
+void PipeWireBase::OnStreamStateChanged(void* data,
+ pw_stream_state old_state,
+ pw_stream_state state,
+ const char* error_message) {
+ PipeWireBase* that = static_cast<PipeWireBase*>(data);
+ RTC_DCHECK(that);
+
+ switch (state) {
+ case PW_STREAM_STATE_ERROR:
+ RTC_LOG(LS_ERROR) << "PipeWire stream state error: " << error_message;
+ break;
+ case PW_STREAM_STATE_CONFIGURE:
+ pw_stream_set_active(that->pw_stream_, true);
+ break;
+ case PW_STREAM_STATE_UNCONNECTED:
+ case PW_STREAM_STATE_CONNECTING:
+ case PW_STREAM_STATE_READY:
+ case PW_STREAM_STATE_PAUSED:
+ case PW_STREAM_STATE_STREAMING:
+ break;
+ }
+}
+
+// static
+void PipeWireBase::OnStreamFormatChanged(void* data,
+ const struct spa_pod* format) {
+ PipeWireBase* that = static_cast<PipeWireBase*>(data);
+ RTC_DCHECK(that);
+
+ RTC_LOG(LS_INFO) << "PipeWire stream format changed.";
+
+ if (!format) {
+ pw_stream_finish_format(that->pw_stream_, /*res=*/0, /*params=*/nullptr,
+ /*n_params=*/0);
+ return;
+ }
+
+ that->spa_video_format_ = new spa_video_info_raw();
+ spa_format_video_raw_parse(format, that->spa_video_format_,
+ &that->pw_type_->format_video);
+
+ auto width = that->spa_video_format_->size.width;
+ auto height = that->spa_video_format_->size.height;
+ auto stride = SPA_ROUND_UP_N(width * kBytesPerPixel, 4);
+ auto size = height * stride;
+
+ that->desktop_size_ = DesktopSize(width, height);
+
+ uint8_t buffer[1024] = {};
+ auto builder = spa_pod_builder{buffer, sizeof(buffer)};
+
+ // Setup buffers and meta header for new format.
+ const struct spa_pod* params[3];
+ params[0] = reinterpret_cast<spa_pod*>(spa_pod_builder_object(
+ &builder,
+ // id to enumerate buffer requirements
+ that->pw_core_type_->param.idBuffers,
+ that->pw_core_type_->param_buffers.Buffers,
+ // Size: specified as integer (i) and set to specified size
+ ":", that->pw_core_type_->param_buffers.size, "i", size,
+ // Stride: specified as integer (i) and set to specified stride
+ ":", that->pw_core_type_->param_buffers.stride, "i", stride,
+ // Buffers: specifies how many buffers we want to deal with, set as
+ // integer (i) where preferred number is 8, then allowed number is defined
+ // as range (r) from min and max values and it is undecided (u) to allow
+ // negotiation
+ ":", that->pw_core_type_->param_buffers.buffers, "iru", 8,
+ SPA_POD_PROP_MIN_MAX(1, 32),
+ // Align: memory alignment of the buffer, set as integer (i) to specified
+ // value
+ ":", that->pw_core_type_->param_buffers.align, "i", 16));
+ params[1] = reinterpret_cast<spa_pod*>(spa_pod_builder_object(
+ &builder,
+ // id to enumerate supported metadata
+ that->pw_core_type_->param.idMeta, that->pw_core_type_->param_meta.Meta,
+ // Type: specified as id or enum (I)
+ ":", that->pw_core_type_->param_meta.type, "I",
+ that->pw_core_type_->meta.Header,
+ // Size: size of the metadata, specified as integer (i)
+ ":", that->pw_core_type_->param_meta.size, "i",
+ sizeof(struct spa_meta_header)));
+ params[2] = reinterpret_cast<spa_pod*>(
+ spa_pod_builder_object(&builder, that->pw_core_type_->param.idMeta,
+ that->pw_core_type_->param_meta.Meta, ":",
+ that->pw_core_type_->param_meta.type, "I",
+ that->pw_core_type_->meta.VideoCrop, ":",
+ that->pw_core_type_->param_meta.size, "i",
+ sizeof(struct spa_meta_video_crop)));
+ pw_stream_finish_format(that->pw_stream_, /*res=*/0, params, /*n_params=*/3);
+}
+
+// static
+void PipeWireBase::OnStreamProcess(void* data) {
+ PipeWireBase* that = static_cast<PipeWireBase*>(data);
+ RTC_DCHECK(that);
+
+ struct pw_buffer* next_buffer;
+ struct pw_buffer* buffer = nullptr;
+
+ next_buffer = pw_stream_dequeue_buffer(that->pw_stream_);
+ while (next_buffer) {
+ buffer = next_buffer;
+ next_buffer = pw_stream_dequeue_buffer(that->pw_stream_);
+
+ if (next_buffer) {
+ pw_stream_queue_buffer(that->pw_stream_, buffer);
+ }
+ }
+
+ if (!buffer) {
+ return;
+ }
+
+ that->HandleBuffer(buffer);
+
+ pw_stream_queue_buffer(that->pw_stream_, buffer);
+}
+
+PipeWireBase::PipeWireBase(int32_t fd) {
+#if defined(WEBRTC_DLOPEN_PIPEWIRE)
+ StubPathMap paths;
+
+ // Check if the PipeWire library is available.
+ paths[kModulePipewire].push_back(kPipeWireLib);
+ if (!InitializeStubs(paths)) {
+ RTC_LOG(LS_ERROR) << "Failed to load the PipeWire library and symbols.";
+ pipewire_init_failed_ = true;
+ return;
+ }
+#endif // defined(WEBRTC_DLOPEN_PIPEWIRE)
+
+ pw_init(/*argc=*/nullptr, /*argc=*/nullptr);
+
+ pw_loop_ = pw_loop_new(/*properties=*/nullptr);
+ pw_main_loop_ = pw_thread_loop_new(pw_loop_, "pipewire-main-loop");
+
+ pw_thread_loop_lock(pw_main_loop_);
+
+ pw_core_ = pw_core_new(pw_loop_, /*properties=*/nullptr);
+ pw_core_type_ = pw_core_get_type(pw_core_);
+ pw_remote_ = pw_remote_new(pw_core_, nullptr, /*user_data_size=*/0);
+
+ InitPipeWireTypes();
+
+ // Initialize event handlers, remote end and stream-related.
+ pw_remote_events_.version = PW_VERSION_REMOTE_EVENTS;
+ pw_remote_events_.state_changed = &OnStateChanged;
+
+ pw_stream_events_.version = PW_VERSION_STREAM_EVENTS;
+ pw_stream_events_.state_changed = &OnStreamStateChanged;
+ pw_stream_events_.format_changed = &OnStreamFormatChanged;
+ pw_stream_events_.process = &OnStreamProcess;
+
+ pw_remote_add_listener(pw_remote_, &spa_remote_listener_, &pw_remote_events_,
+ this);
+ pw_remote_connect_fd(pw_remote_, fd);
+
+ if (pw_thread_loop_start(pw_main_loop_) < 0) {
+ RTC_LOG(LS_ERROR) << "Failed to start main PipeWire loop";
+ pipewire_init_failed_ = true;
+ }
+
+ pw_thread_loop_unlock(pw_main_loop_);
+
+ RTC_LOG(LS_INFO) << "PipeWire remote opened.";
+}
+
+PipeWireBase::~PipeWireBase() {
+ if (pw_main_loop_) {
+ pw_thread_loop_stop(pw_main_loop_);
+ }
+
+ if (pw_type_) {
+ delete pw_type_;
+ }
+
+ if (spa_video_format_) {
+ delete spa_video_format_;
+ }
+
+ if (pw_stream_) {
+ pw_stream_destroy(pw_stream_);
+ }
+
+ if (pw_remote_) {
+ pw_remote_destroy(pw_remote_);
+ }
+
+ if (pw_core_) {
+ pw_core_destroy(pw_core_);
+ }
+
+ if (pw_main_loop_) {
+ pw_thread_loop_destroy(pw_main_loop_);
+ }
+
+ if (pw_loop_) {
+ pw_loop_destroy(pw_loop_);
+ }
+}
+
+uint8_t* PipeWireBase::Frame() const {
+ if (!current_frame_) {
+ return nullptr;
+ }
+
+ return current_frame_.get();
+}
+
+DesktopSize PipeWireBase::FrameSize() const {
+ return video_crop_size_.value_or(desktop_size_);
+}
+
+void PipeWireBase::InitPipeWireTypes() {
+ spa_type_map* map = pw_core_type_->map;
+ pw_type_ = new PipeWireType();
+
+ spa_type_media_type_map(map, &pw_type_->media_type);
+ spa_type_media_subtype_map(map, &pw_type_->media_subtype);
+ spa_type_format_video_map(map, &pw_type_->format_video);
+ spa_type_video_format_map(map, &pw_type_->video_format);
+}
+
+void PipeWireBase::CreateReceivingStream() {
+ spa_rectangle pwMinScreenBounds = spa_rectangle{1, 1};
+ spa_rectangle pwMaxScreenBounds = spa_rectangle{INT32_MAX, INT32_MAX};
+
+ pw_properties* reuseProps =
+ pw_properties_new_string("pipewire.client.reuse=1");
+ pw_stream_ = pw_stream_new(pw_remote_, "webrtc-consume-stream", reuseProps);
+
+ uint8_t buffer[1024] = {};
+ const spa_pod* params[1];
+ spa_pod_builder builder = spa_pod_builder{buffer, sizeof(buffer)};
+ params[0] = reinterpret_cast<spa_pod*>(spa_pod_builder_object(
+ &builder,
+ // id to enumerate formats
+ pw_core_type_->param.idEnumFormat, pw_core_type_->spa_format, "I",
+ pw_type_->media_type.video, "I", pw_type_->media_subtype.raw,
+ // Video format: specified as id or enum (I), preferred format is BGRx,
+ // then allowed formats are enumerated (e) and the format is undecided (u)
+ // to allow negotiation
+ ":", pw_type_->format_video.format, "Ieu", pw_type_->video_format.BGRx,
+ SPA_POD_PROP_ENUM(
+ 4, pw_type_->video_format.RGBx, pw_type_->video_format.BGRx,
+ pw_type_->video_format.RGBA, pw_type_->video_format.BGRA),
+ // Video size: specified as rectangle (R), preferred size is specified as
+ // first parameter, then allowed size is defined as range (r) from min and
+ // max values and the format is undecided (u) to allow negotiation
+ ":", pw_type_->format_video.size, "Rru", &pwMinScreenBounds,
+ SPA_POD_PROP_MIN_MAX(&pwMinScreenBounds, &pwMaxScreenBounds)));
+
+ pw_stream_add_listener(pw_stream_, &spa_stream_listener_, &pw_stream_events_,
+ this);
+ pw_stream_flags flags = static_cast<pw_stream_flags>(
+ PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE);
+ if (pw_stream_connect(pw_stream_, PW_DIRECTION_INPUT, /*port_path=*/nullptr,
+ flags, params,
+ /*n_params=*/1) != 0) {
+ RTC_LOG(LS_ERROR) << "Could not connect receiving stream.";
+ pipewire_init_failed_ = true;
+ return;
+ }
+}
+
+void PipeWireBase::HandleBuffer(pw_buffer* buffer) {
+ struct spa_meta_video_crop* video_crop;
+ spa_buffer* spaBuffer = buffer->buffer;
+ uint8_t* map = nullptr;
+ uint8_t* src = nullptr;
+ uint8_t* dst = nullptr;
+
+ if (spaBuffer->datas[0].chunk->size == 0) {
+ map = nullptr;
+ src = nullptr;
+ } else if (spaBuffer->datas[0].type == pw_core_type_->data.MemFd) {
+ map = static_cast<uint8_t*>(mmap(
+ nullptr, spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset,
+ PROT_READ, MAP_PRIVATE, spaBuffer->datas[0].fd, 0));
+
+ if (map == MAP_FAILED) {
+ RTC_LOG(LS_ERROR) << "Failed to mmap the memory: "
+ << std::strerror(errno);
+ return;
+ }
+
+ src = SPA_MEMBER(map, spaBuffer->datas[0].mapoffset, uint8_t);
+ } else if (spaBuffer->datas[0].type == pw_core_type_->data.DmaBuf) {
+ int fd;
+ fd = spaBuffer->datas[0].fd;
+
+ map = static_cast<uint8_t*>(mmap(
+ nullptr, spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset,
+ PROT_READ, MAP_PRIVATE, fd, 0));
+
+ if (map == MAP_FAILED) {
+ RTC_LOG(LS_ERROR) << "Failed to mmap the memory: "
+ << std::strerror(errno);
+ return;
+ }
+
+ SyncDmaBuf(fd, DMA_BUF_SYNC_START);
+
+ src = SPA_MEMBER(map, spaBuffer->datas[0].mapoffset, uint8_t);
+ } else if (spaBuffer->datas[0].type == pw_core_type_->data.MemPtr) {
+ map = nullptr;
+ src = static_cast<uint8_t*>(spaBuffer->datas[0].data);
+ } else {
+ return;
+ }
+
+ if (!src) {
+ return;
+ }
+
+ DesktopSize prev_crop_size = video_crop_size_.value_or(DesktopSize(0, 0));
+
+ if ((video_crop = static_cast<struct spa_meta_video_crop*>(
+ spa_buffer_find_meta(spaBuffer, pw_core_type_->meta.VideoCrop)))) {
+ RTC_DCHECK(video_crop->width <= desktop_size_.width() &&
+ video_crop->height <= desktop_size_.height());
+ if ((video_crop->width != desktop_size_.width() ||
+ video_crop->height != desktop_size_.height()) &&
+ video_crop->width && video_crop->height) {
+ video_crop_size_ = DesktopSize(video_crop->width, video_crop->height);
+ } else {
+ video_crop_size_.reset();
+ }
+ } else {
+ video_crop_size_.reset();
+ }
+
+ size_t frame_size;
+ if (video_crop_size_) {
+ frame_size =
+ video_crop_size_->width() * video_crop_size_->height() * kBytesPerPixel;
+ } else {
+ frame_size =
+ desktop_size_.width() * desktop_size_.height() * kBytesPerPixel;
+ }
+
+ if (!current_frame_ ||
+ (video_crop_size_ && !video_crop_size_->equals(prev_crop_size)) ||
+ (!video_crop_size_ && !prev_crop_size.is_empty())) {
+ current_frame_ = std::make_unique<uint8_t[]>(frame_size);
+ }
+ RTC_DCHECK(current_frame_ != nullptr);
+
+ const int32_t dstStride = video_crop_size_
+ ? video_crop_size_->width() * kBytesPerPixel
+ : desktop_size_.width() * kBytesPerPixel;
+ const int32_t srcStride = spaBuffer->datas[0].chunk->stride;
+
+ if (srcStride != (desktop_size_.width() * kBytesPerPixel)) {
+ RTC_LOG(LS_ERROR) << "Got buffer with stride different from screen stride: "
+ << srcStride
+ << " != " << (desktop_size_.width() * kBytesPerPixel);
+ pipewire_init_failed_ = true;
+ return;
+ }
+
+ dst = current_frame_.get();
+
+ // Adjust source content based on crop video position
+ if (video_crop_size_ &&
+ (video_crop->y + video_crop_size_->height() <= desktop_size_.height())) {
+ for (int i = 0; i < video_crop->y; ++i) {
+ src += srcStride;
+ }
+ }
+
+ const int xOffset =
+ video_crop_size_ && (video_crop->x + video_crop_size_->width() <=
+ desktop_size_.width())
+ ? video_crop->x * kBytesPerPixel
+ : 0;
+ const int height = video_crop_size_.value_or(desktop_size_).height();
+ for (int i = 0; i < height; ++i) {
+ // Adjust source content based on crop video position if needed
+ src += xOffset;
+ std::memcpy(dst, src, dstStride);
+ // If both sides decided to go with the RGBx format we need to convert it to
+ // BGRx to match color format expected by WebRTC.
+ if (spa_video_format_->format == pw_type_->video_format.RGBx ||
+ spa_video_format_->format == pw_type_->video_format.RGBA) {
+ ConvertRGBToBGR(dst, dstStride);
+ }
+ src += srcStride - xOffset;
+ dst += dstStride;
+ }
+
+ if (map) {
+ if (spaBuffer->datas[0].type == pw_core_type_->data.DmaBuf) {
+ SyncDmaBuf(spaBuffer->datas[0].fd, DMA_BUF_SYNC_END);
+ }
+ munmap(map, spaBuffer->datas[0].maxsize + spaBuffer->datas[0].mapoffset);
+ }
+}
+
+void PipeWireBase::ConvertRGBToBGR(uint8_t* frame, uint32_t size) {
+ // Change color format for KDE KWin which uses RGBx and not BGRx
+ for (uint32_t i = 0; i < size; i += 4) {
+ uint8_t tempR = frame[i];
+ uint8_t tempB = frame[i + 2];
+ frame[i] = tempB;
+ frame[i + 2] = tempR;
+ }
+}
+
+} // namespace webrtc
diff --git a/modules/desktop_capture/linux/pipewire_base.h b/modules/desktop_capture/linux/pipewire_base.h
new file mode 100644
index 0000000..5900b58
--- /dev/null
+++ b/modules/desktop_capture/linux/pipewire_base.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2020 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_PIPEWIRE_BASE_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_PIPEWIRE_BASE_H_
+
+#define typeof __typeof__
+#include <pipewire/pipewire.h>
+#include <spa/param/video/format-utils.h>
+
+#include <memory>
+
+#include "modules/desktop_capture/desktop_geometry.h"
+
+#include "absl/types/optional.h"
+#include "api/ref_counted_base.h"
+#include "rtc_base/constructor_magic.h"
+
+namespace webrtc {
+
+class PipeWireType {
+ public:
+ spa_type_media_type media_type;
+ spa_type_media_subtype media_subtype;
+ spa_type_format_video format_video;
+ spa_type_video_format video_format;
+};
+
+class PipeWireBase : public rtc::RefCountedBase {
+ public:
+ explicit PipeWireBase(int32_t fd);
+ ~PipeWireBase();
+
+ uint8_t* Frame() const;
+ DesktopSize FrameSize() const;
+
+ private:
+ pw_core* pw_core_ = nullptr;
+ pw_type* pw_core_type_ = nullptr;
+ pw_stream* pw_stream_ = nullptr;
+ pw_remote* pw_remote_ = nullptr;
+ pw_loop* pw_loop_ = nullptr;
+ pw_thread_loop* pw_main_loop_ = nullptr;
+ PipeWireType* pw_type_ = nullptr;
+
+ spa_hook spa_stream_listener_ = {};
+ spa_hook spa_remote_listener_ = {};
+
+ pw_stream_events pw_stream_events_ = {};
+ pw_remote_events pw_remote_events_ = {};
+
+ spa_video_info_raw* spa_video_format_ = nullptr;
+
+ int32_t pw_fd_ = -1;
+
+ absl::optional<DesktopSize> video_crop_size_;
+ DesktopSize desktop_size_ = {};
+
+ bool pipewire_init_failed_ = false;
+
+ std::unique_ptr<uint8_t[]> current_frame_;
+
+ void InitPipeWireTypes();
+
+ void CreateReceivingStream();
+ void HandleBuffer(pw_buffer* buffer);
+
+ void ConvertRGBToBGR(uint8_t* frame, uint32_t size);
+
+ static void SyncDmaBuf(int fd, uint64_t start_or_end);
+
+ static void OnStateChanged(void* data,
+ pw_remote_state old_state,
+ pw_remote_state state,
+ const char* error);
+ static void OnStreamStateChanged(void* data,
+ pw_stream_state old_state,
+ pw_stream_state state,
+ const char* error_message);
+
+ static void OnStreamFormatChanged(void* data, const struct spa_pod* format);
+ static void OnStreamProcess(void* data);
+ static void OnNewBuffer(void* data, uint32_t id);
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_PIPEWIRE_BASE_H_
diff --git a/modules/desktop_capture/linux/screen_capturer_pipewire.cc b/modules/desktop_capture/linux/screen_capturer_pipewire.cc
deleted file mode 100644
index fe67214..0000000
--- a/modules/desktop_capture/linux/screen_capturer_pipewire.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2018 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/screen_capturer_pipewire.h"
-
-#include <memory>
-
-
-namespace webrtc {
-
-ScreenCapturerPipeWire::ScreenCapturerPipeWire()
- : BaseCapturerPipeWire(BaseCapturerPipeWire::CaptureSourceType::Screen) {}
-ScreenCapturerPipeWire::~ScreenCapturerPipeWire() {}
-
-// static
-std::unique_ptr<DesktopCapturer>
-ScreenCapturerPipeWire::CreateRawScreenCapturer(
- const DesktopCaptureOptions& options) {
- return std::make_unique<ScreenCapturerPipeWire>();
-}
-
-} // namespace webrtc
diff --git a/modules/desktop_capture/linux/screen_capturer_pipewire.h b/modules/desktop_capture/linux/screen_capturer_pipewire.h
deleted file mode 100644
index 66dcd68..0000000
--- a/modules/desktop_capture/linux/screen_capturer_pipewire.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2018 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_SCREEN_CAPTURER_PIPEWIRE_H_
-#define MODULES_DESKTOP_CAPTURE_LINUX_SCREEN_CAPTURER_PIPEWIRE_H_
-
-#include <memory>
-
-#include "modules/desktop_capture/linux/base_capturer_pipewire.h"
-
-namespace webrtc {
-
-class ScreenCapturerPipeWire : public BaseCapturerPipeWire {
- public:
- ScreenCapturerPipeWire();
- ~ScreenCapturerPipeWire() override;
-
- static std::unique_ptr<DesktopCapturer> CreateRawScreenCapturer(
- const DesktopCaptureOptions& options);
-
- RTC_DISALLOW_COPY_AND_ASSIGN(ScreenCapturerPipeWire);
-};
-
-} // namespace webrtc
-
-#endif // MODULES_DESKTOP_CAPTURE_LINUX_SCREEN_CAPTURER_PIPEWIRE_H_
diff --git a/modules/desktop_capture/linux/window_capturer_pipewire.cc b/modules/desktop_capture/linux/window_capturer_pipewire.cc
deleted file mode 100644
index b455915..0000000
--- a/modules/desktop_capture/linux/window_capturer_pipewire.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2018 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/window_capturer_pipewire.h"
-
-#include <memory>
-
-
-namespace webrtc {
-
-WindowCapturerPipeWire::WindowCapturerPipeWire()
- : BaseCapturerPipeWire(BaseCapturerPipeWire::CaptureSourceType::Window) {}
-WindowCapturerPipeWire::~WindowCapturerPipeWire() {}
-
-// static
-std::unique_ptr<DesktopCapturer>
-WindowCapturerPipeWire::CreateRawWindowCapturer(
- const DesktopCaptureOptions& options) {
- return std::make_unique<WindowCapturerPipeWire>();
-}
-
-} // namespace webrtc
diff --git a/modules/desktop_capture/linux/window_capturer_pipewire.h b/modules/desktop_capture/linux/window_capturer_pipewire.h
deleted file mode 100644
index 7f184ef..0000000
--- a/modules/desktop_capture/linux/window_capturer_pipewire.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2018 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_WINDOW_CAPTURER_PIPEWIRE_H_
-#define MODULES_DESKTOP_CAPTURE_LINUX_WINDOW_CAPTURER_PIPEWIRE_H_
-
-#include <memory>
-
-#include "modules/desktop_capture/linux/base_capturer_pipewire.h"
-
-namespace webrtc {
-
-class WindowCapturerPipeWire : public BaseCapturerPipeWire {
- public:
- WindowCapturerPipeWire();
- ~WindowCapturerPipeWire() override;
-
- static std::unique_ptr<DesktopCapturer> CreateRawWindowCapturer(
- const DesktopCaptureOptions& options);
-
- RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerPipeWire);
-};
-
-} // namespace webrtc
-
-#endif // MODULES_DESKTOP_CAPTURE_LINUX_WINDOW_CAPTURER_PIPEWIRE_H_
diff --git a/modules/desktop_capture/linux/xdg_desktop_portal_base.cc b/modules/desktop_capture/linux/xdg_desktop_portal_base.cc
new file mode 100644
index 0000000..0c7e6930
--- /dev/null
+++ b/modules/desktop_capture/linux/xdg_desktop_portal_base.cc
@@ -0,0 +1,813 @@
+/*
+ * Copyright 2020 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/xdg_desktop_portal_base.h"
+
+#include <gio/gunixfdlist.h>
+
+#include <utility>
+
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+
+namespace webrtc {
+
+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";
+
+template <class T>
+class Scoped {
+ public:
+ Scoped() {}
+ explicit Scoped(T* val) { ptr_ = val; }
+ ~Scoped() { RTC_NOTREACHED(); }
+
+ T* operator->() { return ptr_; }
+
+ bool operator!() { return ptr_ == nullptr; }
+
+ T* get() { return ptr_; }
+
+ T** receive() {
+ RTC_CHECK(!ptr_);
+ return &ptr_;
+ }
+
+ Scoped& operator=(T* val) {
+ ptr_ = val;
+ return *this;
+ }
+
+ protected:
+ T* ptr_ = nullptr;
+};
+
+template <>
+Scoped<GError>::~Scoped() {
+ if (ptr_) {
+ g_error_free(ptr_);
+ }
+}
+
+template <>
+Scoped<gchar>::~Scoped() {
+ if (ptr_) {
+ g_free(ptr_);
+ }
+}
+
+template <>
+Scoped<GVariant>::~Scoped() {
+ if (ptr_) {
+ g_variant_unref(ptr_);
+ }
+}
+
+template <>
+Scoped<GVariantIter>::~Scoped() {
+ if (ptr_) {
+ g_variant_iter_free(ptr_);
+ }
+}
+
+template <>
+Scoped<GDBusMessage>::~Scoped() {
+ if (ptr_) {
+ g_object_unref(ptr_);
+ }
+}
+
+template <>
+Scoped<GUnixFDList>::~Scoped() {
+ if (ptr_) {
+ g_object_unref(ptr_);
+ }
+}
+
+ConnectionData::ConnectionData(int32_t id) : id_(id) {}
+
+ConnectionData::~ConnectionData() {
+ 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;
+ }
+
+ if (session_handle_) {
+ Scoped<GDBusMessage> message(g_dbus_message_new_method_call(
+ kDesktopBusName, session_handle_, 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;
+ }
+ }
+ }
+
+ g_free(start_handle_);
+ start_handle_ = nullptr;
+ g_free(sources_handle_);
+ sources_handle_ = nullptr;
+ g_free(session_handle_);
+ session_handle_ = nullptr;
+ g_free(portal_handle_);
+ portal_handle_ = nullptr;
+
+ if (proxy_) {
+ g_clear_object(&proxy_);
+ proxy_ = nullptr;
+ }
+
+ g_object_unref(connection_);
+ connection_ = nullptr;
+
+ // Restore to initial values
+ id_ = 0;
+ stream_id_ = 0;
+ pw_fd_ = -1;
+ portal_init_failed_ = false;
+}
+
+XdgDesktopPortalBase::XdgDesktopPortalBase() {}
+
+XdgDesktopPortalBase::~XdgDesktopPortalBase() {
+ connection_data_map_.clear();
+}
+
+// static
+rtc::scoped_refptr<XdgDesktopPortalBase> XdgDesktopPortalBase::CreateDefault() {
+ return new XdgDesktopPortalBase();
+}
+
+void XdgDesktopPortalBase::InitPortal(
+ rtc::Callback1<void, bool> callback,
+ XdgDesktopPortalBase::CaptureSourceType requested_type,
+ int32_t id) {
+ if (!id) {
+ callback(false);
+ return;
+ }
+
+ auto data = GetConnectionData(id);
+
+ if (data) {
+ data->callbacks_.push_back(callback);
+ return;
+ }
+
+ std::unique_ptr<ConnectionData> connection_data(new ConnectionData(id));
+ connection_data->callbacks_.push_back(callback);
+ connection_data->requested_source_type_ = requested_type;
+
+ connection_data_map_.insert({id, std::move(connection_data)});
+
+ g_dbus_proxy_new_for_bus(
+ G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, /*info=*/nullptr,
+ kDesktopBusName, kDesktopObjectPath, kScreenCastInterfaceName,
+ /*cancellable=*/nullptr,
+ reinterpret_cast<GAsyncReadyCallback>(OnProxyRequested),
+ new UserData(id, this));
+}
+
+bool XdgDesktopPortalBase::IsConnectionInitialized(
+ const absl::optional<int32_t>& id) const {
+ auto connection_data = GetConnectionData(id);
+
+ if (!connection_data) {
+ return false;
+ }
+
+ if (connection_data->portal_init_failed_) {
+ return false;
+ }
+
+ if (connection_data->pw_fd_ == -1) {
+ return false;
+ }
+
+ return true;
+}
+
+bool XdgDesktopPortalBase::IsConnectionStreamingOnWeb(
+ const absl::optional<int32_t>& id) const {
+ auto connection_data = GetConnectionData(id);
+
+ if (!connection_data) {
+ return false;
+ }
+
+ return connection_data->web_streaming_;
+}
+
+PipeWireBase* XdgDesktopPortalBase::GetPipeWireBase(
+ const absl::optional<int32_t>& id) const {
+ int32_t valid_id = id.value_or(current_connection_id_.value_or(0));
+
+ auto connection_data = GetConnectionData(valid_id);
+ RTC_CHECK(connection_data);
+
+ if (!connection_data->pw_base_) {
+ return nullptr;
+ }
+
+ auto pwBase = connection_data->pw_base_.get();
+
+ // Assume this call/connection has been already used when someone asks for
+ // PipeWireBase which we use to guess we already stream to the web page itself
+ // and not to the preview dialog
+ if (!connection_data->already_used_ && pwBase && pwBase->Frame()) {
+ connection_data->already_used_ = true;
+ }
+
+ return pwBase;
+}
+
+ConnectionData* XdgDesktopPortalBase::GetConnectionData(
+ const absl::optional<int32_t>& id) const {
+ int32_t valid_id = id.value_or(current_connection_id_.value_or(0));
+
+ auto search = connection_data_map_.find(valid_id);
+ if (search != connection_data_map_.end()) {
+ return search->second.get();
+ }
+
+ return nullptr;
+}
+
+absl::optional<int32_t> XdgDesktopPortalBase::CurrentConnectionId() const {
+ return current_connection_id_;
+}
+
+void XdgDesktopPortalBase::SetCurrentConnectionId(
+ const absl::optional<int32_t>& id) {
+ current_connection_id_ = id;
+}
+
+guint XdgDesktopPortalBase::SetupRequestResponseSignal(
+ const gchar* object_path,
+ GDBusSignalCallback callback,
+ UserData* data) {
+ auto connection_data = GetConnectionData(data->GetDataId());
+ RTC_CHECK(connection_data);
+
+ return g_dbus_connection_signal_subscribe(
+ connection_data->connection_, kDesktopBusName, kRequestInterfaceName,
+ "Response", object_path, /*arg0=*/nullptr,
+ G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE, callback, data,
+ /*user_data_free_func=*/nullptr);
+}
+
+// static
+void XdgDesktopPortalBase::OnProxyRequested(GObject* /*object*/,
+ GAsyncResult* result,
+ gpointer user_data) {
+ UserData* data = static_cast<UserData*>(user_data);
+ RTC_DCHECK(data);
+
+ auto* portal_base = data->GetXdgDesktopPortalBase();
+ RTC_CHECK(portal_base);
+
+ auto connection_data = portal_base->GetConnectionData(data->GetDataId());
+ RTC_CHECK(connection_data);
+
+ Scoped<GError> error;
+ connection_data->proxy_ = g_dbus_proxy_new_finish(result, error.receive());
+ if (!connection_data->proxy_) {
+ RTC_LOG(LS_ERROR) << "Failed to create a proxy for the screen cast portal: "
+ << error->message;
+ connection_data->portal_init_failed_ = true;
+ for (rtc::Callback1<void, bool>& callback : connection_data->callbacks_) {
+ callback(false);
+ }
+ portal_base->CloseConnection(connection_data->id_);
+ return;
+ }
+ connection_data->connection_ =
+ g_dbus_proxy_get_connection(connection_data->proxy_);
+
+ RTC_LOG(LS_INFO) << "Created proxy for the screen cast portal.";
+ portal_base->SessionRequest(data);
+}
+
+// static
+gchar* XdgDesktopPortalBase::PrepareSignalHandle(GDBusConnection* connection,
+ const gchar* token) {
+ Scoped<gchar> 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] = '_';
+ }
+ }
+
+ gchar* handle = g_strconcat(kDesktopRequestObjectPath, "/", sender.get(), "/",
+ token, /*end of varargs*/ nullptr);
+
+ return handle;
+}
+
+void XdgDesktopPortalBase::SessionRequest(UserData* data) {
+ auto connection_data = GetConnectionData(data->GetDataId());
+ RTC_CHECK(connection_data);
+
+ GVariantBuilder builder;
+ Scoped<gchar> 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()));
+
+ connection_data->portal_handle_ =
+ PrepareSignalHandle(connection_data->connection_, variant_string.get());
+ connection_data->session_request_signal_id_ = SetupRequestResponseSignal(
+ connection_data->portal_handle_, OnSessionRequestResponseSignal, data);
+
+ RTC_LOG(LS_INFO) << "Screen cast session requested.";
+ g_dbus_proxy_call(connection_data->proxy_, "CreateSession",
+ g_variant_new("(a{sv})", &builder), G_DBUS_CALL_FLAGS_NONE,
+ /*timeout=*/-1, /*cancellable=*/nullptr,
+ reinterpret_cast<GAsyncReadyCallback>(OnSessionRequested),
+ data);
+}
+
+// static
+void XdgDesktopPortalBase::OnSessionRequested(GDBusConnection* connection,
+ GAsyncResult* result,
+ gpointer user_data) {
+ UserData* data = static_cast<UserData*>(user_data);
+ RTC_DCHECK(data);
+
+ auto* portal_base = data->GetXdgDesktopPortalBase();
+ RTC_CHECK(portal_base);
+
+ auto connection_data = portal_base->GetConnectionData(data->GetDataId());
+ RTC_CHECK(connection_data);
+
+ Scoped<GError> error;
+ Scoped<GVariant> variant(g_dbus_proxy_call_finish(connection_data->proxy_,
+ result, error.receive()));
+ if (!variant) {
+ RTC_LOG(LS_ERROR) << "Failed to create a screen cast session: "
+ << error->message;
+ connection_data->portal_init_failed_ = true;
+ for (rtc::Callback1<void, bool>& callback : connection_data->callbacks_) {
+ callback(false);
+ }
+ portal_base->CloseConnection(connection_data->id_);
+ return;
+ }
+ RTC_LOG(LS_INFO) << "Initializing the screen cast session.";
+
+ Scoped<gchar> 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 (connection_data->session_request_signal_id_) {
+ g_dbus_connection_signal_unsubscribe(
+ connection, connection_data->session_request_signal_id_);
+ connection_data->session_request_signal_id_ = 0;
+ }
+ connection_data->portal_init_failed_ = true;
+ for (rtc::Callback1<void, bool>& callback : connection_data->callbacks_) {
+ callback(false);
+ }
+ portal_base->CloseConnection(connection_data->id_);
+ return;
+ }
+
+ RTC_LOG(LS_INFO) << "Subscribing to the screen cast session.";
+}
+
+// static
+void XdgDesktopPortalBase::OnSessionRequestResponseSignal(
+ GDBusConnection* connection,
+ const gchar* sender_name,
+ const gchar* object_path,
+ const gchar* interface_name,
+ const gchar* signal_name,
+ GVariant* parameters,
+ gpointer user_data) {
+ UserData* data = static_cast<UserData*>(user_data);
+ RTC_DCHECK(data);
+
+ auto* portal_base = data->GetXdgDesktopPortalBase();
+ RTC_CHECK(portal_base);
+
+ auto connection_data = portal_base->GetConnectionData(data->GetDataId());
+ RTC_CHECK(connection_data);
+
+ RTC_LOG(LS_INFO)
+ << "Received response for the screen cast session subscription.";
+
+ guint32 portal_response;
+ Scoped<GVariant> response_data;
+ g_variant_get(parameters, "(u@a{sv})", &portal_response,
+ response_data.receive());
+ g_variant_lookup(response_data.get(), "session_handle", "s",
+ &connection_data->session_handle_);
+
+ if (!connection_data->session_handle_ || portal_response) {
+ RTC_LOG(LS_ERROR)
+ << "Failed to request the screen cast session subscription.";
+ connection_data->portal_init_failed_ = true;
+ for (rtc::Callback1<void, bool>& callback : connection_data->callbacks_) {
+ callback(false);
+ }
+ return;
+ }
+
+ portal_base->SourcesRequest(data);
+}
+
+void XdgDesktopPortalBase::SourcesRequest(UserData* data) {
+ auto connection_data = GetConnectionData(data->GetDataId());
+ RTC_CHECK(connection_data);
+
+ GVariantBuilder builder;
+ Scoped<gchar> 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>(
+ connection_data->requested_source_type_)));
+ // We don't want to allow selection of multiple sources.
+ g_variant_builder_add(&builder, "{sv}", "multiple",
+ g_variant_new_boolean(false));
+ 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()));
+
+ connection_data->sources_handle_ =
+ PrepareSignalHandle(connection_data->connection_, variant_string.get());
+ connection_data->sources_request_signal_id_ = SetupRequestResponseSignal(
+ connection_data->sources_handle_, OnSourcesRequestResponseSignal, data);
+
+ RTC_LOG(LS_INFO) << "Requesting sources from the screen cast session.";
+ g_dbus_proxy_call(
+ connection_data->proxy_, "SelectSources",
+ g_variant_new("(oa{sv})", connection_data->session_handle_, &builder),
+ G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, /*cancellable=*/nullptr,
+ reinterpret_cast<GAsyncReadyCallback>(OnSourcesRequested), data);
+}
+
+// static
+void XdgDesktopPortalBase::OnSourcesRequested(GDBusConnection* connection,
+ GAsyncResult* result,
+ gpointer user_data) {
+ UserData* data = static_cast<UserData*>(user_data);
+ RTC_DCHECK(data);
+
+ auto* portal_base = data->GetXdgDesktopPortalBase();
+ RTC_CHECK(portal_base);
+
+ auto connection_data = portal_base->GetConnectionData(data->GetDataId());
+ RTC_CHECK(connection_data);
+
+ Scoped<GError> error;
+ Scoped<GVariant> variant(g_dbus_proxy_call_finish(connection_data->proxy_,
+ result, error.receive()));
+ if (!variant) {
+ RTC_LOG(LS_ERROR) << "Failed to request the sources: " << error->message;
+ connection_data->portal_init_failed_ = true;
+ for (rtc::Callback1<void, bool>& callback : connection_data->callbacks_) {
+ callback(false);
+ }
+ portal_base->CloseConnection(connection_data->id_);
+ return;
+ }
+
+ RTC_LOG(LS_INFO) << "Sources requested from the screen cast session.";
+
+ Scoped<gchar> 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 (connection_data->sources_request_signal_id_) {
+ g_dbus_connection_signal_unsubscribe(
+ connection, connection_data->sources_request_signal_id_);
+ connection_data->sources_request_signal_id_ = 0;
+ }
+ connection_data->portal_init_failed_ = true;
+ for (rtc::Callback1<void, bool>& callback : connection_data->callbacks_) {
+ callback(false);
+ }
+ portal_base->CloseConnection(connection_data->id_);
+ return;
+ }
+
+ RTC_LOG(LS_INFO) << "Subscribed to sources signal.";
+}
+
+// static
+void XdgDesktopPortalBase::OnSourcesRequestResponseSignal(
+ GDBusConnection* connection,
+ const gchar* sender_name,
+ const gchar* object_path,
+ const gchar* interface_name,
+ const gchar* signal_name,
+ GVariant* parameters,
+ gpointer user_data) {
+ UserData* data = static_cast<UserData*>(user_data);
+ RTC_DCHECK(data);
+
+ auto* portal_base = data->GetXdgDesktopPortalBase();
+ RTC_CHECK(portal_base);
+
+ auto connection_data = portal_base->GetConnectionData(data->GetDataId());
+ RTC_CHECK(connection_data);
+
+ RTC_LOG(LS_INFO) << "Received sources signal from session.";
+
+ guint32 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.";
+ connection_data->portal_init_failed_ = true;
+ for (rtc::Callback1<void, bool>& callback : connection_data->callbacks_) {
+ callback(false);
+ }
+ portal_base->CloseConnection(connection_data->id_);
+ return;
+ }
+
+ portal_base->StartRequest(data);
+}
+
+void XdgDesktopPortalBase::StartRequest(UserData* data) {
+ auto connection_data = GetConnectionData(data->GetDataId());
+ RTC_CHECK(connection_data);
+
+ GVariantBuilder builder;
+ Scoped<gchar> 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()));
+
+ connection_data->start_handle_ =
+ PrepareSignalHandle(connection_data->connection_, variant_string.get());
+ connection_data->start_request_signal_id_ = SetupRequestResponseSignal(
+ connection_data->start_handle_, OnStartRequestResponseSignal, data);
+
+ // "Identifier for the application window", this is Wayland, so not "x11:...".
+ const gchar parent_window[] = "";
+
+ RTC_LOG(LS_INFO) << "Starting the screen cast session.";
+ g_dbus_proxy_call(
+ connection_data->proxy_, "Start",
+ g_variant_new("(osa{sv})", connection_data->session_handle_,
+ parent_window, &builder),
+ G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, /*cancellable=*/nullptr,
+ reinterpret_cast<GAsyncReadyCallback>(OnStartRequested), data);
+}
+
+// static
+void XdgDesktopPortalBase::OnStartRequested(GDBusConnection* connection,
+ GAsyncResult* result,
+ gpointer user_data) {
+ UserData* data = static_cast<UserData*>(user_data);
+ RTC_DCHECK(data);
+
+ auto* portal_base = data->GetXdgDesktopPortalBase();
+ RTC_CHECK(portal_base);
+
+ auto connection_data = portal_base->GetConnectionData(data->GetDataId());
+ RTC_CHECK(connection_data);
+
+ Scoped<GError> error;
+ Scoped<GVariant> variant(g_dbus_proxy_call_finish(connection_data->proxy_,
+ result, error.receive()));
+ if (!variant) {
+ RTC_LOG(LS_ERROR) << "Failed to start the screen cast session: "
+ << error->message;
+ connection_data->portal_init_failed_ = true;
+ for (rtc::Callback1<void, bool>& callback : connection_data->callbacks_) {
+ callback(false);
+ }
+ portal_base->CloseConnection(connection_data->id_);
+ return;
+ }
+
+ RTC_LOG(LS_INFO) << "Initializing the start of the screen cast session.";
+
+ gchar* handle = nullptr;
+ g_variant_get_child(variant.get(), 0, "o", &handle);
+ if (!handle) {
+ RTC_LOG(LS_ERROR)
+ << "Failed to initialize the start of the screen cast session.";
+ if (connection_data->start_request_signal_id_) {
+ g_dbus_connection_signal_unsubscribe(
+ connection, connection_data->start_request_signal_id_);
+ connection_data->start_request_signal_id_ = 0;
+ }
+ connection_data->portal_init_failed_ = true;
+ for (rtc::Callback1<void, bool>& callback : connection_data->callbacks_) {
+ callback(false);
+ }
+ portal_base->CloseConnection(connection_data->id_);
+ return;
+ }
+
+ RTC_LOG(LS_INFO) << "Subscribed to the start signal.";
+}
+
+// static
+void XdgDesktopPortalBase::OnStartRequestResponseSignal(
+ GDBusConnection* connection,
+ const gchar* sender_name,
+ const gchar* object_path,
+ const gchar* interface_name,
+ const gchar* signal_name,
+ GVariant* parameters,
+ gpointer user_data) {
+ UserData* data = static_cast<UserData*>(user_data);
+ RTC_DCHECK(data);
+
+ auto* portal_base = data->GetXdgDesktopPortalBase();
+ RTC_CHECK(portal_base);
+
+ auto connection_data = portal_base->GetConnectionData(data->GetDataId());
+ RTC_CHECK(connection_data);
+
+ RTC_LOG(LS_INFO) << "Start signal received.";
+ guint32 portal_response;
+ Scoped<GVariant> response_data;
+ Scoped<GVariantIter> iter;
+ 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.";
+ connection_data->portal_init_failed_ = true;
+ for (rtc::Callback1<void, bool>& callback : connection_data->callbacks_) {
+ callback(false);
+ }
+ portal_base->CloseConnection(connection_data->id_);
+ return;
+ }
+
+ // Array of PipeWire streams. See
+ // https://github.com/flatpak/xdg-desktop-portal/blob/master/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())) {
+ guint32 stream_id;
+ gint32 width;
+ gint32 height;
+ guint32 type;
+ Scoped<GVariant> options;
+
+ g_variant_get(variant.get(), "(u@a{sv})", &stream_id, options.receive());
+ RTC_DCHECK(options.get());
+
+ g_variant_lookup(options.get(), "size", "(ii)", &width, &height);
+
+ if (g_variant_lookup(options.get(), "source_type", "u", &type)) {
+ connection_data->capture_source_type_ =
+ static_cast<XdgDesktopPortalBase::CaptureSourceType>(type);
+ }
+ connection_data->desktop_size_.set(width, height);
+ connection_data->stream_id_ = stream_id;
+
+ break;
+ }
+ }
+
+ portal_base->OpenPipeWireRemote(data);
+}
+
+void XdgDesktopPortalBase::OpenPipeWireRemote(UserData* data) {
+ auto connection_data = GetConnectionData(data->GetDataId());
+ RTC_CHECK(connection_data);
+
+ if (!connection_data) {
+ for (rtc::Callback1<void, bool>& callback : connection_data->callbacks_) {
+ callback(false);
+ }
+ CloseConnection(connection_data->id_);
+ return;
+ }
+
+ 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(
+ connection_data->proxy_, "OpenPipeWireRemote",
+ g_variant_new("(oa{sv})", connection_data->session_handle_, &builder),
+ G_DBUS_CALL_FLAGS_NONE, /*timeout=*/-1, /*fd_list=*/nullptr,
+ /*cancellable=*/nullptr,
+ reinterpret_cast<GAsyncReadyCallback>(OnOpenPipeWireRemoteRequested),
+ data);
+}
+
+// static
+void XdgDesktopPortalBase::OnOpenPipeWireRemoteRequested(
+ GDBusConnection* connection,
+ GAsyncResult* result,
+ gpointer user_data) {
+ UserData* data = static_cast<UserData*>(user_data);
+ RTC_DCHECK(data);
+
+ auto* portal_base = data->GetXdgDesktopPortalBase();
+ RTC_CHECK(portal_base);
+
+ auto connection_data = portal_base->GetConnectionData(data->GetDataId());
+ RTC_CHECK(connection_data);
+
+ Scoped<GError> error;
+ Scoped<GUnixFDList> outlist;
+ Scoped<GVariant> variant(g_dbus_proxy_call_with_unix_fd_list_finish(
+ connection_data->proxy_, outlist.receive(), result, error.receive()));
+ if (!variant) {
+ RTC_LOG(LS_ERROR) << "Failed to open the PipeWire remote: "
+ << error->message;
+ connection_data->portal_init_failed_ = true;
+ for (rtc::Callback1<void, bool>& callback : connection_data->callbacks_) {
+ callback(false);
+ }
+ portal_base->CloseConnection(connection_data->id_);
+ return;
+ }
+
+ gint32 index;
+ g_variant_get(variant.get(), "(h)", &index);
+
+ if ((connection_data->pw_fd_ =
+ g_unix_fd_list_get(outlist.get(), index, error.receive())) == -1) {
+ RTC_LOG(LS_ERROR) << "Failed to get file descriptor from the list: "
+ << error->message;
+ connection_data->portal_init_failed_ = true;
+ for (rtc::Callback1<void, bool>& callback : connection_data->callbacks_) {
+ callback(false);
+ }
+ portal_base->CloseConnection(connection_data->id_);
+ return;
+ }
+
+ connection_data->pw_base_ =
+ std::make_unique<PipeWireBase>(connection_data->pw_fd_);
+
+ for (rtc::Callback1<void, bool>& callback : connection_data->callbacks_) {
+ callback(true);
+ }
+}
+
+void XdgDesktopPortalBase::CloseConnection(const absl::optional<int32_t>& id) {
+ auto connection_data = GetConnectionData(id);
+
+ if (!connection_data) {
+ return;
+ }
+
+ connection_data_map_.erase(connection_data->id_);
+}
+
+void XdgDesktopPortalBase::SetConnectionStreamingOnWeb(
+ const absl::optional<int32_t>& id) {
+ auto connection_data = GetConnectionData(id);
+
+ if (!connection_data) {
+ return;
+ }
+
+ connection_data->web_streaming_ = true;
+}
+
+} // namespace webrtc
diff --git a/modules/desktop_capture/linux/xdg_desktop_portal_base.h b/modules/desktop_capture/linux/xdg_desktop_portal_base.h
new file mode 100644
index 0000000..829cbf9
--- /dev/null
+++ b/modules/desktop_capture/linux/xdg_desktop_portal_base.h
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2020 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_XDG_DESKTOP_PORTAL_BASE_H_
+#define MODULES_DESKTOP_CAPTURE_LINUX_XDG_DESKTOP_PORTAL_BASE_H_
+
+#include <gio/gio.h>
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include "modules/desktop_capture/desktop_geometry.h"
+#include "modules/desktop_capture/linux/pipewire_base.h"
+
+#include "api/ref_counted_base.h"
+#include "api/scoped_refptr.h"
+#include "rtc_base/callback.h"
+#include "rtc_base/constructor_magic.h"
+
+namespace webrtc {
+
+struct ConnectionData;
+class UserData;
+
+class RTC_EXPORT XdgDesktopPortalBase : public rtc::RefCountedBase {
+ public:
+ enum class CaptureSourceType : uint32_t {
+ kScreen = 0b01,
+ kWindow = 0b10,
+ kAny = 0b11
+ };
+
+ XdgDesktopPortalBase();
+ ~XdgDesktopPortalBase();
+
+ static rtc::scoped_refptr<XdgDesktopPortalBase> CreateDefault();
+
+ // Initializes a screen sharing request for a web page identified with a
+ // id. This id is later used to associate it with ConnectionData structure
+ // where we keep all the necessary information about the DBus communication.
+ //
+ // This starts a series of DBus calls:
+ // 1) SessionRequest - requests a session, which will be associated with
+ // this screen sharing request, passing a handle for identification so
+ // we can watch for success/failure.
+ // 2) SourceRequest - requests what content we want to share on given
+ // session (either monitor, screen or both)
+ // 3) StartRequest - requests to
+ // start sharing, which in return will give us information about stream
+ // (stream id and resolution).
+ // 4) OpenPipeWireRemote - requests a file descriptor which we can use
+ // for initialization of PipeWire on the client side and start receiving
+ // content.
+ void InitPortal(rtc::Callback1<void, bool> callback,
+ XdgDesktopPortalBase::CaptureSourceType requested_type,
+ int32_t id);
+
+ void CloseConnection(const absl::optional<int32_t>& id);
+ void SetConnectionStreamingOnWeb(const absl::optional<int32_t>& id);
+
+ bool IsConnectionInitialized(const absl::optional<int32_t>& id) const;
+ bool IsConnectionStreamingOnWeb(const absl::optional<int32_t>& id) const;
+
+ PipeWireBase* GetPipeWireBase(const absl::optional<int32_t>& id) const;
+
+ // Current ID serves for the DesktopCapturerOption to identify portal call
+ // from the client itself so we can skip an additional call which would be
+ // made upon preview dialog confirmation (e.g. Chromium).
+ absl::optional<int32_t> CurrentConnectionId() const;
+ void SetCurrentConnectionId(const absl::optional<int32_t>& id);
+
+ ConnectionData* GetConnectionData(const absl::optional<int32_t>& id) const;
+
+ private:
+ // Methods are defined in the same order in which they are being called
+ guint SetupRequestResponseSignal(const gchar* object_path,
+ GDBusSignalCallback callback,
+ UserData* data);
+
+ static void OnProxyRequested(GObject* object,
+ GAsyncResult* result,
+ gpointer user_data);
+
+ static gchar* PrepareSignalHandle(GDBusConnection* connection,
+ const gchar* token);
+
+ void SessionRequest(UserData* data);
+ static void OnSessionRequested(GDBusConnection* connection,
+ GAsyncResult* result,
+ gpointer user_data);
+ static void OnSessionRequestResponseSignal(GDBusConnection* connection,
+ const gchar* sender_name,
+ const gchar* object_path,
+ const gchar* interface_name,
+ const gchar* signal_name,
+ GVariant* parameters,
+ gpointer user_data);
+
+ void SourcesRequest(UserData* data);
+ static void OnSourcesRequested(GDBusConnection* connection,
+ GAsyncResult* result,
+ gpointer user_data);
+ static void OnSourcesRequestResponseSignal(GDBusConnection* connection,
+ const gchar* sender_name,
+ const gchar* object_path,
+ const gchar* interface_name,
+ const gchar* signal_name,
+ GVariant* parameters,
+ gpointer user_data);
+
+ void StartRequest(UserData* data);
+ static void OnStartRequested(GDBusConnection* connection,
+ GAsyncResult* result,
+ gpointer user_data);
+ static void OnStartRequestResponseSignal(GDBusConnection* connection,
+ const gchar* sender_name,
+ const gchar* object_path,
+ const gchar* interface_name,
+ const gchar* signal_name,
+ GVariant* parameters,
+ gpointer user_data);
+ void OpenPipeWireRemote(UserData* data);
+ static void OnOpenPipeWireRemoteRequested(GDBusConnection* connection,
+ GAsyncResult* result,
+ gpointer user_data);
+
+ absl::optional<int32_t> current_connection_id_;
+ std::map<int32_t, std::unique_ptr<ConnectionData>> connection_data_map_;
+
+ RTC_DISALLOW_COPY_AND_ASSIGN(XdgDesktopPortalBase);
+};
+
+// This class represents each screen sharing request, which consists from a
+// series of DBus calls, where we need to remember the session handle and
+// parameters of the returned stream (id, resolution).
+struct RTC_EXPORT ConnectionData {
+ explicit ConnectionData(int32_t id);
+ ~ConnectionData();
+
+ bool operator=(const int32_t id) { return id_ == id; }
+
+ gint32 pw_fd_ = -1;
+
+ XdgDesktopPortalBase::CaptureSourceType capture_source_type_ =
+ XdgDesktopPortalBase::CaptureSourceType::kAny;
+ XdgDesktopPortalBase::CaptureSourceType requested_source_type_ =
+ XdgDesktopPortalBase::CaptureSourceType::kAny;
+
+ GDBusConnection* connection_ = nullptr;
+ GDBusProxy* proxy_ = nullptr;
+ gchar* portal_handle_ = nullptr;
+ gchar* session_handle_ = nullptr;
+ gchar* sources_handle_ = nullptr;
+ gchar* start_handle_ = nullptr;
+ guint session_request_signal_id_ = 0;
+ guint sources_request_signal_id_ = 0;
+ guint start_request_signal_id_ = 0;
+
+ DesktopSize desktop_size_ = {};
+ guint32 stream_id_ = 0;
+
+ int32_t id_;
+
+ bool already_used_ = false;
+ bool portal_init_failed_ = false;
+ bool web_streaming_ = false;
+ std::vector<rtc::Callback1<void, bool>> callbacks_;
+ rtc::Callback2<void, bool, int32_t> pw_callback_;
+
+ std::unique_ptr<PipeWireBase> pw_base_;
+};
+
+// Structure which is used as user_data property in GLib async calls, where we
+// need to pass two values. One is ID of the web page requesting screen sharing
+// and the other is pointer to the XdgDesktopPortalBase object.
+class UserData {
+ public:
+ UserData(int32_t id, XdgDesktopPortalBase* xdp) {
+ data_id_ = id;
+ xdp_ = xdp;
+ }
+
+ int32_t GetDataId() const { return data_id_; }
+ XdgDesktopPortalBase* GetXdgDesktopPortalBase() const { return xdp_; }
+
+ private:
+ int32_t data_id_ = 0;
+ XdgDesktopPortalBase* xdp_ = nullptr;
+};
+
+} // namespace webrtc
+
+#endif // MODULES_DESKTOP_CAPTURE_LINUX_XDG_DESKTOP_PORTAL_BASE_H_
diff --git a/modules/desktop_capture/screen_capturer_linux.cc b/modules/desktop_capture/screen_capturer_linux.cc
index 82dbae4..71a81b9 100644
--- a/modules/desktop_capture/screen_capturer_linux.cc
+++ b/modules/desktop_capture/screen_capturer_linux.cc
@@ -14,7 +14,7 @@
#include "modules/desktop_capture/desktop_capturer.h"
#if defined(WEBRTC_USE_PIPEWIRE)
-#include "modules/desktop_capture/linux/screen_capturer_pipewire.h"
+#include "modules/desktop_capture/linux/base_capturer_pipewire.h"
#endif // defined(WEBRTC_USE_PIPEWIRE)
#if defined(WEBRTC_USE_X11)
@@ -28,7 +28,7 @@
const DesktopCaptureOptions& options) {
#if defined(WEBRTC_USE_PIPEWIRE)
if (options.allow_pipewire() && DesktopCapturer::IsRunningUnderWayland()) {
- return ScreenCapturerPipeWire::CreateRawScreenCapturer(options);
+ return BaseCapturerPipeWire::CreateRawScreenCapturer(options);
}
#endif // defined(WEBRTC_USE_PIPEWIRE)
diff --git a/modules/desktop_capture/window_capturer_linux.cc b/modules/desktop_capture/window_capturer_linux.cc
index 41dbf83..c0b6154 100644
--- a/modules/desktop_capture/window_capturer_linux.cc
+++ b/modules/desktop_capture/window_capturer_linux.cc
@@ -14,7 +14,7 @@
#include "modules/desktop_capture/desktop_capturer.h"
#if defined(WEBRTC_USE_PIPEWIRE)
-#include "modules/desktop_capture/linux/window_capturer_pipewire.h"
+#include "modules/desktop_capture/linux/base_capturer_pipewire.h"
#endif // defined(WEBRTC_USE_PIPEWIRE)
#if defined(WEBRTC_USE_X11)
@@ -28,7 +28,7 @@
const DesktopCaptureOptions& options) {
#if defined(WEBRTC_USE_PIPEWIRE)
if (options.allow_pipewire() && DesktopCapturer::IsRunningUnderWayland()) {
- return WindowCapturerPipeWire::CreateRawWindowCapturer(options);
+ return BaseCapturerPipeWire::CreateRawWindowCapturer(options);
}
#endif // defined(WEBRTC_USE_PIPEWIRE)